[crypto] x/crypto: Add pkcs12 package for reading pkcs12 data

640 views
Skip to first unread message

Paul Meyer (Gerrit)

unread,
Jul 8, 2015, 7:40:36 PM7/8/15
to Ian Lance Taylor, golang-co...@googlegroups.com
Paul Meyer uploaded a change:
https://go-review.googlesource.com/11986

x/crypto: Add pkcs12 package for reading pkcs12 data

Package pkcs12 provides some Go implementations of PKCS#12.
This implementation is distilled from https://tools.ietf.org/html/rfc7292
and
referenced documents. It is intented for decoding P12/PFX-stored
certificate+key
for use with the crypto/tls package.

Package includes @dgryski's RC2 implementation as a sub package as
requested in
https://github.com/golang/go/issues/10621.

Change-Id: I78401241e39cd0099e9082a3a227cf0a3a36e6d1
---
A pkcs12/bmp-string.go
A pkcs12/bmp-string_test.go
A pkcs12/crypto.go
A pkcs12/crypto_test.go
A pkcs12/errors.go
A pkcs12/mac.go
A pkcs12/mac_test.go
A pkcs12/pbkdf.go
A pkcs12/pbkdf_test.go
A pkcs12/pkcs12.go
A pkcs12/pkcs12_test.go
A pkcs12/rc2/bench_test.go
A pkcs12/rc2/rc2.go
A pkcs12/rc2/rc2_test.go
A pkcs12/safebags.go
15 files changed, 1,583 insertions(+), 0 deletions(-)



