1 use dtls::crypto::{CryptoPrivateKey, CryptoPrivateKeyKind};
2 use rcgen::{CertificateParams, KeyPair};
3 use ring::signature::{EcdsaKeyPair, Ed25519KeyPair, RsaKeyPair};
4 use sha2::{Digest, Sha256};
5 
6 use std::ops::Add;
7 use std::time::{Duration, SystemTime, UNIX_EPOCH};
8 
9 use crate::dtls_transport::dtls_fingerprint::RTCDtlsFingerprint;
10 use crate::error::{Error, Result};
11 use crate::peer_connection::math_rand_alpha;
12 use crate::stats::stats_collector::StatsCollector;
13 use crate::stats::{CertificateStats, StatsReportType};
14 
15 /// Certificate represents a X.509 certificate used to authenticate WebRTC communications.
16 #[derive(Clone, Debug)]
17 pub struct RTCCertificate {
18     /// DTLS certificate.
19     pub(crate) dtls_certificate: dtls::crypto::Certificate,
20     /// Timestamp after which this certificate is no longer valid.
21     pub(crate) expires: SystemTime,
22     /// Certificate's ID used for statistics.
23     ///
24     /// Example: "certificate-1667202302853538793"
25     ///
26     /// See [`CertificateStats`].
27     pub(crate) stats_id: String,
28 }
29 
30 impl PartialEq for RTCCertificate {
eq(&self, other: &Self) -> bool31     fn eq(&self, other: &Self) -> bool {
32         self.dtls_certificate == other.dtls_certificate
33     }
34 }
35 
36 impl RTCCertificate {
37     /// Generates a new certificate from the given parameters.
38     ///
39     /// See [`rcgen::Certificate::from_params`].
from_params(params: CertificateParams) -> Result<Self>40     pub fn from_params(params: CertificateParams) -> Result<Self> {
41         let not_after = params.not_after;
42         let x509_cert = rcgen::Certificate::from_params(params)?;
43 
44         let key_pair = x509_cert.get_key_pair();
45         let serialized_der = key_pair.serialize_der();
46 
47         let private_key = if key_pair.is_compatible(&rcgen::PKCS_ED25519) {
48             CryptoPrivateKey {
49                 kind: CryptoPrivateKeyKind::Ed25519(
50                     Ed25519KeyPair::from_pkcs8(&serialized_der)
51                         .map_err(|e| Error::new(e.to_string()))?,
52                 ),
53                 serialized_der,
54             }
55         } else if key_pair.is_compatible(&rcgen::PKCS_ECDSA_P256_SHA256) {
56             CryptoPrivateKey {
57                 kind: CryptoPrivateKeyKind::Ecdsa256(
58                     EcdsaKeyPair::from_pkcs8(
59                         &ring::signature::ECDSA_P256_SHA256_ASN1_SIGNING,
60                         &serialized_der,
61                     )
62                     .map_err(|e| Error::new(e.to_string()))?,
63                 ),
64                 serialized_der,
65             }
66         } else if key_pair.is_compatible(&rcgen::PKCS_RSA_SHA256) {
67             CryptoPrivateKey {
68                 kind: CryptoPrivateKeyKind::Rsa256(
69                     RsaKeyPair::from_pkcs8(&serialized_der)
70                         .map_err(|e| Error::new(e.to_string()))?,
71                 ),
72                 serialized_der,
73             }
74         } else {
75             return Err(Error::new("Unsupported key_pair".to_owned()));
76         };
77 
78         let expires = if cfg!(target_arch = "arm") {
79             // Workaround for issue overflow when adding duration to instant on armv7
80             // https://github.com/webrtc-rs/examples/issues/5 https://github.com/chronotope/chrono/issues/343
81             SystemTime::now().add(Duration::from_secs(172800)) //60*60*48 or 2 days
82         } else {
83             not_after.into()
84         };
85 
86         Ok(Self {
87             dtls_certificate: dtls::crypto::Certificate {
88                 certificate: vec![rustls::Certificate(x509_cert.serialize_der()?)],
89                 private_key,
90             },
91             expires,
92             stats_id: gen_stats_id(),
93         })
94     }
95 
96     /// Generates a new certificate with default [`CertificateParams`] using the given keypair.
from_key_pair(key_pair: KeyPair) -> Result<Self>97     pub fn from_key_pair(key_pair: KeyPair) -> Result<Self> {
98         let mut params = CertificateParams::new(vec![math_rand_alpha(16)]);
99 
100         if key_pair.is_compatible(&rcgen::PKCS_ED25519) {
101             params.alg = &rcgen::PKCS_ED25519;
102         } else if key_pair.is_compatible(&rcgen::PKCS_ECDSA_P256_SHA256) {
103             params.alg = &rcgen::PKCS_ECDSA_P256_SHA256;
104         } else if key_pair.is_compatible(&rcgen::PKCS_RSA_SHA256) {
105             params.alg = &rcgen::PKCS_RSA_SHA256;
106         } else {
107             return Err(Error::new("Unsupported key_pair".to_owned()));
108         };
109         params.key_pair = Some(key_pair);
110 
111         RTCCertificate::from_params(params)
112     }
113 
114     /// Parses a certificate from the ASCII PEM format.
115     #[cfg(feature = "pem")]
from_pem(pem_str: &str) -> Result<Self>116     pub fn from_pem(pem_str: &str) -> Result<Self> {
117         let mut pem_blocks = pem_str.split("\n\n");
118         let first_block = if let Some(b) = pem_blocks.next() {
119             b
120         } else {
121             return Err(Error::InvalidPEM("empty PEM".into()));
122         };
123         let expires_pem =
124             pem::parse(first_block).map_err(|e| Error::new(format!("can't parse PEM: {e}")))?;
125         if expires_pem.tag != "EXPIRES" {
126             return Err(Error::InvalidPEM(format!(
127                 "invalid tag (expected: 'EXPIRES', got '{}')",
128                 expires_pem.tag
129             )));
130         }
131         let mut bytes = [0u8; 8];
132         bytes.copy_from_slice(&expires_pem.contents[..8]);
133         let expires = if let Some(e) =
134             SystemTime::UNIX_EPOCH.checked_add(Duration::from_secs(u64::from_le_bytes(bytes)))
135         {
136             e
137         } else {
138             return Err(Error::InvalidPEM("failed to calculate SystemTime".into()));
139         };
140         let dtls_certificate =
141             dtls::crypto::Certificate::from_pem(&pem_blocks.collect::<Vec<&str>>().join("\n\n"))?;
142         Ok(RTCCertificate::from_existing(dtls_certificate, expires))
143     }
144 
145     /// Builds a [`RTCCertificate`] using the existing DTLS certificate.
146     ///
147     /// Use this method when you have a persistent certificate (i.e. you don't want to generate a
148     /// new one for each DTLS connection).
149     ///
150     /// NOTE: ID used for statistics will be different as it's neither derived from the given
151     /// certificate nor persisted along it when using [`serialize_pem`].
from_existing(dtls_certificate: dtls::crypto::Certificate, expires: SystemTime) -> Self152     pub fn from_existing(dtls_certificate: dtls::crypto::Certificate, expires: SystemTime) -> Self {
153         Self {
154             dtls_certificate,
155             expires,
156             // TODO: figure out if it needs to be persisted
157             stats_id: gen_stats_id(),
158         }
159     }
160 
161     /// Serializes the certificate (including the private key) in PKCS#8 format in PEM.
162     #[cfg(feature = "pem")]
serialize_pem(&self) -> String163     pub fn serialize_pem(&self) -> String {
164         // Encode `expires` as a PEM block.
165         //
166         // TODO: serialize as nanos when https://github.com/rust-lang/rust/issues/103332 is fixed.
167         let expires_pem = pem::Pem {
168             tag: "EXPIRES".to_string(),
169             contents: self
170                 .expires
171                 .duration_since(SystemTime::UNIX_EPOCH)
172                 .expect("expires to be valid")
173                 .as_secs()
174                 .to_le_bytes()
175                 .to_vec(),
176         };
177         format!(
178             "{}\n{}",
179             pem::encode(&expires_pem),
180             self.dtls_certificate.serialize_pem()
181         )
182     }
183 
184     /// get_fingerprints returns a SHA-256 fingerprint of this certificate.
185     ///
186     /// TODO: return a fingerprint computed with the digest algorithm used in the certificate
187     /// signature.
get_fingerprints(&self) -> Vec<RTCDtlsFingerprint>188     pub fn get_fingerprints(&self) -> Vec<RTCDtlsFingerprint> {
189         let mut fingerprints = Vec::new();
190 
191         for c in &self.dtls_certificate.certificate {
192             let mut h = Sha256::new();
193             h.update(c.as_ref());
194             let hashed = h.finalize();
195             let values: Vec<String> = hashed.iter().map(|x| format! {"{x:02x}"}).collect();
196 
197             fingerprints.push(RTCDtlsFingerprint {
198                 algorithm: "sha-256".to_owned(),
199                 value: values.join(":"),
200             });
201         }
202 
203         fingerprints
204     }
205 
collect_stats(&self, collector: &StatsCollector)206     pub(crate) async fn collect_stats(&self, collector: &StatsCollector) {
207         if let Some(fingerprint) = self.get_fingerprints().into_iter().next() {
208             let stats = CertificateStats::new(self, fingerprint);
209             collector.insert(
210                 self.stats_id.clone(),
211                 StatsReportType::CertificateStats(stats),
212             );
213         }
214     }
215 }
216 
gen_stats_id() -> String217 fn gen_stats_id() -> String {
218     format!(
219         "certificate-{}",
220         SystemTime::now()
221             .duration_since(UNIX_EPOCH)
222             .unwrap()
223             .as_nanos() as u64
224     )
225 }
226 
227 #[cfg(test)]
228 mod test {
229     use super::*;
230 
231     #[test]
test_generate_certificate_rsa() -> Result<()>232     fn test_generate_certificate_rsa() -> Result<()> {
233         let key_pair = KeyPair::generate(&rcgen::PKCS_RSA_SHA256);
234         assert!(key_pair.is_err(), "RcgenError::KeyGenerationUnavailable");
235 
236         Ok(())
237     }
238 
239     #[test]
test_generate_certificate_ecdsa() -> Result<()>240     fn test_generate_certificate_ecdsa() -> Result<()> {
241         let kp = KeyPair::generate(&rcgen::PKCS_ECDSA_P256_SHA256)?;
242         let _cert = RTCCertificate::from_key_pair(kp)?;
243 
244         Ok(())
245     }
246 
247     #[test]
test_generate_certificate_eddsa() -> Result<()>248     fn test_generate_certificate_eddsa() -> Result<()> {
249         let kp = KeyPair::generate(&rcgen::PKCS_ED25519)?;
250         let _cert = RTCCertificate::from_key_pair(kp)?;
251 
252         Ok(())
253     }
254 
255     #[test]
test_certificate_equal() -> Result<()>256     fn test_certificate_equal() -> Result<()> {
257         let kp1 = KeyPair::generate(&rcgen::PKCS_ECDSA_P256_SHA256)?;
258         let cert1 = RTCCertificate::from_key_pair(kp1)?;
259 
260         let kp2 = KeyPair::generate(&rcgen::PKCS_ECDSA_P256_SHA256)?;
261         let cert2 = RTCCertificate::from_key_pair(kp2)?;
262 
263         assert_ne!(cert1, cert2);
264 
265         Ok(())
266     }
267 
268     #[test]
test_generate_certificate_expires_and_stats_id() -> Result<()>269     fn test_generate_certificate_expires_and_stats_id() -> Result<()> {
270         let kp = KeyPair::generate(&rcgen::PKCS_ECDSA_P256_SHA256)?;
271         let cert = RTCCertificate::from_key_pair(kp)?;
272 
273         let now = SystemTime::now();
274         assert!(cert.expires.duration_since(now).is_ok());
275         assert!(cert.stats_id.contains("certificate"));
276 
277         Ok(())
278     }
279 
280     #[cfg(feature = "pem")]
281     #[test]
test_certificate_serialize_pem_and_from_pem() -> Result<()>282     fn test_certificate_serialize_pem_and_from_pem() -> Result<()> {
283         let kp = KeyPair::generate(&rcgen::PKCS_ECDSA_P256_SHA256)?;
284         let cert = RTCCertificate::from_key_pair(kp)?;
285 
286         let pem = cert.serialize_pem();
287         let loaded_cert = RTCCertificate::from_pem(&pem)?;
288 
289         assert_eq!(loaded_cert, cert);
290 
291         Ok(())
292     }
293 }
294