From 4de3062de200abb9cd9bb8bf4cc33c1b1a9c1174 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Thu, 7 Nov 2024 11:14:30 +0100 Subject: [PATCH] Add support for EC_MONTGOMERY keys This refactors the ECDH out of the generic ECC module, as the same code is used also for derivation operation with Montgomery EC keys. Signed-off-by: Jakub Jelen --- Cargo.toml | 3 +- src/ec_montgomery.rs | 244 +++++++++++++++++++++++ src/ecc.rs | 19 +- src/ecdh.rs | 60 ++++++ src/enabled.rs | 12 ++ src/ossl/ec_montgomery.rs | 167 ++++++++++++++++ src/ossl/ecc.rs | 255 ++---------------------- src/ossl/ecdh.rs | 274 ++++++++++++++++++++++++++ src/ossl/mod.rs | 7 + src/tests/ec_montgomery.rs | 386 +++++++++++++++++++++++++++++++++++++ src/tests/mod.rs | 3 + 11 files changed, 1169 insertions(+), 261 deletions(-) create mode 100644 src/ec_montgomery.rs create mode 100644 src/ecdh.rs create mode 100644 src/ossl/ec_montgomery.rs create mode 100644 src/ossl/ecdh.rs create mode 100644 src/tests/ec_montgomery.rs diff --git a/Cargo.toml b/Cargo.toml index 619f5f5b..fe62d4ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,6 +50,7 @@ zeroize = "1.6.0" aes = [] ecc = [] eddsa = [] +ec_montgomery = [] hash = [] hkdf = [] hmac = ["hash"] @@ -69,7 +70,7 @@ basic = [ "aes", "hmac", "pbkdf2", "sqlitedb" ] #select everything by default # Use --no-default-features --features basic, xxx for custom selections -default = [ "basic", "ecc", "eddsa", "hash", "hkdf", "rsa", "sp800_108", "sshkdf", "tlskdf"] +default = [ "basic", "ecc", "ec_montgomery", "eddsa", "hash", "hkdf", "rsa", "sp800_108", "sshkdf", "tlskdf"] fips = [ "rusqlite/bundled", "basic", "ecc", "hash", "hkdf", "rsa", "sp800_108", "sshkdf", "tlskdf"] diff --git a/src/ec_montgomery.rs b/src/ec_montgomery.rs new file mode 100644 index 00000000..2623ce20 --- /dev/null +++ b/src/ec_montgomery.rs @@ -0,0 +1,244 @@ +// Copyright 2024 Jakub Jelen +// See LICENSE.txt file for terms + +use std::fmt::Debug; + +use crate::attribute::Attribute; +use crate::ecc_misc::*; +use crate::error::Result; +use crate::interface::*; +use crate::mechanism::*; +use crate::object::*; +use crate::ossl::ec_montgomery::*; +use crate::{attr_element, bytes_attr_not_empty}; + +use once_cell::sync::Lazy; + +pub const MIN_EC_MONTGOMERY_SIZE_BITS: usize = BITS_CURVE25519; +pub const MAX_EC_MONTGOMERY_SIZE_BITS: usize = BITS_CURVE448; + +#[derive(Debug)] +pub struct ECMontgomeryPubFactory { + attributes: Vec, +} + +impl ECMontgomeryPubFactory { + pub fn new() -> ECMontgomeryPubFactory { + let mut data: ECMontgomeryPubFactory = ECMontgomeryPubFactory { + attributes: Vec::new(), + }; + data.attributes.append(&mut data.init_common_object_attrs()); + data.attributes + .append(&mut data.init_common_storage_attrs()); + data.attributes.append(&mut data.init_common_key_attrs()); + data.attributes + .append(&mut data.init_common_public_key_attrs()); + data.attributes.push(attr_element!( + CKA_EC_PARAMS; OAFlags::AlwaysRequired | OAFlags::Unchangeable; + Attribute::from_bytes; val Vec::new())); + data.attributes.push(attr_element!( + CKA_EC_POINT; OAFlags::RequiredOnCreate + | OAFlags::SettableOnlyOnCreate | OAFlags::Unchangeable; + Attribute::from_bytes; val Vec::new())); + data + } +} + +impl ObjectFactory for ECMontgomeryPubFactory { + fn create(&self, template: &[CK_ATTRIBUTE]) -> Result { + let obj = self.default_object_create(template)?; + + bytes_attr_not_empty!(obj; CKA_EC_PARAMS); + bytes_attr_not_empty!(obj; CKA_EC_POINT); + + Ok(obj) + } + + fn get_attributes(&self) -> &Vec { + &self.attributes + } +} + +impl CommonKeyFactory for ECMontgomeryPubFactory {} + +impl PubKeyFactory for ECMontgomeryPubFactory {} + +#[derive(Debug)] +pub struct ECMontgomeryPrivFactory { + attributes: Vec, +} + +impl ECMontgomeryPrivFactory { + pub fn new() -> ECMontgomeryPrivFactory { + let mut data: ECMontgomeryPrivFactory = ECMontgomeryPrivFactory { + attributes: Vec::new(), + }; + data.attributes.append(&mut data.init_common_object_attrs()); + data.attributes + .append(&mut data.init_common_storage_attrs()); + data.attributes.append(&mut data.init_common_key_attrs()); + data.attributes + .append(&mut data.init_common_private_key_attrs()); + data.attributes.push(attr_element!( + CKA_EC_PARAMS; OAFlags::RequiredOnCreate | OAFlags::Unchangeable; + Attribute::from_bytes; val Vec::new())); + data.attributes.push(attr_element!( + CKA_VALUE; OAFlags::Sensitive | OAFlags::RequiredOnCreate + | OAFlags::SettableOnlyOnCreate | OAFlags::Unchangeable; + Attribute::from_bytes; val Vec::new())); + + /* default to private */ + let private = attr_element!( + CKA_PRIVATE; OAFlags::Defval | OAFlags::ChangeOnCopy; + Attribute::from_bool; val true); + match data + .attributes + .iter() + .position(|x| x.get_type() == CKA_PRIVATE) + { + Some(idx) => data.attributes[idx] = private, + None => data.attributes.push(private), + } + + data + } +} + +impl ObjectFactory for ECMontgomeryPrivFactory { + fn create(&self, template: &[CK_ATTRIBUTE]) -> Result { + let mut obj = self.default_object_create(template)?; + + ec_key_check_import(&mut obj)?; + + Ok(obj) + } + + fn get_attributes(&self) -> &Vec { + &self.attributes + } + + fn export_for_wrapping(&self, key: &Object) -> Result> { + PrivKeyFactory::export_for_wrapping(self, key) + } + + fn import_from_wrapped( + &self, + data: Vec, + template: &[CK_ATTRIBUTE], + ) -> Result { + PrivKeyFactory::import_from_wrapped(self, data, template) + } +} + +impl CommonKeyFactory for ECMontgomeryPrivFactory {} + +impl PrivKeyFactory for ECMontgomeryPrivFactory {} + +static PUBLIC_KEY_FACTORY: Lazy> = + Lazy::new(|| Box::new(ECMontgomeryPubFactory::new())); + +static PRIVATE_KEY_FACTORY: Lazy> = + Lazy::new(|| Box::new(ECMontgomeryPrivFactory::new())); + +#[derive(Debug)] +struct ECMontgomeryMechanism { + info: CK_MECHANISM_INFO, +} + +impl ECMontgomeryMechanism { + fn register_mechanisms(mechs: &mut Mechanisms) { + /* TODO PKCS #11 defines also CKM_XEDDSA for signatures, but it is not implemented by + * OpenSSL */ + mechs.add_mechanism( + CKM_EC_MONTGOMERY_KEY_PAIR_GEN, + Box::new(ECMontgomeryMechanism { + info: CK_MECHANISM_INFO { + ulMinKeySize: CK_ULONG::try_from( + MIN_EC_MONTGOMERY_SIZE_BITS, + ) + .unwrap(), + ulMaxKeySize: CK_ULONG::try_from( + MAX_EC_MONTGOMERY_SIZE_BITS, + ) + .unwrap(), + flags: CKF_GENERATE_KEY_PAIR, + }, + }), + ); + } +} + +impl Mechanism for ECMontgomeryMechanism { + fn info(&self) -> &CK_MECHANISM_INFO { + &self.info + } + + fn generate_keypair( + &self, + mech: &CK_MECHANISM, + pubkey_template: &[CK_ATTRIBUTE], + prikey_template: &[CK_ATTRIBUTE], + ) -> Result<(Object, Object)> { + let mut pubkey = + PUBLIC_KEY_FACTORY.default_object_generate(pubkey_template)?; + if !pubkey.check_or_set_attr(Attribute::from_ulong( + CKA_CLASS, + CKO_PUBLIC_KEY, + ))? { + return Err(CKR_TEMPLATE_INCONSISTENT)?; + } + if !pubkey.check_or_set_attr(Attribute::from_ulong( + CKA_KEY_TYPE, + CKK_EC_EDWARDS, + ))? { + return Err(CKR_TEMPLATE_INCONSISTENT)?; + } + + let mut privkey = + PRIVATE_KEY_FACTORY.default_object_generate(prikey_template)?; + if !privkey.check_or_set_attr(Attribute::from_ulong( + CKA_CLASS, + CKO_PRIVATE_KEY, + ))? { + return Err(CKR_TEMPLATE_INCONSISTENT)?; + } + if !privkey.check_or_set_attr(Attribute::from_ulong( + CKA_KEY_TYPE, + CKK_EC_EDWARDS, + ))? { + return Err(CKR_TEMPLATE_INCONSISTENT)?; + } + + let ec_params = match pubkey.get_attr_as_bytes(CKA_EC_PARAMS) { + Ok(a) => a.clone(), + Err(_) => { + return Err(CKR_ATTRIBUTE_VALUE_INVALID)?; + } + }; + if !privkey.check_or_set_attr(Attribute::from_bytes( + CKA_EC_PARAMS, + ec_params, + ))? { + return Err(CKR_TEMPLATE_INCONSISTENT)?; + } + + ECMontgomeryOperation::generate_keypair(&mut pubkey, &mut privkey)?; + default_key_attributes(&mut privkey, mech.mechanism)?; + default_key_attributes(&mut pubkey, mech.mechanism)?; + + Ok((pubkey, privkey)) + } +} + +pub fn register(mechs: &mut Mechanisms, ot: &mut ObjectFactories) { + ECMontgomeryMechanism::register_mechanisms(mechs); + + ot.add_factory( + ObjectType::new(CKO_PUBLIC_KEY, CKK_EC_MONTGOMERY), + &PUBLIC_KEY_FACTORY, + ); + ot.add_factory( + ObjectType::new(CKO_PRIVATE_KEY, CKK_EC_MONTGOMERY), + &PRIVATE_KEY_FACTORY, + ); +} diff --git a/src/ecc.rs b/src/ecc.rs index 68879c6e..79a8bb60 100644 --- a/src/ecc.rs +++ b/src/ecc.rs @@ -10,8 +10,8 @@ use crate::interface::*; use crate::kasn1::PrivateKeyInfo; use crate::mechanism::*; use crate::object::*; -use crate::ossl::ecc::{ECDHOperation, EccOperation}; -use crate::{attr_element, bytes_attr_not_empty, cast_params}; +use crate::ossl::ecc::EccOperation; +use crate::{attr_element, bytes_attr_not_empty}; use asn1; use once_cell::sync::Lazy; @@ -398,20 +398,6 @@ impl Mechanism for EccMechanism { Ok(Box::new(EccOperation::verify_new(mech, key, &self.info)?)) } - fn derive_operation(&self, mech: &CK_MECHANISM) -> Result { - if self.info.flags & CKF_DERIVE != CKF_DERIVE { - return Err(CKR_MECHANISM_INVALID)?; - } - let kdf = match mech.mechanism { - CKM_ECDH1_DERIVE | CKM_ECDH1_COFACTOR_DERIVE => { - let params = cast_params!(mech, CK_ECDH1_DERIVE_PARAMS); - ECDHOperation::derive_new(mech.mechanism, params)? - } - _ => return Err(CKR_MECHANISM_INVALID)?, - }; - Ok(Operation::Derive(Box::new(kdf))) - } - fn generate_keypair( &self, mech: &CK_MECHANISM, @@ -469,7 +455,6 @@ impl Mechanism for EccMechanism { pub fn register(mechs: &mut Mechanisms, ot: &mut ObjectFactories) { EccOperation::register_mechanisms(mechs); - ECDHOperation::register_mechanisms(mechs); ot.add_factory( ObjectType::new(CKO_PUBLIC_KEY, CKK_EC), diff --git a/src/ecdh.rs b/src/ecdh.rs new file mode 100644 index 00000000..bcdeee2f --- /dev/null +++ b/src/ecdh.rs @@ -0,0 +1,60 @@ +// Copyright 2024 Simo Sorce, Jakub Jelen +// See LICENSE.txt file for terms + +use std::fmt::Debug; + +use crate::ecc::*; +use crate::error::Result; +use crate::interface::*; +use crate::mechanism::{Mechanism, Mechanisms, Operation}; +use crate::object::ObjectFactories; +use crate::ossl::ecdh::ECDHOperation; + +use crate::cast_params; + +pub fn register(mechs: &mut Mechanisms, _: &mut ObjectFactories) { + ECDHMechanism::register_mechanisms(mechs); +} + +#[derive(Debug)] +struct ECDHMechanism { + info: CK_MECHANISM_INFO, +} + +impl ECDHMechanism { + fn new_mechanism() -> Box { + Box::new(ECDHMechanism { + info: CK_MECHANISM_INFO { + ulMinKeySize: CK_ULONG::try_from(MIN_EC_SIZE_BITS).unwrap(), + ulMaxKeySize: CK_ULONG::try_from(MAX_EC_SIZE_BITS).unwrap(), + flags: CKF_DERIVE, + }, + }) + } + + pub fn register_mechanisms(mechs: &mut Mechanisms) { + for ckm in &[CKM_ECDH1_DERIVE, CKM_ECDH1_COFACTOR_DERIVE] { + mechs.add_mechanism(*ckm, Self::new_mechanism()); + } + } +} + +impl Mechanism for ECDHMechanism { + fn info(&self) -> &CK_MECHANISM_INFO { + &self.info + } + + fn derive_operation(&self, mech: &CK_MECHANISM) -> Result { + if self.info.flags & CKF_DERIVE != CKF_DERIVE { + return Err(CKR_MECHANISM_INVALID)?; + } + let kdf = match mech.mechanism { + CKM_ECDH1_DERIVE | CKM_ECDH1_COFACTOR_DERIVE => { + let params = cast_params!(mech, CK_ECDH1_DERIVE_PARAMS); + ECDHOperation::derive_new(mech.mechanism, params)? + } + _ => return Err(CKR_MECHANISM_INVALID)?, + }; + Ok(Operation::Derive(Box::new(kdf))) + } +} diff --git a/src/enabled.rs b/src/enabled.rs index 2064f855..c316866c 100644 --- a/src/enabled.rs +++ b/src/enabled.rs @@ -13,6 +13,12 @@ mod ecc; #[cfg(any(feature = "ecc", feature = "eddsa"))] mod ecc_misc; +#[cfg(all(feature = "ec_montgomery", not(feature = "fips")))] +mod ec_montgomery; + +#[cfg(any(feature = "ec_montgomery", feature = "ecc"))] +mod ecdh; + #[cfg(all(feature = "eddsa", not(feature = "fips")))] mod eddsa; @@ -52,6 +58,12 @@ pub fn register_all(mechs: &mut Mechanisms, ot: &mut ObjectFactories) { #[cfg(feature = "ecc")] ecc::register(mechs, ot); + #[cfg(any(feature = "ec_montgomery", feature = "ecc"))] + ecdh::register(mechs, ot); + + #[cfg(all(feature = "ec_montgomery", not(feature = "fips")))] + ec_montgomery::register(mechs, ot); + #[cfg(all(feature = "eddsa", not(feature = "fips")))] eddsa::register(mechs, ot); diff --git a/src/ossl/ec_montgomery.rs b/src/ossl/ec_montgomery.rs new file mode 100644 index 00000000..1d8d4a16 --- /dev/null +++ b/src/ossl/ec_montgomery.rs @@ -0,0 +1,167 @@ +// Copyright 2024 Jakub Jelen +// See LICENSE.txt file for terms + +use std::ffi::{c_char, c_int}; + +use crate::attribute::Attribute; +use crate::ecc_misc::*; +use crate::error::Result; +use crate::interface::*; +use crate::object::Object; +use crate::ossl::bindings::*; +use crate::ossl::common::*; + +#[cfg(feature = "fips")] +use crate::ossl::fips::*; + +static OSSL_CURVE25519: &[u8; 7] = b"X25519\0"; +static OSSL_CURVE448: &[u8; 5] = b"X448\0"; + +pub const BITS_CURVE25519: usize = 255; +pub const BITS_CURVE448: usize = 448; + +// ASN.1 encoding of the OID +const OID_CURVE25519: asn1::ObjectIdentifier = asn1::oid!(1, 3, 101, 110); +const OID_CURVE448: asn1::ObjectIdentifier = asn1::oid!(1, 3, 101, 111); + +// ASN.1 encoding of the curve name +const STRING_CURVE25519: &[u8] = &[ + 0x13, 0x0a, 0x63, 0x75, 0x72, 0x76, 0x65, 0x32, 0x35, 0x35, 0x31, 0x39, +]; +const STRING_CURVE448: &[u8] = + &[0x13, 0x08, 0x63, 0x75, 0x72, 0x76, 0x65, 0x34, 0x34, 0x38]; + +fn oid_to_bits(oid: asn1::ObjectIdentifier) -> Result { + match oid { + OID_CURVE25519 => Ok(BITS_CURVE25519), + OID_CURVE448 => Ok(BITS_CURVE448), + _ => Err(CKR_GENERAL_ERROR)?, + } +} + +fn curve_name_to_bits(name: asn1::PrintableString) -> Result { + let asn1_name = match asn1::write_single(&name) { + Ok(r) => r, + Err(_) => return Err(CKR_GENERAL_ERROR)?, + }; + match asn1_name.as_slice() { + STRING_CURVE25519 => Ok(BITS_CURVE25519), + STRING_CURVE448 => Ok(BITS_CURVE448), + _ => Err(CKR_GENERAL_ERROR)?, + } +} + +fn make_bits_from_obj(key: &Object) -> Result { + let x = match key.get_attr_as_bytes(CKA_EC_PARAMS) { + Ok(b) => b, + Err(_) => return Err(CKR_GENERAL_ERROR)?, + }; + let bits = match asn1::parse_single::(x) { + Ok(a) => match a { + ECParameters::OId(o) => oid_to_bits(o)?, + ECParameters::CurveName(c) => curve_name_to_bits(c)?, + _ => return Err(CKR_GENERAL_ERROR)?, + }, + Err(_) => return Err(CKR_GENERAL_ERROR)?, + }; + Ok(bits) +} + +fn get_ossl_name_from_obj(key: &Object) -> Result<&'static [u8]> { + match make_bits_from_obj(key) { + Ok(BITS_CURVE25519) => Ok(OSSL_CURVE25519), + Ok(BITS_CURVE448) => Ok(OSSL_CURVE448), + _ => return Err(CKR_GENERAL_ERROR)?, + } +} + +pub fn make_output_length_from_montgomery_obj(key: &Object) -> Result { + match make_bits_from_obj(key) { + Ok(255) => Ok(64), + Ok(448) => Ok(114), + _ => return Err(CKR_GENERAL_ERROR)?, + } +} + +pub fn make_ec_montgomery_public_key( + key: &Object, + ec_point: &Vec, +) -> Result { + let mut params = OsslParam::with_capacity(1); + params.zeroize = true; + params.add_octet_string(name_as_char(OSSL_PKEY_PARAM_PUB_KEY), ec_point)?; + params.finalize(); + + EvpPkey::fromdata( + get_ossl_name_from_obj(key)?.as_ptr() as *const c_char, + EVP_PKEY_PUBLIC_KEY, + ¶ms, + ) +} + +/// Convert the PKCS #11 private key object to OpenSSL EVP_PKEY +pub fn montgomery_object_to_ecc_private_key(key: &Object) -> Result { + let priv_key = match key.get_attr_as_bytes(CKA_VALUE) { + Ok(v) => v, + Err(_) => return Err(CKR_DEVICE_ERROR)?, + }; + let mut priv_key_octet: Vec = Vec::with_capacity(priv_key.len() + 2); + priv_key_octet.push(4); /* tag octet string */ + priv_key_octet.push(u8::try_from(priv_key.len())?); /* length */ + priv_key_octet.extend(priv_key); + + let mut params = OsslParam::with_capacity(1); + params.zeroize = true; + params + .add_octet_string(name_as_char(OSSL_PKEY_PARAM_PRIV_KEY), priv_key)?; + params.finalize(); + + EvpPkey::fromdata( + get_ossl_name_from_obj(key)?.as_ptr() as *const c_char, + EVP_PKEY_PRIVATE_KEY, + ¶ms, + ) +} + +#[derive(Debug)] +pub struct ECMontgomeryOperation {} + +impl ECMontgomeryOperation { + pub fn generate_keypair( + pubkey: &mut Object, + privkey: &mut Object, + ) -> Result<()> { + let evp_pkey = EvpPkey::generate( + get_ossl_name_from_obj(pubkey)?.as_ptr() as *const c_char, + &OsslParam::empty(), + )?; + + let mut params: *mut OSSL_PARAM = std::ptr::null_mut(); + let res = unsafe { + EVP_PKEY_todata( + evp_pkey.as_ptr(), + c_int::try_from(EVP_PKEY_KEYPAIR)?, + &mut params, + ) + }; + if res != 1 { + return Err(CKR_DEVICE_ERROR)?; + } + let params = OsslParam::from_ptr(params)?; + /* Public Key */ + let point_encoded = match asn1::write_single( + ¶ms.get_octet_string(name_as_char(OSSL_PKEY_PARAM_PUB_KEY))?, + ) { + Ok(b) => b, + Err(_) => return Err(CKR_GENERAL_ERROR)?, + }; + pubkey.set_attr(Attribute::from_bytes(CKA_EC_POINT, point_encoded))?; + + /* Private Key */ + let value = params + .get_octet_string(name_as_char(OSSL_PKEY_PARAM_PRIV_KEY))? + .to_vec(); + privkey.set_attr(Attribute::from_bytes(CKA_VALUE, value))?; + Ok(()) + } +} diff --git a/src/ossl/ecc.rs b/src/ossl/ecc.rs index d0fcbb1c..83ba8694 100644 --- a/src/ossl/ecc.rs +++ b/src/ossl/ecc.rs @@ -1,20 +1,19 @@ // Copyright 2023 - 2024 Simo Sorce, Jakub Jelen // See LICENSE.txt file for terms -use core::ffi::{c_char, c_int, c_uint}; -use std::borrow::Cow; +use core::ffi::c_int; -use crate::attribute::{Attribute, CkAttrs}; +use crate::attribute::Attribute; use crate::ecc::*; use crate::ecc_misc::*; use crate::error::Result; use crate::interface::*; use crate::kasn1::DerEncBigUint; use crate::mechanism::*; -use crate::object::{default_key_attributes, Object, ObjectFactories}; +use crate::object::Object; use crate::ossl::bindings::*; use crate::ossl::common::*; -use crate::{bytes_to_vec, some_or_err}; +use crate::some_or_err; #[cfg(feature = "fips")] use crate::ossl::fips::*; @@ -45,7 +44,7 @@ fn make_bits_from_ec_params(key: &Object) -> Result { Ok(bits) } -fn make_output_length_from_obj(key: &Object) -> Result { +pub fn make_output_length_from_ecdsa_obj(key: &Object) -> Result { let bits = match make_bits_from_ec_params(key) { Ok(b) => b, Err(_) => return Err(CKR_GENERAL_ERROR)?, @@ -53,7 +52,7 @@ fn make_output_length_from_obj(key: &Object) -> Result { Ok(2 * ((bits + 7) / 8)) } -fn get_curve_name_from_obj(key: &Object) -> Result> { +pub fn get_curve_name_from_obj(key: &Object) -> Result> { let x = match key.get_attr_as_bytes(CKA_EC_PARAMS) { Ok(b) => b, Err(_) => return Err(CKR_GENERAL_ERROR)?, @@ -87,7 +86,7 @@ fn get_ec_point_from_obj(key: &Object) -> Result> { Ok(octet.to_vec()) } -fn make_ecc_public_key( +pub fn make_ecdsa_public_key( curve_name: &Vec, ec_point: &Vec, ) -> Result { @@ -105,14 +104,14 @@ fn make_ecc_public_key( /// Convert the PKCS #11 public key object to OpenSSL EVP_PKEY fn object_to_ecc_public_key(key: &Object) -> Result { - make_ecc_public_key( + make_ecdsa_public_key( &get_curve_name_from_obj(key)?, &get_ec_point_from_obj(key)?, ) } /// Convert the PKCS #11 private key object to OpenSSL EVP_PKEY -fn object_to_ecc_private_key(key: &Object) -> Result { +pub fn ecdsa_object_to_ecc_private_key(key: &Object) -> Result { let curve_name = get_curve_name_from_obj(key)?; let mut params = OsslParam::with_capacity(2); params.zeroize = true; @@ -253,9 +252,9 @@ impl EccOperation { ) -> Result { Ok(EccOperation { mech: mech.mechanism, - output_len: make_output_length_from_obj(key)?, + output_len: make_output_length_from_ecdsa_obj(key)?, public_key: None, - private_key: Some(object_to_ecc_private_key(key)?), + private_key: Some(ecdsa_object_to_ecc_private_key(key)?), finalized: false, in_use: false, sigctx: match mech.mechanism { @@ -275,7 +274,7 @@ impl EccOperation { ) -> Result { Ok(EccOperation { mech: mech.mechanism, - output_len: make_output_length_from_obj(key)?, + output_len: make_output_length_from_ecdsa_obj(key)?, public_key: Some(object_to_ecc_public_key(key)?), private_key: None, finalized: false, @@ -639,233 +638,3 @@ impl Verify for EccOperation { Ok(self.output_len) } } - -fn kdf_type_to_hash_mech(mech: CK_EC_KDF_TYPE) -> Result { - match mech { - CKD_SHA1_KDF => Ok(CKM_SHA_1), - CKD_SHA224_KDF => Ok(CKM_SHA224), - CKD_SHA256_KDF => Ok(CKM_SHA256), - CKD_SHA384_KDF => Ok(CKM_SHA384), - CKD_SHA512_KDF => Ok(CKM_SHA512), - CKD_SHA3_224_KDF => Ok(CKM_SHA3_224), - CKD_SHA3_256_KDF => Ok(CKM_SHA3_256), - CKD_SHA3_384_KDF => Ok(CKM_SHA3_384), - CKD_SHA3_512_KDF => Ok(CKM_SHA3_512), - _ => return Err(CKR_MECHANISM_PARAM_INVALID)?, - } -} - -#[derive(Debug)] -pub struct ECDHOperation { - mech: CK_MECHANISM_TYPE, - kdf: CK_EC_KDF_TYPE, - public: Vec, - shared: Vec, - finalized: bool, -} - -impl ECDHOperation { - fn new_mechanism() -> Box { - Box::new(EccMechanism::new( - CK_ULONG::try_from(MIN_EC_SIZE_BITS).unwrap(), - CK_ULONG::try_from(MAX_EC_SIZE_BITS).unwrap(), - CKF_DERIVE, - )) - } - - pub fn register_mechanisms(mechs: &mut Mechanisms) { - for ckm in &[CKM_ECDH1_DERIVE, CKM_ECDH1_COFACTOR_DERIVE] { - mechs.add_mechanism(*ckm, Self::new_mechanism()); - } - } - - pub fn derive_new<'a>( - mechanism: CK_MECHANISM_TYPE, - params: CK_ECDH1_DERIVE_PARAMS, - ) -> Result { - if params.kdf == CKD_NULL { - if params.pSharedData != std::ptr::null_mut() - || params.ulSharedDataLen != 0 - { - return Err(CKR_MECHANISM_PARAM_INVALID)?; - } - } - if params.pPublicData == std::ptr::null_mut() - || params.ulPublicDataLen == 0 - { - return Err(CKR_MECHANISM_PARAM_INVALID)?; - } - - Ok(ECDHOperation { - finalized: false, - mech: mechanism, - kdf: params.kdf, - shared: bytes_to_vec!(params.pSharedData, params.ulSharedDataLen), - public: bytes_to_vec!(params.pPublicData, params.ulPublicDataLen), - }) - } -} - -impl MechOperation for ECDHOperation { - fn mechanism(&self) -> Result { - Ok(self.mech) - } - - fn finalized(&self) -> bool { - self.finalized - } -} - -impl Derive for ECDHOperation { - fn derive( - &mut self, - key: &Object, - template: &[CK_ATTRIBUTE], - _mechanisms: &Mechanisms, - objfactories: &ObjectFactories, - ) -> Result> { - if self.finalized { - return Err(CKR_OPERATION_NOT_INITIALIZED)?; - } - self.finalized = true; - - let mode: c_int = if self.mech == CKM_ECDH1_COFACTOR_DERIVE { - 1 - } else { - -1 - }; - let outlen: c_uint; - - let mut params = OsslParam::with_capacity(5); - params.zeroize = true; - params.add_int( - name_as_char(OSSL_EXCHANGE_PARAM_EC_ECDH_COFACTOR_MODE), - &mode, - )?; - - let factory = - objfactories.get_obj_factory_from_key_template(template)?; - - /* the raw ECDH results have length of bit field length */ - let raw_max = make_output_length_from_obj(key)?; - let keylen = match template.iter().find(|x| x.type_ == CKA_VALUE_LEN) { - Some(a) => { - let value_len = usize::try_from(a.to_ulong()?)?; - if self.kdf == CKD_NULL && value_len > raw_max { - return Err(CKR_TEMPLATE_INCONSISTENT)?; - } - value_len - } - None => { - /* X9.63 does not have any maximum size */ - if self.kdf != CKD_NULL { - return Err(CKR_TEMPLATE_INCONSISTENT)?; - } - match factory - .as_secret_key_factory()? - .recommend_key_size(raw_max) - { - Ok(len) => len, - Err(_) => return Err(CKR_TEMPLATE_INCONSISTENT)?, - } - } - }; - /* these do not apply to the raw ECDH */ - match self.kdf { - CKD_SHA1_KDF | CKD_SHA224_KDF | CKD_SHA256_KDF | CKD_SHA384_KDF - | CKD_SHA512_KDF | CKD_SHA3_224_KDF | CKD_SHA3_256_KDF - | CKD_SHA3_384_KDF | CKD_SHA3_512_KDF => { - params.add_const_c_string( - name_as_char(OSSL_EXCHANGE_PARAM_KDF_TYPE), - OSSL_KDF_NAME_X963KDF.as_ptr() as *const c_char, - )?; - params.add_const_c_string( - name_as_char(OSSL_EXCHANGE_PARAM_KDF_DIGEST), - mech_type_to_digest_name(kdf_type_to_hash_mech(self.kdf)?), - )?; - if self.shared.len() > 0 { - params.add_octet_string( - name_as_char(OSSL_EXCHANGE_PARAM_KDF_UKM), - &self.shared, - )?; - } - outlen = c_uint::try_from(keylen)?; - params.add_uint( - name_as_char(OSSL_EXCHANGE_PARAM_KDF_OUTLEN), - &outlen, - )?; - } - CKD_NULL => (), - _ => return Err(CKR_MECHANISM_PARAM_INVALID)?, - } - - params.finalize(); - - let mut pkey = object_to_ecc_private_key(key)?; - let mut ctx = pkey.new_ctx()?; - let res = unsafe { - EVP_PKEY_derive_init_ex(ctx.as_mut_ptr(), params.as_ptr()) - }; - if res != 1 { - return Err(CKR_DEVICE_ERROR)?; - } - - let ec_point = { - let ec_point_size = make_output_length_from_obj(key)? + 1; - if self.public.len() > ec_point_size { - /* try to see if it is a DER encoded point */ - match asn1::parse_single::<&[u8]>(self.public.as_slice()) { - Ok(pt) => Cow::Owned(pt.to_vec()), - Err(_) => return Err(CKR_MECHANISM_PARAM_INVALID)?, - } - } else { - Cow::Borrowed(&self.public) - } - }; - - /* Import peer key */ - let mut peer = - make_ecc_public_key(&get_curve_name_from_obj(key)?, &ec_point)?; - - let res = unsafe { - EVP_PKEY_derive_set_peer(ctx.as_mut_ptr(), peer.as_mut_ptr()) - }; - if res != 1 { - return Err(CKR_DEVICE_ERROR)?; - } - - let mut secret_len = 0usize; - let res = unsafe { - EVP_PKEY_derive( - ctx.as_mut_ptr(), - std::ptr::null_mut(), - &mut secret_len, - ) - }; - if res != 1 { - return Err(CKR_DEVICE_ERROR)?; - } - if secret_len < keylen { - return Err(CKR_TEMPLATE_INCONSISTENT)?; - } - let mut secret = vec![0u8; secret_len]; - let res = unsafe { - EVP_PKEY_derive( - ctx.as_mut_ptr(), - secret.as_mut_ptr(), - &mut secret_len, - ) - }; - if res != 1 { - return Err(CKR_DEVICE_ERROR)?; - } - - let mut tmpl = CkAttrs::from(template); - tmpl.add_owned_slice(CKA_VALUE, &secret[(secret_len - keylen)..])?; - tmpl.zeroize = true; - let mut obj = factory.create(tmpl.as_slice())?; - - default_key_attributes(&mut obj, self.mech)?; - Ok(vec![obj]) - } -} diff --git a/src/ossl/ecdh.rs b/src/ossl/ecdh.rs new file mode 100644 index 00000000..53a1b4ed --- /dev/null +++ b/src/ossl/ecdh.rs @@ -0,0 +1,274 @@ +// Copyright 2023 - 2024 Simo Sorce, Jakub Jelen +// See LICENSE.txt file for terms + +use core::ffi::{c_char, c_int, c_uint}; +use std::borrow::Cow; + +use crate::attribute::CkAttrs; +use crate::bytes_to_vec; +use crate::error::Result; +use crate::interface::*; +use crate::mechanism::*; +use crate::object::{default_key_attributes, Object, ObjectFactories}; +use crate::ossl::bindings::*; +use crate::ossl::common::*; +#[cfg(all(feature = "ec_montgomery", not(feature = "fips")))] +use crate::ossl::ec_montgomery::{ + make_ec_montgomery_public_key, make_output_length_from_montgomery_obj, + montgomery_object_to_ecc_private_key, +}; +#[cfg(feature = "ecc")] +use crate::ossl::ecc::{ + ecdsa_object_to_ecc_private_key, get_curve_name_from_obj, + make_ecdsa_public_key, make_output_length_from_ecdsa_obj, +}; + +fn kdf_type_to_hash_mech(mech: CK_EC_KDF_TYPE) -> Result { + match mech { + CKD_SHA1_KDF => Ok(CKM_SHA_1), + CKD_SHA224_KDF => Ok(CKM_SHA224), + CKD_SHA256_KDF => Ok(CKM_SHA256), + CKD_SHA384_KDF => Ok(CKM_SHA384), + CKD_SHA512_KDF => Ok(CKM_SHA512), + CKD_SHA3_224_KDF => Ok(CKM_SHA3_224), + CKD_SHA3_256_KDF => Ok(CKM_SHA3_256), + CKD_SHA3_384_KDF => Ok(CKM_SHA3_384), + CKD_SHA3_512_KDF => Ok(CKM_SHA3_512), + _ => return Err(CKR_MECHANISM_PARAM_INVALID)?, + } +} + +fn make_derive_output_length_from_obj(key: &Object) -> Result { + let key_type = key.get_attr_as_ulong(CKA_KEY_TYPE)?; + match key_type { + #[cfg(feature = "ecc")] + CKK_EC => make_output_length_from_ecdsa_obj(key), + #[cfg(all(feature = "ec_montgomery", not(feature = "fips")))] + CKK_EC_MONTGOMERY => make_output_length_from_montgomery_obj(key), + _ => return Err(CKR_KEY_TYPE_INCONSISTENT)?, + } +} + +fn derive_object_to_ecc_private_key(key: &Object) -> Result { + let key_type = key.get_attr_as_ulong(CKA_KEY_TYPE)?; + match key_type { + #[cfg(feature = "ecc")] + CKK_EC => ecdsa_object_to_ecc_private_key(key), + #[cfg(all(feature = "ec_montgomery", not(feature = "fips")))] + CKK_EC_MONTGOMERY => montgomery_object_to_ecc_private_key(key), + _ => return Err(CKR_KEY_TYPE_INCONSISTENT)?, + } +} + +fn make_peer_key(key: &Object, ec_point: &Vec) -> Result { + let key_type = key.get_attr_as_ulong(CKA_KEY_TYPE)?; + match key_type { + #[cfg(feature = "ecc")] + CKK_EC => { + make_ecdsa_public_key(&get_curve_name_from_obj(key)?, &ec_point) + } + #[cfg(all(feature = "ec_montgomery", not(feature = "fips")))] + CKK_EC_MONTGOMERY => make_ec_montgomery_public_key(key, ec_point), + _ => return Err(CKR_KEY_TYPE_INCONSISTENT)?, + } +} + +#[derive(Debug)] +pub struct ECDHOperation { + mech: CK_MECHANISM_TYPE, + kdf: CK_EC_KDF_TYPE, + public: Vec, + shared: Vec, + finalized: bool, +} + +impl ECDHOperation { + pub fn derive_new<'a>( + mechanism: CK_MECHANISM_TYPE, + params: CK_ECDH1_DERIVE_PARAMS, + ) -> Result { + if params.kdf == CKD_NULL { + if params.pSharedData != std::ptr::null_mut() + || params.ulSharedDataLen != 0 + { + return Err(CKR_MECHANISM_PARAM_INVALID)?; + } + } + if params.pPublicData == std::ptr::null_mut() + || params.ulPublicDataLen == 0 + { + return Err(CKR_MECHANISM_PARAM_INVALID)?; + } + + Ok(ECDHOperation { + finalized: false, + mech: mechanism, + kdf: params.kdf, + shared: bytes_to_vec!(params.pSharedData, params.ulSharedDataLen), + public: bytes_to_vec!(params.pPublicData, params.ulPublicDataLen), + }) + } +} + +impl MechOperation for ECDHOperation { + fn mechanism(&self) -> Result { + Ok(self.mech) + } + + fn finalized(&self) -> bool { + self.finalized + } +} + +impl Derive for ECDHOperation { + fn derive( + &mut self, + key: &Object, + template: &[CK_ATTRIBUTE], + _mechanisms: &Mechanisms, + objfactories: &ObjectFactories, + ) -> Result> { + if self.finalized { + return Err(CKR_OPERATION_NOT_INITIALIZED)?; + } + self.finalized = true; + + let mode: c_int = if self.mech == CKM_ECDH1_COFACTOR_DERIVE { + 1 + } else { + -1 + }; + let outlen: c_uint; + + let mut params = OsslParam::with_capacity(5); + params.zeroize = true; + params.add_int( + name_as_char(OSSL_EXCHANGE_PARAM_EC_ECDH_COFACTOR_MODE), + &mode, + )?; + + let factory = + objfactories.get_obj_factory_from_key_template(template)?; + + /* the raw ECDH results have length of bit field length */ + let raw_max = make_derive_output_length_from_obj(key)?; + let keylen = match template.iter().find(|x| x.type_ == CKA_VALUE_LEN) { + Some(a) => { + let value_len = usize::try_from(a.to_ulong()?)?; + if self.kdf == CKD_NULL && value_len > raw_max { + return Err(CKR_TEMPLATE_INCONSISTENT)?; + } + value_len + } + None => { + /* X9.63 does not have any maximum size */ + if self.kdf != CKD_NULL { + return Err(CKR_TEMPLATE_INCONSISTENT)?; + } + match factory + .as_secret_key_factory()? + .recommend_key_size(raw_max) + { + Ok(len) => len, + Err(_) => return Err(CKR_TEMPLATE_INCONSISTENT)?, + } + } + }; + /* these do not apply to the raw ECDH */ + match self.kdf { + CKD_SHA1_KDF | CKD_SHA224_KDF | CKD_SHA256_KDF | CKD_SHA384_KDF + | CKD_SHA512_KDF | CKD_SHA3_224_KDF | CKD_SHA3_256_KDF + | CKD_SHA3_384_KDF | CKD_SHA3_512_KDF => { + params.add_const_c_string( + name_as_char(OSSL_EXCHANGE_PARAM_KDF_TYPE), + OSSL_KDF_NAME_X963KDF.as_ptr() as *const c_char, + )?; + params.add_const_c_string( + name_as_char(OSSL_EXCHANGE_PARAM_KDF_DIGEST), + mech_type_to_digest_name(kdf_type_to_hash_mech(self.kdf)?), + )?; + if self.shared.len() > 0 { + params.add_octet_string( + name_as_char(OSSL_EXCHANGE_PARAM_KDF_UKM), + &self.shared, + )?; + } + outlen = c_uint::try_from(keylen)?; + params.add_uint( + name_as_char(OSSL_EXCHANGE_PARAM_KDF_OUTLEN), + &outlen, + )?; + } + CKD_NULL => (), + _ => return Err(CKR_MECHANISM_PARAM_INVALID)?, + } + + params.finalize(); + + let mut pkey = derive_object_to_ecc_private_key(key)?; + let mut ctx = pkey.new_ctx()?; + let res = unsafe { + EVP_PKEY_derive_init_ex(ctx.as_mut_ptr(), params.as_ptr()) + }; + if res != 1 { + return Err(CKR_DEVICE_ERROR)?; + } + + let ec_point = { + let ec_point_size = make_derive_output_length_from_obj(key)? + 1; + if self.public.len() > ec_point_size { + /* try to see if it is a DER encoded point */ + match asn1::parse_single::<&[u8]>(self.public.as_slice()) { + Ok(pt) => Cow::Owned(pt.to_vec()), + Err(_) => return Err(CKR_MECHANISM_PARAM_INVALID)?, + } + } else { + Cow::Borrowed(&self.public) + } + }; + + /* Import peer key */ + let mut peer = make_peer_key(key, &ec_point)?; + + let res = unsafe { + EVP_PKEY_derive_set_peer(ctx.as_mut_ptr(), peer.as_mut_ptr()) + }; + if res != 1 { + return Err(CKR_DEVICE_ERROR)?; + } + + let mut secret_len = 0usize; + let res = unsafe { + EVP_PKEY_derive( + ctx.as_mut_ptr(), + std::ptr::null_mut(), + &mut secret_len, + ) + }; + if res != 1 { + return Err(CKR_DEVICE_ERROR)?; + } + if secret_len < keylen { + return Err(CKR_TEMPLATE_INCONSISTENT)?; + } + let mut secret = vec![0u8; secret_len]; + let res = unsafe { + EVP_PKEY_derive( + ctx.as_mut_ptr(), + secret.as_mut_ptr(), + &mut secret_len, + ) + }; + if res != 1 { + return Err(CKR_DEVICE_ERROR)?; + } + + let mut tmpl = CkAttrs::from(template); + tmpl.add_owned_slice(CKA_VALUE, &secret[(secret_len - keylen)..])?; + tmpl.zeroize = true; + let mut obj = factory.create(tmpl.as_slice())?; + + default_key_attributes(&mut obj, self.mech)?; + Ok(vec![obj]) + } +} diff --git a/src/ossl/mod.rs b/src/ossl/mod.rs index a3cdba3a..e8058125 100644 --- a/src/ossl/mod.rs +++ b/src/ossl/mod.rs @@ -32,9 +32,16 @@ pub mod aes; pub mod common; pub mod drbg; +// the derive code for both ECDSA and Montgomery curves +#[cfg(any(feature = "ecc", feature = "ec_montgomery"))] +pub mod ecdh; + #[cfg(feature = "ecc")] pub mod ecc; +#[cfg(all(feature = "ec_montgomery", not(feature = "fips")))] +pub mod ec_montgomery; + #[cfg(all(feature = "eddsa", not(feature = "fips")))] pub mod eddsa; diff --git a/src/tests/ec_montgomery.rs b/src/tests/ec_montgomery.rs new file mode 100644 index 00000000..5d94172d --- /dev/null +++ b/src/tests/ec_montgomery.rs @@ -0,0 +1,386 @@ +// Copyright 2024 Jakub Jelen +// See LICENSE.txt file for terms + +use crate::tests::*; + +use serial_test::parallel; + +#[test] +#[parallel] +fn test_create_ec_montgomery_objects() { + let mut testtokn = + TestToken::initialized("test_create_ec_montgomery_objects.sql", None); + let session = testtokn.get_session(true); + + /* login */ + testtokn.login(); + + let point = hex::decode( + "8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a", + ) + .expect("Failed to decode hex point"); + let params = hex::decode("130a63757276653235353139") + .expect("Failed to decode hex params"); + let _ = ret_or_panic!(import_object( + session, + CKO_PUBLIC_KEY, + &[(CKA_KEY_TYPE, CKK_EC_MONTGOMERY)], + &[ + (CKA_LABEL, "EC Montgomery Public Key".as_bytes()), + (CKA_EC_POINT, point.as_slice()), + (CKA_EC_PARAMS, params.as_slice()), + ], + &[(CKA_DERIVE, true)] + )); + + /* Private EC key */ + let value = hex::decode( + "77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a", + ) + .expect("Failed to decode value"); + let _ = ret_or_panic!(import_object( + session, + CKO_PRIVATE_KEY, + &[(CKA_KEY_TYPE, CKK_EC_MONTGOMERY)], + &[ + (CKA_LABEL, "EC Montgomery Private Key".as_bytes()), + (CKA_VALUE, value.as_slice()), + (CKA_EC_PARAMS, params.as_slice()), + ], + &[(CKA_DERIVE, true)] + )); + + testtokn.finalize(); +} + +#[derive(Debug)] +struct TestUnit<'a> { + curve: &'a str, + ec_params: &'a str, + a_priv: &'a str, + a_pub: &'a str, + b_priv: &'a str, + b_pub: &'a str, + secret: &'a str, +} + +/* Test vectors from + * https://datatracker.ietf.org/doc/html/rfc7748#section-6.1 + */ +#[test] +#[parallel] +fn test_ec_montgomery_derive_x25519() { + test_ec_montgomery_derive(TestUnit { + curve: "x25519", + ec_params: "130a63757276653235353139", + a_priv: + "77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a", + a_pub: + "8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a", + b_priv: + "5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e0eb", + b_pub: + "de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f", + secret: + "4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742", + }) +} + +#[test] +#[parallel] +fn test_ec_montgomery_derive_x448() { + test_ec_montgomery_derive(TestUnit { + curve: "x448", + ec_params: "13086375727665343438", + a_priv: concat!( + "9a8f4925d1519f5775cf46b04b5800d4ee9ee8bae8bc5565d498c28dd9c9ba", + "f574a9419744897391006382a6f127ab1d9ac2d8c0a598726b" + ), + a_pub: concat!( + "9b08f7cc31b7e3e67d22d5aea121074a273bd2b83de09c63faa73d2c22c5d9", + "bbc836647241d953d40c5b12da88120d53177f80e532c41fa0" + ), + b_priv: concat!( + "1c306a7ac2a0e2e0990b294470cba339e6453772b075811d8fad0d1d6927c1", + "20bb5ee8972b0d3e21374c9c921b09d1b0366f10b65173992d" + ), + b_pub: concat!( + "3eb7a829b0cd20f5bcfc0b599b6feccf6da4627107bdb0d4f345b43027d8b97", + "2fc3e34fb4232a13ca706dcb57aec3dae07bdc1c67bf33609" + ), + secret: concat!( + "07fff4181ac6cc95ec1c16a94a0f74d12da232ce40a77552281d282bb60c0b5", + "6fd2464c335543936521c24403085d59a449a5037514a879d" + ), + }) +} + +fn test_ec_montgomery_derive(t: TestUnit) { + let dbname = format!("test_ec_montgomery_derive_{}.sql", t.curve); + let mut testtokn = TestToken::initialized(&dbname, None); + let session = testtokn.get_session(true); + + /* login */ + testtokn.login(); + + // Alice's Public key + let mut alice_point = + hex::decode(t.a_pub).expect("Failed to decode hex point"); + let params = hex::decode(t.ec_params).expect("Failed to decode hex params"); + + /* Alice's Private key */ + let alice_value = + hex::decode(t.a_priv).expect("Failed to decode alice's value"); + let alice_handle = ret_or_panic!(import_object( + session, + CKO_PRIVATE_KEY, + &[(CKA_KEY_TYPE, CKK_EC_MONTGOMERY)], + &[ + (CKA_LABEL, "Alice's EC Montgomery Private Key".as_bytes()), + (CKA_VALUE, alice_value.as_slice()), + (CKA_EC_PARAMS, params.as_slice()), + ], + &[(CKA_DERIVE, true)] + )); + + /* Bob's Private key */ + let bob_value = + hex::decode(t.b_priv).expect("Failed to decode bob's value"); + let mut bob_point = + hex::decode(t.b_pub).expect("Failed to decode bob's point"); + let bob_handle = ret_or_panic!(import_object( + session, + CKO_PRIVATE_KEY, + &[(CKA_KEY_TYPE, CKK_EC_MONTGOMERY)], + &[ + (CKA_LABEL, "Bob's EC Montgomery Private Key".as_bytes()), + (CKA_VALUE, bob_value.as_slice()), + (CKA_EC_PARAMS, params.as_slice()), + ], + &[(CKA_DERIVE, true)] + )); + + /* derive plain key without shared data */ + let mut params = CK_ECDH1_DERIVE_PARAMS { + kdf: CKD_NULL, + ulSharedDataLen: 0, + pSharedData: std::ptr::null_mut(), + ulPublicDataLen: bob_point.len() as CK_ULONG, + pPublicData: bob_point.as_mut_ptr(), + }; + let mut mechanism: CK_MECHANISM = CK_MECHANISM { + mechanism: CKM_ECDH1_DERIVE, + pParameter: &mut params as *mut _ as CK_VOID_PTR, + ulParameterLen: sizeof!(CK_ECDH1_DERIVE_PARAMS), + }; + + let derive_template = make_attr_template( + &[ + (CKA_CLASS, CKO_SECRET_KEY), + (CKA_KEY_TYPE, CKK_AES), + (CKA_VALUE_LEN, 32), + ], + &[], + &[ + (CKA_ENCRYPT, true), + (CKA_DECRYPT, true), + (CKA_EXTRACTABLE, true), + ], + ); + + let ref_value = + hex::decode(t.secret).expect("Failed to decode expected shared secret"); + + let mut s_handle = CK_INVALID_HANDLE; + let ret = fn_derive_key( + session, + &mut mechanism, + alice_handle, + derive_template.as_ptr() as *mut _, + derive_template.len() as CK_ULONG, + &mut s_handle, + ); + assert_eq!(ret, CKR_OK); + + let mut value = vec![0u8; 32]; + let mut extract_template = make_ptrs_template(&[( + CKA_VALUE, + void_ptr!(value.as_mut_ptr()), + value.len(), + )]); + + let ret = fn_get_attribute_value( + session, + s_handle, + extract_template.as_mut_ptr(), + extract_template.len() as CK_ULONG, + ); + assert_eq!(ret, CKR_OK); + assert_eq!(value, ref_value[(ref_value.len() - 32)..]); + + /* Do the same on the Bob's side */ + params.pPublicData = alice_point.as_mut_ptr(); + // the size matches + + let mut s_handle = CK_INVALID_HANDLE; + let ret = fn_derive_key( + session, + &mut mechanism, + bob_handle, + derive_template.as_ptr() as *mut _, + derive_template.len() as CK_ULONG, + &mut s_handle, + ); + assert_eq!(ret, CKR_OK); + + let mut value = vec![0u8; 32]; + let mut extract_template = make_ptrs_template(&[( + CKA_VALUE, + void_ptr!(value.as_mut_ptr()), + value.len(), + )]); + + let ret = fn_get_attribute_value( + session, + s_handle, + extract_template.as_mut_ptr(), + extract_template.len() as CK_ULONG, + ); + assert_eq!(ret, CKR_OK); + assert_eq!(value, ref_value[(ref_value.len() - 32)..]); + + /* Test the derived key works */ + /* Data need to be exactly one block in size for CBC */ + let data = "0123456789ABCDEF"; + let iv = "FEDCBA0987654321"; + let enc = ret_or_panic!(encrypt( + session, + s_handle, + data.as_bytes(), + &CK_MECHANISM { + mechanism: CKM_AES_CBC, + pParameter: void_ptr!(iv.as_bytes()), + ulParameterLen: iv.len() as CK_ULONG, + } + )); + assert_eq!(enc.len(), 16); + + let dec = ret_or_panic!(decrypt( + session, + s_handle, + enc.as_slice(), + &CK_MECHANISM { + mechanism: CKM_AES_CBC, + pParameter: void_ptr!(iv.as_bytes()), + ulParameterLen: iv.len() as CK_ULONG, + } + )); + assert_eq!(dec.len(), data.len()); + assert_eq!(data.as_bytes(), dec.as_slice()); + + /* Invalid parameters: Missing peer public key */ + params.ulPublicDataLen = 0; + params.pPublicData = std::ptr::null_mut(); + + let mut s_handle = CK_INVALID_HANDLE; + let ret = fn_derive_key( + session, + &mut mechanism, + alice_handle, + derive_template.as_ptr() as *mut _, + derive_template.len() as CK_ULONG, + &mut s_handle, + ); + assert_eq!(ret, CKR_MECHANISM_PARAM_INVALID); + params.ulPublicDataLen = bob_point.len() as CK_ULONG; + params.pPublicData = bob_point.as_mut_ptr(); + + /* Invalid parameters: Shared data are not supported for NULL KDF */ + let shared = "shared data"; + params.ulSharedDataLen = shared.len() as CK_ULONG; + params.pSharedData = shared.as_ptr() as *mut u8; + + let mut s_handle = CK_INVALID_HANDLE; + let ret = fn_derive_key( + session, + &mut mechanism, + alice_handle, + derive_template.as_ptr() as *mut _, + derive_template.len() as CK_ULONG, + &mut s_handle, + ); + assert_eq!(ret, CKR_MECHANISM_PARAM_INVALID); + params.ulSharedDataLen = 0; + params.pSharedData = std::ptr::null_mut(); + + /* Invalid parameters: Blake kdf */ + params.kdf = CKD_BLAKE2B_160_KDF; + + let mut s_handle = CK_INVALID_HANDLE; + let ret = fn_derive_key( + session, + &mut mechanism, + alice_handle, + derive_template.as_ptr() as *mut _, + derive_template.len() as CK_ULONG, + &mut s_handle, + ); + assert_eq!(ret, CKR_MECHANISM_PARAM_INVALID); + params.kdf = CKD_NULL; + + /* Without the explicit CKA_VALUE_LEN -- we should get "reasonable default" for AES */ + let derive_template = make_attr_template( + &[(CKA_CLASS, CKO_SECRET_KEY), (CKA_KEY_TYPE, CKK_AES)], + &[], + &[(CKA_EXTRACTABLE, true)], + ); + let mut s_handle = CK_INVALID_HANDLE; + let ret = fn_derive_key( + session, + &mut mechanism, + alice_handle, + derive_template.as_ptr() as *mut _, + derive_template.len() as CK_ULONG, + &mut s_handle, + ); + assert_eq!(ret, CKR_OK); + + let mut value = vec![0u8; 32]; + let mut extract_template = make_ptrs_template(&[( + CKA_VALUE, + void_ptr!(value.as_mut_ptr()), + value.len(), + )]); + + let ret = fn_get_attribute_value( + session, + s_handle, + extract_template.as_mut_ptr(), + extract_template.len() as CK_ULONG, + ); + assert_eq!(ret, CKR_OK); + assert_eq!(value, ref_value[(ref_value.len() - 32)..]); + + /* With GENERIC_SECRET and explicit CKA_VALUE_LEN larger than field size we should fail */ + let derive_template = make_attr_template( + &[ + (CKA_CLASS, CKO_SECRET_KEY), + (CKA_KEY_TYPE, CKK_GENERIC_SECRET), + (CKA_VALUE_LEN, ref_value.len() as CK_ULONG + 1), + ], + &[], + &[(CKA_EXTRACTABLE, true)], + ); + let mut s_handle = CK_INVALID_HANDLE; + let ret = fn_derive_key( + session, + &mut mechanism, + alice_handle, + derive_template.as_ptr() as *mut _, + derive_template.len() as CK_ULONG, + &mut s_handle, + ); + assert_eq!(ret, CKR_TEMPLATE_INCONSISTENT); + + testtokn.finalize(); +} diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 6c13e945..29e3a146 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -253,6 +253,9 @@ mod session; #[cfg(feature = "ecc")] mod ecc; +#[cfg(all(feature = "ec_montgomery", not(feature = "fips")))] +mod ec_montgomery; + #[cfg(feature = "ecc")] mod ecdh;