diff --git a/pkcs12.go b/pkcs12.go index 6c8afac..1fce710 100644 --- a/pkcs12.go +++ b/pkcs12.go @@ -12,177 +12,41 @@ // Note that only DER-encoded PKCS#12 files are supported, even though PKCS#12 // allows BER encoding. This is because encoding/asn1 only supports DER. // +// # Decoding +// +// Depending on your use case, you may choose from the different decoding functions: +// - [Decode] reads exactly one private key and certificate +// - [DecodeChain] reads exactly one private key, certificate and related root CA certificate chain +// - [DecodeChains] reads multiple private keys, certificates and related root CA certificate chains +// - [DecodeTrustStore] reads multiple certificates, as commonly used in Java trust stores +// +// # Encoding +// +// Before encoding you must choose a specialized Encoder version, one of: +// - [Modern2023] encryption used is PBES2 with PBKDF2-HMAC-SHA-256 and AES-256-CBC; recommended for new applications +// - [LegacyDES] weak encryption used is 3DES using keys derived of HMAC-SHA-1; only for backward compatibility +// - [LegacyRC2] weak encryption used (RC2, and 3DES); only for older Java 8 trust stores +// +// These encoder types offer multiple options to encode PKCS#12 data exists, and you may choose from: +// - [Encoder.Encode] writes a private key and its certificate and related root CA certificates (chain) +// - [Encoder.EncodeTrustStore] writes just certificates, compatible to Java trust store format +// - [Encoder.EncodeTrustStoreEntries] writes friendly names and certificates, compatible to Java trust store format +// // This package is forked from golang.org/x/crypto/pkcs12, which is frozen. // The implementation is distilled from https://tools.ietf.org/html/rfc7292 // and referenced documents. package pkcs12 // import "software.sslmate.com/src/go-pkcs12" import ( - "crypto/ecdsa" - "crypto/rand" - "crypto/rsa" - "crypto/sha1" - "crypto/x509" "crypto/x509/pkix" "encoding/asn1" - "encoding/hex" - "encoding/pem" "errors" - "fmt" - "io" ) // DefaultPassword is the string "changeit", a commonly-used password for // PKCS#12 files. const DefaultPassword = "changeit" -// An Encoder contains methods for encoding PKCS#12 files. This package -// defines several different Encoders with different parameters. -// An Encoder is safe for concurrent use by multiple goroutines. -type Encoder struct { - macAlgorithm asn1.ObjectIdentifier - certAlgorithm asn1.ObjectIdentifier - keyAlgorithm asn1.ObjectIdentifier - macIterations int - encryptionIterations int - saltLen int - rand io.Reader -} - -// WithIterations creates a new Encoder identical to enc except that -// it will use the given number of KDF iterations for deriving the MAC -// and encryption keys. -// -// Note that even with a large number of iterations, a weak -// password can still be brute-forced in much less time than it would -// take to brute-force a high-entropy encrytion key. For the best -// security, don't worry about the number of iterations and just -// use a high-entropy password (e.g. one generated with `openssl rand -hex 16`). -// See https://neilmadden.blog/2023/01/09/on-pbkdf2-iterations/ for more detail. -// -// Panics if iterations is less than 1. -func (enc Encoder) WithIterations(iterations int) *Encoder { - if iterations < 1 { - panic("pkcs12: number of iterations is less than 1") - } - enc.macIterations = iterations - enc.encryptionIterations = iterations - return &enc -} - -// WithRand creates a new Encoder identical to enc except that -// it will use the given io.Reader for its random number generator -// instead of [crypto/rand.Reader]. -func (enc Encoder) WithRand(rand io.Reader) *Encoder { - enc.rand = rand - return &enc -} - -// LegacyRC2 encodes PKCS#12 files using weak algorithms that were -// traditionally used in PKCS#12 files, including those produced -// by OpenSSL before 3.0.0, go-pkcs12 before 0.3.0, and Java when -// keystore.pkcs12.legacy is defined. Specifically, certificates -// are encrypted using PBE with RC2, and keys are encrypted using PBE -// with 3DES, using keys derived with 2048 iterations of HMAC-SHA-1. -// MACs use HMAC-SHA-1 with keys derived with 1 iteration of HMAC-SHA-1. -// -// Due to the weak encryption, it is STRONGLY RECOMMENDED that you use [DefaultPassword] -// when encoding PKCS#12 files using this encoder, and protect the PKCS#12 files -// using other means. -// -// By default, OpenSSL 3 can't decode PKCS#12 files created using this encoder. -// For better compatibility, use [LegacyDES]. For better security, use -// [Modern2023]. -var LegacyRC2 = &Encoder{ - macAlgorithm: oidSHA1, - certAlgorithm: oidPBEWithSHAAnd40BitRC2CBC, - keyAlgorithm: oidPBEWithSHAAnd3KeyTripleDESCBC, - macIterations: 1, - encryptionIterations: 2048, - saltLen: 8, - rand: rand.Reader, -} - -// LegacyDES encodes PKCS#12 files using weak algorithms that are -// supported by a wide variety of software. Certificates and keys -// are encrypted using PBE with 3DES using keys derived with 2048 -// iterations of HMAC-SHA-1. MACs use HMAC-SHA-1 with keys derived -// with 1 iteration of HMAC-SHA-1. These are the same parameters -// used by OpenSSL's -descert option. As of 2023, this encoder is -// likely to produce files that can be read by the most software. -// -// Due to the weak encryption, it is STRONGLY RECOMMENDED that you use [DefaultPassword] -// when encoding PKCS#12 files using this encoder, and protect the PKCS#12 files -// using other means. To create more secure PKCS#12 files, use [Modern2023]. -var LegacyDES = &Encoder{ - macAlgorithm: oidSHA1, - certAlgorithm: oidPBEWithSHAAnd3KeyTripleDESCBC, - keyAlgorithm: oidPBEWithSHAAnd3KeyTripleDESCBC, - macIterations: 1, - encryptionIterations: 2048, - saltLen: 8, - rand: rand.Reader, -} - -// Passwordless encodes PKCS#12 files without any encryption or MACs. -// A lot of software has trouble reading such files, so it's probably only -// useful for creating Java trust stores using [Encoder.EncodeTrustStore] -// or [Encoder.EncodeTrustStoreEntries]. -// -// When using this encoder, you MUST specify an empty password. -var Passwordless = &Encoder{ - macAlgorithm: nil, - certAlgorithm: nil, - keyAlgorithm: nil, - rand: rand.Reader, -} - -// Modern2023 encodes PKCS#12 files using algorithms that are considered modern -// as of 2023. Private keys and certificates are encrypted using PBES2 with -// PBKDF2-HMAC-SHA-256 and AES-256-CBC. The MAC algorithm is HMAC-SHA-2. These -// are the same algorithms used by OpenSSL 3 (by default), Java 20 (by default), -// and Windows Server 2019 (when "stronger" is used). -// -// Files produced with this encoder can be read by OpenSSL 1.1.1 and higher, -// Java 12 and higher, and Windows Server 2019 and higher. -// -// For passwords, it is RECOMMENDED that you do one of the following: -// 1) Use [DefaultPassword] and protect the file using other means, or -// 2) Use a high-entropy password, such as one generated with `openssl rand -hex 16`. -// -// You SHOULD NOT use a lower-entropy password with this encoder because the number of KDF -// iterations is only 2048 and doesn't provide meaningful protection against -// brute-forcing. You can increase the number of iterations using [Encoder.WithIterations], -// but as https://neilmadden.blog/2023/01/09/on-pbkdf2-iterations/ explains, this doesn't -// help as much as you think. -var Modern2023 = &Encoder{ - macAlgorithm: oidSHA256, - certAlgorithm: oidPBES2, - keyAlgorithm: oidPBES2, - macIterations: 2048, - encryptionIterations: 2048, - saltLen: 16, - rand: rand.Reader, -} - -// Legacy encodes PKCS#12 files using weak, legacy parameters that work in -// a wide variety of software. -// -// Currently, this encoder is the same as [LegacyDES], but this -// may change in the future if another encoder is found to provide better -// compatibility. -// -// Due to the weak encryption, it is STRONGLY RECOMMENDED that you use [DefaultPassword] -// when encoding PKCS#12 files using this encoder, and protect the PKCS#12 files -// using other means. -var Legacy = LegacyDES - -// Modern encodes PKCS#12 files using modern, robust parameters. -// -// Currently, this encoder is the same as [Modern2023], but this -// may change in the future to keep up with modern practices. -var Modern = Modern2023 - var ( oidDataContentType = asn1.ObjectIdentifier([]int{1, 2, 840, 113549, 1, 7, 1}) oidEncryptedDataContentType = asn1.ObjectIdentifier([]int{1, 2, 840, 113549, 1, 7, 6}) @@ -225,21 +89,6 @@ func (i encryptedContentInfo) Data() []byte { return i.EncryptedContent } func (i *encryptedContentInfo) SetData(data []byte) { i.EncryptedContent = data } -type safeBag struct { - Id asn1.ObjectIdentifier - Value asn1.RawValue `asn1:"tag:0,explicit"` - Attributes []pkcs12Attribute `asn1:"set,optional"` -} - -func (bag *safeBag) hasAttribute(id asn1.ObjectIdentifier) bool { - for _, attr := range bag.Attributes { - if attr.Id.Equal(id) { - return true - } - } - return false -} - type pkcs12Attribute struct { Id asn1.ObjectIdentifier Value asn1.RawValue `asn1:"set"` @@ -262,14 +111,8 @@ func (i *encryptedPrivateKeyInfo) SetData(data []byte) { i.EncryptedData = data } -// PEM block types -const ( - certificateType = "CERTIFICATE" - privateKeyType = "PRIVATE KEY" -) - // unmarshal calls asn1.Unmarshal, but also returns an error if there is any -// trailing data after unmarshaling. +// trailing data after unmarshalling. func unmarshal(in []byte, out interface{}) error { trailing, err := asn1.Unmarshal(in, out) if err != nil { @@ -280,678 +123,3 @@ func unmarshal(in []byte, out interface{}) error { } return nil } - -// ToPEM converts all "safe bags" contained in pfxData to PEM blocks. -// -// Deprecated: ToPEM creates invalid PEM blocks (private keys -// are encoded as raw RSA or EC private keys rather than PKCS#8 despite being -// labeled "PRIVATE KEY"). To decode a PKCS#12 file, use [DecodeChain] instead, -// and use the [encoding/pem] package to convert to PEM if necessary. -func ToPEM(pfxData []byte, password string) ([]*pem.Block, error) { - encodedPassword, err := bmpStringZeroTerminated(password) - if err != nil { - return nil, ErrIncorrectPassword - } - - bags, encodedPassword, err := getSafeContents(pfxData, encodedPassword, 2, 2) - - if err != nil { - return nil, err - } - - blocks := make([]*pem.Block, 0, len(bags)) - for _, bag := range bags { - block, err := convertBag(&bag, encodedPassword) - if err != nil { - return nil, err - } - blocks = append(blocks, block) - } - - return blocks, nil -} - -func convertBag(bag *safeBag, password []byte) (*pem.Block, error) { - block := &pem.Block{ - Headers: make(map[string]string), - } - - for _, attribute := range bag.Attributes { - k, v, err := convertAttribute(&attribute) - if err != nil { - return nil, err - } - block.Headers[k] = v - } - - switch { - case bag.Id.Equal(oidCertBag): - block.Type = certificateType - certsData, err := decodeCertBag(bag.Value.Bytes) - if err != nil { - return nil, err - } - block.Bytes = certsData - case bag.Id.Equal(oidPKCS8ShroundedKeyBag): - block.Type = privateKeyType - - key, err := decodePkcs8ShroudedKeyBag(bag.Value.Bytes, password) - if err != nil { - return nil, err - } - - switch key := key.(type) { - case *rsa.PrivateKey: - block.Bytes = x509.MarshalPKCS1PrivateKey(key) - case *ecdsa.PrivateKey: - block.Bytes, err = x509.MarshalECPrivateKey(key) - if err != nil { - return nil, err - } - default: - return nil, errors.New("pkcs12: found unknown private key type in PKCS#8 wrapping") - } - default: - return nil, errors.New("pkcs12: don't know how to convert a safe bag of type " + bag.Id.String()) - } - return block, nil -} - -func convertAttribute(attribute *pkcs12Attribute) (key, value string, err error) { - isString := false - - switch { - case attribute.Id.Equal(oidFriendlyName): - key = "friendlyName" - isString = true - case attribute.Id.Equal(oidLocalKeyID): - key = "localKeyId" - case attribute.Id.Equal(oidMicrosoftCSPName): - // This key is chosen to match OpenSSL. - key = "Microsoft CSP Name" - isString = true - default: - return "", "", errors.New("pkcs12: unknown attribute with OID " + attribute.Id.String()) - } - - if isString { - if err := unmarshal(attribute.Value.Bytes, &attribute.Value); err != nil { - return "", "", err - } - if value, err = decodeBMPString(attribute.Value.Bytes); err != nil { - return "", "", err - } - } else { - var id []byte - if err := unmarshal(attribute.Value.Bytes, &id); err != nil { - return "", "", err - } - value = hex.EncodeToString(id) - } - - return key, value, nil -} - -// Decode extracts a certificate and private key from pfxData, which must be a DER-encoded PKCS#12 file. This function -// assumes that there is only one certificate and only one private key in the -// pfxData. Since PKCS#12 files often contain more than one certificate, you -// probably want to use [DecodeChain] instead. -func Decode(pfxData []byte, password string) (privateKey interface{}, certificate *x509.Certificate, err error) { - var caCerts []*x509.Certificate - privateKey, certificate, caCerts, err = DecodeChain(pfxData, password) - if len(caCerts) != 0 { - err = errors.New("pkcs12: expected exactly two safe bags in the PFX PDU") - } - return -} - -// DecodeChain extracts a certificate, a CA certificate chain, and private key -// from pfxData, which must be a DER-encoded PKCS#12 file. This function assumes that there is at least one certificate -// and only one private key in the pfxData. The first certificate is assumed to -// be the leaf certificate, and subsequent certificates, if any, are assumed to -// comprise the CA certificate chain. -func DecodeChain(pfxData []byte, password string) (privateKey interface{}, certificate *x509.Certificate, caCerts []*x509.Certificate, err error) { - encodedPassword, err := bmpStringZeroTerminated(password) - if err != nil { - return nil, nil, nil, err - } - - bags, encodedPassword, err := getSafeContents(pfxData, encodedPassword, 1, 2) - if err != nil { - return nil, nil, nil, err - } - - for _, bag := range bags { - switch { - case bag.Id.Equal(oidCertBag): - certsData, err := decodeCertBag(bag.Value.Bytes) - if err != nil { - return nil, nil, nil, err - } - certs, err := x509.ParseCertificates(certsData) - if err != nil { - return nil, nil, nil, err - } - if len(certs) != 1 { - err = errors.New("pkcs12: expected exactly one certificate in the certBag") - return nil, nil, nil, err - } - if certificate == nil { - certificate = certs[0] - } else { - caCerts = append(caCerts, certs[0]) - } - - case bag.Id.Equal(oidKeyBag): - if privateKey != nil { - err = errors.New("pkcs12: expected exactly one key bag") - return nil, nil, nil, err - } - - if privateKey, err = x509.ParsePKCS8PrivateKey(bag.Value.Bytes); err != nil { - return nil, nil, nil, err - } - case bag.Id.Equal(oidPKCS8ShroundedKeyBag): - if privateKey != nil { - err = errors.New("pkcs12: expected exactly one key bag") - return nil, nil, nil, err - } - - if privateKey, err = decodePkcs8ShroudedKeyBag(bag.Value.Bytes, encodedPassword); err != nil { - return nil, nil, nil, err - } - } - } - - if certificate == nil { - return nil, nil, nil, errors.New("pkcs12: certificate missing") - } - if privateKey == nil { - return nil, nil, nil, errors.New("pkcs12: private key missing") - } - - return -} - -// DecodeTrustStore extracts the certificates from pfxData, which must be a DER-encoded -// PKCS#12 file containing exclusively certificates with attribute 2.16.840.1.113894.746875.1.1, -// which is used by Java to designate a trust anchor. -// -// If the password argument is empty, DecodeTrustStore will decode either password-less -// PKCS#12 files (i.e. those without encryption) or files with a literal empty password. -func DecodeTrustStore(pfxData []byte, password string) (certs []*x509.Certificate, err error) { - encodedPassword, err := bmpStringZeroTerminated(password) - if err != nil { - return nil, err - } - - bags, encodedPassword, err := getSafeContents(pfxData, encodedPassword, 1, 1) - if err != nil { - return nil, err - } - - for _, bag := range bags { - switch { - case bag.Id.Equal(oidCertBag): - if !bag.hasAttribute(oidJavaTrustStore) { - return nil, errors.New("pkcs12: trust store contains a certificate that is not marked as trusted") - } - certsData, err := decodeCertBag(bag.Value.Bytes) - if err != nil { - return nil, err - } - parsedCerts, err := x509.ParseCertificates(certsData) - if err != nil { - return nil, err - } - - if len(parsedCerts) != 1 { - err = errors.New("pkcs12: expected exactly one certificate in the certBag") - return nil, err - } - - certs = append(certs, parsedCerts[0]) - - default: - return nil, errors.New("pkcs12: expected only certificate bags") - } - } - - return -} - -func getSafeContents(p12Data, password []byte, expectedItemsMin int, expectedItemsMax int) (bags []safeBag, updatedPassword []byte, err error) { - pfx := new(pfxPdu) - if err := unmarshal(p12Data, pfx); err != nil { - return nil, nil, errors.New("pkcs12: error reading P12 data: " + err.Error()) - } - - if pfx.Version != 3 { - return nil, nil, NotImplementedError("can only decode v3 PFX PDU's") - } - - if !pfx.AuthSafe.ContentType.Equal(oidDataContentType) { - return nil, nil, NotImplementedError("only password-protected PFX is implemented") - } - - // unmarshal the explicit bytes in the content for type 'data' - if err := unmarshal(pfx.AuthSafe.Content.Bytes, &pfx.AuthSafe.Content); err != nil { - return nil, nil, err - } - - if len(pfx.MacData.Mac.Algorithm.Algorithm) == 0 { - if !(len(password) == 2 && password[0] == 0 && password[1] == 0) { - return nil, nil, errors.New("pkcs12: no MAC in data") - } - } else if err := verifyMac(&pfx.MacData, pfx.AuthSafe.Content.Bytes, password); err != nil { - if err == ErrIncorrectPassword && len(password) == 2 && password[0] == 0 && password[1] == 0 { - // some implementations use an empty byte array - // for the empty string password try one more - // time with empty-empty password - password = nil - err = verifyMac(&pfx.MacData, pfx.AuthSafe.Content.Bytes, password) - } - if err != nil { - return nil, nil, err - } - } - - var authenticatedSafe []contentInfo - if err := unmarshal(pfx.AuthSafe.Content.Bytes, &authenticatedSafe); err != nil { - return nil, nil, err - } - - if len(authenticatedSafe) < expectedItemsMin || len(authenticatedSafe) > expectedItemsMax { - if expectedItemsMin == expectedItemsMax { - return nil, nil, NotImplementedError(fmt.Sprintf("expected exactly %d items in the authenticated safe, but this file has %d", expectedItemsMin, len(authenticatedSafe))) - } - return nil, nil, NotImplementedError(fmt.Sprintf("expected between %d and %d items in the authenticated safe, but this file has %d", expectedItemsMin, expectedItemsMax, len(authenticatedSafe))) - } - - for _, ci := range authenticatedSafe { - var data []byte - - switch { - case ci.ContentType.Equal(oidDataContentType): - if err := unmarshal(ci.Content.Bytes, &data); err != nil { - return nil, nil, err - } - case ci.ContentType.Equal(oidEncryptedDataContentType): - var encryptedData encryptedData - if err := unmarshal(ci.Content.Bytes, &encryptedData); err != nil { - return nil, nil, err - } - if encryptedData.Version != 0 { - return nil, nil, NotImplementedError("only version 0 of EncryptedData is supported") - } - if data, err = pbDecrypt(encryptedData.EncryptedContentInfo, password); err != nil { - return nil, nil, err - } - default: - return nil, nil, NotImplementedError("only data and encryptedData content types are supported in authenticated safe") - } - - var safeContents []safeBag - if err := unmarshal(data, &safeContents); err != nil { - return nil, nil, err - } - bags = append(bags, safeContents...) - } - - return bags, password, nil -} - -// Encode is equivalent to LegacyRC2.WithRand(rand).Encode. -// See [Encoder.Encode] and [LegacyRC2] for details. -// -// Deprecated: for the same behavior, use LegacyRC2.Encode; for -// better compatibility, use Legacy.Encode; for better -// security, use Modern.Encode. -func Encode(rand io.Reader, privateKey interface{}, certificate *x509.Certificate, caCerts []*x509.Certificate, password string) (pfxData []byte, err error) { - return LegacyRC2.WithRand(rand).Encode(privateKey, certificate, caCerts, password) -} - -// Encode produces pfxData containing one private key (privateKey), an -// end-entity certificate (certificate), and any number of CA certificates -// (caCerts). -// -// The pfxData is encrypted and authenticated with keys derived from -// the provided password. -// -// Encode emulates the behavior of OpenSSL's PKCS12_create: it creates two -// SafeContents: one that's encrypted with the certificate encryption algorithm -// and contains the certificates, and another that is unencrypted and contains the -// private key shrouded with the key encryption algorithm. The private key bag and -// the end-entity certificate bag have the LocalKeyId attribute set to the SHA-1 -// fingerprint of the end-entity certificate. -func (enc *Encoder) Encode(privateKey interface{}, certificate *x509.Certificate, caCerts []*x509.Certificate, password string) (pfxData []byte, err error) { - if enc.macAlgorithm == nil && enc.certAlgorithm == nil && enc.keyAlgorithm == nil && password != "" { - return nil, errors.New("pkcs12: password must be empty") - } - - encodedPassword, err := bmpStringZeroTerminated(password) - if err != nil { - return nil, err - } - - var pfx pfxPdu - pfx.Version = 3 - - var certFingerprint = sha1.Sum(certificate.Raw) - var localKeyIdAttr pkcs12Attribute - localKeyIdAttr.Id = oidLocalKeyID - localKeyIdAttr.Value.Class = 0 - localKeyIdAttr.Value.Tag = 17 - localKeyIdAttr.Value.IsCompound = true - if localKeyIdAttr.Value.Bytes, err = asn1.Marshal(certFingerprint[:]); err != nil { - return nil, err - } - - var certBags []safeBag - if certBag, err := makeCertBag(certificate.Raw, []pkcs12Attribute{localKeyIdAttr}); err != nil { - return nil, err - } else { - certBags = append(certBags, *certBag) - } - - for _, cert := range caCerts { - if certBag, err := makeCertBag(cert.Raw, []pkcs12Attribute{}); err != nil { - return nil, err - } else { - certBags = append(certBags, *certBag) - } - } - - var keyBag safeBag - if enc.keyAlgorithm == nil { - keyBag.Id = oidKeyBag - keyBag.Value.Class = 2 - keyBag.Value.Tag = 0 - keyBag.Value.IsCompound = true - if keyBag.Value.Bytes, err = x509.MarshalPKCS8PrivateKey(privateKey); err != nil { - return nil, err - } - } else { - keyBag.Id = oidPKCS8ShroundedKeyBag - keyBag.Value.Class = 2 - keyBag.Value.Tag = 0 - keyBag.Value.IsCompound = true - if keyBag.Value.Bytes, err = encodePkcs8ShroudedKeyBag(enc.rand, privateKey, enc.keyAlgorithm, encodedPassword, enc.encryptionIterations, enc.saltLen); err != nil { - return nil, err - } - } - keyBag.Attributes = append(keyBag.Attributes, localKeyIdAttr) - - // Construct an authenticated safe with two SafeContents. - // The first SafeContents is encrypted and contains the cert bags. - // The second SafeContents is unencrypted and contains the shrouded key bag. - var authenticatedSafe [2]contentInfo - if authenticatedSafe[0], err = makeSafeContents(enc.rand, certBags, enc.certAlgorithm, encodedPassword, enc.encryptionIterations, enc.saltLen); err != nil { - return nil, err - } - if authenticatedSafe[1], err = makeSafeContents(enc.rand, []safeBag{keyBag}, nil, nil, 0, 0); err != nil { - return nil, err - } - - var authenticatedSafeBytes []byte - if authenticatedSafeBytes, err = asn1.Marshal(authenticatedSafe[:]); err != nil { - return nil, err - } - - if enc.macAlgorithm != nil { - // compute the MAC - pfx.MacData.Mac.Algorithm.Algorithm = enc.macAlgorithm - pfx.MacData.MacSalt = make([]byte, enc.saltLen) - if _, err = enc.rand.Read(pfx.MacData.MacSalt); err != nil { - return nil, err - } - pfx.MacData.Iterations = enc.macIterations - if err = computeMac(&pfx.MacData, authenticatedSafeBytes, encodedPassword); err != nil { - return nil, err - } - } - - pfx.AuthSafe.ContentType = oidDataContentType - pfx.AuthSafe.Content.Class = 2 - pfx.AuthSafe.Content.Tag = 0 - pfx.AuthSafe.Content.IsCompound = true - if pfx.AuthSafe.Content.Bytes, err = asn1.Marshal(authenticatedSafeBytes); err != nil { - return nil, err - } - - if pfxData, err = asn1.Marshal(pfx); err != nil { - return nil, errors.New("pkcs12: error writing P12 data: " + err.Error()) - } - return -} - -// EncodeTrustStore is equivalent to LegacyRC2.WithRand(rand).EncodeTrustStore. -// See [Encoder.EncodeTrustStore] and [LegacyRC2] for details. -// -// Deprecated: for the same behavior, use LegacyRC2.EncodeTrustStore; to generate passwordless trust stores, -// use Passwordless.EncodeTrustStore. -func EncodeTrustStore(rand io.Reader, certs []*x509.Certificate, password string) (pfxData []byte, err error) { - return LegacyRC2.WithRand(rand).EncodeTrustStore(certs, password) -} - -// EncodeTrustStore produces pfxData containing any number of CA certificates -// (certs) to be trusted. The certificates will be marked with a special OID that -// allow it to be used as a Java TrustStore in Java 1.8 and newer. -// -// EncodeTrustStore creates a single SafeContents that's optionally encrypted -// and contains the certificates. -// -// The Subject of the certificates are used as the Friendly Names (Aliases) -// within the resulting pfxData. If certificates share a Subject, then the -// resulting Friendly Names (Aliases) will be identical, which Java may treat as -// the same entry when used as a Java TrustStore, e.g. with `keytool`. To -// customize the Friendly Names, use [EncodeTrustStoreEntries]. -func (enc *Encoder) EncodeTrustStore(certs []*x509.Certificate, password string) (pfxData []byte, err error) { - var certsWithFriendlyNames []TrustStoreEntry - for _, cert := range certs { - certsWithFriendlyNames = append(certsWithFriendlyNames, TrustStoreEntry{ - Cert: cert, - FriendlyName: cert.Subject.String(), - }) - } - return enc.EncodeTrustStoreEntries(certsWithFriendlyNames, password) -} - -// TrustStoreEntry represents an entry in a Java TrustStore. -type TrustStoreEntry struct { - Cert *x509.Certificate - FriendlyName string -} - -// EncodeTrustStoreEntries is equivalent to LegacyRC2.WithRand(rand).EncodeTrustStoreEntries. -// See [Encoder.EncodeTrustStoreEntries] and [LegacyRC2] for details. -// -// Deprecated: for the same behavior, use LegacyRC2.EncodeTrustStoreEntries; to generate passwordless trust stores, -// use Passwordless.EncodeTrustStoreEntries. -func EncodeTrustStoreEntries(rand io.Reader, entries []TrustStoreEntry, password string) (pfxData []byte, err error) { - return LegacyRC2.WithRand(rand).EncodeTrustStoreEntries(entries, password) -} - -// EncodeTrustStoreEntries produces pfxData containing any number of CA -// certificates (entries) to be trusted. The certificates will be marked with a -// special OID that allow it to be used as a Java TrustStore in Java 1.8 and newer. -// -// This is identical to [Encoder.EncodeTrustStore], but also allows for setting specific -// Friendly Names (Aliases) to be used per certificate, by specifying a slice -// of TrustStoreEntry. -// -// If the same Friendly Name is used for more than one certificate, then the -// resulting Friendly Names (Aliases) in the pfxData will be identical, which Java -// may treat as the same entry when used as a Java TrustStore, e.g. with `keytool`. -// -// EncodeTrustStoreEntries creates a single SafeContents that's optionally -// encrypted and contains the certificates. -func (enc *Encoder) EncodeTrustStoreEntries(entries []TrustStoreEntry, password string) (pfxData []byte, err error) { - if enc.macAlgorithm == nil && enc.certAlgorithm == nil && password != "" { - return nil, errors.New("pkcs12: password must be empty") - } - - encodedPassword, err := bmpStringZeroTerminated(password) - if err != nil { - return nil, err - } - - var pfx pfxPdu - pfx.Version = 3 - - var certAttributes []pkcs12Attribute - - extKeyUsageOidBytes, err := asn1.Marshal(oidAnyExtendedKeyUsage) - if err != nil { - return nil, err - } - - // the oidJavaTrustStore attribute contains the EKUs for which - // this trust anchor will be valid - certAttributes = append(certAttributes, pkcs12Attribute{ - Id: oidJavaTrustStore, - Value: asn1.RawValue{ - Class: 0, - Tag: 17, - IsCompound: true, - Bytes: extKeyUsageOidBytes, - }, - }) - - var certBags []safeBag - for _, entry := range entries { - - bmpFriendlyName, err := bmpString(entry.FriendlyName) - if err != nil { - return nil, err - } - - encodedFriendlyName, err := asn1.Marshal(asn1.RawValue{ - Class: 0, - Tag: 30, - IsCompound: false, - Bytes: bmpFriendlyName, - }) - if err != nil { - return nil, err - } - - friendlyName := pkcs12Attribute{ - Id: oidFriendlyName, - Value: asn1.RawValue{ - Class: 0, - Tag: 17, - IsCompound: true, - Bytes: encodedFriendlyName, - }, - } - - certBag, err := makeCertBag(entry.Cert.Raw, append(certAttributes, friendlyName)) - if err != nil { - return nil, err - } - certBags = append(certBags, *certBag) - } - - // Construct an authenticated safe with one SafeContent. - // The SafeContents is contains the cert bags. - var authenticatedSafe [1]contentInfo - if authenticatedSafe[0], err = makeSafeContents(enc.rand, certBags, enc.certAlgorithm, encodedPassword, enc.encryptionIterations, enc.saltLen); err != nil { - return nil, err - } - - var authenticatedSafeBytes []byte - if authenticatedSafeBytes, err = asn1.Marshal(authenticatedSafe[:]); err != nil { - return nil, err - } - - if enc.macAlgorithm != nil { - // compute the MAC - pfx.MacData.Mac.Algorithm.Algorithm = enc.macAlgorithm - pfx.MacData.MacSalt = make([]byte, enc.saltLen) - if _, err = enc.rand.Read(pfx.MacData.MacSalt); err != nil { - return nil, err - } - pfx.MacData.Iterations = enc.macIterations - if err = computeMac(&pfx.MacData, authenticatedSafeBytes, encodedPassword); err != nil { - return nil, err - } - } - - pfx.AuthSafe.ContentType = oidDataContentType - pfx.AuthSafe.Content.Class = 2 - pfx.AuthSafe.Content.Tag = 0 - pfx.AuthSafe.Content.IsCompound = true - if pfx.AuthSafe.Content.Bytes, err = asn1.Marshal(authenticatedSafeBytes); err != nil { - return nil, err - } - - if pfxData, err = asn1.Marshal(pfx); err != nil { - return nil, errors.New("pkcs12: error writing P12 data: " + err.Error()) - } - return -} - -func makeCertBag(certBytes []byte, attributes []pkcs12Attribute) (certBag *safeBag, err error) { - certBag = new(safeBag) - certBag.Id = oidCertBag - certBag.Value.Class = 2 - certBag.Value.Tag = 0 - certBag.Value.IsCompound = true - if certBag.Value.Bytes, err = encodeCertBag(certBytes); err != nil { - return nil, err - } - certBag.Attributes = attributes - return -} - -func makeSafeContents(rand io.Reader, bags []safeBag, algoID asn1.ObjectIdentifier, password []byte, iterations int, saltLen int) (ci contentInfo, err error) { - var data []byte - if data, err = asn1.Marshal(bags); err != nil { - return - } - - if algoID == nil { - ci.ContentType = oidDataContentType - ci.Content.Class = 2 - ci.Content.Tag = 0 - ci.Content.IsCompound = true - if ci.Content.Bytes, err = asn1.Marshal(data); err != nil { - return - } - } else { - randomSalt := make([]byte, saltLen) - if _, err = rand.Read(randomSalt); err != nil { - return - } - - var algo pkix.AlgorithmIdentifier - algo.Algorithm = algoID - if algoID.Equal(oidPBES2) { - if algo.Parameters.FullBytes, err = makePBES2Parameters(rand, randomSalt, iterations); err != nil { - return - } - } else { - if algo.Parameters.FullBytes, err = asn1.Marshal(pbeParams{Salt: randomSalt, Iterations: iterations}); err != nil { - return - } - } - - var encryptedData encryptedData - encryptedData.Version = 0 - encryptedData.EncryptedContentInfo.ContentType = oidDataContentType - encryptedData.EncryptedContentInfo.ContentEncryptionAlgorithm = algo - if err = pbEncrypt(&encryptedData.EncryptedContentInfo, data, password); err != nil { - return - } - - ci.ContentType = oidEncryptedDataContentType - ci.Content.Class = 2 - ci.Content.Tag = 0 - ci.Content.IsCompound = true - if ci.Content.Bytes, err = asn1.Marshal(encryptedData); err != nil { - return - } - } - return -} diff --git a/pkcs12_dec.go b/pkcs12_dec.go new file mode 100644 index 0000000..d5b07aa --- /dev/null +++ b/pkcs12_dec.go @@ -0,0 +1,407 @@ +// Copyright 2015, 2018, 2019 Opsmate, Inc. All rights reserved. +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package pkcs12 + +import ( + "bytes" + "crypto" + "crypto/x509" + "encoding/asn1" + "errors" + "fmt" + "sort" +) + +// A CertificateChain represents a private key, a leaf certificate matching it, and the CA certificate chain. +// It also stores the friendlyName of the private key. +type CertificateChain struct { + FriendlyName string + PrivateKey crypto.PrivateKey + LeafCertificate *x509.Certificate + CACertificates []*x509.Certificate +} + +// Decode extracts a certificate and private key from pfxData, which must be a DER-encoded PKCS#12 file. +// This function assumes that there is only one certificate and only one private key in the pfxData. +// Since PKCS#12 files often contain more than one certificate, you probably want to use [DecodeChain] instead. +// It will return an error, if there is more than one private key in the data. +func Decode(pfxData []byte, password string) (privateKey interface{}, certificate *x509.Certificate, err error) { + var caCerts []*x509.Certificate + privateKey, certificate, caCerts, err = DecodeChain(pfxData, password) + if len(caCerts) != 0 { + err = errors.New("pkcs12: expected exactly two safe bags in the PFX PDU") + } + return +} + +// DecodeChain extracts a certificate, a CA certificate chain, and private key +// from pfxData, which must be a DER-encoded PKCS#12 file. This function assumes that there is at least one certificate +// and only one private key in the pfxData. The first certificate is assumed to +// be the leaf certificate, and subsequent certificates, if any, are assumed to +// comprise the CA certificate chain. +func DecodeChain(pfxData []byte, password string) (privateKey interface{}, certificate *x509.Certificate, caCerts []*x509.Certificate, err error) { + encodedPassword, err := bmpStringZeroTerminated(password) + if err != nil { + return nil, nil, nil, err + } + + bags, encodedPassword, err := getSafeContents(pfxData, encodedPassword, 1, 2) + if err != nil { + return nil, nil, nil, err + } + + for _, bag := range bags { + switch { + case bag.Id.Equal(oidCertBag): + certsData, err := decodeCertBag(bag.Value.Bytes) + if err != nil { + return nil, nil, nil, err + } + certs, err := x509.ParseCertificates(certsData) + if err != nil { + return nil, nil, nil, err + } + if len(certs) != 1 { + err = errors.New("pkcs12: expected exactly one certificate in the certBag") + return nil, nil, nil, err + } + if certificate == nil { + certificate = certs[0] + } else { + caCerts = append(caCerts, certs[0]) + } + + case bag.Id.Equal(oidKeyBag): + if privateKey != nil { + err = errors.New("pkcs12: expected exactly one key bag") + return nil, nil, nil, err + } + + if privateKey, err = x509.ParsePKCS8PrivateKey(bag.Value.Bytes); err != nil { + return nil, nil, nil, err + } + case bag.Id.Equal(oidPKCS8ShroundedKeyBag): + if privateKey != nil { + err = errors.New("pkcs12: expected exactly one key bag") + return nil, nil, nil, err + } + + if privateKey, err = decodePkcs8ShroudedKeyBag(bag.Value.Bytes, encodedPassword); err != nil { + return nil, nil, nil, err + } + } + } + + if certificate == nil { + return nil, nil, nil, errors.New("pkcs12: certificate missing") + } + if privateKey == nil { + return nil, nil, nil, errors.New("pkcs12: private key missing") + } + + return +} + +// DecodeChains extracts Chains from pfxData, which must be a DER-encoded PKCS#12 file. The function +// assumes there is at least one private key with a friendlyName attribute and at least one matching certificate. +// The function ignores certificates that do not match any private keys, or are not part of any CA certificates chain. +func DecodeChains(pfxData []byte, password string) (chains []CertificateChain, err error) { + encodedPassword, err := bmpStringZeroTerminated(password) + if err != nil { + return nil, err + } + + bags, encodedPassword, err := getSafeContents(pfxData, encodedPassword, 1, 2) + if err != nil { + return nil, err + } + + // extract all bags + privateKeysAll := make(map[string]crypto.PrivateKey) + var certsAll []*x509.Certificate // do not store cert alias + for _, bag := range bags { + friendlyName, err := extractFriendlyname(bag) + if err != nil { + friendlyName = "" + } + switch { + case bag.Id.Equal(oidCertBag): + certsData, err := decodeCertBag(bag.Value.Bytes) + if err != nil { + return nil, err + } + certs, err := x509.ParseCertificates(certsData) + if err != nil { + return nil, err + } + if len(certs) != 1 { + err = errors.New("pkcs12: expected exactly one certificate in the certBag") + return nil, err + } + certsAll = append(certsAll, certs[0]) + case bag.Id.Equal(oidKeyBag): + privateKey, err := x509.ParsePKCS8PrivateKey(bag.Value.Bytes) + if err != nil { + return nil, err + } + pk, ok := privateKey.(crypto.PrivateKey) + if !ok { + return nil, fmt.Errorf("pkcs12: failed to get private key") + } + privateKeysAll[friendlyName] = pk + case bag.Id.Equal(oidPKCS8ShroundedKeyBag): + privateKey, err := decodePkcs8ShroudedKeyBag(bag.Value.Bytes, encodedPassword) + if err != nil { + return nil, err + } + pk, ok := privateKey.(crypto.PrivateKey) + if !ok { + return nil, fmt.Errorf("pkcs12: failed to get private key") + } + privateKeysAll[friendlyName] = pk + default: + // TODO: bag types: crlBag, secretBag, safeContentsBag aren't supported yet, should signal a warning + } + } + for pkAlias, pk := range privateKeysAll { + chain := CertificateChain{ + FriendlyName: pkAlias, + PrivateKey: pk, + } + // find matching private keys to leaf certificates + var leafCertificate *x509.Certificate + for i, cert := range certsAll { + if certificateMatchesToKey(pk, cert) { + chain.LeafCertificate = cert + leafCertificate = cert + certsAll = removeCert(certsAll, i) + break + } + } + // build the chain, from remaining, un-ordered certificates + for leafCertificate != nil && hasIssuer(leafCertificate) && !selfSigned(leafCertificate) { + foundIssuer := false + for i, issuerCert := range certsAll { + if issuedBy(leafCertificate, issuerCert) { + chain.CACertificates = append(chain.CACertificates, issuerCert) + leafCertificate = issuerCert + certsAll = removeCert(certsAll, i) + foundIssuer = true + break + } + } + if !foundIssuer { + break // incomplete chain, no reason to error + } + } + chains = append(chains, chain) + } + // verify chains + for _, chain := range chains { + if chain.LeafCertificate == nil { + return nil, errors.New("pkcs12: leaf certificate missing") + } + } + + // sort, to make the result predictable + sort.Slice(chains, func(i, j int) bool { return chains[i].FriendlyName < chains[j].FriendlyName }) + + return +} + +func removeCert(slice []*x509.Certificate, s int) []*x509.Certificate { + return append(slice[:s], slice[s+1:]...) +} + +func selfSigned(cert *x509.Certificate) bool { + return issuedBy(cert, cert) +} + +func issuedBy(subject, issuer *x509.Certificate) bool { + return bytes.Equal(subject.RawIssuer, issuer.RawSubject) && + issuer.CheckSignature(subject.SignatureAlgorithm, subject.RawTBSCertificate, subject.Signature) == nil +} + +func hasIssuer(cert *x509.Certificate) bool { + return len(cert.RawIssuer) > 0 +} + +// DecodeTrustStore extracts the certificates from pfxData, which must be a DER-encoded +// PKCS#12 file containing exclusively certificates with attribute 2.16.840.1.113894.746875.1.1, +// which is used by Java to designate a trust anchor. +// +// If the password argument is empty, DecodeTrustStore will decode either password-less +// PKCS#12 files (i.e. those without encryption) or files with a literal empty password. +func DecodeTrustStore(pfxData []byte, password string) (certs []*x509.Certificate, err error) { + encodedPassword, err := bmpStringZeroTerminated(password) + if err != nil { + return nil, err + } + + bags, encodedPassword, err := getSafeContents(pfxData, encodedPassword, 1, 1) + if err != nil { + return nil, err + } + + for _, bag := range bags { + switch { + case bag.Id.Equal(oidCertBag): + if !bag.hasAttribute(oidJavaTrustStore) { + return nil, errors.New("pkcs12: trust store contains a certificate that is not marked as trusted") + } + certsData, err := decodeCertBag(bag.Value.Bytes) + if err != nil { + return nil, err + } + parsedCerts, err := x509.ParseCertificates(certsData) + if err != nil { + return nil, err + } + + if len(parsedCerts) != 1 { + err = errors.New("pkcs12: expected exactly one certificate in the certBag") + return nil, err + } + + certs = append(certs, parsedCerts[0]) + + default: + return nil, errors.New("pkcs12: expected only certificate bags") + } + } + return +} + +type safeBag struct { + Id asn1.ObjectIdentifier + Value asn1.RawValue `asn1:"tag:0,explicit"` + Attributes []pkcs12Attribute `asn1:"set,optional"` +} + +func (bag *safeBag) hasAttribute(id asn1.ObjectIdentifier) bool { + for _, attr := range bag.Attributes { + if attr.Id.Equal(id) { + return true + } + } + return false +} + +func extractFriendlyname(bag safeBag) (string, error) { + for _, attribute := range bag.Attributes { + if attribute.Id.Equal(oidFriendlyName) { + if err := unmarshal(attribute.Value.Bytes, &attribute.Value); err != nil { + return "", err + } + value, err := decodeBMPString(attribute.Value.Bytes) + if err != nil { + return "", err + } + return value, nil + } + } + return "", errors.New("pkcs12: friendlyName attribute not found") +} + +func certificateMatchesToKey(privateKey crypto.PrivateKey, certificate *x509.Certificate) bool { + pk, ok := privateKey.(interface { + Public() crypto.PublicKey + }) + if !ok { + return false + } + publicKey, ok := pk.Public().(interface { + Equal(crypto.PublicKey) bool + }) + if !ok { + return false + } + if publicKey.Equal(certificate.PublicKey) { + return true + } + return false +} + +func getSafeContents(p12Data, password []byte, expectedItemsMin int, expectedItemsMax int) (bags []safeBag, updatedPassword []byte, err error) { + pfx := new(pfxPdu) + if err := unmarshal(p12Data, pfx); err != nil { + return nil, nil, errors.New("pkcs12: error reading P12 data: " + err.Error()) + } + + if pfx.Version != 3 { + return nil, nil, NotImplementedError("can only decode v3 PFX PDU's") + } + + if !pfx.AuthSafe.ContentType.Equal(oidDataContentType) { + return nil, nil, NotImplementedError("only password-protected PFX is implemented") + } + + // unmarshal the explicit bytes in the content for type 'data' + if err := unmarshal(pfx.AuthSafe.Content.Bytes, &pfx.AuthSafe.Content); err != nil { + return nil, nil, err + } + + if len(pfx.MacData.Mac.Algorithm.Algorithm) == 0 { + if !(len(password) == 2 && password[0] == 0 && password[1] == 0) { + return nil, nil, errors.New("pkcs12: no MAC in data") + } + } else if err := verifyMac(&pfx.MacData, pfx.AuthSafe.Content.Bytes, password); err != nil { + if err == ErrIncorrectPassword && len(password) == 2 && password[0] == 0 && password[1] == 0 { + // some implementations use an empty byte array + // for the empty string password try one more + // time with empty-empty password + password = nil + err = verifyMac(&pfx.MacData, pfx.AuthSafe.Content.Bytes, password) + } + if err != nil { + return nil, nil, err + } + } + + var authenticatedSafe []contentInfo + if err := unmarshal(pfx.AuthSafe.Content.Bytes, &authenticatedSafe); err != nil { + return nil, nil, err + } + + if len(authenticatedSafe) < expectedItemsMin || len(authenticatedSafe) > expectedItemsMax { + if expectedItemsMin == expectedItemsMax { + return nil, nil, NotImplementedError(fmt.Sprintf("expected exactly %d items in the authenticated safe, but this file has %d", expectedItemsMin, len(authenticatedSafe))) + } + return nil, nil, NotImplementedError(fmt.Sprintf("expected between %d and %d items in the authenticated safe, but this file has %d", expectedItemsMin, expectedItemsMax, len(authenticatedSafe))) + } + + for _, ci := range authenticatedSafe { + var data []byte + + switch { + case ci.ContentType.Equal(oidDataContentType): + if err := unmarshal(ci.Content.Bytes, &data); err != nil { + return nil, nil, err + } + case ci.ContentType.Equal(oidEncryptedDataContentType): + var encryptedData encryptedData + if err := unmarshal(ci.Content.Bytes, &encryptedData); err != nil { + return nil, nil, err + } + if encryptedData.Version != 0 { + return nil, nil, NotImplementedError("only version 0 of EncryptedData is supported") + } + if data, err = pbDecrypt(encryptedData.EncryptedContentInfo, password); err != nil { + return nil, nil, err + } + default: + return nil, nil, NotImplementedError("only data and encryptedData content types are supported in authenticated safe") + } + + var safeContents []safeBag + if err := unmarshal(data, &safeContents); err != nil { + return nil, nil, err + } + bags = append(bags, safeContents...) + } + + return bags, password, nil +} diff --git a/pkcs12_dec_test.go b/pkcs12_dec_test.go new file mode 100644 index 0000000..026cc16 --- /dev/null +++ b/pkcs12_dec_test.go @@ -0,0 +1,139 @@ +package pkcs12 + +import ( + "crypto/rsa" + _ "embed" + "testing" +) + +//go:embed test-data/example_com_aescbc128.p12 +var fileExampleComAesCbc128 []byte + +//go:embed test-data/example_com_aescbc192.p12 +var fileExampleComAesCbc192 []byte + +//go:embed test-data/ad_standalone_com_aescbc256.p12 +var fileAdStandaloneComAesCbc256 []byte + +var certificateTests = []struct { + testName string + pfxData []byte + password string + commonName string + testDescription string +}{ + { + testName: "AES128CBC", + pfxData: fileExampleComAesCbc128, + password: "rHyQTJsubhfxcpH5JttyilHE6BBsNoZp", + commonName: "example-com", + testDescription: "PKCS7 Encrypted data: PBES2, PBKDF2, AES-128-CBC, Iteration 2048, PRF hmacWithSHA256", + }, + { + testName: "AES192CBC", + pfxData: fileExampleComAesCbc192, + password: "password", + commonName: "example-com", + testDescription: "PKCS7 Encrypted data: PBES2, PBKDF2, AES-192-CBC, Iteration 2048, PRF hmacWithSHA256", + }, + { + testName: "AES256CBC", + pfxData: fileAdStandaloneComAesCbc256, + password: "password", + commonName: "*.ad.standalone.com", + testDescription: "This P12 PDU is a self-signed certificate exported via Windows certmgr. It is encrypted with the following options (verified via openssl): PBES2, PBKDF2, AES-256-CBC, Iteration 2000, PRF hmacWithSHA256", + }, +} + +func Test_DecodeChain_PBES2(t *testing.T) { + for _, tt := range certificateTests { + t.Run(tt.testName, func(t *testing.T) { + pk, cert, caCerts, err := DecodeChain(tt.pfxData, tt.password) + if err != nil { + t.Fatal(err) + } + + rsaPk, ok := pk.(*rsa.PrivateKey) + if !ok { + t.Error("could not cast to rsa private key") + } + if !rsaPk.PublicKey.Equal(cert.PublicKey) { + t.Error("public key embedded in private key not equal to public key of certificate") + } + if cert.Subject.CommonName != tt.commonName { + t.Errorf("unexpected leaf cert common name, got %s, want %s", cert.Subject.CommonName, tt.commonName) + } + if len(caCerts) != 0 { + t.Errorf("unexpected # of caCerts: got %d, want 0", len(caCerts)) + } + }) + } +} + +//go:embed test-data/example_signed_certificates_chain.p12 +var fileExampleSignedCertificatesChain []byte + +func Test_DecodeChains_with_private_key(t *testing.T) { + tests := []struct { + testName string + pfxData []byte + password string + friendlyNames []string + chainLengths []int + }{ + { + testName: "example_signed_certificates_chain.p12", + pfxData: fileExampleSignedCertificatesChain, + password: "password", + friendlyNames: []string{ + "example-ca", + "example-intermediate-ca (example-ca)", + "example-server (example-intermediate-ca)", + }, + chainLengths: []int{0, 1, 2}, + }, + } + for _, tt := range tests { + t.Run(tt.testName, func(t *testing.T) { + chains, err := DecodeChains(tt.pfxData, tt.password) + if err != nil { + t.Fatal(err) + } + if len(chains) != 3 { + t.Errorf("unexpected # of chains: got %d, want 3", len(chains)) + } + for i, chain := range chains { + expectedFriendlyName := tt.friendlyNames[i] + if chain.FriendlyName != expectedFriendlyName { + t.Errorf("unexpected private key friendly name, got '%s', want '%s'", chain.FriendlyName, expectedFriendlyName) + } + pk := chain.PrivateKey + rsaPk, ok := pk.(*rsa.PrivateKey) + if !ok { + t.Error("could not cast to rsa private key") + } + if !rsaPk.PublicKey.Equal(chain.LeafCertificate.PublicKey) { + t.Error("public key embedded in private key not equal to public key of certificate") + } + if len(chain.CACertificates) != tt.chainLengths[i] { + t.Errorf("unexpected # of caCerts: got %d, want %d", len(chain.CACertificates), tt.chainLengths[i]) + } + } + }) + } +} + +func Test_DecodeChains_with_certificate_files(t *testing.T) { + // also the other pfx files should work + for _, tt := range certificateTests { + t.Run(tt.testName, func(t *testing.T) { + certs, err := DecodeChains(tt.pfxData, tt.password) + if err != nil { + t.Fatal(err) + } + if len(certs) != 1 { + t.Errorf("unexpected # of caCerts: got %d, want 1", len(certs)) + } + }) + } +} diff --git a/pkcs12_enc.go b/pkcs12_enc.go new file mode 100644 index 0000000..8760359 --- /dev/null +++ b/pkcs12_enc.go @@ -0,0 +1,518 @@ +// Copyright 2015, 2018, 2019 Opsmate, Inc. All rights reserved. +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package pkcs12 + +import ( + "crypto/rand" + "crypto/sha1" + "crypto/x509" + "crypto/x509/pkix" + "encoding/asn1" + "errors" + "io" +) + +// An Encoder contains methods for encoding PKCS#12 files. This package +// defines several different Encoders with different parameters. +// An Encoder is safe for concurrent use by multiple goroutines. +type Encoder struct { + macAlgorithm asn1.ObjectIdentifier + certAlgorithm asn1.ObjectIdentifier + keyAlgorithm asn1.ObjectIdentifier + macIterations int + encryptionIterations int + saltLen int + rand io.Reader +} + +// LegacyRC2 encodes PKCS#12 files using weak algorithms that were +// traditionally used in PKCS#12 files, including those produced +// by OpenSSL before 3.0.0, go-pkcs12 before 0.3.0, and Java when +// keystore.pkcs12.legacy is defined. Specifically, certificates +// are encrypted using PBE with RC2, and keys are encrypted using PBE +// with 3DES, using keys derived with 2048 iterations of HMAC-SHA-1. +// MACs use HMAC-SHA-1 with keys derived with 1 iteration of HMAC-SHA-1. +// +// Due to the weak encryption, it is STRONGLY RECOMMENDED that you use [DefaultPassword] +// when encoding PKCS#12 files using this encoder, and protect the PKCS#12 files +// using other means. +// +// By default, OpenSSL 3 can't decode PKCS#12 files created using this encoder. +// For better compatibility, use [LegacyDES]. For better security, use +// [Modern2023]. +var LegacyRC2 = &Encoder{ + macAlgorithm: oidSHA1, + certAlgorithm: oidPBEWithSHAAnd40BitRC2CBC, + keyAlgorithm: oidPBEWithSHAAnd3KeyTripleDESCBC, + macIterations: 1, + encryptionIterations: 2048, + saltLen: 8, + rand: rand.Reader, +} + +// LegacyDES encodes PKCS#12 files using weak algorithms that are +// supported by a wide variety of software. Certificates and keys +// are encrypted using PBE with 3DES using keys derived with 2048 +// iterations of HMAC-SHA-1. MACs use HMAC-SHA-1 with keys derived +// with 1 iteration of HMAC-SHA-1. These are the same parameters +// used by OpenSSL's -descert option. As of 2023, this encoder is +// likely to produce files that can be read by the most software. +// +// Due to the weak encryption, it is STRONGLY RECOMMENDED that you use [DefaultPassword] +// when encoding PKCS#12 files using this encoder, and protect the PKCS#12 files +// using other means. To create more secure PKCS#12 files, use [Modern2023]. +var LegacyDES = &Encoder{ + macAlgorithm: oidSHA1, + certAlgorithm: oidPBEWithSHAAnd3KeyTripleDESCBC, + keyAlgorithm: oidPBEWithSHAAnd3KeyTripleDESCBC, + macIterations: 1, + encryptionIterations: 2048, + saltLen: 8, + rand: rand.Reader, +} + +// Passwordless encodes PKCS#12 files without any encryption or MACs. +// A lot of software has trouble reading such files, so it's probably only +// useful for creating Java trust stores using [Encoder.EncodeTrustStore] +// or [Encoder.EncodeTrustStoreEntries]. +// +// When using this encoder, you MUST specify an empty password. +var Passwordless = &Encoder{ + macAlgorithm: nil, + certAlgorithm: nil, + keyAlgorithm: nil, + rand: rand.Reader, +} + +// Modern2023 encodes PKCS#12 files using algorithms that are considered modern +// as of 2023. Private keys and certificates are encrypted using PBES2 with +// PBKDF2-HMAC-SHA-256 and AES-256-CBC. The MAC algorithm is HMAC-SHA-2. These +// are the same algorithms used by OpenSSL 3 (by default), Java 20 (by default), +// and Windows Server 2019 (when "stronger" is used). +// +// Files produced with this encoder can be read by OpenSSL 1.1.1 and higher, +// Java 12 and higher, and Windows Server 2019 and higher. +// +// For passwords, it is RECOMMENDED that you do one of the following: +// 1) Use [DefaultPassword] and protect the file using other means, or +// 2) Use a high-entropy password, such as one generated with `openssl rand -hex 16`. +// +// You SHOULD NOT use a lower-entropy password with this encoder because the number of KDF +// iterations is only 2048 and doesn't provide meaningful protection against +// brute-forcing. You can increase the number of iterations using [Encoder.WithIterations], +// but as https://neilmadden.blog/2023/01/09/on-pbkdf2-iterations/ explains, this doesn't +// help as much as you think. +var Modern2023 = &Encoder{ + macAlgorithm: oidSHA256, + certAlgorithm: oidPBES2, + keyAlgorithm: oidPBES2, + macIterations: 2048, + encryptionIterations: 2048, + saltLen: 16, + rand: rand.Reader, +} + +// Legacy encodes PKCS#12 files using weak, legacy parameters that work in +// a wide variety of software. +// +// Currently, this encoder is the same as [LegacyDES], but this +// may change in the future if another encoder is found to provide better +// compatibility. +// +// Due to the weak encryption, it is STRONGLY RECOMMENDED that you use [DefaultPassword] +// when encoding PKCS#12 files using this encoder, and protect the PKCS#12 files +// using other means. +var Legacy = LegacyDES + +// Modern encodes PKCS#12 files using modern, robust parameters. +// +// Currently, this encoder is the same as [Modern2023], but this +// may change in the future to keep up with modern practices. +var Modern = Modern2023 + +// TrustStoreEntry represents an entry in a Java TrustStore. +type TrustStoreEntry struct { + Cert *x509.Certificate + FriendlyName string +} + +// WithIterations creates a new Encoder identical to enc except that +// it will use the given number of KDF iterations for deriving the MAC +// and encryption keys. +// +// Note that even with a large number of iterations, a weak +// password can still be brute-forced in much less time than it would +// take to brute-force a high-entropy encryption key. For the best +// security, don't worry about the number of iterations and just +// use a high-entropy password (e.g. one generated with `openssl rand -hex 16`). +// See https://neilmadden.blog/2023/01/09/on-pbkdf2-iterations/ for more detail. +// +// Panics if iterations is less than 1. +func (enc Encoder) WithIterations(iterations int) *Encoder { + if iterations < 1 { + panic("pkcs12: number of iterations is less than 1") + } + enc.macIterations = iterations + enc.encryptionIterations = iterations + return &enc +} + +// WithRand creates a new Encoder identical to enc except that +// it will use the given io.Reader for its random number generator +// instead of [crypto/rand.Reader]. +func (enc Encoder) WithRand(rand io.Reader) *Encoder { + enc.rand = rand + return &enc +} + +// Encode is equivalent to LegacyRC2.WithRand(rand).Encode. +// See [Encoder.Encode] and [LegacyRC2] for details. +// +// Deprecated: for the same behavior, use LegacyRC2.Encode; +// for better compatibility, use Legacy.Encode; +// for better security, use Modern.Encode. +func Encode(rand io.Reader, privateKey interface{}, certificate *x509.Certificate, caCerts []*x509.Certificate, password string) (pfxData []byte, err error) { + return LegacyRC2.WithRand(rand).Encode(privateKey, certificate, caCerts, password) +} + +// EncodeTrustStore is equivalent to LegacyRC2.WithRand(rand).EncodeTrustStore. +// See [Encoder.EncodeTrustStore] and [LegacyRC2] for details. +// +// Deprecated: for the same behavior, use LegacyRC2.EncodeTrustStore; +// to generate passwordless trust stores, use Passwordless.EncodeTrustStore. +func EncodeTrustStore(rand io.Reader, certs []*x509.Certificate, password string) (pfxData []byte, err error) { + return LegacyRC2.WithRand(rand).EncodeTrustStore(certs, password) +} + +// Encode produces pfxData containing one private key (privateKey), an +// end-entity certificate (certificate), and any number of CA certificates +// (caCerts). +// +// The pfxData is encrypted and authenticated with keys derived from +// the provided password. +// +// Encode emulates the behavior of OpenSSL's PKCS12_create: it creates two +// SafeContents: one that's encrypted with the certificate encryption algorithm +// and contains the certificates, and another that is unencrypted and contains the +// private key shrouded with the key encryption algorithm. The private key bag and +// the end-entity certificate bag have the LocalKeyId attribute set to the SHA-1 +// fingerprint of the end-entity certificate. +func (enc *Encoder) Encode(privateKey interface{}, certificate *x509.Certificate, caCerts []*x509.Certificate, password string) (pfxData []byte, err error) { + if enc.macAlgorithm == nil && enc.certAlgorithm == nil && enc.keyAlgorithm == nil && password != "" { + return nil, errors.New("pkcs12: password must be empty") + } + + encodedPassword, err := bmpStringZeroTerminated(password) + if err != nil { + return nil, err + } + + var pfx pfxPdu + pfx.Version = 3 + + var certFingerprint = sha1.Sum(certificate.Raw) + var localKeyIdAttr pkcs12Attribute + localKeyIdAttr.Id = oidLocalKeyID + localKeyIdAttr.Value.Class = 0 + localKeyIdAttr.Value.Tag = 17 + localKeyIdAttr.Value.IsCompound = true + if localKeyIdAttr.Value.Bytes, err = asn1.Marshal(certFingerprint[:]); err != nil { + return nil, err + } + + var certBags []safeBag + if certBag, err := makeCertBag(certificate.Raw, []pkcs12Attribute{localKeyIdAttr}); err != nil { + return nil, err + } else { + certBags = append(certBags, *certBag) + } + + for _, cert := range caCerts { + if certBag, err := makeCertBag(cert.Raw, []pkcs12Attribute{}); err != nil { + return nil, err + } else { + certBags = append(certBags, *certBag) + } + } + + var keyBag safeBag + if enc.keyAlgorithm == nil { + keyBag.Id = oidKeyBag + keyBag.Value.Class = 2 + keyBag.Value.Tag = 0 + keyBag.Value.IsCompound = true + if keyBag.Value.Bytes, err = x509.MarshalPKCS8PrivateKey(privateKey); err != nil { + return nil, err + } + } else { + keyBag.Id = oidPKCS8ShroundedKeyBag + keyBag.Value.Class = 2 + keyBag.Value.Tag = 0 + keyBag.Value.IsCompound = true + if keyBag.Value.Bytes, err = encodePkcs8ShroudedKeyBag(enc.rand, privateKey, enc.keyAlgorithm, encodedPassword, enc.encryptionIterations, enc.saltLen); err != nil { + return nil, err + } + } + keyBag.Attributes = append(keyBag.Attributes, localKeyIdAttr) + + // Construct an authenticated safe with two SafeContents. + // The first SafeContents is encrypted and contains the cert bags. + // The second SafeContents is unencrypted and contains the shrouded key bag. + var authenticatedSafe [2]contentInfo + if authenticatedSafe[0], err = makeSafeContents(enc.rand, certBags, enc.certAlgorithm, encodedPassword, enc.encryptionIterations, enc.saltLen); err != nil { + return nil, err + } + if authenticatedSafe[1], err = makeSafeContents(enc.rand, []safeBag{keyBag}, nil, nil, 0, 0); err != nil { + return nil, err + } + + var authenticatedSafeBytes []byte + if authenticatedSafeBytes, err = asn1.Marshal(authenticatedSafe[:]); err != nil { + return nil, err + } + + if enc.macAlgorithm != nil { + // compute the MAC + pfx.MacData.Mac.Algorithm.Algorithm = enc.macAlgorithm + pfx.MacData.MacSalt = make([]byte, enc.saltLen) + if _, err = enc.rand.Read(pfx.MacData.MacSalt); err != nil { + return nil, err + } + pfx.MacData.Iterations = enc.macIterations + if err = computeMac(&pfx.MacData, authenticatedSafeBytes, encodedPassword); err != nil { + return nil, err + } + } + + pfx.AuthSafe.ContentType = oidDataContentType + pfx.AuthSafe.Content.Class = 2 + pfx.AuthSafe.Content.Tag = 0 + pfx.AuthSafe.Content.IsCompound = true + if pfx.AuthSafe.Content.Bytes, err = asn1.Marshal(authenticatedSafeBytes); err != nil { + return nil, err + } + + if pfxData, err = asn1.Marshal(pfx); err != nil { + return nil, errors.New("pkcs12: error writing P12 data: " + err.Error()) + } + return +} + +// EncodeTrustStore produces pfxData containing any number of CA certificates +// (certs) to be trusted. The certificates will be marked with a special OID that +// allow it to be used as a Java TrustStore in Java 1.8 and newer. +// +// EncodeTrustStore creates a single SafeContents that's optionally encrypted +// and contains the certificates. +// +// The Subject of the certificates are used as the Friendly Names (Aliases) +// within the resulting pfxData. If certificates share a Subject, then the +// resulting Friendly Names (Aliases) will be identical, which Java may treat as +// the same entry when used as a Java TrustStore, e.g. with `keytool`. To +// customize the Friendly Names, use [EncodeTrustStoreEntries]. +func (enc *Encoder) EncodeTrustStore(certs []*x509.Certificate, password string) (pfxData []byte, err error) { + var certsWithFriendlyNames []TrustStoreEntry + for _, cert := range certs { + certsWithFriendlyNames = append(certsWithFriendlyNames, TrustStoreEntry{ + Cert: cert, + FriendlyName: cert.Subject.String(), + }) + } + return enc.EncodeTrustStoreEntries(certsWithFriendlyNames, password) +} + +// EncodeTrustStoreEntries is equivalent to LegacyRC2.WithRand(rand).EncodeTrustStoreEntries. +// See [Encoder.EncodeTrustStoreEntries] and [LegacyRC2] for details. +// +// Deprecated: for the same behavior, use LegacyRC2.EncodeTrustStoreEntries; to generate passwordless trust stores, +// use Passwordless.EncodeTrustStoreEntries. +func EncodeTrustStoreEntries(rand io.Reader, entries []TrustStoreEntry, password string) (pfxData []byte, err error) { + return LegacyRC2.WithRand(rand).EncodeTrustStoreEntries(entries, password) +} + +// EncodeTrustStoreEntries produces pfxData containing any number of CA +// certificates (entries) to be trusted. The certificates will be marked with a +// special OID that allow it to be used as a Java TrustStore in Java 1.8 and newer. +// +// This is identical to [Encoder.EncodeTrustStore], but also allows for setting specific +// Friendly Names (Aliases) to be used per certificate, by specifying a slice +// of TrustStoreEntry. +// +// If the same Friendly Name is used for more than one certificate, then the +// resulting Friendly Names (Aliases) in the pfxData will be identical, which Java +// may treat as the same entry when used as a Java TrustStore, e.g. with `keytool`. +// +// EncodeTrustStoreEntries creates a single SafeContents that's optionally +// encrypted and contains the certificates. +func (enc *Encoder) EncodeTrustStoreEntries(entries []TrustStoreEntry, password string) (pfxData []byte, err error) { + if enc.macAlgorithm == nil && enc.certAlgorithm == nil && password != "" { + return nil, errors.New("pkcs12: password must be empty") + } + + encodedPassword, err := bmpStringZeroTerminated(password) + if err != nil { + return nil, err + } + + var pfx pfxPdu + pfx.Version = 3 + + var certAttributes []pkcs12Attribute + + extKeyUsageOidBytes, err := asn1.Marshal(oidAnyExtendedKeyUsage) + if err != nil { + return nil, err + } + + // the oidJavaTrustStore attribute contains the EKUs for which + // this trust anchor will be valid + certAttributes = append(certAttributes, pkcs12Attribute{ + Id: oidJavaTrustStore, + Value: asn1.RawValue{ + Class: 0, + Tag: 17, + IsCompound: true, + Bytes: extKeyUsageOidBytes, + }, + }) + + var certBags []safeBag + for _, entry := range entries { + + bmpFriendlyName, err := bmpString(entry.FriendlyName) + if err != nil { + return nil, err + } + + encodedFriendlyName, err := asn1.Marshal(asn1.RawValue{ + Class: 0, + Tag: 30, + IsCompound: false, + Bytes: bmpFriendlyName, + }) + if err != nil { + return nil, err + } + + friendlyName := pkcs12Attribute{ + Id: oidFriendlyName, + Value: asn1.RawValue{ + Class: 0, + Tag: 17, + IsCompound: true, + Bytes: encodedFriendlyName, + }, + } + + certBag, err := makeCertBag(entry.Cert.Raw, append(certAttributes, friendlyName)) + if err != nil { + return nil, err + } + certBags = append(certBags, *certBag) + } + + // Construct an authenticated safe with one SafeContent. + // The SafeContents it contains the cert bags. + var authenticatedSafe [1]contentInfo + if authenticatedSafe[0], err = makeSafeContents(enc.rand, certBags, enc.certAlgorithm, encodedPassword, enc.encryptionIterations, enc.saltLen); err != nil { + return nil, err + } + + var authenticatedSafeBytes []byte + if authenticatedSafeBytes, err = asn1.Marshal(authenticatedSafe[:]); err != nil { + return nil, err + } + + if enc.macAlgorithm != nil { + // compute the MAC + pfx.MacData.Mac.Algorithm.Algorithm = enc.macAlgorithm + pfx.MacData.MacSalt = make([]byte, enc.saltLen) + if _, err = enc.rand.Read(pfx.MacData.MacSalt); err != nil { + return nil, err + } + pfx.MacData.Iterations = enc.macIterations + if err = computeMac(&pfx.MacData, authenticatedSafeBytes, encodedPassword); err != nil { + return nil, err + } + } + + pfx.AuthSafe.ContentType = oidDataContentType + pfx.AuthSafe.Content.Class = 2 + pfx.AuthSafe.Content.Tag = 0 + pfx.AuthSafe.Content.IsCompound = true + if pfx.AuthSafe.Content.Bytes, err = asn1.Marshal(authenticatedSafeBytes); err != nil { + return nil, err + } + + if pfxData, err = asn1.Marshal(pfx); err != nil { + return nil, errors.New("pkcs12: error writing P12 data: " + err.Error()) + } + return +} + +func makeCertBag(certBytes []byte, attributes []pkcs12Attribute) (certBag *safeBag, err error) { + certBag = new(safeBag) + certBag.Id = oidCertBag + certBag.Value.Class = 2 + certBag.Value.Tag = 0 + certBag.Value.IsCompound = true + if certBag.Value.Bytes, err = encodeCertBag(certBytes); err != nil { + return nil, err + } + certBag.Attributes = attributes + return +} + +func makeSafeContents(rand io.Reader, bags []safeBag, algoID asn1.ObjectIdentifier, password []byte, iterations int, saltLen int) (ci contentInfo, err error) { + var data []byte + if data, err = asn1.Marshal(bags); err != nil { + return + } + + if algoID == nil { + ci.ContentType = oidDataContentType + ci.Content.Class = 2 + ci.Content.Tag = 0 + ci.Content.IsCompound = true + if ci.Content.Bytes, err = asn1.Marshal(data); err != nil { + return + } + } else { + randomSalt := make([]byte, saltLen) + if _, err = rand.Read(randomSalt); err != nil { + return + } + + var algo pkix.AlgorithmIdentifier + algo.Algorithm = algoID + if algoID.Equal(oidPBES2) { + if algo.Parameters.FullBytes, err = makePBES2Parameters(rand, randomSalt, iterations); err != nil { + return + } + } else { + if algo.Parameters.FullBytes, err = asn1.Marshal(pbeParams{Salt: randomSalt, Iterations: iterations}); err != nil { + return + } + } + + var encryptedData encryptedData + encryptedData.Version = 0 + encryptedData.EncryptedContentInfo.ContentType = oidDataContentType + encryptedData.EncryptedContentInfo.ContentEncryptionAlgorithm = algo + if err = pbEncrypt(&encryptedData.EncryptedContentInfo, data, password); err != nil { + return + } + + ci.ContentType = oidEncryptedDataContentType + ci.Content.Class = 2 + ci.Content.Tag = 0 + ci.Content.IsCompound = true + if ci.Content.Bytes, err = asn1.Marshal(encryptedData); err != nil { + return + } + } + return +} diff --git a/pkcs12_pem.go b/pkcs12_pem.go new file mode 100644 index 0000000..c443bb0 --- /dev/null +++ b/pkcs12_pem.go @@ -0,0 +1,132 @@ +// Copyright 2015, 2018, 2019 Opsmate, Inc. All rights reserved. +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package pkcs12 + +import ( + "crypto/ecdsa" + "crypto/rsa" + "crypto/x509" + "encoding/hex" + "encoding/pem" + "errors" +) + +// PEM block types +const ( + certificateType = "CERTIFICATE" + privateKeyType = "PRIVATE KEY" +) + +// ToPEM converts all "safe bags" contained in pfxData to PEM blocks. +// +// Deprecated: ToPEM creates invalid PEM blocks (private keys +// are encoded as raw RSA or EC private keys rather than PKCS#8 despite being +// labeled "PRIVATE KEY"). To decode a PKCS#12 file, use [DecodeChain] instead, +// and use the [encoding/pem] package to convert to PEM if necessary. +func ToPEM(pfxData []byte, password string) ([]*pem.Block, error) { + encodedPassword, err := bmpStringZeroTerminated(password) + if err != nil { + return nil, ErrIncorrectPassword + } + + bags, encodedPassword, err := getSafeContents(pfxData, encodedPassword, 2, 2) + + if err != nil { + return nil, err + } + + blocks := make([]*pem.Block, 0, len(bags)) + for _, bag := range bags { + block, err := convertBag(&bag, encodedPassword) + if err != nil { + return nil, err + } + blocks = append(blocks, block) + } + + return blocks, nil +} + +func convertBag(bag *safeBag, password []byte) (*pem.Block, error) { + block := &pem.Block{ + Headers: make(map[string]string), + } + + for _, attribute := range bag.Attributes { + k, v, err := convertAttribute(&attribute) + if err != nil { + return nil, err + } + block.Headers[k] = v + } + + switch { + case bag.Id.Equal(oidCertBag): + block.Type = certificateType + certsData, err := decodeCertBag(bag.Value.Bytes) + if err != nil { + return nil, err + } + block.Bytes = certsData + case bag.Id.Equal(oidPKCS8ShroundedKeyBag): + block.Type = privateKeyType + + key, err := decodePkcs8ShroudedKeyBag(bag.Value.Bytes, password) + if err != nil { + return nil, err + } + + switch key := key.(type) { + case *rsa.PrivateKey: + block.Bytes = x509.MarshalPKCS1PrivateKey(key) + case *ecdsa.PrivateKey: + block.Bytes, err = x509.MarshalECPrivateKey(key) + if err != nil { + return nil, err + } + default: + return nil, errors.New("pkcs12: found unknown private key type in PKCS#8 wrapping") + } + default: + return nil, errors.New("pkcs12: don't know how to convert a safe bag of type " + bag.Id.String()) + } + return block, nil +} + +func convertAttribute(attribute *pkcs12Attribute) (key, value string, err error) { + isString := false + + switch { + case attribute.Id.Equal(oidFriendlyName): + key = "friendlyName" + isString = true + case attribute.Id.Equal(oidLocalKeyID): + key = "localKeyId" + case attribute.Id.Equal(oidMicrosoftCSPName): + // This key is chosen to match OpenSSL. + key = "Microsoft CSP Name" + isString = true + default: + return "", "", errors.New("pkcs12: unknown attribute with OID " + attribute.Id.String()) + } + + if isString { + if err := unmarshal(attribute.Value.Bytes, &attribute.Value); err != nil { + return "", "", err + } + if value, err = decodeBMPString(attribute.Value.Bytes); err != nil { + return "", "", err + } + } else { + var id []byte + if err := unmarshal(attribute.Value.Bytes, &id); err != nil { + return "", "", err + } + value = hex.EncodeToString(id) + } + + return key, value, nil +} diff --git a/pkcs12_test.go b/pkcs12_test.go index f563577..99eebad 100644 --- a/pkcs12_test.go +++ b/pkcs12_test.go @@ -9,213 +9,97 @@ import ( "crypto/rsa" "crypto/tls" "crypto/x509" - "encoding/base64" + _ "embed" "encoding/pem" "testing" ) -func TestPfx(t *testing.T) { - for commonName, base64P12 := range testdata { - p12, _ := base64.StdEncoding.DecodeString(base64P12) +//go:embed test-data/testing_at_example_com.p12 +var fileTestingAtExampleCom []byte - priv, cert, err := Decode(p12, "") - if err != nil { - t.Fatal(err) - } +//go:embed test-data/windows_azure_tools.p12 +var fileWindowsAzureTools []byte - if err := priv.(*rsa.PrivateKey).Validate(); err != nil { - t.Errorf("error while validating private key: %v", err) - } +var testdata = map[string][]byte{ + // 'null' password test case + "Windows Azure Tools": fileWindowsAzureTools, + // empty string password test case + "testing@example.com": fileTestingAtExampleCom, +} - if cert.Subject.CommonName != commonName { - t.Errorf("expected common name to be %q, but found %q", commonName, cert.Subject.CommonName) - } +func TestPfx(t *testing.T) { + for commonName, p12 := range testdata { + t.Run(commonName, func(t *testing.T) { + priv, cert, err := Decode(p12, "") + if err != nil { + t.Fatal(err) + } + + if err := priv.(*rsa.PrivateKey).Validate(); err != nil { + t.Errorf("error while validating private key: %v", err) + } + + if cert.Subject.CommonName != commonName { + t.Errorf("expected common name to be %q, but found %q", commonName, cert.Subject.CommonName) + } + }) } } func TestPEM(t *testing.T) { - for commonName, base64P12 := range testdata { - p12, _ := base64.StdEncoding.DecodeString(base64P12) - - blocks, err := ToPEM(p12, "") - if err != nil { - t.Fatalf("error while converting to PEM: %s", err) - } - - var pemData []byte - for _, b := range blocks { - pemData = append(pemData, pem.EncodeToMemory(b)...) - } - - cert, err := tls.X509KeyPair(pemData, pemData) - if err != nil { - t.Errorf("err while converting to key pair: %v", err) - } - config := tls.Config{ - Certificates: []tls.Certificate{cert}, - } - config.BuildNameToCertificate() - - if _, exists := config.NameToCertificate[commonName]; !exists { - t.Errorf("did not find our cert in PEM?: %v", config.NameToCertificate) - } + for commonName, p12 := range testdata { + t.Run(commonName, func(t *testing.T) { + blocks, err := ToPEM(p12, "") + if err != nil { + t.Fatalf("error while converting to PEM: %s", err) + } + + var pemData []byte + for _, b := range blocks { + pemData = append(pemData, pem.EncodeToMemory(b)...) + } + + cert, err := tls.X509KeyPair(pemData, pemData) + if err != nil { + t.Errorf("err while converting to key pair: %v", err) + } + config := tls.Config{ + Certificates: []tls.Certificate{cert}, + } + config.BuildNameToCertificate() + + if _, exists := config.NameToCertificate[commonName]; !exists { + t.Errorf("did not find our cert in PEM?: %v", config.NameToCertificate) + } + }) } } func TestTrustStore(t *testing.T) { - for commonName, base64P12 := range testdata { - p12, _ := base64.StdEncoding.DecodeString(base64P12) - - _, cert, err := Decode(p12, "") - if err != nil { - t.Fatal(err) - } - - pfxData, err := EncodeTrustStore(rand.Reader, []*x509.Certificate{cert}, "password") - if err != nil { - t.Fatal(err) - } - - decodedCerts, err := DecodeTrustStore(pfxData, "password") - if err != nil { - t.Fatal(err) - } - - if len(decodedCerts) != 1 { - t.Fatal("Unexpected number of certs") - } - - if decodedCerts[0].Subject.CommonName != commonName { - t.Errorf("expected common name to be %q, but found %q", commonName, decodedCerts[0].Subject.CommonName) - } - } -} - -func TestPBES2_AES256CBC(t *testing.T) { - // This P12 PDU is a self-signed certificate exported via Windows certmgr. - // It is encrypted with the following options (verified via openssl): PBES2, PBKDF2, AES-256-CBC, Iteration 2000, PRF hmacWithSHA256 - commonName := "*.ad.standalone.com" - base64P12 := `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` - p12, _ := base64.StdEncoding.DecodeString(base64P12) - pk, cert, caCerts, err := DecodeChain(p12, "password") - if err != nil { - t.Fatal(err) - } - - rsaPk, ok := pk.(*rsa.PrivateKey) - if !ok { - t.Error("could not cast to rsa private key") - } - if !rsaPk.PublicKey.Equal(cert.PublicKey) { - t.Error("public key embedded in private key not equal to public key of certificate") - } - if cert.Subject.CommonName != commonName { - t.Errorf("unexpected leaf cert common name, got %s, want %s", cert.Subject.CommonName, commonName) + for commonName, p12 := range testdata { + t.Run(commonName, func(t *testing.T) { + _, cert, err := Decode(p12, "") + if err != nil { + t.Fatal(err) + } + + pfxData, err := EncodeTrustStore(rand.Reader, []*x509.Certificate{cert}, "password") + if err != nil { + t.Fatal(err) + } + + decodedCerts, err := DecodeTrustStore(pfxData, "password") + if err != nil { + t.Fatal(err) + } + + if len(decodedCerts) != 1 { + t.Fatal("Unexpected number of certs") + } + + if decodedCerts[0].Subject.CommonName != commonName { + t.Errorf("expected common name to be %q, but found %q", commonName, decodedCerts[0].Subject.CommonName) + } + }) } - if len(caCerts) != 0 { - t.Errorf("unexpected # of caCerts: got %d, want 0", len(caCerts)) - } -} - -func TestPBES2_AES128CBC(t *testing.T) { - //PKCS7 Encrypted data: PBES2, PBKDF2, AES-128-CBC, Iteration 2048, PRF hmacWithSHA256 - commonName := "example-com" - base64P12 := `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` - - p12, _ := base64.StdEncoding.DecodeString(base64P12) - pk, cert, caCerts, err := DecodeChain(p12, "rHyQTJsubhfxcpH5JttyilHE6BBsNoZp") - if err != nil { - t.Fatal(err) - } - - rsaPk, ok := pk.(*rsa.PrivateKey) - if !ok { - t.Error("could not cast to rsa private key") - } - if !rsaPk.PublicKey.Equal(cert.PublicKey) { - t.Error("public key embedded in private key not equal to public key of certificate") - } - if cert.Subject.CommonName != commonName { - t.Errorf("unexpected leaf cert common name, got %s, want %s", cert.Subject.CommonName, commonName) - } - if len(caCerts) != 0 { - t.Errorf("unexpected # of caCerts: got %d, want 0", len(caCerts)) - } -} - -func TestPBES2_AES192CBC(t *testing.T) { - //PKCS7 Encrypted data: PBES2, PBKDF2, AES-192-CBC, Iteration 2048, PRF hmacWithSHA256 - commonName := "example-com" - base64P12 := `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` - - p12, _ := base64.StdEncoding.DecodeString(base64P12) - pk, cert, caCerts, err := DecodeChain(p12, "password") - if err != nil { - t.Fatal(err) - } - - rsaPk, ok := pk.(*rsa.PrivateKey) - if !ok { - t.Error("could not cast to rsa private key") - } - if !rsaPk.PublicKey.Equal(cert.PublicKey) { - t.Error("public key embedded in private key not equal to public key of certificate") - } - if cert.Subject.CommonName != commonName { - t.Errorf("unexpected leaf cert common name, got %s, want %s", cert.Subject.CommonName, commonName) - } - if len(caCerts) != 0 { - t.Errorf("unexpected # of caCerts: got %d, want 0", len(caCerts)) - } -} - -var testdata = map[string]string{ - // 'null' password test case - "Windows Azure Tools": `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`, - // empty string password test case - "testing@example.com": `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 -HQ8CAggA`, } diff --git a/test-data/ad_standalone_com_aescbc256.p12 b/test-data/ad_standalone_com_aescbc256.p12 new file mode 100644 index 0000000..a1f7bf9 Binary files /dev/null and b/test-data/ad_standalone_com_aescbc256.p12 differ diff --git a/test-data/example_com_aescbc128.p12 b/test-data/example_com_aescbc128.p12 new file mode 100644 index 0000000..6088478 Binary files /dev/null and b/test-data/example_com_aescbc128.p12 differ diff --git a/test-data/example_com_aescbc192.p12 b/test-data/example_com_aescbc192.p12 new file mode 100644 index 0000000..83cce79 Binary files /dev/null and b/test-data/example_com_aescbc192.p12 differ diff --git a/test-data/example_signed_certificates_chain.p12 b/test-data/example_signed_certificates_chain.p12 new file mode 100644 index 0000000..31de0b7 Binary files /dev/null and b/test-data/example_signed_certificates_chain.p12 differ diff --git a/test-data/example_signed_certificates_chain.png b/test-data/example_signed_certificates_chain.png new file mode 100644 index 0000000..f92809e Binary files /dev/null and b/test-data/example_signed_certificates_chain.png differ diff --git a/test-data/testing_at_example_com.p12 b/test-data/testing_at_example_com.p12 new file mode 100644 index 0000000..5e6c1e0 Binary files /dev/null and b/test-data/testing_at_example_com.p12 differ diff --git a/test-data/windows_azure_tools.p12 b/test-data/windows_azure_tools.p12 new file mode 100644 index 0000000..81ed49a Binary files /dev/null and b/test-data/windows_azure_tools.p12 differ