use base64::{Engine, engine::general_purpose};
use cosmian_kmip::{
kmip_0::kmip_types::{BlockCipherMode, CryptographicUsageMask, PaddingMethod},
kmip_2_1::{
kmip_data_structures::{KeyBlock, KeyMaterial, KeyValue, KeyWrappingData},
kmip_objects::Object,
kmip_types::{CryptographicAlgorithm, EncodingOption, KeyFormatType},
},
};
use openssl::pkey::{Id, PKey, Private};
use x509_parser::nom::AsBytes;
use zeroize::Zeroizing;
use super::WRAPPING_SECRET_LENGTH;
#[cfg(feature = "non-fips")]
use crate::crypto::elliptic_curves::ecies::ecies_decrypt;
#[cfg(feature = "non-fips")]
use crate::crypto::rsa::ckm_rsa_pkcs::ckm_rsa_pkcs_key_unwrap;
use crate::{
crypto::{
FIPS_MIN_SALT_SIZE,
password_derivation::derive_key_from_password,
rsa::{
ckm_rsa_aes_key_wrap::ckm_rsa_aes_key_unwrap,
ckm_rsa_pkcs_oaep::ckm_rsa_pkcs_oaep_key_unwrap,
},
symmetric::{
rfc3394::rfc3394_unwrap,
rfc5649::rfc5649_unwrap,
symmetric_ciphers::{SymCipher, decrypt},
},
wrap::common::rsa_parameters,
},
crypto_bail,
error::{CryptoError, result::CryptoResultHelper},
openssl::kmip_private_key_to_openssl,
};
const NONCE_LENGTH: usize = 12;
const TAG_LENGTH: usize = 16;
pub fn aes_gcm_decrypt(
wrapped_key_b64: &str,
unwrap_secret: &[u8],
) -> Result<Zeroizing<Vec<u8>>, CryptoError> {
let wrapped_key = general_purpose::STANDARD
.decode(wrapped_key_b64)
.map_err(|e| CryptoError::Default(format!("Invalid base64 wrapped key: {e}")))?;
let len = wrapped_key.len();
if len < TAG_LENGTH + NONCE_LENGTH {
crypto_bail!("Invalid wrapped key - insufficient length.");
}
let nonce = wrapped_key
.get(..NONCE_LENGTH)
.ok_or_else(|| CryptoError::IndexingSlicing("cse_wrapped_key_decrypt: nonce".to_owned()))?;
let ciphertext = wrapped_key
.get(NONCE_LENGTH..len - TAG_LENGTH)
.ok_or_else(|| {
CryptoError::IndexingSlicing("cse_wrapped_key_decrypt: ciphertext".to_owned())
})?;
let tag = wrapped_key
.get(len - TAG_LENGTH..)
.ok_or_else(|| CryptoError::IndexingSlicing("cse_wrapped_key_decrypt: tag".to_owned()))?;
let sym = SymCipher::from_algorithm_and_key_size(
CryptographicAlgorithm::AES,
Some(BlockCipherMode::GCM),
unwrap_secret.len(),
)?;
decrypt(sym, unwrap_secret, nonce, &[], ciphertext, tag, None)
}
pub fn unwrap_key_bytes(
salt: &[u8; FIPS_MIN_SALT_SIZE],
key: &[u8],
wrapping_password: &str,
) -> Result<Zeroizing<Vec<u8>>, CryptoError> {
let wrapping_secret =
derive_key_from_password::<WRAPPING_SECRET_LENGTH>(salt, wrapping_password.as_bytes())?;
rfc5649_unwrap(key, wrapping_secret.as_ref()).map_err(|e| CryptoError::Default(e.to_string()))
}
pub fn unwrap_key_block(
object_key_block: &mut KeyBlock,
unwrapping_key: &Object,
) -> Result<(), CryptoError> {
let key_wrapping_data = object_key_block
.key_wrapping_data
.as_ref()
.context("unable to unwrap key: key wrapping data is missing")?;
let Some(KeyValue::ByteString(wrapped_key)) = object_key_block.key_value.as_ref() else {
crypto_bail!("unable to unwrap key: key value is not a byte string")
};
let key_value = unwrap(
unwrapping_key,
key_wrapping_data,
wrapped_key,
object_key_block.key_format_type,
)?;
object_key_block.key_value = Some(key_value);
object_key_block.key_wrapping_data = None;
Ok(())
}
pub(super) fn unwrap(
unwrapping_key: &Object,
key_wrapping_data: &KeyWrappingData,
wrapped_key: &[u8],
key_format_type: KeyFormatType,
) -> Result<KeyValue, CryptoError> {
let unwrapping_authorized = unwrapping_key.attributes().is_ok_and(|attrs| {
attrs
.is_usage_authorized_for(CryptographicUsageMask::UnwrapKey)
.unwrap_or(false)
});
if !unwrapping_authorized {
return Err(CryptoError::Kmip(
"CryptographicUsageMask not authorized for UnwrapKey".to_owned(),
));
}
let unwrapping_key_block = unwrapping_key
.key_block()
.context("Unable to unwrap: the key encryption key is not a key")?;
if unwrapping_key_block.key_wrapping_data.is_some() {
crypto_bail!(
"unable to unwrap: the key encryption key is wrapped, and that is not supported"
)
}
let plaintext = match unwrapping_key_block.key_format_type {
KeyFormatType::TransparentSymmetricKey | KeyFormatType::Raw => {
unwrap_with_symmetric_key(key_wrapping_data, wrapped_key, unwrapping_key_block)
}
KeyFormatType::TransparentECPrivateKey | KeyFormatType::TransparentRSAPrivateKey => {
let p_key = kmip_private_key_to_openssl(unwrapping_key)?;
unwrap_with_private_key(&p_key, key_wrapping_data, wrapped_key)
}
KeyFormatType::PKCS8 => {
let p_key = PKey::private_key_from_der(&unwrapping_key_block.pkcs_der_bytes()?)?;
unwrap_with_private_key(&p_key, key_wrapping_data, wrapped_key)
}
x => {
crypto_bail!("Unable to unwrap: the key encryption key format is not supported: {x:?}")
}
}?;
decode_unwrapped_key(key_wrapping_data.get_encoding(), key_format_type, plaintext)
}
pub fn decode_unwrapped_key(
encoding: EncodingOption,
key_format_type: KeyFormatType,
plaintext: Zeroizing<Vec<u8>>,
) -> Result<KeyValue, CryptoError> {
match encoding {
EncodingOption::TTLVEncoding => {
KeyValue::from_ttlv_bytes(plaintext.as_bytes(), key_format_type).map_err(Into::into)
}
EncodingOption::NoEncoding => {
match key_format_type {
KeyFormatType::Raw
| KeyFormatType::ECPrivateKey
| KeyFormatType::Opaque
| KeyFormatType::PKCS1
| KeyFormatType::PKCS10
| KeyFormatType::PKCS12
| KeyFormatType::PKCS7
| KeyFormatType::PKCS8
| KeyFormatType::X509
| KeyFormatType::CoverCryptSecretKey
| KeyFormatType::CoverCryptPublicKey => {
let key_material = KeyMaterial::ByteString(plaintext);
Ok(KeyValue::Structure {
key_material,
attributes: None,
})
}
#[cfg(feature = "non-fips")]
KeyFormatType::Pkcs12Legacy => {
let key_material = KeyMaterial::ByteString(plaintext);
Ok(KeyValue::Structure {
key_material,
attributes: None,
})
}
KeyFormatType::TransparentSymmetricKey => {
let key_material = KeyMaterial::TransparentSymmetricKey { key: plaintext };
Ok(KeyValue::Structure {
key_material,
attributes: None,
})
}
f => {
crypto_bail!(
"Unable to decode the unwrapped key using No Encoding, its format is not \
supported: {f:?}"
)
}
}
}
}
}
fn unwrap_with_symmetric_key(
key_wrapping_data: &KeyWrappingData,
ciphertext: &[u8],
unwrapping_key_block: &KeyBlock,
) -> Result<Zeroizing<Vec<u8>>, CryptoError> {
let block_cipher_mode = key_wrapping_data
.encryption_key_information
.clone()
.and_then(|information| information.cryptographic_parameters)
.and_then(|parameters| parameters.block_cipher_mode);
let unwrap_secret = unwrapping_key_block
.key_bytes()
.context("unwrapping key bytes:")?;
if block_cipher_mode == Some(BlockCipherMode::GCM) {
aes_gcm_unwrap(ciphertext, &unwrap_secret)
} else if block_cipher_mode == Some(BlockCipherMode::NISTKeyWrap) {
rfc3394_unwrap(ciphertext, &unwrap_secret)
} else {
rfc5649_unwrap(ciphertext, &unwrap_secret)
}
}
fn aes_gcm_unwrap(
ciphertext: &[u8],
unwrap_secret: &Zeroizing<Vec<u8>>,
) -> Result<Zeroizing<Vec<u8>>, CryptoError> {
let len = ciphertext.len();
if len < TAG_LENGTH + NONCE_LENGTH {
crypto_bail!("Invalid wrapped key - insufficient length.");
}
let aead = SymCipher::Aes256Gcm;
let nonce = ciphertext
.get(..NONCE_LENGTH)
.ok_or_else(|| CryptoError::IndexingSlicing("unwrap: nonce".to_owned()))?;
let wrapped_key_bytes = ciphertext
.get(NONCE_LENGTH..len - TAG_LENGTH)
.ok_or_else(|| CryptoError::IndexingSlicing("unwrap: wrapped_key_bytes".to_owned()))?;
let tag = ciphertext
.get(len - TAG_LENGTH..)
.ok_or_else(|| CryptoError::IndexingSlicing("unwrap: tag".to_owned()))?;
decrypt(
aead,
unwrap_secret,
nonce,
&[],
wrapped_key_bytes,
tag,
None,
)
}
fn unwrap_with_private_key(
private_key: &PKey<Private>,
key_wrapping_data: &KeyWrappingData,
ciphertext: &[u8],
) -> Result<Zeroizing<Vec<u8>>, CryptoError> {
match private_key.id() {
Id::RSA => unwrap_with_rsa(private_key, key_wrapping_data, ciphertext),
#[cfg(feature = "non-fips")]
Id::EC | Id::X25519 | Id::ED25519 => ecies_decrypt(private_key, ciphertext),
other => {
crypto_bail!(
"Unable to wrap key: wrapping public key type not supported: {:?}",
other
)
}
}
}
fn unwrap_with_rsa(
private_key: &PKey<Private>,
key_wrapping_data: &KeyWrappingData,
wrapped_key: &[u8],
) -> Result<Zeroizing<Vec<u8>>, CryptoError> {
let (algorithm, padding, hashing_fn) = rsa_parameters(key_wrapping_data);
match algorithm {
CryptographicAlgorithm::RSA => match padding {
PaddingMethod::None => ckm_rsa_aes_key_unwrap(private_key, hashing_fn, wrapped_key),
PaddingMethod::OAEP => {
ckm_rsa_pkcs_oaep_key_unwrap(private_key, hashing_fn, hashing_fn, None, wrapped_key)
}
#[cfg(feature = "non-fips")]
PaddingMethod::PKCS1v15 => ckm_rsa_pkcs_key_unwrap(private_key, wrapped_key),
_ => crypto_bail!(
"Unable to unwrap key with RSA: padding method not supported: {padding:?}"
),
},
x => {
crypto_bail!(
"Unable to unwrap key with RSA: algorithm not supported for unwrapping: {:?}",
x
)
}
}
}