Skip to content

Commit c8b9e63

Browse files
paulmeyagl
authored andcommitted
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 golang/go#10621. Change-Id: I78401241e39cd0099e9082a3a227cf0a3a36e6d1 Reviewed-on: https://go-review.googlesource.com/11986 Reviewed-by: Adam Langley <[email protected]> Run-TryBot: Adam Langley <[email protected]>
1 parent 0c93e1f commit c8b9e63

File tree

15 files changed

+1587
-0
lines changed

15 files changed

+1587
-0
lines changed

pkcs12/bmp-string.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Copyright 2015 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package pkcs12
6+
7+
import (
8+
"errors"
9+
"unicode/utf16"
10+
)
11+
12+
// bmpString returns s encoded in UCS-2 with a zero terminator.
13+
func bmpString(s string) ([]byte, error) {
14+
// References:
15+
// https://tools.ietf.org/html/rfc7292#appendix-B.1
16+
// http://en.wikipedia.org/wiki/Plane_(Unicode)#Basic_Multilingual_Plane
17+
// - non-BMP characters are encoded in UTF 16 by using a surrogate pair of 16-bit codes
18+
// EncodeRune returns 0xfffd if the rune does not need special encoding
19+
// - the above RFC provides the info that BMPStrings are NULL terminated.
20+
21+
ret := make([]byte, 0, 2*len(s)+2)
22+
23+
for _, r := range s {
24+
if t, _ := utf16.EncodeRune(r); t != 0xfffd {
25+
return nil, errors.New("pkcs12: string contains characters that cannot be encoded in UCS-2")
26+
}
27+
ret = append(ret, byte(r/256), byte(r%256))
28+
}
29+
30+
return append(ret, 0, 0), nil
31+
}
32+
33+
func decodeBMPString(bmpString []byte) (string, error) {
34+
if len(bmpString)%2 != 0 {
35+
return "", errors.New("pkcs12: odd-length BMP string")
36+
}
37+
38+
// strip terminator if present
39+
if l := len(bmpString); l >= 2 && bmpString[l-1] == 0 && bmpString[l-2] == 0 {
40+
bmpString = bmpString[:l-2]
41+
}
42+
43+
s := make([]uint16, 0, len(bmpString)/2)
44+
for len(bmpString) > 0 {
45+
s = append(s, uint16(bmpString[0])<<8+uint16(bmpString[1]))
46+
bmpString = bmpString[2:]
47+
}
48+
49+
return string(utf16.Decode(s)), nil
50+
}

pkcs12/bmp-string_test.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// Copyright 2015 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package pkcs12
6+
7+
import (
8+
"bytes"
9+
"encoding/hex"
10+
"testing"
11+
)
12+
13+
var bmpStringTests = []struct {
14+
in string
15+
expectedHex string
16+
shouldFail bool
17+
}{
18+
{"", "0000", false},
19+
// Example from https://tools.ietf.org/html/rfc7292#appendix-B.
20+
{"Beavis", "0042006500610076006900730000", false},
21+
// Some characters from the "Letterlike Symbols Unicode block".
22+
{"\u2115 - Double-struck N", "21150020002d00200044006f00750062006c0065002d00730074007200750063006b0020004e0000", false},
23+
// any character outside the BMP should trigger an error.
24+
{"\U0001f000 East wind (Mahjong)", "", true},
25+
}
26+
27+
func TestBMPString(t *testing.T) {
28+
for i, test := range bmpStringTests {
29+
expected, err := hex.DecodeString(test.expectedHex)
30+
if err != nil {
31+
t.Fatalf("#%d: failed to decode expectation", i)
32+
}
33+
34+
out, err := bmpString(test.in)
35+
if err == nil && test.shouldFail {
36+
t.Errorf("#%d: expected to fail, but produced %x", i, out)
37+
continue
38+
}
39+
40+
if err != nil && !test.shouldFail {
41+
t.Errorf("#%d: failed unexpectedly: %s", i, err)
42+
continue
43+
}
44+
45+
if !test.shouldFail {
46+
if !bytes.Equal(out, expected) {
47+
t.Errorf("#%d: expected %s, got %x", i, test.expectedHex, out)
48+
continue
49+
}
50+
51+
roundTrip, err := decodeBMPString(out)
52+
if err != nil {
53+
t.Errorf("#%d: decoding output gave an error: %s", i, err)
54+
continue
55+
}
56+
57+
if roundTrip != test.in {
58+
t.Errorf("#%d: decoding output resulted in %q, but it should have been %q", i, roundTrip, test.in)
59+
continue
60+
}
61+
}
62+
}
63+
}

pkcs12/crypto.go

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
// Copyright 2015 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package pkcs12
6+
7+
import (
8+
"bytes"
9+
"crypto/cipher"
10+
"crypto/des"
11+
"crypto/x509/pkix"
12+
"encoding/asn1"
13+
"errors"
14+
15+
"golang.org/x/crypto/pkcs12/internal/rc2"
16+
)
17+
18+
var (
19+
oidPBEWithSHAAnd3KeyTripleDESCBC = asn1.ObjectIdentifier([]int{1, 2, 840, 113549, 1, 12, 1, 3})
20+
oidPBEWithSHAAnd40BitRC2CBC = asn1.ObjectIdentifier([]int{1, 2, 840, 113549, 1, 12, 1, 6})
21+
)
22+
23+
// pbeCipher is an abstraction of a PKCS#12 cipher.
24+
type pbeCipher interface {
25+
// create returns a cipher.Block given a key.
26+
create(key []byte) (cipher.Block, error)
27+
// deriveKey returns a key derived from the given password and salt.
28+
deriveKey(salt, password []byte, iterations int) []byte
29+
// deriveKey returns an IV derived from the given password and salt.
30+
deriveIV(salt, password []byte, iterations int) []byte
31+
}
32+
33+
type shaWithTripleDESCBC struct{}
34+
35+
func (shaWithTripleDESCBC) create(key []byte) (cipher.Block, error) {
36+
return des.NewTripleDESCipher(key)
37+
}
38+
39+
func (shaWithTripleDESCBC) deriveKey(salt, password []byte, iterations int) []byte {
40+
return pbkdf(sha1Sum, 20, 64, salt, password, iterations, 1, 24)
41+
}
42+
43+
func (shaWithTripleDESCBC) deriveIV(salt, password []byte, iterations int) []byte {
44+
return pbkdf(sha1Sum, 20, 64, salt, password, iterations, 2, 8)
45+
}
46+
47+
type shaWith40BitRC2CBC struct{}
48+
49+
func (shaWith40BitRC2CBC) create(key []byte) (cipher.Block, error) {
50+
return rc2.New(key, len(key)*8)
51+
}
52+
53+
func (shaWith40BitRC2CBC) deriveKey(salt, password []byte, iterations int) []byte {
54+
return pbkdf(sha1Sum, 20, 64, salt, password, iterations, 1, 5)
55+
}
56+
57+
func (shaWith40BitRC2CBC) deriveIV(salt, password []byte, iterations int) []byte {
58+
return pbkdf(sha1Sum, 20, 64, salt, password, iterations, 2, 8)
59+
}
60+
61+
type pbeParams struct {
62+
Salt []byte
63+
Iterations int
64+
}
65+
66+
func pbDecrypterFor(algorithm pkix.AlgorithmIdentifier, password []byte) (cipher.BlockMode, int, error) {
67+
var cipherType pbeCipher
68+
69+
switch {
70+
case algorithm.Algorithm.Equal(oidPBEWithSHAAnd3KeyTripleDESCBC):
71+
cipherType = shaWithTripleDESCBC{}
72+
case algorithm.Algorithm.Equal(oidPBEWithSHAAnd40BitRC2CBC):
73+
cipherType = shaWith40BitRC2CBC{}
74+
default:
75+
return nil, 0, NotImplementedError("algorithm " + algorithm.Algorithm.String() + " is not supported")
76+
}
77+
78+
var params pbeParams
79+
if err := unmarshal(algorithm.Parameters.FullBytes, &params); err != nil {
80+
return nil, 0, err
81+
}
82+
83+
key := cipherType.deriveKey(params.Salt, password, params.Iterations)
84+
iv := cipherType.deriveIV(params.Salt, password, params.Iterations)
85+
86+
block, err := cipherType.create(key)
87+
if err != nil {
88+
return nil, 0, err
89+
}
90+
91+
return cipher.NewCBCDecrypter(block, iv), block.BlockSize(), nil
92+
}
93+
94+
func pbDecrypt(info decryptable, password []byte) (decrypted []byte, err error) {
95+
cbc, blockSize, err := pbDecrypterFor(info.Algorithm(), password)
96+
if err != nil {
97+
return nil, err
98+
}
99+
100+
encrypted := info.Data()
101+
if len(encrypted) == 0 {
102+
return nil, errors.New("pkcs12: empty encrypted data")
103+
}
104+
if len(encrypted)%blockSize != 0 {
105+
return nil, errors.New("pkcs12: input is not a multiple of the block size")
106+
}
107+
decrypted = make([]byte, len(encrypted))
108+
cbc.CryptBlocks(decrypted, encrypted)
109+
110+
psLen := int(decrypted[len(decrypted)-1])
111+
if psLen == 0 || psLen > blockSize {
112+
return nil, ErrDecryption
113+
}
114+
115+
if len(decrypted) < psLen {
116+
return nil, ErrDecryption
117+
}
118+
ps := decrypted[len(decrypted)-psLen:]
119+
decrypted = decrypted[:len(decrypted)-psLen]
120+
if bytes.Compare(ps, bytes.Repeat([]byte{byte(psLen)}, psLen)) != 0 {
121+
return nil, ErrDecryption
122+
}
123+
124+
return
125+
}
126+
127+
// decryptable abstracts a object that contains ciphertext.
128+
type decryptable interface {
129+
Algorithm() pkix.AlgorithmIdentifier
130+
Data() []byte
131+
}

