Skip to content

Commit

Permalink
chore: Use simple_x509 to generate webrtc self-signed cert (#219)
Browse files Browse the repository at this point in the history
  • Loading branch information
dariusc93 authored May 13, 2024
1 parent 88f91f1 commit 4302b78
Show file tree
Hide file tree
Showing 5 changed files with 169 additions and 25 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# 0.11.18
- 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.

Expand Down
24 changes: 24 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"}
Expand Down Expand Up @@ -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"] }
Expand Down
21 changes: 3 additions & 18 deletions src/p2p/transport.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)?;

Expand Down Expand Up @@ -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 =
Expand Down Expand Up @@ -229,18 +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) =
generate_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 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)?
Expand Down
144 changes: 137 additions & 7 deletions src/p2p/transport/misc.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand All @@ -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 <http://oid-info.com/get/2.5.4.10>.
const ORGANISATION_NAME_OID: [u64; 4] = [2, 5, 4, 10];

/// OID for Elliptic Curve Public Key Cryptography. See <http://oid-info.com/get/1.2.840.10045.2.1>.
const EC_OID: [u64; 6] = [1, 2, 840, 10045, 2, 1];

/// OID for 256-bit Elliptic Curve Cryptography (ECC) with the P256 curve. See <http://oid-info.com/get/1.2.840.10045.3.1.7>.
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 <http://oid-info.com/get/1.2.840.10045.4.3.2>.
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,
Expand All @@ -20,7 +37,7 @@ const ENCODE_CONFIG: pem::EncodeConfig = {

/// 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,
Expand Down Expand Up @@ -56,8 +73,79 @@ 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
#[allow(dead_code)]
pub(crate) fn generate_wrtc_cert(keypair: &Keypair) -> io::Result<String> {
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()
.issuer_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(
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)?
.replace("PRIVATE KEY", "PRIVATE_KEY");

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")
.to_der()
.unwrap();

pem::encode_config(
&pem::Pem::new("EXPIRES".to_string(), expired),
ENCODE_CONFIG,
)
};

let pem = expired_pem + "\n\n" + &private_pem + "\n\n" + &cert_pem;

Ok(pem)
}

fn derive_keypair(keypair: &Keypair, salt: &[u8]) -> io::Result<KeyPair> {
//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::<Sha256>::from_prk(secret.as_ref()).expect("key length to be valid");

Expand All @@ -69,12 +157,9 @@ fn derive_keypair(keypair: &Keypair, salt: &[u8]) -> io::Result<KeyPair> {
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]> {
Expand Down Expand Up @@ -102,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")
}

}

0 comments on commit 4302b78

Please sign in to comment.