blob: fe54f291fd169358d45f3fb5873e55afff395436 [file]
/** @file
PEM (Privacy Enhanced Mail) Format Handler Wrapper Implementation over OpenSSL.
Copyright (c) 2010 - 2020, Intel Corporation. All rights reserved.<BR>
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include "InternalCryptLib.h"
#include "KeyContext.h"
#include <openssl/pem.h>
/**
Callback function for password phrase conversion used for retrieving the encrypted PEM.
@param[out] Buf Pointer to the buffer to write the passphrase to.
@param[in] Size Maximum length of the passphrase (i.e. the size of Buf).
@param[in] Flag A flag which is set to 0 when reading and 1 when writing.
@param[in] Key Key data to be passed to the callback routine.
@retval The number of characters in the passphrase or 0 if an error occurred.
**/
INTN
PasswordCallback (
OUT CHAR8 *Buf,
IN INTN Size,
IN INTN Flag,
IN VOID *Key
)
{
INTN KeyLength;
ZeroMem ((VOID *)Buf, (UINTN)Size);
if (Key != NULL) {
//
// Duplicate key phrase directly.
//
KeyLength = (INTN)AsciiStrLen ((CHAR8 *)Key);
KeyLength = (KeyLength > Size) ? Size : KeyLength;
CopyMem (Buf, Key, (UINTN)KeyLength);
return KeyLength;
} else {
return 0;
}
}
/**
Retrieve a private key from PEM-encoded data using OpenSSL BIO.
This helper function creates a memory BIO, writes the PEM data to it, and reads
the private key using OpenSSL's PEM_read_bio_PrivateKey function. It supports
password-protected PEM data.
@param[in] PemData Pointer to the PEM-encoded key data.
@param[in] PemSize Size of the PEM key data in bytes.
@param[in] Password NULL-terminated passphrase used for encrypted PEM key data.
@param[out] Pkey Pointer to receive the EVP_PKEY structure containing the private key.
@retval TRUE Private key was retrieved successfully.
@retval FALSE Failed to create BIO, write data, or read private key.
**/
STATIC
BOOLEAN
GetPrivateKeyFromPem (
IN CONST UINT8 *PemData,
IN UINTN PemSize,
IN CONST CHAR8 *Password,
OUT EVP_PKEY **Pkey
)
{
BIO *PemBio;
BOOLEAN Result;
// Create a memory BIO and write PEM data to it
PemBio = BIO_new (BIO_s_mem ());
if (PemBio == NULL) {
return FALSE;
}
if (BIO_write (PemBio, PemData, (int)PemSize) <= 0) {
BIO_free (PemBio);
return FALSE;
}
Result = FALSE;
// Read Private Key from encrypted PEM data
*Pkey = PEM_read_bio_PrivateKey (PemBio, NULL, (pem_password_cb *)&PasswordCallback, (void *)Password);
if (*Pkey != NULL) {
Result = TRUE;
}
// Always free the BIO before returning
BIO_free (PemBio);
return Result;
}
/**
Allocate and initialize a KEY_CONTEXT structure wrapping an EVP_PKEY.
This helper function allocates a KEY_CONTEXT structure and wraps the provided
EVP_PKEY pointer within it.
@param[in] Pkey Pointer to an EVP_PKEY structure to be wrapped.
@param[in] Nid The NID representing the type of key (e.g., EVP_PKEY_ED448).
@param[out] Context Pointer to receive the allocated KEY_CONTEXT structure.
@retval TRUE KEY_CONTEXT was allocated and initialized successfully.
@retval FALSE Memory allocation failed.
**/
STATIC
BOOLEAN
AllocateKeyContext (
IN EVP_PKEY *Pkey,
IN INT32 Nid,
OUT VOID **Context
)
{
KEY_CONTEXT *Ctx;
Ctx = (KEY_CONTEXT *)AllocateZeroPool (sizeof (KEY_CONTEXT));
if (Ctx == NULL) {
return FALSE;
}
Ctx->EvpPkey = Pkey;
Ctx->Nid = Nid;
*Context = (VOID *)Ctx;
return TRUE;
}
/**
Convert an ML-DSA type name string to an OpenSSL NID.
This helper function translates ML-DSA type name strings (e.g., "ML-DSA-87")
to their corresponding OpenSSL EVP_PKEY NIDs (e.g., EVP_PKEY_ML_DSA_87).
If the type name is not recognized, EVP_PKEY_NONE is returned.
@param[in] TypeName ML-DSA type name string (e.g., "ML-DSA-87").
@retval OpenSSL NID (e.g., EVP_PKEY_ML_DSA_87) if recognized.
@retval EVP_PKEY_NONE if the type name is not recognized.
**/
STATIC
INT32
MlDsaTypeNameToNid (
IN CONST CHAR8 *TypeName
)
{
INT32 Nid;
if (AsciiStrCmp (TypeName, "ML-DSA-87") == 0) {
Nid = EVP_PKEY_ML_DSA_87;
} else {
Nid = EVP_PKEY_NONE;
}
return Nid;
}
/**
Check if the given NID is supported for ML-DSA.
This helper function checks if the provided NID corresponds to a supported
ML-DSA type. Currently, only EVP_PKEY_ML_DSA_87 is supported.
@param[in] Nid The NID to check.
@retval TRUE The NID is supported for ML-DSA.
@retval FALSE The NID is not supported for ML-DSA.
**/
STATIC
BOOLEAN
IsMlDsaNidSupported (
IN INT32 Nid
)
{
switch (Nid) {
case EVP_PKEY_ML_DSA_87:
return TRUE;
default:
return FALSE;
}
}
/**
Convert an SLH-DSA type name string to an OpenSSL NID.
This helper function translates SLH-DSA type name strings (e.g., "SLH-DSA-SHAKE-256s")
to their corresponding OpenSSL EVP_PKEY NIDs (e.g., EVP_PKEY_SLH_DSA_SHAKE_256S).
If the type name is not recognized, EVP_PKEY_NONE is returned.
@param[in] TypeName SLH-DSA type name string (e.g., "SLH-DSA-SHAKE-256s").
@retval OpenSSL NID (e.g., EVP_PKEY_SLH_DSA_SHAKE_256S) if recognized.
@retval EVP_PKEY_NONE if the type name is not recognized.
**/
STATIC
INT32
SlhDsaTypeNameToNid (
IN CONST CHAR8 *TypeName
)
{
INT32 Nid;
if (AsciiStrCmp (TypeName, "SLH-DSA-SHAKE-256s") == 0) {
Nid = EVP_PKEY_SLH_DSA_SHAKE_256S;
} else {
Nid = EVP_PKEY_NONE;
}
return Nid;
}
/**
Check if the given NID is supported for SLH-DSA.
This helper function checks if the provided NID corresponds to a supported
SLH-DSA type. Currently, only EVP_PKEY_SLH_DSA_SHAKE_256S is supported.
@param[in] Nid The NID to check.
@retval TRUE The NID is supported for SLH-DSA.
@retval FALSE The NID is not supported for SLH-DSA.
**/
STATIC
BOOLEAN
IsSlhDsaNidSupported (
IN INT32 Nid
)
{
switch (Nid) {
case EVP_PKEY_SLH_DSA_SHAKE_256S:
return TRUE;
default:
return FALSE;
}
}
/**
Retrieve the RSA Private Key from the password-protected PEM key data.
@param[in] PemData Pointer to the PEM-encoded key data to be retrieved.
@param[in] PemSize Size of the PEM key data in bytes.
@param[in] Password NULL-terminated passphrase used for encrypted PEM key data.
@param[out] RsaContext Pointer to new-generated RSA context which contain the retrieved
RSA private key component. Use RsaFree() function to free the
resource.
If PemData is NULL, then return FALSE.
If RsaContext is NULL, then return FALSE.
@retval TRUE RSA Private Key was retrieved successfully.
@retval FALSE Invalid PEM key data or incorrect password.
**/
BOOLEAN
EFIAPI
RsaGetPrivateKeyFromPem (
IN CONST UINT8 *PemData,
IN UINTN PemSize,
IN CONST CHAR8 *Password,
OUT VOID **RsaContext
)
{
BOOLEAN Status;
BIO *PemBio;
//
// Check input parameters.
//
if ((PemData == NULL) || (RsaContext == NULL) || (PemSize > INT_MAX)) {
return FALSE;
}
//
// Add possible block-cipher descriptor for PEM data decryption.
// NOTE: Only support most popular ciphers AES for the encrypted PEM.
//
if (EVP_add_cipher (EVP_aes_128_cbc ()) == 0) {
return FALSE;
}
if (EVP_add_cipher (EVP_aes_192_cbc ()) == 0) {
return FALSE;
}
if (EVP_add_cipher (EVP_aes_256_cbc ()) == 0) {
return FALSE;
}
Status = FALSE;
//
// Read encrypted PEM Data.
//
PemBio = BIO_new (BIO_s_mem ());
if (PemBio == NULL) {
goto _Exit;
}
if (BIO_write (PemBio, PemData, (int)PemSize) <= 0) {
goto _Exit;
}
//
// Retrieve RSA Private Key from encrypted PEM data.
//
*RsaContext = PEM_read_bio_RSAPrivateKey (PemBio, NULL, (pem_password_cb *)&PasswordCallback, (void *)Password);
if (*RsaContext != NULL) {
Status = TRUE;
}
_Exit:
//
// Release Resources.
//
BIO_free (PemBio);
return Status;
}
/**
Retrieve the EC Private Key from the password-protected PEM key data.
@param[in] PemData Pointer to the PEM-encoded key data to be retrieved.
@param[in] PemSize Size of the PEM key data in bytes.
@param[in] Password NULL-terminated passphrase used for encrypted PEM key data.
@param[out] EcContext Pointer to new-generated EC DSA context which contain the retrieved
EC private key component. Use EcFree() function to free the
resource.
If PemData is NULL, then return FALSE.
If EcContext is NULL, then return FALSE.
@retval TRUE EC Private Key was retrieved successfully.
@retval FALSE Invalid PEM key data or incorrect password.
**/
BOOLEAN
EFIAPI
EcGetPrivateKeyFromPem (
IN CONST UINT8 *PemData,
IN UINTN PemSize,
IN CONST CHAR8 *Password,
OUT VOID **EcContext
)
{
BOOLEAN Status;
BIO *PemBio;
//
// Check input parameters.
//
if ((PemData == NULL) || (EcContext == NULL) || (PemSize > INT_MAX)) {
return FALSE;
}
//
// Add possible block-cipher descriptor for PEM data decryption.
// NOTE: Only support most popular ciphers AES for the encrypted PEM.
//
if (EVP_add_cipher (EVP_aes_128_cbc ()) == 0) {
return FALSE;
}
if (EVP_add_cipher (EVP_aes_192_cbc ()) == 0) {
return FALSE;
}
if (EVP_add_cipher (EVP_aes_256_cbc ()) == 0) {
return FALSE;
}
Status = FALSE;
//
// Read encrypted PEM Data.
//
PemBio = BIO_new (BIO_s_mem ());
if (PemBio == NULL) {
goto _Exit;
}
if (BIO_write (PemBio, PemData, (int)PemSize) <= 0) {
goto _Exit;
}
//
// Retrieve EC Private Key from encrypted PEM data.
//
*EcContext = PEM_read_bio_ECPrivateKey (PemBio, NULL, (pem_password_cb *)&PasswordCallback, (void *)Password);
if (*EcContext != NULL) {
Status = TRUE;
}
_Exit:
//
// Release Resources.
//
BIO_free (PemBio);
return Status;
}
/**
Retrieve the EdDSA Private Key from the password-protected PEM key data.
@param[in] PemData Pointer to the PEM-encoded key data to be retrieved.
@param[in] PemSize Size of the PEM key data in bytes.
@param[in] Password NULL-terminated passphrase used for encrypted PEM key data.
@param[out] EdDsaContext Pointer to new-generated EdDSA context which contains the retrieved
EdDSA private key component. Use EdDsaFree() function to free the
resource.
If PemData is NULL, then return FALSE.
If EdDsaContext is NULL, then return FALSE.
@retval TRUE EdDSA Private Key was retrieved successfully.
@retval FALSE Invalid PEM key data or incorrect password.
**/
BOOLEAN
EFIAPI
EdDsaGetPrivateKeyFromPem (
IN CONST UINT8 *PemData,
IN UINTN PemSize,
IN CONST CHAR8 *Password,
OUT VOID **EdDsaContext
)
{
EVP_PKEY *Pkey;
INT32 Nid;
// Check input parameters
if ((PemData == NULL) || (EdDsaContext == NULL) || (PemSize > INT_MAX)) {
return FALSE;
}
// Read PEM data
if (!GetPrivateKeyFromPem (PemData, PemSize, Password, &Pkey)) {
return FALSE;
}
Nid = EVP_PKEY_id (Pkey);
if (Nid != EVP_PKEY_ED448) {
EVP_PKEY_free (Pkey);
return FALSE;
}
// Allocate wrapper structure (now consistent with other key types)
if (!AllocateKeyContext (Pkey, Nid, EdDsaContext)) {
EVP_PKEY_free (Pkey);
return FALSE;
}
return TRUE;
}
/**
Retrieve the ML-DSA Private Key from the password-protected PEM key data.
If PemData is NULL, then return FALSE.
If MlDsaContext is NULL, then return FALSE.
@param[in] PemData Pointer to the PEM-encoded key data to be retrieved.
@param[in] PemSize Size of the PEM key data in bytes.
@param[in] Password NULL-terminated passphrase used for encrypted PEM key data.
@param[out] MlDsaContext Pointer to new-generated ML-DSA context which contains
the retrieved ML-DSA private key. Use MlDsaFree() to free.
@retval TRUE ML-DSA Private Key was retrieved successfully.
@retval FALSE Invalid PEM key data or incorrect password.
**/
BOOLEAN
EFIAPI
MlDsaGetPrivateKeyFromPem (
IN CONST UINT8 *PemData,
IN UINTN PemSize,
IN CONST CHAR8 *Password,
OUT VOID **MlDsaContext
)
{
EVP_PKEY *Pkey;
INT32 Nid;
//
// Check input parameters.
//
if ((PemData == NULL) || (MlDsaContext == NULL) || (PemSize > INT_MAX)) {
return FALSE;
}
// Read PEM data
if (!GetPrivateKeyFromPem (PemData, PemSize, Password, &Pkey)) {
return FALSE;
}
Nid = MlDsaTypeNameToNid (EVP_PKEY_get0_type_name (Pkey));
if (!IsMlDsaNidSupported (Nid)) {
EVP_PKEY_free (Pkey);
return FALSE;
}
// Allocate wrapper structure (now consistent with other key types)
if (!AllocateKeyContext (Pkey, Nid, MlDsaContext)) {
EVP_PKEY_free (Pkey);
return FALSE;
}
return TRUE;
}
/**
Retrieve the SLH-DSA Private Key from the password-protected PEM key data.
If PemData is NULL, then return FALSE.
If SlhDsaContext is NULL, then return FALSE.
@param[in] PemData Pointer to the PEM-encoded key data to be retrieved.
@param[in] PemSize Size of the PEM key data in bytes.
@param[in] Password NULL-terminated passphrase used for encrypted PEM key data.
@param[out] SlhDsaContext Pointer to new-generated SLH-DSA context which contains
the retrieved SLH-DSA private key. Use SlhDsaFree() to free.
@retval TRUE SLH-DSA Private Key was retrieved successfully.
@retval FALSE Invalid PEM key data or incorrect password.
**/
BOOLEAN
EFIAPI
SlhDsaGetPrivateKeyFromPem (
IN CONST UINT8 *PemData,
IN UINTN PemSize,
IN CONST CHAR8 *Password,
OUT VOID **SlhDsaContext
)
{
EVP_PKEY *Pkey;
INT32 Nid;
//
// Check input parameters.
//
if ((PemData == NULL) || (SlhDsaContext == NULL) || (PemSize > INT_MAX)) {
return FALSE;
}
// Read PEM data
if (!GetPrivateKeyFromPem (PemData, PemSize, Password, &Pkey)) {
return FALSE;
}
Nid = SlhDsaTypeNameToNid (EVP_PKEY_get0_type_name (Pkey));
if (!IsSlhDsaNidSupported (Nid)) {
EVP_PKEY_free (Pkey);
return FALSE;
}
// Allocate wrapper structure (now consistent with other key types)
if (!AllocateKeyContext (Pkey, Nid, SlhDsaContext)) {
EVP_PKEY_free (Pkey);
return FALSE;
}
return TRUE;
}