Skip to content

Commit

Permalink
add certificate equiv test, move to sha256, newer openssl distributio…
Browse files Browse the repository at this point in the history
…ns do not contain sha1 support by default
  • Loading branch information
drahnr committed Feb 5, 2025
1 parent 2fde6df commit dbe4ab5
Show file tree
Hide file tree
Showing 4 changed files with 271 additions and 11 deletions.
60 changes: 60 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ test-case = "3.3.1"
thirtyfour_sync = { version = "0.27", default-features = false, features = [
"reqwest-rustls-tls",
] }
openssl = { version = "=0.10.64" }

[target.'cfg(unix)'.dependencies]
daemonize = "0.5"
Expand Down
30 changes: 19 additions & 11 deletions src/dist/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ pub mod urls {
}

#[cfg(feature = "dist-server")]
mod server {
pub(crate) mod server {
use crate::util::new_reqwest_blocking_client;
use byteorder::{BigEndian, ReadBytesExt};
use chrono::{Datelike, Timelike};
Expand Down Expand Up @@ -312,7 +312,16 @@ mod server {
}

fn create_https_cert_and_privkey(addr: SocketAddr) -> Result<(Vec<u8>, Vec<u8>, Vec<u8>)> {
let mut rng = OsRng;
create_https_cert_and_privkey_inner(OsRng, chrono::Utc::now().naive_utc(), addr)
}

pub(crate) fn create_https_cert_and_privkey_inner<
R: rand::RngCore + rand::prelude::CryptoRng,
>(
mut rng: R,
now: chrono::NaiveDateTime,
addr: SocketAddr,
) -> Result<(Vec<u8>, Vec<u8>, Vec<u8>)> {
let rsa_key = rsa::RsaPrivateKey::new(&mut rng, 2048)?;

let line_ending = rsa::pkcs8::LineEnding::default();
Expand All @@ -323,15 +332,14 @@ mod server {
// convert to picky
let sk = PrivateKey::from_pem_str(sk_pkcs8.as_str())?;
let pk = PublicKey::from_pem_str(pk_pkcs8.as_str())?;
let today = chrono::Utc::now().naive_utc();
let expires = today + chrono::Duration::days(365);
let expires = now + chrono::Days::new(365);
let start = UTCDate::new(
today.year() as u16,
today.month() as u8,
today.day() as u8,
today.time().hour() as u8,
today.time().minute() as u8,
today.time().second() as u8,
now.year() as u16,
now.month() as u8,
now.day() as u8,
now.time().hour() as u8,
now.time().minute() as u8,
now.time().second() as u8,
)
.unwrap();
let end = UTCDate::new(
Expand Down Expand Up @@ -385,7 +393,7 @@ mod server {
.subject(subject_name, pk)
.subject_alt_name(subject_alt_name)
.serial_number(vec![1]) // cannot be 0 according to picky internal notes
.signature_hash_type(SignatureAlgorithm::RsaPkcs1v15(HashAlgorithm::SHA1))
.signature_hash_type(SignatureAlgorithm::RsaPkcs1v15(HashAlgorithm::SHA2_256))
.extended_key_usage(extended_key_usage)
.self_signed(issuer_name, &sk)
.build()?;
Expand Down
191 changes: 191 additions & 0 deletions src/dist/test.rs
Original file line number Diff line number Diff line change
@@ -1 +1,192 @@
use super::*;

use crate::dist::http::server::create_https_cert_and_privkey_inner;
use chrono::{NaiveDateTime, Utc};
use once_cell::sync::Lazy;
use picky::{key::PrivateKey, signature::SignatureAlgorithm, x509::Cert};

use rand::{rngs::OsRng, RngCore};
use rouille::accept;
use serde::Serialize;
use std::convert::Infallible;
use std::io::Read;
use std::net::{IpAddr, SocketAddr};
use std::result::Result as StdResult;
use std::sync::atomic;
use std::sync::Mutex;
use std::{
collections::HashMap,
net::{Ipv4Addr, SocketAddrV4},
};

/// Replicates previous generation behaviour, but uses sha256 since sha1 is not included in some openssl distributions
fn create_https_cert_and_privkey_legacy_openssl(
now: NaiveDateTime,
sk: PrivateKey,
addr: SocketAddr,
) -> Result<(Vec<u8>, Vec<u8>, Vec<u8>)> {
let der_sk = sk.to_pem()?;
let rsa_key = openssl::rsa::Rsa::private_key_from_pem(der_sk.as_bytes())
.context("failed to generate rsa privkey")?;
let privkey_pem = rsa_key
.private_key_to_pem()
.context("failed to create pem from rsa privkey")?;
let privkey = openssl::pkey::PKey::from_rsa(rsa_key)
.context("failed to create openssl skey from rsa privkey")?;
let mut builder = openssl::x509::X509::builder().context("failed to create x509 builder")?;

// Populate the certificate with the necessary parts, mostly from mkcert in openssl
builder
.set_version(2)
.context("failed to set x509 version")?;
let serial_number = openssl::bn::BigNum::from_u32(0)
.and_then(|bn| bn.to_asn1_integer())
.context("failed to create openssl asn1 0")?;
builder
.set_serial_number(serial_number.as_ref())
.context("failed to set x509 serial number")?;
let not_before = openssl::asn1::Asn1Time::from_unix(now.and_utc().timestamp())
.context("failed to create openssl not before asn1")?;
builder
.set_not_before(not_before.as_ref())
.context("failed to set not before on x509")?;
let not_after = openssl::asn1::Asn1Time::from_unix(
now.checked_add_days(chrono::Days::new(365))
.unwrap()
.and_utc()
.timestamp(),
)
.context("failed to create openssl not after asn1")?;
builder
.set_not_after(not_after.as_ref())
.context("failed to set not after on x509")?;
builder
.set_pubkey(privkey.as_ref())
.context("failed to set pubkey for x509")?;

let mut name = openssl::x509::X509Name::builder()?;
name.append_entry_by_nid(openssl::nid::Nid::COMMONNAME, &addr.to_string())?;
let name = name.build();

builder
.set_subject_name(&name)
.context("failed to set subject name")?;
builder
.set_issuer_name(&name)
.context("failed to set issuer name")?;

// Add the SubjectAlternativeName
let extension = openssl::x509::extension::SubjectAlternativeName::new()
.ip(&addr.ip().to_string())
.build(&builder.x509v3_context(None, None))
.context("failed to build SAN extension for x509")?;
builder
.append_extension(extension)
.context("failed to append SAN extension for x509")?;

// Add ExtendedKeyUsage
let ext_key_usage = openssl::x509::extension::ExtendedKeyUsage::new()
.server_auth()
.build()
.context("failed to build EKU extension for x509")?;
builder
.append_extension(ext_key_usage)
.context("fails to append EKU extension for x509")?;

// Finish the certificate
builder
.sign(&privkey, openssl::hash::MessageDigest::sha256())
.context("failed to sign x509 with sha1")?;
let cert: openssl::x509::X509 = builder.build();
let cert_pem = cert.to_pem().context("failed to create pem from x509")?;
let cert_digest = cert
.digest(openssl::hash::MessageDigest::sha256())
.context("failed to create digest of x509 certificate")?
.as_ref()
.to_owned();
Ok((cert_digest, cert_pem, privkey_pem))
}

#[derive(Default, Debug, Clone, Copy)]
struct FlipperRng(u8);

use rand::CryptoRng;

impl FlipperRng {
fn next_byte(&mut self) -> u8 {
let v = self.0;
self.0 = self.0.wrapping_add(17);
v
}

fn fill_array<const N: usize>(&mut self, dest: &mut [u8; N]) {
for i in 0..N {
dest[i] = self.next_byte();
}
}
}
impl CryptoRng for FlipperRng {}

impl RngCore for FlipperRng {
fn next_u32(&mut self) -> u32 {
let mut v = [0u8; 4];
self.fill_array(&mut v);
u32::from_le_bytes(v)
}

fn next_u64(&mut self) -> u64 {
let mut v = [0u8; 8];
self.fill_array(&mut v);
u64::from_le_bytes(v)
}

fn fill_bytes(&mut self, dest: &mut [u8]) {
for byte in dest {
*byte = self.next_byte();
}
}

fn try_fill_bytes(&mut self, dest: &mut [u8]) -> std::result::Result<(), rand::Error> {
Ok(self.fill_bytes(dest))
}
}

#[test]
fn certificate_compatibility_assurance() {
let now: NaiveDateTime = Utc::now().naive_utc();
let addr = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 32452));
let mut rng = OsRng;
//FlipperRng::default();

fn parse<'a>(digest: &'a [u8], cert: &[u8], sk: &[u8]) -> Result<(&'a [u8], Cert, PrivateKey)> {
eprintln!("PEM parsing start");
let cert = picky::pem::parse_pem(cert)?;
let sk = picky::pem::parse_pem(sk)?;

let cert = picky::x509::Cert::from_pem(&cert)?;
let sk = picky::key::PrivateKey::from_pem(&sk)?;
eprintln!("PEM parsing end");
Ok((digest, cert, sk))
}

eprintln!("picky start");
let (digest, cert, sk) = create_https_cert_and_privkey_inner(&mut rng, now, addr).unwrap();
let (digest, cert, sk) = parse(&digest, &cert, &sk).unwrap();
eprintln!("picky end");

eprintln!("LEGACY start");
let (legacy_digest, legacy_cert, legacy_sk) =
create_https_cert_and_privkey_legacy_openssl(now, sk.clone(), addr).unwrap();
let (legacy_digest, legacy_cert, legacy_sk) =
parse(&legacy_digest, &legacy_cert, &legacy_sk).unwrap();
eprintln!("LEGACY end");

assert_eq!(dbg!(legacy_cert.extensions()), dbg!(cert.extensions()));

assert_eq!(dbg!(legacy_cert), dbg!(cert));
assert_eq!(dbg!(legacy_sk), dbg!(sk));

// since the time varies, it's impossible these are identical
dbg!(legacy_digest);
dbg!(digest);
}

0 comments on commit dbe4ab5

Please sign in to comment.