Home | History | Annotate | Download | only in x509
      1 // Copyright 2012 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 x509
      6 
      7 // RFC 1423 describes the encryption of PEM blocks. The algorithm used to
      8 // generate a key from the password was derived by looking at the OpenSSL
      9 // implementation.
     10 
     11 import (
     12 	"crypto/aes"
     13 	"crypto/cipher"
     14 	"crypto/des"
     15 	"crypto/md5"
     16 	"encoding/hex"
     17 	"encoding/pem"
     18 	"errors"
     19 	"io"
     20 	"strings"
     21 )
     22 
     23 type PEMCipher int
     24 
     25 // Possible values for the EncryptPEMBlock encryption algorithm.
     26 const (
     27 	_ PEMCipher = iota
     28 	PEMCipherDES
     29 	PEMCipher3DES
     30 	PEMCipherAES128
     31 	PEMCipherAES192
     32 	PEMCipherAES256
     33 )
     34 
     35 // rfc1423Algo holds a method for enciphering a PEM block.
     36 type rfc1423Algo struct {
     37 	cipher     PEMCipher
     38 	name       string
     39 	cipherFunc func(key []byte) (cipher.Block, error)
     40 	keySize    int
     41 	blockSize  int
     42 }
     43 
     44 // rfc1423Algos holds a slice of the possible ways to encrypt a PEM
     45 // block. The ivSize numbers were taken from the OpenSSL source.
     46 var rfc1423Algos = []rfc1423Algo{{
     47 	cipher:     PEMCipherDES,
     48 	name:       "DES-CBC",
     49 	cipherFunc: des.NewCipher,
     50 	keySize:    8,
     51 	blockSize:  des.BlockSize,
     52 }, {
     53 	cipher:     PEMCipher3DES,
     54 	name:       "DES-EDE3-CBC",
     55 	cipherFunc: des.NewTripleDESCipher,
     56 	keySize:    24,
     57 	blockSize:  des.BlockSize,
     58 }, {
     59 	cipher:     PEMCipherAES128,
     60 	name:       "AES-128-CBC",
     61 	cipherFunc: aes.NewCipher,
     62 	keySize:    16,
     63 	blockSize:  aes.BlockSize,
     64 }, {
     65 	cipher:     PEMCipherAES192,
     66 	name:       "AES-192-CBC",
     67 	cipherFunc: aes.NewCipher,
     68 	keySize:    24,
     69 	blockSize:  aes.BlockSize,
     70 }, {
     71 	cipher:     PEMCipherAES256,
     72 	name:       "AES-256-CBC",
     73 	cipherFunc: aes.NewCipher,
     74 	keySize:    32,
     75 	blockSize:  aes.BlockSize,
     76 },
     77 }
     78 
     79 // deriveKey uses a key derivation function to stretch the password into a key
     80 // with the number of bits our cipher requires. This algorithm was derived from
     81 // the OpenSSL source.
     82 func (c rfc1423Algo) deriveKey(password, salt []byte) []byte {
     83 	hash := md5.New()
     84 	out := make([]byte, c.keySize)
     85 	var digest []byte
     86 
     87 	for i := 0; i < len(out); i += len(digest) {
     88 		hash.Reset()
     89 		hash.Write(digest)
     90 		hash.Write(password)
     91 		hash.Write(salt)
     92 		digest = hash.Sum(digest[:0])
     93 		copy(out[i:], digest)
     94 	}
     95 	return out
     96 }
     97 
     98 // IsEncryptedPEMBlock returns if the PEM block is password encrypted.
     99 func IsEncryptedPEMBlock(b *pem.Block) bool {
    100 	_, ok := b.Headers["DEK-Info"]
    101 	return ok
    102 }
    103 
    104 // IncorrectPasswordError is returned when an incorrect password is detected.
    105 var IncorrectPasswordError = errors.New("x509: decryption password incorrect")
    106 
    107 // DecryptPEMBlock takes a password encrypted PEM block and the password used to
    108 // encrypt it and returns a slice of decrypted DER encoded bytes. It inspects
    109 // the DEK-Info header to determine the algorithm used for decryption. If no
    110 // DEK-Info header is present, an error is returned. If an incorrect password
    111 // is detected an IncorrectPasswordError is returned. Because of deficiencies
    112 // in the encrypted-PEM format, it's not always possible to detect an incorrect
    113 // password. In these cases no error will be returned but the decrypted DER
    114 // bytes will be random noise.
    115 func DecryptPEMBlock(b *pem.Block, password []byte) ([]byte, error) {
    116 	dek, ok := b.Headers["DEK-Info"]
    117 	if !ok {
    118 		return nil, errors.New("x509: no DEK-Info header in block")
    119 	}
    120 
    121 	idx := strings.Index(dek, ",")
    122 	if idx == -1 {
    123 		return nil, errors.New("x509: malformed DEK-Info header")
    124 	}
    125 
    126 	mode, hexIV := dek[:idx], dek[idx+1:]
    127 	ciph := cipherByName(mode)
    128 	if ciph == nil {
    129 		return nil, errors.New("x509: unknown encryption mode")
    130 	}
    131 	iv, err := hex.DecodeString(hexIV)
    132 	if err != nil {
    133 		return nil, err
    134 	}
    135 	if len(iv) != ciph.blockSize {
    136 		return nil, errors.New("x509: incorrect IV size")
    137 	}
    138 
    139 	// Based on the OpenSSL implementation. The salt is the first 8 bytes
    140 	// of the initialization vector.
    141 	key := ciph.deriveKey(password, iv[:8])
    142 	block, err := ciph.cipherFunc(key)
    143 	if err != nil {
    144 		return nil, err
    145 	}
    146 
    147 	if len(b.Bytes)%block.BlockSize() != 0 {
    148 		return nil, errors.New("x509: encrypted PEM data is not a multiple of the block size")
    149 	}
    150 
    151 	data := make([]byte, len(b.Bytes))
    152 	dec := cipher.NewCBCDecrypter(block, iv)
    153 	dec.CryptBlocks(data, b.Bytes)
    154 
    155 	// Blocks are padded using a scheme where the last n bytes of padding are all
    156 	// equal to n. It can pad from 1 to blocksize bytes inclusive. See RFC 1423.
    157 	// For example:
    158 	//	[x y z 2 2]
    159 	//	[x y 7 7 7 7 7 7 7]
    160 	// If we detect a bad padding, we assume it is an invalid password.
    161 	dlen := len(data)
    162 	if dlen == 0 || dlen%ciph.blockSize != 0 {
    163 		return nil, errors.New("x509: invalid padding")
    164 	}
    165 	last := int(data[dlen-1])
    166 	if dlen < last {
    167 		return nil, IncorrectPasswordError
    168 	}
    169 	if last == 0 || last > ciph.blockSize {
    170 		return nil, IncorrectPasswordError
    171 	}
    172 	for _, val := range data[dlen-last:] {
    173 		if int(val) != last {
    174 			return nil, IncorrectPasswordError
    175 		}
    176 	}
    177 	return data[:dlen-last], nil
    178 }
    179 
    180 // EncryptPEMBlock returns a PEM block of the specified type holding the
    181 // given DER-encoded data encrypted with the specified algorithm and
    182 // password.
    183 func EncryptPEMBlock(rand io.Reader, blockType string, data, password []byte, alg PEMCipher) (*pem.Block, error) {
    184 	ciph := cipherByKey(alg)
    185 	if ciph == nil {
    186 		return nil, errors.New("x509: unknown encryption mode")
    187 	}
    188 	iv := make([]byte, ciph.blockSize)
    189 	if _, err := io.ReadFull(rand, iv); err != nil {
    190 		return nil, errors.New("x509: cannot generate IV: " + err.Error())
    191 	}
    192 	// The salt is the first 8 bytes of the initialization vector,
    193 	// matching the key derivation in DecryptPEMBlock.
    194 	key := ciph.deriveKey(password, iv[:8])
    195 	block, err := ciph.cipherFunc(key)
    196 	if err != nil {
    197 		return nil, err
    198 	}
    199 	enc := cipher.NewCBCEncrypter(block, iv)
    200 	pad := ciph.blockSize - len(data)%ciph.blockSize
    201 	encrypted := make([]byte, len(data), len(data)+pad)
    202 	// We could save this copy by encrypting all the whole blocks in
    203 	// the data separately, but it doesn't seem worth the additional
    204 	// code.
    205 	copy(encrypted, data)
    206 	// See RFC 1423, section 1.1
    207 	for i := 0; i < pad; i++ {
    208 		encrypted = append(encrypted, byte(pad))
    209 	}
    210 	enc.CryptBlocks(encrypted, encrypted)
    211 
    212 	return &pem.Block{
    213 		Type: blockType,
    214 		Headers: map[string]string{
    215 			"Proc-Type": "4,ENCRYPTED",
    216 			"DEK-Info":  ciph.name + "," + hex.EncodeToString(iv),
    217 		},
    218 		Bytes: encrypted,
    219 	}, nil
    220 }
    221 
    222 func cipherByName(name string) *rfc1423Algo {
    223 	for i := range rfc1423Algos {
    224 		alg := &rfc1423Algos[i]
    225 		if alg.name == name {
    226 			return alg
    227 		}
    228 	}
    229 	return nil
    230 }
    231 
    232 func cipherByKey(key PEMCipher) *rfc1423Algo {
    233 	for i := range rfc1423Algos {
    234 		alg := &rfc1423Algos[i]
    235 		if alg.cipher == key {
    236 			return alg
    237 		}
    238 	}
    239 	return nil
    240 }
    241