use crate::identity::PkiError;
use alloc::format;
pub fn adjust_participant_guid_prefix(
candidate_guid: &[u8; 16],
cert_der: &[u8],
) -> Result<[u8; 12], PkiError> {
use crate::backend::digest::{SHA256, digest};
use x509_cert::Certificate;
use x509_cert::der::{Decode, Encode};
let cert = Certificate::from_der(cert_der)
.map_err(|e| PkiError::InvalidPem(format!("adjust_guid: cert der: {e}")))?;
let subject_der = cert
.tbs_certificate
.subject
.to_der()
.map_err(|e| PkiError::InvalidPem(format!("adjust_guid: subject der: {e}")))?;
let h1 = digest(&SHA256, &subject_der);
let h1 = h1.as_ref();
let h2 = digest(&SHA256, candidate_guid);
let h2 = h2.as_ref();
let mut prefix = [0u8; 12];
for (i, p) in prefix.iter_mut().take(6).enumerate() {
*p = offset_1bit(h1, i);
}
prefix[0] |= 0x80;
prefix[6..12].copy_from_slice(&h2[0..6]);
Ok(prefix)
}
fn offset_1bit(a: &[u8], i: usize) -> u8 {
(a[i] >> 1)
| if i == 0 {
0
} else if a[i - 1] & 1 != 0 {
0x80
} else {
0
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
#[test]
fn offset_1bit_is_a_one_bit_right_shift() {
let a = [0b1000_0001u8, 0b0000_0010u8];
assert_eq!(offset_1bit(&a, 0), 0b0100_0000);
assert_eq!(offset_1bit(&a, 1), 0b1000_0001);
}
#[test]
fn adjusted_prefix_sets_high_bit_and_is_deterministic_per_identity() {
use rcgen::{CertificateParams, DistinguishedName, DnType, KeyPair};
let mk_cert = |cn: &str| -> alloc::vec::Vec<u8> {
let mut p = CertificateParams::new(alloc::vec::Vec::new()).unwrap();
let mut dn = DistinguishedName::new();
dn.push(DnType::CommonName, cn);
p.distinguished_name = dn;
let k = KeyPair::generate().unwrap();
let c = p.self_signed(&k).unwrap();
c.der().to_vec()
};
let cert_a = mk_cert("CN=alice");
let cert_b = mk_cert("CN=bob");
let candidate = [
0x01, 0x10, 0xff, 0xb5, 0xb6, 0xb2, 0xd6, 0x02, 0x9d, 0x3b, 0xc9, 0x06, 0x00, 0x00,
0x01, 0xc1,
];
let pa = adjust_participant_guid_prefix(&candidate, &cert_a).unwrap();
assert_eq!(pa[0] & 0x80, 0x80);
assert_eq!(
pa,
adjust_participant_guid_prefix(&candidate, &cert_a).unwrap()
);
let pb = adjust_participant_guid_prefix(&candidate, &cert_b).unwrap();
assert_ne!(pa, pb);
let mut candidate2 = candidate;
candidate2[3] ^= 0xff;
let pa2 = adjust_participant_guid_prefix(&candidate2, &cert_a).unwrap();
assert_eq!(
pa[..6],
pa2[..6],
"leading 6 bytes = subject hash (identity-bound)"
);
assert_ne!(
pa[6..],
pa2[6..],
"trailing 6 bytes = hash of the candidate GUID"
);
}
}