diff --git a/pkcs8/README.md b/pkcs8/README.md new file mode 100644 index 0000000..f15d497 --- /dev/null +++ b/pkcs8/README.md @@ -0,0 +1,7 @@ +OpenSSL can generate private keys in both "traditional format" and PKCS#8 format. Newer applications are advised to use more secure PKCS#8 format. Go standard crypto package provides a [function](http://golang.org/pkg/crypto/x509/#ParsePKCS8PrivateKey) to parse private key in PKCS#8 format. There is a limitation to this function. It can only handle unencrypted PKCS#8 private keys. To use this function, the user has to save the private key in file without encryption, which is a bad practice to leave private keys unprotected on file systems. In addition, Go standard package lacks the functions to convert RSA/ECDSA private keys into PKCS#8 format. + +pkcs8 package fills the gap here. It implements functions to process private keys in PKCS#8 format, as defined in [RFC5208](https://tools.ietf.org/html/rfc5208) and [RFC5958](https://tools.ietf.org/html/rfc5958). It can handle both unencrypted PKCS#8 PrivateKeyInfo format and EncryptedPrivateKeyInfo format with PKCS#5 (v2.0) algorithms. + +## Credits +This is a fork of [youmark/pkcs8](https://github.com/youmark/pkcs8), and we added support for ShangMi. + diff --git a/pkcs8/cipher.go b/pkcs8/cipher.go new file mode 100644 index 0000000..87e4f89 --- /dev/null +++ b/pkcs8/cipher.go @@ -0,0 +1,164 @@ +package pkcs8 + +import ( + "crypto/cipher" + "crypto/rand" + "crypto/x509/pkix" + "encoding/asn1" + "errors" + + "github.com/emmansun/gmsm/padding" +) + +func genRandom(len int) ([]byte, error) { + value := make([]byte, len) + _, err := rand.Read(value) + return value, err +} + +type cipherWithBlock struct { + oid asn1.ObjectIdentifier + ivSize int + keySize int + newBlock func(key []byte) (cipher.Block, error) +} + +func (c cipherWithBlock) KeySize() int { + return c.keySize +} + +func (c cipherWithBlock) OID() asn1.ObjectIdentifier { + return c.oid +} + +func (c cipherWithBlock) Encrypt(key, plaintext []byte) (*pkix.AlgorithmIdentifier, []byte, error) { + block, err := c.newBlock(key) + if err != nil { + return nil, nil, err + } + iv, err := genRandom(c.ivSize) + if err != nil { + return nil, nil, err + } + ciphertext, err := cbcEncrypt(block, key, iv, plaintext) + if err != nil { + return nil, nil, err + } + marshalledIV, err := asn1.Marshal(iv) + if err != nil { + return nil, nil, err + } + + encryptionScheme := pkix.AlgorithmIdentifier{ + Algorithm: c.oid, + Parameters: asn1.RawValue{FullBytes: marshalledIV}, + } + + return &encryptionScheme, ciphertext, nil +} + +func (c cipherWithBlock) Decrypt(key []byte, parameters *asn1.RawValue, encryptedKey []byte) ([]byte, error) { + block, err := c.newBlock(key) + if err != nil { + return nil, err + } + + var iv []byte + if _, err := asn1.Unmarshal(parameters.FullBytes, &iv); err != nil { + return nil, errors.New("pkcs8: invalid cipher parameters") + } + + return cbcDecrypt(block, key, iv, encryptedKey) +} + +func cbcEncrypt(block cipher.Block, key, iv, plaintext []byte) ([]byte, error) { + mode := cipher.NewCBCEncrypter(block, iv) + pkcs7 := padding.NewPKCS7Padding(uint(block.BlockSize())) + plainText := pkcs7.Pad(plaintext) + ciphertext := make([]byte, len(plainText)) + mode.CryptBlocks(ciphertext, plainText) + return ciphertext, nil +} + +func cbcDecrypt(block cipher.Block, key, iv, ciphertext []byte) ([]byte, error) { + mode := cipher.NewCBCDecrypter(block, iv) + pkcs7 := padding.NewPKCS7Padding(uint(block.BlockSize())) + plaintext := make([]byte, len(ciphertext)) + mode.CryptBlocks(plaintext, ciphertext) + return pkcs7.Unpad(plaintext) +} + +type cipherWithGCM struct { + oid asn1.ObjectIdentifier + nonceSize int + keySize int + newBlock func(key []byte) (cipher.Block, error) +} + +// http://javadoc.iaik.tugraz.at/iaik_jce/current/index.html?iaik/security/cipher/GCMParameters.html +type gcmParameters struct { + Nonce []byte `asn1:"tag:4"` + ICVLen int +} + +func (c cipherWithGCM) KeySize() int { + return c.keySize +} + +func (c cipherWithGCM) OID() asn1.ObjectIdentifier { + return c.oid +} + +func (c cipherWithGCM) Encrypt(key, plaintext []byte) (*pkix.AlgorithmIdentifier, []byte, error) { + block, err := c.newBlock(key) + if err != nil { + return nil, nil, err + } + nonce, err := genRandom(c.nonceSize) + if err != nil { + return nil, nil, err + } + + aead, err := cipher.NewGCMWithNonceSize(block, c.nonceSize) + if err != nil { + return nil, nil, err + } + ciphertext := aead.Seal(nil, nonce, plaintext, nil) + paramSeq := gcmParameters{ + Nonce: nonce, + ICVLen: aead.Overhead(), + } + paramBytes, err := asn1.Marshal(paramSeq) + if err != nil { + return nil, nil, err + } + encryptionAlgorithm := pkix.AlgorithmIdentifier{ + Algorithm: c.oid, + Parameters: asn1.RawValue{ + Tag: asn1.TagSequence, + Bytes: paramBytes, + }, + } + return &encryptionAlgorithm, ciphertext, nil +} + +func (c cipherWithGCM) Decrypt(key []byte, parameters *asn1.RawValue, encryptedKey []byte) ([]byte, error) { + block, err := c.newBlock(key) + if err != nil { + return nil, err + } + params := gcmParameters{} + _, err = asn1.Unmarshal(parameters.Bytes, ¶ms) + if err != nil { + return nil, err + } + aead, err := cipher.NewGCMWithNonceSize(block, len(params.Nonce)) + if err != nil { + return nil, err + } + if params.ICVLen != aead.Overhead() { + return nil, errors.New("pkcs8: invalid tag size") + } + + return aead.Open(nil, params.Nonce, encryptedKey, nil) +} diff --git a/pkcs8/cipher_aes.go b/pkcs8/cipher_aes.go new file mode 100644 index 0000000..00c6da2 --- /dev/null +++ b/pkcs8/cipher_aes.go @@ -0,0 +1,84 @@ +package pkcs8 + +import ( + "crypto/aes" + "encoding/asn1" +) + +var ( + oidAES128CBC = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 1, 2} + oidAES128GCM = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 1, 6} + oidAES192CBC = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 1, 22} + oidAES192GCM = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 1, 26} + oidAES256CBC = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 1, 42} + oidAES256GCM = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 1, 46} +) + +func init() { + RegisterCipher(oidAES128CBC, func() Cipher { + return &AES128CBC + }) + RegisterCipher(oidAES128GCM, func() Cipher { + return &AES128GCM + }) + RegisterCipher(oidAES192CBC, func() Cipher { + return &AES192CBC + }) + RegisterCipher(oidAES192GCM, func() Cipher { + return &AES192GCM + }) + RegisterCipher(oidAES256CBC, func() Cipher { + return &AES256CBC + }) + RegisterCipher(oidAES256GCM, func() Cipher { + return &AES256GCM + }) +} + +// AES128CBC is the 128-bit key AES cipher in CBC mode. +var AES128CBC = cipherWithBlock{ + ivSize: aes.BlockSize, + keySize: 16, + newBlock: aes.NewCipher, + oid: oidAES128CBC, +} + +// AES128GCM is the 128-bit key AES cipher in GCM mode. +var AES128GCM = cipherWithGCM{ + nonceSize: 12, + keySize: 16, + newBlock: aes.NewCipher, + oid: oidAES128GCM, +} + +// AES192CBC is the 192-bit key AES cipher in CBC mode. +var AES192CBC = cipherWithBlock{ + ivSize: aes.BlockSize, + keySize: 24, + newBlock: aes.NewCipher, + oid: oidAES192CBC, +} + +// AES192GCM is the 912-bit key AES cipher in GCM mode. +var AES192GCM = cipherWithGCM{ + nonceSize: 12, + keySize: 24, + newBlock: aes.NewCipher, + oid: oidAES192GCM, +} + +// AES256CBC is the 256-bit key AES cipher in CBC mode. +var AES256CBC = cipherWithBlock{ + ivSize: aes.BlockSize, + keySize: 32, + newBlock: aes.NewCipher, + oid: oidAES256CBC, +} + +// AES256GCM is the 256-bit key AES cipher in GCM mode. +var AES256GCM = cipherWithGCM{ + nonceSize: 12, + keySize: 32, + newBlock: aes.NewCipher, + oid: oidAES256GCM, +} diff --git a/pkcs8/cipher_des.go b/pkcs8/cipher_des.go new file mode 100644 index 0000000..e229e2f --- /dev/null +++ b/pkcs8/cipher_des.go @@ -0,0 +1,36 @@ +package pkcs8 + +import ( + "crypto/des" + "encoding/asn1" +) + +var ( + oidDESCBC = asn1.ObjectIdentifier{1, 3, 14, 3, 2, 7} + oidDESEDE3CBC = asn1.ObjectIdentifier{1, 2, 840, 113549, 3, 7} +) + +func init() { + RegisterCipher(oidDESCBC, func() Cipher { + return &DESCBC + }) + + RegisterCipher(oidDESEDE3CBC, func() Cipher { + return &TripleDESCBC + }) +} + +var DESCBC = cipherWithBlock{ + ivSize: des.BlockSize, + keySize: 8, + newBlock: des.NewCipher, + oid: oidDESCBC, +} + +// TripleDESCBC is the 168-bit key 3DES cipher in CBC mode. +var TripleDESCBC = cipherWithBlock{ + ivSize: des.BlockSize, + keySize: 24, + newBlock: des.NewTripleDESCipher, + oid: oidDESEDE3CBC, +} diff --git a/pkcs8/cipher_sm4.go b/pkcs8/cipher_sm4.go new file mode 100644 index 0000000..50c4116 --- /dev/null +++ b/pkcs8/cipher_sm4.go @@ -0,0 +1,37 @@ +package pkcs8 + +import ( + "encoding/asn1" + + "github.com/emmansun/gmsm/sm4" +) + +var ( + oidSM4CBC = asn1.ObjectIdentifier{1, 2, 156, 10197, 1, 104, 2} + oidSM4GCM = asn1.ObjectIdentifier{1, 2, 156, 10197, 1, 104, 8} +) + +func init() { + RegisterCipher(oidSM4CBC, func() Cipher { + return &SM4CBC + }) + RegisterCipher(oidSM4GCM, func() Cipher { + return &SM4GCM + }) +} + +// SM4CBC is the 128-bit key SM4 cipher in CBC mode. +var SM4CBC = cipherWithBlock{ + ivSize: sm4.BlockSize, + keySize: 16, + newBlock: sm4.NewCipher, + oid: oidSM4CBC, +} + +// SM4GCM is the 128-bit key SM4 cipher in GCM mode. +var SM4GCM = cipherWithGCM{ + nonceSize: 12, + keySize: 16, + newBlock: sm4.NewCipher, + oid: oidSM4GCM, +} diff --git a/pkcs8/kdf_pbkdf2.go b/pkcs8/kdf_pbkdf2.go new file mode 100644 index 0000000..d071a1f --- /dev/null +++ b/pkcs8/kdf_pbkdf2.go @@ -0,0 +1,140 @@ +package pkcs8 + +// +// Reference https://datatracker.ietf.org/doc/html/rfc8018#section-5.2 +// + +import ( + "crypto/sha1" + "crypto/sha256" + "crypto/sha512" + "crypto/x509/pkix" + "encoding/asn1" + "errors" + "hash" + + "github.com/emmansun/gmsm/sm3" + "golang.org/x/crypto/pbkdf2" +) + +// http://gmssl.org/docs/oid.html +var ( + oidPKCS5PBKDF2 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 5, 12} + oidHMACWithSHA1 = asn1.ObjectIdentifier{1, 2, 840, 113549, 2, 7} + oidHMACWithSHA224 = asn1.ObjectIdentifier{1, 2, 840, 113549, 2, 8} + oidHMACWithSHA256 = asn1.ObjectIdentifier{1, 2, 840, 113549, 2, 9} + oidHMACWithSHA384 = asn1.ObjectIdentifier{1, 2, 840, 113549, 2, 10} + oidHMACWithSHA512 = asn1.ObjectIdentifier{1, 2, 840, 113549, 2, 11} + oidHMACWithSHA512_224 = asn1.ObjectIdentifier{1, 2, 840, 113549, 2, 12} + oidHMACWithSHA512_256 = asn1.ObjectIdentifier{1, 2, 840, 113549, 2, 13} + oidHMACWithSM3 = asn1.ObjectIdentifier{1, 2, 156, 10197, 1, 401, 2} +) + +func init() { + RegisterKDF(oidPKCS5PBKDF2, func() KDFParameters { + return new(pbkdf2Params) + }) +} + +func newHashFromPRF(ai pkix.AlgorithmIdentifier) (func() hash.Hash, error) { + switch { + case len(ai.Algorithm) == 0 || ai.Algorithm.Equal(oidHMACWithSHA1): + return sha1.New, nil + case ai.Algorithm.Equal(oidHMACWithSHA224): + return sha256.New224, nil + case ai.Algorithm.Equal(oidHMACWithSHA256): + return sha256.New, nil + case ai.Algorithm.Equal(oidHMACWithSHA384): + return sha512.New384, nil + case ai.Algorithm.Equal(oidHMACWithSHA512): + return sha512.New, nil + case ai.Algorithm.Equal(oidHMACWithSHA512_224): + return sha512.New512_224, nil + case ai.Algorithm.Equal(oidHMACWithSHA512_256): + return sha512.New512_256, nil + case ai.Algorithm.Equal(oidHMACWithSM3): + return sm3.New, nil + default: + return nil, errors.New("pkcs8: unsupported hash function") + } +} + +func newPRFParamFromHash(h Hash) (pkix.AlgorithmIdentifier, error) { + switch h { + case SHA1: + return pkix.AlgorithmIdentifier{ + Algorithm: oidHMACWithSHA1, + Parameters: asn1.RawValue{Tag: asn1.TagNull}}, nil + case SHA224: + return pkix.AlgorithmIdentifier{ + Algorithm: oidHMACWithSHA224, + Parameters: asn1.RawValue{Tag: asn1.TagNull}}, nil + case SHA256: + return pkix.AlgorithmIdentifier{ + Algorithm: oidHMACWithSHA256, + Parameters: asn1.RawValue{Tag: asn1.TagNull}}, nil + case SHA384: + return pkix.AlgorithmIdentifier{ + Algorithm: oidHMACWithSHA384, + Parameters: asn1.RawValue{Tag: asn1.TagNull}}, nil + case SHA512: + return pkix.AlgorithmIdentifier{ + Algorithm: oidHMACWithSHA512, + Parameters: asn1.RawValue{Tag: asn1.TagNull}}, nil + case SHA512_224: + return pkix.AlgorithmIdentifier{ + Algorithm: oidHMACWithSHA512_224, + Parameters: asn1.RawValue{Tag: asn1.TagNull}}, nil + case SHA512_256: + return pkix.AlgorithmIdentifier{ + Algorithm: oidHMACWithSHA512_256, + Parameters: asn1.RawValue{Tag: asn1.TagNull}}, nil + case SM3: + return pkix.AlgorithmIdentifier{ + Algorithm: oidHMACWithSM3, + Parameters: asn1.RawValue{Tag: asn1.TagNull}}, nil + + } + return pkix.AlgorithmIdentifier{}, errors.New("pkcs8: unsupported hash function") +} + +type pbkdf2Params struct { + Salt []byte + IterationCount int + PRF pkix.AlgorithmIdentifier `asn1:"optional"` +} + +func (p pbkdf2Params) DeriveKey(password []byte, size int) (key []byte, err error) { + h, err := newHashFromPRF(p.PRF) + if err != nil { + return nil, err + } + return pbkdf2.Key(password, p.Salt, p.IterationCount, size, h), nil +} + +// PBKDF2Opts contains options for the PBKDF2 key derivation function. +type PBKDF2Opts struct { + SaltSize int + IterationCount int + HMACHash Hash +} + +func (p PBKDF2Opts) DeriveKey(password, salt []byte, size int) ( + key []byte, params KDFParameters, err error) { + + key = pbkdf2.Key(password, salt, p.IterationCount, size, p.HMACHash.New) + prfParam, err := newPRFParamFromHash(p.HMACHash) + if err != nil { + return nil, nil, err + } + params = pbkdf2Params{salt, p.IterationCount, prfParam} + return key, params, nil +} + +func (p PBKDF2Opts) GetSaltSize() int { + return p.SaltSize +} + +func (p PBKDF2Opts) OID() asn1.ObjectIdentifier { + return oidPKCS5PBKDF2 +} diff --git a/pkcs8/kdf_scrypt.go b/pkcs8/kdf_scrypt.go new file mode 100644 index 0000000..102c34f --- /dev/null +++ b/pkcs8/kdf_scrypt.go @@ -0,0 +1,66 @@ +package pkcs8 + +// +// Reference https://datatracker.ietf.org/doc/html/rfc7914 +// + +import ( + "encoding/asn1" + + "golang.org/x/crypto/scrypt" +) + +var ( + oidScrypt = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 11591, 4, 11} +) + +func init() { + RegisterKDF(oidScrypt, func() KDFParameters { + return new(scryptParams) + }) +} + +type scryptParams struct { + Salt []byte + CostParameter int + BlockSize int + ParallelizationParameter int +} + +func (p scryptParams) DeriveKey(password []byte, size int) (key []byte, err error) { + return scrypt.Key(password, p.Salt, p.CostParameter, p.BlockSize, + p.ParallelizationParameter, size) +} + +// ScryptOpts contains options for the scrypt key derivation function. +type ScryptOpts struct { + SaltSize int + CostParameter int + BlockSize int + ParallelizationParameter int +} + +func (p ScryptOpts) DeriveKey(password, salt []byte, size int) ( + key []byte, params KDFParameters, err error) { + + key, err = scrypt.Key(password, salt, p.CostParameter, p.BlockSize, + p.ParallelizationParameter, size) + if err != nil { + return nil, nil, err + } + params = scryptParams{ + BlockSize: p.BlockSize, + CostParameter: p.CostParameter, + ParallelizationParameter: p.ParallelizationParameter, + Salt: salt, + } + return key, params, nil +} + +func (p ScryptOpts) GetSaltSize() int { + return p.SaltSize +} + +func (p ScryptOpts) OID() asn1.ObjectIdentifier { + return oidScrypt +} diff --git a/pkcs8/pkcs8.go b/pkcs8/pkcs8.go new file mode 100644 index 0000000..26819d1 --- /dev/null +++ b/pkcs8/pkcs8.go @@ -0,0 +1,359 @@ +// Package pkcs8 implements functions to parse and convert private keys in PKCS#8 format, as defined in RFC5208 and RFC5958 +package pkcs8 + +import ( + "crypto/ecdsa" + "crypto/rand" + "crypto/rsa" + "crypto/sha1" + "crypto/sha256" + "crypto/sha512" + "crypto/x509/pkix" + "encoding/asn1" + "encoding/pem" + "errors" + "fmt" + "hash" + "strconv" + + "github.com/emmansun/gmsm/sm2" + "github.com/emmansun/gmsm/sm3" + "github.com/emmansun/gmsm/smx509" +) + +// Hash identifies a cryptographic hash function that is implemented in another +// package. +type Hash uint + +const ( + SHA1 Hash = 1 + iota + SHA224 + SHA256 + SHA384 + SHA512 + SHA512_224 + SHA512_256 + SM3 +) + +// HashFunc simply returns the value of h so that Hash implements SignerOpts. +func (h Hash) HashFunc() Hash { + return h +} + +// New returns a new hash.Hash calculating the given hash function. New panics +// if the hash function is not linked into the binary. +func (h Hash) New() hash.Hash { + switch h { + case SM3: + return sm3.New() + case SHA1: + return sha1.New() + case SHA224: + return sha256.New224() + case SHA256: + return sha256.New() + case SHA384: + return sha512.New384() + case SHA512: + return sha512.New() + case SHA512_224: + return sha512.New512_224() + case SHA512_256: + return sha512.New512_256() + + } + panic("pkcs8: requested hash function #" + strconv.Itoa(int(h)) + " is unavailable") +} + +// DefaultOpts are the default options for encrypting a key if none are given. +// The defaults can be changed by the library user. +var DefaultOpts = &Opts{ + Cipher: AES256CBC, + KDFOpts: PBKDF2Opts{ + SaltSize: 8, + IterationCount: 10000, + HMACHash: SHA256, + }, +} + +// KDFOpts contains options for a key derivation function. +// An implementation of this interface must be specified when encrypting a PKCS#8 key. +type KDFOpts interface { + // DeriveKey derives a key of size bytes from the given password and salt. + // It returns the key and the ASN.1-encodable parameters used. + DeriveKey(password, salt []byte, size int) (key []byte, params KDFParameters, err error) + // GetSaltSize returns the salt size specified. + GetSaltSize() int + // OID returns the OID of the KDF specified. + OID() asn1.ObjectIdentifier +} + +// KDFParameters contains parameters (salt, etc.) for a key deriviation function. +// It must be a ASN.1-decodable structure. +// An implementation of this interface is created when decoding an encrypted PKCS#8 key. +type KDFParameters interface { + // DeriveKey derives a key of size bytes from the given password. + // It uses the salt from the decoded parameters. + DeriveKey(password []byte, size int) (key []byte, err error) +} + +var kdfs = make(map[string]func() KDFParameters) + +// RegisterKDF registers a function that returns a new instance of the given KDF +// parameters. This allows the library to support client-provided KDFs. +func RegisterKDF(oid asn1.ObjectIdentifier, params func() KDFParameters) { + kdfs[oid.String()] = params +} + +// for encrypted private-key information +type encryptedPrivateKeyInfo struct { + EncryptionAlgorithm pkix.AlgorithmIdentifier + EncryptedData []byte +} + +// Cipher represents a cipher for encrypting the key material. +type Cipher interface { + // KeySize returns the key size of the cipher, in bytes. + KeySize() int + // Encrypt encrypts the key material. + Encrypt(key, plaintext []byte) (*pkix.AlgorithmIdentifier, []byte, error) + // Decrypt decrypts the key material. + Decrypt(key []byte, parameters *asn1.RawValue, encryptedKey []byte) ([]byte, error) + // OID returns the OID of the cipher specified. + OID() asn1.ObjectIdentifier +} + +var ciphers = make(map[string]func() Cipher) + +// RegisterCipher registers a function that returns a new instance of the given +// cipher. This allows the library to support client-provided ciphers. +func RegisterCipher(oid asn1.ObjectIdentifier, cipher func() Cipher) { + ciphers[oid.String()] = cipher +} + +// Opts contains options for encrypting a PKCS#8 key. +type Opts struct { + Cipher Cipher + KDFOpts KDFOpts +} + +// Unecrypted PKCS8 +var ( + oidPBES2 = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 5, 13} +) + +type pbes2Params struct { + KeyDerivationFunc pkix.AlgorithmIdentifier + EncryptionScheme pkix.AlgorithmIdentifier +} + +func parseKeyDerivationFunc(keyDerivationFunc pkix.AlgorithmIdentifier) (KDFParameters, error) { + oid := keyDerivationFunc.Algorithm.String() + newParams, ok := kdfs[oid] + if !ok { + return nil, fmt.Errorf("pkcs8: unsupported KDF (OID: %s)", oid) + } + params := newParams() + _, err := asn1.Unmarshal(keyDerivationFunc.Parameters.FullBytes, params) + if err != nil { + return nil, errors.New("pkcs8: invalid KDF parameters") + } + return params, nil +} + +func parseEncryptionScheme(encryptionScheme *pkix.AlgorithmIdentifier) (Cipher, error) { + oid := encryptionScheme.Algorithm.String() + newCipher, ok := ciphers[oid] + if !ok { + return nil, fmt.Errorf("pkcs8: unsupported cipher (OID: %s)", oid) + } + cipher := newCipher() + return cipher, nil +} + +// ParsePrivateKey parses a DER-encoded PKCS#8 private key. +// Password can be nil. +// This is equivalent to ParsePKCS8PrivateKey. +func ParsePrivateKey(der []byte, password []byte) (interface{}, KDFParameters, error) { + // No password provided, assume the private key is unencrypted + if len(password) == 0 { + privateKey, err := smx509.ParsePKCS8PrivateKey(der) + return privateKey, nil, err + } + + // Use the password provided to decrypt the private key + var privKey encryptedPrivateKeyInfo + if _, err := asn1.Unmarshal(der, &privKey); err != nil { + if block, _ := pem.Decode(der); block != nil { + return nil, nil, errors.New("pkcs8: this method just supports DER-encoded key") + } + return nil, nil, errors.New("pkcs8: only PKCS #5 v2.0 supported") + } + + if !privKey.EncryptionAlgorithm.Algorithm.Equal(oidPBES2) { + return nil, nil, errors.New("pkcs8: only PBES2 supported") + } + + var params pbes2Params + if _, err := asn1.Unmarshal(privKey.EncryptionAlgorithm.Parameters.FullBytes, ¶ms); err != nil { + return nil, nil, errors.New("pkcs8: invalid PBES2 parameters") + } + + cipher, err := parseEncryptionScheme(¶ms.EncryptionScheme) + if err != nil { + return nil, nil, err + } + + kdfParams, err := parseKeyDerivationFunc(params.KeyDerivationFunc) + if err != nil { + return nil, nil, err + } + + keySize := cipher.KeySize() + symkey, err := kdfParams.DeriveKey(password, keySize) + if err != nil { + return nil, nil, err + } + + encryptedKey := privKey.EncryptedData + decryptedKey, err := cipher.Decrypt(symkey, ¶ms.EncryptionScheme.Parameters, encryptedKey) + if err != nil { + return nil, nil, err + } + + key, err := smx509.ParsePKCS8PrivateKey(decryptedKey) + if err != nil { + return nil, nil, errors.New("pkcs8: incorrect password") + } + return key, kdfParams, nil +} + +// MarshalPrivateKey encodes a private key into DER-encoded PKCS#8 with the given options. +// Password can be nil. +func MarshalPrivateKey(priv interface{}, password []byte, opts *Opts) ([]byte, error) { + if len(password) == 0 { + return smx509.MarshalPKCS8PrivateKey(priv) + } + + if opts == nil { + opts = DefaultOpts + } + + // Convert private key into PKCS8 format + pkey, err := smx509.MarshalPKCS8PrivateKey(priv) + if err != nil { + return nil, err + } + + encAlg := opts.Cipher + salt := make([]byte, opts.KDFOpts.GetSaltSize()) + _, err = rand.Read(salt) + if err != nil { + return nil, err + } + + key, kdfParams, err := opts.KDFOpts.DeriveKey(password, salt, encAlg.KeySize()) + if err != nil { + return nil, err + } + + encryptionScheme, encryptedKey, err := encAlg.Encrypt(key, pkey) + if err != nil { + return nil, err + } + + marshalledParams, err := asn1.Marshal(kdfParams) + if err != nil { + return nil, err + } + keyDerivationFunc := pkix.AlgorithmIdentifier{ + Algorithm: opts.KDFOpts.OID(), + Parameters: asn1.RawValue{FullBytes: marshalledParams}, + } + + encryptionAlgorithmParams := pbes2Params{ + EncryptionScheme: *encryptionScheme, + KeyDerivationFunc: keyDerivationFunc, + } + marshalledEncryptionAlgorithmParams, err := asn1.Marshal(encryptionAlgorithmParams) + if err != nil { + return nil, err + } + encryptionAlgorithm := pkix.AlgorithmIdentifier{ + Algorithm: oidPBES2, + Parameters: asn1.RawValue{FullBytes: marshalledEncryptionAlgorithmParams}, + } + + encryptedPkey := encryptedPrivateKeyInfo{ + EncryptionAlgorithm: encryptionAlgorithm, + EncryptedData: encryptedKey, + } + + return asn1.Marshal(encryptedPkey) +} + +// ParsePKCS8PrivateKey parses encrypted/unencrypted private keys in PKCS#8 format. +// To parse encrypted private keys, a password of []byte type should be provided to the function as the second parameter. +func ParsePKCS8PrivateKey(der []byte, v ...[]byte) (interface{}, error) { + var password []byte + if len(v) > 0 { + password = v[0] + } + privateKey, _, err := ParsePrivateKey(der, password) + return privateKey, err +} + +// ParsePKCS8PrivateKeyRSA parses encrypted/unencrypted private keys in PKCS#8 format. +// To parse encrypted private keys, a password of []byte type should be provided to the function as the second parameter. +func ParsePKCS8PrivateKeyRSA(der []byte, v ...[]byte) (*rsa.PrivateKey, error) { + key, err := ParsePKCS8PrivateKey(der, v...) + if err != nil { + return nil, err + } + typedKey, ok := key.(*rsa.PrivateKey) + if !ok { + return nil, errors.New("pkcs8: key block is not of type RSA") + } + return typedKey, nil +} + +// ParsePKCS8PrivateKeyECDSA parses encrypted/unencrypted private keys in PKCS#8 format. +// To parse encrypted private keys, a password of []byte type should be provided to the function as the second parameter. +func ParsePKCS8PrivateKeyECDSA(der []byte, v ...[]byte) (*ecdsa.PrivateKey, error) { + key, err := ParsePKCS8PrivateKey(der, v...) + if err != nil { + return nil, err + } + typedKey, ok := key.(*ecdsa.PrivateKey) + if !ok { + return nil, errors.New("pkcs8: key block is not of type ECDSA") + } + return typedKey, nil +} + +// ParsePKCS8PrivateKeySM2 parses encrypted/unencrypted private keys in PKCS#8 format. +// To parse encrypted private keys, a password of []byte type should be provided to the function as the second parameter. +func ParsePKCS8PrivateKeySM2(der []byte, v ...[]byte) (*sm2.PrivateKey, error) { + key, err := ParsePKCS8PrivateKey(der, v...) + if err != nil { + return nil, err + } + typedKey, ok := key.(*sm2.PrivateKey) + if !ok { + return nil, errors.New("pkcs8: key block is not of type SM2") + } + return typedKey, nil +} + +// ConvertPrivateKeyToPKCS8 converts the private key into PKCS#8 format. +// To encrypt the private key, the password of []byte type should be provided as the second parameter. +// +// The only supported key types are RSA and ECDSA (*rsa.PrivateKey or *ecdsa.PrivateKey for priv) +func ConvertPrivateKeyToPKCS8(priv interface{}, v ...[]byte) ([]byte, error) { + var password []byte + if len(v) > 0 { + password = v[0] + } + return MarshalPrivateKey(priv, password, nil) +} diff --git a/pkcs8/pkcs8_test.go b/pkcs8/pkcs8_test.go new file mode 100644 index 0000000..938ad06 --- /dev/null +++ b/pkcs8/pkcs8_test.go @@ -0,0 +1,611 @@ +package pkcs8_test + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/rsa" + "encoding/pem" + "testing" + + "github.com/emmansun/gmsm/sm2" + "github.com/emmansun/gmsm/pkcs8" +) + +const rsa2048 = `-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDBMF0LikTFOU/T +8DUDSvORootvhUD67f6AXmEnntfXRvQ3O91+qt40tevS8JtFaq4gKxugRjjZRtni +50aUGcEZ4leq3DboBL9XH089IEmxxLbJeJIXxgPeRHrXRINvUSspwRrJkX6fnXyi +MdRhqdH2tG1yrXKkt9UvdSHfRYimDcJ+ry2zYlcbz9aoLDO1vEdS/IBu0jXAZ/Z/ +xaEVfkoWMzZM2SU+lfJeyzobii00VXGuSQKnI8E/e16kDpBXJ6PFSm6EyZmAad6O +f+B9d/ZEXGQlbaooG54v5sGj54mg7m/75qMaxL2H8NER31gAeyvoyovfXI0vbswH +8AozxGwDAgMBAAECggEAautIY62nt/urKaIExQjDWvO59gOq3fW/5+3UGWh5DqUv +Xi5cvND2X/fbR4hwdu++5QDWrlKO/fmPd1wGnMrQK3IwkNiF7s1J1H74jN0EzEUR +4NlBCbVGyMnfrqo1j/M9T0OXfr1udgpkQyQO5epl0QM0m8ZQ78bqTvSlxXsnULbQ +py0Tx0uCWaP6FzDsZ+t2rj/SVH7hQNf8ITfQJhVol/n5Hza4+NRfp/DPXWZEvPlo +GeMs9PDCa16tw8wI9EUnmFaeFlmtJPdTs5rVo9Ya/zmtoxN6AGTCG0IE6YRvh3Qn +jttIp2QitOSBKmXpu1ZI6UTtimGgnfiJKK1BGVaMOQKBgQDfF6ZBMY/tLmDg1mgS +QQKAOWMB0/3CvzcM96R0VACO2vr1BbePMXQQ/i27rD001Xl2wNTsETRk1Ji6btwQ +64m4uxRSZCJmYyBAcJjfBtMWIDiihQTL55NFTd9YIPmqGmbj1ASQgtpQR5Cq/5YR +9Vu0kTxMmADoiq1tR2VGZeScnwKBgQDdr4ITDFGSpqWKnyHQaQgTIW4uxQ5pQKIx +aKbCNZOtSgJfqUCY+8gJMkFOtQzawrburD4qllFxdqhHLiXSx6/8zSTrsiexml2i +7HxUZaSmn5Q4HFNngKKHXd4NGsWp237k8fJ2953KX89yEov8FpIiq6qvZH/LS8DN ++GORAPSSHQKBgCHobUuRZefN2cmyrOTBXsjwb/zyJKq593sQFL3dmqwb2nLtaIXq +JVMD3x2cQz1JiQmkq3gp8UW2DnSfrvEfa7JZNPCE6bmYLWm9825KkkDVquYAw8be +LsMk3+J8OJZDJwpPylXQnbAAAJwM9tlJ6qNaQ8j8fX7avRtT86+sgv/PAoGABjJp +yG6HuTm/Vuir4U+OUjqVAemwRXDxF8B9KOCmiCmRd2sbyyr+pIMrIDAfc94Njw5x +jm81R56xhYvcss+yM7boWU5ZnbVa+LrznshYme/MDOV9z17hLDeLhYJCFEV2fp/k +zz6MwqN7AQ1TrHBVFXMHCnAcwmoTsa5H2j3UmGECgYEAvvJ+o5+FPnBs+VU5FJxF +fAGFpF3AwfbSCm2ARZOxMHAkpsz/FBXlo+rVZv6loTKTPQFMxIB15il7ls0CGI9q +6UaZ5hkKjEOQUW8UYc8Cv0xpSkcuxcGrWzw4AMdc84XXi6F1+48ab9Gt0pN3tgUG +qg+KU+JDsQLHHmykZ92cHPA= +-----END PRIVATE KEY----- +` + +const encryptedRSA2048aes = `-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFLTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQIrrW09+9XumECAggA +MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAEqBBAxglavSPtrKNsM9cDXmrS1BIIE +0Gy226c9+zxZ8jUsUIbDdsq1mPbqAWs1xImAj4nA7NMv6G/5QH9CrsmB+4r4GIiy +CafN1W9YvFg3SISUbe+h156Pt2iKoZlVCbSa4XVo4diwmjloZIHM4Jk0Pu28CbJo +QDVwPCuWMKppkfwr63RT+FBSfBEBaRCi4eXz6tOcMduBOlaiQvSREvDCCOeY9gja +RgvyUa2Hf8oHNkSG9yXoMrvz0FayMWK/i7LU+2NqiPZVTvfGkqNkJJF/M7INKgLs +d6A1hgyA7HVv4czQOPQJCArXeCycI1EJ4uSthJxqd/iYX0z52Tfa7q/0oAZ4HZt+ +wmcov8GwqfAg7Cu9soifYwfMYTghXOX2UKmQa/0UNK5ibj5cC9+oA09Ucx5twKDs +nwSGEIb+7qNhZSRtEXtOL7bxQL8PUvAXWrTXluvZ+bv/9S53XYPL4E95rrLnTF/L +csEYleNIpY/6HkPFtqPZiWCsVUZep9uPjZo29kh/246yKBFjsw5mXtm1S6ha4Xb9 +gUxqKQiWe9+tCkPHRVo2KJX1H4Al7UB9GqDR5oUhIayp6nYCeI/dLwPpikq1F8HO +iJva/qV2iltxwyQHhEenyM9TPkPawqOOUKvDd1hZR0wzABcC3koLtwwKyEGzQPPW +bxp5GBim9Pu/EGWY1d1H38eVu44jRP/3ONk8wvZcsIbn6U8bOeToUFmcjuuQ3cxf +pDUruIA9PjWL9Se6TI3CytTUCbCb4bKRP+eE0B2LPwq6+dyvcY2yidYj9C2D25tb +F+E1Wr7ro97OXQ8grMWwrTpZ9rUzmz5wzYWmOFaKJRiepkuUpx4HWl+fKn5r5LyV ++cyYoSjApNgHe/9Pz7mNXNdeSmWcn4BVs1XgKi1MiJNWn5tNlKB3kz1kgraKOWbs +9/dspegd5fQ6Lzvlt7CsJh/I76rE+90LAbXWVlQ/jm/4jrWownjW1oVIj0Xfxx7i +UlmtTFoCIvNWRyoyK5pfL0JvxOtd5leHZniJoww0CPKYS0mibxYLc883Q5Hq5ZH/ +C7iBJN0aDJfVfkl0o4EQWaWQS0rAInhe7xTHmFFe5NP9lVTEwQt+C/fz7qalHe9P +ulV8MsT/vg2/9twvxKbVCSzaDyta/TyhX76LTULprPr6ahDhP9rybmmK548m86kZ +IxWdmed7Pt3YPeEImoLBoXh8eaWpYDlX2Be5/eqjw2wbg6srBKoA7swSkMsFXm5q ++HgF4X6R9lfmLjs/UMOi9SM6ODh4xgq1DxX+bJZLfJwXj90i56Ij8OhjcBJ+DwUi +ntosYkXp6lMZIyfI3jWG4IZwE9nt8oXJZfUtIU5mYF9DAV92fRwm1mCLMx0iznv1 +bvCu7yJ51nWB3xkIOqCYbzbREWmL+6/akGOqu1KDrFKBu0IyAqUWt0XrY3b4V5jm +WjTXywDkCcGC6W0t4yhu1Yz8QhE5Giw2PHwwZ3940QZQsFcBM6RJOcnkbYTu8TFm +7s9ZItSShwAN/i1nN1daF9lgdm4WKHWd/jqHIgl2NijiDgb5F5YaWgurKg9tOrEK +oGJlPmBUiNynhqcz69ljjW6q4U2cfF4g6Onl2sucLdsFXejgVdsKBVXw+gjGr2TS +lgmeHTcvZmTShvbN/TrHETjO7jEB4V2I4a4L7uybuWF/ +-----END ENCRYPTED PRIVATE KEY----- +` + +const encryptedRSA2048des3 = `-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFHDBOBgkqhkiG9w0BBQ0wQTApBgkqhkiG9w0BBQwwHAQIKXB+OWAc6pwCAggA +MAwGCCqGSIb3DQIJBQAwFAYIKoZIhvcNAwcECCeQ2z+ohlaTBIIEyAbgxv69udAD +N0JzM0+E/dYKTRxEVED1cyY+fcROfxkJvT3FOOWS65vaPo5i8n0e7pFHvm/ezkoL +mNRYhoyd45pog4ALJ6O03bUBTLJPiowz90uHC7GWQelMl7LeNyX/7/5s2jOpgW82 +oB6JizF9SjZzCTzKTmZLOAz3GjIERWHmoIczy40nxP7zmHzVrxTp1V6gnzxgUIuA +X/7FTMRlWvEbX9gzODx7stI/5/bLla1Y7PDWEs2aJCnsN7pXJSd0Ry2/iBnQKe6n +p4RW7jRAiFTGXbR1E5ZoFsSUs0K9JLEJA+kq6x+smRGxioV3I/r6MLaeumNZ37Bx +9OfiJAWk0Ei9EUjM4ZLWjnhgRyI2mThEXTbCevv2GonwG9G968QEMjfbXcLA6Opt +0mmRutT6IgvflEZRi9BlmCGOecNHl+cojVCwmAPZKkk2e9lZe+x9+TXW66GJVFiK +6BlgRwTcNPKePCYWPjsV5wUZACq0Y61nksBViyRUFsEkEEYMXIbh6bbUTTlJg/tk +tCp/LF9oTf1XacJ8a/s6oLuz95R07u9E/liibzVavK0nVNSR5Xdo7QDivWxnaSLd +wt8qUOnVbW0eSyq2BAKK7yvZfhz44D9WS8M8jp8gwj7Eti81LGqeh5IvqekDYmoz +BFiY24PnRcZnpETA/e6v5dNrpE/OLHmdY1ag6aifIJCc1UG84Oi/nPBTZ7eHLGCd +Kn4/9xdCVHd4077Qx9JLW9LutZXkqYaBckOEHtvaMfyWUaXiNty/N5RECGvn5wmM +dwC6td6CqtojiHOB7GAUiwjHgbQLpNoIz1BiVTIo1eoD32+4RHYUxNmhsk0r22Zf +ZnfnKBGgV7KKNKP3eFQnzSeNE0qFd5AtSpeJX0G0IsbuvXOE/7P0pj7DhD4HoYS7 +Mf2za6Wm/CVWNM4ekc3MsKb9D+ogzdQ4VYI2mzBdLulrYvfPCE6SHZlZ+ePE4LSr +jexB6LYLZJU7Bxnslt9E/mjSzWHctF9LhHf7sl7NUhCHdDvij6Hd0l4+noQlDTtd +rnXgL9fTjSfaVDv3Rt+AmNN9Cu9Y1FSBLYMe8LfGXXUPg86bTbGk3uFjkyIY3hE2 +/Kz1re4KXdDdjYe4ja5qZK8fWx0704NkzH1UO/HMd4Cnx3agyVeyJVG3iRSIesRG +vI1ESJMMv1+MMGiWwRzHYvv7yrqp2steAVwjGu26/s1vwkZrvy8SjzYAXo1RLT9o +cNnlPra6N7xReSohOibAp0kx1d81JqvEOvJIhR7KDXSRutgIPlqQgHXtmDW/VlCb +w05Ptg3SXaCE0+pY0T+FYHusA3JEmyU0e629Ed/dl/j7Xpynl1V6/ndf3gdRGX0l +d2IGneJsnj8yvP0dUsB2l71W/ZIM3HERDLxP9JByyINCBQ1BFsN81qUXpj6vGYjb +hPyUmmsAGibXJOiGzmaP3nGgF9qbe1XiTRdbm2AZ3pEaJxkkFWsT+Yivz9yzZE0P +3/w14HvS94X/Z2+yDLtQQgsLNkfw/Gpc4O0GMnLuOl4KSaTA37IdJR2jOFP7LtHR +9Egbm93atZWSAyTO7OtZGmna6k6eGUsk8Dxp7cWOUkLf7C5sL6l3bBH7omlQHx9P +RIiDkxAd7hbpm4/C/DoUZQ== +-----END ENCRYPTED PRIVATE KEY----- +` + +const encryptedRSA2048scrypt = `-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFJTBPBgkqhkiG9w0BBQ0wQjAhBgkrBgEEAdpHBAswFAQIY6+u2Dcq3hwCAkAA +AgEIAgEBMB0GCWCGSAFlAwQBKgQQ6Kut7Q560w1e+fqSiF6uUgSCBNBWRJP19DiT +m/ZWEh4ukxTnrBpx59ATbuiBZjjty9vw/dkusUivNLsIoJDezuv4YxjxNx4zZsv+ +vI5/gWT78XdF0XHgrRKjB0AvQ4rdVSUhV2/sxMa8P5bwE7NikovkzP1rM0cPCLRE +K5J81+pEOVKumJJg3jNtK18HtCiH0D4n276xK6fJ2BptA1BMNhlDkoz99kmwPfhg +gMiJxbcGrYHMvCZAL8towTRomI82fjRwpEtT8eZ7aLUALDM53JXzhiz/bO2cKCRx +4oLx6rChrqCTS4bZ++PPBS9klwW1kx5eMTGdv3IS+/Y7wvPtZ9jbwcjkSKpOsALv +h/6CzUuTo5dIPDaOidLLHS4bfgKCC/da/uuow/ET7K6KBOZ6kCnXi300D+hZE8cJ +GYjIQGVY3FtrtZx55hjeqyRsVrdKP0e83wNEnGgofsgeJ+H88zUxaMqIiz3e773M +zshNXcCko9jAgr8PwRg7ARPql+TcS3fJ+HPBA1mDlT4xMXyFOgckMkz8xR08EA0M +UcvtGAxLJYtsiMJigdrCI7lGmWZbj8tB2sS0JD95QsbR5CcsqzaELzoKMdOpG5MP +4ZdgHpeGNtw15aAIdxfoFGGcNgLiZ+y7BC0fM9xYAPARrb64A3e3gmsJ3ZKEkZzR +MbK9a08S+6VI68T9M0f3i53p/e09CYZ0TN3yMN/g/usxERzpji7zCjEYf6yuUeRF +c3ceVVaxldexAOV0dEIUq8xehUhvhV129/hUHUyqsx1XiURWSx2TRSjuZ3SE63Sc +LO81rijz4rFa69JXPGWNrzR0IS0CY8aMF79fwqpcLaRHIpfQLiIQ19qDHiipXCs/ +ZLli5MZQZ7AHoXqbHBQOqhiT2LLEgeVF4uEi0qM1ULfmmZMoJQg+ugRXPEJR0fa0 +ji6Hb/ZTDGwsdrNZGfTD4lJeiel3IPVcOzfeZqb6OsdkUSQzZZSvAET95qkKn/CN +diPkX96iYuhjcace/f8xLnVY3TJ6WRpDW9oBzVFEm5jXtlHhVltau2Qmoi28pthE +25QrNfoOs4qr2gaGA37VXSEW4yLU3jyqlP1esXxyEiqg9CKPnk/K/XxREjGJXElr +FQtRif9b4QDBrZc38Y5ct7x+Ce7llJ3kKslVdF2rbVEn4nPIHIqw8oKDv/6+CNwo +8O1B4u16WUqj2Th6hOQcmWb9Nb6Js5TSRtIJxrif6PTfTczSB9bZgU1fTxgr0tTI +AERJLqFA9dvCxAehWrlegsSOwvJ/E8FwbGJLhiJs6aFk1fJ6NPkp62UkmvBMDq1w +qYuwLSr920KrPsCYBa09Ldm9e88+nCQz5QWcJt2vvdIz0UQUqtsjUo8DWL0qNXgU +JVSRrfE+64II2sxt3/9oywLCk9DG+dcWZH6SRjSt7y9KhWfhdGq1S6Og1Mjc+U4A +L/TgycaVTBodGTmw5YlsbSBYzAwSBCaR7GLThhZqIlPrk6P3w8VZJ1B14nEcTP9Y +GVdcEOqE0mwNtWZYcuy1cqPj6g/p9NOmLOnT8HGbjw9qtdl+iEGN/ZDWfu+En7ES +Dv4v0MiWAMArKY8rAMWa9/phbWXVEtNz6RnJ460qxIax5GR0QPce3+lrswhmXSm4 +RNXdI4NIGtOdg8zwuKI5AefoLlWjt56Pzg== +-----END ENCRYPTED PRIVATE KEY----- +` + +const ec256 = `-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgjLFzKb/8hsdSmPft +s98RZ7AWzQnLDsMhy6v+/3BZlZ6hRANCAASKkodoH+hHmBfwoFfrvv1E+iMLt3g1 +s6hxOUMbkv6ZTVFXND/3z9zlJli6/YGrlSnsHOJc0GbwSYD1AMwZyr0T +-----END PRIVATE KEY----- +` + +const encryptedEC256aes = `-----BEGIN ENCRYPTED PRIVATE KEY----- +MIHsMFcGCSqGSIb3DQEFDTBKMCkGCSqGSIb3DQEFDDAcBAjVvKZtHlmIbAICCAAw +DAYIKoZIhvcNAgkFADAdBglghkgBZQMEASoEEL3jdkBvObn+QELgKVE2cnMEgZAl +wgo3AjtXevJaGgep5GsW2krw9S7dC7xG9dR33Z/a9nBnO1rKm7Htf0+986w/1vmj +4k3M2QiI/VY+tnDFE+46DLLKYtJGRT1aoAH+mwhzaQGwzJnKhbeA23aE0f7KWCAK ++f999+SeHWro7FiRZjHEYVVLGQr/I7K5Wyh24YjN2nR4CU4X+GQU25My/pgSRog= +-----END ENCRYPTED PRIVATE KEY----- +` + +const ec128 = `-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgjLFzKb/8hsdSmPft +s98RZ7AWzQnLDsMhy6v+/3BZlZ6hRANCAASKkodoH+hHmBfwoFfrvv1E+iMLt3g1 +s6hxOUMbkv6ZTVFXND/3z9zlJli6/YGrlSnsHOJc0GbwSYD1AMwZyr0T +-----END PRIVATE KEY-----` + +const encryptedEC128aes = `-----BEGIN ENCRYPTED PRIVATE KEY----- +MIHeMEkGCSqGSIb3DQEFDTA8MBsGCSqGSIb3DQEFDDAOBAg7qE4RYQEEugICCAAw +HQYJYIZIAWUDBAECBBBa+6eKv6il/iEjOw8/AmEHBIGQ24YmBiMfzjJjFU+PAwXr +zCfR3NPOHBwn3+BkpyivaezSrFWIF919cnDyI15Omd+Iz2oljrT/R4IDC9NOmoAy +5uKixYGAOi74Qr9kdgrT2Bfvu9wq+dYqPwLjR4WFHl2ofrLn7RCaOa8mOh3bgfHP +SnXPiACchx53PDh6bZTIZ0V9v0ymcMuXf758OXbUmSGN +-----END ENCRYPTED PRIVATE KEY-----` + +const encryptedEC256aes128sha1 = `-----BEGIN ENCRYPTED PRIVATE KEY----- +MIHeMEkGCSqGSIb3DQEFDTA8MBsGCSqGSIb3DQEFDDAOBAgEoFG3x07DbQICCAAw +HQYJYIZIAWUDBAECBBCRN9PNX9rBqXhaHLUOsv7YBIGQFfXAPPV+COWABJdSarog +eUHFNaQ+R6x55Tz/mquNIwiOrP9DNoEd1PGtKaHaO+ACSEQwMfrGeh8BuNV69EwP +bhsob/MZeexRbrLe2YN7Y7/Y0wpujalGlliMvs35f1fpq/9RfVU+qRpFED2lT4dm +zOuhMC9Oo3oMYlbEXAT9mq33MkGKMUth2ek/bQIvnCHG +-----END ENCRYPTED PRIVATE KEY----- +` + +// From https://tools.ietf.org/html/rfc7914 +const encryptedRFCscrypt = `-----BEGIN ENCRYPTED PRIVATE KEY----- +MIHiME0GCSqGSIb3DQEFDTBAMB8GCSsGAQQB2kcECzASBAVNb3VzZQIDEAAAAgEI +AgEBMB0GCWCGSAFlAwQBKgQQyYmguHMsOwzGMPoyObk/JgSBkJb47EWd5iAqJlyy ++ni5ftd6gZgOPaLQClL7mEZc2KQay0VhjZm/7MbBUNbqOAXNM6OGebXxVp6sHUAL +iBGY/Dls7B1TsWeGObE0sS1MXEpuREuloZjcsNVcNXWPlLdZtkSH6uwWzR0PyG/Z ++ZXfNodZtd/voKlvLOw5B3opGIFaLkbtLZQwMiGtl42AS89lZg== +-----END ENCRYPTED PRIVATE KEY----- +` + +func TestParsePKCS8PrivateKeyRSA(t *testing.T) { + keyList := []struct { + name string + clear string + encrypted string + }{ + { + name: "encryptedRSA2048aes", + clear: rsa2048, + encrypted: encryptedRSA2048aes, + }, + { + name: "encryptedRSA2048des3", + clear: rsa2048, + encrypted: encryptedRSA2048des3, + }, + } + for i, key := range keyList { + t.Run(key.name, func(t *testing.T) { + block, _ := pem.Decode([]byte(key.encrypted)) + _, err := pkcs8.ParsePKCS8PrivateKeyRSA(block.Bytes, []byte("password")) + if err != nil { + t.Errorf("%d: ParsePKCS8PrivateKeyRSA returned: %s", i, err) + } + _, err = pkcs8.ParsePKCS8PrivateKeyRSA(block.Bytes, []byte("wrong password")) + if err == nil { + t.Errorf("%d: should have failed", i) + } + _, err = pkcs8.ParsePKCS8PrivateKeyRSA(block.Bytes) + if err == nil { + t.Errorf("%d: should have failed", i) + } + + block, _ = pem.Decode([]byte(key.clear)) + _, err = pkcs8.ParsePKCS8PrivateKeyRSA(block.Bytes) + if err != nil { + t.Errorf("%d: ParsePKCS8PrivateKeyRSA returned: %s", i, err) + } + }) + } +} + +func TestParsePKCS8PrivateKeyECDSA(t *testing.T) { + keyList := []struct { + name string + clear string + encrypted string + }{ + { + name: "encryptedEC256aes", + clear: ec256, + encrypted: encryptedEC256aes, + }, + } + for i, key := range keyList { + t.Run(key.name, func(t *testing.T) { + block, _ := pem.Decode([]byte(key.encrypted)) + _, err := pkcs8.ParsePKCS8PrivateKeyECDSA(block.Bytes, []byte("password")) + if err != nil { + t.Errorf("%d: ParsePKCS8PrivateKeyECDSA returned: %s", i, err) + } + _, err = pkcs8.ParsePKCS8PrivateKeyECDSA(block.Bytes, []byte("wrong password")) + if err == nil { + t.Errorf("%d: should have failed", i) + } + _, err = pkcs8.ParsePKCS8PrivateKeyECDSA(block.Bytes) + if err == nil { + t.Errorf("%d: should have failed", i) + } + + block, _ = pem.Decode([]byte(key.clear)) + _, err = pkcs8.ParsePKCS8PrivateKeyECDSA(block.Bytes) + if err != nil { + t.Errorf("%d: ParsePKCS8PrivateKeyECDSA returned: %s", i, err) + } + }) + } +} + +func TestParsePKCS8PrivateKey(t *testing.T) { + keyList := []struct { + name string + clear string + encrypted string + password string + }{ + { + name: "encryptedRSA2048aes", + clear: rsa2048, + encrypted: encryptedRSA2048aes, + password: "password", + }, + { + name: "encryptedRSA2048des3", + clear: rsa2048, + encrypted: encryptedRSA2048des3, + password: "password", + }, + { + name: "encryptedRSA2048scrypt", + clear: rsa2048, + encrypted: encryptedRSA2048scrypt, + password: "password", + }, + { + name: "encryptedEC256aes", + clear: ec256, + encrypted: encryptedEC256aes, + password: "password", + }, + { + name: "encryptedEC256aes128sha1", + clear: ec256, + encrypted: encryptedEC256aes128sha1, + password: "password", + }, + { + name: "encryptedRFCscrypt", + clear: "", + encrypted: encryptedRFCscrypt, + password: "Rabbit", + }, + { + name: "encryptedEC128aes", + clear: ec128, + encrypted: encryptedEC128aes, + password: "password", + }, + } + for i, key := range keyList { + t.Run(key.name, func(t *testing.T) { + block, _ := pem.Decode([]byte(key.encrypted)) + _, err := pkcs8.ParsePKCS8PrivateKey(block.Bytes, []byte(key.password)) + if err != nil { + t.Errorf("%d: ParsePKCS8PrivateKey returned: %s", i, err) + } + _, err = pkcs8.ParsePKCS8PrivateKey(block.Bytes, []byte("wrong password")) + if err == nil { + t.Errorf("%d: should have failed", i) + } + _, err = pkcs8.ParsePKCS8PrivateKey(block.Bytes) + if err == nil { + t.Errorf("%d: should have failed", i) + } + + if key.clear != "" { + block, _ = pem.Decode([]byte(key.clear)) + _, err = pkcs8.ParsePKCS8PrivateKey(block.Bytes) + if err != nil { + t.Errorf("%d: ParsePKCS8PrivateKey returned: %s", i, err) + } + } + }) + } +} + +func TestConvertPrivateKeyToPKCS8(t *testing.T) { + for i, password := range [][]byte{nil, []byte("password")} { + var args [][]byte + if password != nil { + args = append(args, password) + } + rsaPrivateKey, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + t.Fatalf("%d: GenerateKey returned: %s", i, err) + } + der, err := pkcs8.ConvertPrivateKeyToPKCS8(rsaPrivateKey, args...) + if err != nil { + t.Fatalf("%d: ConvertPrivateKeyToPKCS8 returned: %s", i, err) + } + decodedRSAPrivateKey, err := pkcs8.ParsePKCS8PrivateKey(der, args...) + if err != nil { + t.Fatalf("%d: ParsePKCS8PrivateKey returned: %s", i, err) + } + if rsaPrivateKey.D.Cmp(decodedRSAPrivateKey.(*rsa.PrivateKey).D) != 0 { + t.Fatalf("%d: Decoded key does not match original key", i) + } + + for _, curve := range []elliptic.Curve{ + elliptic.P224(), elliptic.P256(), elliptic.P384(), elliptic.P521(), + } { + ecPrivateKey, err := ecdsa.GenerateKey(curve, rand.Reader) + if err != nil { + t.Fatalf("%d, %s: GenerateKey returned: %s", i, curve, err) + } + der, err = pkcs8.ConvertPrivateKeyToPKCS8(ecPrivateKey, args...) + if err != nil { + t.Fatalf("%d, %s: ConvertPrivateKeyToPKCS8 returned: %s", i, curve, err) + } + decodedECPrivateKey, err := pkcs8.ParsePKCS8PrivateKey(der, args...) + if err != nil { + t.Fatalf("%d, %s: ParsePKCS8PrivateKey returned: %s", i, curve, err) + } + if ecPrivateKey.D.Cmp(decodedECPrivateKey.(*ecdsa.PrivateKey).D) != 0 { + t.Fatalf("%d, %s: Decoded key does not match original key", i, curve) + } + } + } +} + +func TestMarshalPrivateKey(t *testing.T) { + for i, tt := range []struct { + password []byte + opts *pkcs8.Opts + }{ + { + password: nil, + opts: nil, + }, + { + password: []byte("password"), + opts: &pkcs8.Opts{ + Cipher: pkcs8.SM4CBC, + KDFOpts: pkcs8.PBKDF2Opts{ + SaltSize: 8, IterationCount: 2048, HMACHash: pkcs8.SM3, + }, + }, + }, + { + password: []byte("password"), + opts: &pkcs8.Opts{ + Cipher: pkcs8.SM4GCM, + KDFOpts: pkcs8.PBKDF2Opts{ + SaltSize: 8, IterationCount: 2048, HMACHash: pkcs8.SM3, + }, + }, + }, + { + password: []byte("password"), + opts: &pkcs8.Opts{ + Cipher: pkcs8.AES128CBC, + KDFOpts: pkcs8.PBKDF2Opts{ + SaltSize: 8, IterationCount: 2048, HMACHash: pkcs8.SHA224, + }, + }, + }, + { + password: []byte("password"), + opts: &pkcs8.Opts{ + Cipher: pkcs8.AES128CBC, + KDFOpts: pkcs8.PBKDF2Opts{ + SaltSize: 8, IterationCount: 2048, HMACHash: pkcs8.SHA256, + }, + }, + }, + { + password: []byte("password"), + opts: &pkcs8.Opts{ + Cipher: pkcs8.AES128CBC, + KDFOpts: pkcs8.PBKDF2Opts{ + SaltSize: 8, IterationCount: 2048, HMACHash: pkcs8.SHA512, + }, + }, + }, + { + password: []byte("password"), + opts: &pkcs8.Opts{ + Cipher: pkcs8.AES128CBC, + KDFOpts: pkcs8.PBKDF2Opts{ + SaltSize: 8, IterationCount: 2048, HMACHash: pkcs8.SHA384, + }, + }, + }, + { + password: []byte("password"), + opts: &pkcs8.Opts{ + Cipher: pkcs8.AES128CBC, + KDFOpts: pkcs8.PBKDF2Opts{ + SaltSize: 8, IterationCount: 2048, HMACHash: pkcs8.SHA512_224, + }, + }, + }, + { + password: []byte("password"), + opts: &pkcs8.Opts{ + Cipher: pkcs8.AES128CBC, + KDFOpts: pkcs8.PBKDF2Opts{ + SaltSize: 8, IterationCount: 2048, HMACHash: pkcs8.SHA512_256, + }, + }, + }, + { + password: []byte("password"), + opts: &pkcs8.Opts{ + Cipher: pkcs8.AES192CBC, + KDFOpts: pkcs8.PBKDF2Opts{ + SaltSize: 8, IterationCount: 1000, HMACHash: pkcs8.SHA256, + }, + }, + }, + { + password: []byte("password"), + opts: &pkcs8.Opts{ + Cipher: pkcs8.AES256CBC, + KDFOpts: pkcs8.PBKDF2Opts{ + SaltSize: 16, IterationCount: 2000, HMACHash: pkcs8.SHA256, + }, + }, + }, + { + password: []byte("password"), + opts: &pkcs8.Opts{ + Cipher: pkcs8.AES128GCM, + KDFOpts: pkcs8.PBKDF2Opts{ + SaltSize: 8, IterationCount: 2048, HMACHash: pkcs8.SHA256, + }, + }, + }, + { + password: []byte("password"), + opts: &pkcs8.Opts{ + Cipher: pkcs8.AES192GCM, + KDFOpts: pkcs8.PBKDF2Opts{ + SaltSize: 8, IterationCount: 10000, HMACHash: pkcs8.SHA256, + }, + }, + }, + { + password: []byte("password"), + opts: &pkcs8.Opts{ + Cipher: pkcs8.AES256GCM, + KDFOpts: pkcs8.PBKDF2Opts{ + SaltSize: 16, IterationCount: 16, HMACHash: pkcs8.SHA256, + }, + }, + }, + { + password: []byte("password"), + opts: &pkcs8.Opts{ + Cipher: pkcs8.DESCBC, + KDFOpts: pkcs8.PBKDF2Opts{ + SaltSize: 16, IterationCount: 16, HMACHash: pkcs8.SHA1, + }, + }, + }, + { + password: []byte("password"), + opts: &pkcs8.Opts{ + Cipher: pkcs8.TripleDESCBC, + KDFOpts: pkcs8.PBKDF2Opts{ + SaltSize: 16, IterationCount: 16, HMACHash: pkcs8.SHA1, + }, + }, + }, + { + password: []byte("password"), + opts: &pkcs8.Opts{ + Cipher: pkcs8.AES256CBC, + KDFOpts: pkcs8.ScryptOpts{ + CostParameter: 1 << 2, + BlockSize: 8, + ParallelizationParameter: 1, + SaltSize: 16, + }, + }, + }, + } { + rsaPrivateKey, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + t.Fatalf("%d: GenerateKey returned: %s", i, err) + } + der, err := pkcs8.MarshalPrivateKey(rsaPrivateKey, tt.password, tt.opts) + if err != nil { + t.Fatalf("%d: MarshalPrivateKey returned: %s", i, err) + } + decodedRSAPrivateKey, _, err := pkcs8.ParsePrivateKey(der, tt.password) + if err != nil { + t.Fatalf("%d: ParsePKCS8PrivateKey returned: %s", i, err) + } + if rsaPrivateKey.D.Cmp(decodedRSAPrivateKey.(*rsa.PrivateKey).D) != 0 { + t.Fatalf("%d: Decoded key does not match original key", i) + } + + sm2PrivateKey, err := sm2.GenerateKey(rand.Reader) + if err != nil { + t.Fatalf("%d: GenerateKey returned: %s", i, err) + } + + der, err = pkcs8.MarshalPrivateKey(sm2PrivateKey, tt.password, tt.opts) + if err != nil { + t.Fatalf("%d: MarshalPrivateKey returned: %s", i, err) + } + + decodedSM2PrivateKey, _, err := pkcs8.ParsePrivateKey(der, tt.password) + if err != nil { + t.Fatalf("%d: ParsePKCS8PrivateKey returned: %s", i, err) + } + if !sm2PrivateKey.Equal(decodedSM2PrivateKey) { + t.Fatalf("%d: Decoded key does not match original key", i) + } + + for _, curve := range []elliptic.Curve{ + elliptic.P224(), elliptic.P256(), elliptic.P384(), elliptic.P521(), + } { + ecPrivateKey, err := ecdsa.GenerateKey(curve, rand.Reader) + if err != nil { + t.Fatalf("%d, %s: ConvertPrivateKeyToPKCS8 returned: %s", i, curve, err) + } + der, err = pkcs8.MarshalPrivateKey(ecPrivateKey, tt.password, tt.opts) + if err != nil { + t.Fatalf("%d, %s: ConvertPrivateKeyToPKCS8 returned: %s", i, curve, err) + } + decodedECPrivateKey, _, err := pkcs8.ParsePrivateKey(der, tt.password) + if err != nil { + t.Fatalf("%d, %s: ParsePKCS8PrivateKey returned: %s", i, curve, err) + } + if ecPrivateKey.D.Cmp(decodedECPrivateKey.(*ecdsa.PrivateKey).D) != 0 { + t.Fatalf("%d, %s: Decoded key does not match original key", i, curve) + } + } + } +} + +type unknown int + +func TestUnknownTypeFailure(t *testing.T) { + badInput := unknown(0) + _, err := pkcs8.ConvertPrivateKeyToPKCS8(badInput, []byte("password")) + if err == nil { + t.Fatal("expected error") + } +}