-
Notifications
You must be signed in to change notification settings - Fork 2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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]>
- Loading branch information
Showing
15 changed files
with
1,587 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
// 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" | ||
) | ||
|
||
// bmpString returns s encoded in UCS-2 with a zero terminator. | ||
func bmpString(s string) ([]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. | ||
|
||
ret := make([]byte, 0, 2*len(s)+2) | ||
|
||
for _, r := range s { | ||
if t, _ := utf16.EncodeRune(r); t != 0xfffd { | ||
return nil, errors.New("pkcs12: string contains characters that cannot be encoded in UCS-2") | ||
} | ||
ret = append(ret, byte(r/256), byte(r%256)) | ||
} | ||
|
||
return append(ret, 0, 0), nil | ||
} | ||
|
||
func decodeBMPString(bmpString []byte) (string, error) { | ||
if len(bmpString)%2 != 0 { | ||
return "", errors.New("pkcs12: odd-length BMP string") | ||
} | ||
|
||
// strip terminator if present | ||
if l := len(bmpString); l >= 2 && bmpString[l-1] == 0 && bmpString[l-2] == 0 { | ||
bmpString = bmpString[:l-2] | ||
} | ||
|
||
s := make([]uint16, 0, len(bmpString)/2) | ||
for len(bmpString) > 0 { | ||
s = append(s, uint16(bmpString[0])<<8+uint16(bmpString[1])) | ||
bmpString = bmpString[2:] | ||
} | ||
|
||
return string(utf16.Decode(s)), nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
// Copyright 2015 The Go Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style | ||
// license that can be found in the LICENSE file. | ||
|
||
package pkcs12 | ||
|
||
import ( | ||
"bytes" | ||
"encoding/hex" | ||
"testing" | ||
) | ||
|
||
var bmpStringTests = []struct { | ||
in string | ||
expectedHex string | ||
shouldFail bool | ||
}{ | ||
{"", "0000", false}, | ||
// Example from https://tools.ietf.org/html/rfc7292#appendix-B. | ||
{"Beavis", "0042006500610076006900730000", false}, | ||
// Some characters from the "Letterlike Symbols Unicode block". | ||
{"\u2115 - Double-struck N", "21150020002d00200044006f00750062006c0065002d00730074007200750063006b0020004e0000", false}, | ||
// any character outside the BMP should trigger an error. | ||
{"\U0001f000 East wind (Mahjong)", "", true}, | ||
} | ||
|
||
func TestBMPString(t *testing.T) { | ||
for i, test := range bmpStringTests { | ||
expected, err := hex.DecodeString(test.expectedHex) | ||
if err != nil { | ||
t.Fatalf("#%d: failed to decode expectation", i) | ||
} | ||
|
||
out, err := bmpString(test.in) | ||
if err == nil && test.shouldFail { | ||
t.Errorf("#%d: expected to fail, but produced %x", i, out) | ||
continue | ||
} | ||
|
||
if err != nil && !test.shouldFail { | ||
t.Errorf("#%d: failed unexpectedly: %s", i, err) | ||
continue | ||
} | ||
|
||
if !test.shouldFail { | ||
if !bytes.Equal(out, expected) { | ||
t.Errorf("#%d: expected %s, got %x", i, test.expectedHex, out) | ||
continue | ||
} | ||
|
||
roundTrip, err := decodeBMPString(out) | ||
if err != nil { | ||
t.Errorf("#%d: decoding output gave an error: %s", i, err) | ||
continue | ||
} | ||
|
||
if roundTrip != test.in { | ||
t.Errorf("#%d: decoding output resulted in %q, but it should have been %q", i, roundTrip, test.in) | ||
continue | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
// 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" | ||
"errors" | ||
|
||
"golang.org/x/crypto/pkcs12/internal/rc2" | ||
) | ||
|
||
var ( | ||
oidPBEWithSHAAnd3KeyTripleDESCBC = asn1.ObjectIdentifier([]int{1, 2, 840, 113549, 1, 12, 1, 3}) | ||
oidPBEWithSHAAnd40BitRC2CBC = asn1.ObjectIdentifier([]int{1, 2, 840, 113549, 1, 12, 1, 6}) | ||
) | ||
|
||
// pbeCipher is an abstraction of a PKCS#12 cipher. | ||
type pbeCipher interface { | ||
// create returns a cipher.Block given a key. | ||
create(key []byte) (cipher.Block, error) | ||
// deriveKey returns a key derived from the given password and salt. | ||
deriveKey(salt, password []byte, iterations int) []byte | ||
// deriveKey returns an IV derived from the given password and salt. | ||
deriveIV(salt, password []byte, iterations int) []byte | ||
} | ||
|
||
type shaWithTripleDESCBC struct{} | ||
|
||
func (shaWithTripleDESCBC) create(key []byte) (cipher.Block, error) { | ||
return des.NewTripleDESCipher(key) | ||
} | ||
|
||
func (shaWithTripleDESCBC) deriveKey(salt, password []byte, iterations int) []byte { | ||
return pbkdf(sha1Sum, 20, 64, salt, password, iterations, 1, 24) | ||
} | ||
|
||
func (shaWithTripleDESCBC) deriveIV(salt, password []byte, iterations int) []byte { | ||
return pbkdf(sha1Sum, 20, 64, salt, password, iterations, 2, 8) | ||
} | ||
|
||
type shaWith40BitRC2CBC struct{} | ||
|
||
func (shaWith40BitRC2CBC) create(key []byte) (cipher.Block, error) { | ||
return rc2.New(key, len(key)*8) | ||
} | ||
|
||
func (shaWith40BitRC2CBC) deriveKey(salt, password []byte, iterations int) []byte { | ||
return pbkdf(sha1Sum, 20, 64, salt, password, iterations, 1, 5) | ||
} | ||
|
||
func (shaWith40BitRC2CBC) deriveIV(salt, password []byte, iterations int) []byte { | ||
return pbkdf(sha1Sum, 20, 64, salt, password, iterations, 2, 8) | ||
} | ||
|
||
type pbeParams struct { | ||
Salt []byte | ||
Iterations int | ||
} | ||
|
||
func pbDecrypterFor(algorithm pkix.AlgorithmIdentifier, password []byte) (cipher.BlockMode, int, error) { | ||
var cipherType pbeCipher | ||
|
||
switch { | ||
case algorithm.Algorithm.Equal(oidPBEWithSHAAnd3KeyTripleDESCBC): | ||
cipherType = shaWithTripleDESCBC{} | ||
case algorithm.Algorithm.Equal(oidPBEWithSHAAnd40BitRC2CBC): | ||
cipherType = shaWith40BitRC2CBC{} | ||
default: | ||
return nil, 0, NotImplementedError("algorithm " + algorithm.Algorithm.String() + " is not supported") | ||
} | ||
|
||
var params pbeParams | ||
if err := unmarshal(algorithm.Parameters.FullBytes, ¶ms); err != nil { | ||
return nil, 0, err | ||
} | ||
|
||
key := cipherType.deriveKey(params.Salt, password, params.Iterations) | ||
iv := cipherType.deriveIV(params.Salt, password, params.Iterations) | ||
|
||
block, err := cipherType.create(key) | ||
if err != nil { | ||
return nil, 0, err | ||
} | ||
|
||
return cipher.NewCBCDecrypter(block, iv), block.BlockSize(), nil | ||
} | ||
|
||
func pbDecrypt(info decryptable, password []byte) (decrypted []byte, err error) { | ||
cbc, blockSize, err := pbDecrypterFor(info.Algorithm(), password) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
encrypted := info.Data() | ||
if len(encrypted) == 0 { | ||
return nil, errors.New("pkcs12: empty encrypted data") | ||
} | ||
if len(encrypted)%blockSize != 0 { | ||
return nil, errors.New("pkcs12: input is not a multiple of the block size") | ||
} | ||
decrypted = make([]byte, len(encrypted)) | ||
cbc.CryptBlocks(decrypted, encrypted) | ||
|
||
psLen := int(decrypted[len(decrypted)-1]) | ||
if psLen == 0 || psLen > blockSize { | ||
return nil, ErrDecryption | ||
} | ||
|
||
if len(decrypted) < psLen { | ||
return nil, ErrDecryption | ||
} | ||
ps := decrypted[len(decrypted)-psLen:] | ||
decrypted = decrypted[:len(decrypted)-psLen] | ||
if bytes.Compare(ps, bytes.Repeat([]byte{byte(psLen)}, psLen)) != 0 { | ||
return nil, ErrDecryption | ||
} | ||
|
||
return | ||
} | ||
|
||
// decryptable abstracts a object that contains ciphertext. | ||
type decryptable interface { | ||
Algorithm() pkix.AlgorithmIdentifier | ||
Data() []byte | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
// 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" | ||
) | ||
|
||
var sha1WithTripleDES = asn1.ObjectIdentifier([]int{1, 2, 840, 113549, 1, 12, 1, 3}) | ||
|
||
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("Sesame open") | ||
|
||
_, _, err := pbDecrypterFor(alg, pass) | ||
if _, ok := err.(NotImplementedError); !ok { | ||
t.Errorf("expected not implemented error, got: %T %s", err, err) | ||
} | ||
|
||
alg.Algorithm = sha1WithTripleDES | ||
cbc, blockSize, err := pbDecrypterFor(alg, pass) | ||
if err != nil { | ||
t.Errorf("unexpected error from pbDecrypterFor %v", err) | ||
} | ||
if blockSize != 8 { | ||
t.Errorf("unexpected block size %d, wanted 8", blockSize) | ||
} | ||
|
||
plaintext := []byte{1, 2, 3, 4, 5, 6, 7, 8} | ||
expectedCiphertext := []byte{185, 73, 135, 249, 137, 1, 122, 247} | ||
ciphertext := make([]byte, len(plaintext)) | ||
cbc.CryptBlocks(ciphertext, plaintext) | ||
|
||
if bytes.Compare(ciphertext, expectedCiphertext) != 0 { | ||
t.Errorf("bad ciphertext, got %x but wanted %x", ciphertext, expectedCiphertext) | ||
} | ||
} | ||
|
||
var pbDecryptTests = []struct { | ||
in []byte | ||
expected []byte | ||
expectedError error | ||
}{ | ||
{ | ||
[]byte("\x33\x73\xf3\x9f\xda\x49\xae\xfc\xa0\x9a\xdf\x5a\x58\xa0\xea\x46"), // 7 padding bytes | ||
[]byte("A secret!"), | ||
nil, | ||
}, | ||
{ | ||
[]byte("\x33\x73\xf3\x9f\xda\x49\xae\xfc\x96\x24\x2f\x71\x7e\x32\x3f\xe7"), // 8 padding bytes | ||
[]byte("A secret"), | ||
nil, | ||
}, | ||
{ | ||
[]byte("\x35\x0c\xc0\x8d\xab\xa9\x5d\x30\x7f\x9a\xec\x6a\xd8\x9b\x9c\xd9"), // 9 padding bytes, incorrect | ||
nil, | ||
ErrDecryption, | ||
}, | ||
{ | ||
[]byte("\xb2\xf9\x6e\x06\x60\xae\x20\xcf\x08\xa0\x7b\xd9\x6b\x20\xef\x41"), // incorrect padding bytes: [ ... 0x04 0x02 ] | ||
nil, | ||
ErrDecryption, | ||
}, | ||
} | ||
|
||
func TestPbDecrypt(t *testing.T) { | ||
for i, test := range pbDecryptTests { | ||
decryptable := testDecryptable{ | ||
data: test.in, | ||
algorithm: pkix.AlgorithmIdentifier{ | ||
Algorithm: sha1WithTripleDES, | ||
Parameters: pbeParams{ | ||
Salt: []byte("\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8"), | ||
Iterations: 4096, | ||
}.RawASN1(), | ||
}, | ||
} | ||
password, _ := bmpString("sesame") | ||
|
||
plaintext, err := pbDecrypt(decryptable, password) | ||
if err != test.expectedError { | ||
t.Errorf("#%d: got error %q, but wanted %q", i, err, test.expectedError) | ||
continue | ||
} | ||
|
||
if !bytes.Equal(plaintext, test.expected) { | ||
t.Errorf("#%d: got %x, but wanted %x", i, plaintext, test.expected) | ||
} | ||
} | ||
} | ||
|
||
type testDecryptable struct { | ||
data []byte | ||
algorithm pkix.AlgorithmIdentifier | ||
} | ||
|
||
func (d testDecryptable) Algorithm() pkix.AlgorithmIdentifier { return d.algorithm } | ||
func (d testDecryptable) Data() []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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
// Copyright 2015 The Go Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style | ||
// license that can be found in the LICENSE file. | ||
|
||
package pkcs12 | ||
|
||
import "errors" | ||
|
||
var ( | ||
// ErrDecryption represents a failure to decrypt the input. | ||
ErrDecryption = errors.New("pkcs12: decryption error, incorrect padding") | ||
|
||
// ErrIncorrectPassword is returned when an incorrect password is detected. | ||
// Usually, P12/PFX data is signed to be able to verify the password. | ||
ErrIncorrectPassword = errors.New("pkcs12: decryption password incorrect") | ||
) | ||
|
||
// NotImplementedError indicates that the input is not currently supported. | ||
type NotImplementedError string | ||
|
||
func (e NotImplementedError) Error() string { | ||
return "pkcs12: " + string(e) | ||
} |
Oops, something went wrong.