Skip to content

Instantly share code, notes, and snippets.

@nnathan
Last active September 28, 2024 06:07
Show Gist options
  • Save nnathan/73ccb767ba706ccd07686066e0fde0f5 to your computer and use it in GitHub Desktop.
Save nnathan/73ccb767ba706ccd07686066e0fde0f5 to your computer and use it in GitHub Desktop.
Generating deterministic keypairs & certificates (rough cut #66)
[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"] }
// 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