Skip to content

Commit

Permalink
Implement deterministic certificate generation
Browse files Browse the repository at this point in the history
  • Loading branch information
thomaseizinger committed Oct 25, 2022
1 parent 9a80510 commit 492fd50
Show file tree
Hide file tree
Showing 5 changed files with 152 additions and 45 deletions.
8 changes: 7 additions & 1 deletion transports/webrtc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ multihash = { version = "0.16", default-features = false, features = ["sha2"] }
prost = "0.11"
prost-codec = { version = "0.2.1", path = "../../misc/prost-codec" }
rand = "0.8"
rcgen = "0.9.3"
ring = "0.16.20"
serde = { version = "1.0", features = ["derive"] }
stun = "0.4"
thiserror = "1"
Expand All @@ -43,5 +45,9 @@ anyhow = "1.0"
env_logger = "0.9"
hex-literal = "0.3"
libp2p = { path = "../..", features = ["request-response", "webrtc"], default-features = false }
rcgen = "0.9.3"
unsigned-varint = { version = "0.7", features = ["asynchronous_codec"] }
rand_chacha = "0.3.1"

[[test]]
name = "smoke"
required-features = ["tokio"]
126 changes: 126 additions & 0 deletions transports/webrtc/src/tokio/certificate.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
use crate::tokio::fingerprint::Fingerprint;
use rand::distributions::DistString;
use rand::{CryptoRng, Rng};
use rcgen::{CertificateParams, RcgenError, PKCS_ED25519};
use ring::signature::Ed25519KeyPair;
use ring::test::rand::FixedSliceSequenceRandom;
use std::cell::UnsafeCell;
use webrtc::peer_connection::certificate::RTCCertificate;

#[derive(Clone, PartialEq)]
pub struct Certificate {
inner: RTCCertificate,
}

impl Certificate {
/// Generate new certificate.
///
/// This function is pure and will generate the same certificate provided the exact same randomness source.
pub fn generate<R>(rng: &mut R) -> Result<Self, Error>
where
R: CryptoRng + Rng,
{
let keypair = new_keypair(rng)?;
let alt_name = rand::distributions::Alphanumeric.sample_string(rng, 16);

let mut params = CertificateParams::new(vec![alt_name.clone()]);
params.alg = &PKCS_ED25519;
params.key_pair = Some(keypair);

let certificate = RTCCertificate::from_params(params).map_err(Kind::WebRTC)?;

Ok(Self { inner: certificate })
}

pub fn fingerprint(&self) -> Fingerprint {
let fingerprints = self.inner.get_fingerprints().expect("to never fail");
let sha256_fingerprint = fingerprints
.iter()
.find(|f| f.algorithm == "sha-256")
.expect("a SHA-256 fingerprint");

Fingerprint::try_from_rtc_dtls(sha256_fingerprint).expect("we filtered by sha-256")
}

pub fn to_pem(&self) -> &str {
self.inner.pem()
}

/// Extract the [`RTCCertificate`] from this wrapper.
///
/// This function is `pub(crate)` to avoid leaking the `webrtc` dependency to our users.
pub(crate) fn to_rtc_certificate(&self) -> RTCCertificate {
self.inner.clone()
}
}

