Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions Cargo.lock

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

7 changes: 6 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ apple-crypto = ["dep:str0m-apple-crypto"]
vendored = ["str0m-openssl?/vendored"]
unversioned = []

# For crypto providers that have dimpl and non-dimpl DTLS support, use dimpl.
# This has no effect for providers that always dimpl.
prefer_dimpl = ["str0m-openssl?/prefer_dimpl", "str0m-wincrypto?/prefer_dimpl"]

# Redacts personally identifiable information (PII) from logs debug and above
pii = []

Expand All @@ -56,7 +60,8 @@ crc = "3"
serde = { version = "1.0.152", features = ["derive"] }

# DTLS made for str0m (required dependency)
dimpl = { version = "0.4.3", default-features = false }
#dimpl = { version = "0.4.3", default-features = false }
dimpl = {git = "https://github.com/algesten/dimpl.git", default-features = false }

time = "0.3"

Expand Down
3 changes: 1 addition & 2 deletions crypto/apple-crypto/Cargo.lock

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

3 changes: 2 additions & 1 deletion crypto/apple-crypto/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ rust-version = "1.85.0"

[dependencies]
str0m-proto = { version = "0.2.0", path = "../../proto" }
dimpl = { version = "0.4.3", default-features = false }
#dimpl = { version = "0.4.3", default-features = false }
dimpl = {git = "https://github.com/algesten/dimpl.git", default-features = false}
time = "0.3"

# Apple platform bindings
Expand Down
207 changes: 207 additions & 0 deletions crypto/apple-crypto/src/cert.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
//! Certificate generation routines.
//!

use str0m_proto::crypto::CryptoError;

// ---------------------------------------------------------------------------
// X.509 / PKCS#8 certificate helpers
// ---------------------------------------------------------------------------

/// Build a self-signed X.509 v3 certificate.
pub fn build_self_signed_certificate<SignFn>(
common_name: &str,
serial_number: [u8; 16],
public_key_bytes: &[u8],
sign_with_ecdsa_sha256: SignFn,
) -> Result<Vec<u8>, CryptoError>
where
SignFn: FnOnce(&[u8]) -> Result<Vec<u8>, CryptoError>,
{
let mut tbs_certificate = Vec::new();

// Version: v3 (encoded as [0] EXPLICIT INTEGER 2)
let version = encode_explicit_tag(0, &encode_integer(&[2]));
tbs_certificate.extend_from_slice(&version);

// Serial number (random)
tbs_certificate.extend_from_slice(&encode_integer(&serial_number));

// Signature algorithm: ecdsa-with-SHA256
let ecdsa_with_sha256_oid = &[0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x02];
tbs_certificate.extend_from_slice(&encode_algorithm_identifier(ecdsa_with_sha256_oid));

// Issuer: CN=WebRTC
let issuer = encode_name(common_name);
tbs_certificate.extend_from_slice(&issuer);

// Validity: 1 year from now
let validity = encode_validity()?;
tbs_certificate.extend_from_slice(&validity);

// Subject: CN=WebRTC (same as issuer for self-signed)
tbs_certificate.extend_from_slice(&issuer);

// Subject Public Key Info
let spki = encode_ec_public_key_info(public_key_bytes)?;
tbs_certificate.extend_from_slice(&spki);

let tbs_certificate = encode_sequence(&tbs_certificate);

// Sign the TBS certificate using the private key directly
let signature = sign_with_ecdsa_sha256(&tbs_certificate)?;

// Encode the full certificate
let ecdsa_with_sha256_oid = &[0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x02]; // 1.2.840.10045.4.3.2
let sig_algorithm = encode_algorithm_identifier(ecdsa_with_sha256_oid);

// Signature as BIT STRING (prepend 0x00 for no unused bits)
let signature_bits = encode_bit_string(&signature);

// Full certificate SEQUENCE
let mut signed_certificate = Vec::new();
signed_certificate.extend_from_slice(&tbs_certificate);
signed_certificate.extend_from_slice(&sig_algorithm);
signed_certificate.extend_from_slice(&signature_bits);

Ok(encode_sequence(&signed_certificate))
}

