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  }