add ecdsa key support and enable 4,092 RSA

* apcssh: add descriptive error when required file(s) not passed
* create: dont create key+cert file when key isn't supported by NMC2
* config: fix usage messages re: key types
* p15 files: dont generate key+cert when it isn't needed (aka NMC2 doesn't support key)
* pkcs15: pre-calculate envelope when making the p15 struct
* pkcs15: omit key ID 8 & 9 from EC keys
* pkcs15: update key decode logic
* pkcs15: add key type value for easy determination of compatibility
* pkcs15: add ec key support
* pkcs15: separate functions for key and key+cert p15 files
* update README
see: https://github.com/gregtwallace/apc-p15-tool/issues/6
This commit is contained in:
Greg T. Wallace 2024-07-09 19:05:14 -04:00
parent b8e9a23386
commit 40eca754e0
12 changed files with 508 additions and 266 deletions

View file

@ -58,23 +58,36 @@ and licensed under the GPL-3.0 license.
Both NMC2 and NMC3 devices should be fully supported. However, I have one Both NMC2 and NMC3 devices should be fully supported. However, I have one
NMC2 device in a home lab and have no way to guarantee success in all cases. NMC2 device in a home lab and have no way to guarantee success in all cases.
Only RSA 1,024, 2,048, and 3,072 bit keys are accepted. 1,024 bit RSA is no ### Key Types and Sizes
longer considered completely secure; avoid keys of this size if possible. Most
(all?) public ACME services won't accept keys of this size anyway.
NMC2 does not officially support the 3,072 bit key size, however, it works fine NMC2:
on my NMC2. If you use this size and it doesn't work on your NMC2, try a 2,048 - RSA 1,024, 2,048, 3,072* bit lengths.
bit key instead. Later versions of the NMC3 firmware support RSA 4,096 and
ECDSA keys, but this tool does not. ECDSA was not included in APC's proprietary NMC3:
tool, and as such I have no way to generate files to reverse engineer. - RSA 1,024, 2,048, 3,072, and 4,092 bit lengths.
- ECDSA curves P-256, P-384, and P-521.
* 3,072 bit length is not officially supported by my NMC2, but appears to work
fine.
1,024 bit RSA is no longer considered completely secure; avoid keys of
this size if possible. Most (all?) public ACME services won't accept keys
of this size anyway.
### General Troubleshooting
My setup (and therefore the testing setup) is: My setup (and therefore the testing setup) is:
- APC Smart-UPS 1500VA RM 2U SUA1500RM2U (Firmware Revision 667.18.D) - APC Smart-UPS 1500VA RM 2U SUA1500RM2U (Firmware Revision 667.18.D)
- AP9631 NMC2 Hardware Revision 05 running AOS v7.1.2 and Boot Monitor - AP9631 NMC2 Hardware Revision 05 running AOS v7.1.2 and Boot Monitor
v1.0.9. v1.0.9.
If you have problems, please post the log in an issue and I can try to fix it If you have trouble, your first step should be to update your NMC's firmware.
but it may be difficult without your particular hardware to test with. Many issues with this tool will be resolved simply by updating to the newest
firmware.
If you have a problem after that, please post the log in an issue and I can
try to fix it but it may be difficult without your particular hardware to
test with.
In particular, if you are experiencing `ssh: handshake failed:` first try In particular, if you are experiencing `ssh: handshake failed:` first try
using the `--insecurecipher` flag. If this works, you should upgrade your using the `--insecurecipher` flag. If this works, you should upgrade your

View file

