| // 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 implements some of PKCS#12. |
| // |
| // This implementation is distilled from https://tools.ietf.org/html/rfc7292 |
| // and referenced documents. It is intended for decoding P12/PFX-stored |
| // certificates and keys for use with the crypto/tls package. |
| // |
| // This package is frozen. If it's missing functionality you need, consider |
| // an alternative like software.sslmate.com/src/go-pkcs12. |
| package pkcs12 |
| |
| import ( |
| "crypto/ecdsa" |
| "crypto/rsa" |
| "crypto/x509" |
| "crypto/x509/pkix" |
| "encoding/asn1" |
| "encoding/hex" |
| "encoding/pem" |
| "errors" |
| ) |
| |
| var ( |
| oidDataContentType = asn1.ObjectIdentifier([]int{1, 2, 840, 113549, 1, 7, 1}) |
| oidEncryptedDataContentType = asn1.ObjectIdentifier([]int{1, 2, 840, 113549, 1, 7, 6}) |
| |
| oidFriendlyName = asn1.ObjectIdentifier([]int{1, 2, 840, 113549, 1, 9, 20}) |
| oidLocalKeyID = asn1.ObjectIdentifier([]int{1, 2, 840, 113549, 1, 9, 21}) |
| oidMicrosoftCSPName = asn1.ObjectIdentifier([]int{1, 3, 6, 1, 4, 1, 311, 17, 1}) |
| |
| errUnknownAttributeOID = errors.New("pkcs12: unknown attribute OID") |
| ) |
| |
| type pfxPdu struct { |
| Version int |
| AuthSafe contentInfo |
| MacData macData `asn1:"optional"` |
| } |
| |
| type contentInfo struct { |
| ContentType asn1.ObjectIdentifier |
| Content asn1.RawValue `asn1:"tag:0,explicit,optional"` |
| } |
| |
| type encryptedData struct { |
| Version int |
| EncryptedContentInfo encryptedContentInfo |
| } |
| |
| type encryptedContentInfo struct { |
| ContentType asn1.ObjectIdentifier |
| ContentEncryptionAlgorithm pkix.AlgorithmIdentifier |
| EncryptedContent []byte `asn1:"tag:0,optional"` |
| } |
| |
| func (i encryptedContentInfo) Algorithm() pkix.AlgorithmIdentifier { |
| return i.ContentEncryptionAlgorithm |
| } |
| |
| func (i encryptedContentInfo) Data() []byte { return i.EncryptedContent } |
| |
| type safeBag struct { |
| Id asn1.ObjectIdentifier |
| Value asn1.RawValue `asn1:"tag:0,explicit"` |
| Attributes []pkcs12Attribute `asn1:"set,optional"` |
| } |
| |
| type pkcs12Attribute struct { |
| Id asn1.ObjectIdentifier |
| Value asn1.RawValue `asn1:"set"` |
| } |
| |
| type encryptedPrivateKeyInfo struct { |
| AlgorithmIdentifier pkix.AlgorithmIdentifier |
| EncryptedData []byte |
| } |
| |
| func (i encryptedPrivateKeyInfo) Algorithm() pkix.AlgorithmIdentifier { |
| return i.AlgorithmIdentifier |
| } |
| |
| func (i encryptedPrivateKeyInfo) Data() []byte { |
| return i.EncryptedData |
| } |
| |
| // 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. |
| func unmarshal(in []byte, out interface{}) error { |
| trailing, err := asn1.Unmarshal(in, out) |
| if err != nil { |
| return err |
| } |
| if len(trailing) != 0 { |
| return errors.New("pkcs12: trailing data found") |
| } |
| return nil |
| } |
| |
| // ToPEM converts all "safe bags" contained in pfxData to PEM blocks. |
| // Unknown attributes are discarded. |
| // |
| // Note that although the returned PEM blocks for private keys have type |
| // "PRIVATE KEY", the bytes are not encoded according to PKCS #8, but according |
| // to PKCS #1 for RSA keys and SEC 1 for ECDSA keys. |
| func ToPEM(pfxData []byte, password string) ([]*pem.Block, error) { |
| encodedPassword, err := bmpString(password) |
| if err != nil { |
| return nil, ErrIncorrectPassword |
| } |
| |
| bags, encodedPassword, err := getSafeContents(pfxData, encodedPassword) |
| |
| 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 == errUnknownAttributeOID { |
| continue |
| } |
| 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("found unknown private key type in PKCS#8 wrapping") |
| } |
| default: |
| return nil, errors.New("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 "", "", errUnknownAttributeOID |
| } |
| |
| 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. This function |
| // assumes that there is only one certificate and only one private key in the |
| // pfxData; if there are more use ToPEM instead. |
| func Decode(pfxData []byte, password string) (privateKey interface{}, certificate *x509.Certificate, err error) { |
| encodedPassword, err := bmpString(password) |
| if err != nil { |
| return nil, nil, err |
| } |
| |
| bags, encodedPassword, err := getSafeContents(pfxData, encodedPassword) |
| if err != nil { |
| return nil, nil, err |
| } |
| |
| if len(bags) != 2 { |
| err = errors.New("pkcs12: expected exactly two safe bags in the PFX PDU") |
| return |
| } |
| |
| for _, bag := range bags { |
| switch { |
| case bag.Id.Equal(oidCertBag): |
| if certificate != nil { |
| err = errors.New("pkcs12: expected exactly one certificate bag") |
| } |
| |
| certsData, err := decodeCertBag(bag.Value.Bytes) |
| if err != nil { |
| return nil, nil, err |
| } |
| certs, err := x509.ParseCertificates(certsData) |
| if err != nil { |
| return nil, nil, err |
| } |
| if len(certs) != 1 { |
| err = errors.New("pkcs12: expected exactly one certificate in the certBag") |
| return nil, nil, err |
| } |
| certificate = certs[0] |
| |
| case bag.Id.Equal(oidPKCS8ShroundedKeyBag): |
| if privateKey != nil { |
| err = errors.New("pkcs12: expected exactly one key bag") |
| return nil, nil, err |
| } |
| |
| if privateKey, err = decodePkcs8ShroudedKeyBag(bag.Value.Bytes, encodedPassword); err != nil { |
| return nil, nil, err |
| } |
| } |
| } |
| |
| if certificate == nil { |
| return nil, nil, errors.New("pkcs12: certificate missing") |
| } |
| if privateKey == nil { |
| return nil, nil, errors.New("pkcs12: private key missing") |
| } |
| |
| return |
| } |
| |
| func getSafeContents(p12Data, password []byte) (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 { |
| return nil, nil, errors.New("pkcs12: no MAC in data") |
| } |
| |
| 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) != 2 { |
| return nil, nil, NotImplementedError("expected exactly two items in the authenticated safe") |
| } |
| |
| 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 |
| } |