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
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
longer considered completely secure; avoid keys of this size if possible. Most
(all?) public ACME services won't accept keys of this size anyway.
### Key Types and Sizes
NMC2 does not officially support the 3,072 bit key size, however, it works fine
on my NMC2. If you use this size and it doesn't work on your NMC2, try a 2,048
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
tool, and as such I have no way to generate files to reverse engineer.
NMC2:
- RSA 1,024, 2,048, 3,072* bit lengths.
NMC3:
- 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:
- 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
v1.0.9.
If you have problems, 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.
If you have trouble, your first step should be to update your NMC's firmware.
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
using the `--insecurecipher` flag. If this works, you should upgrade your

View file

@ -1,10 +1,13 @@
package apcssh
import (
"errors"
"fmt"
"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
// UPS. It has logic to deduce if the NMC is a newer version (e.g., NMC3 with
// 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
// command `ssl`. This command is not present on older devices (e.g., NMC2) or firmwares.
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
err := cli.UploadSCP("/ssl/nmc.key", keyP15, 0600)
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
// firmwares that do not support the `ssl` command.
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
err := cli.UploadSCP("/ssl/defaultcert.p15", keyCertP15, 0600)
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)
err = os.WriteFile(keyCertFileName, apcKeyCertFile, 0600)
if err != nil {
return fmt.Errorf("create: failed to write apc p15 key+cert file (%s)", err)
// skip key+cert if it wasn't generated
if len(apcKeyCertFile) > 0 {
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
// 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)
keyCertFileNameDebug := keyCertFileName + ".noheader.b64"
err = os.WriteFile(keyCertFileNameDebug, []byte(base64.StdEncoding.EncodeToString(apcKeyCertFile[apcHeaderLen:])), 0600)
if err != nil {
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)
// skip key+cert if it wasn't generated
if len(apcKeyCertFile) > 0 {
keyCertFileNameDebug := keyCertFileName + ".noheader.b64"
err = os.WriteFile(keyCertFileNameDebug, []byte(base64.StdEncoding.EncodeToString(apcKeyCertFile[apcHeaderLen:])), 0600)
if err != nil {
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"
err = os.WriteFile(keyCertFileNameHeaderDebug, []byte(base64.StdEncoding.EncodeToString(apcKeyCertFile[:apcHeaderLen])), 0600)
if err != nil {
return fmt.Errorf("create: failed to write apc p15 key+cert file (%s)", err)
keyCertFileNameHeaderDebug := keyCertFileName + ".header.b64"
err = os.WriteFile(keyCertFileNameHeaderDebug, []byte(base64.StdEncoding.EncodeToString(apcKeyCertFile[:apcHeaderLen])), 0600)
if err != nil {
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
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.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.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")
@ -88,9 +88,9 @@ func (app *app) getConfig(args []string) error {
// install -- subcommand
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.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.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")

View file

@ -3,13 +3,22 @@ package app
import (
"apc-p15-tool/pkg/pkcs15"
"fmt"
"slices"
)
// pemToAPCP15 reads the specified pem files and returns the apc p15 files (both a
// p15 file with just the private key, and also a p15 file with both the private key
// and certificate). The key+cert file includes the required APC header, prepended.
// list of keys supported by the NMC2
var nmc2SupportedKeyTypes = []pkcs15.KeyType{
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) {
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
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)
}
app.stdLogger.Printf("%s: successfully loaded pem files", parentCmdName)
app.stdLogger.Printf("%s: successfully parsed pem files", parentCmdName)
// make file bytes
keyCertFile, keyFile, err := p15.ToP15Files()
// make key file (always)
keyFile, err = p15.ToP15Key()
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
apcHeader, err := makeFileHeader(keyCertFile)
if err != nil {
return nil, nil, fmt.Errorf("%s: failed to make p15 file header (%w)", parentCmdName, err)
app.stdLogger.Printf("%s: successfully generated p15 key file content", parentCmdName)
// check key type for compat with NMC2
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
apcKeyCertFile = append(apcHeader, keyCertFile...)
app.stdLogger.Printf("%s: apc p15 file data succesfully generated", parentCmdName)
app.stdLogger.Printf("%s: apc p15 file(s) data succesfully generated", parentCmdName)
return keyFile, apcKeyCertFile, nil
}

View file

@ -21,14 +21,19 @@ const (
apcKEKIterations = 5000
)
// encryptedKeyEnvelope encrypts p15's rsa private key using the algorithms and
// params expected in the APC file. Salt values are always random.
func (p15 *pkcs15KeyCert) encryptedKeyEnvelope() ([]byte, error) {
// encryptedKeyEnvelope encrypts p15's private key using the algorithms and
// params expected in the APC file.
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
kekSalt := make([]byte, 8)
_, err := rand.Read(kekSalt)
if err != nil {
return nil, err
return err
}
// kek hash alg
@ -42,7 +47,7 @@ func (p15 *pkcs15KeyCert) encryptedKeyEnvelope() ([]byte, error) {
// make DES cipher from KEK for CEK
cekDesCipher, err := des.NewTripleDESCipher(kek)
if err != nil {
return nil, err
return err
}
// cek (16 bytes for authEnc128) -- see: rfc3211
@ -50,7 +55,7 @@ func (p15 *pkcs15KeyCert) encryptedKeyEnvelope() ([]byte, error) {
cek := make([]byte, cekLen)
_, err = rand.Read(cek)
if err != nil {
return nil, err
return err
}
// LEN + Check Val [3]
@ -71,7 +76,7 @@ func (p15 *pkcs15KeyCert) encryptedKeyEnvelope() ([]byte, error) {
cekPadding := make([]byte, cekPadLen)
_, err = rand.Read(cekPadding)
if err != nil {
return nil, err
return err
}
wrappedCEK = append(wrappedCEK, cekPadding...)
@ -80,7 +85,7 @@ func (p15 *pkcs15KeyCert) encryptedKeyEnvelope() ([]byte, error) {
cekEncryptSalt := make([]byte, 8)
_, err = rand.Read(cekEncryptSalt)
if err != nil {
return nil, err
return err
}
cekEncrypter := cipher.NewCBCEncrypter(cekDesCipher, cekEncryptSalt)
@ -94,13 +99,13 @@ func (p15 *pkcs15KeyCert) encryptedKeyEnvelope() ([]byte, error) {
contentEncSalt := make([]byte, 8)
_, err = rand.Read(contentEncSalt)
if err != nil {
return nil, err
return err
}
contentEncryptKey := pbkdf2.Key(cek, []byte("encryption"), 1, 24, sha1.New)
contentDesCipher, err := des.NewTripleDESCipher(contentEncryptKey)
if err != nil {
return nil, err
return err
}
// envelope content (that will be encrypted)
@ -151,7 +156,7 @@ func (p15 *pkcs15KeyCert) encryptedKeyEnvelope() ([]byte, error) {
// make MAC
_, err = macHasher.Write(hashMe)
if err != nil {
return nil, err
return err
}
mac := macHasher.Sum(nil)
@ -218,5 +223,7 @@ func (p15 *pkcs15KeyCert) encryptedKeyEnvelope() ([]byte, error) {
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 (
"apc-p15-tool/pkg/tools/asn1obj"
"crypto/ecdsa"
"crypto/rsa"
"crypto/sha1"
"encoding/binary"
@ -119,9 +120,13 @@ func (p15 *pkcs15KeyCert) keyIdInt8() []byte {
nBytes := privKey.N.Bytes()
keyIdVal = nBytes[len(nBytes)-8:]
case *ecdsa.PrivateKey:
// don't use this key id, leave empty
return nil
default:
// panic if non-RSA key
panic("key id 8 for non-rsa key is unexpected and unsupported")
// panic if unexpected key type
panic("key id 8 for key is unexpected and unsupported")
}
// object to return
@ -181,33 +186,13 @@ func (p15 *pkcs15KeyCert) keyIdInt9() []byte {
e := big.NewInt(int64(privKey.PublicKey.E))
publicKeyPacket = append(publicKeyPacket, bigIntToMpi(e)...)
// case *ecdsa.PrivateKey:
// // A one-octet number denoting the public-key algorithm of this key.
// // 19 - ECDSA public key algorithm (see rfc 6637 s. 5)
// 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))
// }
case *ecdsa.PrivateKey:
// don't use this key id, leave empty
return nil
default:
// panic if non-RSA key
panic("key id 9 for non-rsa key is unexpected and unsupported")
// panic if unexpected key type
panic("key id 9 for key is unexpected and unsupported")
}
// Assemble the V4 byte array that will be hashed

View file

@ -2,6 +2,7 @@ package pkcs15
import (
"crypto"
"crypto/ecdsa"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
@ -9,21 +10,27 @@ import (
"errors"
"fmt"
"reflect"
"slices"
)
var (
errPemKeyBadBlock = errors.New("pkcs15: pem key: failed to decode pem block")
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)")
errPemKeyWrongType = errors.New("pkcs15: pem key: unsupported key type (only rsa 1,024, 2,048, and 3,072 supported)")
errPemKeyWrongBlockType = errors.New("pkcs15: pem key: unsupported pem block type")
errKeyWrongType = errors.New("pkcs15: pem key: unsupported key type")
errPemCertBadBlock = errors.New("pkcs15: pem cert: failed to decode pem block")
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
// to parse an RSA 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
// 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 supported.
func pemKeyDecode(keyPem []byte) (crypto.PrivateKey, error) {
// decode
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)
}
// verify proper bitlen
if rsaKey.N.BitLen() != 1024 && rsaKey.N.BitLen() != 2048 && rsaKey.N.BitLen() != 3072 {
return nil, errPemKeyWrongType
// verify supported rsa bitlen
if !slices.Contains(supportedRSASizes, rsaKey.N.BitLen()) {
return nil, errKeyWrongType
}
// good to go
privateKey = rsaKey
// case "EC PRIVATE KEY": // SEC1, ASN.1
// var ecdKey *ecdsa.PrivateKey
// ecdKey, err := x509.ParseECPrivateKey(pemBlock.Bytes)
// if err != nil {
// return nil, errPemKeyFailedToParse
// }
case "EC PRIVATE KEY": // SEC1, ASN.1
ecdKey, err := x509.ParseECPrivateKey(pemBlock.Bytes)
if err != nil {
return nil, errPemKeyFailedToParse
}
// // verify acceptable curve name
// if ecdKey.Curve.Params().Name != "P-256" && ecdKey.Curve.Params().Name != "P-384" {
// return nil, errPemKeyWrongType
// }
// verify supported curve name
if !slices.Contains(supportedECDSACurves, ecdKey.Curve.Params().Name) {
return nil, errKeyWrongType
}
// // good to go
// privateKey = ecdKey
// good to go
privateKey = ecdKey
case "PRIVATE KEY": // PKCS8
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)
}
// verify proper bitlen
if pkcs8Key.N.BitLen() != 1024 && pkcs8Key.N.BitLen() != 2048 && pkcs8Key.N.BitLen() != 3072 {
return nil, errPemKeyWrongType
// verify supported rsa bitlen
if !slices.Contains(supportedRSASizes, pkcs8Key.N.BitLen()) {
return nil, errKeyWrongType
}
// good to go
privateKey = pkcs8Key
// case *ecdsa.PrivateKey:
// // verify acceptable curve name
// if pkcs8Key.Curve.Params().Name != "P-256" && pkcs8Key.Curve.Params().Name != "P-384" {
// return nil, errPemKeyWrongType
// }
case *ecdsa.PrivateKey:
// verify supported curve name
if !slices.Contains(supportedECDSACurves, pkcs8Key.Curve.Params().Name) {
return nil, errKeyWrongType
}
// // good to go
// privateKey = pkcs8Key
// good to go
privateKey = pkcs8Key
default:
return nil, errPemKeyWrongType
return nil, errKeyWrongType
}
default:

View file

@ -2,6 +2,8 @@ package pkcs15
import (
"crypto"
"crypto/ecdsa"
"crypto/rsa"
"crypto/x509"
)
@ -10,6 +12,59 @@ import (
type pkcs15KeyCert struct {
key crypto.PrivateKey
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
@ -27,10 +82,17 @@ func ParsePEMToPKCS15(keyPem, certPem []byte) (*pkcs15KeyCert, error) {
return nil, err
}
// create p15 struct
p15 := &pkcs15KeyCert{
key: key,
cert: cert,
}
// pre-calculate encrypted envelope
err = p15.computeEncryptedKeyEnvelope()
if err != nil {
return nil, err
}
return p15, nil
}

View file

@ -2,8 +2,10 @@ package pkcs15
import (
"apc-p15-tool/pkg/tools/asn1obj"
"crypto/ecdsa"
"crypto/rsa"
"encoding/asn1"
"fmt"
"math/big"
)
@ -13,39 +15,87 @@ const (
// 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)
func (p15 *pkcs15KeyCert) toP15KeyCert(keyEnvelope []byte) (keyCert []byte, err error) {
// private key object
privateKey := 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)}),
// CommonKeyAttributes - startDate
asn1obj.GeneralizedTime(p15.cert.NotBefore),
// CommonKeyAttributes - [0] endDate
asn1obj.GeneralizedTimeExplicitValue(0, p15.cert.NotAfter),
}),
// ObjectValue - indirect-protected
asn1obj.ExplicitCompound(1, [][]byte{
func (p15 *pkcs15KeyCert) ToP15KeyCert() (keyCert []byte, err error) {
// encrypted envelope is required
err = p15.computeEncryptedKeyEnvelope()
if err != nil {
return nil, err
}
// create private key object
var privKeyObj []byte
switch p15.key.(type) {
case *rsa.PrivateKey:
// private key object
privKeyObj =
asn1obj.Sequence([][]byte{
// AuthEnvelopedData Type ([4])
asn1obj.ExplicitCompound(4, [][]byte{
keyEnvelope,
// 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)}),
// 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 := asn1obj.Sequence([][]byte{
certObj := asn1obj.Sequence([][]byte{
// commonObjectAttributes - Label
asn1obj.Sequence([][]byte{
asn1obj.UTF8String(apcKeyLabel),
@ -59,6 +109,7 @@ func (p15 *pkcs15KeyCert) toP15KeyCert(keyEnvelope []byte) (keyCert []byte, err
p15.keyIdInt3(),
p15.keyIdInt6(),
p15.keyIdInt7(),
// 8 & 9 will return nil for EC keys (effectively omitting them)
p15.keyIdInt8(),
p15.keyIdInt9(),
}),
@ -77,7 +128,7 @@ func (p15 *pkcs15KeyCert) toP15KeyCert(keyEnvelope []byte) (keyCert []byte, err
}),
})
// build the file
// build the object
// ContentInfo
keyCert = asn1obj.Sequence([][]byte{
@ -92,12 +143,12 @@ func (p15 *pkcs15KeyCert) toP15KeyCert(keyEnvelope []byte) (keyCert []byte, err
asn1obj.Sequence([][]byte{
asn1obj.ExplicitCompound(0, [][]byte{
asn1obj.ExplicitCompound(0, [][]byte{
privateKey,
privKeyObj,
}),
}),
asn1obj.ExplicitCompound(4, [][]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
// the APC tool uses when generating a new private key (Note: no header is used on
// this file)
func (p15 *pkcs15KeyCert) toP15Key(keyEnvelope []byte) (key []byte, err error) {
// create public key object
var pubKeyObj []byte
func (p15 *pkcs15KeyCert) ToP15Key() (key []byte, err error) {
// encrypted envelope is required
err = p15.computeEncryptedKeyEnvelope()
if err != nil {
return nil, err
}
// create private and public key objects
var pubKeyObj, privKeyObj []byte
switch privKey := p15.key.(type) {
case *rsa.PrivateKey:
pubKeyObj = asn1obj.ExplicitCompound(1, [][]byte{
// private key object (slightly different than the key+cert format)
privKeyObj =
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(1, [][]byte{
asn1obj.Sequence([][]byte{
asn1obj.ObjectIdentifier(asn1obj.OIDrsaEncryptionPKCS1),
asn1.NullBytes,
asn1obj.Sequence([][]byte{
asn1obj.ExplicitCompound(0, [][]byte{
p15.keyIdInt2(),
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:
// panic if non-RSA key
panic("p15 key file for non-rsa key is unexpected and unsupported")
// bad key type
return nil, errKeyWrongType
}
// private key object (slightly different than the key+cert format)
privateKey := asn1obj.Sequence([][]byte{
// commonObjectAttributes - Label
// assemble complete object
key =
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)}),
}),
//
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)),
// 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{
// [0] Private Key
asn1obj.ExplicitCompound(0, [][]byte{
asn1obj.Integer(big.NewInt(0)),
asn1obj.Sequence([][]byte{
// [0] Private Keys
asn1obj.ExplicitCompound(0, [][]byte{
privateKey,
asn1obj.ExplicitCompound(0, [][]byte{
privKeyObj,
}),
}),
}),
// [1] Public Key
asn1obj.ExplicitCompound(1, [][]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)}),
}),
// [1] Public Keys
asn1obj.ExplicitCompound(1, [][]byte{
asn1obj.ExplicitCompound(0, [][]byte{
pubKeyObj,
}),
}),
}),
}),
}),
}),
})
})
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 (
"apc-p15-tool/pkg/tools/asn1obj"
"crypto/ecdsa"
"crypto/rsa"
)
@ -27,15 +28,13 @@ func (p15 *pkcs15KeyCert) privateKeyObject() []byte {
asn1obj.IntegerExplicitValue(7, privKey.Precomputed.Qinv),
})
// case *ecdsa.PrivateKey:
// // Only private piece is the integer D
// privKeyObj = asn1obj.Sequence([][]byte{
// asn1obj.Integer(privKey.D),
// })
case *ecdsa.PrivateKey:
// Only private piece is the integer D
privKeyObj = asn1obj.Integer(privKey.D)
default:
// panic if non-RSA key
panic("private key object for non-rsa key is unexpected and unsupported")
// panic if unsupported key
panic("private key type is unexpected and unsupported")
}
return privKeyObj

View file

@ -11,6 +11,10 @@ var (
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)
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