pkcs12/crypto_test.go

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
// Copyright 2015 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package pkcs12
6+
7+
import (
8+
"bytes"
9+
"crypto/x509/pkix"
10+
"encoding/asn1"
11+
"testing"
12+
)
13+
14+
var sha1WithTripleDES = asn1.ObjectIdentifier([]int{1, 2, 840, 113549, 1, 12, 1, 3})
15+
16+
func TestPbDecrypterFor(t *testing.T) {
17+
params, _ := asn1.Marshal(pbeParams{
18+
Salt: []byte{1, 2, 3, 4, 5, 6, 7, 8},
19+
Iterations: 2048,
20+
})
21+
alg := pkix.AlgorithmIdentifier{
22+
Algorithm: asn1.ObjectIdentifier([]int{1, 2, 3}),
23+
Parameters: asn1.RawValue{
24+
FullBytes: params,
25+
},
26+
}
27+
28+
pass, _ := bmpString("Sesame open")
29+
30+
_, _, err := pbDecrypterFor(alg, pass)
31+
if _, ok := err.(NotImplementedError); !ok {
32+
t.Errorf("expected not implemented error, got: %T %s", err, err)
33+
}
34+
35+
alg.Algorithm = sha1WithTripleDES
36+
cbc, blockSize, err := pbDecrypterFor(alg, pass)
37+
if err != nil {
38+
t.Errorf("unexpected error from pbDecrypterFor %v", err)
39+
}
40+
if blockSize != 8 {
41+
t.Errorf("unexpected block size %d, wanted 8", blockSize)
42+
}
43+
44+
plaintext := []byte{1, 2, 3, 4, 5, 6, 7, 8}
45+
expectedCiphertext := []byte{185, 73, 135, 249, 137, 1, 122, 247}
46+
ciphertext := make([]byte, len(plaintext))
47+
cbc.CryptBlocks(ciphertext, plaintext)
48+
49+
if bytes.Compare(ciphertext, expectedCiphertext) != 0 {
50+
t.Errorf("bad ciphertext, got %x but wanted %x", ciphertext, expectedCiphertext)
51+
}
52+
}
53+
54+
var pbDecryptTests = []struct {
55+
in []byte
56+
expected []byte
57+
expectedError error
58+
}{
59+
{
60+
[]byte("\x33\x73\xf3\x9f\xda\x49\xae\xfc\xa0\x9a\xdf\x5a\x58\xa0\xea\x46"), // 7 padding bytes
61+
[]byte("A secret!"),
62+
nil,
63+
},
64+
{
65+
[]byte("\x33\x73\xf3\x9f\xda\x49\xae\xfc\x96\x24\x2f\x71\x7e\x32\x3f\xe7"), // 8 padding bytes
66+
[]byte("A secret"),
67+
nil,
68+
},
69+
{
70+
[]byte("\x35\x0c\xc0\x8d\xab\xa9\x5d\x30\x7f\x9a\xec\x6a\xd8\x9b\x9c\xd9"), // 9 padding bytes, incorrect
71+
nil,
72+
ErrDecryption,
73+
},
74+
{
75+
[]byte("\xb2\xf9\x6e\x06\x60\xae\x20\xcf\x08\xa0\x7b\xd9\x6b\x20\xef\x41"), // incorrect padding bytes: [ ... 0x04 0x02 ]
76+
nil,
77+
ErrDecryption,
78+
},
79+
}
80+
81+
func TestPbDecrypt(t *testing.T) {
82+
for i, test := range pbDecryptTests {
83+
decryptable := testDecryptable{
84+
data: test.in,
85+
algorithm: pkix.AlgorithmIdentifier{
86+
Algorithm: sha1WithTripleDES,
87+
Parameters: pbeParams{
88+
Salt: []byte("\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8"),
89+
Iterations: 4096,
90+
}.RawASN1(),
91+
},
92+
}
93+
password, _ := bmpString("sesame")
94+
95+
plaintext, err := pbDecrypt(decryptable, password)
96+
if err != test.expectedError {
97+
t.Errorf("#%d: got error %q, but wanted %q", i, err, test.expectedError)
98+
continue
99+
}
100+
101+
if !bytes.Equal(plaintext, test.expected) {
102+
t.Errorf("#%d: got %x, but wanted %x", i, plaintext, test.expected)
103+
}
104+
}
105+
}
106+
107+
type testDecryptable struct {
108+
data []byte
109+
algorithm pkix.AlgorithmIdentifier
110+
}
111+
112+
func (d testDecryptable) Algorithm() pkix.AlgorithmIdentifier { return d.algorithm }
113+
func (d testDecryptable) Data() []byte { return d.data }
114+
115+
func (params pbeParams) RawASN1() (raw asn1.RawValue) {
116+
asn1Bytes, err := asn1.Marshal(params)
117+
if err != nil {
118+
panic(err)
119+
}
120+
_, err = asn1.Unmarshal(asn1Bytes, &raw)
121+
if err != nil {
122+
panic(err)
123+
}
124+
return
125+
}

pkcs12/errors.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Copyright 2015 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package pkcs12
6+
7+
import "errors"
8+
9+
var (
10+
// ErrDecryption represents a failure to decrypt the input.
11+
ErrDecryption = errors.New("pkcs12: decryption error, incorrect padding")
12+
13+
// ErrIncorrectPassword is returned when an incorrect password is detected.
14+
// Usually, P12/PFX data is signed to be able to verify the password.
15+
ErrIncorrectPassword = errors.New("pkcs12: decryption password incorrect")
16+
)
17+
18+
// NotImplementedError indicates that the input is not currently supported.
19+
type NotImplementedError string
20+
21+
func (e NotImplementedError) Error() string {
22+
return "pkcs12: " + string(e)
23+
}

0 commit comments

Comments
 (0)