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, ¶ms);
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