Last active
September 28, 2024 06:07
-
-
Save nnathan/73ccb767ba706ccd07686066e0fde0f5 to your computer and use it in GitHub Desktop.
Generating deterministic keypairs & certificates (rough cut #66)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[package] | |
name = "bsvpn" | |
version = "0.1.0" | |
edition = "2021" | |
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | |
[dependencies] | |
rand = "0.8.5" | |
rand_chacha = "0.3.1" | |
rsa = { version = "0.9.6", features = ["sha2", "pem"] } | |
sha1 = "0.10.6" | |
spki = { version = "0.7.3", features = ["fingerprint"] } | |
x509-cert = { version = "0.2.5", features = ["builder", "hazmat"] } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Generates two self-signed cert/CAs deterministically; certs looks like | |
// they've been generated by openssl. | |
use rand::prelude::*; | |
use rand_chacha::rand_core::SeedableRng; | |
use rand_chacha::ChaCha20Rng; | |
use rsa::pkcs1v15::SigningKey; | |
use rsa::pkcs8::EncodePublicKey; | |
use rsa::sha2::Sha256; | |
use rsa::RsaPrivateKey; | |
use sha1::{Digest, Sha1}; | |
use rsa::pkcs8::EncodePrivateKey; | |
use std::str::FromStr; | |
use x509_cert::builder::Builder; | |
use x509_cert::builder::{CertificateBuilder, Profile}; | |
use x509_cert::der::asn1::OctetString; | |
use x509_cert::der::pem::LineEnding; | |
use x509_cert::der::referenced::OwnedToRef; | |
use x509_cert::der::Decode; | |
use x509_cert::der::EncodePem; | |
use x509_cert::ext::pkix::{AuthorityKeyIdentifier, BasicConstraints, SubjectKeyIdentifier}; | |
use x509_cert::name::Name; | |
use x509_cert::serial_number::SerialNumber; | |
use x509_cert::spki::SubjectPublicKeyInfoOwned; | |
use x509_cert::time::Time; | |
use x509_cert::time::Validity; | |
use std::time::Duration; | |
use std::time::{SystemTime, UNIX_EPOCH}; | |
use std::fs::File; | |
use std::io::Write; | |
pub fn increment_key(key: &mut [u8; 32], incr: u8) { | |
let mut acc: u16 = incr as u16; | |
let mut i = 0; | |
while i < key.len() { | |
acc += key[i] as u16; | |
key[i] = (acc & 0xFF) as u8; | |
acc >>= 8; | |
i += 1; | |
} | |
} | |
fn main() { | |
let get_unix_timestamp = || { | |
let start = SystemTime::now(); | |
let since_the_epoch = start | |
.duration_since(UNIX_EPOCH) | |
.expect("Time went backwards"); | |
since_the_epoch.as_secs() | |
}; | |
const YEAR_IN_SECS: u64 = 365 * 86400; | |
let year_from_timestamp = |timestamp| { | |
let year = (timestamp / YEAR_IN_SECS) * YEAR_IN_SECS; | |
year | |
}; | |
let mut seed: [u8; 32] = [ | |
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
0x00, 0x00, | |
]; | |
increment_key(&mut seed, 1); | |
let mut rng = ChaCha20Rng::from_seed(seed); | |
let bits = 2048; | |
let private_key = RsaPrivateKey::new(&mut rng, bits).expect("failed to generate a key"); | |
let signing_key = SigningKey::<Sha256>::new(private_key.clone()); | |
let subject = Name::from_str("CN=localhost,C=US").unwrap(); | |
let profile = Profile::Manual { issuer: None }; | |
let mut serial_number_bytes = [0u8; 20]; | |
rng.fill_bytes(&mut serial_number_bytes); | |
// the serial number has to be a positive value | |
serial_number_bytes[0] = rng.gen_range(0..128); | |
let serial_number = SerialNumber::new(&serial_number_bytes).unwrap(); | |
let public_key_der = private_key.to_public_key().to_public_key_der().unwrap(); | |
let subj_public_key = SubjectPublicKeyInfoOwned::from_der(public_key_der.as_bytes()).unwrap(); | |
let subj_public_key_ref = subj_public_key.owned_to_ref(); | |
let not_before = | |
Time::try_from(UNIX_EPOCH + Duration::from_secs(year_from_timestamp(get_unix_timestamp()))) | |
.unwrap(); | |
let not_after = Time::try_from( | |
UNIX_EPOCH | |
+ Duration::from_secs(year_from_timestamp( | |
get_unix_timestamp() + YEAR_IN_SECS + YEAR_IN_SECS, | |
)), | |
) | |
.unwrap(); | |
let validity = Validity { | |
not_before, | |
not_after, | |
}; | |
let mut builder = CertificateBuilder::new( | |
profile, | |
serial_number, | |
validity, | |
subject, | |
subj_public_key.clone(), | |
&signing_key, | |
) | |
.expect("Create server certificate"); | |
builder | |
.add_extension(&SubjectKeyIdentifier::try_from(subj_public_key_ref.clone()).unwrap()) | |
.unwrap(); | |
let aki = AuthorityKeyIdentifier { | |
key_identifier: Some( | |
OctetString::new( | |
Sha1::digest(subj_public_key_ref.clone().subject_public_key.raw_bytes()).as_slice(), | |
) | |
.unwrap(), | |
), | |
authority_cert_issuer: None, | |
authority_cert_serial_number: None, | |
}; | |
builder.add_extension(&aki).unwrap(); | |
builder | |
.add_extension(&BasicConstraints { | |
ca: true, | |
path_len_constraint: None, | |
}) | |
.unwrap(); | |
let cert = builder.build_with_rng(&mut rng).unwrap(); | |
let pem = cert.to_pem(LineEnding::LF).expect("generate pem"); | |
let pem_bytes = pem.into_bytes(); | |
let mut file = File::create("output_cert.pem").unwrap(); | |
file.write_all(&pem_bytes).unwrap(); | |
let pem_key = private_key | |
.to_pkcs8_pem(LineEnding::LF) | |
.expect("generate private pem"); | |
let pem_bytes = pem_key.as_bytes(); | |
let mut file = File::create("output_cert.key").unwrap(); | |
file.write_all(&pem_bytes).unwrap(); | |
let bits = 2048; | |
let private_key = RsaPrivateKey::new(&mut rng, bits).expect("failed to generate a key"); | |
let signing_key = SigningKey::<Sha256>::new(private_key.clone()); | |
let subject = Name::from_str("CN=alice,C=US").unwrap(); | |
let profile = Profile::Manual { issuer: None }; | |
let mut serial_number_bytes = [0u8; 20]; | |
rng.fill_bytes(&mut serial_number_bytes); | |
// the serial number has to be a positive value | |
serial_number_bytes[0] = rng.gen_range(0..128); | |
let serial_number = SerialNumber::new(&serial_number_bytes).unwrap(); | |
let public_key_der = private_key.to_public_key().to_public_key_der().unwrap(); | |
let subj_public_key = SubjectPublicKeyInfoOwned::from_der(public_key_der.as_bytes()).unwrap(); | |
let subj_public_key_ref = subj_public_key.owned_to_ref(); | |
let not_before = | |
Time::try_from(UNIX_EPOCH + Duration::from_secs(year_from_timestamp(get_unix_timestamp()))) | |
.unwrap(); | |
let not_after = Time::try_from( | |
UNIX_EPOCH | |
+ Duration::from_secs(year_from_timestamp( | |
get_unix_timestamp() + YEAR_IN_SECS + YEAR_IN_SECS, | |
)), | |
) | |
.unwrap(); | |
let validity = Validity { | |
not_before, | |
not_after, | |
}; | |
let mut builder = CertificateBuilder::new( | |
profile, | |
serial_number, | |
validity, | |
subject, | |
subj_public_key.clone(), | |
&signing_key, | |
) | |
.expect("Create client certificate"); | |
builder | |
.add_extension(&SubjectKeyIdentifier::try_from(subj_public_key_ref.clone()).unwrap()) | |
.unwrap(); | |
let aki = AuthorityKeyIdentifier { | |
key_identifier: Some( | |
OctetString::new( | |
Sha1::digest(subj_public_key_ref.clone().subject_public_key.raw_bytes()).as_slice(), | |
) | |
.unwrap(), | |
), | |
authority_cert_issuer: None, | |
authority_cert_serial_number: None, | |
}; | |
builder.add_extension(&aki).unwrap(); | |
builder | |
.add_extension(&BasicConstraints { | |
ca: true, | |
path_len_constraint: None, | |
}) | |
.unwrap(); | |
let cert = builder.build_with_rng(&mut rng).unwrap(); | |
let pem = cert.to_pem(LineEnding::LF).expect("generate pem"); | |
let pem_bytes = pem.into_bytes(); | |
let mut file = File::create("alice.pem").unwrap(); | |
file.write_all(&pem_bytes).unwrap(); | |
let pem_key = private_key | |
.to_pkcs8_pem(LineEnding::LF) | |
.expect("generate private pem"); | |
let pem_bytes = pem_key.as_bytes(); | |
let mut file = File::create("alice.key").unwrap(); | |
file.write_all(&pem_bytes).unwrap(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment