mirror of
https://github.com/denoland/deno.git
synced 2025-08-04 10:59:13 +00:00
198 lines
6.6 KiB
Rust
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));
|
|
}
|
|
}
|