Skip to content

Commit 4302b78

Browse files
authored
chore: Use simple_x509 to generate webrtc self-signed cert (#219)
1 parent 88f91f1 commit 4302b78

File tree

5 files changed

+169
-25
lines changed

5 files changed

+169
-25
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# 0.11.18
2+
- chore: Use simple_x509 for certificate generation. [PR 219](https://github.com/dariusc93/rust-ipfs/pull/219)
3+
14
# 0.11.17
25
- fix: Remove aws-lc-rs feature.
36

Cargo.lock

Lines changed: 24 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ serde = { default-features = false, version = "1"}
7272
serde_json = { default-features = false, version = "1" }
7373
serde-wasm-bindgen = "0.6"
7474
sha2 = "0.10.8"
75+
simple_x509 = "=1.1.0"
7576
sled = { version = "0.34" }
7677
thiserror = { default-features = false, version = "1"}
7778
tracing = { default-features = false, features = ["log"], version = "0.1"}
@@ -143,6 +144,7 @@ libp2p-webrtc = { workspace = true, features = ["tokio",], optional = true }
143144
rcgen.workspace = true
144145
redb = { workspace = true, optional = true }
145146
rlimit.workspace = true
147+
simple_x509.workspace = true
146148
sled = { workspace = true, optional = true }
147149
tokio = { features = ["full"], workspace = true }
148150
tokio-stream = { workspace = true, features = ["fs"] }

src/p2p/transport.rs

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -141,8 +141,8 @@ pub(crate) fn build_transport(
141141
use libp2p::quic::tokio::Transport as TokioQuicTransport;
142142
use libp2p::quic::Config as QuicConfig;
143143
use libp2p::tcp::{tokio::Transport as TokioTcpTransport, Config as GenTcpConfig};
144-
use rcgen::KeyPair;
145144
use misc::generate_cert;
145+
use rcgen::KeyPair;
146146

147147
let noise_config = noise::Config::new(&keypair).map_err(io::Error::other)?;
148148

@@ -173,11 +173,7 @@ pub(crate) fn build_transport(
173173
(cert, priv_key)
174174
}
175175
None => {
176-
let (cert, prv, _) = generate_cert(
177-
&keypair,
178-
b"libp2p-websocket",
179-
false,
180-
)?;
176+
let (cert, prv, _) = generate_cert(&keypair, b"libp2p-websocket", false)?;
181177

182178
let priv_key = libp2p::websocket::tls::PrivateKey::new(prv.serialize_der());
183179
let self_cert =
@@ -229,18 +225,7 @@ pub(crate) fn build_transport(
229225
None => {
230226
// This flag is internal, but is meant to allow generating an expired pem to satify webrtc
231227
let expired = true;
232-
let (cert, prv, expired_pem) =
233-
generate_cert(&keypair, b"libp2p-webrtc", expired)?;
234-
// dtls requires pem with a dash in the label?
235-
let priv_key = prv.serialize_pem().replace("PRIVATE KEY", "PRIVATE_KEY");
236-
let cert = cert.pem();
237-
238-
let pem = priv_key + "\n\n" + &cert;
239-
240-
let pem = match expired_pem {
241-
Some(epem) => epem + "\n\n" + &pem,
242-
None => pem,
243-
};
228+
let pem = misc::generate_wrtc_cert(&keypair)?;
244229

245230
libp2p_webrtc::tokio::Certificate::from_pem(&pem)
246231
.map_err(std::io::Error::other)?

src/p2p/transport/misc.rs

Lines changed: 137 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use hkdf::Hkdf;
22
use libp2p::identity::{self as identity, Keypair};
3+
use p256::ecdsa::signature::Signer;
34
use rand::SeedableRng;
45
use rand_chacha::ChaCha20Rng;
56
use rcgen::{Certificate, CertificateParams, DnType, KeyPair};
@@ -8,8 +9,24 @@ use sha2::Sha256;
89
use std::io;
910
use web_time::{Duration, SystemTime};
1011

12+
/// The year 2000.
13+
const UNIX_2000: i64 = 946645200;
14+
15+
/// The year 3000.
1116
const UNIX_3000: i64 = 32503640400;
1217

18+
/// OID for the organisation name. See <http://oid-info.com/get/2.5.4.10>.
19+
const ORGANISATION_NAME_OID: [u64; 4] = [2, 5, 4, 10];
20+
21+
/// OID for Elliptic Curve Public Key Cryptography. See <http://oid-info.com/get/1.2.840.10045.2.1>.
22+
const EC_OID: [u64; 6] = [1, 2, 840, 10045, 2, 1];
23+
24+
/// 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>.
25+
const P256_OID: [u64; 7] = [1, 2, 840, 10045, 3, 1, 7];
26+
27+
/// 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>.
28+
const ECDSA_SHA256_OID: [u64; 7] = [1, 2, 840, 10045, 4, 3, 2];
29+
1330
const ENCODE_CONFIG: pem::EncodeConfig = {
1431
let line_ending = match cfg!(target_family = "windows") {
1532
true => pem::LineEnding::CRLF,
@@ -20,7 +37,7 @@ const ENCODE_CONFIG: pem::EncodeConfig = {
2037

2138
/// Generates a TLS certificate that derives from libp2p `Keypair` with a salt.
2239
/// Note: If `expire` is true, it will produce a expired pem that can be appended for webrtc transport
23-
/// Additionally, this function does not generate deterministic certs *yet* due to
40+
/// Additionally, this function does not generate deterministic certs *yet* due to
2441
/// `CertificateParams::self_signed` using ring rng. This may change in the future
2542
pub fn generate_cert(
2643
keypair: &Keypair,
@@ -56,8 +73,79 @@ pub fn generate_cert(
5673
Ok((cert, internal_keypair, expired_pem))
5774
}
5875

76+
/// Used to generate webrtc certificates.
77+
/// Note: Although simple_x509 does not deal with crypto directly (eg signing certificate)
78+
/// we would still have to be careful of any changes upstream that may cause a change in the certificate
79+
#[allow(dead_code)]
80+
pub(crate) fn generate_wrtc_cert(keypair: &Keypair) -> io::Result<String> {
81+
let (secret, public_key) = derive_keypair_secret(keypair, b"libp2p-webrtc")?;
82+
let peer_id = keypair.public().to_peer_id();
83+
84+
let certificate = simple_x509::X509::builder()
85+
.issuer_utf8(Vec::from(ORGANISATION_NAME_OID), "rust-ipfs")
86+
.subject_utf8(Vec::from(ORGANISATION_NAME_OID), &peer_id.to_string())
87+
.not_before_gen(UNIX_2000)
88+
.not_after_gen(UNIX_3000)
89+
.pub_key_ec(
90+
Vec::from(EC_OID),
91+
public_key.to_encoded_point(false).as_bytes().to_owned(),
92+
Vec::from(P256_OID),
93+
)
94+
.sign_oid(Vec::from(ECDSA_SHA256_OID))
95+
.build()
96+
.sign(
97+
|cert, _| {
98+
let signature: p256::ecdsa::DerSignature = secret.sign(cert);
99+
Some(signature.as_bytes().to_owned())
100+
},
101+
&[], // We close over the keypair so no need to pass it.
102+
)
103+
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, format!("{e:?}")))?;
104+
105+
let der_bytes = certificate.x509_enc().unwrap();
106+
107+
let cert_pem = pem::encode_config(
108+
&pem::Pem::new("CERTIFICATE".to_string(), der_bytes),
109+
ENCODE_CONFIG,
110+
);
111+
112+
let private_pem = secret
113+
.to_pkcs8_pem(Default::default())
114+
.map_err(std::io::Error::other)?
115+
.replace("PRIVATE KEY", "PRIVATE_KEY");
116+
117+
let expired_pem = {
118+
let expired = SystemTime::UNIX_EPOCH
119+
.checked_add(Duration::from_secs(UNIX_3000 as u64))
120+
.expect("year 3000 to be representable by SystemTime")
121+
.to_der()
122+
.unwrap();
123+
124+
pem::encode_config(
125+
&pem::Pem::new("EXPIRES".to_string(), expired),
126+
ENCODE_CONFIG,
127+
)
128+
};
129+
130+
let pem = expired_pem + "\n\n" + &private_pem + "\n\n" + &cert_pem;
131+
132+
Ok(pem)
133+
}
134+
59135
fn derive_keypair(keypair: &Keypair, salt: &[u8]) -> io::Result<KeyPair> {
60-
//Note: We could use `Keypair::derive_secret`, but this seems more sensible?
136+
let (secret, _) = derive_keypair_secret(keypair, salt)?;
137+
138+
let pem = secret
139+
.to_pkcs8_pem(Default::default())
140+
.map_err(std::io::Error::other)?;
141+
142+
KeyPair::from_pem(&pem).map_err(std::io::Error::other)
143+
}
144+
145+
fn derive_keypair_secret(
146+
keypair: &Keypair,
147+
salt: &[u8],
148+
) -> io::Result<(p256::ecdsa::SigningKey, p256::ecdsa::VerifyingKey)> {
61149
let secret = keypair_secret(keypair).ok_or(io::Error::from(io::ErrorKind::Unsupported))?;
62150
let hkdf_gen = Hkdf::<Sha256>::from_prk(secret.as_ref()).expect("key length to be valid");
63151

@@ -69,12 +157,9 @@ fn derive_keypair(keypair: &Keypair, salt: &[u8]) -> io::Result<KeyPair> {
69157
let mut rng = ChaCha20Rng::from_seed(seed);
70158

71159
let secret = p256::ecdsa::SigningKey::random(&mut rng);
160+
let public = p256::ecdsa::VerifyingKey::from(&secret);
72161

73-
let pem = secret
74-
.to_pkcs8_pem(Default::default())
75-
.map_err(std::io::Error::other)?;
76-
77-
KeyPair::from_pem(&pem).map_err(std::io::Error::other)
162+
Ok((secret, public))
78163
}
79164

80165
fn keypair_secret(keypair: &Keypair) -> Option<[u8; 32]> {
@@ -102,3 +187,48 @@ fn keypair_secret(keypair: &Keypair) -> Option<[u8; 32]> {
102187
}
103188
}
104189
}
190+
191+
#[cfg(test)]
192+
mod test {
193+
use libp2p::identity::Keypair;
194+
195+
use crate::p2p::transport::misc::generate_wrtc_cert;
196+
197+
const PEM: &str = r#"-----BEGIN EXPIRES-----
198+
GA8yOTk5MTIzMTEzMDAwMFo=
199+
-----END EXPIRES-----
200+
201+
202+
-----BEGIN PRIVATE_KEY-----
203+
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgXARqgq74dVCrVR6G
204+
VT/iHnwBmx9s217QqvegG1xKNpqhRANCAAQvm08WYqoMCCEF36I5OAhA/XS7SqhR
205+
7n2CahGwC/fEqtvRrwAfZGejF21lzOW/m+A3EbDIzjy+xpUY+zaCE57V
206+
-----END PRIVATE_KEY-----
207+
208+
209+
-----BEGIN CERTIFICATE-----
210+
MIIBPjCB5QIBADAKBggqhkjOPQQDAjAUMRIwEAYDVQQKDAlydXN0LWlwZnMwIhgP
211+
MTk5OTEyMzExMzAwMDBaGA8yOTk5MTIzMTEzMDAwMFowPzE9MDsGA1UECgw0MTJE
212+
M0tvb1dQamNlUXJTd2RXWFB5TExlQUJSWG11cXQ2OVJnM3NCWWJVMU5mdDlIeVE2
213+
WDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABC+bTxZiqgwIIQXfojk4CED9dLtK
214+
qFHufYJqEbAL98Sq29GvAB9kZ6MXbWXM5b+b4DcRsMjOPL7GlRj7NoITntUwCgYI
215+
KoZIzj0EAwIDSAAwRQIhAP+F5COvtCQbZiyBQpAoiIoQP12KwIsNe1zhumki4bkU
216+
AiAH43Q833G8p1eXxqJr2xRrA1B5vCZ1qgl/44Z++NDMqQ==
217+
-----END CERTIFICATE-----
218+
"#;
219+
220+
#[test]
221+
fn generate_cert() {
222+
let keypair = generate_ed25519();
223+
let pem = generate_wrtc_cert(&keypair).expect("not to fail");
224+
assert_eq!(pem, PEM)
225+
}
226+
227+
fn generate_ed25519() -> Keypair {
228+
let mut bytes = [0u8; 32];
229+
bytes[0] = 1;
230+
231+
Keypair::ed25519_from_bytes(bytes).expect("only errors on wrong length")
232+
}
233+
234+
}

0 commit comments

Comments
 (0)