// 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 (
	
	
	
	
	
	
	
	
	
)

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 ( rfc1423Algo) (,  []byte) []byte {
	 := md5.New()
	 := make([]byte, .keySize)
	var  []byte

	for  := 0;  < len();  += len() {
		.Reset()
		.Write()
		.Write()
		.Write()
		 = .Sum([:0])
		copy([:], )
	}
	return 
}

// 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 ( *pem.Block) bool {
	,  := .Headers["DEK-Info"]
	return 
}

// 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 ( *pem.Block,  []byte) ([]byte, error) {
	,  := .Headers["DEK-Info"]
	if ! {
		return nil, errors.New("x509: no DEK-Info header in block")
	}

	, ,  := strings.Cut(, ",")
	if ! {
		return nil, errors.New("x509: malformed DEK-Info header")
	}

	 := cipherByName()
	if  == nil {
		return nil, errors.New("x509: unknown encryption mode")
	}
	,  := hex.DecodeString()
	if  != nil {
		return nil, 
	}
	if len() != .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.
	 := .deriveKey(, [:8])
	,  := .cipherFunc()
	if  != nil {
		return nil, 
	}

	if len(.Bytes)%.BlockSize() != 0 {
		return nil, errors.New("x509: encrypted PEM data is not a multiple of the block size")
	}

	 := make([]byte, len(.Bytes))
	 := cipher.NewCBCDecrypter(, )
	.CryptBlocks(, .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.
	 := len()
	if  == 0 || %.blockSize != 0 {
		return nil, errors.New("x509: invalid padding")
	}
	 := int([-1])
	if  <  {
		return nil, IncorrectPasswordError
	}
	if  == 0 ||  > .blockSize {
		return nil, IncorrectPasswordError
	}
	for ,  := range [-:] {
		if int() !=  {
			return nil, IncorrectPasswordError
		}
	}
	return [:-], 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 ( io.Reader,  string, ,  []byte,  PEMCipher) (*pem.Block, error) {
	 := cipherByKey()
	if  == nil {
		return nil, errors.New("x509: unknown encryption mode")
	}
	 := make([]byte, .blockSize)
	if ,  := io.ReadFull(, );  != nil {
		return nil, errors.New("x509: cannot generate IV: " + .Error())
	}
	// The salt is the first 8 bytes of the initialization vector,
	// matching the key derivation in DecryptPEMBlock.
	 := .deriveKey(, [:8])
	,  := .cipherFunc()
	if  != nil {
		return nil, 
	}
	 := cipher.NewCBCEncrypter(, )
	 := .blockSize - len()%.blockSize
	 := make([]byte, len(), len()+)
	// 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(, )
	// See RFC 1423, Section 1.1.
	for  := 0;  < ; ++ {
		 = append(, byte())
	}
	.CryptBlocks(, )

	return &pem.Block{
		Type: ,
		Headers: map[string]string{
			"Proc-Type": "4,ENCRYPTED",
			"DEK-Info":  .name + "," + hex.EncodeToString(),
		},
		Bytes: ,
	}, nil
}

func cipherByName( string) *rfc1423Algo {
	for  := range rfc1423Algos {
		 := &rfc1423Algos[]
		if .name ==  {
			return 
		}
	}
	return nil
}

func cipherByKey( PEMCipher) *rfc1423Algo {
	for  := range rfc1423Algos {
		 := &rfc1423Algos[]
		if .cipher ==  {
			return 
		}
	}
	return nil
}