rust_ipfs/p2p/transport/
misc.rs

1use hkdf::Hkdf;
2use libp2p::identity::{self as identity, Keypair};
3use p256::ecdsa::signature::Signer;
4use rand::SeedableRng;
5use rand_chacha::ChaCha20Rng;
6use rcgen::{Certificate, CertificateParams, DnType, KeyPair};
7use sec1::{der::Encode, pkcs8::EncodePrivateKey};
8use sha2::Sha256;
9use std::io;
10use web_time::{Duration, SystemTime};
11
12/// The year 2000.
13const UNIX_2000: i64 = 946645200;
14
15/// The year 3000.
16const UNIX_3000: i64 = 32503640400;
17
18/// OID for the organisation name. See <http://oid-info.com/get/2.5.4.10>.
19const 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>.
22const 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>.
25const 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>.
28const ECDSA_SHA256_OID: [u64; 7] = [1, 2, 840, 10045, 4, 3, 2];
29
30const ENCODE_CONFIG: pem::EncodeConfig = {
31    let line_ending = match cfg!(target_family = "windows") {
32        true => pem::LineEnding::CRLF,
33        false => pem::LineEnding::LF,
34    };
35    pem::EncodeConfig::new().set_line_ending(line_ending)
36};
37
38/// Generates a TLS certificate that derives from libp2p `Keypair` with a salt.
39/// Note: If `expire` is true, it will produce a expired pem that can be appended for webrtc transport
40///       Additionally, this function does not generate deterministic certs *yet* due to
41///       `CertificateParams::self_signed` using ring rng. This may change in the future
42pub fn generate_cert(
43    keypair: &Keypair,
44    salt: &[u8],
45    expire: bool,
46) -> io::Result<(Certificate, KeyPair, Option<String>)> {
47    let internal_keypair = derive_keypair(keypair, salt)?;
48    let mut param =
49        CertificateParams::new(vec!["localhost".into()]).map_err(std::io::Error::other)?;
50    param.distinguished_name.push(
51        DnType::CommonName,
52        keypair.public().to_peer_id().to_string().as_str(),
53    );
54
55    // Note: The certificate, while it is signed,
56    let cert = param
57        .self_signed(&internal_keypair)
58        .map_err(std::io::Error::other)?;
59
60    let expired_pem = expire.then(|| {
61        let expired = SystemTime::UNIX_EPOCH
62            .checked_add(Duration::from_secs(UNIX_3000 as u64))
63            .expect("year 3000 to be representable by SystemTime")
64            .to_der()
65            .unwrap();
66
67        pem::encode_config(
68            &pem::Pem::new("EXPIRES".to_string(), expired),
69            ENCODE_CONFIG,
70        )
71    });
72
73    Ok((cert, internal_keypair, expired_pem))
74}
75
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)]
80pub(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
135fn derive_keypair(keypair: &Keypair, salt: &[u8]) -> io::Result<KeyPair> {
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
145fn derive_keypair_secret(
146    keypair: &Keypair,
147    salt: &[u8],
148) -> io::Result<(p256::ecdsa::SigningKey, p256::ecdsa::VerifyingKey)> {
149    let secret = keypair_secret(keypair).ok_or(io::Error::from(io::ErrorKind::Unsupported))?;
150    let hkdf_gen = Hkdf::<Sha256>::from_prk(secret.as_ref()).expect("key length to be valid");
151
152    let mut seed = [0u8; 32];
153    hkdf_gen
154        .expand(salt, &mut seed)
155        .expect("key length to be valid");
156
157    let mut rng = ChaCha20Rng::from_seed(seed);
158
159    let secret = p256::ecdsa::SigningKey::random(&mut rng);
160    let public = p256::ecdsa::VerifyingKey::from(&secret);
161
162    Ok((secret, public))
163}
164
165fn keypair_secret(keypair: &Keypair) -> Option<[u8; 32]> {
166    match keypair.key_type() {
167        identity::KeyType::Ed25519 => {
168            let keypair = keypair.clone().try_into_ed25519().ok()?;
169            let secret = keypair.secret();
170            Some(secret.as_ref().try_into().expect("secret is 32 bytes"))
171        }
172        identity::KeyType::RSA => None,
173        identity::KeyType::Secp256k1 => {
174            let keypair = keypair.clone().try_into_secp256k1().ok()?;
175            let secret = keypair.secret();
176            Some(secret.to_bytes())
177        }
178        identity::KeyType::Ecdsa => {
179            let keypair = keypair.clone().try_into_ecdsa().ok()?;
180            Some(
181                keypair
182                    .secret()
183                    .to_bytes()
184                    .try_into()
185                    .expect("secret is 32 bytes"),
186            )
187        }
188    }
189}
190
191#[cfg(test)]
192mod test {
193    use libp2p::identity::Keypair;
194
195    use crate::p2p::transport::misc::generate_wrtc_cert;
196
197    const PEM: &str = r#"-----BEGIN EXPIRES-----
198GA8yOTk5MTIzMTEzMDAwMFo=
199-----END EXPIRES-----
200
201
202-----BEGIN PRIVATE_KEY-----
203MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgXARqgq74dVCrVR6G
204VT/iHnwBmx9s217QqvegG1xKNpqhRANCAAQvm08WYqoMCCEF36I5OAhA/XS7SqhR
2057n2CahGwC/fEqtvRrwAfZGejF21lzOW/m+A3EbDIzjy+xpUY+zaCE57V
206-----END PRIVATE_KEY-----
207
208
209-----BEGIN CERTIFICATE-----
210MIIBPjCB5QIBADAKBggqhkjOPQQDAjAUMRIwEAYDVQQKDAlydXN0LWlwZnMwIhgP
211MTk5OTEyMzExMzAwMDBaGA8yOTk5MTIzMTEzMDAwMFowPzE9MDsGA1UECgw0MTJE
212M0tvb1dQamNlUXJTd2RXWFB5TExlQUJSWG11cXQ2OVJnM3NCWWJVMU5mdDlIeVE2
213WDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABC+bTxZiqgwIIQXfojk4CED9dLtK
214qFHufYJqEbAL98Sq29GvAB9kZ6MXbWXM5b+b4DcRsMjOPL7GlRj7NoITntUwCgYI
215KoZIzj0EAwIDSAAwRQIhAP+F5COvtCQbZiyBQpAoiIoQP12KwIsNe1zhumki4bkU
216AiAH43Q833G8p1eXxqJr2xRrA1B5vCZ1qgl/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}