diff --git a/go.mod b/go.mod index db76ddb5f..3c8e4278f 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/prometheus/client_golang v1.19.0 github.com/zmap/zcrypto v0.0.0-20230310154051-c8b263fd8300 github.com/zmap/zlint/v3 v3.5.0 - golang.org/x/crypto v0.21.0 + golang.org/x/crypto v0.22.0 ) require ( @@ -34,7 +34,7 @@ require ( github.com/weppos/publicsuffix-go v0.30.0 // indirect github.com/ziutek/mymysql v1.5.4 // indirect golang.org/x/net v0.22.0 // indirect - golang.org/x/sys v0.18.0 // indirect + golang.org/x/sys v0.19.0 // indirect golang.org/x/text v0.14.0 // indirect google.golang.org/protobuf v1.33.0 // indirect k8s.io/klog/v2 v2.120.1 // indirect diff --git a/go.sum b/go.sum index 40b8a5a3c..a35283203 100644 --- a/go.sum +++ b/go.sum @@ -384,8 +384,8 @@ golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392/go.mod h1:jdWPYTVW3xRLrWP golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= +golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -527,14 +527,14 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= +golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/helpers/helpers.go b/helpers/helpers.go index e721e352c..974c48197 100644 --- a/helpers/helpers.go +++ b/helpers/helpers.go @@ -29,7 +29,7 @@ import ( cttls "github.com/google/certificate-transparency-go/tls" ctx509 "github.com/google/certificate-transparency-go/x509" "golang.org/x/crypto/ocsp" - "golang.org/x/crypto/pkcs12" + "software.sslmate.com/src/go-pkcs12" ) // OneYear is a time.Duration representing a year's worth of seconds. @@ -259,7 +259,9 @@ func ParseCertificatesDER(certsDER []byte, password string) (certs []*x509.Certi if err != nil { var pkcs12data interface{} certs = make([]*x509.Certificate, 1) - pkcs12data, certs[0], err = pkcs12.Decode(certsDER, password) + + var caCerts []*x509.Certificate + pkcs12data, certs[0], caCerts, err = pkcs12.DecodeChain(certsDER, password) if err != nil { certs, err = x509.ParseCertificates(certsDER) if err != nil { @@ -268,6 +270,8 @@ func ParseCertificatesDER(certsDER []byte, password string) (certs []*x509.Certi } else { key = pkcs12data.(crypto.Signer) } + + certs = append(certs, caCerts...) } else { if pkcs7data.ContentInfo != "SignedData" { return nil, nil, cferr.Wrap(cferr.CertificateError, cferr.DecodeFailed, errors.New("can only extract certificates from signed data content info")) diff --git a/vendor/software.sslmate.com/src/go-pkcs12/.gitattributes b/vendor/software.sslmate.com/src/go-pkcs12/.gitattributes new file mode 100644 index 000000000..d2f212e5d --- /dev/null +++ b/vendor/software.sslmate.com/src/go-pkcs12/.gitattributes @@ -0,0 +1,10 @@ +# Treat all files in this repo as binary, with no git magic updating +# line endings. Windows users contributing to Go will need to use a +# modern version of git and editors capable of LF line endings. +# +# We'll prevent accidental CRLF line endings from entering the repo +# via the git-review gofmt checks. +# +# See golang.org/issue/9281 + +* -text diff --git a/vendor/software.sslmate.com/src/go-pkcs12/.gitignore b/vendor/software.sslmate.com/src/go-pkcs12/.gitignore new file mode 100644 index 000000000..8339fd61d --- /dev/null +++ b/vendor/software.sslmate.com/src/go-pkcs12/.gitignore @@ -0,0 +1,2 @@ +# Add no patterns to .hgignore except for files generated by the build. +last-change diff --git a/vendor/software.sslmate.com/src/go-pkcs12/LICENSE b/vendor/software.sslmate.com/src/go-pkcs12/LICENSE new file mode 100644 index 000000000..bcecd3d97 --- /dev/null +++ b/vendor/software.sslmate.com/src/go-pkcs12/LICENSE @@ -0,0 +1,28 @@ +Copyright (c) 2015, 2018, 2019 Opsmate, Inc. All rights reserved. +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/software.sslmate.com/src/go-pkcs12/README.md b/vendor/software.sslmate.com/src/go-pkcs12/README.md new file mode 100644 index 000000000..b0af9ab72 --- /dev/null +++ b/vendor/software.sslmate.com/src/go-pkcs12/README.md @@ -0,0 +1,31 @@ +# package 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" + +Package pkcs12 implements some of PKCS#12 (also known as P12 or PFX). +It is intended for decoding DER-encoded P12/PFX files for use with the `crypto/tls` +package, and for encoding P12/PFX files for use by legacy applications which +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 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. + +## Import Path + +Note that although the source code and issue tracker for this package are hosted +on GitHub, the import path is: + + software.sslmate.com/src/go-pkcs12 + +Please be sure to use this path when you `go get` and `import` this package. + +## Report Issues / Send Patches + +Open an issue or PR at https://github.com/SSLMate/go-pkcs12 diff --git a/vendor/software.sslmate.com/src/go-pkcs12/bmp-string.go b/vendor/software.sslmate.com/src/go-pkcs12/bmp-string.go new file mode 100644 index 000000000..2bfbf2e5a --- /dev/null +++ b/vendor/software.sslmate.com/src/go-pkcs12/bmp-string.go @@ -0,0 +1,63 @@ +// 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 ( + "errors" + "unicode/utf16" +) + +// 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 + + ret := make([]byte, 0, 2*len(s)+2) + + for _, r := range s { + if t, _ := utf16.EncodeRune(r); t != 0xfffd { + return nil, errors.New("pkcs12: string contains characters that cannot be encoded in UCS-2") + } + ret = append(ret, byte(r/256), byte(r%256)) + } + + return ret, nil +} + +func decodeBMPString(bmpString []byte) (string, error) { + if len(bmpString)%2 != 0 { + return "", errors.New("pkcs12: odd-length BMP string") + } + + // strip terminator if present + if l := len(bmpString); l >= 2 && bmpString[l-1] == 0 && bmpString[l-2] == 0 { + bmpString = bmpString[:l-2] + } + + s := make([]uint16, 0, len(bmpString)/2) + for len(bmpString) > 0 { + s = append(s, uint16(bmpString[0])<<8+uint16(bmpString[1])) + bmpString = bmpString[2:] + } + + return string(utf16.Decode(s)), nil +} diff --git a/vendor/software.sslmate.com/src/go-pkcs12/crypto.go b/vendor/software.sslmate.com/src/go-pkcs12/crypto.go new file mode 100644 index 000000000..5d564df52 --- /dev/null +++ b/vendor/software.sslmate.com/src/go-pkcs12/crypto.go @@ -0,0 +1,321 @@ +// 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/aes" + "crypto/cipher" + "crypto/des" + "crypto/sha1" + "crypto/sha256" + "crypto/x509/pkix" + "encoding/asn1" + "errors" + "hash" + "io" + + "golang.org/x/crypto/pbkdf2" + "software.sslmate.com/src/go-pkcs12/internal/rc2" +) + +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}) +) + +// pbeCipher is an abstraction of a PKCS#12 cipher. +type pbeCipher interface { + // create returns a cipher.Block given a key. + create(key []byte) (cipher.Block, error) + // deriveKey returns a key derived from the given password and salt. + deriveKey(salt, password []byte, iterations int) []byte + // deriveKey returns an IV derived from the given password and salt. + deriveIV(salt, password []byte, iterations int) []byte +} + +type shaWithTripleDESCBC struct{} + +func (shaWithTripleDESCBC) create(key []byte) (cipher.Block, error) { + return des.NewTripleDESCipher(key) +} + +func (shaWithTripleDESCBC) deriveKey(salt, password []byte, iterations int) []byte { + return pbkdf(sha1Sum, 20, 64, salt, password, iterations, 1, 24) +} + +func (shaWithTripleDESCBC) deriveIV(salt, password []byte, iterations int) []byte { + 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) { + return rc2.New(key, len(key)*8) +} + +func (shaWith40BitRC2CBC) deriveKey(salt, password []byte, iterations int) []byte { + return pbkdf(sha1Sum, 20, 64, salt, password, iterations, 1, 5) +} + +func (shaWith40BitRC2CBC) deriveIV(salt, password []byte, iterations int) []byte { + return pbkdf(sha1Sum, 20, 64, salt, password, iterations, 2, 8) +} + +type pbeParams struct { + Salt []byte + Iterations int +} + +func pbeCipherFor(algorithm pkix.AlgorithmIdentifier, password []byte) (cipher.Block, []byte, error) { + var cipherType pbeCipher + + 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): + // rfc7292#appendix-B.1 (the original PKCS#12 PBE) requires passwords formatted as BMPStrings. + // However, rfc8018#section-3 recommends that the password for PBES2 follow ASCII or UTF-8. + // This is also what Windows expects. + // Therefore, we convert the password to UTF-8. + originalPassword, err := decodeBMPString(password) + if err != nil { + return nil, nil, err + } + utf8Password := []byte(originalPassword) + return pbes2CipherFor(algorithm, utf8Password) + default: + return nil, nil, NotImplementedError("algorithm " + algorithm.Algorithm.String() + " is not supported") + } + + var params pbeParams + if err := unmarshal(algorithm.Parameters.FullBytes, ¶ms); err != nil { + return nil, nil, err + } + + key := cipherType.deriveKey(params.Salt, password, params.Iterations) + iv := cipherType.deriveIV(params.Salt, password, params.Iterations) + + block, err := cipherType.create(key) + if err != nil { + return nil, nil, err + } + + return block, iv, nil +} + +func pbDecrypterFor(algorithm pkix.AlgorithmIdentifier, password []byte) (cipher.BlockMode, int, error) { + block, iv, err := pbeCipherFor(algorithm, password) + if err != nil { + return nil, 0, err + } + + return cipher.NewCBCDecrypter(block, iv), block.BlockSize(), nil +} + +func pbDecrypt(info decryptable, password []byte) (decrypted []byte, err error) { + cbc, blockSize, err := pbDecrypterFor(info.Algorithm(), password) + if err != nil { + return nil, err + } + + encrypted := info.Data() + if len(encrypted) == 0 { + return nil, errors.New("pkcs12: empty encrypted data") + } + if len(encrypted)%blockSize != 0 { + return nil, errors.New("pkcs12: input is not a multiple of the block size") + } + decrypted = make([]byte, len(encrypted)) + cbc.CryptBlocks(decrypted, encrypted) + + psLen := int(decrypted[len(decrypted)-1]) + if psLen == 0 || psLen > blockSize { + return nil, ErrDecryption + } + + 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 { + return nil, ErrDecryption + } + + return +} + +// 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 +// } +type pbkdf2Params struct { + Salt asn1.RawValue + Iterations int + KeyLength int `asn1:"optional"` + Prf pkix.AlgorithmIdentifier `asn1:"optional"` +} + +func pbes2CipherFor(algorithm pkix.AlgorithmIdentifier, password []byte) (cipher.Block, []byte, error) { + var params pbes2Params + if err := unmarshal(algorithm.Parameters.FullBytes, ¶ms); err != nil { + return nil, nil, err + } + + if !params.Kdf.Algorithm.Equal(oidPBKDF2) { + return nil, nil, NotImplementedError("kdf algorithm " + params.Kdf.Algorithm.String() + " is not supported") + } + + var kdfParams pbkdf2Params + if err := unmarshal(params.Kdf.Parameters.FullBytes, &kdfParams); err != nil { + return nil, nil, err + } + if kdfParams.Salt.Tag != asn1.TagOctetString { + return nil, nil, errors.New("pkcs12: only octet string salts are supported for pbkdf2") + } + + var prf func() hash.Hash + switch { + case kdfParams.Prf.Algorithm.Equal(oidHmacWithSHA256): + prf = sha256.New + case kdfParams.Prf.Algorithm.Equal(oidHmacWithSHA1): + 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") + } + + var keyLen int + switch { + case params.EncryptionScheme.Algorithm.Equal(oidAES256CBC): + 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 +} + +// decryptable abstracts an object that contains ciphertext. +type decryptable interface { + Algorithm() pkix.AlgorithmIdentifier + Data() []byte +} + +func pbEncrypterFor(algorithm pkix.AlgorithmIdentifier, password []byte) (cipher.BlockMode, int, error) { + block, iv, err := pbeCipherFor(algorithm, password) + if err != nil { + return nil, 0, err + } + + return cipher.NewCBCEncrypter(block, iv), block.BlockSize(), nil +} + +func pbEncrypt(info encryptable, decrypted []byte, password []byte) error { + cbc, blockSize, err := pbEncrypterFor(info.Algorithm(), password) + if err != nil { + return err + } + + psLen := blockSize - len(decrypted)%blockSize + encrypted := make([]byte, len(decrypted)+psLen) + copy(encrypted[:len(decrypted)], decrypted) + copy(encrypted[len(decrypted):], bytes.Repeat([]byte{byte(psLen)}, psLen)) + cbc.CryptBlocks(encrypted, encrypted) + + info.SetData(encrypted) + + return nil +} + +// encryptable abstracts a object that contains ciphertext. +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) +} diff --git a/vendor/software.sslmate.com/src/go-pkcs12/errors.go b/vendor/software.sslmate.com/src/go-pkcs12/errors.go new file mode 100644 index 000000000..7377ce6fb --- /dev/null +++ b/vendor/software.sslmate.com/src/go-pkcs12/errors.go @@ -0,0 +1,23 @@ +// 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 "errors" + +var ( + // ErrDecryption represents a failure to decrypt the input. + ErrDecryption = errors.New("pkcs12: decryption error, incorrect padding") + + // ErrIncorrectPassword is returned when an incorrect password is detected. + // Usually, P12/PFX data is signed to be able to verify the password. + ErrIncorrectPassword = errors.New("pkcs12: decryption password incorrect") +) + +// NotImplementedError indicates that the input is not currently supported. +type NotImplementedError string + +func (e NotImplementedError) Error() string { + return "pkcs12: " + string(e) +} diff --git a/vendor/software.sslmate.com/src/go-pkcs12/internal/rc2/rc2.go b/vendor/software.sslmate.com/src/go-pkcs12/internal/rc2/rc2.go new file mode 100644 index 000000000..7499e3fb6 --- /dev/null +++ b/vendor/software.sslmate.com/src/go-pkcs12/internal/rc2/rc2.go @@ -0,0 +1,271 @@ +// 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 rc2 implements the RC2 cipher +/* +https://www.ietf.org/rfc/rfc2268.txt +http://people.csail.mit.edu/rivest/pubs/KRRR98.pdf + +This code is licensed under the MIT license. +*/ +package rc2 + +import ( + "crypto/cipher" + "encoding/binary" +) + +// The rc2 block size in bytes +const BlockSize = 8 + +type rc2Cipher struct { + k [64]uint16 +} + +// New returns a new rc2 cipher with the given key and effective key length t1 +func New(key []byte, t1 int) (cipher.Block, error) { + // TODO(dgryski): error checking for key length + return &rc2Cipher{ + k: expandKey(key, t1), + }, nil +} + +func (*rc2Cipher) BlockSize() int { return BlockSize } + +var piTable = [256]byte{ + 0xd9, 0x78, 0xf9, 0xc4, 0x19, 0xdd, 0xb5, 0xed, 0x28, 0xe9, 0xfd, 0x79, 0x4a, 0xa0, 0xd8, 0x9d, + 0xc6, 0x7e, 0x37, 0x83, 0x2b, 0x76, 0x53, 0x8e, 0x62, 0x4c, 0x64, 0x88, 0x44, 0x8b, 0xfb, 0xa2, + 0x17, 0x9a, 0x59, 0xf5, 0x87, 0xb3, 0x4f, 0x13, 0x61, 0x45, 0x6d, 0x8d, 0x09, 0x81, 0x7d, 0x32, + 0xbd, 0x8f, 0x40, 0xeb, 0x86, 0xb7, 0x7b, 0x0b, 0xf0, 0x95, 0x21, 0x22, 0x5c, 0x6b, 0x4e, 0x82, + 0x54, 0xd6, 0x65, 0x93, 0xce, 0x60, 0xb2, 0x1c, 0x73, 0x56, 0xc0, 0x14, 0xa7, 0x8c, 0xf1, 0xdc, + 0x12, 0x75, 0xca, 0x1f, 0x3b, 0xbe, 0xe4, 0xd1, 0x42, 0x3d, 0xd4, 0x30, 0xa3, 0x3c, 0xb6, 0x26, + 0x6f, 0xbf, 0x0e, 0xda, 0x46, 0x69, 0x07, 0x57, 0x27, 0xf2, 0x1d, 0x9b, 0xbc, 0x94, 0x43, 0x03, + 0xf8, 0x11, 0xc7, 0xf6, 0x90, 0xef, 0x3e, 0xe7, 0x06, 0xc3, 0xd5, 0x2f, 0xc8, 0x66, 0x1e, 0xd7, + 0x08, 0xe8, 0xea, 0xde, 0x80, 0x52, 0xee, 0xf7, 0x84, 0xaa, 0x72, 0xac, 0x35, 0x4d, 0x6a, 0x2a, + 0x96, 0x1a, 0xd2, 0x71, 0x5a, 0x15, 0x49, 0x74, 0x4b, 0x9f, 0xd0, 0x5e, 0x04, 0x18, 0xa4, 0xec, + 0xc2, 0xe0, 0x41, 0x6e, 0x0f, 0x51, 0xcb, 0xcc, 0x24, 0x91, 0xaf, 0x50, 0xa1, 0xf4, 0x70, 0x39, + 0x99, 0x7c, 0x3a, 0x85, 0x23, 0xb8, 0xb4, 0x7a, 0xfc, 0x02, 0x36, 0x5b, 0x25, 0x55, 0x97, 0x31, + 0x2d, 0x5d, 0xfa, 0x98, 0xe3, 0x8a, 0x92, 0xae, 0x05, 0xdf, 0x29, 0x10, 0x67, 0x6c, 0xba, 0xc9, + 0xd3, 0x00, 0xe6, 0xcf, 0xe1, 0x9e, 0xa8, 0x2c, 0x63, 0x16, 0x01, 0x3f, 0x58, 0xe2, 0x89, 0xa9, + 0x0d, 0x38, 0x34, 0x1b, 0xab, 0x33, 0xff, 0xb0, 0xbb, 0x48, 0x0c, 0x5f, 0xb9, 0xb1, 0xcd, 0x2e, + 0xc5, 0xf3, 0xdb, 0x47, 0xe5, 0xa5, 0x9c, 0x77, 0x0a, 0xa6, 0x20, 0x68, 0xfe, 0x7f, 0xc1, 0xad, +} + +func expandKey(key []byte, t1 int) [64]uint16 { + + l := make([]byte, 128) + copy(l, key) + + var t = len(key) + var t8 = (t1 + 7) / 8 + var tm = byte(255 % uint(1<<(8+uint(t1)-8*uint(t8)))) + + for i := len(key); i < 128; i++ { + l[i] = piTable[l[i-1]+l[uint8(i-t)]] + } + + l[128-t8] = piTable[l[128-t8]&tm] + + for i := 127 - t8; i >= 0; i-- { + l[i] = piTable[l[i+1]^l[i+t8]] + } + + var k [64]uint16 + + for i := range k { + k[i] = uint16(l[2*i]) + uint16(l[2*i+1])*256 + } + + return k +} + +func rotl16(x uint16, b uint) uint16 { + return (x >> (16 - b)) | (x << b) +} + +func (c *rc2Cipher) Encrypt(dst, src []byte) { + + r0 := binary.LittleEndian.Uint16(src[0:]) + r1 := binary.LittleEndian.Uint16(src[2:]) + r2 := binary.LittleEndian.Uint16(src[4:]) + r3 := binary.LittleEndian.Uint16(src[6:]) + + var j int + + for j <= 16 { + // mix r0 + r0 = r0 + c.k[j] + (r3 & r2) + ((^r3) & r1) + r0 = rotl16(r0, 1) + j++ + + // mix r1 + r1 = r1 + c.k[j] + (r0 & r3) + ((^r0) & r2) + r1 = rotl16(r1, 2) + j++ + + // mix r2 + r2 = r2 + c.k[j] + (r1 & r0) + ((^r1) & r3) + r2 = rotl16(r2, 3) + j++ + + // mix r3 + r3 = r3 + c.k[j] + (r2 & r1) + ((^r2) & r0) + r3 = rotl16(r3, 5) + j++ + + } + + r0 = r0 + c.k[r3&63] + r1 = r1 + c.k[r0&63] + r2 = r2 + c.k[r1&63] + r3 = r3 + c.k[r2&63] + + for j <= 40 { + // mix r0 + r0 = r0 + c.k[j] + (r3 & r2) + ((^r3) & r1) + r0 = rotl16(r0, 1) + j++ + + // mix r1 + r1 = r1 + c.k[j] + (r0 & r3) + ((^r0) & r2) + r1 = rotl16(r1, 2) + j++ + + // mix r2 + r2 = r2 + c.k[j] + (r1 & r0) + ((^r1) & r3) + r2 = rotl16(r2, 3) + j++ + + // mix r3 + r3 = r3 + c.k[j] + (r2 & r1) + ((^r2) & r0) + r3 = rotl16(r3, 5) + j++ + + } + + r0 = r0 + c.k[r3&63] + r1 = r1 + c.k[r0&63] + r2 = r2 + c.k[r1&63] + r3 = r3 + c.k[r2&63] + + for j <= 60 { + // mix r0 + r0 = r0 + c.k[j] + (r3 & r2) + ((^r3) & r1) + r0 = rotl16(r0, 1) + j++ + + // mix r1 + r1 = r1 + c.k[j] + (r0 & r3) + ((^r0) & r2) + r1 = rotl16(r1, 2) + j++ + + // mix r2 + r2 = r2 + c.k[j] + (r1 & r0) + ((^r1) & r3) + r2 = rotl16(r2, 3) + j++ + + // mix r3 + r3 = r3 + c.k[j] + (r2 & r1) + ((^r2) & r0) + r3 = rotl16(r3, 5) + j++ + } + + binary.LittleEndian.PutUint16(dst[0:], r0) + binary.LittleEndian.PutUint16(dst[2:], r1) + binary.LittleEndian.PutUint16(dst[4:], r2) + binary.LittleEndian.PutUint16(dst[6:], r3) +} + +func (c *rc2Cipher) Decrypt(dst, src []byte) { + + r0 := binary.LittleEndian.Uint16(src[0:]) + r1 := binary.LittleEndian.Uint16(src[2:]) + r2 := binary.LittleEndian.Uint16(src[4:]) + r3 := binary.LittleEndian.Uint16(src[6:]) + + j := 63 + + for j >= 44 { + // unmix r3 + r3 = rotl16(r3, 16-5) + r3 = r3 - c.k[j] - (r2 & r1) - ((^r2) & r0) + j-- + + // unmix r2 + r2 = rotl16(r2, 16-3) + r2 = r2 - c.k[j] - (r1 & r0) - ((^r1) & r3) + j-- + + // unmix r1 + r1 = rotl16(r1, 16-2) + r1 = r1 - c.k[j] - (r0 & r3) - ((^r0) & r2) + j-- + + // unmix r0 + r0 = rotl16(r0, 16-1) + r0 = r0 - c.k[j] - (r3 & r2) - ((^r3) & r1) + j-- + } + + r3 = r3 - c.k[r2&63] + r2 = r2 - c.k[r1&63] + r1 = r1 - c.k[r0&63] + r0 = r0 - c.k[r3&63] + + for j >= 20 { + // unmix r3 + r3 = rotl16(r3, 16-5) + r3 = r3 - c.k[j] - (r2 & r1) - ((^r2) & r0) + j-- + + // unmix r2 + r2 = rotl16(r2, 16-3) + r2 = r2 - c.k[j] - (r1 & r0) - ((^r1) & r3) + j-- + + // unmix r1 + r1 = rotl16(r1, 16-2) + r1 = r1 - c.k[j] - (r0 & r3) - ((^r0) & r2) + j-- + + // unmix r0 + r0 = rotl16(r0, 16-1) + r0 = r0 - c.k[j] - (r3 & r2) - ((^r3) & r1) + j-- + + } + + r3 = r3 - c.k[r2&63] + r2 = r2 - c.k[r1&63] + r1 = r1 - c.k[r0&63] + r0 = r0 - c.k[r3&63] + + for j >= 0 { + // unmix r3 + r3 = rotl16(r3, 16-5) + r3 = r3 - c.k[j] - (r2 & r1) - ((^r2) & r0) + j-- + + // unmix r2 + r2 = rotl16(r2, 16-3) + r2 = r2 - c.k[j] - (r1 & r0) - ((^r1) & r3) + j-- + + // unmix r1 + r1 = rotl16(r1, 16-2) + r1 = r1 - c.k[j] - (r0 & r3) - ((^r0) & r2) + j-- + + // unmix r0 + r0 = rotl16(r0, 16-1) + r0 = r0 - c.k[j] - (r3 & r2) - ((^r3) & r1) + j-- + + } + + binary.LittleEndian.PutUint16(dst[0:], r0) + binary.LittleEndian.PutUint16(dst[2:], r1) + binary.LittleEndian.PutUint16(dst[4:], r2) + binary.LittleEndian.PutUint16(dst[6:], r3) +} diff --git a/vendor/software.sslmate.com/src/go-pkcs12/mac.go b/vendor/software.sslmate.com/src/go-pkcs12/mac.go new file mode 100644 index 000000000..587904ffa --- /dev/null +++ b/vendor/software.sslmate.com/src/go-pkcs12/mac.go @@ -0,0 +1,71 @@ +// 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/hmac" + "crypto/sha1" + "crypto/sha256" + "crypto/x509/pkix" + "encoding/asn1" + "hash" +) + +type macData struct { + Mac digestInfo + MacSalt []byte + Iterations int `asn1:"optional,default:1"` +} + +// from PKCS#7: +type digestInfo struct { + Algorithm pkix.AlgorithmIdentifier + Digest []byte +} + +var ( + oidSHA1 = asn1.ObjectIdentifier([]int{1, 3, 14, 3, 2, 26}) + oidSHA256 = asn1.ObjectIdentifier([]int{2, 16, 840, 1, 101, 3, 4, 2, 1}) +) + +func doMac(macData *macData, message, password []byte) ([]byte, error) { + var hFn func() hash.Hash + var key []byte + switch { + case macData.Mac.Algorithm.Algorithm.Equal(oidSHA1): + hFn = sha1.New + key = pbkdf(sha1Sum, 20, 64, macData.MacSalt, password, macData.Iterations, 3, 20) + case macData.Mac.Algorithm.Algorithm.Equal(oidSHA256): + hFn = sha256.New + key = pbkdf(sha256Sum, 32, 64, macData.MacSalt, password, macData.Iterations, 3, 32) + default: + return nil, NotImplementedError("unknown digest algorithm: " + macData.Mac.Algorithm.Algorithm.String()) + } + + mac := hmac.New(hFn, key) + mac.Write(message) + 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 + } + return nil +} + +func computeMac(macData *macData, message, password []byte) error { + digest, err := doMac(macData, message, password) + if err != nil { + return err + } + macData.Mac.Digest = digest + return nil +} diff --git a/vendor/software.sslmate.com/src/go-pkcs12/pbkdf.go b/vendor/software.sslmate.com/src/go-pkcs12/pbkdf.go new file mode 100644 index 000000000..e6e0c6209 --- /dev/null +++ b/vendor/software.sslmate.com/src/go-pkcs12/pbkdf.go @@ -0,0 +1,177 @@ +// 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/sha1" + "crypto/sha256" + "math/big" +) + +var ( + one = big.NewInt(1) +) + +// sha1Sum returns the SHA-1 hash of in. +func sha1Sum(in []byte) []byte { + sum := sha1.Sum(in) + return sum[:] +} + +// sha256Sum returns the SHA-256 hash of in. +func sha256Sum(in []byte) []byte { + sum := sha256.Sum256(in) + return sum[:] +} + +// fillWithRepeats returns v*ceiling(len(pattern) / v) bytes consisting of +// repeats of pattern. +func fillWithRepeats(pattern []byte, v int) []byte { + if len(pattern) == 0 { + return nil + } + outputLen := v * ((len(pattern) + v - 1) / v) + return bytes.Repeat(pattern, (outputLen+len(pattern)-1)/len(pattern))[:outputLen] +} + +func pbkdf(hash func([]byte) []byte, u, v int, salt, password []byte, r int, ID byte, size int) (key []byte) { + // implementation of https://tools.ietf.org/html/rfc7292#appendix-B.2 , RFC text verbatim in comments + + // Let H be a hash function built around a compression function f: + + // Z_2^u x Z_2^v -> Z_2^u + + // (that is, H has a chaining variable and output of length u bits, and + // the message input to the compression function of H is v bits). The + // values for u and v are as follows: + + // HASH FUNCTION VALUE u VALUE v + // MD2, MD5 128 512 + // SHA-1 160 512 + // SHA-224 224 512 + // SHA-256 256 512 + // SHA-384 384 1024 + // SHA-512 512 1024 + // SHA-512/224 224 1024 + // SHA-512/256 256 1024 + + // Furthermore, let r be the iteration count. + + // We assume here that u and v are both multiples of 8, as are the + // lengths of the password and salt strings (which we denote by p and s, + // respectively) and the number n of pseudorandom bits required. In + // addition, u and v are of course non-zero. + + // For information on security considerations for MD5 [19], see [25] and + // [1], and on those for MD2, see [18]. + + // The following procedure can be used to produce pseudorandom bits for + // a particular "purpose" that is identified by a byte called "ID". + // This standard specifies 3 different values for the ID byte: + + // 1. If ID=1, then the pseudorandom bits being produced are to be used + // as key material for performing encryption or decryption. + + // 2. If ID=2, then the pseudorandom bits being produced are to be used + // as an IV (Initial Value) for encryption or decryption. + + // 3. If ID=3, then the pseudorandom bits being produced are to be used + // as an integrity key for MACing. + + // 1. Construct a string, D (the "diversifier"), by concatenating v/8 + // copies of ID. + var D []byte + for i := 0; i < v; i++ { + D = append(D, ID) + } + + // 2. Concatenate copies of the salt together to create a string S of + // length v(ceiling(s/v)) bits (the final copy of the salt may be + // truncated to create S). Note that if the salt is the empty + // string, then so is S. + + S := fillWithRepeats(salt, v) + + // 3. Concatenate copies of the password together to create a string P + // of length v(ceiling(p/v)) bits (the final copy of the password + // may be truncated to create P). Note that if the password is the + // empty string, then so is P. + + P := fillWithRepeats(password, v) + + // 4. Set I=S||P to be the concatenation of S and P. + I := append(S, P...) + + // 5. Set c=ceiling(n/u). + c := (size + u - 1) / u + + // 6. For i=1, 2, ..., c, do the following: + A := make([]byte, c*u) + var IjBuf []byte + for i := 0; i < c; i++ { + // A. Set A2=H^r(D||I). (i.e., the r-th hash of D||1, + // H(H(H(... H(D||I)))) + Ai := hash(append(D, I...)) + for j := 1; j < r; j++ { + Ai = hash(Ai) + } + copy(A[i*u:], Ai[:]) + + if i < c-1 { // skip on last iteration + // B. Concatenate copies of Ai to create a string B of length v + // bits (the final copy of Ai may be truncated to create B). + var B []byte + for len(B) < v { + B = append(B, Ai[:]...) + } + B = B[:v] + + // C. Treating I as a concatenation I_0, I_1, ..., I_(k-1) of v-bit + // blocks, where k=ceiling(s/v)+ceiling(p/v), modify I by + // setting I_j=(I_j+B+1) mod 2^v for each j. + { + Bbi := new(big.Int).SetBytes(B) + Ij := new(big.Int) + + for j := 0; j < len(I)/v; j++ { + Ij.SetBytes(I[j*v : (j+1)*v]) + Ij.Add(Ij, Bbi) + Ij.Add(Ij, one) + Ijb := Ij.Bytes() + // We expect Ijb to be exactly v bytes, + // if it is longer or shorter we must + // adjust it accordingly. + if len(Ijb) > v { + Ijb = Ijb[len(Ijb)-v:] + } + if len(Ijb) < v { + if IjBuf == nil { + IjBuf = make([]byte, v) + } + bytesShort := v - len(Ijb) + for i := 0; i < bytesShort; i++ { + IjBuf[i] = 0 + } + copy(IjBuf[bytesShort:], Ijb) + Ijb = IjBuf + } + copy(I[j*v:(j+1)*v], Ijb) + } + } + } + } + // 7. Concatenate A_1, A_2, ..., A_c together to form a pseudorandom + // bit string, A. + + // 8. Use the first n bits of A as the output of this entire process. + return A[:size] + + // If the above process is being used to generate a DES key, the process + // should be used to create 64 random bits, and the key's parity bits + // should be set after the 64 bits have been produced. Similar concerns + // hold for 2-key and 3-key triple-DES keys, for CDMF keys, and for any + // similar keys with parity bits "built into them". +} diff --git a/vendor/software.sslmate.com/src/go-pkcs12/pkcs12.go b/vendor/software.sslmate.com/src/go-pkcs12/pkcs12.go new file mode 100644 index 000000000..14dd3a5ba --- /dev/null +++ b/vendor/software.sslmate.com/src/go-pkcs12/pkcs12.go @@ -0,0 +1,956 @@ +// 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 implements some of PKCS#12 (also known as P12 or PFX). +// It is intended for decoding DER-encoded P12/PFX files for use with the crypto/tls +// package, and for encoding P12/PFX files for use by legacy applications which +// 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 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. +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. +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}) + + 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}) + + oidJavaTrustStore = asn1.ObjectIdentifier([]int{2, 16, 840, 1, 113894, 746875, 1, 1}) + oidAnyExtendedKeyUsage = asn1.ObjectIdentifier([]int{2, 5, 29, 37, 0}) +) + +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 } + +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"` +} + +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 +} + +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. +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. +// +// 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("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, 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("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("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/vendor/software.sslmate.com/src/go-pkcs12/safebags.go b/vendor/software.sslmate.com/src/go-pkcs12/safebags.go new file mode 100644 index 000000000..b4ab7d6cd --- /dev/null +++ b/vendor/software.sslmate.com/src/go-pkcs12/safebags.go @@ -0,0 +1,107 @@ +// 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/x509" + "encoding/asn1" + "errors" + "io" +) + +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}) +) + +type certBag struct { + Id asn1.ObjectIdentifier + Data []byte `asn1:"tag:0,explicit"` +} + +func decodePkcs8ShroudedKeyBag(asn1Data, password []byte) (privateKey interface{}, err error) { + pkinfo := new(encryptedPrivateKeyInfo) + if err = unmarshal(asn1Data, pkinfo); err != nil { + return nil, errors.New("pkcs12: error decoding PKCS#8 shrouded key bag: " + err.Error()) + } + + pkData, err := pbDecrypt(pkinfo, password) + if err != nil { + return nil, errors.New("pkcs12: error decrypting PKCS#8 shrouded key bag: " + err.Error()) + } + + ret := new(asn1.RawValue) + if err = unmarshal(pkData, ret); err != nil { + return nil, errors.New("pkcs12: error unmarshaling decrypted private key: " + err.Error()) + } + + if privateKey, err = x509.ParsePKCS8PrivateKey(pkData); err != nil { + return nil, errors.New("pkcs12: error parsing PKCS#8 private key: " + err.Error()) + } + + return privateKey, nil +} + +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, saltLen) + if _, err = rand.Read(randomSalt); err != nil { + return nil, errors.New("pkcs12: error reading random salt: " + err.Error()) + } + + var paramBytes []byte + 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 = algoID + pkinfo.AlgorithmIdentifier.Parameters.FullBytes = paramBytes + + if err = pbEncrypt(&pkinfo, pkData, password); err != nil { + return nil, errors.New("pkcs12: error encrypting PKCS#8 shrouded key bag: " + err.Error()) + } + + if asn1Data, err = asn1.Marshal(pkinfo); err != nil { + return nil, errors.New("pkcs12: error encoding PKCS#8 shrouded key bag: " + err.Error()) + } + + return asn1Data, nil +} + +func decodeCertBag(asn1Data []byte) (x509Certificates []byte, err error) { + bag := new(certBag) + if err := unmarshal(asn1Data, bag); err != nil { + return nil, errors.New("pkcs12: error decoding cert bag: " + err.Error()) + } + if !bag.Id.Equal(oidCertTypeX509Certificate) { + return nil, NotImplementedError("only X509 certificates are supported") + } + return bag.Data, nil +} + +func encodeCertBag(x509Certificates []byte) (asn1Data []byte, err error) { + var bag certBag + bag.Id = oidCertTypeX509Certificate + bag.Data = x509Certificates + if asn1Data, err = asn1.Marshal(bag); err != nil { + return nil, errors.New("pkcs12: error encoding cert bag: " + err.Error()) + } + return asn1Data, nil +}