...

Source file src/crypto/x509/pem_decrypt.go

Documentation: crypto/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 whether the PEM block is password encrypted
    99  // according to RFC 1423.
   100  //
   101  // Deprecated: Legacy PEM encryption as specified in RFC 1423 is insecure by
   102  // design. Since it does not authenticate the ciphertext, it is vulnerable to
   103  // padding oracle attacks that can let an attacker recover the plaintext.
   104  func IsEncryptedPEMBlock(b *pem.Block) bool {
   105  	_, ok := b.Headers["DEK-Info"]
   106  	return ok
   107  }
   108  
   109  // IncorrectPasswordError is returned when an incorrect password is detected.
   110  var IncorrectPasswordError = errors.New("x509: decryption password incorrect")
   111  
   112  // DecryptPEMBlock takes a PEM block encrypted according to RFC 1423 and the
   113  // password used to encrypt it and returns a slice of decrypted DER encoded
   114  // bytes. It inspects the DEK-Info header to determine the algorithm used for
   115  // decryption. If no DEK-Info header is present, an error is returned. If an
   116  // incorrect password is detected an [IncorrectPasswordError] is returned. Because
   117  // of deficiencies in the format, it's not always possible to detect an
   118  // incorrect password. In these cases no error will be returned but the
   119  // decrypted DER bytes will be random noise.
   120  //
   121  // Deprecated: Legacy PEM encryption as specified in RFC 1423 is insecure by
   122  // design. Since it does not authenticate the ciphertext, it is vulnerable to
   123  // padding oracle attacks that can let an attacker recover the plaintext.
   124  func DecryptPEMBlock(b *pem.Block, password []byte) ([]byte, error) {
   125  	dek, ok := b.Headers["DEK-Info"]
   126  	if !ok {
   127  		return nil, errors.New("x509: no DEK-Info header in block")
   128  	}
   129  
   130  	mode, hexIV, ok := strings.Cut(dek, ",")
   131  	if !ok {
   132  		return nil, errors.New("x509: malformed DEK-Info header")
   133  	}
   134  
   135  	ciph := cipherByName(mode)
   136  	if ciph == nil {
   137  		return nil, errors.New("x509: unknown encryption mode")
   138  	}
   139  	iv, err := hex.DecodeString(hexIV)
   140  	if err != nil {
   141  		return nil, err
   142  	}
   143  	if len(iv) != ciph.blockSize {
   144  		return nil, errors.New("x509: incorrect IV size")
   145  	}
   146  
   147  	// Based on the OpenSSL implementation. The salt is the first 8 bytes
   148  	// of the initialization vector.
   149  	key := ciph.deriveKey(password, iv[:8])
   150  	block, err := ciph.cipherFunc(key)
   151  	if err != nil {
   152  		return nil, err
   153  	}
   154  
   155  	if len(b.Bytes)%block.BlockSize() != 0 {
   156  		return nil, errors.New("x509: encrypted PEM data is not a multiple of the block size")
   157  	}
   158  
   159  	data := make([]byte, len(b.Bytes))
   160  	dec := cipher.NewCBCDecrypter(block, iv)
   161  	dec.CryptBlocks(data, b.Bytes)
   162  
   163  	// Blocks are padded using a scheme where the last n bytes of padding are all
   164  	// equal to n. It can pad from 1 to blocksize bytes inclusive. See RFC 1423.
   165  	// For example:
   166  	//	[x y z 2 2]
   167  	//	[x y 7 7 7 7 7 7 7]
   168  	// If we detect a bad padding, we assume it is an invalid password.
   169  	dlen := len(data)
   170  	if dlen == 0 || dlen%ciph.blockSize != 0 {
   171  		return nil, errors.New("x509: invalid padding")
   172  	}
   173  	last := int(data[dlen-1])
   174  	if dlen < last {
   175  		return nil, IncorrectPasswordError
   176  	}
   177  	if last == 0 || last > ciph.blockSize {
   178  		return nil, IncorrectPasswordError
   179  	}
   180  	for _, val := range data[dlen-last:] {
   181  		if int(val) != last {
   182  			return nil, IncorrectPasswordError
   183  		}
   184  	}
   185  	return data[:dlen-last], nil
   186  }
   187  
   188  // EncryptPEMBlock returns a PEM block of the specified type holding the
   189  // given DER encoded data encrypted with the specified algorithm and
   190  // password according to RFC 1423.
   191  //
   192  // Deprecated: Legacy PEM encryption as specified in RFC 1423 is insecure by
   193  // design. Since it does not authenticate the ciphertext, it is vulnerable to
   194  // padding oracle attacks that can let an attacker recover the plaintext.
   195  func EncryptPEMBlock(rand io.Reader, blockType string, data, password []byte, alg PEMCipher) (*pem.Block, error) {
   196  	ciph := cipherByKey(alg)
   197  	if ciph == nil {
   198  		return nil, errors.New("x509: unknown encryption mode")
   199  	}
   200  	iv := make([]byte, ciph.blockSize)
   201  	if _, err := io.ReadFull(rand, iv); err != nil {
   202  		return nil, errors.New("x509: cannot generate IV: " + err.Error())
   203  	}
   204  	// The salt is the first 8 bytes of the initialization vector,
   205  	// matching the key derivation in DecryptPEMBlock.
   206  	key := ciph.deriveKey(password, iv[:8])
   207  	block, err := ciph.cipherFunc(key)
   208  	if err != nil {
   209  		return nil, err
   210  	}
   211  	enc := cipher.NewCBCEncrypter(block, iv)
   212  	pad := ciph.blockSize - len(data)%ciph.blockSize
   213  	encrypted := make([]byte, len(data), len(data)+pad)
   214  	// We could save this copy by encrypting all the whole blocks in
   215  	// the data separately, but it doesn't seem worth the additional
   216  	// code.
   217  	copy(encrypted, data)
   218  	// See RFC 1423, Section 1.1.
   219  	for i := 0; i < pad; i++ {
   220  		encrypted = append(encrypted, byte(pad))
   221  	}
   222  	enc.CryptBlocks(encrypted, encrypted)
   223  
   224  	return &pem.Block{
   225  		Type: blockType,
   226  		Headers: map[string]string{
   227  			"Proc-Type": "4,ENCRYPTED",
   228  			"DEK-Info":  ciph.name + "," + hex.EncodeToString(iv),
   229  		},
   230  		Bytes: encrypted,
   231  	}, nil
   232  }
   233  
   234  func cipherByName(name string) *rfc1423Algo {
   235  	for i := range rfc1423Algos {
   236  		alg := &rfc1423Algos[i]
   237  		if alg.name == name {
   238  			return alg
   239  		}
   240  	}
   241  	return nil
   242  }
   243  
   244  func cipherByKey(key PEMCipher) *rfc1423Algo {
   245  	for i := range rfc1423Algos {
   246  		alg := &rfc1423Algos[i]
   247  		if alg.cipher == key {
   248  			return alg
   249  		}
   250  	}
   251  	return nil
   252  }
   253  

View as plain text