531 lines
14 KiB
Go
531 lines
14 KiB
Go
// 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.
|
|
package pkcs12
|
|
|
|
import (
|
|
"crypto/ecdsa"
|
|
"crypto/rand"
|
|
"crypto/rsa"
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"encoding/asn1"
|
|
"encoding/hex"
|
|
"encoding/pem"
|
|
"errors"
|
|
"io"
|
|
)
|
|
|
|
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})
|
|
|
|
localKeyId = []byte{0x01, 0x00, 0x00, 0x00}
|
|
)
|
|
|
|
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 unmarshalling.
|
|
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
|
|
}
|
|
|
|
// ConvertToPEM converts all "safe bags" contained in pfxData to PEM blocks.
|
|
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)
|
|
|
|
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(oidPKCS8ShroudedKeyBag):
|
|
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 "", "", 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. This function
|
|
// assumes that there is only one certificate and only one private key in the
|
|
// pfxData.
|
|
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(oidPKCS8ShroudedKeyBag):
|
|
if privateKey != nil {
|
|
err = errors.New("pkcs12: expected exactly one key bag")
|
|
}
|
|
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 getLocalKeyId(id []byte) (attribute pkcs12Attribute, err error) {
|
|
octetString := asn1.RawValue{Tag: 4, Class: 0, IsCompound: false, Bytes: id}
|
|
bytes, err := asn1.Marshal(octetString)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
attribute = pkcs12Attribute{
|
|
Id: oidLocalKeyID,
|
|
Value: asn1.RawValue{Tag: 17, Class: 0, IsCompound: true, Bytes: bytes},
|
|
}
|
|
|
|
return attribute, nil
|
|
}
|
|
|
|
func convertToRawVal(val interface{}) (raw asn1.RawValue, err error) {
|
|
bytes, err := asn1.Marshal(val)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
_, err = asn1.Unmarshal(bytes, &raw)
|
|
return raw, nil
|
|
}
|
|
|
|
func makeSafeBags(oid asn1.ObjectIdentifier, value []byte) ([]safeBag, error) {
|
|
attribute, err := getLocalKeyId(localKeyId)
|
|
|
|
if err != nil {
|
|
return nil, EncodeError("local key id: " + err.Error())
|
|
}
|
|
|
|
bag := make([]safeBag, 1)
|
|
bag[0] = safeBag{
|
|
Id: oid,
|
|
Value: asn1.RawValue{Tag: 0, Class: 2, IsCompound: true, Bytes: value},
|
|
Attributes: []pkcs12Attribute{attribute},
|
|
}
|
|
|
|
return bag, nil
|
|
}
|
|
|
|
func makeCertBagContentInfo(derBytes []byte) (*contentInfo, error) {
|
|
certBag1 := certBag{
|
|
Id: oidCertTypeX509Certificate,
|
|
Data: derBytes,
|
|
}
|
|
|
|
bytes, err := asn1.Marshal(certBag1)
|
|
if err != nil {
|
|
return nil, EncodeError("encoding cert bag: " + err.Error())
|
|
}
|
|
|
|
certSafeBags, err := makeSafeBags(oidCertBag, bytes)
|
|
if err != nil {
|
|
return nil, EncodeError("safe bags: " + err.Error())
|
|
}
|
|
|
|
return makeContentInfo(certSafeBags)
|
|
}
|
|
|
|
func makeShroudedKeyBagContentInfo(privateKey interface{}, password []byte) (*contentInfo, error) {
|
|
shroudedKeyBagBytes, err := encodePkcs8ShroudedKeyBag(privateKey, password)
|
|
if err != nil {
|
|
return nil, EncodeError("encode PKCS#8 shrouded key bag: " + err.Error())
|
|
}
|
|
|
|
safeBags, err := makeSafeBags(oidPKCS8ShroudedKeyBag, shroudedKeyBagBytes)
|
|
if err != nil {
|
|
return nil, EncodeError("safe bags: " + err.Error())
|
|
}
|
|
|
|
return makeContentInfo(safeBags)
|
|
}
|
|
|
|
func makeContentInfo(val interface{}) (*contentInfo, error) {
|
|
fullBytes, err := asn1.Marshal(val)
|
|
if err != nil {
|
|
return nil, EncodeError("contentInfo raw value marshal: " + err.Error())
|
|
}
|
|
|
|
octetStringVal := asn1.RawValue{Tag: 4, Class: 0, IsCompound: false, Bytes: fullBytes}
|
|
octetStringFullBytes, err := asn1.Marshal(octetStringVal)
|
|
if err != nil {
|
|
return nil, EncodeError("raw contentInfo to octet string: " + err.Error())
|
|
}
|
|
|
|
contentInfo := contentInfo{ContentType: oidDataContentType}
|
|
contentInfo.Content = asn1.RawValue{Tag: 0, Class: 2, IsCompound: true, Bytes: octetStringFullBytes}
|
|
|
|
return &contentInfo, nil
|
|
}
|
|
|
|
func makeContentInfos(derBytes []byte, privateKey interface{}, password []byte) ([]contentInfo, error) {
|
|
shroudedKeyContentInfo, err := makeShroudedKeyBagContentInfo(privateKey, password)
|
|
if err != nil {
|
|
return nil, EncodeError("shrouded key content info: " + err.Error())
|
|
}
|
|
|
|
certBagContentInfo, err := makeCertBagContentInfo(derBytes)
|
|
if err != nil {
|
|
return nil, EncodeError("cert bag content info: " + err.Error())
|
|
}
|
|
|
|
contentInfos := make([]contentInfo, 2)
|
|
contentInfos[0] = *shroudedKeyContentInfo
|
|
contentInfos[1] = *certBagContentInfo
|
|
|
|
return contentInfos, nil
|
|
}
|
|
|
|
func makeSalt(saltByteCount int) ([]byte, error) {
|
|
salt := make([]byte, saltByteCount)
|
|
_, err := io.ReadFull(rand.Reader, salt)
|
|
return salt, err
|
|
}
|
|
|
|
// Encode converts a certificate and a private key to the PKCS#12 byte stream format.
|
|
//
|
|
// derBytes is a DER encoded certificate.
|
|
// privateKey is an RSA
|
|
func Encode(derBytes []byte, privateKey interface{}, password string) (pfxBytes []byte, err error) {
|
|
secret, err := bmpString(password)
|
|
if err != nil {
|
|
return nil, ErrIncorrectPassword
|
|
}
|
|
|
|
contentInfos, err := makeContentInfos(derBytes, privateKey, secret)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Marshal []contentInfo so we can re-constitute the byte stream that will
|
|
// be suitable for computing the MAC
|
|
bytes, err := asn1.Marshal(contentInfos)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Unmarshal as an asn1.RawValue so, we can compute the MAC against the .Bytes
|
|
var contentInfosRaw asn1.RawValue
|
|
err = unmarshal(bytes, &contentInfosRaw)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
authSafeContentInfo, err := makeContentInfo(contentInfosRaw)
|
|
if err != nil {
|
|
return nil, EncodeError("authSafe content info: " + err.Error())
|
|
}
|
|
|
|
salt, err := makeSalt(pbeSaltSizeBytes)
|
|
if err != nil {
|
|
return nil, EncodeError("salt value: " + err.Error())
|
|
}
|
|
|
|
// Compute the MAC for marshaled bytes of contentInfos, which includes the
|
|
// cert bag, and the shrouded key bag.
|
|
digest := computeMac(contentInfosRaw.FullBytes, pbeIterationCount, salt, secret)
|
|
|
|
pfx := pfxPdu{
|
|
Version: 3,
|
|
AuthSafe: *authSafeContentInfo,
|
|
MacData: macData{
|
|
Iterations: pbeIterationCount,
|
|
MacSalt: salt,
|
|
Mac: digestInfo{
|
|
Algorithm: pkix.AlgorithmIdentifier{
|
|
Algorithm: oidSHA1,
|
|
},
|
|
Digest: digest,
|
|
},
|
|
},
|
|
}
|
|
|
|
bytes, err = asn1.Marshal(pfx)
|
|
if err != nil {
|
|
return nil, EncodeError("marshal PFX PDU: " + err.Error())
|
|
}
|
|
|
|
return bytes, err
|
|
}
|
|
|
|
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
|
|
}
|