diff --git a/pkcs12/bmp-string.go b/pkcs12/bmp-string.go
new file mode 100644
index 0000000..3db9b2e
--- /dev/null
+++ b/pkcs12/bmp-string.go
@@ -0,0 +1,53 @@
+// 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"
+ "unicode/utf8"
+)
+
+func bmpString(utf8String []byte) ([]byte, error) {
+ // References:
+ // https://tools.ietf.org/html/rfc7292#appendix-B.1
+ // http://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.
+
+ rv := make([]byte, 0, 2*len(utf8String)+2)
+
+ start := 0
+ for start < len(utf8String) {
+ c, size := utf8.DecodeRune(utf8String[start:])
+ start += size
+ if t, _ := utf16.EncodeRune(c); t != 0xfffd {
+ return nil, errors.New("string contains characters that cannot be
encoded in UCS-2")
+ }
+ rv = append(rv, byte(c/256), byte(c%256))
+ }
+ rv = append(rv, 0, 0)
+ return rv, nil
+}
+
+func decodeBMPString(bmpString []byte) (string, error) {
+ if len(bmpString)%2 != 0 {
+ return "", errors.New("expected BMP byte string to be an even length")
+ }
+
+ // strip terminator if present
+ if terminator := bmpString[len(bmpString)-2:]; terminator[0] ==
terminator[1] && terminator[1] == 0 {
+ bmpString = bmpString[:len(bmpString)-2]
+ }
+
+ s := make([]uint16, 0, len(bmpString)/2)
+ for len(bmpString) > 0 {
+ s = append(s, uint16(bmpString[0])*265+uint16(bmpString[1]))
+ bmpString = bmpString[2:]
+ }
+
+ return string(utf16.Decode(s)), nil
+}
diff --git a/pkcs12/bmp-string_test.go b/pkcs12/bmp-string_test.go
new file mode 100644
index 0000000..44c8a0d
--- /dev/null
+++ b/pkcs12/bmp-string_test.go
@@ -0,0 +1,46 @@
+// 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"
+ "testing"
+)
+
+func TestBMPString(t *testing.T) {
+ str, err := bmpString([]byte(""))
+ if bytes.Compare(str, []byte{0, 0}) != 0 {
+ t.Errorf("expected empty string to return double 0, but found: % x", str)
+ }
+ if err != nil {
+ t.Errorf("err: %v", err)
+ }
+
+ // Example from https://tools.ietf.org/html/rfc7292#appendix-B
+ str, err = bmpString([]byte("Beavis"))
+ if bytes.Compare(str, []byte{0x00, 0x42, 0x00, 0x65, 0x00, 0x61, 0x00,
0x0076, 0x00, 0x69, 0x00, 0x73, 0x00, 0x00}) != 0 {
+ t.Errorf("expected 'Beavis' to return 0x00 0x42 0x00 0x65 0x00 0x61 0x00
0x76 0x00 0x69 0x00 0x73 0x00 0x00, but found: % x", str)
+ }
+ if err != nil {
+ t.Errorf("err: %v", err)
+ }
+
+ // some characters from the "Letterlike Symbols Unicode block"
+ tst := "\u2115 - Double-struck N"
+ str, err = bmpString([]byte(tst))
+ if bytes.Compare(str, []byte{0x21, 0x15, 0x00, 0x20, 0x00, 0x2d, 0x00,
0x20, 0x00, 0x44, 0x00, 0x6f, 0x00, 0x75, 0x00, 0x62, 0x00, 0x6c, 0x00,
0x65, 0x00, 0x2d, 0x00, 0x73, 0x00, 0x74, 0x00, 0x72, 0x00, 0x75, 0x00,
0x63, 0x00, 0x6b, 0x00, 0x20, 0x00, 0x4e, 0x00, 0x00}) != 0 {
+ t.Errorf("expected '%s' to return 0x21 0x15 0x00 0x20 0x00 0x2d 0x00
0x20 0x00 0x44 0x00 0x6f 0x00 0x75 0x00 0x62 0x00 0x6c 0x00 0x65 0x00 0x2d
0x00 0x73 0x00 0x74 0x00 0x72 0x00 0x75 0x00 0x63 0x00 0x6b 0x00 0x20 0x00
0x4e 0x00 0x00, but found: % x", tst, str)
+ }
+ if err != nil {
+ t.Errorf("err: %v", err)
+ }
+
+ // some character outside the BMP should error
+ tst = "\U0001f000 East wind (Mahjong)"
+ str, err = bmpString([]byte(tst))
+ if err == nil {
+ t.Errorf("expected '%s' to throw error because the first character is
not in the BMP", tst)
+ }
+}
diff --git a/pkcs12/crypto.go b/pkcs12/crypto.go
new file mode 100644
index 0000000..a59734e
--- /dev/null
+++ b/pkcs12/crypto.go
@@ -0,0 +1,91 @@
+// 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/cipher"
+ "crypto/des"
+ "crypto/x509/pkix"
+ "encoding/asn1"
+ "golang.org/x/crypto/pkcs12/rc2"
+)
+
+const (
+ pbeWithSHAAnd3KeyTripleDESCBC = "pbeWithSHAAnd3-KeyTripleDES-CBC"
+ pbewithSHAAnd40BitRC2CBC = "pbewithSHAAnd40BitRC2-CBC"
+)
+
+var algByOID = map[string]string{
+ "1.2.840.113549.1.12.1.3": pbeWithSHAAnd3KeyTripleDESCBC,
+ "1.2.840.113549.1.12.1.6": pbewithSHAAnd40BitRC2CBC,
+}
+
+var blockcodeByAlg = map[string]func(key []byte) (cipher.Block, error){
+ pbeWithSHAAnd3KeyTripleDESCBC: des.NewTripleDESCipher,
+ pbewithSHAAnd40BitRC2CBC: func(key []byte) (cipher.Block, error) {
+ return rc2.New(key, len(key)*8)
+ },
+}
+
+type pbeParams struct {
+ Salt []byte
+ Iterations int
+}
+
+func pbDecrypterFor(algorithm pkix.AlgorithmIdentifier, password []byte)
(cipher.BlockMode, error) {
+ algorithmName, supported := algByOID[algorithm.Algorithm.String()]
+ if !supported {
+ return nil, NotImplementedError("algorithm " +
algorithm.Algorithm.String() + " is not supported")
+ }
+
+ var params pbeParams
+ if _, err := asn1.Unmarshal(algorithm.Parameters.FullBytes, &params);
err != nil {
+ return nil, err
+ }
+
+ k := deriveKeyByAlg[algorithmName](params.Salt, password,
params.Iterations)
+ iv := deriveIVByAlg[algorithmName](params.Salt, password,
params.Iterations)
+ password = nil
+
+ code, err := blockcodeByAlg[algorithmName](k)
+ if err != nil {
+ return nil, err
+ }
+
+ cbc := cipher.NewCBCDecrypter(code, iv)
+ return cbc, nil
+}
+
+func pbDecrypt(info decryptable, password []byte) (decrypted []byte, err
error) {
+ cbc, err := pbDecrypterFor(info.GetAlgorithm(), password)
+ password = nil
+ if err != nil {
+ return nil, err
+ }
+
+ encrypted := info.GetData()
+
+ decrypted = make([]byte, len(encrypted))
+ cbc.CryptBlocks(decrypted, encrypted)
+
+ if psLen := int(decrypted[len(decrypted)-1]); psLen > 0 && psLen < 9 {
+ m := decrypted[:len(decrypted)-psLen]
+ ps := decrypted[len(decrypted)-psLen:]
+ if bytes.Compare(ps, bytes.Repeat([]byte{byte(psLen)}, psLen)) != 0 {
+ return nil, ErrDecryption
+ }
+ decrypted = m
+ } else {
+ return nil, ErrDecryption
+ }
+
+ return
+}
+
+type decryptable interface {
+ GetAlgorithm() pkix.AlgorithmIdentifier
+ GetData() []byte
+}
diff --git a/pkcs12/crypto_test.go b/pkcs12/crypto_test.go
new file mode 100644
index 0000000..b27bedb
--- /dev/null
+++ b/pkcs12/crypto_test.go
@@ -0,0 +1,112 @@
+// 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/x509/pkix"
+ "encoding/asn1"
+ "testing"
+)
+
+func TestPbDecrypterFor(t *testing.T) {
+ params, _ := asn1.Marshal(pbeParams{
+ Salt: []byte{1, 2, 3, 4, 5, 6, 7, 8},
+ Iterations: 2048,
+ })
+ alg := pkix.AlgorithmIdentifier{
+ Algorithm: asn1.ObjectIdentifier([]int{1, 2, 3}),
+ Parameters: asn1.RawValue{
+ FullBytes: params,
+ },
+ }
+
+ pass, _ := bmpString([]byte("Sesame open"))
+
+ _, err := pbDecrypterFor(alg, pass)
+ if _, ok := err.(NotImplementedError); !ok {
+ t.Errorf("expected not implemented error, got: %T %s", err, err)
+ }
+
+ alg.Algorithm = asn1.ObjectIdentifier([]int{1, 2, 840, 113549, 1, 12, 1,
3})
+ cbc, err := pbDecrypterFor(alg, pass)
+ if err != nil {
+ t.Errorf("err: %v", err)
+ }
+
+ M := []byte{1, 2, 3, 4, 5, 6, 7, 8}
+ expectedM := []byte{185, 73, 135, 249, 137, 1, 122, 247}
+ cbc.CryptBlocks(M, M)
+
+ if bytes.Compare(M, expectedM) != 0 {
+ t.Errorf("expected M to be '%d', but found '%d", expectedM, M)
+ }
+}
+
+func TestPbDecrypt(t *testing.T) {
+
+ tests := [][]byte{
+
[]byte("\x33\x73\xf3\x9f\xda\x49\xae\xfc\xa0\x9a\xdf\x5a\x58\xa0\xea\x46"),
// 7 padding bytes
+
[]byte("\x33\x73\xf3\x9f\xda\x49\xae\xfc\x96\x24\x2f\x71\x7e\x32\x3f\xe7"),
// 8 padding bytes
+
[]byte("\x35\x0c\xc0\x8d\xab\xa9\x5d\x30\x7f\x9a\xec\x6a\xd8\x9b\x9c\xd9"),
// 9 padding bytes, incorrect
+
[]byte("\xb2\xf9\x6e\x06\x60\xae\x20\xcf\x08\xa0\x7b\xd9\x6b\x20\xef\x41"),
// incorrect padding bytes: [ ... 0x04 0x02 ]
+ }
+ expected := []interface{}{
+ []byte("A secret!"),
+ []byte("A secret"),
+ ErrDecryption,
+ ErrDecryption,
+ }
+
+ for i, c := range tests {
+ td := testDecryptable{
+ data: c,
+ algorithm: pkix.AlgorithmIdentifier{
+ Algorithm: asn1.ObjectIdentifier([]int{1, 2, 840, 113549, 1, 12, 1,
3}), // SHA1/3TDES
+ Parameters: pbeParams{
+ Salt: []byte("\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8"),
+ Iterations: 4096,
+ }.RawASN1(),
+ },
+ }
+ p, _ := bmpString([]byte("sesame"))
+
+ m, err := pbDecrypt(td, p)
+
+ switch e := expected[i].(type) {
+ case []byte:
+ if err != nil {
+ t.Errorf("error decrypting C=%x: %v", c, err)
+ }
+ if bytes.Compare(m, e) != 0 {
+ t.Errorf("expected C=%x to be decoded to M=%x, but found %x", c, e, m)
+ }
+ case error:
+ if err == nil || err.Error() != e.Error() {
+ t.Errorf("expecting error '%v' during decryption of c=%x, but found
err='%v'", e, c, err)
+ }
+ }
+ }
+}
+
+type testDecryptable struct {
+ data []byte
+ algorithm pkix.AlgorithmIdentifier
+}
+
+func (d testDecryptable) GetAlgorithm() pkix.AlgorithmIdentifier { return
d.algorithm }
+func (d testDecryptable) GetData() []byte { return
d.data }
+
+func (params pbeParams) RawASN1() (raw asn1.RawValue) {
+ asn1Bytes, err := asn1.Marshal(params)
+ if err != nil {
+ panic(err)
+ }
+ _, err = asn1.Unmarshal(asn1Bytes, &raw)
+ if err != nil {
+ panic(err)
+ }
+ return
+}
diff --git a/pkcs12/errors.go b/pkcs12/errors.go
new file mode 100644
index 0000000..17fe3f6
--- /dev/null
+++ b/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 string(e)
+}
diff --git a/pkcs12/mac.go b/pkcs12/mac.go
new file mode 100644
index 0000000..426836b
--- /dev/null
+++ b/pkcs12/mac.go
@@ -0,0 +1,55 @@
+// 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/x509/pkix"
+ "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
+}
+
+const (
+ sha1Algorithm = "SHA-1"
+)
+
+var (
+ hashNameByID = map[string]string{
+ "1.3.14.3.2.26": sha1Algorithm,
+ }
+ hashByName = map[string]func() hash.Hash{
+ sha1Algorithm: sha1.New,
+ }
+)
+
+func verifyMac(macData *macData, message, password []byte) error {
+ name, ok := hashNameByID[macData.Mac.Algorithm.Algorithm.String()]
+ if !ok {
+ return NotImplementedError("unknown digest algorithm: " +
macData.Mac.Algorithm.Algorithm.String())
+ }
+ k := deriveMacKeyByAlg[name](macData.MacSalt, password,
macData.Iterations)
+ password = nil
+
+ mac := hmac.New(hashByName[name], k)
+ mac.Write(message)
+ expectedMAC := mac.Sum(nil)
+
+ if !hmac.Equal(macData.Mac.Digest, expectedMAC) {
+ return ErrIncorrectPassword
+ }
+ return nil
+}
diff --git a/pkcs12/mac_test.go b/pkcs12/mac_test.go
new file mode 100644
index 0000000..0fc5861
--- /dev/null
+++ b/pkcs12/mac_test.go
@@ -0,0 +1,42 @@
+// 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 (
+ "encoding/asn1"
+ "testing"
+)
+
+func TestVerifyMac(t *testing.T) {
+ td := macData{
+ Mac: digestInfo{
+ Digest: []byte{0x18, 0x20, 0x3d, 0xff, 0x1e, 0x16, 0xf4, 0x92, 0xf2,
0xaf, 0xc8, 0x91, 0xa9, 0xba, 0xd6, 0xca, 0x9d, 0xee, 0x51, 0x93},
+ },
+ MacSalt: []byte{1, 2, 3, 4, 5, 6, 7, 8},
+ Iterations: 2048,
+ }
+
+ message := []byte{11, 12, 13, 14, 15}
+ password, _ := bmpString([]byte(""))
+
+ td.Mac.Algorithm.Algorithm = asn1.ObjectIdentifier([]int{1, 2, 3})
+ err := verifyMac(&td, message, password)
+ if _, ok := err.(NotImplementedError); !ok {
+ t.Errorf("err: %v", err)
+ }
+
+ td.Mac.Algorithm.Algorithm = asn1.ObjectIdentifier([]int{1, 3, 14, 3, 2,
26})
+ err = verifyMac(&td, message, password)
+ if err != ErrIncorrectPassword {
+ t.Errorf("Expected incorrect password, got err: %v", err)
+ }
+
+ password, _ = bmpString([]byte("Sesame open"))
+ err = verifyMac(&td, message, password)
+ if err != nil {
+ t.Errorf("err: %v", err)
+ }
+
+}
diff --git a/pkcs12/pbkdf.go b/pkcs12/pbkdf.go
new file mode 100644
index 0000000..435ae4b
--- /dev/null
+++ b/pkcs12/pbkdf.go
@@ -0,0 +1,196 @@
+// 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/sha1"
+ "math/big"
+)
+
+var (
+ deriveKeyByAlg = map[string]func(salt, password []byte, iterations int)
[]byte{
+ pbeWithSHAAnd3KeyTripleDESCBC: func(salt, password []byte, iterations
int) []byte {
+ return pbkdf(sha1Sum, 20, 64, salt, password, iterations, 1, 24)
+ },
+ pbewithSHAAnd40BitRC2CBC: func(salt, password []byte, iterations int)
[]byte {
+ return pbkdf(sha1Sum, 20, 64, salt, password, iterations, 1, 5)
+ },
+ }
+ deriveIVByAlg = map[string]func(salt, password []byte, iterations int)
[]byte{
+ pbeWithSHAAnd3KeyTripleDESCBC: func(salt, password []byte, iterations
int) []byte {
+ return pbkdf(sha1Sum, 20, 64, salt, password, iterations, 2, 8)
+ },
+ pbewithSHAAnd40BitRC2CBC: func(salt, password []byte, iterations int)
[]byte {
+ return pbkdf(sha1Sum, 20, 64, salt, password, iterations, 2, 8)
+ },
+ }
+ deriveMacKeyByAlg = map[string]func(salt, password []byte, iterations
int) []byte{
+ sha1Algorithm: func(salt, password []byte, iterations int) []byte {
+ return pbkdf(sha1Sum, 20, 64, salt, password, iterations, 3, 20)
+ },
+ }
+)
+
+func sha1Sum(in []byte) []byte {
+ sum := sha1.Sum(in)
+ return sum[:]
+}
+
+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.
+ 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 := []byte{}
+ {
+ s := len(salt)
+ times := s / v
+ if s%v > 0 {
+ times++
+ }
+ for len(S) < times*v {
+ S = append(S, salt...)
+ }
+ S = S[:times*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 := []byte{}
+ {
+ s := len(password)
+ times := s / v
+ if s%v > 0 {
+ times++
+ }
+ for len(P) < times*v {
+ P = append(P, password...)
+ }
+ password = nil
+ P = P[:times*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
+ if size%u > 0 {
+ c++
+ }
+
+ // 6. For i=1, 2, ..., c, do the following:
+ A := make([]byte, c*20)
+ 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*20:], 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).
+ 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)
+ Bbi.SetBytes(B)
+
+ one := big.NewInt(1)
+
+ for j := 0; j < len(I)/v; j++ {
+ Ij := new(big.Int)
+ Ij.SetBytes(I[j*v : (j+1)*v])
+ Ij.Add(Ij, Bbi)
+ Ij.Add(Ij, one)
+ Ijb := Ij.Bytes()
+ if len(Ijb) > v {
+ Ijb = Ijb[len(Ijb)-v:]
+ }
+ 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.
+ A = A[:size]
+
+ return A
+
+ // 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/pkcs12/pbkdf_test.go b/pkcs12/pbkdf_test.go
new file mode 100644
index 0000000..b4e7b89
--- /dev/null
+++ b/pkcs12/pbkdf_test.go
@@ -0,0 +1,24 @@
+// 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"
+ "testing"
+)
+
+var hit = false
+
+func TestThatPBKDFWorksCorrectlyForLongKeys(t *testing.T) {
+ pbkdf := deriveKeyByAlg[pbeWithSHAAnd3KeyTripleDESCBC]
+
+ salt := []byte("\xff\xff\xff\xff\xff\xff\xff\xff")
+ password, _ := bmpString([]byte("sesame"))
+ key := pbkdf(salt, password, 2048)
+
+ if expected :=
[]byte("\x7c\xd9\xfd\x3e\x2b\x3b\xe7\x69\x1a\x44\xe3\xbe\xf0\xf9\xea\x0f\xb9\xb8\x97\xd4\xe3\x25\xd9\xd1");
bytes.Compare(key, expected) != 0 {
+ t.Fatalf("expected key '% x', but found '% x'", key, expected)
+ }
+}
diff --git a/pkcs12/pkcs12.go b/pkcs12/pkcs12.go
new file mode 100644
index 0000000..a330c99
--- /dev/null
+++ b/pkcs12/pkcs12.go
@@ -0,0 +1,327 @@
+// 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 provides some implementations of PKCS#12.
+//
+// This implementation is distilled from
https://tools.ietf.org/html/rfc7292 and referenced documents.
+// It is intended for decoding P12/PFX-stored certificate+key for use with
the crypto/tls package.
+package pkcs12
+
+import (
+ "bytes"
+ "crypto/ecdsa"
+ "crypto/rsa"
+ "crypto/x509"
+ "crypto/x509/pkix"
+ "encoding/asn1"
+ "encoding/pem"
+ "errors"
+ "fmt"
+)
+
+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"`
+}
+
+const (
+ oidDataContentType = "1.2.840.113549.1.7.1"
+ oidEncryptedDataContentType = "1.2.840.113549.1.7.6"
+)
+
+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) GetAlgorithm() pkix.AlgorithmIdentifier {
+ return i.ContentEncryptionAlgorithm
+}
+func (i encryptedContentInfo) GetData() []byte { return i.EncryptedContent
}
+
+type safeBag struct {
+ ID asn1.ObjectIdentifier
+ Value asn1.RawValue `asn1:"tag:0,explicit"`
+ Attributes []pkcs12Attribute `asn1:"set,optional"`
+}
+
+type pkcs12Attribute struct {
+ ID asn1.ObjectIdentifier
+ Value asn1.RawValue `ans1:"set"`
+}
+
+type encryptedPrivateKeyInfo struct {
+ AlgorithmIdentifier pkix.AlgorithmIdentifier
+ EncryptedData []byte
+}
+
+func (i encryptedPrivateKeyInfo) GetAlgorithm() pkix.AlgorithmIdentifier {
return i.AlgorithmIdentifier }
+func (i encryptedPrivateKeyInfo) GetData() []byte {
return i.EncryptedData }
+
+// PEM block types
+const (
+ CertificateType = "CERTIFICATE"
+ PrivateKeyType = "PRIVATE KEY"
+)
+
+// ConvertToPEM converts all "safe bags" contained in pfxData to PEM
blocks.
+func ConvertToPEM(pfxData, utf8Password []byte) (blocks []*pem.Block, err
error) {
+ p, err := bmpString(utf8Password)
+
+ defer func() { // clear out BMP version of the password before we return
+ for i := 0; i < len(p); i++ {
+ p[i] = 0
+ }
+ }()
+
+ if err != nil {
+ return nil, ErrIncorrectPassword
+ }
+
+ bags, p, err := getSafeContents(pfxData, p)
+
+ blocks = make([]*pem.Block, 0, 2)
+ for _, bag := range bags {
+ var block *pem.Block
+ block, err = convertBag(&bag, p)
+ if err != nil {
+ return
+ }
+ blocks = append(blocks, block)
+ }
+
+ return
+}
+
+func convertBag(bag *safeBag, password []byte) (*pem.Block, error) {
+ b := new(pem.Block)
+
+ for _, attribute := range bag.Attributes {
+ k, v, err := convertAttribute(&attribute)
+ if err != nil {
+ return nil, err
+ }
+ if b.Headers == nil {
+ b.Headers = make(map[string]string)
+ }
+ b.Headers[k] = v
+ }
+
+ bagType := bagTypeNameByOID[bag.ID.String()]
+ switch bagType {
+ case certBagType:
+ b.Type = CertificateType
+ certsData, err := decodeCertBag(bag.Value.Bytes)
+ if err != nil {
+ return nil, err
+ }
+ b.Bytes = certsData
+ case pkcs8ShroudedKeyBagType:
+ b.Type = PrivateKeyType
+
+ key, err := decodePkcs8ShroudedKeyBag(bag.Value.Bytes, password)
+ if err != nil {
+ return nil, err
+ }
+
+ switch key := key.(type) {
+ case *rsa.PrivateKey:
+ b.Bytes = x509.MarshalPKCS1PrivateKey(key)
+ case *ecdsa.PrivateKey:
+ b.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 b, nil
+}
+
+const (
+ oidFriendlyName = "1.2.840.113549.1.9.20"
+ oidLocalKeyID = "1.2.840.113549.1.9.21"
+ oidMicrosoftCSPName = "1.3.6.1.4.1.311.17.1"
+)
+
+var attributeNameByOID = map[string]string{
+ oidFriendlyName: "friendlyName",
+ oidLocalKeyID: "localKeyId",
+ oidMicrosoftCSPName: "Microsoft CSP Name", // openssl-compatible
+}
+
+func convertAttribute(attribute *pkcs12Attribute) (key, value string, err
error) {
+ oid := attribute.ID.String()
+ key = attributeNameByOID[oid]
+ switch oid {
+ case oidMicrosoftCSPName:
+ fallthrough
+ case oidFriendlyName:
+ if _, err = asn1.Unmarshal(attribute.Value.Bytes, &attribute.Value);
err != nil {
+ return
+ }
+ if value, err = decodeBMPString(attribute.Value.Bytes); err != nil {
+ return
+ }
+ case oidLocalKeyID:
+ id := new([]byte)
+ if _, err = asn1.Unmarshal(attribute.Value.Bytes, id); err != nil {
+ return
+ }
+ value = fmt.Sprintf("% x", *id)
+ default:
+ err = errors.New("don't know how to handle attribute with OID " +
attribute.ID.String())
+ return
+ }
+
+ return key, value, nil
+}
+
+// Decode extracts a certificate and private key from pfxData.
+// This function assumes that there is only one certificate and only one
private key in the pfxData.
+func Decode(pfxData, utf8Password []byte) (privateKey interface{},
certificate *x509.Certificate, err error) {
+ p, err := bmpString(utf8Password)
+ defer func() { // clear out BMP version of the password before we return
+ for i := 0; i < len(p); i++ {
+ p[i] = 0
+ }
+ }()
+
+ if err != nil {
+ return nil, nil, err
+ }
+ bags, p, err := getSafeContents(pfxData, p)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ if len(bags) != 2 {
+ err = errors.New("expected exactly two safe bags in the PFX PDU")
+ return
+ }
+
+ for _, bag := range bags {
+ bagType := bagTypeNameByOID[bag.ID.String()]
+
+ switch bagType {
+ case certBagType:
+ certsData, err := decodeCertBag(bag.Value.Bytes)
+ if err != nil {
+ return nil, nil, err
+ }
+ certs, err := x509.ParseCertificates(certsData)
+ if err != nil {
+ return nil, nil, err
+ }
+ if len(certs) != 1 {
+ err = errors.New("expected exactly one certificate in the certBag")
+ return nil, nil, err
+ }
+ certificate = certs[0]
+ case pkcs8ShroudedKeyBagType:
+ if privateKey, err = decodePkcs8ShroudedKeyBag(bag.Value.Bytes, p);
err != nil {
+ return nil, nil, err
+ }
+ }
+ }
+
+ if certificate == nil {
+ return nil, nil, errors.New("certificate missing")
+ }
+ if privateKey == nil {
+ return nil, nil, errors.New("private key missing")
+ }
+
+ return
+}
+
+func getSafeContents(p12Data, password []byte) (bags []safeBag,
actualPassword []byte, err error) {
+ pfx := new(pfxPdu)
+ if _, err = asn1.Unmarshal(p12Data, pfx); err != nil {
+ return nil, nil, fmt.Errorf("error reading P12 data: %v", err)
+ }
+
+ if pfx.Version != 3 {
+ return nil, nil, NotImplementedError("can only decode v3 PFX PDU's")
+ }
+
+ if pfx.AuthSafe.ContentType.String() != oidDataContentType {
+ return nil, nil, NotImplementedError("only password-protected PFX is
implemented")
+ }
+
+ // unmarshal the explicit bytes in the content for type 'data'
+ if _, err = asn1.Unmarshal(pfx.AuthSafe.Content.Bytes,
&pfx.AuthSafe.Content); err != nil {
+ return nil, nil, err
+ }
+
+ actualPassword = password
+ password = nil
+ if len(pfx.MacData.Mac.Algorithm.Algorithm) > 0 {
+ if err = verifyMac(&pfx.MacData, pfx.AuthSafe.Content.Bytes,
actualPassword); err != nil {
+ if err == ErrIncorrectPassword && bytes.Compare(actualPassword,
[]byte{0, 0}) == 0 {
+ // some implementations use an empty byte array for the empty string
password
+ // try one more time with empty-empty password
+ actualPassword = []byte{}
+ err = verifyMac(&pfx.MacData, pfx.AuthSafe.Content.Bytes,
actualPassword)
+ }
+ }
+ if err != nil {
+ return
+ }
+ }
+
+ var authenticatedSafe []contentInfo
+ if _, err = asn1.Unmarshal(pfx.AuthSafe.Content.Bytes,
&authenticatedSafe); err != nil {
+ return
+ }
+
+ if len(authenticatedSafe) != 2 {
+ return nil, nil, NotImplementedError("expected exactly two items in the
authenticated safe")
+ }
+
+ for _, ci := range authenticatedSafe {
+ var data []byte
+ switch ci.ContentType.String() {
+ case oidDataContentType:
+ if _, err = asn1.Unmarshal(ci.Content.Bytes, &data); err != nil {
+ return
+ }
+ case oidEncryptedDataContentType:
+ var encryptedData encryptedData
+ if _, err = asn1.Unmarshal(ci.Content.Bytes, &encryptedData); err !=
nil {
+ return
+ }
+ if encryptedData.Version != 0 {
+ return nil, nil, NotImplementedError("only version 0 of EncryptedData
is supported")
+ }
+ if data, err = pbDecrypt(encryptedData.EncryptedContentInfo,
actualPassword); err != nil {
+ return
+ }
+ default:
+ return nil, nil, NotImplementedError("only data and encryptedData
content types are supported in authenticated safe")
+ }
+
+ var safeContents []safeBag
+ if _, err = asn1.Unmarshal(data, &safeContents); err != nil {
+ return
+ }
+ bags = append(bags, safeContents...)
+ }
+ return
+}
diff --git a/pkcs12/pkcs12_test.go b/pkcs12/pkcs12_test.go
new file mode 100644
index 0000000..ce8d09a
--- /dev/null
+++ b/pkcs12/pkcs12_test.go
@@ -0,0 +1,145 @@
+// 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/rsa"
+ "crypto/tls"
+ "encoding/base64"
+ "encoding/pem"
+ "fmt"
+ "testing"
+)
+
+func TestPfx(t *testing.T) {
+ for commonName, base64P12 := range testdata {
+ var p12, _ = base64.StdEncoding.DecodeString(base64P12)
+
+ pk, c, err := Decode(p12, []byte(""))
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ t.Logf("pk: %v", pk)
+ t.Logf("c: %v", c)
+
+ err = pk.(*rsa.PrivateKey).Validate()
+ if err != nil {
+ t.Errorf("err while validating private key: %v", err)
+ }
+
+ if c.Subject.CommonName != commonName {
+ t.Errorf("expected common name to be '%s', but found '%s'", commonName,
c.Subject.CommonName)
+ }
+ }
+}
+
+func TestPEM(t *testing.T) {
+ for commonName, base64P12 := range testdata {
+ var p12, _ = base64.StdEncoding.DecodeString(base64P12)
+ blocks, err := ConvertToPEM(p12, []byte(""))
+ if err != nil {
+ t.Fatalf("err while converting to PEM: %v", err)
+ }
+ pemData := []byte{}
+ for _, b := range blocks {
+ t.Logf(" writing %s", b.Type)
+ pemData = append(pemData, pem.EncodeToMemory(b)...)
+ }
+
+ cert, err := tls.X509KeyPair(pemData, pemData)
+ if err != nil {
+ t.Errorf("err while converting to key pair: %v", err)
+ }
+ config := tls.Config{
+ Certificates: []tls.Certificate{cert},
+ }
+ config.BuildNameToCertificate()
+
+ if _, exists := config.NameToCertificate[commonName]; !exists {
+ t.Errorf("did not find our cert in PEM?: %v", config.NameToCertificate)
+ }
+ }
+}
+
+func ExampleConvertToPEM() {
+ var p12, _ = base64.StdEncoding.DecodeString(`MIIJzgIBAzCCCZQGCS ...
CA+gwggPk==`)
+ blocks, err := ConvertToPEM(p12, []byte("password"))
+ if err != nil {
+ panic(err)
+ }
+
+ pemData := []byte{}
+ for _, b := range blocks {
+ pemData = append(pemData, pem.EncodeToMemory(b)...)
+ }
+
+ // then use PEM data for tls to construct tls certificate:
+
+ cert, err := tls.X509KeyPair(pemData, pemData)
+ if err != nil {
+ panic(err)
+ }
+
+ config := tls.Config{
+ Certificates: []tls.Certificate{cert},
+ }
+
+ config.BuildNameToCertificate()
+ for name := range config.NameToCertificate {
+ fmt.Println(name)
+ }
+}
+
+var testdata = map[string]string{
+ // 'null' password test case
+ "Windows Azure Tools":
`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`,
+ // empty string password test case
+ "tes...@example.com":
`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
+SCu` +
`nTQ8UB/M5IXWKkhMOO11dP4niWwb39Y7pCWpau7mwbXOKfRPX96cgHnQJK5uG+BesDD1oYnX0
+6frN7FOnTSHKruRIwuI8KnOQ/I+owmyz71wiv5LMQt+yM47UrEjB/EZa5X8dpEwOZvkdqL7utcyo
+l0XH5kWMXdW856LL/FYftAqJIDAmtX1TXF/rbP6mPyN/IlDC0gjP84Uzd/a2UyTIWr+wk49Ek3vQ
+/uDamq6QrwAxVmNh5Tset5Vhpc1e1kb7mRMZIzxSP8JcTuYd45oFKi98I8YjvueHVZce1g7OudQP
+SbFQoJvdT46iBg1TTatlltpOiH2mFaxWVS0xYjAjBgkqhkiG9w0BCRUxFgQUdA9eVqvETX4an/c8
+p8SsTugkit8wOwYJKoZIhvcNAQkUMS4eLABGAHIAaQBlAG4AZABsAHkAIABuAGEAbQBlACAAZgBv
+AHIAIABjAGUAcgB0MDEwITAJBgUrDgMCGgUABBRFsNz3Zd1O1GI8GTuFwCWuDOjEEwQIuBEfIcAy
+HQ8CAggA`,
+}
diff --git a/pkcs12/rc2/bench_test.go b/pkcs12/rc2/bench_test.go
new file mode 100644
index 0000000..3347f33
--- /dev/null
+++ b/pkcs12/rc2/bench_test.go
@@ -0,0 +1,27 @@
+// 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
+
+import (
+ "testing"
+)
+
+func BenchmarkEncrypt(b *testing.B) {
+ r, _ := New([]byte{0, 0, 0, 0, 0, 0, 0, 0}, 64)
+ b.ResetTimer()
+ var src [8]byte
+ for i := 0; i < b.N; i++ {
+ r.Encrypt(src[:], src[:])
+ }
+}
+
+func BenchmarkDecrypt(b *testing.B) {
+ r, _ := New([]byte{0, 0, 0, 0, 0, 0, 0, 0}, 64)
+ b.ResetTimer()
+ var src [8]byte
+ for i := 0; i < b.N; i++ {
+ r.Decrypt(src[:], src[:])
+ }
+}
diff --git a/pkcs12/rc2/rc2.go b/pkcs12/rc2/rc2.go
new file mode 100644
index 0000000..8c70902
--- /dev/null
+++ b/pkcs12/rc2/rc2.go
@@ -0,0 +1,274 @@
+// 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/pkcs12/rc2/rc2_test.go b/pkcs12/rc2/rc2_test.go
new file mode 100644
index 0000000..8a49dfa
--- /dev/null
+++ b/pkcs12/rc2/rc2_test.go
@@ -0,0 +1,93 @@
+// 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
+
+import (
+ "bytes"
+ "encoding/hex"
+ "testing"
+)
+
+func TestEncryptDecrypt(t *testing.T) {
+
+ // TODO(dgryski): add the rest of the test vectors from the RFC
+ var tests = []struct {
+ key string
+ plain string
+ cipher string
+ t1 int
+ }{
+ {
+ "0000000000000000",
+ "0000000000000000",
+ "ebb773f993278eff",
+ 63,
+ },
+ {
+ "ffffffffffffffff",
+ "ffffffffffffffff",
+ "278b27e42e2f0d49",
+ 64,
+ },
+ {
+ "3000000000000000",
+ "1000000000000001",
+ "30649edf9be7d2c2",
+ 64,
+ },
+ {
+ "88",
+ "0000000000000000",
+ "61a8a244adacccf0",
+ 64,
+ },
+ {
+ "88bca90e90875a",
+ "0000000000000000",
+ "6ccf4308974c267f",
+ 64,
+ },
+ {
+ "88bca90e90875a7f0f79c384627bafb2",
+ "0000000000000000",
+ "1a807d272bbe5db1",
+ 64,
+ },
+ {
+ "88bca90e90875a7f0f79c384627bafb2",
+ "0000000000000000",
+ "2269552ab0f85ca6",
+ 128,
+ },
+ {
+ "88bca90e90875a7f0f79c384627bafb216f80a6f85920584c42fceb0be255daf1e",
+ "0000000000000000",
+ "5b78d3a43dfff1f1",
+ 129,
+ },
+ }
+
+ for _, tt := range tests {
+ k, _ := hex.DecodeString(tt.key)
+ p, _ := hex.DecodeString(tt.plain)
+ c, _ := hex.DecodeString(tt.cipher)
+
+ b, _ := New(k, tt.t1)
+
+ var dst [8]byte
+
+ b.Encrypt(dst[:], p)
+
+ if !bytes.Equal(dst[:], c) {
+ t.Errorf("encrypt failed: got % 2x wanted % 2x\n", dst, c)
+ }
+
+ b.Decrypt(dst[:], c)
+
+ if !bytes.Equal(dst[:], p) {
+ t.Errorf("decrypt failed: got % 2x wanted % 2x\n", dst, p)
+ }
+ }
+}
diff --git a/pkcs12/safebags.go b/pkcs12/safebags.go
new file mode 100644
index 0000000..0937a64
--- /dev/null
+++ b/pkcs12/safebags.go
@@ -0,0 +1,75 @@
+// 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"
+ "fmt"
+)
+
+//see https://tools.ietf.org/html/rfc7292#appendix-D
+var bagTypeNameByOID = map[string]string{
+ "1.2.840.113549.1.12.10.1.1": keyBagType,
+ "1.2.840.113549.1.12.10.1.2": pkcs8ShroudedKeyBagType,
+ "1.2.840.113549.1.12.10.1.3": certBagType,
+ "1.2.840.113549.1.12.10.1.4": crlBagType,
+ "1.2.840.113549.1.12.10.1.5": secretBagType,
+ "1.2.840.113549.1.12.10.1.6": safeContentsBagType,
+}
+
+const (
+ keyBagType = "keyBag"
+ pkcs8ShroudedKeyBagType = "pkcs8ShroudedKeyBag"
+ certBagType = "certBag"
+ crlBagType = "crlBag"
+ secretBagType = "secretBag"
+ safeContentsBagType = "safeContentsBag"
+
+ oidCertTypeX509Certificate = "1.2.840.113549.1.9.22.1"
+ oidLocalKeyIDAttribute = "1.2.840.113549.1.9.21"
+)
+
+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 = asn1.Unmarshal(asn1Data, pkinfo); err != nil {
+ err = fmt.Errorf("error decoding PKCS8 shrouded key bag: %v", err)
+ return nil, err
+ }
+
+ pkData, err := pbDecrypt(pkinfo, password)
+ if err != nil {
+ err = fmt.Errorf("error decrypting PKCS8 shrouded key bag: %v", err)
+ return
+ }
+
+ rv := new(asn1.RawValue)
+ if _, err = asn1.Unmarshal(pkData, rv); err != nil {
+ err = fmt.Errorf("could not decode decrypted private key data")
+ }
+
+ if privateKey, err = x509.ParsePKCS8PrivateKey(pkData); err != nil {
+ err = fmt.Errorf("error parsing PKCS8 private key: %v", err)
+ return nil, err
+ }
+ return
+}
+
+func decodeCertBag(asn1Data []byte) (x509Certificates []byte, err error) {
+ bag := new(certBag)
+ if _, err := asn1.Unmarshal(asn1Data, bag); err != nil {
+ err = fmt.Errorf("error decoding cert bag: %v", err)
+ return nil, err
+ }
+ if bag.ID.String() != oidCertTypeX509Certificate {
+ return nil, NotImplementedError("only X509 certificates are supported")
+ }
+ return bag.Data, nil
+}

--
https://go-review.googlesource.com/11986

Adam Langley (Gerrit)

unread,
Sep 12, 2015, 6:35:56 PM9/12/15
to Paul Meyer, golang-co...@googlegroups.com
Adam Langley uploaded a new patch set:
15 files changed, 1,584 insertions(+), 0 deletions(-)


--
https://go-review.googlesource.com/11986
Gerrit-Reviewer: Adam Langley <a...@golang.org>

Adam Langley (Gerrit)

unread,
Sep 12, 2015, 6:41:08 PM9/12/15
to Paul Meyer, golang-co...@googlegroups.com
Adam Langley has posted comments on this change.

x/crypto: Add pkcs12 package for reading pkcs12 data

Patch Set 1:

I've done a significant pass on this. Please check to see if I've obviously
messed anything up.

One blocker is that rc2/rc2.go lists an MIT license. In order to ensure the
licensing is sound I'll email dgryski and ask him to chime in here to
confirm that the code is submitted under the CLA.

--
https://go-review.googlesource.com/11986
Gerrit-Reviewer: Adam Langley <a...@golang.org>
Gerrit-HasComments: No

Adam Langley (Gerrit)

unread,
Sep 13, 2015, 1:01:38 PM9/13/15
to Paul Meyer, golang-co...@googlegroups.com
Adam Langley has posted comments on this change.

x/crypto: Add pkcs12 package for reading pkcs12 data

Patch Set 2:

> I've done a significant pass on this. Please check to see if I've
> obviously messed anything up.

p.s. also, the MAC check in the code is optional. Is that actually a
reality of PKCS#12 files in the wild? Without the MAC check, a padding
oracle attack easily breaks the confidentiality of the data.

Paul Meyer

unread,
Sep 14, 2015, 6:58:57 PM9/14/15
to a...@golang.org, golang-co...@googlegroups.com
Hi Adam,

Dgryski submitted this code to Azure/go-pkcs12 under MS CLA (https://github.com/Azure/go-pkcs12/pull/24). I apparently forgot to remove this MIT license mention when submitting to x/crypto.
Looking through the other changes...

Thanks, Paul

-----Original Message-----
From: Adam Langley (Gerrit) [mailto:noreply-gerritcoderevie...@google.com]
Sent: Saturday, September 12, 2015 3:41 PM
To: Paul Meyer <Paul....@microsoft.com>
Cc: golang-co...@googlegroups.com
Subject: [crypto] x/crypto: Add pkcs12 package for reading pkcs12 data

Adam Langley has posted comments on this change.

x/crypto: Add pkcs12 package for reading pkcs12 data

Patch Set 1:

I've done a significant pass on this. Please check to see if I've obviously messed anything up.

One blocker is that rc2/rc2.go lists an MIT license. In order to ensure the licensing is sound I'll email dgryski and ask him to chime in here to confirm that the code is submitted under the CLA.

--
https://na01.safelinks.protection.outlook.com/?url=https%3a%2f%2fgo-review.googlesource.com%2f11986&data=01%7c01%7cpaul.meyer%40microsoft.com%7c2882f467509a44106fed08d2bbc33ffe%7c72f988bf86f141af91ab2d7cd011db47%7c1&sdata=8uAfXrs6zvL2EJt4pwLAOGZPtAyxE8pc7rarCIFXJV0%3d

Minux Ma (Gerrit)

unread,
Sep 14, 2015, 7:36:26 PM9/14/15
to Adam Langley, Paul Meyer, Minux Ma, golang-co...@googlegroups.com
Minux Ma has posted comments on this change.

x/crypto: Add pkcs12 package for reading pkcs12 data

Patch Set 2:

if we really don't want people to use the rc2 package,
should we move it to an internal package.
(e.g. x/crypto/internal/rc2)

--
https://go-review.googlesource.com/11986
Gerrit-Reviewer: Adam Langley <a...@golang.org>
Gerrit-Reviewer: Minux Ma <mi...@golang.org>
Gerrit-HasComments: No

Paul Meyer

unread,
Sep 14, 2015, 8:08:45 PM9/14/15
to mi...@golang.org, Adam Langley, golang-co...@googlegroups.com
I looked through Adam's changes and it LGTM.

Also, regarding optionality of the MAC check, from https://tools.ietf.org/html/rfc7292#section-3.1:
"Password integrity mode: Integrity is guaranteed through a Message
Authentication Code (MAC) derived from a secret integrity
password. If password privacy mode is used as well, the privacy
password and the integrity password may or may not be the same."

Since this is the only integrity mode implemented in the current code, I would make sense to return an error if MacData is not set as expected instead of skipping the MAC check.

Thanks, Paul

-----Original Message-----
From: Minux Ma (Gerrit) [mailto:noreply-gerritcoderevie...@google.com]
Sent: Monday, September 14, 2015 4:36 PM
To: Adam Langley <a...@golang.org>; Paul Meyer <Paul....@microsoft.com>
Cc: Minux Ma <mi...@golang.org>; golang-co...@googlegroups.com
Subject: [crypto] x/crypto: Add pkcs12 package for reading pkcs12 data

Minux Ma has posted comments on this change.

x/crypto: Add pkcs12 package for reading pkcs12 data

Patch Set 2:

if we really don't want people to use the rc2 package, should we move it to an internal package.
(e.g. x/crypto/internal/rc2)

--
https://na01.safelinks.protection.outlook.com/?url=https%3a%2f%2fgo-review.googlesource.com%2f11986&data=01%7c01%7cpaul.meyer%40microsoft.com%7c23170c897eab4033504408d2bd5d4e7b%7c72f988bf86f141af91ab2d7cd011db47%7c1&sdata=fDJVQ8zzaQnbWsMjCmL7q9XQ36Dsb%2bMhNUpqf%2bltpgg%3d

Adam Langley (Gerrit)

unread,
Sep 29, 2015, 8:27:19 PM9/29/15
to Paul Meyer, Minux Ma, golang-co...@googlegroups.com
Adam Langley has posted comments on this change.

x/crypto: Add pkcs12 package for reading pkcs12 data

Patch Set 2:

Currently on hold:

dgrisky has not replied to my request to confirm that he intends this to be
submitted under the CLA.

Paul has not replied to pending questions.

--
https://go-review.googlesource.com/11986

Paul Meyer (Gerrit)

unread,
Sep 30, 2015, 1:08:25 PM9/30/15
to Adam Langley, Minux Ma, golang-co...@googlegroups.com
Paul Meyer has posted comments on this change.

x/crypto: Add pkcs12 package for reading pkcs12 data

Patch Set 2:

Sorry, I replied to gerrit's email, but that goes to /dev/null

I looked through Adam's changes and it LGTM.

Regarding optionality of the MAC check, from
https://tools.ietf.org/html/rfc7292#section-3.1:
"Password integrity mode: Integrity is guaranteed through a Message
Authentication Code (MAC) derived from a secret integrity
password. If password privacy mode is used as well, the privacy
password and the integrity password may or may not be the same."
Since this is the only integrity mode implemented in the current code, I
would make sense to return an error if MacData is not set as expected
instead of skipping the MAC check.

Dgryski submitted this code to Azure/go-pkcs12 under MS CLA
(https://github.com/Azure/go-pkcs12/pull/24). So legally, I am authorized
to submit this code to Go's codebase. I apparently forgot to remove this
MIT license mention when submitting to x/crypto.

Thanks, Paul

--
https://go-review.googlesource.com/11986
Gerrit-Reviewer: Adam Langley <a...@golang.org>
Gerrit-Reviewer: Minux Ma <mi...@golang.org>
Gerrit-Reviewer: Paul Meyer <paul....@microsoft.com>
Gerrit-HasComments: No

Damian Gryski (Gerrit)

unread,
Oct 1, 2015, 11:43:59 AM10/1/15
to Adam Langley, Paul Meyer, Minux Ma, golang-co...@googlegroups.com
Damian Gryski has posted comments on this change.

x/crypto: Add pkcs12 package for reading pkcs12 data

Patch Set 2:

Hi Adam,

What email address did you use? I haven't seen anything from you about the
PKCS12 code.

And to answer here, yes I wrote the code and submitted it under the
Microsoft CLA and approve of it being relicensed as appropriate for
inclusion in the Go project. I've also previously signed Google's CLA.


Damian

--
https://go-review.googlesource.com/11986
Gerrit-Reviewer: Adam Langley <a...@golang.org>
Gerrit-Reviewer: Damian Gryski <dgr...@gmail.com>

Adam Langley (Gerrit)

unread,
Oct 4, 2015, 12:40:35 PM10/4/15
to Paul Meyer, Minux Ma, Damian Gryski, golang-co...@googlegroups.com
Adam Langley has posted comments on this change.

x/crypto: Add pkcs12 package for reading pkcs12 data

Patch Set 2:

> What email address did you use? I haven't seen anything from you
> about the PKCS12 code.

(dgrisky at gmail)

> And to answer here, yes I wrote the code and submitted it under the
> Microsoft CLA and approve of it being relicensed as appropriate for
> inclusion in the Go project. I've also previously signed Google's
> CLA.

Thanks for confirming.

Adam Langley (Gerrit)

unread,
Oct 4, 2015, 12:43:31 PM10/4/15
to Paul Meyer, Minux Ma, Damian Gryski, golang-co...@googlegroups.com
Adam Langley has posted comments on this change.

x/crypto: Add pkcs12 package for reading pkcs12 data

Patch Set 2:

> Since this is the only integrity mode implemented in the current
> code, I would make sense to return an error if MacData is not set
> as expected instead of skipping the MAC check.

Thanks. I'll upload a tweaked version to make the MAC check required.

Adam Langley (Gerrit)

unread,
Oct 4, 2015, 2:51:13 PM10/4/15
to Paul Meyer, Damian Gryski, Minux Ma, golang-co...@googlegroups.com
Adam Langley uploaded a new patch set:
https://go-review.googlesource.com/11986

x/crypto: Add pkcs12 package for reading pkcs12 data

Package pkcs12 provides some Go implementations of PKCS#12.
This implementation is distilled from https://tools.ietf.org/html/rfc7292
and
referenced documents. It is intented for decoding P12/PFX-stored
certificate+key
for use with the crypto/tls package.

Package includes @dgryski's RC2 implementation as a sub package as
requested in
https://github.com/golang/go/issues/10621.

Change-Id: I78401241e39cd0099e9082a3a227cf0a3a36e6d1
---
A pkcs12/bmp-string.go
A pkcs12/bmp-string_test.go
A pkcs12/crypto.go
A pkcs12/crypto_test.go
A pkcs12/errors.go
A pkcs12/internal/rc2/bench_test.go
A pkcs12/internal/rc2/rc2.go
A pkcs12/internal/rc2/rc2_test.go
A pkcs12/mac.go
A pkcs12/mac_test.go
A pkcs12/pbkdf.go
A pkcs12/pbkdf_test.go
A pkcs12/pkcs12.go
A pkcs12/pkcs12_test.go
A pkcs12/safebags.go
15 files changed, 1,587 insertions(+), 0 deletions(-)

Adam Langley (Gerrit)

unread,
Oct 4, 2015, 2:51:48 PM10/4/15
to Paul Meyer, Minux Ma, Damian Gryski, golang-co...@googlegroups.com
Adam Langley has posted comments on this change.

x/crypto: Add pkcs12 package for reading pkcs12 data

Patch Set 3: Code-Review+2 Run-TryBot+1

--
https://go-review.googlesource.com/11986
Gerrit-Reviewer: Adam Langley <a...@golang.org>
Gerrit-Reviewer: Damian Gryski <dgr...@gmail.com>
Gerrit-Reviewer: Minux Ma <mi...@golang.org>
Gerrit-Reviewer: Paul Meyer <paul....@microsoft.com>
Gerrit-HasComments: No

Adam Langley (Gerrit)

unread,
Oct 4, 2015, 2:58:15 PM10/4/15
to Paul Meyer, golang-...@googlegroups.com, Minux Ma, Damian Gryski, golang-co...@googlegroups.com
Adam Langley has submitted this change and it was merged.

x/crypto: Add pkcs12 package for reading pkcs12 data

Package pkcs12 provides some Go implementations of PKCS#12.
This implementation is distilled from https://tools.ietf.org/html/rfc7292
and
referenced documents. It is intented for decoding P12/PFX-stored
certificate+key
for use with the crypto/tls package.

Package includes @dgryski's RC2 implementation as a sub package as
requested in
https://github.com/golang/go/issues/10621.

Change-Id: I78401241e39cd0099e9082a3a227cf0a3a36e6d1
Reviewed-on: https://go-review.googlesource.com/11986
Reviewed-by: Adam Langley <a...@golang.org>
Run-TryBot: Adam Langley <a...@golang.org>
---
A pkcs12/bmp-string.go
A pkcs12/bmp-string_test.go
A pkcs12/crypto.go
A pkcs12/crypto_test.go
A pkcs12/errors.go
A pkcs12/internal/rc2/bench_test.go
A pkcs12/internal/rc2/rc2.go
A pkcs12/internal/rc2/rc2_test.go
A pkcs12/mac.go
A pkcs12/mac_test.go
A pkcs12/pbkdf.go
A pkcs12/pbkdf_test.go
A pkcs12/pkcs12.go
A pkcs12/pkcs12_test.go
A pkcs12/safebags.go
15 files changed, 1,587 insertions(+), 0 deletions(-)

Approvals:
Adam Langley: Looks good to me, approved; Run TryBots
Reply all
Reply to author
Forward
0 new messages