Skip to content

Commit

Permalink
add certficate
Browse files Browse the repository at this point in the history
  • Loading branch information
yngrtc committed Jan 6, 2024
1 parent 3dc5d75 commit 3ef8dc4
Show file tree
Hide file tree
Showing 9 changed files with 280 additions and 3 deletions.
5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,15 @@ bytes = "1.5.0"
log = "0.4.20"
rand = "0.8.5"
async-broadcast = "0.6.0"
rcgen = { version = "0.12.0", features = ["pem", "x509-parser"] }
rcgen = { version = "0.11", features = ["pem", "x509-parser"] }
ctrlc = "3.4.2"
thiserror = "1.0.53"
core_affinity = "0.8.1"
num_cpus = "1.16.0"
smol = "2.0.0"
ring = "0.17.7"
sha2 = "0.10.8"
rustls = "0.21.7"

# RTC protocol
shared = { path = "rtc/shared"}
Expand Down
1 change: 0 additions & 1 deletion examples/chat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@ fn main() -> anyhow::Result<()> {

//let key_pair = rcgen::KeyPair::generate(&rcgen::PKCS_ECDSA_P256_SHA256)?;
// let certificate = RTCCertificate::from_key_pair(key_pair)?;
// fingerprints = certificate.get_fingerprints();

/*let (cancel_tx, signal_cancel_rx) = broadcast::channel(1);
let rtc_cancel_rx = cancel_tx.subscribe();
Expand Down
224 changes: 224 additions & 0 deletions src/server/certificate.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
use dtls::crypto::{CryptoPrivateKey, CryptoPrivateKeyKind};
use rand::thread_rng;
use rand::Rng;
use rcgen::{CertificateParams, KeyPair};
use ring::rand::SystemRandom;
use ring::rsa;
use ring::signature::{EcdsaKeyPair, Ed25519KeyPair};
use sha2::{Digest, Sha256};
use shared::error::{Error, Result};
use std::ops::Add;
use std::time::{Duration, SystemTime};

/// DTLSFingerprint specifies the hash function algorithm and certificate
/// fingerprint as described in <https://tools.ietf.org/html/rfc4572>.
#[derive(Default, Debug, Clone)]
pub struct RTCDtlsFingerprint {
/// Algorithm specifies one of the the hash function algorithms defined in
/// the 'Hash function Textual Names' registry.
pub algorithm: String,

/// Value specifies the value of the certificate fingerprint in lowercase
/// hex string as expressed utilizing the syntax of 'fingerprint' in
/// <https://tools.ietf.org/html/rfc4572#section-5>.
pub value: String,
}

/// Certificate represents a X.509 certificate used to authenticate WebRTC communications.
#[derive(Clone, Debug)]
pub struct RTCCertificate {
/// DTLS certificate.
pub(crate) dtls_certificate: dtls::crypto::Certificate,
/// Timestamp after which this certificate is no longer valid.
pub(crate) expires: SystemTime,
}

impl PartialEq for RTCCertificate {
fn eq(&self, other: &Self) -> bool {
self.dtls_certificate == other.dtls_certificate
}
}

impl RTCCertificate {
/// Generates a new certificate from the given parameters.
///
/// See [`rcgen::Certificate::from_params`].
pub fn from_params(params: CertificateParams) -> Result<Self> {
let not_after = params.not_after;
let x509_cert = rcgen::Certificate::from_params(params)?;

let key_pair = x509_cert.get_key_pair();
let serialized_der = key_pair.serialize_der();

let private_key = if key_pair.is_compatible(&rcgen::PKCS_ED25519) {
CryptoPrivateKey {
kind: CryptoPrivateKeyKind::Ed25519(
Ed25519KeyPair::from_pkcs8(&serialized_der)
.map_err(|e| Error::Other(e.to_string()))?,
),
serialized_der,
}
} else if key_pair.is_compatible(&rcgen::PKCS_ECDSA_P256_SHA256) {
CryptoPrivateKey {
kind: CryptoPrivateKeyKind::Ecdsa256(
EcdsaKeyPair::from_pkcs8(
&ring::signature::ECDSA_P256_SHA256_ASN1_SIGNING,
&serialized_der,
&SystemRandom::new(),
)
.map_err(|e| Error::Other(e.to_string()))?,
),
serialized_der,
}
} else if key_pair.is_compatible(&rcgen::PKCS_RSA_SHA256) {
CryptoPrivateKey {
kind: CryptoPrivateKeyKind::Rsa256(
rsa::KeyPair::from_pkcs8(&serialized_der)
.map_err(|e| Error::Other(e.to_string()))?,
),
serialized_der,
}
} else {
return Err(Error::Other("Unsupported key_pair".to_owned()));
};

let expires = if cfg!(target_arch = "arm") {
// Workaround for issue overflow when adding duration to instant on armv7
// https://github.com/webrtc-rs/examples/issues/5 https://github.com/chronotope/chrono/issues/343
SystemTime::now().add(Duration::from_secs(172800)) //60*60*48 or 2 days
} else {
not_after.into()
};

Ok(Self {
dtls_certificate: dtls::crypto::Certificate {
certificate: vec![rustls::Certificate(x509_cert.serialize_der()?)],
private_key,
},
expires,
})
}

/// Generates a new certificate with default [`CertificateParams`] using the given keypair.
pub fn from_key_pair(key_pair: KeyPair) -> Result<Self> {
let mut params = CertificateParams::new(vec![math_rand_alpha(16)]);

if key_pair.is_compatible(&rcgen::PKCS_ED25519) {
params.alg = &rcgen::PKCS_ED25519;
} else if key_pair.is_compatible(&rcgen::PKCS_ECDSA_P256_SHA256) {
params.alg = &rcgen::PKCS_ECDSA_P256_SHA256;
} else if key_pair.is_compatible(&rcgen::PKCS_RSA_SHA256) {
params.alg = &rcgen::PKCS_RSA_SHA256;
} else {
return Err(Error::Other("Unsupported key_pair".to_owned()));
};
params.key_pair = Some(key_pair);

RTCCertificate::from_params(params)
}

/// Parses a certificate from the ASCII PEM format.
#[cfg(feature = "pem")]
pub fn from_pem(pem_str: &str) -> 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::new(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"))?;
Ok(RTCCertificate::from_existing(dtls_certificate, expires))
}

/// Builds a [`RTCCertificate`] using the existing DTLS 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).
///
/// NOTE: ID used for statistics will be different as it's neither derived from the given
/// certificate nor persisted along it when using [`serialize_pem`].
pub fn from_existing(dtls_certificate: dtls::crypto::Certificate, expires: SystemTime) -> Self {
Self {
dtls_certificate,
expires,
}
}

/// Serializes the certificate (including the private key) in PKCS#8 format in PEM.
#[cfg(feature = "pem")]
pub fn serialize_pem(&self) -> String {
// Encode `expires` as a PEM block.
//
// TODO: serialize as nanos when https://github.com/rust-lang/rust/issues/103332 is fixed.
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(),
);
format!(
"{}\n{}",
pem::encode(&expires_pem),
self.dtls_certificate.serialize_pem()
)
}

/// get_fingerprints returns a SHA-256 fingerprint of this certificate.
///
/// TODO: return a fingerprint computed with the digest algorithm used in the certificate
/// signature.
pub fn get_fingerprints(&self) -> Vec<RTCDtlsFingerprint> {
let mut fingerprints = Vec::new();

for c in &self.dtls_certificate.certificate {
let mut h = Sha256::new();
h.update(c.as_ref());
let hashed = h.finalize();
let values: Vec<String> = hashed.iter().map(|x| format! {"{x:02x}"}).collect();

fingerprints.push(RTCDtlsFingerprint {
algorithm: "sha-256".to_owned(),
value: values.join(":"),
});
}

fingerprints
}
}

const RUNES_ALPHA: &[u8] = b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";

/// math_rand_alpha generates a mathematical random alphabet sequence of the requested length.
fn math_rand_alpha(n: usize) -> String {
let mut rng = thread_rng();

let rand_string: String = (0..n)
.map(|_| {
let idx = rng.gen_range(0..RUNES_ALPHA.len());
RUNES_ALPHA[idx] as char
})
.collect();

rand_string
}
6 changes: 6 additions & 0 deletions src/server/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
use crate::server::certificate::RTCCertificate;

#[derive(Default, Debug)]
pub struct ServerConfig {
pub certificate: Option<RTCCertificate>,
}
3 changes: 3 additions & 0 deletions src/server/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;

pub mod certificate;
pub mod config;
pub mod room;
pub mod states;

use crate::shared::types::RoomId;
use room::Room;
Expand Down
2 changes: 1 addition & 1 deletion src/server/room/endpoint/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::shared::types::{EndpointId, RoomId};

#[derive(Default)]
#[derive(Default, Debug)]
pub struct Endpoint {
room_id: RoomId,
endpoint_id: EndpointId,
Expand Down
1 change: 1 addition & 0 deletions src/server/room/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ pub mod endpoint;
use crate::shared::types::{EndpointId, RoomId};
use endpoint::Endpoint;

#[derive(Default, Debug)]
pub struct Room {
room_id: RoomId,
endpoints: RefCell<HashMap<EndpointId, Rc<Endpoint>>>,
Expand Down
23 changes: 23 additions & 0 deletions src/server/states.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use crate::server::config::ServerConfig;
use crate::server::room::{endpoint::Endpoint, Room};
use crate::shared::types::{FourTuple, RoomId};
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
use std::sync::Arc;

#[derive(Default, Debug)]
pub struct ServerStates {
config: Arc<ServerConfig>,
rooms: RefCell<HashMap<RoomId, Rc<Room>>>,
endpoints: RefCell<HashMap<FourTuple, Rc<Endpoint>>>,
}

impl ServerStates {
pub fn new(config: Arc<ServerConfig>) -> Self {
Self {
config,
..Default::default()
}
}
}
18 changes: 18 additions & 0 deletions src/shared/types.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,20 @@
use retty::transport::TransportContext;
use std::net::SocketAddr;

pub type EndpointId = u64;
pub type RoomId = u64;

#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct FourTuple {
pub local_addr: SocketAddr,
pub peer_addr: SocketAddr,
}

impl From<&TransportContext> for FourTuple {
fn from(value: &TransportContext) -> Self {
Self {
local_addr: value.local_addr,
peer_addr: value.peer_addr,
}
}
}

0 comments on commit 3ef8dc4

Please sign in to comment.