From b453574265d7394983abee30a6c71a7c27d65805 Mon Sep 17 00:00:00 2001 From: Darius Clark Date: Sat, 11 May 2024 01:44:32 -0400 Subject: [PATCH 1/6] chore: Use simple_x509 for webrtc default certs --- Cargo.lock | 24 ++++++++++ Cargo.toml | 2 + src/p2p/transport.rs | 13 ++--- src/p2p/transport/misc.rs | 99 ++++++++++++++++++++++++++++++++++++--- 4 files changed, 123 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 35ff60b65..5a7066939 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4780,6 +4780,7 @@ dependencies = [ "serde-wasm-bindgen", "serde_json", "sha2 0.10.8", + "simple_x509", "sled", "tempfile", "thiserror", @@ -5196,6 +5197,29 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "simple_asn1" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eb4ea60fb301dc81dfc113df680571045d375ab7345d171c5dc7d7e13107a80" +dependencies = [ + "chrono", + "num-bigint", + "num-traits", + "thiserror", +] + +[[package]] +name = "simple_x509" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "417fc25f99e6af54350cbc26997572b6ee04c4b8deec627ce5f16f76a7ed887b" +dependencies = [ + "chrono", + "num-traits", + "simple_asn1", +] + [[package]] name = "slab" version = "0.4.9" diff --git a/Cargo.toml b/Cargo.toml index 00c13e3e6..65c71727a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,6 +72,7 @@ serde = { default-features = false, version = "1"} serde_json = { default-features = false, version = "1" } serde-wasm-bindgen = "0.6" sha2 = "0.10.8" +simple_x509 = "=1.1.0" sled = { version = "0.34" } thiserror = { default-features = false, version = "1"} tracing = { default-features = false, features = ["log"], version = "0.1"} @@ -143,6 +144,7 @@ libp2p-webrtc = { workspace = true, features = ["tokio",], optional = true } rcgen.workspace = true redb = { workspace = true, optional = true } rlimit.workspace = true +simple_x509.workspace = true sled = { workspace = true, optional = true } tokio = { features = ["full"], workspace = true } tokio-stream = { workspace = true, features = ["fs"] } diff --git a/src/p2p/transport.rs b/src/p2p/transport.rs index 3c7b7a483..0e4347a54 100644 --- a/src/p2p/transport.rs +++ b/src/p2p/transport.rs @@ -141,8 +141,8 @@ pub(crate) fn build_transport( use libp2p::quic::tokio::Transport as TokioQuicTransport; use libp2p::quic::Config as QuicConfig; use libp2p::tcp::{tokio::Transport as TokioTcpTransport, Config as GenTcpConfig}; - use rcgen::KeyPair; use misc::generate_cert; + use rcgen::KeyPair; let noise_config = noise::Config::new(&keypair).map_err(io::Error::other)?; @@ -173,11 +173,7 @@ pub(crate) fn build_transport( (cert, priv_key) } None => { - let (cert, prv, _) = generate_cert( - &keypair, - b"libp2p-websocket", - false, - )?; + let (cert, prv, _) = generate_cert(&keypair, b"libp2p-websocket", false)?; let priv_key = libp2p::websocket::tls::PrivateKey::new(prv.serialize_der()); let self_cert = @@ -230,10 +226,9 @@ pub(crate) fn build_transport( // This flag is internal, but is meant to allow generating an expired pem to satify webrtc let expired = true; let (cert, prv, expired_pem) = - generate_cert(&keypair, b"libp2p-webrtc", expired)?; + misc::generate_wrtc_cert(&keypair, b"libp2p-webrtc", expired)?; // dtls requires pem with a dash in the label? - let priv_key = prv.serialize_pem().replace("PRIVATE KEY", "PRIVATE_KEY"); - let cert = cert.pem(); + let priv_key = prv.replace("PRIVATE KEY", "PRIVATE_KEY"); let pem = priv_key + "\n\n" + &cert; diff --git a/src/p2p/transport/misc.rs b/src/p2p/transport/misc.rs index 7779bf37d..110177808 100644 --- a/src/p2p/transport/misc.rs +++ b/src/p2p/transport/misc.rs @@ -1,5 +1,6 @@ use hkdf::Hkdf; use libp2p::identity::{self as identity, Keypair}; +use p256::ecdsa::signature::Signer; use rand::SeedableRng; use rand_chacha::ChaCha20Rng; use rcgen::{Certificate, CertificateParams, DnType, KeyPair}; @@ -8,8 +9,24 @@ use sha2::Sha256; use std::io; use web_time::{Duration, SystemTime}; +/// The year 2000. +const UNIX_2000: i64 = 946645200; + +/// The year 3000. const UNIX_3000: i64 = 32503640400; +/// OID for the organisation name. See . +const ORGANISATION_NAME_OID: [u64; 4] = [2, 5, 4, 10]; + +/// OID for Elliptic Curve Public Key Cryptography. See . +const EC_OID: [u64; 6] = [1, 2, 840, 10045, 2, 1]; + +/// OID for 256-bit Elliptic Curve Cryptography (ECC) with the P256 curve. See . +const P256_OID: [u64; 7] = [1, 2, 840, 10045, 3, 1, 7]; + +/// OID for the ECDSA signature algorithm with using SHA256 as the hash function. See . +const ECDSA_SHA256_OID: [u64; 7] = [1, 2, 840, 10045, 4, 3, 2]; + const ENCODE_CONFIG: pem::EncodeConfig = { let line_ending = match cfg!(target_family = "windows") { true => pem::LineEnding::CRLF, @@ -18,6 +35,9 @@ const ENCODE_CONFIG: pem::EncodeConfig = { pem::EncodeConfig::new().set_line_ending(line_ending) }; +type Cert = String; +type Key = zeroize::Zeroizing; + /// Generates a TLS certificate that derives from libp2p `Keypair` with a salt. /// Note: If `expire` is true, it will produce a expired pem that can be appended for webrtc transport /// Additionally, this function does not generate deterministic certs *yet* due to @@ -56,8 +76,78 @@ pub fn generate_cert( Ok((cert, internal_keypair, expired_pem)) } +/// Used to generate webrtc certificates. +/// Note: Although simple_x509 does not deal with crypto directly (eg signing certificate) +/// we would still have to be careful of any changes upstream that may cause a change in the certificate +pub(crate) fn generate_wrtc_cert( + keypair: &Keypair, + salt: &[u8], + expire: bool, +) -> io::Result<(Cert, Key, Option)> { + let (secret, public_key) = derive_keypair_secret(keypair, salt)?; + + let certificate = simple_x509::X509::builder() + .issuer_utf8(Vec::from(ORGANISATION_NAME_OID), "rust-ipfs") + .subject_utf8(Vec::from(ORGANISATION_NAME_OID), "rust-ipfs") + .not_before_gen(UNIX_2000) + .not_after_gen(UNIX_3000) + .pub_key_ec( + Vec::from(EC_OID), + public_key.to_encoded_point(false).as_bytes().to_owned(), + Vec::from(P256_OID), + ) + .sign_oid(Vec::from(ECDSA_SHA256_OID)) + .build() + .sign( + |cert, _| { + let signature: p256::ecdsa::DerSignature = secret.sign(cert); + Some(signature.as_bytes().to_owned()) + }, + &[], // We close over the keypair so no need to pass it. + ) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, format!("{e:?}")))?; + + let der_bytes = certificate.x509_enc().unwrap(); + + let cert_pem = pem::encode_config( + &pem::Pem::new("CERTIFICATE".to_string(), der_bytes), + ENCODE_CONFIG, + ); + + let private_pem = secret + .to_pkcs8_pem(Default::default()) + .map_err(std::io::Error::other)?; + + let expired_pem = expire.then(|| { + let expired = SystemTime::UNIX_EPOCH + .checked_add(Duration::from_secs(UNIX_3000 as u64)) + .expect("year 3000 to be representable by SystemTime") + .to_der() + .unwrap(); + + pem::encode_config( + &pem::Pem::new("EXPIRES".to_string(), expired), + ENCODE_CONFIG, + ) + }); + + Ok((cert_pem, private_pem, expired_pem)) +} + fn derive_keypair(keypair: &Keypair, salt: &[u8]) -> io::Result { - //Note: We could use `Keypair::derive_secret`, but this seems more sensible? + let (secret, _) = derive_keypair_secret(keypair, salt)?; + + let pem = secret + .to_pkcs8_pem(Default::default()) + .map_err(std::io::Error::other)?; + + KeyPair::from_pem(&pem).map_err(std::io::Error::other) +} + +fn derive_keypair_secret( + keypair: &Keypair, + salt: &[u8], +) -> io::Result<(p256::ecdsa::SigningKey, p256::ecdsa::VerifyingKey)> { let secret = keypair_secret(keypair).ok_or(io::Error::from(io::ErrorKind::Unsupported))?; let hkdf_gen = Hkdf::::from_prk(secret.as_ref()).expect("key length to be valid"); @@ -69,12 +159,9 @@ fn derive_keypair(keypair: &Keypair, salt: &[u8]) -> io::Result { let mut rng = ChaCha20Rng::from_seed(seed); let secret = p256::ecdsa::SigningKey::random(&mut rng); + let public = p256::ecdsa::VerifyingKey::from(&secret); - let pem = secret - .to_pkcs8_pem(Default::default()) - .map_err(std::io::Error::other)?; - - KeyPair::from_pem(&pem).map_err(std::io::Error::other) + Ok((secret, public)) } fn keypair_secret(keypair: &Keypair) -> Option<[u8; 32]> { From 53e789bfa7668079a921311fed4c8446062774f5 Mon Sep 17 00:00:00 2001 From: Darius Clark Date: Sat, 11 May 2024 02:16:02 -0400 Subject: [PATCH 2/6] chore: Use peer_id as subject name --- src/p2p/transport/misc.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/p2p/transport/misc.rs b/src/p2p/transport/misc.rs index 110177808..36f87db5a 100644 --- a/src/p2p/transport/misc.rs +++ b/src/p2p/transport/misc.rs @@ -40,7 +40,7 @@ type Key = zeroize::Zeroizing; /// Generates a TLS certificate that derives from libp2p `Keypair` with a salt. /// Note: If `expire` is true, it will produce a expired pem that can be appended for webrtc transport -/// Additionally, this function does not generate deterministic certs *yet* due to +/// Additionally, this function does not generate deterministic certs *yet* due to /// `CertificateParams::self_signed` using ring rng. This may change in the future pub fn generate_cert( keypair: &Keypair, @@ -85,10 +85,11 @@ pub(crate) fn generate_wrtc_cert( expire: bool, ) -> io::Result<(Cert, Key, Option)> { let (secret, public_key) = derive_keypair_secret(keypair, salt)?; + let peer_id = keypair.public().to_peer_id(); let certificate = simple_x509::X509::builder() .issuer_utf8(Vec::from(ORGANISATION_NAME_OID), "rust-ipfs") - .subject_utf8(Vec::from(ORGANISATION_NAME_OID), "rust-ipfs") + .subject_utf8(Vec::from(ORGANISATION_NAME_OID), &peer_id.to_string()) .not_before_gen(UNIX_2000) .not_after_gen(UNIX_3000) .pub_key_ec( From 979c282ba92e831d0c7f07dfc93477d0754938a0 Mon Sep 17 00:00:00 2001 From: Darius Clark Date: Mon, 13 May 2024 16:37:31 -0400 Subject: [PATCH 3/6] chore: Return a single string and add test --- src/p2p/transport.rs | 12 +------ src/p2p/transport/misc.rs | 68 +++++++++++++++++++++++++++++++-------- 2 files changed, 56 insertions(+), 24 deletions(-) diff --git a/src/p2p/transport.rs b/src/p2p/transport.rs index 0e4347a54..18e04ecfd 100644 --- a/src/p2p/transport.rs +++ b/src/p2p/transport.rs @@ -225,17 +225,7 @@ pub(crate) fn build_transport( None => { // This flag is internal, but is meant to allow generating an expired pem to satify webrtc let expired = true; - let (cert, prv, expired_pem) = - misc::generate_wrtc_cert(&keypair, b"libp2p-webrtc", expired)?; - // dtls requires pem with a dash in the label? - let priv_key = prv.replace("PRIVATE KEY", "PRIVATE_KEY"); - - let pem = priv_key + "\n\n" + &cert; - - let pem = match expired_pem { - Some(epem) => epem + "\n\n" + &pem, - None => pem, - }; + let pem = misc::generate_wrtc_cert(&keypair)?; libp2p_webrtc::tokio::Certificate::from_pem(&pem) .map_err(std::io::Error::other)? diff --git a/src/p2p/transport/misc.rs b/src/p2p/transport/misc.rs index 36f87db5a..69c7715f3 100644 --- a/src/p2p/transport/misc.rs +++ b/src/p2p/transport/misc.rs @@ -35,9 +35,6 @@ const ENCODE_CONFIG: pem::EncodeConfig = { pem::EncodeConfig::new().set_line_ending(line_ending) }; -type Cert = String; -type Key = zeroize::Zeroizing; - /// Generates a TLS certificate that derives from libp2p `Keypair` with a salt. /// Note: If `expire` is true, it will produce a expired pem that can be appended for webrtc transport /// Additionally, this function does not generate deterministic certs *yet* due to @@ -79,12 +76,9 @@ pub fn generate_cert( /// Used to generate webrtc certificates. /// Note: Although simple_x509 does not deal with crypto directly (eg signing certificate) /// we would still have to be careful of any changes upstream that may cause a change in the certificate -pub(crate) fn generate_wrtc_cert( - keypair: &Keypair, - salt: &[u8], - expire: bool, -) -> io::Result<(Cert, Key, Option)> { - let (secret, public_key) = derive_keypair_secret(keypair, salt)?; + +pub(crate) fn generate_wrtc_cert(keypair: &Keypair) -> io::Result { + let (secret, public_key) = derive_keypair_secret(keypair, b"libp2p-webrtc")?; let peer_id = keypair.public().to_peer_id(); let certificate = simple_x509::X509::builder() @@ -117,9 +111,10 @@ pub(crate) fn generate_wrtc_cert( let private_pem = secret .to_pkcs8_pem(Default::default()) - .map_err(std::io::Error::other)?; + .map_err(std::io::Error::other)? + .replace("PRIVATE KEY", "PRIVATE_KEY"); - let expired_pem = expire.then(|| { + let expired_pem = { let expired = SystemTime::UNIX_EPOCH .checked_add(Duration::from_secs(UNIX_3000 as u64)) .expect("year 3000 to be representable by SystemTime") @@ -130,9 +125,11 @@ pub(crate) fn generate_wrtc_cert( &pem::Pem::new("EXPIRES".to_string(), expired), ENCODE_CONFIG, ) - }); + }; - Ok((cert_pem, private_pem, expired_pem)) + let pem = expired_pem + "\n\n" + &private_pem + "\n\n" + &cert_pem; + + Ok(pem) } fn derive_keypair(keypair: &Keypair, salt: &[u8]) -> io::Result { @@ -190,3 +187,48 @@ fn keypair_secret(keypair: &Keypair) -> Option<[u8; 32]> { } } } + +#[cfg(test)] +mod test { + use libp2p::identity::Keypair; + + use crate::p2p::transport::misc::generate_wrtc_cert; + + const PEM: &str = r#"-----BEGIN EXPIRES----- +GA8yOTk5MTIzMTEzMDAwMFo= +-----END EXPIRES----- + + +-----BEGIN PRIVATE_KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgXARqgq74dVCrVR6G +VT/iHnwBmx9s217QqvegG1xKNpqhRANCAAQvm08WYqoMCCEF36I5OAhA/XS7SqhR +7n2CahGwC/fEqtvRrwAfZGejF21lzOW/m+A3EbDIzjy+xpUY+zaCE57V +-----END PRIVATE_KEY----- + + +-----BEGIN CERTIFICATE----- +MIIBPjCB5QIBADAKBggqhkjOPQQDAjAUMRIwEAYDVQQKDAlydXN0LWlwZnMwIhgP +MTk5OTEyMzExMzAwMDBaGA8yOTk5MTIzMTEzMDAwMFowPzE9MDsGA1UECgw0MTJE +M0tvb1dQamNlUXJTd2RXWFB5TExlQUJSWG11cXQ2OVJnM3NCWWJVMU5mdDlIeVE2 +WDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABC+bTxZiqgwIIQXfojk4CED9dLtK +qFHufYJqEbAL98Sq29GvAB9kZ6MXbWXM5b+b4DcRsMjOPL7GlRj7NoITntUwCgYI +KoZIzj0EAwIDSAAwRQIhAP+F5COvtCQbZiyBQpAoiIoQP12KwIsNe1zhumki4bkU +AiAH43Q833G8p1eXxqJr2xRrA1B5vCZ1qgl/44Z++NDMqQ== +-----END CERTIFICATE----- +"#; + + #[test] + fn generate_cert() { + let keypair = generate_ed25519(); + let pem = generate_wrtc_cert(&keypair).expect("not to fail"); + assert_eq!(pem, PEM) + } + + fn generate_ed25519() -> Keypair { + let mut bytes = [0u8; 32]; + bytes[0] = 1; + + Keypair::ed25519_from_bytes(bytes).expect("only errors on wrong length") + } + +} From c1cb6746f86336f6af28d6551f69c172b94758db Mon Sep 17 00:00:00 2001 From: Darius Clark Date: Mon, 13 May 2024 17:29:02 -0400 Subject: [PATCH 4/6] chore: Update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 08b4ddc48..ee4aba060 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# 0.11.18 +- chore: Use simple_x509 for certificate generation. [PR XXX](https://github.com/dariusc93/rust-ipfs/pull/XXX) + # 0.11.17 - fix: Remove aws-lc-rs feature. From 3c2b694dcf51af91b9ee1b6bdf026107e45db80a Mon Sep 17 00:00:00 2001 From: Darius Clark Date: Mon, 13 May 2024 17:49:59 -0400 Subject: [PATCH 5/6] chore: ignore unused function --- src/p2p/transport/misc.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/p2p/transport/misc.rs b/src/p2p/transport/misc.rs index 69c7715f3..fd80d1603 100644 --- a/src/p2p/transport/misc.rs +++ b/src/p2p/transport/misc.rs @@ -76,7 +76,7 @@ pub fn generate_cert( /// Used to generate webrtc certificates. /// Note: Although simple_x509 does not deal with crypto directly (eg signing certificate) /// we would still have to be careful of any changes upstream that may cause a change in the certificate - +#[allow(dead_code)] pub(crate) fn generate_wrtc_cert(keypair: &Keypair) -> io::Result { let (secret, public_key) = derive_keypair_secret(keypair, b"libp2p-webrtc")?; let peer_id = keypair.public().to_peer_id(); From 62a3d25ab6d206133b36f33d2f50eccad6ec3f9a Mon Sep 17 00:00:00 2001 From: Darius Clark Date: Mon, 13 May 2024 19:01:19 -0400 Subject: [PATCH 6/6] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ee4aba060..2a6c85601 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,5 @@ # 0.11.18 -- chore: Use simple_x509 for certificate generation. [PR XXX](https://github.com/dariusc93/rust-ipfs/pull/XXX) +- chore: Use simple_x509 for certificate generation. [PR 219](https://github.com/dariusc93/rust-ipfs/pull/219) # 0.11.17 - fix: Remove aws-lc-rs feature.