/// Build a PKCS#8 `PrivateKeyInfo` for an EC P-256 key.
pub fn build_pkcs8(
private_scalar: &[u8; 32],
public_key_bytes: &[u8],
) -> Result<Vec<u8>, CryptoError> {
// Version: 0
let version = encode_integer(&[0]);

// Algorithm: ecPublicKey with prime256v1
let ec_public_key_oid = &[0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01];
let prime256v1_oid = &[0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07];
let algorithm =
encode_sequence(&[encode_oid(ec_public_key_oid), encode_oid(prime256v1_oid)].concat());

// PrivateKey: SEC1 ECPrivateKey wrapped in OCTET STRING
let ec_private_key = encode_ec_private_key(private_scalar, public_key_bytes)?;
let private_key = encode_tag(0x04, &ec_private_key); // OCTET STRING

Ok(encode_sequence(&[version, algorithm, private_key].concat()))
}

// --- ASN.1 DER encoding helpers (private) ---

fn encode_sequence(content: &[u8]) -> Vec<u8> {
encode_tag(0x30, content)
}

fn encode_tag(tag: u8, content: &[u8]) -> Vec<u8> {
let mut result = vec![tag];
encode_length(content.len(), &mut result);
result.extend_from_slice(content);
result
}

fn encode_length(len: usize, out: &mut Vec<u8>) {
if len < 128 {
// len fits in a single byte
} else if len < 256 {
out.push(0x81);
} else {
out.push(0x82);
out.push((len >> 8) as u8);
}
out.push(len as u8);
}

fn encode_integer(value: &[u8]) -> Vec<u8> {
// Skip leading zeros but keep at least one byte
let mut start = 0;
while start < value.len() - 1 && value[start] == 0 {
start += 1;
}

let value = &value[start..];

// If high bit is set, prepend 0x00
if value[0] & 0x80 != 0 {
let mut content = vec![0x00];
content.extend_from_slice(value);
encode_tag(0x02, &content)
} else {
encode_tag(0x02, value)
}
}

fn encode_explicit_tag(tag_num: u8, content: &[u8]) -> Vec<u8> {
encode_tag(0xA0 | tag_num, content)
}

fn encode_oid(oid_bytes: &[u8]) -> Vec<u8> {
encode_tag(0x06, oid_bytes)
}

fn encode_algorithm_identifier(oid_bytes: &[u8]) -> Vec<u8> {
let oid = encode_oid(oid_bytes);
encode_sequence(&oid)
}

fn encode_name(cn: &str) -> Vec<u8> {
// CN OID: 2.5.4.3
let cn_oid = &[0x55, 0x04, 0x03];
let oid = encode_oid(cn_oid);
let value = encode_tag(0x0C, cn.as_bytes()); // UTF8String
let attr_type_value = encode_sequence(&[oid, value].concat());
let rdn = encode_tag(0x31, &attr_type_value); // SET
encode_sequence(&rdn)
}

fn encode_validity() -> Result<Vec<u8>, CryptoError> {
// For simplicity, use fixed dates that are valid
// Format: YYYYMMDDHHMMSSZ
let not_before = b"20240101000000Z";
let not_after = b"20251231235959Z";

let nb = encode_tag(0x18, not_before); // GeneralizedTime
let na = encode_tag(0x18, not_after);

Ok(encode_sequence(&[nb, na].concat()))
}

fn encode_ec_public_key_info(public_key_bytes: &[u8]) -> Result<Vec<u8>, CryptoError> {
// OID: 1.2.840.10045.2.1 (ecPublicKey)
let ec_public_key_oid = &[0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01];
// OID: 1.2.840.10045.3.1.7 (prime256v1/secp256r1)
let prime256v1_oid = &[0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07];

let algorithm =
encode_sequence(&[encode_oid(ec_public_key_oid), encode_oid(prime256v1_oid)].concat());

let public_key_bits = encode_bit_string(public_key_bytes);

Ok(encode_sequence(&[algorithm, public_key_bits].concat()))
}

fn encode_bit_string(data: &[u8]) -> Vec<u8> {
let mut content = vec![0x00]; // No unused bits
content.extend_from_slice(data);
encode_tag(0x03, &content)
}