@ -1,10 +1,13 @@
package apcssh package apcssh
import ( import (
"errors"
"fmt" "fmt"
"strings" "strings"
) )
var errSSLMissingData = errors.New("apcssh: ssl cert install: cant install nil data (unsupported key/nmc version/nmc firmware combo?)")
// InstallSSLCert installs the specified p15 key and p15 cert files on the // InstallSSLCert installs the specified p15 key and p15 cert files on the
// UPS. It has logic to deduce if the NMC is a newer version (e.g., NMC3 with // UPS. It has logic to deduce if the NMC is a newer version (e.g., NMC3 with
// newer firmware) and acts accordingly. // newer firmware) and acts accordingly.
@ -29,6 +32,11 @@ func (cli *Client) InstallSSLCert(keyP15 []byte, certPem []byte, keyCertP15 []by
// installSSLCertModern installs the SSL key and certificate using the UPS built-in // installSSLCertModern installs the SSL key and certificate using the UPS built-in
// command `ssl`. This command is not present on older devices (e.g., NMC2) or firmwares. // command `ssl`. This command is not present on older devices (e.g., NMC2) or firmwares.
func (cli *Client) installSSLCertModern(keyP15 []byte, certPem []byte) error { func (cli *Client) installSSLCertModern(keyP15 []byte, certPem []byte) error {
// fail if required data isn't present
if keyP15 == nil || len(keyP15) <= 0 || certPem == nil || len(certPem) <= 0 {
return errSSLMissingData
}
// upload the key P15 file // upload the key P15 file
err := cli.UploadSCP("/ssl/nmc.key", keyP15, 0600) err := cli.UploadSCP("/ssl/nmc.key", keyP15, 0600)
if err != nil { if err != nil {
@ -63,6 +71,11 @@ func (cli *Client) installSSLCertModern(keyP15 []byte, certPem []byte) error {
// them to a .p15 file on the UPS. This is used for older devices (e.g., NMC2) and // them to a .p15 file on the UPS. This is used for older devices (e.g., NMC2) and
// firmwares that do not support the `ssl` command. // firmwares that do not support the `ssl` command.
func (cli *Client) installSSLCertLegacy(keyCertP15 []byte) error { func (cli *Client) installSSLCertLegacy(keyCertP15 []byte) error {
// fail if required data isn't present
if keyCertP15 == nil || len(keyCertP15) <= 0 {
return errSSLMissingData
}
// upload/install keyCert P15 file // upload/install keyCert P15 file
err := cli.UploadSCP("/ssl/defaultcert.p15", keyCertP15, 0600) err := cli.UploadSCP("/ssl/defaultcert.p15", keyCertP15, 0600)
if err != nil { if err != nil {

View file

@ -51,11 +51,14 @@ func (app *app) cmdCreate(_ context.Context, args []string) error {
} }
app.stdLogger.Printf("create: apc p15 key file %s written to disk", keyFileName) app.stdLogger.Printf("create: apc p15 key file %s written to disk", keyFileName)
err = os.WriteFile(keyCertFileName, apcKeyCertFile, 0600) // skip key+cert if it wasn't generated
if err != nil { if len(apcKeyCertFile) > 0 {
return fmt.Errorf("create: failed to write apc p15 key+cert file (%s)", err) err = os.WriteFile(keyCertFileName, apcKeyCertFile, 0600)
if err != nil {
return fmt.Errorf("create: failed to write apc p15 key+cert file (%s)", err)
}
app.stdLogger.Printf("create: apc p15 key+cert file %s written to disk", keyCertFileName)
} }
app.stdLogger.Printf("create: apc p15 key+cert file %s written to disk", keyCertFileName)
// if debug, write additional debug files (b64 format to make copy/paste into asn1 decoder // if debug, write additional debug files (b64 format to make copy/paste into asn1 decoder
// easy to do e.g., https://lapo.it/asn1js) // easy to do e.g., https://lapo.it/asn1js)
@ -67,19 +70,22 @@ func (app *app) cmdCreate(_ context.Context, args []string) error {
} }
app.debugLogger.Printf("create: apc p15 key file %s written to disk", keyFileNameDebug) app.debugLogger.Printf("create: apc p15 key file %s written to disk", keyFileNameDebug)
keyCertFileNameDebug := keyCertFileName + ".noheader.b64" // skip key+cert if it wasn't generated
err = os.WriteFile(keyCertFileNameDebug, []byte(base64.StdEncoding.EncodeToString(apcKeyCertFile[apcHeaderLen:])), 0600) if len(apcKeyCertFile) > 0 {
if err != nil { keyCertFileNameDebug := keyCertFileName + ".noheader.b64"
return fmt.Errorf("create: failed to write apc p15 key+cert file (%s)", err) err = os.WriteFile(keyCertFileNameDebug, []byte(base64.StdEncoding.EncodeToString(apcKeyCertFile[apcHeaderLen:])), 0600)
} if err != nil {
app.debugLogger.Printf("create: apc p15 key+cert file %s written to disk", keyCertFileNameDebug) return fmt.Errorf("create: failed to write apc p15 key+cert file (%s)", err)
}
app.debugLogger.Printf("create: apc p15 key+cert file %s written to disk", keyCertFileNameDebug)
keyCertFileNameHeaderDebug := keyCertFileName + ".header.b64" keyCertFileNameHeaderDebug := keyCertFileName + ".header.b64"
err = os.WriteFile(keyCertFileNameHeaderDebug, []byte(base64.StdEncoding.EncodeToString(apcKeyCertFile[:apcHeaderLen])), 0600) err = os.WriteFile(keyCertFileNameHeaderDebug, []byte(base64.StdEncoding.EncodeToString(apcKeyCertFile[:apcHeaderLen])), 0600)
if err != nil { if err != nil {
return fmt.Errorf("create: failed to write apc p15 key+cert file (%s)", err) return fmt.Errorf("create: failed to write apc p15 key+cert file (%s)", err)
}
app.debugLogger.Printf("create: apc p15 key+cert file header %s written to disk", keyCertFileNameHeaderDebug)
} }
app.debugLogger.Printf("create: apc p15 key+cert file header %s written to disk", keyCertFileNameHeaderDebug)
} }

View file

@ -68,9 +68,9 @@ func (app *app) getConfig(args []string) error {
// create -- subcommand // create -- subcommand
createFlags := ff.NewFlagSet("create").SetParent(rootFlags) createFlags := ff.NewFlagSet("create").SetParent(rootFlags)
cfg.create.keyPemFilePath = createFlags.StringLong("keyfile", "", "path and filename of the rsa-1024 or rsa-2048 key in pem format") cfg.create.keyPemFilePath = createFlags.StringLong("keyfile", "", "path and filename of the key in pem format")
cfg.create.certPemFilePath = createFlags.StringLong("certfile", "", "path and filename of the certificate in pem format") cfg.create.certPemFilePath = createFlags.StringLong("certfile", "", "path and filename of the certificate in pem format")
cfg.create.keyPem = createFlags.StringLong("keypem", "", "string of the rsa-1024 or rsa-2048 key in pem format") cfg.create.keyPem = createFlags.StringLong("keypem", "", "string of the key in pem format")
cfg.create.certPem = createFlags.StringLong("certpem", "", "string of the certificate in pem format") cfg.create.certPem = createFlags.StringLong("certpem", "", "string of the certificate in pem format")
cfg.create.outFilePath = createFlags.StringLong("outfile", createDefaultOutFilePath, "path and filename to write the key+cert p15 file to") cfg.create.outFilePath = createFlags.StringLong("outfile", createDefaultOutFilePath, "path and filename to write the key+cert p15 file to")
cfg.create.outKeyFilePath = createFlags.StringLong("outkeyfile", createDefaultOutKeyFilePath, "path and filename to write the key p15 file to") cfg.create.outKeyFilePath = createFlags.StringLong("outkeyfile", createDefaultOutKeyFilePath, "path and filename to write the key p15 file to")
@ -88,9 +88,9 @@ func (app *app) getConfig(args []string) error {
// install -- subcommand // install -- subcommand
installFlags := ff.NewFlagSet("install").SetParent(rootFlags) installFlags := ff.NewFlagSet("install").SetParent(rootFlags)
cfg.install.keyPemFilePath = installFlags.StringLong("keyfile", "", "path and filename of the rsa-1024 or rsa-2048 key in pem format") cfg.install.keyPemFilePath = installFlags.StringLong("keyfile", "", "path and filename of the key in pem format")
cfg.install.certPemFilePath = installFlags.StringLong("certfile", "", "path and filename of the certificate in pem format") cfg.install.certPemFilePath = installFlags.StringLong("certfile", "", "path and filename of the certificate in pem format")
cfg.install.keyPem = installFlags.StringLong("keypem", "", "string of the rsa-1024 or rsa-2048 key in pem format") cfg.install.keyPem = installFlags.StringLong("keypem", "", "string of the key in pem format")
cfg.install.certPem = installFlags.StringLong("certpem", "", "string of the certificate in pem format") cfg.install.certPem = installFlags.StringLong("certpem", "", "string of the certificate in pem format")
cfg.install.hostAndPort = installFlags.StringLong("apchost", "", "hostname:port of the apc ups to install the certificate on") cfg.install.hostAndPort = installFlags.StringLong("apchost", "", "hostname:port of the apc ups to install the certificate on")
cfg.install.fingerprint = installFlags.StringLong("fingerprint", "", "the SHA256 fingerprint value of the ups' ssh server") cfg.install.fingerprint = installFlags.StringLong("fingerprint", "", "the SHA256 fingerprint value of the ups' ssh server")

View file

@ -3,13 +3,22 @@ package app
import ( import (
"apc-p15-tool/pkg/pkcs15" "apc-p15-tool/pkg/pkcs15"
"fmt" "fmt"
"slices"
) )
// pemToAPCP15 reads the specified pem files and returns the apc p15 files (both a // list of keys supported by the NMC2
// p15 file with just the private key, and also a p15 file with both the private key var nmc2SupportedKeyTypes = []pkcs15.KeyType{
// and certificate). The key+cert file includes the required APC header, prepended. pkcs15.KeyTypeRSA1024,
pkcs15.KeyTypeRSA2048,
pkcs15.KeyTypeRSA3072, // officially not supported but works
}
// pemToAPCP15 reads the specified pem files and returns the apc p15 file(s). If the
// key type of the key is not supported by NMC2, the combined key+cert file is not
// generated and nil is returned instead for that file. If the key IS supported by
// NMC2, the key+cert file is generated and the proper header is prepended.
func (app *app) pemToAPCP15(keyPem, certPem []byte, parentCmdName string) (keyFile []byte, apcKeyCertFile []byte, err error) { func (app *app) pemToAPCP15(keyPem, certPem []byte, parentCmdName string) (keyFile []byte, apcKeyCertFile []byte, err error) {
app.stdLogger.Printf("%s: making apc p15 file from pem", parentCmdName) app.stdLogger.Printf("%s: making apc p15 file(s) content from pem", parentCmdName)
// make p15 struct // make p15 struct
p15, err := pkcs15.ParsePEMToPKCS15(keyPem, certPem) p15, err := pkcs15.ParsePEMToPKCS15(keyPem, certPem)
@ -17,24 +26,40 @@ func (app *app) pemToAPCP15(keyPem, certPem []byte, parentCmdName string) (keyFi
return nil, nil, fmt.Errorf("%s: failed to parse pem files (%w)", parentCmdName, err) return nil, nil, fmt.Errorf("%s: failed to parse pem files (%w)", parentCmdName, err)
} }
app.stdLogger.Printf("%s: successfully loaded pem files", parentCmdName) app.stdLogger.Printf("%s: successfully parsed pem files", parentCmdName)
// make file bytes // make key file (always)
keyCertFile, keyFile, err := p15.ToP15Files() keyFile, err = p15.ToP15Key()
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("%s: failed to make p15 file (%w)", parentCmdName, err) return nil, nil, fmt.Errorf("%s: failed to make p15 key file (%w)", parentCmdName, err)
} }
// make header for file bytes app.stdLogger.Printf("%s: successfully generated p15 key file content", parentCmdName)
apcHeader, err := makeFileHeader(keyCertFile)
if err != nil { // check key type for compat with NMC2
return nil, nil, fmt.Errorf("%s: failed to make p15 file header (%w)", parentCmdName, err) if slices.Contains(nmc2SupportedKeyTypes, p15.KeyType()) {
app.stdLogger.Printf("%s: key type is supported by NMC2, generating p15 key+cert file content...", parentCmdName)
// make file bytes
keyCertFile, err := p15.ToP15KeyCert()
if err != nil {
return nil, nil, fmt.Errorf("%s: failed to make p15 key+cert file content (%w)", parentCmdName, err)
}
// make header for file bytes
apcHeader, err := makeFileHeader(keyCertFile)
if err != nil {
return nil, nil, fmt.Errorf("%s: failed to make p15 key+cert file header (%w)", parentCmdName, err)
}
// combine header with file
apcKeyCertFile = append(apcHeader, keyCertFile...)
} else {
// NMC2 unsupported
app.stdLogger.Printf("%s: key type is not supported by NMC2, skipping p15 key+cert file content", parentCmdName)
} }
// combine header with file app.stdLogger.Printf("%s: apc p15 file(s) data succesfully generated", parentCmdName)
apcKeyCertFile = append(apcHeader, keyCertFile...)
app.stdLogger.Printf("%s: apc p15 file data succesfully generated", parentCmdName)
return keyFile, apcKeyCertFile, nil return keyFile, apcKeyCertFile, nil
} }

View file

@ -21,14 +21,19 @@ const (
apcKEKIterations = 5000 apcKEKIterations = 5000
) )
// encryptedKeyEnvelope encrypts p15's rsa private key using the algorithms and // encryptedKeyEnvelope encrypts p15's private key using the algorithms and
// params expected in the APC file. Salt values are always random. // params expected in the APC file.
func (p15 *pkcs15KeyCert) encryptedKeyEnvelope() ([]byte, error) { func (p15 *pkcs15KeyCert) computeEncryptedKeyEnvelope() error {
// if computation already performed, this is a no-op (keep existing envelope)
if p15.envelopedPrivateKey != nil && len(p15.envelopedPrivateKey) != 0 {
return nil
}
// calculate values for the object // calculate values for the object
kekSalt := make([]byte, 8) kekSalt := make([]byte, 8)
_, err := rand.Read(kekSalt) _, err := rand.Read(kekSalt)
if err != nil { if err != nil {
return nil, err return err
} }
// kek hash alg // kek hash alg
@ -42,7 +47,7 @@ func (p15 *pkcs15KeyCert) encryptedKeyEnvelope() ([]byte, error) {
// make DES cipher from KEK for CEK // make DES cipher from KEK for CEK
cekDesCipher, err := des.NewTripleDESCipher(kek) cekDesCipher, err := des.NewTripleDESCipher(kek)
if err != nil { if err != nil {
return nil, err return err
} }
// cek (16 bytes for authEnc128) -- see: rfc3211 // cek (16 bytes for authEnc128) -- see: rfc3211
@ -50,7 +55,7 @@ func (p15 *pkcs15KeyCert) encryptedKeyEnvelope() ([]byte, error) {
cek := make([]byte, cekLen) cek := make([]byte, cekLen)
_, err = rand.Read(cek) _, err = rand.Read(cek)
if err != nil { if err != nil {
return nil, err return err
} }
// LEN + Check Val [3] // LEN + Check Val [3]
@ -71,7 +76,7 @@ func (p15 *pkcs15KeyCert) encryptedKeyEnvelope() ([]byte, error) {
cekPadding := make([]byte, cekPadLen) cekPadding := make([]byte, cekPadLen)
_, err = rand.Read(cekPadding) _, err = rand.Read(cekPadding)
if err != nil { if err != nil {
return nil, err return err
} }
wrappedCEK = append(wrappedCEK, cekPadding...) wrappedCEK = append(wrappedCEK, cekPadding...)
@ -80,7 +85,7 @@ func (p15 *pkcs15KeyCert) encryptedKeyEnvelope() ([]byte, error) {
cekEncryptSalt := make([]byte, 8) cekEncryptSalt := make([]byte, 8)
_, err = rand.Read(cekEncryptSalt) _, err = rand.Read(cekEncryptSalt)
if err != nil { if err != nil {
return nil, err return err
} }
cekEncrypter := cipher.NewCBCEncrypter(cekDesCipher, cekEncryptSalt) cekEncrypter := cipher.NewCBCEncrypter(cekDesCipher, cekEncryptSalt)
@ -94,13 +99,13 @@ func (p15 *pkcs15KeyCert) encryptedKeyEnvelope() ([]byte, error) {
contentEncSalt := make([]byte, 8) contentEncSalt := make([]byte, 8)
_, err = rand.Read(contentEncSalt) _, err = rand.Read(contentEncSalt)
if err != nil { if err != nil {
return nil, err return err
} }
contentEncryptKey := pbkdf2.Key(cek, []byte("encryption"), 1, 24, sha1.New) contentEncryptKey := pbkdf2.Key(cek, []byte("encryption"), 1, 24, sha1.New)
contentDesCipher, err := des.NewTripleDESCipher(contentEncryptKey) contentDesCipher, err := des.NewTripleDESCipher(contentEncryptKey)
if err != nil { if err != nil {
return nil, err return err
} }
// envelope content (that will be encrypted) // envelope content (that will be encrypted)
@ -151,7 +156,7 @@ func (p15 *pkcs15KeyCert) encryptedKeyEnvelope() ([]byte, error) {
// make MAC // make MAC
_, err = macHasher.Write(hashMe) _, err = macHasher.Write(hashMe)
if err != nil { if err != nil {
return nil, err return err
} }
mac := macHasher.Sum(nil) mac := macHasher.Sum(nil)
@ -218,5 +223,7 @@ func (p15 *pkcs15KeyCert) encryptedKeyEnvelope() ([]byte, error) {
finalEnv = append(finalEnv, envelope[i]...) finalEnv = append(finalEnv, envelope[i]...)
} }
return finalEnv, nil // set p15 struct envelope
p15.envelopedPrivateKey = finalEnv
return nil
} }

View file

@ -2,6 +2,7 @@ package pkcs15
import ( import (
"apc-p15-tool/pkg/tools/asn1obj" "apc-p15-tool/pkg/tools/asn1obj"
"crypto/ecdsa"
"crypto/rsa" "crypto/rsa"
"crypto/sha1" "crypto/sha1"
"encoding/binary" "encoding/binary"
@ -119,9 +120,13 @@ func (p15 *pkcs15KeyCert) keyIdInt8() []byte {
nBytes := privKey.N.Bytes() nBytes := privKey.N.Bytes()
keyIdVal = nBytes[len(nBytes)-8:] keyIdVal = nBytes[len(nBytes)-8:]
case *ecdsa.PrivateKey:
// don't use this key id, leave empty
return nil
default: default:
// panic if non-RSA key // panic if unexpected key type
panic("key id 8 for non-rsa key is unexpected and unsupported") panic("key id 8 for key is unexpected and unsupported")
} }
// object to return // object to return
@ -181,33 +186,13 @@ func (p15 *pkcs15KeyCert) keyIdInt9() []byte {
e := big.NewInt(int64(privKey.PublicKey.E)) e := big.NewInt(int64(privKey.PublicKey.E))
publicKeyPacket = append(publicKeyPacket, bigIntToMpi(e)...) publicKeyPacket = append(publicKeyPacket, bigIntToMpi(e)...)
// case *ecdsa.PrivateKey: case *ecdsa.PrivateKey:
// // A one-octet number denoting the public-key algorithm of this key. // don't use this key id, leave empty
// // 19 - ECDSA public key algorithm (see rfc 6637 s. 5) return nil
// publicKeyPacket = append(publicKeyPacket, uint8(19))
// // Algorithm-Specific Fields for ECDSA public keys (see rfc 6637 s. 11 table)
// // This is a length byte followed by the curve ID (length is the number of bytes the curve ID uses)
// switch privKey.Curve.Params().Name {
// case "P-256":
// // 1.2.840.10045.3.1.7 8 2A 86 48 CE 3D 03 01 07 NIST curve P-256
// publicKeyPacket = append(publicKeyPacket, byte(8))
// hex, _ := hex.DecodeString("2A8648CE3D030107")
// publicKeyPacket = append(publicKeyPacket, hex...)
// case "P-384":
// // 1.3.132.0.34 5 2B 81 04 00 22 NIST curve P-384
// publicKeyPacket = append(publicKeyPacket, byte(5))
// hex, _ := hex.DecodeString("2B81040022")
// publicKeyPacket = append(publicKeyPacket, hex...)
// default:
// panic(fmt.Sprintf("key id 9 for ecdsa key curve %s is unexpected and unsupported", privKey.Curve.Params().Name))
// }
default: default:
// panic if non-RSA key // panic if unexpected key type
panic("key id 9 for non-rsa key is unexpected and unsupported") panic("key id 9 for key is unexpected and unsupported")
} }
// Assemble the V4 byte array that will be hashed // Assemble the V4 byte array that will be hashed

View file

@ -2,6 +2,7 @@ package pkcs15
import ( import (
"crypto" "crypto"
"crypto/ecdsa"
"crypto/rsa" "crypto/rsa"
"crypto/tls" "crypto/tls"
"crypto/x509" "crypto/x509"
@ -9,21 +10,27 @@ import (
"errors" "errors"
"fmt" "fmt"
"reflect" "reflect"
"slices"
) )
var ( var (
errPemKeyBadBlock = errors.New("pkcs15: pem key: failed to decode pem block") errPemKeyBadBlock = errors.New("pkcs15: pem key: failed to decode pem block")
errPemKeyFailedToParse = errors.New("pkcs15: pem key: failed to parse key") errPemKeyFailedToParse = errors.New("pkcs15: pem key: failed to parse key")
errPemKeyWrongBlockType = errors.New("pkcs15: pem key: unsupported pem block type (only pkcs1 and pkcs8 supported)") errPemKeyWrongBlockType = errors.New("pkcs15: pem key: unsupported pem block type")
errPemKeyWrongType = errors.New("pkcs15: pem key: unsupported key type (only rsa 1,024, 2,048, and 3,072 supported)") errKeyWrongType = errors.New("pkcs15: pem key: unsupported key type")
errPemCertBadBlock = errors.New("pkcs15: pem cert: failed to decode pem block") errPemCertBadBlock = errors.New("pkcs15: pem cert: failed to decode pem block")
errPemCertFailedToParse = errors.New("pkcs15: pem cert: failed to parse cert") errPemCertFailedToParse = errors.New("pkcs15: pem cert: failed to parse cert")
) )
var (
supportedRSASizes = []int{1024, 2048, 3072, 4096}
supportedECDSACurves = []string{"P-256", "P-384", "P-521"}
)
// pemKeyDecode attempts to decode a pem encoded byte slice and then attempts // pemKeyDecode attempts to decode a pem encoded byte slice and then attempts
// to parse an RSA private key from the decoded pem block. an error is returned // to parse a private key from the decoded pem block. an error is returned
// if any of these steps fail OR if the key is not RSA and of bitlen 1,024 or 2,048 // if any of these steps fail OR if the key is not supported.
func pemKeyDecode(keyPem []byte) (crypto.PrivateKey, error) { func pemKeyDecode(keyPem []byte) (crypto.PrivateKey, error) {
// decode // decode
pemBlock, _ := pem.Decode([]byte(keyPem)) pemBlock, _ := pem.Decode([]byte(keyPem))
@ -47,28 +54,27 @@ func pemKeyDecode(keyPem []byte) (crypto.PrivateKey, error) {
return nil, fmt.Errorf("pkcs15: pem key: failed sanity check (%s)", err) return nil, fmt.Errorf("pkcs15: pem key: failed sanity check (%s)", err)
} }
// verify proper bitlen // verify supported rsa bitlen
if rsaKey.N.BitLen() != 1024 && rsaKey.N.BitLen() != 2048 && rsaKey.N.BitLen() != 3072 { if !slices.Contains(supportedRSASizes, rsaKey.N.BitLen()) {
return nil, errPemKeyWrongType return nil, errKeyWrongType
} }
// good to go // good to go
privateKey = rsaKey privateKey = rsaKey
// case "EC PRIVATE KEY": // SEC1, ASN.1 case "EC PRIVATE KEY": // SEC1, ASN.1
// var ecdKey *ecdsa.PrivateKey ecdKey, err := x509.ParseECPrivateKey(pemBlock.Bytes)
// ecdKey, err := x509.ParseECPrivateKey(pemBlock.Bytes) if err != nil {
// if err != nil { return nil, errPemKeyFailedToParse
// return nil, errPemKeyFailedToParse }
// }
// // verify acceptable curve name // verify supported curve name
// if ecdKey.Curve.Params().Name != "P-256" && ecdKey.Curve.Params().Name != "P-384" { if !slices.Contains(supportedECDSACurves, ecdKey.Curve.Params().Name) {
// return nil, errPemKeyWrongType return nil, errKeyWrongType
// } }
// // good to go // good to go
// privateKey = ecdKey privateKey = ecdKey
case "PRIVATE KEY": // PKCS8 case "PRIVATE KEY": // PKCS8
pkcs8Key, err := x509.ParsePKCS8PrivateKey(pemBlock.Bytes) pkcs8Key, err := x509.ParsePKCS8PrivateKey(pemBlock.Bytes)
@ -84,25 +90,25 @@ func pemKeyDecode(keyPem []byte) (crypto.PrivateKey, error) {
return nil, fmt.Errorf("pkcs15: pem key: failed sanity check (%s)", err) return nil, fmt.Errorf("pkcs15: pem key: failed sanity check (%s)", err)
} }
// verify proper bitlen // verify supported rsa bitlen
if pkcs8Key.N.BitLen() != 1024 && pkcs8Key.N.BitLen() != 2048 && pkcs8Key.N.BitLen() != 3072 { if !slices.Contains(supportedRSASizes, pkcs8Key.N.BitLen()) {
return nil, errPemKeyWrongType return nil, errKeyWrongType
} }
// good to go // good to go
privateKey = pkcs8Key privateKey = pkcs8Key
// case *ecdsa.PrivateKey: case *ecdsa.PrivateKey:
// // verify acceptable curve name // verify supported curve name
// if pkcs8Key.Curve.Params().Name != "P-256" && pkcs8Key.Curve.Params().Name != "P-384" { if !slices.Contains(supportedECDSACurves, pkcs8Key.Curve.Params().Name) {
// return nil, errPemKeyWrongType return nil, errKeyWrongType
// } }
// // good to go // good to go
// privateKey = pkcs8Key privateKey = pkcs8Key
default: default:
return nil, errPemKeyWrongType return nil, errKeyWrongType
} }
default: default:

View file

@ -2,6 +2,8 @@ package pkcs15
import ( import (
"crypto" "crypto"
"crypto/ecdsa"
"crypto/rsa"
"crypto/x509" "crypto/x509"
) )
@ -10,6 +12,59 @@ import (
type pkcs15KeyCert struct { type pkcs15KeyCert struct {
key crypto.PrivateKey key crypto.PrivateKey
cert *x509.Certificate cert *x509.Certificate
// store the encrypted enveloped Private Key for re-use
envelopedPrivateKey []byte
}
// KeyType is used by consumers to check for compatibility
type KeyType int
const (
KeyTypeRSA1024 KeyType = iota
KeyTypeRSA2048
KeyTypeRSA3072
KeyTypeRSA4096
KeyTypeECP256
KeyTypeECP384
KeyTypeECP521
KeyTypeUnknown
)
// KeyType returns the private key type
func (p15 *pkcs15KeyCert) KeyType() KeyType {
switch pKey := p15.key.(type) {
case *rsa.PrivateKey:
switch pKey.N.BitLen() {
case 1024:
return KeyTypeRSA1024
case 2048:
return KeyTypeRSA2048
case 3072:
return KeyTypeRSA3072
case 4096:
return KeyTypeRSA4096
default:
}
case *ecdsa.PrivateKey:
switch pKey.Curve.Params().Name {
case "P-256":
return KeyTypeECP256
case "P-384":
return KeyTypeECP384
case "P-521":
return KeyTypeECP521
default:
}
default:
}
return KeyTypeUnknown
} }
// ParsePEMToPKCS15 parses the provide pem files to a pkcs15 struct; it also does some // ParsePEMToPKCS15 parses the provide pem files to a pkcs15 struct; it also does some
@ -27,10 +82,17 @@ func ParsePEMToPKCS15(keyPem, certPem []byte) (*pkcs15KeyCert, error) {
return nil, err return nil, err
} }
// create p15 struct
p15 := &pkcs15KeyCert{ p15 := &pkcs15KeyCert{
key: key, key: key,
cert: cert, cert: cert,
} }
// pre-calculate encrypted envelope
err = p15.computeEncryptedKeyEnvelope()
if err != nil {
return nil, err
}
return p15, nil return p15, nil
} }

View file

@ -2,8 +2,10 @@ package pkcs15
import ( import (
"apc-p15-tool/pkg/tools/asn1obj" "apc-p15-tool/pkg/tools/asn1obj"
"crypto/ecdsa"
"crypto/rsa" "crypto/rsa"
"encoding/asn1" "encoding/asn1"
"fmt"
"math/big" "math/big"
) )
@ -13,39 +15,87 @@ const (
// toP15KeyCert creates a P15 file with both the private key and certificate, mirroring the // toP15KeyCert creates a P15 file with both the private key and certificate, mirroring the
// final p15 file an APC UPS expects (though without the header) // final p15 file an APC UPS expects (though without the header)
func (p15 *pkcs15KeyCert) toP15KeyCert(keyEnvelope []byte) (keyCert []byte, err error) { func (p15 *pkcs15KeyCert) ToP15KeyCert() (keyCert []byte, err error) {
// private key object // encrypted envelope is required
privateKey := asn1obj.Sequence([][]byte{ err = p15.computeEncryptedKeyEnvelope()
// commonObjectAttributes - Label if err != nil {
asn1obj.Sequence([][]byte{ return nil, err
asn1obj.UTF8String(apcKeyLabel), }
}),
// CommonKeyAttributes // create private key object
asn1obj.Sequence([][]byte{ var privKeyObj []byte
// CommonKeyAttributes - iD - uses keyId that is SHA1( SubjectPublicKeyInfo SEQUENCE )
asn1obj.OctetString(p15.keyId()), switch p15.key.(type) {
// CommonKeyAttributes - usage (trailing 0s will drop) case *rsa.PrivateKey:
asn1obj.BitString([]byte{byte(0b11100010)}), // private key object
// CommonKeyAttributes - accessFlags (trailing 0s will drop) privKeyObj =
asn1obj.BitString([]byte{byte(0b10110000)}),
// CommonKeyAttributes - startDate
asn1obj.GeneralizedTime(p15.cert.NotBefore),
// CommonKeyAttributes - [0] endDate
asn1obj.GeneralizedTimeExplicitValue(0, p15.cert.NotAfter),
}),
// ObjectValue - indirect-protected
asn1obj.ExplicitCompound(1, [][]byte{
asn1obj.Sequence([][]byte{ asn1obj.Sequence([][]byte{
// AuthEnvelopedData Type ([4]) // commonObjectAttributes - Label
asn1obj.ExplicitCompound(4, [][]byte{ asn1obj.Sequence([][]byte{
keyEnvelope, asn1obj.UTF8String(apcKeyLabel),
}), }),
}), // CommonKeyAttributes
}), asn1obj.Sequence([][]byte{
}) // CommonKeyAttributes - iD - uses keyId that is SHA1( SubjectPublicKeyInfo SEQUENCE )
asn1obj.OctetString(p15.keyId()),
// CommonKeyAttributes - usage (trailing 0s will drop)
asn1obj.BitString([]byte{byte(0b11100010)}),
// CommonKeyAttributes - accessFlags (trailing 0s will drop)
asn1obj.BitString([]byte{byte(0b10110000)}),
// CommonKeyAttributes - startDate
asn1obj.GeneralizedTime(p15.cert.NotBefore),
// CommonKeyAttributes - [0] endDate
asn1obj.GeneralizedTimeExplicitValue(0, p15.cert.NotAfter),
}),
// ObjectValue - indirect-protected
asn1obj.ExplicitCompound(1, [][]byte{
asn1obj.Sequence([][]byte{
// AuthEnvelopedData Type ([4])
asn1obj.ExplicitCompound(4, [][]byte{
p15.envelopedPrivateKey,
}),
}),
}),
})
case *ecdsa.PrivateKey:
privKeyObj =
asn1obj.ExplicitCompound(0, [][]byte{
// commonObjectAttributes - Label
asn1obj.Sequence([][]byte{
asn1obj.UTF8String(apcKeyLabel),
}),
// CommonKeyAttributes
asn1obj.Sequence([][]byte{
// CommonKeyAttributes - iD - uses keyId that is SHA1( SubjectPublicKeyInfo SEQUENCE )
asn1obj.OctetString(p15.keyId()),
// CommonKeyAttributes - usage (trailing 0s will drop)
asn1obj.BitString([]byte{byte(0b00100010)}),
// CommonKeyAttributes - accessFlags (trailing 0s will drop)
asn1obj.BitString([]byte{byte(0b10110000)}),
// CommonKeyAttributes - startDate
asn1obj.GeneralizedTime(p15.cert.NotBefore),
// CommonKeyAttributes - [0] endDate
asn1obj.GeneralizedTimeExplicitValue(0, p15.cert.NotAfter),
}),
// ObjectValue - indirect-protected
asn1obj.ExplicitCompound(1, [][]byte{
asn1obj.Sequence([][]byte{
// AuthEnvelopedData Type ([4])
asn1obj.ExplicitCompound(4, [][]byte{
p15.envelopedPrivateKey,
}),
}),
}),
})
default:
// bad key type
return nil, errKeyWrongType
}
// cert object // cert object
cert := asn1obj.Sequence([][]byte{ certObj := asn1obj.Sequence([][]byte{
// commonObjectAttributes - Label // commonObjectAttributes - Label
asn1obj.Sequence([][]byte{ asn1obj.Sequence([][]byte{
asn1obj.UTF8String(apcKeyLabel), asn1obj.UTF8String(apcKeyLabel),
@ -59,6 +109,7 @@ func (p15 *pkcs15KeyCert) toP15KeyCert(keyEnvelope []byte) (keyCert []byte, err
p15.keyIdInt3(), p15.keyIdInt3(),
p15.keyIdInt6(), p15.keyIdInt6(),
p15.keyIdInt7(), p15.keyIdInt7(),
// 8 & 9 will return nil for EC keys (effectively omitting them)
p15.keyIdInt8(), p15.keyIdInt8(),
p15.keyIdInt9(), p15.keyIdInt9(),
}), }),
@ -77,7 +128,7 @@ func (p15 *pkcs15KeyCert) toP15KeyCert(keyEnvelope []byte) (keyCert []byte, err
}), }),
}) })
// build the file // build the object
// ContentInfo // ContentInfo
keyCert = asn1obj.Sequence([][]byte{ keyCert = asn1obj.Sequence([][]byte{
@ -92,12 +143,12 @@ func (p15 *pkcs15KeyCert) toP15KeyCert(keyEnvelope []byte) (keyCert []byte, err
asn1obj.Sequence([][]byte{ asn1obj.Sequence([][]byte{
asn1obj.ExplicitCompound(0, [][]byte{ asn1obj.ExplicitCompound(0, [][]byte{
asn1obj.ExplicitCompound(0, [][]byte{ asn1obj.ExplicitCompound(0, [][]byte{
privateKey, privKeyObj,
}), }),
}), }),
asn1obj.ExplicitCompound(4, [][]byte{ asn1obj.ExplicitCompound(4, [][]byte{
asn1obj.ExplicitCompound(0, [][]byte{ asn1obj.ExplicitCompound(0, [][]byte{
cert, certObj,
}), }),
}), }),
}), }),
@ -111,141 +162,212 @@ func (p15 *pkcs15KeyCert) toP15KeyCert(keyEnvelope []byte) (keyCert []byte, err
// toP15Key creates a P15 file with just the private key, mirroring the p15 format // toP15Key creates a P15 file with just the private key, mirroring the p15 format
// the APC tool uses when generating a new private key (Note: no header is used on // the APC tool uses when generating a new private key (Note: no header is used on
// this file) // this file)
func (p15 *pkcs15KeyCert) toP15Key(keyEnvelope []byte) (key []byte, err error) { func (p15 *pkcs15KeyCert) ToP15Key() (key []byte, err error) {
// create public key object // encrypted envelope is required
var pubKeyObj []byte err = p15.computeEncryptedKeyEnvelope()
if err != nil {
return nil, err
}
// create private and public key objects
var pubKeyObj, privKeyObj []byte
switch privKey := p15.key.(type) { switch privKey := p15.key.(type) {
case *rsa.PrivateKey: case *rsa.PrivateKey:
pubKeyObj = asn1obj.ExplicitCompound(1, [][]byte{ // private key object (slightly different than the key+cert format)
privKeyObj =
asn1obj.Sequence([][]byte{ asn1obj.Sequence([][]byte{
// commonObjectAttributes - Label
asn1obj.Sequence([][]byte{
asn1obj.UTF8String(apcKeyLabel),
}),
// CommonKeyAttributes
asn1obj.Sequence([][]byte{
// CommonKeyAttributes - iD - uses keyId that is SHA1( SubjectPublicKeyInfo SEQUENCE )
asn1obj.OctetString(p15.keyId()),
// CommonKeyAttributes - usage (trailing 0s will drop)
asn1obj.BitString([]byte{byte(0b11100010)}),
// CommonKeyAttributes - accessFlags (trailing 0s will drop)
asn1obj.BitString([]byte{byte(0b10110000)}),
}),
// Key IDs
asn1obj.ExplicitCompound(0, [][]byte{ asn1obj.ExplicitCompound(0, [][]byte{
asn1obj.ExplicitCompound(1, [][]byte{ asn1obj.Sequence([][]byte{
asn1obj.Sequence([][]byte{ asn1obj.ExplicitCompound(0, [][]byte{
asn1obj.ObjectIdentifier(asn1obj.OIDrsaEncryptionPKCS1), p15.keyIdInt2(),
asn1.NullBytes, p15.keyIdInt8(),
p15.keyIdInt9(),
}), }),
// RSAPublicKey SubjectPublicKeyInfo
asn1obj.BitString(
asn1obj.Sequence([][]byte{
asn1obj.Integer(privKey.PublicKey.N),
asn1obj.Integer(big.NewInt(int64(privKey.PublicKey.E))),
}),
),
}), }),
}), }),
// not 100% certain but appears to be rsa key byte len
asn1obj.Integer(big.NewInt(int64(privKey.PublicKey.N.BitLen() / 8))), // ObjectValue - indirect-protected
}), asn1obj.ExplicitCompound(1, [][]byte{
}) asn1obj.Sequence([][]byte{
// AuthEnvelopedData Type ([4])
asn1obj.ExplicitCompound(4, [][]byte{
p15.envelopedPrivateKey,
}),
}),
}),
})
// pub key stub
pubKeyObj =
asn1obj.Sequence([][]byte{
// commonObjectAttributes - Label
asn1obj.Sequence([][]byte{
asn1obj.UTF8String(apcKeyLabel),
}),
// CommonKeyAttributes
asn1obj.Sequence([][]byte{
asn1obj.OctetString(p15.keyId()),
asn1obj.BitString([]byte{byte(0b10000010)}),
asn1obj.BitString([]byte{byte(0b01000000)}),
}),
asn1obj.ExplicitCompound(1, [][]byte{
asn1obj.Sequence([][]byte{
asn1obj.ExplicitCompound(0, [][]byte{
asn1obj.ExplicitCompound(1, [][]byte{
asn1obj.Sequence([][]byte{
asn1obj.ObjectIdentifier(asn1obj.OIDrsaEncryptionPKCS1),
asn1.NullBytes,
}),
// RSAPublicKey SubjectPublicKeyInfo
asn1obj.BitString(
asn1obj.Sequence([][]byte{
asn1obj.Integer(privKey.PublicKey.N),
asn1obj.Integer(big.NewInt(int64(privKey.PublicKey.E))),
}),
),
}),
}),
// not 100% certain but appears to be rsa key byte len
asn1obj.Integer(big.NewInt(int64(privKey.PublicKey.N.BitLen() / 8))),
}),
}),
})
case *ecdsa.PrivateKey:
// private key object (slightly different than the key+cert format)
privKeyObj =
asn1obj.ExplicitCompound(0, [][]byte{
// commonObjectAttributes - Label
asn1obj.Sequence([][]byte{
asn1obj.UTF8String(apcKeyLabel),
}),
// CommonKeyAttributes
asn1obj.Sequence([][]byte{
// CommonKeyAttributes - iD - uses keyId that is SHA1( SubjectPublicKeyInfo SEQUENCE )
asn1obj.OctetString(p15.keyId()),
// CommonKeyAttributes - usage (trailing 0s will drop)
asn1obj.BitString([]byte{byte(0b00100010)}),
// CommonKeyAttributes - accessFlags (trailing 0s will drop)
asn1obj.BitString([]byte{byte(0b10110000)}),
}),
// Key IDs
asn1obj.ExplicitCompound(0, [][]byte{
asn1obj.Sequence([][]byte{
asn1obj.ExplicitCompound(0, [][]byte{
p15.keyIdInt2(),
}),
}),
}),
// ObjectValue - indirect-protected
asn1obj.ExplicitCompound(1, [][]byte{
asn1obj.Sequence([][]byte{
// AuthEnvelopedData Type ([4])
asn1obj.ExplicitCompound(4, [][]byte{
p15.envelopedPrivateKey,
}),
}),
}),
})
// convert ec pub key to a form that provides a public key bytes function
ecdhKey, err := privKey.PublicKey.ECDH()
if err != nil {
return nil, fmt.Errorf("failed to parse ec public key (%s)", err)
}
// select correct OID for curve
var curveOID asn1.ObjectIdentifier
switch privKey.Curve.Params().Name {
case "P-256":
curveOID = asn1obj.OIDprime256v1
case "P-384":
curveOID = asn1obj.OIDsecp384r1
case "P-521":
curveOID = asn1obj.OIDsecp521r1
default:
// bad curve name
return nil, errKeyWrongType
}
// pub key stub
pubKeyObj =
asn1obj.ExplicitCompound(0, [][]byte{
// commonObjectAttributes - Label
asn1obj.Sequence([][]byte{
asn1obj.UTF8String(apcKeyLabel),
}),
// CommonKeyAttributes
asn1obj.Sequence([][]byte{
asn1obj.OctetString(p15.keyId()),
asn1obj.BitString([]byte{byte(0b00000010)}),
asn1obj.BitString([]byte{byte(0b01000000)}),
}),
asn1obj.ExplicitCompound(1, [][]byte{
asn1obj.Sequence([][]byte{
asn1obj.ExplicitCompound(0, [][]byte{
asn1obj.Sequence([][]byte{
asn1obj.Sequence([][]byte{
asn1obj.ObjectIdentifier(asn1obj.OIDecPublicKey),
asn1obj.ObjectIdentifier(curveOID),
}),
asn1obj.BitString(ecdhKey.Bytes()),
}),
}),
}),
}),
})
default: default:
// panic if non-RSA key // bad key type
panic("p15 key file for non-rsa key is unexpected and unsupported") return nil, errKeyWrongType
} }
// private key object (slightly different than the key+cert format) // assemble complete object
privateKey := asn1obj.Sequence([][]byte{ key =
// commonObjectAttributes - Label
asn1obj.Sequence([][]byte{ asn1obj.Sequence([][]byte{
asn1obj.UTF8String(apcKeyLabel), // contentType: OID: 1.2.840.113549.1.15.3.1 pkcs15content (PKCS #15 content type)
}), asn1obj.ObjectIdentifier(asn1obj.OIDPkscs15Content),
// CommonKeyAttributes // content
asn1obj.Sequence([][]byte{ asn1obj.ExplicitCompound(0, [][]byte{
// CommonKeyAttributes - iD - uses keyId that is SHA1( SubjectPublicKeyInfo SEQUENCE )
asn1obj.OctetString(p15.keyId()),
// CommonKeyAttributes - usage (trailing 0s will drop)
asn1obj.BitString([]byte{byte(0b11100010)}),
// CommonKeyAttributes - accessFlags (trailing 0s will drop)
asn1obj.BitString([]byte{byte(0b10110000)}),
}),
//
asn1obj.ExplicitCompound(0, [][]byte{
asn1obj.Sequence([][]byte{
asn1obj.ExplicitCompound(0, [][]byte{
p15.keyIdInt2(),
p15.keyIdInt8(),
p15.keyIdInt9(),
}),
}),
}),
// ObjectValue - indirect-protected
asn1obj.ExplicitCompound(1, [][]byte{
asn1obj.Sequence([][]byte{
// AuthEnvelopedData Type ([4])
asn1obj.ExplicitCompound(4, [][]byte{
keyEnvelope,
}),
}),
}),
})
// ContentInfo
key = asn1obj.Sequence([][]byte{
// contentType: OID: 1.2.840.113549.1.15.3.1 pkcs15content (PKCS #15 content type)
asn1obj.ObjectIdentifier(asn1obj.OIDPkscs15Content),
// content
asn1obj.ExplicitCompound(0, [][]byte{
asn1obj.Sequence([][]byte{
asn1obj.Integer(big.NewInt(0)),
asn1obj.Sequence([][]byte{ asn1obj.Sequence([][]byte{
// [0] Private Key asn1obj.Integer(big.NewInt(0)),
asn1obj.ExplicitCompound(0, [][]byte{ asn1obj.Sequence([][]byte{
// [0] Private Keys
asn1obj.ExplicitCompound(0, [][]byte{ asn1obj.ExplicitCompound(0, [][]byte{
privateKey, asn1obj.ExplicitCompound(0, [][]byte{
privKeyObj,
}),
}), }),
}), // [1] Public Keys
// [1] Public Key asn1obj.ExplicitCompound(1, [][]byte{
asn1obj.ExplicitCompound(1, [][]byte{ asn1obj.ExplicitCompound(0, [][]byte{
asn1obj.ExplicitCompound(0, [][]byte{
asn1obj.Sequence([][]byte{
// commonObjectAttributes - Label
asn1obj.Sequence([][]byte{
asn1obj.UTF8String(apcKeyLabel),
}),
// CommonKeyAttributes
asn1obj.Sequence([][]byte{
asn1obj.OctetString(p15.keyId()),
asn1obj.BitString([]byte{byte(0b10000010)}),
asn1obj.BitString([]byte{byte(0b01000000)}),
}),
pubKeyObj, pubKeyObj,
}), }),
}), }),
}), }),
}), }),
}), }),
}), })
})
return key, nil return key, nil
} }
// ToP15File turns the key and cert into a properly formatted and encoded
// p15 file
func (p15 *pkcs15KeyCert) ToP15Files() (keyCertFile []byte, keyFile []byte, err error) {
// rsa encrypted key in encrypted envelope (will be shared by both output files)
envelope, err := p15.encryptedKeyEnvelope()
if err != nil {
return nil, nil, err
}
// key + cert file
keyCertFile, err = p15.toP15KeyCert(envelope)
if err != nil {
return nil, nil, err
}
// key only file
keyFile, err = p15.toP15Key(envelope)
if err != nil {
return nil, nil, err
}
return keyCertFile, keyFile, nil
}

View file

@ -2,6 +2,7 @@ package pkcs15
import ( import (
"apc-p15-tool/pkg/tools/asn1obj" "apc-p15-tool/pkg/tools/asn1obj"
"crypto/ecdsa"
"crypto/rsa" "crypto/rsa"
) )
@ -27,15 +28,13 @@ func (p15 *pkcs15KeyCert) privateKeyObject() []byte {
asn1obj.IntegerExplicitValue(7, privKey.Precomputed.Qinv), asn1obj.IntegerExplicitValue(7, privKey.Precomputed.Qinv),
}) })
// case *ecdsa.PrivateKey: case *ecdsa.PrivateKey:
// // Only private piece is the integer D // Only private piece is the integer D
// privKeyObj = asn1obj.Sequence([][]byte{ privKeyObj = asn1obj.Integer(privKey.D)
// asn1obj.Integer(privKey.D),
// })
default: default:
// panic if non-RSA key // panic if unsupported key
panic("private key object for non-rsa key is unexpected and unsupported") panic("private key type is unexpected and unsupported")
} }
return privKeyObj return privKeyObj

View file

@ -11,6 +11,10 @@ var (
OIDdesEDE3CBC = asn1.ObjectIdentifier{1, 2, 840, 113549, 3, 7} // des-EDE3-CBC (RSADSI encryptionAlgorithm) OIDdesEDE3CBC = asn1.ObjectIdentifier{1, 2, 840, 113549, 3, 7} // des-EDE3-CBC (RSADSI encryptionAlgorithm)
OIDpkcs7Data = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7, 1} // data (PKCS #7) OIDpkcs7Data = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 7, 1} // data (PKCS #7)
OIDauthEnc128 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 16, 3, 15} // authEnc128 (S/MIME Algorithms) OIDauthEnc128 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 16, 3, 15} // authEnc128 (S/MIME Algorithms)
OIDecPublicKey = asn1.ObjectIdentifier{1, 2, 840, 10045, 2, 1} // ecPublicKey (ANSI X9.62 public key type)
OIDprime256v1 = asn1.ObjectIdentifier{1, 2, 840, 10045, 3, 1, 7} // prime256v1 (ANSI X9.62 named elliptic curve)
OIDsecp384r1 = asn1.ObjectIdentifier{1, 3, 132, 0, 34} // secp384r1 (SECG (Certicom) named elliptic curve)
OIDsecp521r1 = asn1.ObjectIdentifier{1, 3, 132, 0, 35} // secp521r1 (SECG (Certicom) named elliptic curve)
) )
// ObjectIdentifier returns an ASN.1 OBJECT IDENTIFIER with the oidValue bytes // ObjectIdentifier returns an ASN.1 OBJECT IDENTIFIER with the oidValue bytes