Update module software.sslmate.com/src/go-pkcs12 to v0.4.0

This commit is contained in:
Renovate Bot
2024-06-04 20:15:24 +00:00
parent 7f03dbbc14
commit 0cab6ed1c7
9 changed files with 510 additions and 154 deletions

4
vendor/modules.txt vendored
View File

@@ -81,7 +81,7 @@ google.golang.org/protobuf/runtime/protoimpl
google.golang.org/protobuf/types/known/anypb
google.golang.org/protobuf/types/known/durationpb
google.golang.org/protobuf/types/known/timestamppb
# software.sslmate.com/src/go-pkcs12 v0.0.0-20200830195227-52f69702a001
## explicit
# software.sslmate.com/src/go-pkcs12 v0.4.0
## explicit; go 1.19
software.sslmate.com/src/go-pkcs12
software.sslmate.com/src/go-pkcs12/internal/rc2

View File

@@ -1,6 +1,6 @@
# package pkcs12
[![GoDoc](https://godoc.org/software.sslmate.com/src/go-pkcs12?status.svg)](https://godoc.org/software.sslmate.com/src/go-pkcs12)
[![Documentation](https://pkg.go.dev/badge/software.sslmate.com/src/go-pkcs12)](https://pkg.go.dev/software.sslmate.com/src/go-pkcs12)
import "software.sslmate.com/src/go-pkcs12"
@@ -11,14 +11,12 @@ do not support newer formats. Since PKCS#12 uses weak encryption
primitives, it SHOULD NOT be used for new applications.
Note that only DER-encoded PKCS#12 files are supported, even though PKCS#12
allows BER encoding. This is becuase encoding/asn1 only supports DER.
allows BER encoding. This is because encoding/asn1 only supports DER.
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.
This repository holds supplementary Go cryptography libraries.
## Import Path
Note that although the source code and issue tracker for this package are hosted
@@ -28,11 +26,6 @@ on GitHub, the import path is:
Please be sure to use this path when you `go get` and `import` this package.
## Download/Install
The easiest way to install is to run `go get -u software.sslmate.com/src/go-pkcs12`. You
can also manually git clone the repository to `$GOPATH/src/software.sslmate.com/src/go-pkcs12`.
## Report Issues / Send Patches
Open an issue or PR at https://github.com/SSLMate/go-pkcs12

View File

@@ -9,14 +9,27 @@ import (
"unicode/utf16"
)
// bmpString returns s encoded in UCS-2 with a zero terminator.
// bmpStringZeroTerminated returns s encoded in UCS-2 with a zero terminator.
func bmpStringZeroTerminated(s string) ([]byte, error) {
// References:
// https://tools.ietf.org/html/rfc7292#appendix-B.1
// The above RFC provides the info that BMPStrings are NULL terminated.
ret, err := bmpString(s)
if err != nil {
return nil, err
}
return append(ret, 0, 0), nil
}
// bmpString returns s encoded in UCS-2
func bmpString(s string) ([]byte, error) {
// References:
// https://tools.ietf.org/html/rfc7292#appendix-B.1
// https://en.wikipedia.org/wiki/Plane_(Unicode)#Basic_Multilingual_Plane
// - non-BMP characters are encoded in UTF 16 by using a surrogate pair of 16-bit codes
// EncodeRune returns 0xfffd if the rune does not need special encoding
// - the above RFC provides the info that BMPStrings are NULL terminated.
ret := make([]byte, 0, 2*len(s)+2)
@@ -27,7 +40,7 @@ func bmpString(s string) ([]byte, error) {
ret = append(ret, byte(r/256), byte(r%256))
}
return append(ret, 0, 0), nil
return ret, nil
}
func decodeBMPString(bmpString []byte) (string, error) {

View File

@@ -16,6 +16,7 @@ import (
"encoding/asn1"
"errors"
"hash"
"io"
"golang.org/x/crypto/pbkdf2"
"software.sslmate.com/src/go-pkcs12/internal/rc2"
@@ -23,11 +24,14 @@ import (
var (
oidPBEWithSHAAnd3KeyTripleDESCBC = asn1.ObjectIdentifier([]int{1, 2, 840, 113549, 1, 12, 1, 3})
oidPBEWithSHAAnd128BitRC2CBC = asn1.ObjectIdentifier([]int{1, 2, 840, 113549, 1, 12, 1, 5})
oidPBEWithSHAAnd40BitRC2CBC = asn1.ObjectIdentifier([]int{1, 2, 840, 113549, 1, 12, 1, 6})
oidPBES2 = asn1.ObjectIdentifier([]int{1, 2, 840, 113549, 1, 5, 13})
oidPBKDF2 = asn1.ObjectIdentifier([]int{1, 2, 840, 113549, 1, 5, 12})
oidHmacWithSHA1 = asn1.ObjectIdentifier([]int{1, 2, 840, 113549, 2, 7})
oidHmacWithSHA256 = asn1.ObjectIdentifier([]int{1, 2, 840, 113549, 2, 9})
oidAES128CBC = asn1.ObjectIdentifier([]int{2, 16, 840, 1, 101, 3, 4, 1, 2})
oidAES192CBC = asn1.ObjectIdentifier([]int{2, 16, 840, 1, 101, 3, 4, 1, 22})
oidAES256CBC = asn1.ObjectIdentifier([]int{2, 16, 840, 1, 101, 3, 4, 1, 42})
)
@@ -55,6 +59,20 @@ func (shaWithTripleDESCBC) deriveIV(salt, password []byte, iterations int) []byt
return pbkdf(sha1Sum, 20, 64, salt, password, iterations, 2, 8)
}
type shaWith128BitRC2CBC struct{}
func (shaWith128BitRC2CBC) create(key []byte) (cipher.Block, error) {
return rc2.New(key, len(key)*8)
}
func (shaWith128BitRC2CBC) deriveKey(salt, password []byte, iterations int) []byte {
return pbkdf(sha1Sum, 20, 64, salt, password, iterations, 1, 16)
}
func (shaWith128BitRC2CBC) deriveIV(salt, password []byte, iterations int) []byte {
return pbkdf(sha1Sum, 20, 64, salt, password, iterations, 2, 8)
}
type shaWith40BitRC2CBC struct{}
func (shaWith40BitRC2CBC) create(key []byte) (cipher.Block, error) {
@@ -80,6 +98,8 @@ func pbeCipherFor(algorithm pkix.AlgorithmIdentifier, password []byte) (cipher.B
switch {
case algorithm.Algorithm.Equal(oidPBEWithSHAAnd3KeyTripleDESCBC):
cipherType = shaWithTripleDESCBC{}
case algorithm.Algorithm.Equal(oidPBEWithSHAAnd128BitRC2CBC):
cipherType = shaWith128BitRC2CBC{}
case algorithm.Algorithm.Equal(oidPBEWithSHAAnd40BitRC2CBC):
cipherType = shaWith40BitRC2CBC{}
case algorithm.Algorithm.Equal(oidPBES2):
@@ -146,6 +166,7 @@ func pbDecrypt(info decryptable, password []byte) (decrypted []byte, err error)
if len(decrypted) < psLen {
return nil, ErrDecryption
}
ps := decrypted[len(decrypted)-psLen:]
decrypted = decrypted[:len(decrypted)-psLen]
if bytes.Compare(ps, bytes.Repeat([]byte{byte(psLen)}, psLen)) != 0 {
@@ -155,30 +176,30 @@ func pbDecrypt(info decryptable, password []byte) (decrypted []byte, err error)
return
}
// PBES2-params ::= SEQUENCE {
// keyDerivationFunc AlgorithmIdentifier {{PBES2-KDFs}},
// encryptionScheme AlgorithmIdentifier {{PBES2-Encs}}
// }
// PBES2-params ::= SEQUENCE {
// keyDerivationFunc AlgorithmIdentifier {{PBES2-KDFs}},
// encryptionScheme AlgorithmIdentifier {{PBES2-Encs}}
// }
type pbes2Params struct {
Kdf pkix.AlgorithmIdentifier
EncryptionScheme pkix.AlgorithmIdentifier
}
// PBKDF2-params ::= SEQUENCE {
// salt CHOICE {
// specified OCTET STRING,
// otherSource AlgorithmIdentifier {{PBKDF2-SaltSources}}
// },
// iterationCount INTEGER (1..MAX),
// keyLength INTEGER (1..MAX) OPTIONAL,
// prf AlgorithmIdentifier {{PBKDF2-PRFs}} DEFAULT
// algid-hmacWithSHA1
// }
// PBKDF2-params ::= SEQUENCE {
// salt CHOICE {
// specified OCTET STRING,
// otherSource AlgorithmIdentifier {{PBKDF2-SaltSources}}
// },
// iterationCount INTEGER (1..MAX),
// keyLength INTEGER (1..MAX) OPTIONAL,
// prf AlgorithmIdentifier {{PBKDF2-PRFs}} DEFAULT
// algid-hmacWithSHA1
// }
type pbkdf2Params struct {
Salt asn1.RawValue
Iterations int
KeyLength int `asn1:"optional"`
Prf pkix.AlgorithmIdentifier
KeyLength int `asn1:"optional"`
Prf pkix.AlgorithmIdentifier `asn1:"optional"`
}
func pbes2CipherFor(algorithm pkix.AlgorithmIdentifier, password []byte) (cipher.Block, []byte, error) {
@@ -207,22 +228,29 @@ func pbes2CipherFor(algorithm pkix.AlgorithmIdentifier, password []byte) (cipher
prf = sha1.New
case kdfParams.Prf.Algorithm.Equal(asn1.ObjectIdentifier([]int{})):
prf = sha1.New
default:
return nil, nil, NotImplementedError("pbes2 prf " + kdfParams.Prf.Algorithm.String() + " is not supported")
}
key := pbkdf2.Key(password, kdfParams.Salt.Bytes, kdfParams.Iterations, 32, prf)
iv := params.EncryptionScheme.Parameters.Bytes
var block cipher.Block
var keyLen int
switch {
case params.EncryptionScheme.Algorithm.Equal(oidAES256CBC):
b, err := aes.NewCipher(key)
if err != nil {
return nil, nil, err
}
block = b
keyLen = 32
case params.EncryptionScheme.Algorithm.Equal(oidAES192CBC):
keyLen = 24
case params.EncryptionScheme.Algorithm.Equal(oidAES128CBC):
keyLen = 16
default:
return nil, nil, NotImplementedError("pbes2 algorithm " + params.EncryptionScheme.Algorithm.String() + " is not supported")
}
key := pbkdf2.Key(password, kdfParams.Salt.Bytes, kdfParams.Iterations, keyLen, prf)
iv := params.EncryptionScheme.Parameters.Bytes
block, err := aes.NewCipher(key)
if err != nil {
return nil, nil, err
}
return block, iv, nil
}
@@ -263,3 +291,31 @@ type encryptable interface {
Algorithm() pkix.AlgorithmIdentifier
SetData([]byte)
}
func makePBES2Parameters(rand io.Reader, salt []byte, iterations int) ([]byte, error) {
var err error
randomIV := make([]byte, 16)
if _, err := rand.Read(randomIV); err != nil {
return nil, err
}
var kdfparams pbkdf2Params
if kdfparams.Salt.FullBytes, err = asn1.Marshal(salt); err != nil {
return nil, err
}
kdfparams.Iterations = iterations
kdfparams.Prf.Algorithm = oidHmacWithSHA256
var params pbes2Params
params.Kdf.Algorithm = oidPBKDF2
if params.Kdf.Parameters.FullBytes, err = asn1.Marshal(kdfparams); err != nil {
return nil, err
}
params.EncryptionScheme.Algorithm = oidAES256CBC
if params.EncryptionScheme.Parameters.FullBytes, err = asn1.Marshal(randomIV); err != nil {
return nil, err
}
return asn1.Marshal(params)
}

View File

@@ -31,7 +31,7 @@ var (
oidSHA256 = asn1.ObjectIdentifier([]int{2, 16, 840, 1, 101, 3, 4, 2, 1})
)
func verifyMac(macData *macData, message, password []byte) error {
func doMac(macData *macData, message, password []byte) ([]byte, error) {
var hFn func() hash.Hash
var key []byte
switch {
@@ -42,13 +42,19 @@ func verifyMac(macData *macData, message, password []byte) error {
hFn = sha256.New
key = pbkdf(sha256Sum, 32, 64, macData.MacSalt, password, macData.Iterations, 3, 32)
default:
return NotImplementedError("unknown digest algorithm: " + macData.Mac.Algorithm.Algorithm.String())
return nil, NotImplementedError("unknown digest algorithm: " + macData.Mac.Algorithm.Algorithm.String())
}
mac := hmac.New(hFn, key)
mac.Write(message)
expectedMAC := mac.Sum(nil)
return mac.Sum(nil), nil
}
func verifyMac(macData *macData, message, password []byte) error {
expectedMAC, err := doMac(macData, message, password)
if err != nil {
return err
}
if !hmac.Equal(macData.Mac.Digest, expectedMAC) {
return ErrIncorrectPassword
}
@@ -56,15 +62,10 @@ func verifyMac(macData *macData, message, password []byte) error {
}
func computeMac(macData *macData, message, password []byte) error {
if !macData.Mac.Algorithm.Algorithm.Equal(oidSHA1) {
return NotImplementedError("unknown digest algorithm: " + macData.Mac.Algorithm.Algorithm.String())
digest, err := doMac(macData, message, password)
if err != nil {
return err
}
key := pbkdf(sha1Sum, 20, 64, macData.MacSalt, password, macData.Iterations, 3, 20)
mac := hmac.New(sha1.New, key)
mac.Write(message)
macData.Mac.Digest = mac.Sum(nil)
macData.Mac.Digest = digest
return nil
}

View File

@@ -10,7 +10,7 @@
// primitives, it SHOULD NOT be used for new applications.
//
// Note that only DER-encoded PKCS#12 files are supported, even though PKCS#12
// allows BER encoding. This is becuase encoding/asn1 only supports DER.
// allows BER encoding. This is because encoding/asn1 only supports DER.
//
// This package is forked from golang.org/x/crypto/pkcs12, which is frozen.
// The implementation is distilled from https://tools.ietf.org/html/rfc7292
@@ -19,6 +19,7 @@ package pkcs12 // import "software.sslmate.com/src/go-pkcs12"
import (
"crypto/ecdsa"
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/x509"
@@ -27,15 +28,160 @@ import (
"encoding/hex"
"encoding/pem"
"errors"
"fmt"
"io"
)
// DefaultPassword is the string "changeit", a commonly-used password for
// PKCS#12 files. Due to the weak encryption used by PKCS#12, it is
// RECOMMENDED that you use DefaultPassword when encoding PKCS#12 files,
// and protect the PKCS#12 files using other means.
// PKCS#12 files.
const DefaultPassword = "changeit"
// An Encoder contains methods for encoding PKCS#12 files. This package
// defines several different Encoders with different parameters.
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})
@@ -44,7 +190,8 @@ var (
oidLocalKeyID = asn1.ObjectIdentifier([]int{1, 2, 840, 113549, 1, 9, 21})
oidMicrosoftCSPName = asn1.ObjectIdentifier([]int{1, 3, 6, 1, 4, 1, 311, 17, 1})
oidJavaTrustStore = asn1.ObjectIdentifier([]int{2, 16, 840, 1, 113894, 746875, 1, 1})
oidJavaTrustStore = asn1.ObjectIdentifier([]int{2, 16, 840, 1, 113894, 746875, 1, 1})
oidAnyExtendedKeyUsage = asn1.ObjectIdentifier([]int{2, 5, 29, 37, 0})
)
type pfxPdu struct {
@@ -134,17 +281,18 @@ func unmarshal(in []byte, out interface{}) error {
}
// ToPEM converts all "safe bags" contained in pfxData to PEM blocks.
// DO NOT USE THIS FUNCTION. ToPEM creates invalid PEM blocks; private keys
//
// 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.
// 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 := bmpString(password)
encodedPassword, err := bmpStringZeroTerminated(password)
if err != nil {
return nil, ErrIncorrectPassword
}
bags, encodedPassword, err := getSafeContents(pfxData, encodedPassword, 2)
bags, encodedPassword, err := getSafeContents(pfxData, encodedPassword, 2, 2)
if err != nil {
return nil, err
@@ -246,7 +394,7 @@ func convertAttribute(attribute *pkcs12Attribute) (key, value string, err error)
// 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.
// 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)
@@ -262,12 +410,12 @@ func Decode(pfxData []byte, password string) (privateKey interface{}, certificat
// 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 := bmpString(password)
encodedPassword, err := bmpStringZeroTerminated(password)
if err != nil {
return nil, nil, nil, err
}
bags, encodedPassword, err := getSafeContents(pfxData, encodedPassword, 2)
bags, encodedPassword, err := getSafeContents(pfxData, encodedPassword, 1, 2)
if err != nil {
return nil, nil, nil, err
}
@@ -293,6 +441,15 @@ func DecodeChain(pfxData []byte, password string) (privateKey interface{}, certi
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")
@@ -318,13 +475,16 @@ func DecodeChain(pfxData []byte, password string) (privateKey interface{}, certi
// 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 := bmpString(password)
encodedPassword, err := bmpStringZeroTerminated(password)
if err != nil {
return nil, err
}
bags, encodedPassword, err := getSafeContents(pfxData, encodedPassword, 1)
bags, encodedPassword, err := getSafeContents(pfxData, encodedPassword, 1, 1)
if err != nil {
return nil, err
}
@@ -359,7 +519,7 @@ func DecodeTrustStore(pfxData []byte, password string) (certs []*x509.Certificat
return
}
func getSafeContents(p12Data, password []byte, expectedItems int) (bags []safeBag, updatedPassword []byte, err error) {
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())
@@ -379,10 +539,10 @@ func getSafeContents(p12Data, password []byte, expectedItems int) (bags []safeBa
}
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 !(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
@@ -400,8 +560,11 @@ func getSafeContents(p12Data, password []byte, expectedItems int) (bags []safeBa
return nil, nil, err
}
if len(authenticatedSafe) != expectedItems {
return nil, nil, NotImplementedError("expected exactly two items in the authenticated safe")
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 {
@@ -437,26 +600,35 @@ func getSafeContents(p12Data, password []byte, expectedItems int) (bags []safeBa
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 private key is encrypted with the provided password, but due to the
// weak encryption primitives used by PKCS#12, it is RECOMMENDED that you
// specify a hard-coded password (such as pkcs12.DefaultPassword) and protect
// the resulting pfxData using other means.
//
// The rand argument is used to provide entropy for the encryption, and
// can be set to rand.Reader from the crypto/rand package.
// 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 RC2 and contains the certificates,
// and another that is unencrypted and contains the private key shrouded with
// 3DES 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 Encode(rand io.Reader, privateKey interface{}, certificate *x509.Certificate, caCerts []*x509.Certificate, password string) (pfxData []byte, err error) {
encodedPassword, err := bmpString(password)
// 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("password must be empty")
}
encodedPassword, err := bmpStringZeroTerminated(password)
if err != nil {
return nil, err
}
@@ -475,26 +647,37 @@ func Encode(rand io.Reader, privateKey interface{}, certificate *x509.Certificat
}
var certBags []safeBag
var certBag *safeBag
if certBag, err = makeCertBag(certificate.Raw, []pkcs12Attribute{localKeyIdAttr}); err != nil {
if certBag, err := makeCertBag(certificate.Raw, []pkcs12Attribute{localKeyIdAttr}); err != nil {
return nil, err
}
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)
}
for _, cert := range caCerts {
if certBag, err := makeCertBag(cert.Raw, []pkcs12Attribute{}); err != nil {
return nil, err
} else {
certBags = append(certBags, *certBag)
}
}
var keyBag safeBag
keyBag.Id = oidPKCS8ShroundedKeyBag
keyBag.Value.Class = 2
keyBag.Value.Tag = 0
keyBag.Value.IsCompound = true
if keyBag.Value.Bytes, err = encodePkcs8ShroudedKeyBag(rand, privateKey, encodedPassword); err != nil {
return nil, err
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)
@@ -502,10 +685,10 @@ func Encode(rand io.Reader, privateKey interface{}, certificate *x509.Certificat
// 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(rand, certBags, encodedPassword); err != nil {
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(rand, []safeBag{keyBag}, nil); err != nil {
if authenticatedSafe[1], err = makeSafeContents(enc.rand, []safeBag{keyBag}, nil, nil, 0, 0); err != nil {
return nil, err
}
@@ -514,15 +697,17 @@ func Encode(rand io.Reader, privateKey interface{}, certificate *x509.Certificat
return nil, err
}
// compute the MAC
pfx.MacData.Mac.Algorithm.Algorithm = oidSHA1
pfx.MacData.MacSalt = make([]byte, 8)
if _, err = rand.Read(pfx.MacData.MacSalt); err != nil {
return nil, err
}
pfx.MacData.Iterations = 1
if err = computeMac(&pfx.MacData, authenticatedSafeBytes, encodedPassword); 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
@@ -539,21 +724,73 @@ func Encode(rand io.Reader, privateKey interface{}, certificate *x509.Certificat
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.
//
// Due to the weak encryption primitives used by PKCS#12, it is RECOMMENDED that
// you specify a hard-coded password (such as pkcs12.DefaultPassword) and protect
// the resulting pfxData using other means.
//
// The rand argument is used to provide entropy for the encryption, and
// can be set to rand.Reader from the crypto/rand package.
//
// EncodeTrustStore creates a single SafeContents that's encrypted with RC2
// EncodeTrustStore creates a single SafeContents that's optionally encrypted
// and contains the certificates.
func EncodeTrustStore(rand io.Reader, certs []*x509.Certificate, password string) (pfxData []byte, err error) {
encodedPassword, err := bmpString(password)
//
// 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("password must be empty")
}
encodedPassword, err := bmpStringZeroTerminated(password)
if err != nil {
return nil, err
}
@@ -561,16 +798,54 @@ func EncodeTrustStore(rand io.Reader, certs []*x509.Certificate, password string
var pfx pfxPdu
pfx.Version = 3
// Setting this attribute will make the certificates trusted in Java >= 1.8
var javaTrustStoreAttr pkcs12Attribute
javaTrustStoreAttr.Id = oidJavaTrustStore
javaTrustStoreAttr.Value.Class = 0
javaTrustStoreAttr.Value.Tag = 17
javaTrustStoreAttr.Value.IsCompound = true
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 _, cert := range certs {
certBag, err := makeCertBag(cert.Raw, []pkcs12Attribute{javaTrustStoreAttr})
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
}
@@ -578,9 +853,9 @@ func EncodeTrustStore(rand io.Reader, certs []*x509.Certificate, password string
}
// Construct an authenticated safe with one SafeContent.
// The SafeContents is encrypted and contains the cert bags.
// The SafeContents is contains the cert bags.
var authenticatedSafe [1]contentInfo
if authenticatedSafe[0], err = makeSafeContents(rand, certBags, encodedPassword); err != nil {
if authenticatedSafe[0], err = makeSafeContents(enc.rand, certBags, enc.certAlgorithm, encodedPassword, enc.encryptionIterations, enc.saltLen); err != nil {
return nil, err
}
@@ -589,15 +864,17 @@ func EncodeTrustStore(rand io.Reader, certs []*x509.Certificate, password string
return nil, err
}
// compute the MAC
pfx.MacData.Mac.Algorithm.Algorithm = oidSHA1
pfx.MacData.MacSalt = make([]byte, 8)
if _, err = rand.Read(pfx.MacData.MacSalt); err != nil {
return nil, err
}
pfx.MacData.Iterations = 1
if err = computeMac(&pfx.MacData, authenticatedSafeBytes, encodedPassword); 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
@@ -627,13 +904,13 @@ func makeCertBag(certBytes []byte, attributes []pkcs12Attribute) (certBag *safeB
return
}
func makeSafeContents(rand io.Reader, bags []safeBag, password []byte) (ci contentInfo, err error) {
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 password == nil {
if algoID == nil {
ci.ContentType = oidDataContentType
ci.Content.Class = 2
ci.Content.Tag = 0
@@ -642,15 +919,21 @@ func makeSafeContents(rand io.Reader, bags []safeBag, password []byte) (ci conte
return
}
} else {
randomSalt := make([]byte, 8)
randomSalt := make([]byte, saltLen)
if _, err = rand.Read(randomSalt); err != nil {
return
}
var algo pkix.AlgorithmIdentifier
algo.Algorithm = oidPBEWithSHAAnd40BitRC2CBC
if algo.Parameters.FullBytes, err = asn1.Marshal(pbeParams{Salt: randomSalt, Iterations: 2048}); err != nil {
return
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

View File

@@ -15,6 +15,7 @@ import (
var (
// see https://tools.ietf.org/html/rfc7292#appendix-D
oidCertTypeX509Certificate = asn1.ObjectIdentifier([]int{1, 2, 840, 113549, 1, 9, 22, 1})
oidKeyBag = asn1.ObjectIdentifier([]int{1, 2, 840, 113549, 1, 12, 10, 1, 1})
oidPKCS8ShroundedKeyBag = asn1.ObjectIdentifier([]int{1, 2, 840, 113549, 1, 12, 10, 1, 2})
oidCertBag = asn1.ObjectIdentifier([]int{1, 2, 840, 113549, 1, 12, 10, 1, 3})
)
@@ -47,23 +48,30 @@ func decodePkcs8ShroudedKeyBag(asn1Data, password []byte) (privateKey interface{
return privateKey, nil
}
func encodePkcs8ShroudedKeyBag(rand io.Reader, privateKey interface{}, password []byte) (asn1Data []byte, err error) {
func encodePkcs8ShroudedKeyBag(rand io.Reader, privateKey interface{}, algoID asn1.ObjectIdentifier, password []byte, iterations int, saltLen int) (asn1Data []byte, err error) {
var pkData []byte
if pkData, err = x509.MarshalPKCS8PrivateKey(privateKey); err != nil {
return nil, errors.New("pkcs12: error encoding PKCS#8 private key: " + err.Error())
}
randomSalt := make([]byte, 8)
randomSalt := make([]byte, saltLen)
if _, err = rand.Read(randomSalt); err != nil {
return nil, errors.New("pkcs12: error reading random salt: " + err.Error())
}
var paramBytes []byte
if paramBytes, err = asn1.Marshal(pbeParams{Salt: randomSalt, Iterations: 2048}); err != nil {
return nil, errors.New("pkcs12: error encoding params: " + err.Error())
if algoID.Equal(oidPBES2) {
if paramBytes, err = makePBES2Parameters(rand, randomSalt, iterations); err != nil {
return nil, errors.New("pkcs12: error encoding params: " + err.Error())
}
} else {
if paramBytes, err = asn1.Marshal(pbeParams{Salt: randomSalt, Iterations: iterations}); err != nil {
return nil, errors.New("pkcs12: error encoding params: " + err.Error())
}
}
var pkinfo encryptedPrivateKeyInfo
pkinfo.AlgorithmIdentifier.Algorithm = oidPBEWithSHAAnd3KeyTripleDESCBC
pkinfo.AlgorithmIdentifier.Algorithm = algoID
pkinfo.AlgorithmIdentifier.Parameters.FullBytes = paramBytes
if err = pbEncrypt(&pkinfo, pkData, password); err != nil {