fn encode_ec_private_key(
private_scalar: &[u8],
public_key_bytes: &[u8],
) -> Result<Vec<u8>, CryptoError> {
let version = encode_integer(&[1]);
let private_key = encode_tag(0x04, private_scalar); // OCTET STRING

// Parameters: prime256v1 OID
let prime256v1_oid = &[0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07];
let params = encode_explicit_tag(0, &encode_oid(prime256v1_oid));

// Public key as [1] BIT STRING
let public_key_bits = encode_bit_string(public_key_bytes);
let public_key = encode_explicit_tag(1, &public_key_bits);

Ok(encode_sequence(
&[version, private_key, params, public_key].concat(),
))
}
119 changes: 0 additions & 119 deletions crypto/apple-crypto/src/dimpl_provider/hash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,122 +99,3 @@ impl HashContext for Sha384Context {
out.extend_from_slice(&digest);
}
}

#[cfg(test)]
mod test {
use super::*;

// Test vectors from NIST CAVP
// https://csrc.nist.gov/projects/cryptographic-algorithm-validation-program
fn slice_to_hex(data: &[u8]) -> String {
let mut s = String::new();
for byte in data.iter() {
s.push_str(&format!("{:02x}", byte));
}
s
}

// SHA-256 Test Vectors

#[test]
fn test_sha256_context_single_update() {
let mut ctx = Sha256Context::new();
ctx.update(b"abc");
let mut out = Buf::new();
ctx.clone_and_finalize(&mut out);
assert_eq!(
slice_to_hex(&out),
"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
);
}

#[test]
fn test_sha256_context_multiple_updates() {
let mut ctx = Sha256Context::new();
ctx.update(b"abcdbcde");
ctx.update(b"cdefdefg");
ctx.update(b"efghfghighijhijkijkljklmklmnlmnomnopnopq");
let mut out = Buf::new();
ctx.clone_and_finalize(&mut out);
assert_eq!(
slice_to_hex(&out),
"248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1"
);
}

#[test]
fn test_sha256_context_snapshot() {
let mut ctx = Sha256Context::new();
ctx.update(b"abc");
let mut out1 = Buf::new();
ctx.clone_and_finalize(&mut out1);

// Update again and verify first snapshot is unchanged
ctx.update(b"def");
let mut out2 = Buf::new();
ctx.clone_and_finalize(&mut out2);

assert_eq!(
slice_to_hex(&out1),
"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
);
assert_eq!(
slice_to_hex(&out2),
"bef57ec7f53a6d40beb640a780a639c83bc29ac8a9816f1fc6c5c6dcd93c4721"
);
}

// SHA-384 Test Vectors

#[test]
fn test_sha384_context_single_update() {
let mut ctx = Sha384Context::new();
ctx.update(b"abc");
let mut out = Buf::new();
ctx.clone_and_finalize(&mut out);
assert_eq!(
slice_to_hex(&out),
"cb00753f45a35e8bb5a03d699ac65007272c32ab0eded1631a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7"
);
}

#[test]
fn test_sha384_context_multiple_updates() {
let mut ctx = Sha384Context::new();
ctx.update(b"abcdefghbcdefghicdefghijdefghijk");
ctx.update(b"efghijklfghijklmghijklmnhijklmno");
ctx.update(b"ijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu");
let mut out = Buf::new();
ctx.clone_and_finalize(&mut out);
assert_eq!(
slice_to_hex(&out),
"09330c33f71147e83d192fc782cd1b4753111b173b3b05d22fa08086e3b0f712fcc7c71a557e2db966c3e9fa91746039"
);
}

#[test]
fn test_hash_provider_sha256() {
let provider = AppleHashProvider;
let mut ctx = provider.create_hash(HashAlgorithm::SHA256);
ctx.update(b"abc");
let mut out = Buf::new();
ctx.clone_and_finalize(&mut out);
assert_eq!(
slice_to_hex(&out),
"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
);
}

#[test]
fn test_hash_provider_sha384() {
let provider = AppleHashProvider;
let mut ctx = provider.create_hash(HashAlgorithm::SHA384);
ctx.update(b"abc");
let mut out = Buf::new();
ctx.clone_and_finalize(&mut out);
assert_eq!(
slice_to_hex(&out),
"cb00753f45a35e8bb5a03d699ac65007272c32ab0eded1631a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7"
);
}
}
Loading
Loading