Index: src/pkg/crypto/x509/pem_decrypt.go |
=================================================================== |
--- a/src/pkg/crypto/x509/pem_decrypt.go |
+++ b/src/pkg/crypto/x509/pem_decrypt.go |
@@ -16,23 +16,64 @@ |
"encoding/hex" |
"encoding/pem" |
"errors" |
+ "io" |
"strings" |
) |
-// rfc1423Algos represents how to create a block cipher for a decryption mode. |
+type PEMCipher int |
+ |
+// Possible values for the EncryptPEMBlock encryption algorithm. |
+const ( |
+ _ PEMCipher = iota |
+ PEMCipherDES |
+ PEMCipher3DES |
+ PEMCipherAES128 |
+ PEMCipherAES192 |
+ PEMCipherAES256 |
+) |
+ |
+// rfc1423Algo holds a method for enciphering a PEM block. |
type rfc1423Algo struct { |
- cipherFunc func([]byte) (cipher.Block, error) |
+ cipher PEMCipher |
+ name string |
+ cipherFunc func(key []byte) (cipher.Block, error) |
keySize int |
+ blockSize int |
} |
-// rfc1423Algos is a mapping of encryption algorithm to an rfc1423Algo that can |
-// create block ciphers for that mode. |
-var rfc1423Algos = map[string]rfc1423Algo{ |
- "DES-CBC": {des.NewCipher, 8}, |
- "DES-EDE3-CBC": {des.NewTripleDESCipher, 24}, |
- "AES-128-CBC": {aes.NewCipher, 16}, |
- "AES-192-CBC": {aes.NewCipher, 24}, |
- "AES-256-CBC": {aes.NewCipher, 32}, |
+// rfc1423Algos holds a slice of the possible ways to encrypt a PEM |
+// block. The ivSize numbers were taken from the OpenSSL source. |
+var rfc1423Algos = []rfc1423Algo{{ |
+ cipher: PEMCipherDES, |
+ name: "DES-CBC", |
+ cipherFunc: des.NewCipher, |
+ keySize: 8, |
+ blockSize: des.BlockSize, |
+}, { |
+ cipher: PEMCipher3DES, |
+ name: "DES-EDE3-CBC", |
+ cipherFunc: des.NewTripleDESCipher, |
+ keySize: 24, |
+ blockSize: des.BlockSize, |
+}, { |
+ cipher: PEMCipherAES128, |
+ name: "AES-128-CBC", |
+ cipherFunc: aes.NewCipher, |
+ keySize: 16, |
+ blockSize: aes.BlockSize, |
+}, { |
+ cipher: PEMCipherAES192, |
+ name: "AES-192-CBC", |
+ cipherFunc: aes.NewCipher, |
+ keySize: 24, |
+ blockSize: aes.BlockSize, |
+}, { |
+ cipher: PEMCipherAES256, |
+ name: "AES-256-CBC", |
+ cipherFunc: aes.NewCipher, |
+ keySize: 32, |
+ blockSize: aes.BlockSize, |
+}, |
} |
// deriveKey uses a key derivation function to stretch the password into a key |
@@ -51,7 +92,6 @@ |
digest = hash.Sum(digest[:0]) |
copy(out[i:], digest) |
} |
- |
return out |
} |
@@ -81,16 +121,16 @@ |
} |
mode, hexIV := dek[:idx], dek[idx+1:] |
- ciph, ok := rfc1423Algos[mode] |
- if !ok { |
+ ciph := cipherByName(mode) |
+ if ciph == nil { |
return nil, errors.New("x509: unknown encryption mode") |
} |
iv, err := hex.DecodeString(hexIV) |
if err != nil { |
return nil, err |
} |
- if len(iv) < 8 { |
- return nil, errors.New("x509: not enough bytes in IV") |
+ if len(iv) != ciph.blockSize { |
+ return nil, errors.New("x509: incorrect IV size") |
} |
// Based on the OpenSSL implementation. The salt is the first 8 bytes |
@@ -112,15 +152,14 @@ |
// [x y 7 7 7 7 7 7 7] |
// If we detect a bad padding, we assume it is an invalid password. |
dlen := len(data) |
- blockSize := block.BlockSize() |
- if dlen == 0 || dlen%blockSize != 0 { |
+ if dlen == 0 || dlen%ciph.blockSize != 0 { |
return nil, errors.New("x509: invalid padding") |
} |
last := int(data[dlen-1]) |
if dlen < last { |
return nil, IncorrectPasswordError |
} |
- if last == 0 || last > blockSize { |
+ if last == 0 || last > ciph.blockSize { |
return nil, IncorrectPasswordError |
} |
for _, val := range data[dlen-last:] { |
@@ -130,3 +169,65 @@ |
} |
return data[:dlen-last], nil |
} |
+ |
+// EncryptPEMBlock returns a PEM block of the specified type holding the |
+// given DER-encoded data encrypted with the specified algorithm and |
+// password. |
+func EncryptPEMBlock(rand io.Reader, blockType string, data, password []byte, alg PEMCipher) (*pem.Block, error) { |
+ ciph := cipherByKey(alg) |
+ if ciph == nil { |
+ return nil, errors.New("x509: unknown encryption mode") |
+ } |
+ iv := make([]byte, ciph.blockSize) |
+ if _, err := io.ReadFull(rand, iv); err != nil { |
+ return nil, errors.New("x509: cannot generate IV: " + err.Error()) |
+ } |
+ // The salt is the first 8 bytes of the initialization vector, |
+ // matching the key derivation in DecryptPEMBlock. |
+ key := ciph.deriveKey(password, iv[:8]) |
+ block, err := ciph.cipherFunc(key) |
+ if err != nil { |
+ return nil, err |
+ } |
+ enc := cipher.NewCBCEncrypter(block, iv) |
+ pad := ciph.blockSize - len(data)%ciph.blockSize |
+ encrypted := make([]byte, len(data), len(data)+pad) |
+ // We could save this copy by encrypting all the whole blocks in |
+ // the data separately, but it doesn't seem worth the additional |
+ // code. |
+ copy(encrypted, data) |
+ // See RFC 1423, section 1.1 |
+ for i := 0; i < pad; i++ { |
+ encrypted = append(encrypted, byte(pad)) |
+ } |
+ enc.CryptBlocks(encrypted, encrypted) |
+ |
+ return &pem.Block{ |
+ Type: blockType, |
+ Headers: map[string]string{ |
+ "Proc-Type": "4,ENCRYPTED", |
+ "DEK-Info": ciph.name + "," + hex.EncodeToString(iv), |
+ }, |
+ Bytes: encrypted, |
+ }, nil |
+} |
+ |
+func cipherByName(name string) *rfc1423Algo { |
+ for i := range rfc1423Algos { |
+ alg := &rfc1423Algos[i] |
+ if alg.name == name { |
+ return alg |
+ } |
+ } |
+ return nil |
+} |
+ |
+func cipherByKey(key PEMCipher) *rfc1423Algo { |
+ for i := range rfc1423Algos { |
+ alg := &rfc1423Algos[i] |
+ if alg.cipher == key { |
+ return alg |
+ } |
+ } |
+ return nil |
+} |