webrtc/peer_connection/
certificate.rs1use std::ops::Add;
2use std::time::{Duration, SystemTime, UNIX_EPOCH};
3
4use dtls::crypto::{CryptoPrivateKey, CryptoPrivateKeyKind};
5use rcgen::{CertificateParams, KeyPair};
6use ring::rand::SystemRandom;
7use ring::rsa;
8use ring::signature::{EcdsaKeyPair, Ed25519KeyPair};
9use sha2::{Digest, Sha256};
10
11use crate::dtls_transport::dtls_fingerprint::RTCDtlsFingerprint;
12use crate::error::{Error, Result};
13use crate::peer_connection::math_rand_alpha;
14use crate::stats::stats_collector::StatsCollector;
15use crate::stats::{CertificateStats, StatsReportType};
16
17#[derive(Clone, Debug)]
27pub struct RTCCertificate {
28 pub(crate) dtls_certificate: dtls::crypto::Certificate,
30 pub(crate) expires: SystemTime,
32 pub(crate) stats_id: String,
38}
39
40impl PartialEq for RTCCertificate {
41 fn eq(&self, other: &Self) -> bool {
42 self.dtls_certificate == other.dtls_certificate
43 }
44}
45
46impl RTCCertificate {
47 fn from_params(params: CertificateParams, key_pair: KeyPair) -> Result<Self> {
51 let not_after = params.not_after;
52
53 let x509_cert = params.self_signed(&key_pair).unwrap();
54 let serialized_der = key_pair.serialize_der();
55
56 let private_key = if key_pair.is_compatible(&rcgen::PKCS_ED25519) {
57 CryptoPrivateKey {
58 kind: CryptoPrivateKeyKind::Ed25519(
59 Ed25519KeyPair::from_pkcs8(&serialized_der)
60 .map_err(|e| Error::new(e.to_string()))?,
61 ),
62 serialized_der,
63 }
64 } else if key_pair.is_compatible(&rcgen::PKCS_ECDSA_P256_SHA256) {
65 CryptoPrivateKey {
66 kind: CryptoPrivateKeyKind::Ecdsa256(
67 EcdsaKeyPair::from_pkcs8(
68 &ring::signature::ECDSA_P256_SHA256_ASN1_SIGNING,
69 &serialized_der,
70 &SystemRandom::new(),
71 )
72 .map_err(|e| Error::new(e.to_string()))?,
73 ),
74 serialized_der,
75 }
76 } else if key_pair.is_compatible(&rcgen::PKCS_RSA_SHA256) {
77 CryptoPrivateKey {
78 kind: CryptoPrivateKeyKind::Rsa256(
79 rsa::KeyPair::from_pkcs8(&serialized_der)
80 .map_err(|e| Error::new(e.to_string()))?,
81 ),
82 serialized_der,
83 }
84 } else {
85 return Err(Error::new("Unsupported key_pair".to_owned()));
86 };
87
88 let expires = if cfg!(target_arch = "arm") {
89 SystemTime::now().add(Duration::from_secs(172800)) } else {
93 not_after.into()
94 };
95
96 Ok(Self {
97 dtls_certificate: dtls::crypto::Certificate {
98 certificate: vec![x509_cert.der().to_owned()],
99 private_key,
100 },
101 expires,
102 stats_id: gen_stats_id(),
103 })
104 }
105
106 pub fn from_key_pair(key_pair: KeyPair) -> Result<Self> {
108 if !(key_pair.is_compatible(&rcgen::PKCS_ED25519)
109 || key_pair.is_compatible(&rcgen::PKCS_ECDSA_P256_SHA256)
110 || key_pair.is_compatible(&rcgen::PKCS_RSA_SHA256))
111 {
112 return Err(Error::new("Unsupported key_pair".to_owned()));
113 }
114
115 RTCCertificate::from_params(
116 CertificateParams::new(vec![math_rand_alpha(16)]).unwrap(),
117 key_pair,
118 )
119 }
120
121 #[cfg(feature = "pem")]
123 pub fn from_pem(pem_str: &str) -> Result<Self> {
124 let mut pem_blocks = pem_str.split("\n\n");
125 let first_block = if let Some(b) = pem_blocks.next() {
126 b
127 } else {
128 return Err(Error::InvalidPEM("empty PEM".into()));
129 };
130 let expires_pem =
131 pem::parse(first_block).map_err(|e| Error::new(format!("can't parse PEM: {e}")))?;
132 if expires_pem.tag() != "EXPIRES" {
133 return Err(Error::InvalidPEM(format!(
134 "invalid tag (expected: 'EXPIRES', got '{}')",
135 expires_pem.tag()
136 )));
137 }
138 let mut bytes = [0u8; 8];
139 bytes.copy_from_slice(&expires_pem.contents()[..8]);
140 let expires = if let Some(e) =
141 SystemTime::UNIX_EPOCH.checked_add(Duration::from_secs(u64::from_le_bytes(bytes)))
142 {
143 e
144 } else {
145 return Err(Error::InvalidPEM("failed to calculate SystemTime".into()));
146 };
147 let dtls_certificate =
148 dtls::crypto::Certificate::from_pem(&pem_blocks.collect::<Vec<&str>>().join("\n\n"))?;
149 Ok(RTCCertificate::from_existing(dtls_certificate, expires))
150 }
151
152 pub fn from_existing(dtls_certificate: dtls::crypto::Certificate, expires: SystemTime) -> Self {
160 Self {
161 dtls_certificate,
162 expires,
163 stats_id: gen_stats_id(),
165 }
166 }
167
168 #[cfg(any(doc, feature = "pem"))]
170 pub fn serialize_pem(&self) -> String {
171 let expires_pem = pem::Pem::new(
175 "EXPIRES".to_string(),
176 self.expires
177 .duration_since(SystemTime::UNIX_EPOCH)
178 .expect("expires to be valid")
179 .as_secs()
180 .to_le_bytes()
181 .to_vec(),
182 );
183 format!(
184 "{}\n{}",
185 pem::encode(&expires_pem),
186 self.dtls_certificate.serialize_pem()
187 )
188 }
189
190 pub fn get_fingerprints(&self) -> Vec<RTCDtlsFingerprint> {
195 let mut fingerprints = Vec::new();
196
197 for c in &self.dtls_certificate.certificate {
198 let mut h = Sha256::new();
199 h.update(c.as_ref());
200 let hashed = h.finalize();
201 let values: Vec<String> = hashed.iter().map(|x| format! {"{x:02x}"}).collect();
202
203 fingerprints.push(RTCDtlsFingerprint {
204 algorithm: "sha-256".to_owned(),
205 value: values.join(":"),
206 });
207 }
208
209 fingerprints
210 }
211
212 pub(crate) async fn collect_stats(&self, collector: &StatsCollector) {
213 if let Some(fingerprint) = self.get_fingerprints().into_iter().next() {
214 let stats = CertificateStats::new(self, fingerprint);
215 collector.insert(
216 self.stats_id.clone(),
217 StatsReportType::CertificateStats(stats),
218 );
219 }
220 }
221}
222
223fn gen_stats_id() -> String {
224 format!(
225 "certificate-{}",
226 SystemTime::now()
227 .duration_since(UNIX_EPOCH)
228 .unwrap()
229 .as_nanos() as u64
230 )
231}
232
233#[cfg(test)]
234mod test {
235 use super::*;
236
237 #[test]
238 fn test_generate_certificate_rsa() -> Result<()> {
239 let key_pair = KeyPair::generate_for(&rcgen::PKCS_RSA_SHA256);
240 assert!(key_pair.is_err(), "RcgenError::KeyGenerationUnavailable");
241
242 Ok(())
243 }
244
245 #[test]
246 fn test_generate_certificate_ecdsa() -> Result<()> {
247 let kp = KeyPair::generate_for(&rcgen::PKCS_ECDSA_P256_SHA256)?;
248 let _cert = RTCCertificate::from_key_pair(kp)?;
249
250 Ok(())
251 }
252
253 #[test]
254 fn test_generate_certificate_eddsa() -> Result<()> {
255 let kp = KeyPair::generate_for(&rcgen::PKCS_ED25519)?;
256 let _cert = RTCCertificate::from_key_pair(kp)?;
257
258 Ok(())
259 }
260
261 #[test]
262 fn test_certificate_equal() -> Result<()> {
263 let kp1 = KeyPair::generate_for(&rcgen::PKCS_ECDSA_P256_SHA256)?;
264 let cert1 = RTCCertificate::from_key_pair(kp1)?;
265
266 let kp2 = KeyPair::generate_for(&rcgen::PKCS_ECDSA_P256_SHA256)?;
267 let cert2 = RTCCertificate::from_key_pair(kp2)?;
268
269 assert_ne!(cert1, cert2);
270
271 Ok(())
272 }
273
274 #[test]
275 fn test_generate_certificate_expires_and_stats_id() -> Result<()> {
276 let kp = KeyPair::generate_for(&rcgen::PKCS_ECDSA_P256_SHA256)?;
277 let cert = RTCCertificate::from_key_pair(kp)?;
278
279 let now = SystemTime::now();
280 assert!(cert.expires.duration_since(now).is_ok());
281 assert!(cert.stats_id.contains("certificate"));
282
283 Ok(())
284 }
285
286 #[cfg(feature = "pem")]
287 #[test]
288 fn test_certificate_serialize_pem_and_from_pem() -> Result<()> {
289 let kp = KeyPair::generate_for(&rcgen::PKCS_ECDSA_P256_SHA256)?;
290 let cert = RTCCertificate::from_key_pair(kp)?;
291
292 let pem = cert.serialize_pem();
293 let loaded_cert = RTCCertificate::from_pem(&pem)?;
294
295 assert_eq!(loaded_cert, cert);
296
297 Ok(())
298 }
299}