Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/certificate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ pub fn generate_self_signed_certificate() -> Result<DtlsCertificate, Certificate

Ok(DtlsCertificate {
certificate: cert_der,
private_key: key_der,
private_key: crate::DtlsCertificatePrivateKey::Pkcs8(key_der),
})
}

Expand Down
15 changes: 15 additions & 0 deletions src/crypto/aws_lc_rs/sign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use crate::types::{HashAlgorithm, NamedGroup, SignatureAlgorithm};
struct EcdsaSigningKey {
key_pair: EcdsaKeyPair,
signing_algorithm: &'static EcdsaSigningAlgorithm,
key_der: Vec<u8>,
}

impl std::fmt::Debug for EcdsaSigningKey {
Expand Down Expand Up @@ -55,6 +56,16 @@ impl SigningKey for EcdsaSigningKey {
panic!("Unsupported signing algorithm")
}
}

fn clone_box(&self) -> Box<dyn SigningKey> {
let key_pair = EcdsaKeyPair::from_pkcs8(self.signing_algorithm, &self.key_der)
.expect("Re-parsing key should not fail");
Box::new(EcdsaSigningKey {
key_pair,
signing_algorithm: self.signing_algorithm,
key_der: self.key_der.clone(),
})
}
}

/// Key provider implementation.
Expand All @@ -68,12 +79,14 @@ impl KeyProvider for AwsLcKeyProvider {
return Ok(Box::new(EcdsaSigningKey {
key_pair,
signing_algorithm: &ECDSA_P256_SHA256_ASN1_SIGNING,
key_der: key_der.to_vec(),
}));
}
if let Ok(key_pair) = EcdsaKeyPair::from_pkcs8(&ECDSA_P384_SHA384_ASN1_SIGNING, key_der) {
return Ok(Box::new(EcdsaSigningKey {
key_pair,
signing_algorithm: &ECDSA_P384_SHA384_ASN1_SIGNING,
key_der: key_der.to_vec(),
}));
}

Expand Down Expand Up @@ -124,6 +137,7 @@ impl KeyProvider for AwsLcKeyProvider {
return Ok(Box::new(EcdsaSigningKey {
key_pair,
signing_algorithm: &ECDSA_P256_SHA256_ASN1_SIGNING,
key_der: pkcs8_der.clone(),
}));
}
}
Expand All @@ -136,6 +150,7 @@ impl KeyProvider for AwsLcKeyProvider {
return Ok(Box::new(EcdsaSigningKey {
key_pair,
signing_algorithm: &ECDSA_P384_SHA384_ASN1_SIGNING,
key_der: pkcs8_der.clone(),
}));
}
}
Expand Down
4 changes: 4 additions & 0 deletions src/crypto/provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,10 @@ pub trait SigningKey: CryptoSafe {

/// Default hash algorithm for this key.
fn hash_algorithm(&self) -> HashAlgorithm;

/// Clone this signing key into a new boxed instance.
/// Used to support cloning `DtlsCertificate` when using external signing keys.
fn clone_box(&self) -> Box<dyn SigningKey>;
}

/// Active key exchange instance (ephemeral keypair for one handshake).
Expand Down
7 changes: 7 additions & 0 deletions src/crypto/rust_crypto/sign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,13 @@ impl SigningKeyTrait for EcdsaSigningKey {
EcdsaSigningKey::P384(_) => HashAlgorithm::SHA384,
}
}

fn clone_box(&self) -> Box<dyn SigningKeyTrait> {
match self {
EcdsaSigningKey::P256(key) => Box::new(EcdsaSigningKey::P256(key.clone())),
EcdsaSigningKey::P384(key) => Box::new(EcdsaSigningKey::P384(key.clone())),
}
}
}

