deno/libs/crypto/spki.rs

198 lines
6.6 KiB
Rust

// Copyright 2018-2025 the Deno authors. MIT license.
use std::ptr::NonNull;
use crate::ffi::Bio;
use crate::ffi::PKey;
#[derive(Debug)]
pub struct NetscapeSpki(*mut aws_lc_sys::NETSCAPE_SPKI);
impl NetscapeSpki {
/// Decodes a base64-encoded SPKI certificate.
fn from_base64(data: &[u8]) -> Result<Self, &'static str> {
// Trim trailing characters for compatibility with OpenSSL.
let end = data
.iter()
.rposition(|&b| !b" \n\r\t".contains(&b))
.map_or(0, |i| i + 1);
if end == 0 {
return Err("Invalid SPKI data: no base64 content found");
}
// SAFETY: Cast data pointer to convert base64 to NETSCAPE_SPKI
unsafe {
let spki = aws_lc_sys::NETSCAPE_SPKI_b64_decode(
data.as_ptr() as *const _,
end as isize,
);
if spki.is_null() {
return Err("Failed to decode base64 SPKI data");
}
Ok(NetscapeSpki(spki))
}
}
fn verify(&self, pkey: &PKey) -> bool {
// SAFETY: Use public key to verify SPKI certificate
unsafe {
let result = aws_lc_sys::NETSCAPE_SPKI_verify(self.0, pkey.as_ptr());
result > 0
}
}
fn spkac(&self) -> Result<&aws_lc_sys::NETSCAPE_SPKAC, &'static str> {
// SAFETY: Access spkac field via raw pointer with null checks
unsafe {
if self.0.is_null() || (*self.0).spkac.is_null() {
return Err("Invalid SPKAC structure");
}
Ok(&*(*self.0).spkac)
}
}
fn get_public_key(&self) -> Result<PKey, &'static str> {
// SAFETY: Extract public key, null checked by PKey::from_ptr
unsafe {
let pkey = aws_lc_sys::NETSCAPE_SPKI_get_pubkey(self.0);
PKey::from_ptr(pkey).ok_or("Failed to extract public key")
}
}
fn get_challenge(&self) -> Result<Vec<u8>, &'static str> {
// SAFETY: Extract challenge with null checks and BufferGuard for cleanup
unsafe {
let spkac = self.spkac()?;
let challenge = spkac.challenge;
if challenge.is_null() {
return Err("No challenge found in SPKI certificate");
}
let mut buf = std::ptr::null_mut();
let buf_len = aws_lc_sys::ASN1_STRING_to_UTF8(&mut buf, challenge);
if buf_len <= 0 || buf.is_null() {
return Err("Failed to extract challenge string");
}
let _guard = BufferGuard(NonNull::new(buf).unwrap());
let challenge_slice =
std::slice::from_raw_parts(buf as *const u8, buf_len as usize);
Ok(challenge_slice.to_vec())
}
}
pub fn as_ptr(&self) -> *mut aws_lc_sys::NETSCAPE_SPKI {
self.0
}
}
impl Drop for NetscapeSpki {
fn drop(&mut self) {
// SAFETY: Free NETSCAPE_SPKI with null check
unsafe {
if !self.0.is_null() {
aws_lc_sys::NETSCAPE_SPKI_free(self.0);
}
}
}
}
// RAII guard for automatically freeing ASN1 string buffers
struct BufferGuard(NonNull<u8>);
impl Drop for BufferGuard {
fn drop(&mut self) {
// SAFETY: Free ASN1_STRING buffer (NonNull guarantees non-null)
unsafe {
aws_lc_sys::OPENSSL_free(self.0.as_ptr() as *mut std::ffi::c_void);
}
}
}
/// Validates the SPKAC data structure.
///
/// Returns true if the signature in the SPKAC data is valid.
pub fn verify_spkac(data: &[u8]) -> bool {
let spki = match NetscapeSpki::from_base64(data) {
Ok(spki) => spki,
Err(_) => return false,
};
let pkey = match extract_public_key_from_spkac(&spki) {
Ok(pkey) => pkey,
Err(_) => return false,
};
spki.verify(&pkey)
}
/// Extracts the public key from the SPKAC structure.
fn extract_public_key_from_spkac(
spki: &NetscapeSpki,
) -> Result<PKey, &'static str> {
// SAFETY: Extract public key with null checks and proper ownership
unsafe {
let spkac = spki.spkac()?;
let pubkey = spkac.pubkey;
if pubkey.is_null() {
return Err("No public key in SPKAC structure");
}
let pkey = aws_lc_sys::X509_PUBKEY_get(pubkey);
PKey::from_ptr(pkey).ok_or("Failed to extract public key from X509_PUBKEY")
}
}
/// Exports the public key from the SPKAC data in PEM format.
pub fn export_public_key(data: &[u8]) -> Option<Vec<u8>> {
let spki = NetscapeSpki::from_base64(data).ok()?;
let pkey = spki.get_public_key().ok()?;
let bio = Bio::new_memory().ok()?;
// SAFETY: Write public key to BIO in PEM format, check result
unsafe {
let result = aws_lc_sys::PEM_write_bio_PUBKEY(bio.as_ptr(), pkey.as_ptr());
if result <= 0 {
return None;
}
}
bio.get_contents().ok()
}
/// Exports the challenge string from the SPKAC data.
pub fn export_challenge(data: &[u8]) -> Option<Vec<u8>> {
let spki = NetscapeSpki::from_base64(data).ok()?;
spki.get_challenge().ok()
}
#[cfg(test)]
mod tests {
use crate::spki::verify_spkac;
#[test]
fn test_md_spkac() {
// md4 and md5 based signatures are not supported.
// https://github.com/aws/aws-lc/commit/7e28b9ee89d85fbc80b69bc0eeb0070de81ac563
let spkac_data = br#"MIICUzCCATswggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC33FiIiiexwLe/P8DZx5HsqFlmUO7/lvJ7necJVNwqdZ3ax5jpQB0p6uxfqeOvzcN3k5V7UFb/Am+nkSNZMAZhsWzCU2Z4Pjh50QYz3f0Hour7/yIGStOLyYY3hgLK2K8TbhgjQPhdkw9+QtKlpvbL8fLgONAoGrVOFnRQGcr70iFffsm79mgZhKVMgYiHPJqJgGHvCtkGg9zMgS7p63+Q3ZWedtFS2RhMX3uCBy/mH6EOlRCNBbRmA4xxNzyf5GQaki3T+Iz9tOMjdPP+CwV2LqEdylmBuik8vrfTb3qIHLKKBAI8lXN26wWtA3kN4L7NP+cbKlCRlqctvhmylLH1AgMBAAEWE3RoaXMtaXMtYS1jaGFsbGVuZ2UwDQYJKoZIhvcNAQEEBQADggEBAIozmeW1kfDfAVwRQKileZGLRGCD7AjdHLYEe16xTBPve8Af1bDOyuWsAm4qQLYA4FAFROiKeGqxCtIErEvm87/09tCfF1My/1Uj+INjAk39DK9J9alLlTsrwSgd1lb3YlXY7TyitCmh7iXLo4pVhA2chNA3njiMq3CUpSvGbpzrESL2dv97lv590gUD988wkTDVyYsf0T8+X0Kww3AgPWGji+2f2i5/jTfD/s1lK1nqi7ZxFm0pGZoy1MJ51SCEy7Y82ajroI+5786nC02mo9ak7samca4YDZOoxN4d3tax4B/HDF5dqJSm1/31xYLDTfujCM5FkSjRc4m6hnriEkc="#;
assert!(!verify_spkac(spkac_data));
}
#[test]
fn test_spkac_verify() {
let spkac = b"MIICUzCCATswggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCXzfKgGnkkOF7+VwMzGpiWy5nna/VGJOfPBsCVg5WooJHN9nAFyqLxoV0WyhwvIdHhIgcTX2L4BHRa+4B0zb4stRHK02ZknJvionK4kBfa+k7Q4DzasW3ulLCTXPLVBKzW9QSzE4Wult17BX6uSUy3Bpr/Nuk6B4Ja3JnFpdSYmJbWP55kRONFBZYPCXr7T8k6hzEHcevFE/PUi6IU+LKiwyGH5KXAUzRbMtqbZLn/rEAmEBxmv/z/+shAwiRE8s9RqBi+pVdwqWdw6ibNkbM7G3j4CMyfAk7EOpGf5loRIrVWB4XrVYWb2EQ6sd9LfiQ9GwqlFYw006MUo6nxoEtNAgMBAAEWE3RoaXMtaXMtYS1jaGFsbGVuZ2UwDQYJKoZIhvcNAQELBQADggEBAHUw1UoZjG7TCb/JhFo5p8XIFeizGEwYoqttBoVTQ+MeCfnNoLLeAyId0atb2jPnYsI25Z/PHHV1N9t0L/NelY3rZC/Z00Wx8IGeslnGXXbqwnp36Umb0r2VmxTr8z1QaToGyOQXp4Xor9qbQFoANIivyVUYsuqJ1FnDJCC/jBPo4IWiQbTst331v2fiVdV+/XUh9AIjcm4085b65HjFwLxDeWhbgAZ+UfhqBbTVA1K8uUqS8e3gbeaNstZvnclxZ3PlHSk8v1RdIG4e5ThTOwPH5u/7KKeafn9SwgY/Q8KqaVfHHCv1IeVlijamjnyFhWc35kGlBUNgLOnWAOE3GsM=";
assert!(verify_spkac(spkac));
}
#[test]
fn test_spkac_empty() {
let empty_spkac = b"";
assert!(!verify_spkac(empty_spkac));
}
}