use super::concat_kdf_ikm;
use crate::error::Error as KemError;
use alloc::vec::Vec;
use dcrypt_algorithms::ec::p384 as ec_p384;
use dcrypt_api::{
error::Error as ApiError,
traits::serialize::{Serialize, SerializeSecret},
Kem, Key as ApiKey, Result as ApiResult, ZeroizingBytes,
};
use dcrypt_common::security::SecretBuffer;
use dcrypt_internal::random::{CryptoRng, RngCore};
use dcrypt_internal::zeroing::Zeroizing;
const KDF_INFO: &[u8] = b"dcrypt-v3/ECDH-P384-KEM/shared-secret";
pub struct EcdhP384;
#[derive(Clone)]
pub struct EcdhP384PublicKey([u8; ec_p384::P384_POINT_COMPRESSED_SIZE]);
impl_zeroize_tuple!(EcdhP384PublicKey);
impl AsRef<[u8]> for EcdhP384PublicKey {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl AsMut<[u8]> for EcdhP384PublicKey {
fn as_mut(&mut self) -> &mut [u8] {
&mut self.0
}
}
#[derive(Clone)]
pub struct EcdhP384SecretKey(SecretBuffer<{ ec_p384::P384_SCALAR_SIZE }>);
impl_zeroize_on_drop_tuple!(EcdhP384SecretKey);
impl AsRef<[u8]> for EcdhP384SecretKey {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}
#[derive(Clone)]
pub struct EcdhP384SharedSecret(ApiKey);
impl_zeroize_on_drop_tuple!(EcdhP384SharedSecret);
impl AsRef<[u8]> for EcdhP384SharedSecret {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}
#[derive(Clone)]
pub struct EcdhP384Ciphertext([u8; ec_p384::P384_POINT_COMPRESSED_SIZE]);
impl AsRef<[u8]> for EcdhP384Ciphertext {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl AsMut<[u8]> for EcdhP384Ciphertext {
fn as_mut(&mut self) -> &mut [u8] {
&mut self.0
}
}
impl EcdhP384PublicKey {
pub fn from_bytes(bytes: &[u8]) -> ApiResult<Self> {
if bytes.len() != ec_p384::P384_POINT_COMPRESSED_SIZE {
return Err(ApiError::InvalidLength {
context: "EcdhP384PublicKey::from_bytes",
expected: ec_p384::P384_POINT_COMPRESSED_SIZE,
actual: bytes.len(),
});
}
let point = ec_p384::Point::deserialize_compressed(bytes)
.map_err(|e| ApiError::from(KemError::from(e)))?;
if point.is_identity() {
return Err(ApiError::InvalidKey {
context: "EcdhP384PublicKey::from_bytes",
#[cfg(feature = "std")]
message: "Public key cannot be the identity point".to_string(),
});
}
let mut key_bytes = [0u8; ec_p384::P384_POINT_COMPRESSED_SIZE];
key_bytes.copy_from_slice(bytes);
Ok(Self(key_bytes))
}
pub fn to_bytes(&self) -> Vec<u8> {
self.0.to_vec()
}
}
impl Serialize for EcdhP384PublicKey {
fn from_bytes(bytes: &[u8]) -> ApiResult<Self> {
Self::from_bytes(bytes)
}
fn to_bytes(&self) -> Vec<u8> {
self.to_bytes()
}
}
impl EcdhP384SecretKey {
pub fn from_bytes(bytes: &[u8]) -> ApiResult<Self> {
if bytes.len() != ec_p384::P384_SCALAR_SIZE {
return Err(ApiError::InvalidLength {
context: "EcdhP384SecretKey::from_bytes",
expected: ec_p384::P384_SCALAR_SIZE,
actual: bytes.len(),
});
}
let mut buffer = SecretBuffer::zeroed();
buffer.as_mut().copy_from_slice(bytes);
let scalar = ec_p384::Scalar::from_secret_buffer(buffer.clone())
.map_err(|e| ApiError::from(KemError::from(e)))?;
drop(scalar);
Ok(Self(buffer))
}
pub fn to_bytes(&self) -> ZeroizingBytes {
self.0.to_bytes_zeroizing_boxed()
}
}
impl SerializeSecret for EcdhP384SecretKey {
fn from_bytes(bytes: &[u8]) -> ApiResult<Self> {
Self::from_bytes(bytes)
}
fn to_bytes_zeroizing(&self) -> ZeroizingBytes {
self.to_bytes()
}
}
impl EcdhP384SharedSecret {
pub fn to_bytes(&self) -> ZeroizingBytes {
self.0.to_bytes_zeroizing_boxed()
}
pub fn to_bytes_zeroizing(&self) -> ZeroizingBytes {
self.to_bytes()
}
}
impl SerializeSecret for EcdhP384SharedSecret {
fn from_bytes(bytes: &[u8]) -> ApiResult<Self> {
if bytes.len() != ec_p384::P384_KEM_SHARED_SECRET_KDF_OUTPUT_SIZE {
return Err(ApiError::InvalidLength {
context: "EcdhP384SharedSecret::from_bytes",
expected: ec_p384::P384_KEM_SHARED_SECRET_KDF_OUTPUT_SIZE,
actual: bytes.len(),
});
}
Ok(Self(ApiKey::new(bytes)))
}
fn to_bytes_zeroizing(&self) -> ZeroizingBytes {
self.to_bytes_zeroizing()
}
}
impl EcdhP384Ciphertext {
pub fn from_bytes(bytes: &[u8]) -> ApiResult<Self> {
if bytes.len() != ec_p384::P384_POINT_COMPRESSED_SIZE {
return Err(ApiError::InvalidLength {
context: "EcdhP384Ciphertext::from_bytes",
expected: ec_p384::P384_POINT_COMPRESSED_SIZE,
actual: bytes.len(),
});
}
let point = ec_p384::Point::deserialize_compressed(bytes)
.map_err(|e| ApiError::from(KemError::from(e)))?;
if point.is_identity() {
return Err(ApiError::InvalidCiphertext {
context: "EcdhP384Ciphertext::from_bytes",
#[cfg(feature = "std")]
message: "Ephemeral public key cannot be the identity point".to_string(),
});
}
let mut ct_bytes = [0u8; ec_p384::P384_POINT_COMPRESSED_SIZE];
ct_bytes.copy_from_slice(bytes);
Ok(Self(ct_bytes))
}
pub fn to_bytes(&self) -> Vec<u8> {
self.0.to_vec()
}
}
impl Serialize for EcdhP384Ciphertext {
fn from_bytes(bytes: &[u8]) -> ApiResult<Self> {
Self::from_bytes(bytes)
}
fn to_bytes(&self) -> Vec<u8> {
self.to_bytes()
}
}
impl Kem for EcdhP384 {
type PublicKey = EcdhP384PublicKey;
type SecretKey = EcdhP384SecretKey;
type SharedSecret = EcdhP384SharedSecret;
type Ciphertext = EcdhP384Ciphertext;
type KeyPair = (Self::PublicKey, Self::SecretKey);
fn name() -> &'static str {
"ECDH-P384"
}
fn keypair<R: CryptoRng + RngCore>(rng: &mut R) -> ApiResult<Self::KeyPair> {
let (sk_scalar, pk_point) =
ec_p384::generate_keypair(rng).map_err(|e| ApiError::from(KemError::from(e)))?;
let public_key = EcdhP384PublicKey(pk_point.serialize_compressed());
let secret_key = EcdhP384SecretKey(sk_scalar.as_secret_buffer().clone());
Ok((public_key, secret_key))
}
fn public_key(keypair: &Self::KeyPair) -> Self::PublicKey {
keypair.0.clone()
}
fn secret_key(keypair: &Self::KeyPair) -> Self::SecretKey {
keypair.1.clone()
}
fn encapsulate<R: CryptoRng + RngCore>(
rng: &mut R,
public_key_recipient: &Self::PublicKey,
) -> ApiResult<(Self::Ciphertext, Self::SharedSecret)> {
let pk_r_point = ec_p384::Point::deserialize_compressed(&public_key_recipient.0)
.map_err(|e| ApiError::from(KemError::from(e)))?;
if pk_r_point.is_identity() {
return Err(ApiError::InvalidKey {
context: "ECDH-P384 encapsulate",
#[cfg(feature = "std")]
message: "Recipient public key cannot be the identity point".to_string(),
});
}
let (ephemeral_scalar, ephemeral_point) =
ec_p384::generate_keypair(rng).map_err(|e| ApiError::from(KemError::from(e)))?;
let ciphertext = EcdhP384Ciphertext(ephemeral_point.serialize_compressed());
let shared_point = ec_p384::scalar_mult(&ephemeral_scalar, &pk_r_point)
.map_err(|e| ApiError::from(KemError::from(e)))?;
if shared_point.is_identity() {
return Err(ApiError::DecryptionFailed {
context: "ECDH-P384 encapsulate",
#[cfg(feature = "std")]
message: "Shared point is the identity".to_string(),
});
}
let x_coord_bytes = Zeroizing::new(shared_point.x_coordinate_bytes());
let kdf_ikm = concat_kdf_ikm(
x_coord_bytes.as_ref(),
&ephemeral_point.serialize_compressed(),
&public_key_recipient.0,
);
let ss_bytes = ec_p384::kdf_hkdf_sha384_for_ecdh_kem(&kdf_ikm, Some(KDF_INFO))
.map_err(|e| ApiError::from(KemError::from(e)))?;
let shared_secret = EcdhP384SharedSecret(ApiKey::new(&ss_bytes[..]));
drop(ephemeral_scalar);
Ok((ciphertext, shared_secret))
}
fn decapsulate(
secret_key_recipient: &Self::SecretKey,
ciphertext_ephemeral_pk: &Self::Ciphertext,
) -> ApiResult<Self::SharedSecret> {
let sk_r_scalar = ec_p384::Scalar::from_secret_buffer(secret_key_recipient.0.clone())
.map_err(|e| ApiError::from(KemError::from(e)))?;
let q_e_point = ec_p384::Point::deserialize_compressed(&ciphertext_ephemeral_pk.0)
.map_err(|e| ApiError::from(KemError::from(e)))?;
if q_e_point.is_identity() {
return Err(ApiError::InvalidCiphertext {
context: "ECDH-P384 decapsulate",
#[cfg(feature = "std")]
message: "Ephemeral public key cannot be the identity point".to_string(),
});
}
let shared_point = ec_p384::scalar_mult(&sk_r_scalar, &q_e_point)
.map_err(|e| ApiError::from(KemError::from(e)))?;
if shared_point.is_identity() {
return Err(ApiError::DecryptionFailed {
context: "ECDH-P384 decapsulate",
#[cfg(feature = "std")]
message: "Shared point is the identity".to_string(),
});
}
let x_coord_bytes = Zeroizing::new(shared_point.x_coordinate_bytes());
let q_r_point = ec_p384::scalar_mult_base_g(&sk_r_scalar)
.map_err(|e| ApiError::from(KemError::from(e)))?;
let kdf_ikm = concat_kdf_ikm(
x_coord_bytes.as_ref(),
&ciphertext_ephemeral_pk.0,
&q_r_point.serialize_compressed(),
);
let ss_bytes = ec_p384::kdf_hkdf_sha384_for_ecdh_kem(&kdf_ikm, Some(KDF_INFO))
.map_err(|e| ApiError::from(KemError::from(e)))?;
let shared_secret = EcdhP384SharedSecret(ApiKey::new(&ss_bytes[..]));
Ok(shared_secret)
}
}
#[cfg(test)]
mod tests;