/// Key provider implementation.
Expand Down
28 changes: 16 additions & 12 deletions src/dtls12/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,24 +73,28 @@ impl CryptoContext {
/// Create a new crypto context
pub fn new(
certificate: Vec<u8>,
private_key_bytes: Vec<u8>,
private_key: crate::DtlsCertificatePrivateKey,
config: Arc<crate::Config>,
) -> Self {
// Validate that we have a certificate and private key
// Validate that we have a certificate
if certificate.is_empty() {
panic!("Client certificate cannot be empty");
}

if private_key_bytes.is_empty() {
panic!("Client private key cannot be empty");
}

// Parse the private key using the provider
let private_key = config
.crypto_provider()
.key_provider
.load_private_key(&private_key_bytes)
.expect("Failed to parse client private key");
// Load or use the private key
let private_key = match &private_key {
crate::DtlsCertificatePrivateKey::Pkcs8(key_der) => {
if key_der.is_empty() {
panic!("Client private key cannot be empty");
}
config
.crypto_provider()
.key_provider
.load_private_key(key_der)
.expect("Failed to parse client private key")
}
crate::DtlsCertificatePrivateKey::SigningKey(key) => (**key).clone_box(),
};

CryptoContext {
config,
Expand Down
15 changes: 9 additions & 6 deletions src/dtls13/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ use crate::dtls13::message::Sequence;
use crate::timer::ExponentialBackoff;
use crate::types::{HashAlgorithm, Random};
use crate::window::ReplayWindow;
use crate::{Config, DtlsCertificate, Error, Output, SeededRng};
use crate::{Config, DtlsCertificate, DtlsCertificatePrivateKey, Error, Output, SeededRng};

const MAX_DEFRAGMENT_PACKETS: usize = 50;

Expand Down Expand Up @@ -196,11 +196,14 @@ impl Engine {
let flight_backoff =
ExponentialBackoff::new(config.flight_start_rto(), config.flight_retries(), &mut rng);

let signing_key = config
.crypto_provider()
.key_provider
.load_private_key(&certificate.private_key)
.expect("Failed to load private key");
let signing_key = match &certificate.private_key {
DtlsCertificatePrivateKey::Pkcs8(key_der) => config
.crypto_provider()
.key_provider
.load_private_key(key_der)
.expect("Failed to load private key"),
DtlsCertificatePrivateKey::SigningKey(key) => (**key).clone_box(),
};

let aead_encryption_threshold =
jittered_aead_threshold(config.aead_encryption_limit(), &mut rng);
Expand Down
92 changes: 88 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ pub mod certificate;

pub mod crypto;

pub use crypto::{KeyingMaterial, SrtpProfile};
pub use crypto::{KeyingMaterial, SigningKey, SrtpProfile};

mod timer;

Expand All @@ -211,15 +211,99 @@ pub(crate) use rng::SeededRng;
pub struct DtlsCertificate {
/// Certificate in DER format.
pub certificate: Vec<u8>,
/// Private key in DER format.
pub private_key: Vec<u8>,
/// Private key (either PKCS8 DER bytes or a signing key trait object).
pub private_key: DtlsCertificatePrivateKey,
}

/// Private key representation for DTLS certificates.
///
/// Supports either PKCS8 DER-encoded private key bytes or a pre-loaded
/// signing key trait object. The latter is useful for hardware security
/// modules or keystores where the private key material cannot be exported.
///
/// When using `SigningKey`, the Arc wrapper enables drop tracking: when the
/// last reference is dropped, implementers can clean up native crypto resources
/// via the SigningKey's Drop implementation.
///
/// # Examples
///
/// Using PKCS8 DER bytes (the default):
/// ```ignore
/// let cert = DtlsCertificate {
/// certificate: cert_der,
/// private_key: DtlsCertificatePrivateKey::Pkcs8(key_der),
/// };
/// ```
///
/// Using a custom signing key (e.g., from a hardware security module):
/// ```ignore
/// struct MyHsmSigningKey { /* ... */ }
///
/// impl SigningKey for MyHsmSigningKey {
/// fn sign(&mut self, data: &[u8], out: &mut Buf) -> Result<(), String> {
/// // Call HSM to sign data
/// todo!()
/// }
///
/// fn algorithm(&self) -> SignatureAlgorithm {
/// SignatureAlgorithm::ECDSA
/// }
///
/// fn hash_algorithm(&self) -> HashAlgorithm {
/// HashAlgorithm::SHA256
/// }
///
/// fn clone_box(&self) -> Box<dyn SigningKey> {
/// Box::new(MyHsmSigningKey { /* clone fields */ })
/// }
/// }
///
/// impl Drop for MyHsmSigningKey {
/// fn drop(&mut self) {
/// // Clean up HSM resources when the last reference is dropped
/// }
/// }
///
/// let signing_key = Arc::new(Box::new(MyHsmSigningKey { /* ... */ }) as Box<dyn SigningKey>);
/// let cert = DtlsCertificate {
/// certificate: cert_der,
/// private_key: DtlsCertificatePrivateKey::SigningKey(signing_key),
/// };
/// ```
pub enum DtlsCertificatePrivateKey {
/// Private key in PKCS8 DER format.
Pkcs8(Vec<u8>),
/// Pre-loaded signing key. Wrapped in Arc for cloning and drop tracking.
SigningKey(Arc<Box<dyn SigningKey>>),
}

impl Clone for DtlsCertificatePrivateKey {
fn clone(&self) -> Self {
match self {
Self::Pkcs8(bytes) => Self::Pkcs8(bytes.clone()),
Self::SigningKey(key) => Self::SigningKey(Arc::clone(key)),
}
}
}

impl fmt::Debug for DtlsCertificatePrivateKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Pkcs8(bytes) => write!(f, "Pkcs8({} bytes)", bytes.len()),
Self::SigningKey(_) => write!(f, "SigningKey"),
}
}
}

impl fmt::Debug for DtlsCertificate {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let private_key_desc = match &self.private_key {
DtlsCertificatePrivateKey::Pkcs8(bytes) => format!("Pkcs8({})", bytes.len()),
DtlsCertificatePrivateKey::SigningKey(_) => "SigningKey".to_string(),
};
f.debug_struct("DtlsCertificate")
.field("certificate", &self.certificate.len())
.field("private_key", &self.private_key.len())
.field("private_key", &private_key_desc)
.finish()
}
}
Expand Down