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