use std::ops::Add;
use std::sync::Arc;
use std::time::{Duration, SystemTime};
use crypto::{HashAlgorithm, PublicKeyEncoding, RTCCrypto, SignatureScheme, SigningKey};
pub use rcgen::CertificateParams;
use rustls::pki_types::CertificateDer;
use crate::peer_connection::transport::dtls::fingerprint::RTCDtlsFingerprint;
use shared::error::{Error, Result};
#[derive(Clone, Debug)]
pub struct RTCCertificate {
pub dtls_certificate: dtls::crypto::Certificate,
pub expires: SystemTime,
}
impl PartialEq for RTCCertificate {
fn eq(&self, other: &Self) -> bool {
self.dtls_certificate == other.dtls_certificate
}
}
impl RTCCertificate {
pub fn generate(
crypto: &dyn RTCCrypto,
scheme: SignatureScheme,
params: CertificateParams,
) -> Result<Self> {
let signing_key = crypto.generate_signing_key(scheme).map_err(crypto_error)?;
Self::generate_from_signing_key(params, scheme, signing_key)
}
pub fn from_pkcs8(
crypto: &dyn RTCCrypto,
scheme: SignatureScheme,
certificate_chain: Vec<CertificateDer<'static>>,
private_key_der: &[u8],
expires: SystemTime,
) -> Result<Self> {
let signing_key = crypto
.import_signing_key(scheme, private_key_der)
.map_err(crypto_error)?;
Ok(Self::from_signing_key(
certificate_chain,
signing_key,
expires,
))
}
#[must_use]
pub fn from_signing_key(
certificate_chain: Vec<CertificateDer<'static>>,
signing_key: Arc<dyn SigningKey>,
expires: SystemTime,
) -> Self {
Self {
dtls_certificate: dtls::crypto::Certificate::from_signing_key(
certificate_chain,
signing_key,
),
expires,
}
}
pub fn generate_from_signing_key(
params: CertificateParams,
scheme: SignatureScheme,
signing_key: Arc<dyn SigningKey>,
) -> Result<Self> {
let not_after = params.not_after;
let adapter = RcgenSigningKey::new(scheme, signing_key.clone())?;
let x509_cert = params
.self_signed(&adapter)
.map_err(|error| Error::Other(error.to_string()))?;
let expires = certificate_expiration(not_after);
Ok(Self::from_signing_key(
vec![x509_cert.der().to_owned()],
signing_key,
expires,
))
}
pub fn from_pem(pem_str: &str, crypto: &dyn RTCCrypto) -> Result<Self> {
let mut pem_blocks = pem_str.split("\n\n");
let first_block = if let Some(b) = pem_blocks.next() {
b
} else {
return Err(Error::InvalidPEM("empty PEM".into()));
};
let expires_pem =
pem::parse(first_block).map_err(|e| Error::Other(format!("can't parse PEM: {e}")))?;
if expires_pem.tag() != "EXPIRES" {
return Err(Error::InvalidPEM(format!(
"invalid tag (expected: 'EXPIRES', got '{}')",
expires_pem.tag()
)));
}
let mut bytes = [0u8; 8];
bytes.copy_from_slice(&expires_pem.contents()[..8]);
let expires = if let Some(e) =
SystemTime::UNIX_EPOCH.checked_add(Duration::from_secs(u64::from_le_bytes(bytes)))
{
e
} else {
return Err(Error::InvalidPEM("failed to calculate SystemTime".into()));
};
let dtls_certificate = dtls::crypto::Certificate::from_pem(
&pem_blocks.collect::<Vec<&str>>().join("\n\n"),
crypto,
)?;
Ok(RTCCertificate::from_existing(dtls_certificate, expires))
}
pub fn from_existing(dtls_certificate: dtls::crypto::Certificate, expires: SystemTime) -> Self {
Self {
dtls_certificate,
expires,
}
}
pub fn serialize_pem(&self) -> Result<String> {
let expires_pem = pem::Pem::new(
"EXPIRES".to_string(),
self.expires
.duration_since(SystemTime::UNIX_EPOCH)
.expect("expires to be valid")
.as_secs()
.to_le_bytes()
.to_vec(),
);
Ok(format!(
"{}\n{}",
pem::encode(&expires_pem),
self.dtls_certificate.serialize_pem()?
))
}
pub fn get_fingerprints(&self, crypto: &dyn RTCCrypto) -> Result<Vec<RTCDtlsFingerprint>> {
let mut fingerprints = Vec::new();
for c in &self.dtls_certificate.certificate {
let hashed = crypto
.hash(HashAlgorithm::Sha256, c.as_ref())
.map_err(crypto_error)?;
let values: Vec<String> = hashed.iter().map(|x| format! {"{x:02x}"}).collect();
fingerprints.push(RTCDtlsFingerprint {
algorithm: "sha-256".to_owned(),
value: values.join(":"),
});
}
Ok(fingerprints)
}
}
fn crypto_error(error: crypto::CryptoError) -> Error {
Error::Crypto(error.to_string())
}
fn certificate_expiration(not_after: impl Into<SystemTime>) -> SystemTime {
if cfg!(target_arch = "arm") {
SystemTime::now().add(Duration::from_secs(172800)) } else {
not_after.into()
}
}
struct RcgenSigningKey {
scheme: SignatureScheme,
algorithm: &'static rcgen::SignatureAlgorithm,
signing_key: Arc<dyn SigningKey>,
public_key: Vec<u8>,
}
impl RcgenSigningKey {
fn new(scheme: SignatureScheme, signing_key: Arc<dyn SigningKey>) -> Result<Self> {
let algorithm = match scheme {
SignatureScheme::Ed25519 => &rcgen::PKCS_ED25519,
SignatureScheme::EcdsaP256Sha256 => &rcgen::PKCS_ECDSA_P256_SHA256,
SignatureScheme::EcdsaP384Sha384 => &rcgen::PKCS_ECDSA_P384_SHA384,
SignatureScheme::RsaPkcs1Sha256 => &rcgen::PKCS_RSA_SHA256,
SignatureScheme::RsaPkcs1Sha384 => &rcgen::PKCS_RSA_SHA384,
SignatureScheme::RsaPkcs1Sha512 => &rcgen::PKCS_RSA_SHA512,
_ => {
return Err(Error::Crypto(format!(
"certificate generation does not support {scheme:?}"
)));
}
};
if !signing_key.supports(scheme) {
return Err(Error::Crypto(format!(
"signing key does not support {scheme:?}"
)));
}
let public_key = signing_key.public_key();
let public_key = match public_key.encoding {
PublicKeyEncoding::SubjectPublicKeyInfoDer => {
use x509_parser::prelude::FromDer;
let (remaining, subject_public_key_info) =
x509_parser::x509::SubjectPublicKeyInfo::from_der(public_key.bytes)
.map_err(|error| Error::Other(error.to_string()))?;
if !remaining.is_empty() {
return Err(Error::Other(
"trailing bytes in SubjectPublicKeyInfo".to_owned(),
));
}
subject_public_key_info.subject_public_key.data.to_vec()
}
PublicKeyEncoding::EcUncompressedPoint
| PublicKeyEncoding::Ed25519Raw
| PublicKeyEncoding::RsaPkcs1Der => public_key.bytes.to_vec(),
_ => {
return Err(Error::Crypto(format!(
"certificate generation does not support public-key encoding {:?}",
public_key.encoding
)));
}
};
Ok(Self {
scheme,
algorithm,
signing_key,
public_key,
})
}
}
impl rcgen::PublicKeyData for RcgenSigningKey {
fn der_bytes(&self) -> &[u8] {
&self.public_key
}
fn algorithm(&self) -> &'static rcgen::SignatureAlgorithm {
self.algorithm
}
}
impl rcgen::SigningKey for RcgenSigningKey {
fn sign(&self, message: &[u8]) -> std::result::Result<Vec<u8>, rcgen::Error> {
self.signing_key
.sign(self.scheme, message)
.map_err(|_| rcgen::Error::RemoteKeyError)
}
}
#[cfg(all(test, any(feature = "crypto-ring", feature = "crypto-aws-lc-rs")))]
mod test {
use super::*;
use crypto::RTCCryptoProvider;
struct NonExportableSigningKey(Arc<dyn SigningKey>);
impl SigningKey for NonExportableSigningKey {
fn supports(&self, scheme: SignatureScheme) -> bool {
self.0.supports(scheme)
}
fn public_key(&self) -> crypto::PublicKey<'_> {
self.0.public_key()
}
fn sign(
&self,
scheme: SignatureScheme,
message: &[u8],
) -> std::result::Result<Vec<u8>, crypto::CryptoError> {
self.0.sign(scheme, message)
}
}
fn default_test_provider() -> Result<Arc<dyn RTCCryptoProvider>> {
crypto::default_provider().map_err(crypto_error)
}
fn provider_certificate(crypto: &dyn RTCCrypto) -> Result<RTCCertificate> {
RTCCertificate::generate(
crypto,
SignatureScheme::EcdsaP256Sha256,
CertificateParams::new(vec!["webrtc.rs".to_owned()])
.map_err(|e| Error::Other(e.to_string()))?,
)
}
#[test]
fn test_generate_certificate_rsa() -> Result<()> {
let provider = default_test_provider()?;
if !provider
.crypto()
.supports(crypto::CryptoAlgorithm::SigningKeyGeneration(
SignatureScheme::RsaPkcs1Sha256,
))
{
return Ok(());
}
let _certificate = RTCCertificate::generate(
provider.crypto(),
SignatureScheme::RsaPkcs1Sha256,
CertificateParams::new(vec!["webrtc.rs".to_owned()])
.map_err(|e| Error::Other(e.to_string()))?,
)?;
Ok(())
}
#[test]
fn test_generate_certificate_ecdsa() -> Result<()> {
let _cert = RTCCertificate::generate(
default_test_provider()?.crypto(),
SignatureScheme::EcdsaP256Sha256,
CertificateParams::new(vec!["webrtc.rs".to_owned()])
.map_err(|e| Error::Other(e.to_string()))?,
)?;
Ok(())
}
#[test]
fn test_generate_certificate_eddsa() -> Result<()> {
let _cert = RTCCertificate::generate(
default_test_provider()?.crypto(),
SignatureScheme::Ed25519,
CertificateParams::new(vec!["webrtc.rs".to_owned()])
.map_err(|e| Error::Other(e.to_string()))?,
)?;
Ok(())
}
#[test]
fn test_certificate_equal() -> Result<()> {
let cert1 = RTCCertificate::generate(
default_test_provider()?.crypto(),
SignatureScheme::EcdsaP256Sha256,
CertificateParams::new(vec!["webrtc.rs".to_owned()])
.map_err(|e| Error::Other(e.to_string()))?,
)?;
let cert2 = RTCCertificate::generate(
default_test_provider()?.crypto(),
SignatureScheme::EcdsaP256Sha256,
CertificateParams::new(vec!["webrtc.rs".to_owned()])
.map_err(|e| Error::Other(e.to_string()))?,
)?;
assert_ne!(cert1, cert2);
Ok(())
}
#[test]
fn test_generate_certificate_expires() -> Result<()> {
let cert = RTCCertificate::generate(
default_test_provider()?.crypto(),
SignatureScheme::EcdsaP256Sha256,
CertificateParams::new(vec!["webrtc.rs".to_owned()])
.map_err(|e| Error::Other(e.to_string()))?,
)?;
let now = SystemTime::now(); assert!(cert.expires.duration_since(now).is_ok());
Ok(())
}
#[test]
fn test_certificate_serialize_pem_and_from_pem() -> Result<()> {
let cert = RTCCertificate::generate(
default_test_provider()?.crypto(),
SignatureScheme::EcdsaP256Sha256,
CertificateParams::new(vec!["webrtc.rs".to_owned()])
.map_err(|e| Error::Other(e.to_string()))?,
)?;
let pem = cert.serialize_pem()?;
let loaded_cert = RTCCertificate::from_pem(&pem, default_test_provider()?.crypto())?;
assert_eq!(loaded_cert, cert);
Ok(())
}
#[cfg(feature = "crypto-ring")]
#[test]
fn ring_provider_generates_imports_and_fingerprints_certificates() -> Result<()> {
provider_certificate_round_trip(Arc::new(crypto::providers::RingProvider::new()).crypto())
}
#[cfg(feature = "crypto-aws-lc-rs")]
#[test]
fn aws_provider_generates_imports_and_fingerprints_certificates() -> Result<()> {
provider_certificate_round_trip(
Arc::new(crypto::providers::AwsLcRsProvider::new()).crypto(),
)
}
fn provider_certificate_round_trip(crypto: &dyn RTCCrypto) -> Result<()> {
let certificate = provider_certificate(crypto)?;
let fingerprints = certificate.get_fingerprints(crypto)?;
assert_eq!(fingerprints.len(), 1);
assert_eq!(fingerprints[0].algorithm, "sha-256");
let pem = certificate.serialize_pem()?;
let imported = RTCCertificate::from_pem(&pem, crypto)?;
assert_eq!(imported, certificate);
let private_key = certificate
.dtls_certificate
.private_key
.signing_key
.to_pkcs8_der()
.map_err(crypto_error)?
.expect("built-in generated keys are exportable");
let imported = RTCCertificate::from_pkcs8(
crypto,
SignatureScheme::EcdsaP256Sha256,
certificate.dtls_certificate.certificate.clone(),
private_key.as_ref(),
certificate.expires,
)?;
assert_eq!(imported, certificate);
Ok(())
}
#[test]
fn non_exportable_signing_key_returns_an_explicit_pem_error() -> Result<()> {
let provider = crypto::default_provider().map_err(crypto_error)?;
let certificate = provider_certificate(provider.crypto())?;
let signing_key = certificate.dtls_certificate.private_key.signing_key.clone();
let certificate = RTCCertificate::from_signing_key(
certificate.dtls_certificate.certificate,
Arc::new(NonExportableSigningKey(signing_key)),
certificate.expires,
);
let error = certificate.serialize_pem().unwrap_err();
assert!(error.to_string().contains("not exportable"));
Ok(())
}
}