Skip to content

Commit

Permalink
Add EC-DSA, custom certificate common names for DTLS
Browse files Browse the repository at this point in the history
  • Loading branch information
efer-ms authored Jan 10, 2025
1 parent 5b20343 commit c72d48c
Show file tree
Hide file tree
Showing 13 changed files with 416 additions and 172 deletions.
36 changes: 32 additions & 4 deletions src/crypto/dtls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use super::{CryptoError, Fingerprint, KeyingMaterial, SrtpProfile};
//
// Pion also sets this to "WebRTC", maybe for compatibility reasons.
// https://github.com/pion/webrtc/blob/eed2bb2d3b9f204f9de1cd7e1046ca5d652778d2/constants.go#L31
pub const DTLS_CERT_IDENTITY: &str = "WebRTC";
const DTLS_CERT_IDENTITY: &str = "WebRTC";

/// Events arising from a [`Dtls`] instance.
pub enum DtlsEvent {
Expand All @@ -34,6 +34,34 @@ pub enum DtlsEvent {
Data(Vec<u8>),
}

/// Defines the type of key pair to generate for the DTLS certificate.
#[derive(Clone, Debug, Default)]
pub enum DtlsPKeyType {
/// Generate an RSA key pair
Rsa2048,
/// Generate an EC-DSA key pair using the NIST P-256 curve
#[default]
EcDsaP256,
}

/// Controls certificate generation options.
#[derive(Clone, Debug)]
pub struct DtlsCertOptions {
/// The common name for the certificate.
pub common_name: String,
/// The type of key to generate.
pub pkey_type: DtlsPKeyType,
}

impl Default for DtlsCertOptions {
fn default() -> Self {
Self {
common_name: DTLS_CERT_IDENTITY.into(),
pkey_type: Default::default(),
}
}
}

/// Certificate used for DTLS.
#[derive(Clone)]
pub struct DtlsCert(DtlsCertInner);
Expand All @@ -59,12 +87,12 @@ impl DtlsCert {
///
/// * **openssl** (defaults to on) for crypto backed by OpenSSL.
/// * **wincrypto** for crypto backed by windows crypto.
pub fn new(p: CryptoProvider) -> Self {
pub fn new(p: CryptoProvider, opts: DtlsCertOptions) -> Self {
let inner = match p {
CryptoProvider::OpenSsl => {
#[cfg(feature = "openssl")]
{
let cert = super::ossl::OsslDtlsCert::new();
let cert = super::ossl::OsslDtlsCert::new(opts);
DtlsCertInner::OpenSsl(cert)
}
#[cfg(not(feature = "openssl"))]
Expand All @@ -75,7 +103,7 @@ impl DtlsCert {
CryptoProvider::WinCrypto => {
#[cfg(all(feature = "wincrypto", target_os = "windows"))]
{
let cert = super::wincrypto::WinCryptoDtlsCert::new();
let cert = super::wincrypto::WinCryptoDtlsCert::new(opts);
DtlsCertInner::WinCrypto(cert)
}
#[cfg(not(all(feature = "wincrypto", target_os = "windows")))]
Expand Down
4 changes: 2 additions & 2 deletions src/crypto/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,8 @@ mod ossl;
mod wincrypto;

mod dtls;
pub use dtls::DtlsCert;
pub(crate) use dtls::{DtlsEvent, DtlsImpl};
pub(crate) use dtls::DtlsImpl;
pub use dtls::{DtlsCert, DtlsCertOptions, DtlsEvent, DtlsPKeyType};

mod finger;
pub use finger::Fingerprint;
Expand Down
27 changes: 19 additions & 8 deletions src/crypto/ossl/cert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ use std::time::SystemTime;

use openssl::asn1::{Asn1Integer, Asn1Time, Asn1Type};
use openssl::bn::BigNum;
use openssl::ec::{EcGroup, EcKey};
use openssl::hash::MessageDigest;
use openssl::nid::Nid;
use openssl::pkey::{PKey, Private};
use openssl::rsa::Rsa;
use openssl::x509::{X509Name, X509};

use crate::crypto::dtls::DTLS_CERT_IDENTITY;
use crate::crypto::dtls::{DtlsCertOptions, DtlsPKeyType};
use crate::crypto::Fingerprint;

use super::CryptoError;
Expand All @@ -25,16 +26,26 @@ pub struct OsslDtlsCert {

impl OsslDtlsCert {
/// Creates a new (self signed) DTLS certificate.
pub fn new() -> Self {
Self::self_signed().expect("create dtls cert")
pub fn new(options: DtlsCertOptions) -> Self {
Self::self_signed(options).expect("create dtls cert")
}

// The libWebRTC code we try to match is at:
// https://webrtc.googlesource.com/src/+/1568f1b1330f94494197696fe235094e6293b258/rtc_base/openssl_certificate.cc#58
fn self_signed() -> Result<Self, CryptoError> {
fn self_signed(options: DtlsCertOptions) -> Result<Self, CryptoError> {
let f4 = BigNum::from_u32(RSA_F4).unwrap();
let key = Rsa::generate_with_e(2048, &f4)?;
let pkey = PKey::from_rsa(key)?;
let pkey = match options.pkey_type {
DtlsPKeyType::Rsa2048 => {
let key = Rsa::generate_with_e(2048, &f4)?;
PKey::from_rsa(key)?
}
DtlsPKeyType::EcDsaP256 => {
let nid = Nid::X9_62_PRIME256V1; // NIST P-256 curve
let group = EcGroup::from_curve_name(nid)?;
let key = EcKey::generate(&group)?;
PKey::from_ec_key(key)?
}
};

let mut x509b = X509::builder()?;
x509b.set_version(2)?; // X509.V3 (zero indexed)
Expand Down Expand Up @@ -64,7 +75,7 @@ impl OsslDtlsCert {
let mut nameb = X509Name::builder()?;
nameb.append_entry_by_nid_with_type(
Nid::COMMONNAME,
DTLS_CERT_IDENTITY,
options.common_name.as_str(),
Asn1Type::UTF8STRING,
)?;

Expand All @@ -73,7 +84,7 @@ impl OsslDtlsCert {
x509b.set_subject_name(&name)?;
x509b.set_issuer_name(&name)?;

x509b.sign(&pkey, MessageDigest::sha1())?;
x509b.sign(&pkey, MessageDigest::sha256())?;
let x509 = x509b.build();

Ok(OsslDtlsCert { pkey, x509 })
Expand Down
16 changes: 12 additions & 4 deletions src/crypto/wincrypto/cert.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::CryptoError;
use super::WinCryptoDtls;
use crate::crypto::dtls::DTLS_CERT_IDENTITY;
use crate::crypto::dtls::{DtlsCertOptions, DtlsPKeyType};
use crate::crypto::Fingerprint;
use std::sync::Arc;
use str0m_wincrypto::WinCryptoError;
Expand All @@ -11,10 +11,18 @@ pub struct WinCryptoDtlsCert {
}

impl WinCryptoDtlsCert {
pub fn new() -> Self {
pub fn new(options: DtlsCertOptions) -> Self {
let use_ec_dsa_keys = match options.pkey_type {
DtlsPKeyType::Rsa2048 => false,
DtlsPKeyType::EcDsaP256 => true,
};

let certificate = Arc::new(
str0m_wincrypto::Certificate::new_self_signed(&format!("CN={}", DTLS_CERT_IDENTITY))
.expect("Failed to create self-signed certificate"),
str0m_wincrypto::Certificate::new_self_signed(
use_ec_dsa_keys,
&format!("CN={}", options.common_name),
)
.expect("Failed to create self-signed certificate"),
);
Self { certificate }
}
Expand Down
3 changes: 2 additions & 1 deletion src/crypto/wincrypto/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ pub use dtls::WinCryptoDtls;
mod srtp;
pub use srtp::WinCryptoSrtpCryptoImpl;

#[cfg(not(feature = "sha1"))]
mod sha1;
#[allow(unused_imports)] // If 'sha1' feature is enabled this is not used.
#[cfg(not(feature = "sha1"))]
pub use sha1::sha1_hmac;

pub use str0m_wincrypto::WinCryptoError;
3 changes: 1 addition & 2 deletions src/dtls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ use thiserror::Error;

use crate::crypto::{CryptoError, DtlsImpl, Fingerprint};

pub use crate::crypto::DtlsCert;
pub(crate) use crate::crypto::DtlsEvent;
pub use crate::crypto::{DtlsCert, DtlsCertOptions, DtlsEvent};
use crate::net::DatagramSend;

/// Errors that can arise in DTLS.
Expand Down
93 changes: 56 additions & 37 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -626,8 +626,7 @@ use crypto::CryptoProvider;
use crypto::Fingerprint;

mod dtls;
use dtls::DtlsCert;
use dtls::{Dtls, DtlsEvent};
use dtls::{Dtls, DtlsCert, DtlsCertOptions, DtlsEvent};

#[path = "ice/mod.rs"]
mod ice_;
Expand All @@ -637,7 +636,7 @@ pub use ice_::{Candidate, CandidateKind, IceConnectionState, IceCreds};

/// Additional configuration.
pub mod config {
pub use super::crypto::{CryptoProvider, DtlsCert, Fingerprint};
pub use super::crypto::{CryptoProvider, DtlsCert, DtlsCertOptions, DtlsPKeyType, Fingerprint};
}

/// Low level ICE access.
Expand Down Expand Up @@ -1141,10 +1140,9 @@ impl Rtc {
ice.set_ice_lite(config.ice_lite);
}

let dtls_cert = if let Some(c) = config.dtls_cert {
c
} else {
DtlsCert::new(config.crypto_provider)
let dtls_cert = match config.dtls_cert_config {
DtlsCertConfig::Options(options) => DtlsCert::new(config.crypto_provider, options),
DtlsCertConfig::PregeneratedCert(cert) => cert,
};

let crypto_provider = dtls_cert.crypto_provider();
Expand Down Expand Up @@ -1854,6 +1852,25 @@ impl Rtc {
}
}

/// Configuation for the DTLS certificate used for the Rtc instance. This can be set to
/// allow a pregenerated certificate, or options to pass when generating a certificate
/// on-the-fly.
///
/// The default value is DtlsCertConfig::Options(DtlsCertOptions::default())
#[derive(Clone, Debug)]
pub enum DtlsCertConfig {
/// The options to use for the DTLS certificate generated for this Rtc instance.
Options(DtlsCertOptions),
/// A pregenerated certificate to use for this Rtc instance.
PregeneratedCert(DtlsCert),
}

impl Default for DtlsCertConfig {
fn default() -> Self {
DtlsCertConfig::Options(DtlsCertOptions::default())
}
}

/// Customized config for creating an [`Rtc`] instance.
///
/// ```
Expand All @@ -1871,7 +1888,7 @@ impl Rtc {
pub struct RtcConfig {
local_ice_credentials: Option<IceCreds>,
crypto_provider: CryptoProvider,
dtls_cert: Option<DtlsCert>,
dtls_cert_config: DtlsCertConfig,
fingerprint_verification: bool,
ice_lite: bool,
codec_config: CodecConfig,
Expand Down Expand Up @@ -1921,7 +1938,7 @@ impl RtcConfig {
///
/// This overrides what is set in [`CryptoProvider::install_process_default()`].
pub fn set_crypto_provider(mut self, p: CryptoProvider) -> Self {
if let Some(c) = &self.dtls_cert {
if let DtlsCertConfig::PregeneratedCert(c) = &self.dtls_cert_config {
if p != c.crypto_provider() {
panic!("set_dtls_cert() locked crypto provider to: {}", p);
}
Expand All @@ -1939,46 +1956,48 @@ impl RtcConfig {
self.crypto_provider
}

/// Get the configured DTLS certificate, if set.
///
/// Returns [`None`] if no DTLS certificate is set. In such cases,
/// the certificate will be created on build and you can use the
/// direct API on an [`Rtc`] instance to obtain the local
/// DTLS fingerprint.
/// Returns the configured DTLS certificate configuration.
///
/// Defaults to a configuration similar to libwebrtc:
/// ```
/// # #[cfg(feature = "openssl")] {
/// # use str0m::RtcConfig;
/// let fingerprint = RtcConfig::default()
/// .build()
/// .direct_api()
/// .local_dtls_fingerprint();
/// # }
/// # use str0m::DtlsCertConfig;
/// # use str0m::config::{DtlsCertOptions, DtlsPKeyType};
///
/// DtlsCertConfig::Options(DtlsCertOptions {
/// common_name: "WebRTC".into(),
/// pkey_type: DtlsPKeyType::EcDsaP256,
/// });
/// ```
pub fn dtls_cert(&self) -> Option<&DtlsCert> {
self.dtls_cert.as_ref()
pub fn dtls_cert_config(&self) -> &DtlsCertConfig {
&self.dtls_cert_config
}

/// Set the DTLS certificate for secure communication.
/// Set the DTLS certificate configuration for certificate generation.
///
/// Generating a certificate can be a time-consuming process.
/// Use this API to reuse a previously created [`DtlsCert`] if available.
/// Setting this permits you to assign a Pregenerated certificate, or
/// options for certificate generation, such as signing key type, and
/// subject name.
///
/// Setting this locks the `crypto_provider()` setting to the [`CryptoProvider`],
/// for the DTLS certificate.
/// If a Pregenerated certificate is set, this locks the `crypto_provider()`
/// setting to the [`CryptoProvider`], for the DTLS certificate.
///
/// ```
/// # use str0m::RtcConfig;
/// # use str0m::config::{DtlsCert, CryptoProvider};
/// # use str0m::{DtlsCertConfig, RtcConfig};
/// # use str0m::config::{DtlsCertOptions, DtlsPKeyType};
///
/// let dtls_cert = DtlsCert::new(CryptoProvider::OpenSsl);
/// let dtls_cert_config = DtlsCertConfig::Options(DtlsCertOptions {
/// common_name: "Clark Kent".into(),
/// pkey_type: DtlsPKeyType::EcDsaP256,
/// });
///
/// let rtc_config = RtcConfig::default()
/// .set_dtls_cert(dtls_cert);
/// .set_dtls_cert_config(dtls_cert_config);
/// ```
pub fn set_dtls_cert(mut self, dtls_cert: DtlsCert) -> Self {
self.crypto_provider = dtls_cert.crypto_provider();
self.dtls_cert = Some(dtls_cert);
pub fn set_dtls_cert_config(mut self, dtls_cert_config: DtlsCertConfig) -> Self {
if let DtlsCertConfig::PregeneratedCert(ref cert) = dtls_cert_config {
self.crypto_provider = cert.crypto_provider();
}
self.dtls_cert_config = dtls_cert_config;
self
}

Expand Down Expand Up @@ -2388,7 +2407,7 @@ impl Default for RtcConfig {
Self {
local_ice_credentials: None,
crypto_provider: CryptoProvider::process_default().unwrap_or(CryptoProvider::OpenSsl),
dtls_cert: None,
dtls_cert_config: Default::default(),
fingerprint_verification: true,
ice_lite: false,
codec_config: CodecConfig::new_with_defaults(),
Expand Down
4 changes: 2 additions & 2 deletions wincrypto/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion wincrypto/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ license = "MIT OR Apache-2.0"
[dependencies]
thiserror = { version = "1.0.38" }
tracing = "0.1.37"
windows = { version = "0.58", features=["Win32_Security_Cryptography", "Win32_Security_Authentication_Identity", "Win32_Security_Credentials",]}
windows = { version = "0.58", features=["Win32_Security_Cryptography", "Win32_Security_Authentication_Identity", "Win32_Security_Credentials", "Win32_System_Rpc",]}
Loading

0 comments on commit c72d48c

Please sign in to comment.