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