diff --git a/cryptoki/src/mechanism/eddsa.rs b/cryptoki/src/mechanism/eddsa.rs new file mode 100644 index 00000000..0471933a --- /dev/null +++ b/cryptoki/src/mechanism/eddsa.rs @@ -0,0 +1,175 @@ +//! EdDSA mechanism types + +use cryptoki_sys::*; +use std::{convert::TryInto, ffi::c_void, marker::PhantomData, ptr::null_mut}; + +/// EdDSA signature schemes. +/// +/// The EdDSA mechanism, denoted CKM_EDDSA, is a mechanism for +/// single-part and multipart signatures and verification for +/// EdDSA. This mechanism implements the five EdDSA signature +/// schemes defined in RFC 8032 and RFC 8410. +/// +/// For curves according to RFC 8032, this mechanism has an +/// optional parameter, a CK_EDDSA_PARAMS structure. +/// +/// | Signature Scheme | Mechanism Param | phFlag | Context Data | +/// |------------------|-----------------|--------|--------------| +/// | Ed25519 | Not Required | N/A | N/A | +/// | Ed25519ctx | Required | False | Optional | +/// | Ed25519ph | Required | True | Optional | +/// | Ed448 | Required | False | Optional | +/// | Ed448ph | Required | True | Optional | +/// +/// The absence or presence of the parameter as well as its +/// content is used to identify which signature scheme is to be +/// used. +#[derive(Debug, Clone, Copy)] +pub enum EddsaSignatureScheme<'a> { + /// Pure EdDSA mode where the scheme is implicitly defined + /// by the curve. + Pure, + /// Ed25519 signature scheme without additional parameters. + Ed25519, + /// Ed25519 signature scheme with optional context-specific + /// data. + Ed25519ctx(&'a [u8]), + /// Ed25519 signature scheme with pre-hashing and optional + /// context-specific data. + Ed25519ph(&'a [u8]), + /// Ed448 signature scheme with optional context-specific data. + Ed448(&'a [u8]), + /// Ed448 signature scheme with pre-hashing and optional + /// context-specific data. + Ed448ph(&'a [u8]), +} + +impl EddsaSignatureScheme<'_> { + /// Convert an `EddsaSignatureScheme` into the corresponding + /// parameters. + /// + /// This function prepares the appropriate parameters for + /// the mechanism based on the signature scheme variant. + /// + /// # Returns + /// + /// A pointer the mechanism-specific parameters. + /// + /// For `Pure` and `Ed25519`, this returns `null_mut()` as no + /// additional parameters are required. For other schemes, a + /// pointer to the an `CK_EDDSA_PARAMS` structure is returned. + pub fn into_params(&self) -> *mut c_void { + match self { + EddsaSignatureScheme::Pure | EddsaSignatureScheme::Ed25519 => null_mut(), + EddsaSignatureScheme::Ed448(context) | EddsaSignatureScheme::Ed25519ctx(context) => { + &CK_EDDSA_PARAMS { + phFlag: false.into(), + pContextData: context.as_ptr() as *mut _, + ulContextDataLen: context + .len() + .try_into() + .expect("usize can not fit in CK_ULONG"), + } as *const CK_EDDSA_PARAMS as *mut _ + } + EddsaSignatureScheme::Ed448ph(context) | EddsaSignatureScheme::Ed25519ph(context) => { + &CK_EDDSA_PARAMS { + phFlag: true.into(), + pContextData: context.as_ptr() as *mut _, + ulContextDataLen: context + .len() + .try_into() + .expect("usize can not fit in CK_ULONG"), + } as *const CK_EDDSA_PARAMS as *mut _ + } + } + } +} + +/// EdDSA parameters. +/// +/// The EdDSA mechanism, denoted CKM_EDDSA, is a mechanism for +/// single-part and multipart signatures and verification for +/// EdDSA. This mechanism implements the five EdDSA signature +/// schemes defined in RFC 8032 and RFC 8410. +/// +/// For curves according to RFC 8032, this mechanism has an +/// optional parameter, a CK_EDDSA_PARAMS structure. +/// +/// The absence or presence of the parameter as well as its +/// content is used to identify which signature scheme is to be +/// used. +/// +/// | Signature Scheme | Mechanism Param | phFlag | Context Data | +/// |------------------|-----------------|--------|--------------| +/// | Ed25519 | Not Required | N/A | N/A | +/// | Ed25519ctx | Required | False | Optional | +/// | Ed25519ph | Required | True | Optional | +/// | Ed448 | Required | False | Optional | +/// | Ed448ph | Required | True | Optional | +/// +/// This structure wraps a `CK_EDDSA_PARAMS` structure. +#[derive(Copy, Debug, Clone)] +#[repr(transparent)] +pub struct EddsaParams<'a> { + inner: Option, + _marker: PhantomData<&'a [u8]>, +} + +impl EddsaParams<'_> { + /// Construct EdDSA parameters. + /// + /// # Arguments + /// + /// * `params` - The CK_EDDSA_PARAMS structure. + /// + /// # Returns + /// + /// A new EddsaParams struct. + pub fn new(scheme: EddsaSignatureScheme) -> Self { + let params = + match scheme { + EddsaSignatureScheme::Pure | EddsaSignatureScheme::Ed25519 => None, + EddsaSignatureScheme::Ed25519ctx(context) + | EddsaSignatureScheme::Ed448(context) => Some({ + CK_EDDSA_PARAMS { + phFlag: false.into(), + pContextData: context.as_ptr() as *mut _, + ulContextDataLen: context + .len() + .try_into() + .expect("usize can not fit in CK_ULONG"), + } + }), + EddsaSignatureScheme::Ed25519ph(context) + | EddsaSignatureScheme::Ed448ph(context) => Some({ + CK_EDDSA_PARAMS { + phFlag: true.into(), + pContextData: context.as_ptr() as *mut _, + ulContextDataLen: context + .len() + .try_into() + .expect("usize can not fit in CK_ULONG"), + } + }), + }; + + Self { + inner: params, + _marker: PhantomData, + } + } + + /// Retrieve the inner `CK_EDDSA_PARAMS` struct, if present. + /// + /// This method provides a reference to the `CK_EDDSA_PARAMS` + /// struct encapsulated within the `EddsaParams`, if the signature + /// scheme requires additional parameters. + /// + /// # Returns + /// + /// `Some(&CK_EDDSA_PARAMS)` if the signature scheme has associated + /// parameters, otherwise `None`. + pub fn inner(&self) -> Option<&CK_EDDSA_PARAMS> { + self.inner.as_ref() + } +} diff --git a/cryptoki/src/mechanism/mod.rs b/cryptoki/src/mechanism/mod.rs index 52a78528..4b9041e3 100644 --- a/cryptoki/src/mechanism/mod.rs +++ b/cryptoki/src/mechanism/mod.rs @@ -3,6 +3,7 @@ //! Data types for mechanisms pub mod aead; +pub mod eddsa; pub mod ekdf; pub mod elliptic_curve; pub mod hkdf; @@ -902,11 +903,16 @@ pub enum Mechanism<'a> { EcdsaSha512, /// EDDSA mechanism /// + /// This mechanism has an optional parameter, a CK_EDDSA_PARAMS + /// structure. The absence or presence of the parameter as well + /// as its content is used to identify which signature scheme + /// is to be used. + /// /// Note: EdDSA is not part of the PKCS#11 v2.40 standard and as - /// such may not be understood by the backend. It is included here - /// because some vendor implementations support it through the - /// v2.40 interface. - Eddsa, + /// such may not be understood by some backends. It is included + /// here because some vendor implementations support it through + /// the v2.40 interface. + Eddsa(eddsa::EddsaParams<'a>), // SHA-n /// SHA-1 mechanism @@ -1001,7 +1007,7 @@ impl Mechanism<'_> { Mechanism::EccKeyPairGen => MechanismType::ECC_KEY_PAIR_GEN, Mechanism::EccEdwardsKeyPairGen => MechanismType::ECC_EDWARDS_KEY_PAIR_GEN, Mechanism::EccMontgomeryKeyPairGen => MechanismType::ECC_MONTGOMERY_KEY_PAIR_GEN, - Mechanism::Eddsa => MechanismType::EDDSA, + Mechanism::Eddsa(_) => MechanismType::EDDSA, Mechanism::Ecdh1Derive(_) => MechanismType::ECDH1_DERIVE, Mechanism::Ecdsa => MechanismType::ECDSA, Mechanism::EcdsaSha1 => MechanismType::ECDSA_SHA1, @@ -1073,6 +1079,14 @@ impl From<&Mechanism<'_>> for CK_MECHANISM { | Mechanism::Sha512RsaPkcsPss(params) => make_mechanism(mechanism, params), Mechanism::RsaPkcsOaep(params) => make_mechanism(mechanism, params), Mechanism::Ecdh1Derive(params) => make_mechanism(mechanism, params), + Mechanism::Eddsa(params) => match params.inner() { + None => CK_MECHANISM { + mechanism, + pParameter: null_mut(), + ulParameterLen: 0, + }, + Some(params) => make_mechanism(mechanism, params), + }, Mechanism::HkdfDerive(params) | Mechanism::HkdfData(params) => { make_mechanism(mechanism, params) } @@ -1098,7 +1112,6 @@ impl From<&Mechanism<'_>> for CK_MECHANISM { | Mechanism::EccKeyPairGen | Mechanism::EccEdwardsKeyPairGen | Mechanism::EccMontgomeryKeyPairGen - | Mechanism::Eddsa | Mechanism::Ecdsa | Mechanism::EcdsaSha1 | Mechanism::EcdsaSha224 diff --git a/cryptoki/tests/basic.rs b/cryptoki/tests/basic.rs index 1e3b32f8..a60beaab 100644 --- a/cryptoki/tests/basic.rs +++ b/cryptoki/tests/basic.rs @@ -7,6 +7,7 @@ use common::init_pins; use cryptoki::context::Function; use cryptoki::error::{Error, RvError}; use cryptoki::mechanism::aead::GcmParams; +use cryptoki::mechanism::eddsa::{EddsaParams, EddsaSignatureScheme}; use cryptoki::mechanism::rsa::{PkcsMgfType, PkcsOaepParams, PkcsOaepSource}; use cryptoki::mechanism::{Mechanism, MechanismType}; use cryptoki::object::{ @@ -72,7 +73,7 @@ fn sign_verify() -> TestResult { #[test] #[serial] -fn sign_verify_ed25519() -> TestResult { +fn sign_verify_eddsa() -> TestResult { let (pkcs11, slot) = init_pins(); let session = pkcs11.open_rw_session(slot)?; @@ -99,9 +100,111 @@ fn sign_verify_ed25519() -> TestResult { let data = [0xFF, 0x55, 0xDD]; - let signature = session.sign(&Mechanism::Eddsa, private, &data)?; + let scheme = EddsaSignatureScheme::Pure; - session.verify(&Mechanism::Eddsa, public, &data, &signature)?; + let params = EddsaParams::new(scheme); + + let signature = session.sign(&Mechanism::Eddsa(params), private, &data)?; + + session.verify(&Mechanism::Eddsa(params), public, &data, &signature)?; + + session.destroy_object(public)?; + session.destroy_object(private)?; + + Ok(()) +} + +#[test] +#[serial] +fn sign_verify_eddsa_with_ed25519_schemes() -> TestResult { + let (pkcs11, slot) = init_pins(); + + let session = pkcs11.open_rw_session(slot)?; + + session.login(UserType::User, Some(&AuthPin::new(USER_PIN.into())))?; + + let mechanism = Mechanism::EccEdwardsKeyPairGen; + + let pub_key_template = vec![ + Attribute::Token(true), + Attribute::Private(false), + Attribute::Verify(true), + // Ed25519 OID + // See: https://github.com/opendnssec/SoftHSMv2/blob/ac70dc398b236e4522101930e790008936489e2d/src/lib/test/SignVerifyTests.cpp#L173 + Attribute::EcParams(vec![ + 0x13, 0x0c, 0x65, 0x64, 0x77, 0x61, 0x72, 0x64, 0x73, 0x32, 0x35, 0x35, 0x31, 0x39, + ]), + ]; + + let priv_key_template = vec![Attribute::Token(true)]; + + let (public, private) = + session.generate_key_pair(&mechanism, &pub_key_template, &priv_key_template)?; + + let data = [0xFF, 0x55, 0xDD]; + + let schemes = [ + EddsaSignatureScheme::Ed25519, + EddsaSignatureScheme::Ed25519ctx(b"context"), + EddsaSignatureScheme::Ed25519ph(&[]), + EddsaSignatureScheme::Ed25519ph(b"context"), + ]; + + for scheme in schemes { + let params = EddsaParams::new(scheme); + + let signature = session.sign(&Mechanism::Eddsa(params), private, &data)?; + + session.verify(&Mechanism::Eddsa(params), public, &data, &signature)?; + } + + session.destroy_object(public)?; + session.destroy_object(private)?; + + Ok(()) +} + +#[test] +#[serial] +fn sign_verify_eddsa_with_ed448_schemes() -> TestResult { + let (pkcs11, slot) = init_pins(); + + let session = pkcs11.open_rw_session(slot)?; + + session.login(UserType::User, Some(&AuthPin::new(USER_PIN.into())))?; + + let mechanism = Mechanism::EccEdwardsKeyPairGen; + + let pub_key_template = vec![ + Attribute::Token(true), + Attribute::Private(false), + Attribute::Verify(true), + // Ed448 OID + // See: https://github.com/opendnssec/SoftHSMv2/blob/ac70dc398b236e4522101930e790008936489e2d/src/lib/test/SignVerifyTests.cpp#L173 + Attribute::EcParams(vec![ + 0x13, 0x0a, 0x65, 0x64, 0x77, 0x61, 0x72, 0x64, 0x73, 0x34, 0x34, 0x38, + ]), + ]; + + let priv_key_template = vec![Attribute::Token(true)]; + + let (public, private) = + session.generate_key_pair(&mechanism, &pub_key_template, &priv_key_template)?; + + let data = [0xFF, 0x55, 0xDD]; + + let schemes = [ + EddsaSignatureScheme::Ed448(b"context"), + EddsaSignatureScheme::Ed448ph(b"context"), + ]; + + for scheme in schemes { + let params = EddsaParams::new(scheme); + + let signature = session.sign(&Mechanism::Eddsa(params), private, &data)?; + + session.verify(&Mechanism::Eddsa(params), public, &data, &signature)?; + } session.destroy_object(public)?; session.destroy_object(private)?;