#[derive(thiserror::Error, Debug)]
#[error("Failed to generate certificate")]
pub struct Error(#[from] Kind);

#[derive(thiserror::Error, Debug)]
enum Kind {
#[error(transparent)]
Rcgen(RcgenError),
#[error(transparent)]
Ring(ring::error::Unspecified),
#[error(transparent)]
WebRTC(webrtc::Error),
}

/// Generates a new [`rcgen::KeyPair`] from the given randomness source.
///
/// This implementation uses `ring`'s [`FixedSliceSequenceRandom`] to create a deterministic randomness
/// source which allows us to fully control the resulting keypair.
///
/// [`FixedSliceSequenceRandom`] only hands out the provided byte-slices for each call to
/// [`SecureRandom::fill`] and therefore does not offer ANY guarantees about the randomess, it is
/// all under our control.
///
/// Using [`FixedSliceSequenceRandom`] over [`FixedSliceRandom`] ensures that we only ever use the
/// provided byte-slice once and don't reuse our "randomness" for different variables which could be
/// a security problem.
fn new_keypair<R>(rng: &mut R) -> Result<rcgen::KeyPair, Error>
where
R: CryptoRng + Rng,
{
let ring_rng = FixedSliceSequenceRandom {
bytes: &[&rng.gen::<[u8; 32]>()],
current: UnsafeCell::new(0),
};

let document = Ed25519KeyPair::generate_pkcs8(&ring_rng).map_err(Kind::Ring)?;
let der = document.as_ref();
let keypair = rcgen::KeyPair::from_der(der).map_err(Kind::Rcgen)?;

Ok(keypair)
}

#[cfg(test)]
mod tests {
use super::*;
use rand::thread_rng;
use rand::SeedableRng;
use rand_chacha::ChaCha20Rng;

#[test]
fn certificate_generation_is_deterministic() {
let certificate = Certificate::generate(&mut ChaCha20Rng::from_seed([0u8; 32])).unwrap();

let pem = certificate.to_pem();

assert_eq!(
pem,
"-----BEGIN CERTIFICATE-----\r\nMIIBGTCBzKADAgECAgkA349L+6JmEFgwBQYDK2VwMCExHzAdBgNVBAMMFnJjZ2Vu\r\nIHNlbGYgc2lnbmVkIGNlcnQwIBcNNzUwMTAxMDAwMDAwWhgPNDA5NjAxMDEwMDAw\r\nMDBaMCExHzAdBgNVBAMMFnJjZ2VuIHNlbGYgc2lnbmVkIGNlcnQwKjAFBgMrZXAD\r\nIQDqCj9zLT7ijKUXLEZw7/JZeuDEvleLkSgyFN3Q8lhWXqMfMB0wGwYDVR0RBBQw\r\nEoIQNTRDZG14TlhhREhFd1k4VzAFBgMrZXADQQBQdGOd+rpYKM63TTDT7V4TysD3\r\nhSD7qs2fNQ+tM7mGe9r2mScaOXvnoCnlLt/wDsEB3hFwpkmRbgZMLjCooZ8E\r\n-----END CERTIFICATE-----\r\n"
)
}

#[test]
fn cloned_certificate_is_equivalent() {
let certificate = Certificate::generate(&mut thread_rng()).unwrap();

let cloned_certificate = certificate.clone();

assert!(certificate == cloned_certificate)
}
}
2 changes: 2 additions & 0 deletions transports/webrtc/src/tokio/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

pub mod certificate;
mod connection;
mod error;
mod fingerprint;
Expand All @@ -28,6 +29,7 @@ mod transport;
mod udp_mux;
mod upgrade;

pub use certificate::Certificate;
pub use connection::Connection;
pub use error::Error;
pub use transport::Transport;
42 changes: 12 additions & 30 deletions transports/webrtc/src/tokio/transport.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ use libp2p_core::{
transport::{ListenerId, TransportError, TransportEvent},
PeerId,
};
use webrtc::peer_connection::certificate::RTCCertificate;
use webrtc::peer_connection::configuration::RTCConfiguration;

use std::net::IpAddr;
Expand All @@ -37,6 +36,7 @@ use std::{
};

use crate::tokio::{
certificate::Certificate,
connection::Connection,
error::Error,
fingerprint::Fingerprint,
Expand All @@ -61,20 +61,15 @@ impl Transport {
/// use libp2p_core::identity;
/// use webrtc::peer_connection::certificate::RTCCertificate;
/// use rand::distributions::DistString;
/// use libp2p_webrtc::tokio::Transport;
/// use rand::thread_rng;
/// use libp2p_webrtc::tokio::{Certificate, Transport};
///
/// let id_keys = identity::Keypair::generate_ed25519();
/// let certificate = {
/// let mut params = rcgen::CertificateParams::new(vec![
/// rand::distributions::Alphanumeric.sample_string(&mut rand::thread_rng(), 16)
/// ]);
/// params.alg = &rcgen::PKCS_ECDSA_P256_SHA256;
/// RTCCertificate::from_params(params).expect("default params to work")
/// };
/// let certificate = Certificate::generate(&mut thread_rng()).unwrap();
///
/// let transport = Transport::new(id_keys, certificate);
/// ```
pub fn new(id_keys: identity::Keypair, certificate: RTCCertificate) -> Self {
pub fn new(id_keys: identity::Keypair, certificate: Certificate) -> Self {
Self {
config: Config::new(id_keys, certificate),
listeners: SelectAll::new(),
Expand Down Expand Up @@ -348,21 +343,16 @@ impl Config {
///
/// This function will panic if there's no fingerprint with the SHA-256 algorithm (see
/// [`RTCCertificate::get_fingerprints`]).
fn new(id_keys: identity::Keypair, certificate: RTCCertificate) -> Self {
let fingerprints = certificate.get_fingerprints().expect("to never fail");
let sha256_fingerprint = fingerprints
.iter()
.find(|f| f.algorithm == "sha-256")
.expect("a SHA-256 fingerprint");
fn new(id_keys: identity::Keypair, certificate: Certificate) -> Self {
let fingerprint = certificate.fingerprint();

Self {
id_keys,
inner: RTCConfiguration {
certificates: vec![certificate],
certificates: vec![certificate.to_rtc_certificate()],
..RTCConfiguration::default()
},
fingerprint: Fingerprint::try_from_rtc_dtls(sha256_fingerprint)
.expect("we specified SHA-256"),
fingerprint,
}
}
}
Expand Down Expand Up @@ -447,7 +437,7 @@ mod tests {
use super::*;
use futures::future::poll_fn;
use libp2p_core::{multiaddr::Protocol, Transport as _};
use rand::distributions::DistString;
use rand::thread_rng;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};

#[test]
Expand Down Expand Up @@ -567,8 +557,8 @@ mod tests {
#[tokio::test]
async fn close_listener() {
let id_keys = identity::Keypair::generate_ed25519();
let certificate = generate_certificate();
let mut transport = Transport::new(id_keys, certificate);
let mut transport =
Transport::new(id_keys, Certificate::generate(&mut thread_rng()).unwrap());

assert!(poll_fn(|cx| Pin::new(&mut transport).as_mut().poll(cx))
.now_or_never()
Expand Down Expand Up @@ -617,12 +607,4 @@ mod tests {
assert!(transport.listeners.is_empty());
}
}

fn generate_certificate() -> RTCCertificate {
let mut params = rcgen::CertificateParams::new(vec![
rand::distributions::Alphanumeric.sample_string(&mut rand::thread_rng(), 16)
]);
params.alg = &rcgen::PKCS_ECDSA_P256_SHA256;
RTCCertificate::from_params(params).expect("default params to work")
}
}
19 changes: 5 additions & 14 deletions transports/webrtc/tests/smoke.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

use ::webrtc::peer_connection::certificate::RTCCertificate;
use anyhow::Result;
use async_trait::async_trait;
use futures::{
Expand All @@ -33,9 +32,7 @@ use libp2p::request_response::{
};
use libp2p::swarm::{Swarm, SwarmBuilder, SwarmEvent};
use libp2p::webrtc::tokio as webrtc;
use rand::distributions::DistString;
use rand::RngCore;

use rand::{thread_rng, RngCore};
use std::{io, iter};

#[tokio::test]
Expand Down Expand Up @@ -472,8 +469,10 @@ impl RequestResponseCodec for PingCodec {
fn create_swarm() -> Result<Swarm<RequestResponse<PingCodec>>> {
let id_keys = identity::Keypair::generate_ed25519();
let peer_id = id_keys.public().to_peer_id();
let certificate = generate_certificate();
let transport = webrtc::Transport::new(id_keys, certificate);
let transport = webrtc::Transport::new(
id_keys,
webrtc::Certificate::generate(&mut thread_rng()).unwrap(),
);

let protocols = iter::once((PingProtocol(), ProtocolSupport::Full));
let cfg = RequestResponseConfig::default();
Expand All @@ -488,11 +487,3 @@ fn create_swarm() -> Result<Swarm<RequestResponse<PingCodec>>> {
}))
.build())
}

fn generate_certificate() -> RTCCertificate {
let mut params = rcgen::CertificateParams::new(vec![
rand::distributions::Alphanumeric.sample_string(&mut rand::thread_rng(), 16)
]);
params.alg = &rcgen::PKCS_ECDSA_P256_SHA256;
RTCCertificate::from_params(params).expect("default params to work")
}

0 comments on commit 492fd50

Please sign in to comment.