| // Copyright 2012 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 x509 | |
| // RFC 1423 describes the encryption of PEM blocks. The algorithm used to | |
| // generate a key from the password was derived by looking at the OpenSSL | |
| // implementation. | |
| import ( | |
| "crypto/aes" | |
| "crypto/cipher" | |
| "crypto/des" | |
| "crypto/md5" | |
| "encoding/hex" | |
| "encoding/pem" | |
| "errors" | |
| "io" | |
| "strings" | |
| ) | |
| 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 { | |
| cipher PEMCipher | |
| name string | |
| cipherFunc func(key []byte) (cipher.Block, error) | |
| keySize int | |
| blockSize int | |
| } | |
| // 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 | |
| // with the number of bits our cipher requires. This algorithm was derived from | |
| // the OpenSSL source. | |
| func (c rfc1423Algo) deriveKey(password, salt []byte) []byte { | |
| hash := md5.New() | |
| out := make([]byte, c.keySize) | |
| var digest []byte | |
| for i := 0; i < len(out); i += len(digest) { | |
| hash.Reset() | |
| hash.Write(digest) | |
| hash.Write(password) | |
| hash.Write(salt) | |
| digest = hash.Sum(digest[:0]) | |
| copy(out[i:], digest) | |
| } | |
| return out | |
| } | |
| // IsEncryptedPEMBlock returns whether the PEM block is password encrypted | |
| // according to RFC 1423. | |
| // | |
| // Deprecated: Legacy PEM encryption as specified in RFC 1423 is insecure by | |
| // design. Since it does not authenticate the ciphertext, it is vulnerable to | |
| // padding oracle attacks that can let an attacker recover the plaintext. | |
| func IsEncryptedPEMBlock(b *pem.Block) bool { | |
| _, ok := b.Headers["DEK-Info"] | |
| return ok | |
| } | |
| // IncorrectPasswordError is returned when an incorrect password is detected. | |
| var IncorrectPasswordError = errors.New("x509: decryption password incorrect") | |
| // DecryptPEMBlock takes a PEM block encrypted according to RFC 1423 and the | |
| // password used to encrypt it and returns a slice of decrypted DER encoded | |
| // bytes. It inspects the DEK-Info header to determine the algorithm used for | |
| // decryption. If no DEK-Info header is present, an error is returned. If an | |
| // incorrect password is detected an IncorrectPasswordError is returned. Because | |
| // of deficiencies in the format, it's not always possible to detect an | |
| // incorrect password. In these cases no error will be returned but the | |
| // decrypted DER bytes will be random noise. | |
| // | |
| // Deprecated: Legacy PEM encryption as specified in RFC 1423 is insecure by | |
| // design. Since it does not authenticate the ciphertext, it is vulnerable to | |
| // padding oracle attacks that can let an attacker recover the plaintext. | |
| func DecryptPEMBlock(b *pem.Block, password []byte) ([]byte, error) { | |
| dek, ok := b.Headers["DEK-Info"] | |
| if !ok { | |
| return nil, errors.New("x509: no DEK-Info header in block") | |
| } | |
| idx := strings.Index(dek, ",") | |
| if idx == -1 { | |
| return nil, errors.New("x509: malformed DEK-Info header") | |
| } | |
| mode, hexIV := dek[:idx], dek[idx+1:] | |
| 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) != ciph.blockSize { | |
| return nil, errors.New("x509: incorrect IV size") | |
| } | |
| // Based on the OpenSSL implementation. The salt is the first 8 bytes | |
| // of the initialization vector. | |
| key := ciph.deriveKey(password, iv[:8]) | |
| block, err := ciph.cipherFunc(key) | |
| if err != nil { | |
| return nil, err | |
| } | |
| if len(b.Bytes)%block.BlockSize() != 0 { | |
| return nil, errors.New("x509: encrypted PEM data is not a multiple of the block size") | |
| } | |
| data := make([]byte, len(b.Bytes)) | |
| dec := cipher.NewCBCDecrypter(block, iv) | |
| dec.CryptBlocks(data, b.Bytes) | |
| // Blocks are padded using a scheme where the last n bytes of padding are all | |
| // equal to n. It can pad from 1 to blocksize bytes inclusive. See RFC 1423. | |
| // For example: | |
| // [x y z 2 2] | |
| // [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) | |
| 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 > ciph.blockSize { | |
| return nil, IncorrectPasswordError | |
| } | |
| for _, val := range data[dlen-last:] { | |
| if int(val) != last { | |
| return nil, IncorrectPasswordError | |
| } | |
| } | |
| 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 according to RFC 1423. | |
| // | |
| // Deprecated: Legacy PEM encryption as specified in RFC 1423 is insecure by | |
| // design. Since it does not authenticate the ciphertext, it is vulnerable to | |
| // padding oracle attacks that can let an attacker recover the plaintext. | |
| 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 | |
| } |