Skip to content

Commit

Permalink
[peer_connection] allow persistent certificates (#204)
Browse files Browse the repository at this point in the history
Closes #168 

This PR adds `RTCCertificate::from_existing` method, which constructs `RTCCertificate` from an existing  DTLS certificate. An existing certificate might be needed in cases like [this](libp2p/rust-libp2p#2622) where you need DTLS identity to be fixed for some period of time (whole duration of the certificate or some part of it).

* peer_connection: replace x509_cert with pem field

also, make `pem` and `expires` fields private
and add `RTCCertificate::from_existing` method, which gives a way to construct a
`RTCCertificate` using an already generated certificate (as opposed to
generating a new one using `from_params` or `from_key_pair` methods).

* make RTCConfiguration clonable

otherwise, it's not possible to reuse the same config across N peer
connections.
  • Loading branch information
melekes committed Jul 4, 2022
1 parent 9db7d2e commit 46fd086
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 19 deletions.
77 changes: 59 additions & 18 deletions src/peer_connection/certificate.rs
Expand Up @@ -15,11 +15,13 @@ use tokio::sync::Mutex;
use waitgroup::Worker;

/// Certificate represents a x509Cert used to authenticate WebRTC communications.
#[derive(Clone)]
pub struct RTCCertificate {
pub(crate) certificate: dtls::crypto::Certificate,
pub(crate) stats_id: String,
pub(crate) x509_cert: rcgen::Certificate,
pub(crate) expires: SystemTime,

pem: String,
expires: SystemTime,
}

/// Equals determines if two certificates are identical by comparing only certificate
Expand Down Expand Up @@ -100,16 +102,45 @@ impl RTCCertificate {
.unwrap()
.as_nanos() as u64
),
x509_cert,
pem: x509_cert.serialize_pem()?,
expires,
})
}

/// Constructs a `RTCCertificate` from an existing certificate.
///
/// Use this method when you have a persistent certificate (i.e. you don't want to generate a
/// new one for each DTLS connection).
pub fn from_existing(
certificate: dtls::crypto::Certificate,
pem: &str,
expires: SystemTime,
) -> Self {
Self {
certificate,
stats_id: format!(
"certificate-{}",
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_nanos() as u64
),
pem: pem.to_owned(),
expires,
}
}

/// expires returns the timestamp after which this certificate is no longer valid.
pub fn expires(&self) -> SystemTime {
self.expires
}

/// pem returns the certificate encoded as two PEM blocks: one for the X509 certificate and the
/// other for the private key.
pub fn pem(&self) -> &str {
&self.pem
}

/// get_fingerprints returns certificate fingerprints, one of which
/// is computed with the digest algorithm used in the certificate signature.
pub fn get_fingerprints(&self) -> Result<Vec<RTCDtlsFingerprint>> {
Expand Down Expand Up @@ -239,12 +270,6 @@ impl RTCCertificate {

RTCCertificate::from_params(params)
}

/// PEM returns the certificate encoded as two pem block: once for the X509
/// certificate and the other for the private key
pub fn pem(&self) -> Result<String> {
Ok(self.x509_cert.serialize_pem()?)
}
}

#[cfg(test)]
Expand All @@ -259,9 +284,8 @@ mod test {
let kp_pem = kp.serialize_pem();
let cert = Certificate::generate_certificate(kp)?;
let cert_pem = cert.x509_cert.serialize_pem()?;
//_, err = tls.X509KeyPair(certPEM, skPEM)
//_, err = tls.X509KeyPair(cert.pem(), skPEM)
*/
Ok(())
}
Expand All @@ -273,8 +297,7 @@ mod test {
assert!(kp_pem.contains("PRIVATE KEY"));

let cert = RTCCertificate::from_key_pair(kp)?;
let cert_pem = cert.x509_cert.serialize_pem()?;
assert!(cert_pem.contains("CERTIFICATE"));
assert!(cert.pem().contains("CERTIFICATE"));

//_, err = tls.X509KeyPair(certPEM, skPEM)

Expand Down Expand Up @@ -304,14 +327,14 @@ mod test {
let kp1 = KeyPair::generate(&rcgen::PKCS_ECDSA_P256_SHA256)?;
let kp1_pem = kp1.serialize_pem();
let cert1 = RTCCertificate::from_key_pair(kp1)?;
let cert1_pem = cert1.pem()?;
let cert1_pem = cert1.pem();

let kp2 = KeyPair::generate(&rcgen::PKCS_ECDSA_P256_SHA256)?;
let _cert2 = RTCCertificate::from_key_pair(kp2)?;

let kp3 = KeyPair::from_pem(kp1_pem.as_str())?;
let kp3_pem = kp3.serialize_pem();
let _cert3 = RTCCertificate::from_pem(cert1_pem.as_str(), kp3)?;
let _cert3 = RTCCertificate::from_pem(cert1_pem, kp3)?;

assert_eq!(kp1_pem, kp3_pem);
//assert!(cert1 != cert2);
Expand Down Expand Up @@ -355,18 +378,36 @@ mod test {
let kp = KeyPair::generate(&rcgen::PKCS_ECDSA_P256_SHA256)?;
let kp_pem = kp.serialize_pem();
let cert = RTCCertificate::from_key_pair(kp)?;
let pem = cert.pem()?;
let pem = cert.pem();
log::info!("{}", pem);

let kp2 = KeyPair::from_pem(kp_pem.as_str())?;
let kp2_pem = kp2.serialize_pem();
let cert2 = RTCCertificate::from_pem(pem.as_str(), kp2)?;
let pem2 = cert2.pem()?;
let cert2 = RTCCertificate::from_pem(pem, kp2)?;
let pem2 = cert2.pem();
log::info!("{}", pem2);

assert_eq!(kp_pem, kp2_pem);
//TODO: assert_eq!(pem, pem2);

Ok(())
}

#[test]
fn test_from_existing() -> Result<()> {
// NOTE `dtls_cert` key pair and `key_pair` are different, but it's fine here.
let key_pair = KeyPair::generate(&rcgen::PKCS_ECDSA_P256_SHA256)?;
let dtls_cert = dtls::crypto::Certificate::generate_self_signed(["localhost".to_owned()])?;

let expires = SystemTime::now();
let pem = key_pair.serialize_pem();

let cert = RTCCertificate::from_existing(dtls_cert, &pem, expires);

assert_ne!("", cert.stats_id);
assert_eq!(expires, cert.expires());
assert_eq!(pem, cert.pem());

Ok(())
}
}
2 changes: 1 addition & 1 deletion src/peer_connection/configuration.rs
Expand Up @@ -10,7 +10,7 @@ use crate::peer_connection::policy::sdp_semantics::RTCSdpSemantics;
/// Configurations may be set up once and reused across multiple connections.
/// Configurations are treated as readonly. As long as they are unmodified,
/// they are safe for concurrent use.
#[derive(Default)]
#[derive(Default, Clone)]
pub struct RTCConfiguration {
/// iceservers defines a slice describing servers available to be used by
/// ICE, such as STUN and TURN servers.
Expand Down

0 comments on commit 46fd086

Please sign in to comment.