diff options
author | Bartek IwaĆczuk <biwanczuk@gmail.com> | 2023-04-24 12:22:21 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-04-24 12:22:21 +0200 |
commit | 1f0360c07382dbd86066d1aa8aa4bae34aff18c5 (patch) | |
tree | cc82d00aea829f0b3d3949f40df9696b099ee662 /ext/node/ops/crypto/x509.rs | |
parent | 28e2c7204fe02304a8fc3339d7758eec0f64f723 (diff) |
refactor(ext/node): reorganize ops (#18799)
Move all op related code of "ext/node" to "ext/node/ops" module.
These files were unnecessarily scattered around the extension.
Diffstat (limited to 'ext/node/ops/crypto/x509.rs')
-rw-r--r-- | ext/node/ops/crypto/x509.rs | 319 |
1 files changed, 319 insertions, 0 deletions
diff --git a/ext/node/ops/crypto/x509.rs b/ext/node/ops/crypto/x509.rs new file mode 100644 index 000000000..402c58b72 --- /dev/null +++ b/ext/node/ops/crypto/x509.rs @@ -0,0 +1,319 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +use deno_core::error::bad_resource_id; +use deno_core::error::AnyError; +use deno_core::op; +use deno_core::OpState; +use deno_core::Resource; + +use std::borrow::Cow; + +use x509_parser::der_parser::asn1_rs::Any; +use x509_parser::der_parser::asn1_rs::Tag; +use x509_parser::der_parser::oid::Oid; +use x509_parser::extensions; +use x509_parser::pem; +use x509_parser::prelude::*; + +use digest::Digest; + +struct Certificate { + _buf: Vec<u8>, + pem: Option<pem::Pem>, + cert: X509Certificate<'static>, +} + +impl Certificate { + fn fingerprint<D: Digest>(&self) -> Option<String> { + self.pem.as_ref().map(|pem| { + let mut hasher = D::new(); + hasher.update(&pem.contents); + let bytes = hasher.finalize(); + // OpenSSL returns colon separated upper case hex values. + let mut hex = String::with_capacity(bytes.len() * 2); + for byte in bytes { + hex.push_str(&format!("{:02X}:", byte)); + } + hex.pop(); + hex + }) + } +} + +impl std::ops::Deref for Certificate { + type Target = X509Certificate<'static>; + + fn deref(&self) -> &Self::Target { + &self.cert + } +} + +impl Resource for Certificate { + fn name(&self) -> Cow<str> { + "x509Certificate".into() + } +} + +#[op] +pub fn op_node_x509_parse( + state: &mut OpState, + buf: &[u8], +) -> Result<u32, AnyError> { + let pem = match pem::parse_x509_pem(buf) { + Ok((_, pem)) => Some(pem), + Err(_) => None, + }; + + let cert = pem + .as_ref() + .map(|pem| pem.parse_x509()) + .unwrap_or_else(|| X509Certificate::from_der(buf).map(|(_, cert)| cert))?; + + let cert = Certificate { + _buf: buf.to_vec(), + // SAFETY: Extending the lifetime of the certificate. Backing buffer is + // owned by the resource. + cert: unsafe { std::mem::transmute(cert) }, + pem, + }; + let rid = state.resource_table.add(cert); + Ok(rid) +} + +#[op] +pub fn op_node_x509_ca( + state: &mut OpState, + rid: u32, +) -> Result<bool, AnyError> { + let cert = state + .resource_table + .get::<Certificate>(rid) + .map_err(|_| bad_resource_id())?; + Ok(cert.is_ca()) +} + +#[op] +pub fn op_node_x509_check_email( + state: &mut OpState, + rid: u32, + email: &str, +) -> Result<bool, AnyError> { + let cert = state + .resource_table + .get::<Certificate>(rid) + .map_err(|_| bad_resource_id())?; + + let subject = cert.subject(); + if subject + .iter_email() + .any(|e| e.as_str().unwrap_or("") == email) + { + return Ok(true); + } + + let subject_alt = cert + .extensions() + .iter() + .find(|e| e.oid == x509_parser::oid_registry::OID_X509_EXT_SUBJECT_ALT_NAME) + .and_then(|e| match e.parsed_extension() { + extensions::ParsedExtension::SubjectAlternativeName(s) => Some(s), + _ => None, + }); + + if let Some(subject_alt) = subject_alt { + for name in &subject_alt.general_names { + dbg!(name); + if let extensions::GeneralName::RFC822Name(n) = name { + if *n == email { + return Ok(true); + } + } + } + } + + Ok(false) +} + +#[op] +pub fn op_node_x509_fingerprint( + state: &mut OpState, + rid: u32, +) -> Result<Option<String>, AnyError> { + let cert = state + .resource_table + .get::<Certificate>(rid) + .map_err(|_| bad_resource_id())?; + Ok(cert.fingerprint::<sha1::Sha1>()) +} + +#[op] +pub fn op_node_x509_fingerprint256( + state: &mut OpState, + rid: u32, +) -> Result<Option<String>, AnyError> { + let cert = state + .resource_table + .get::<Certificate>(rid) + .map_err(|_| bad_resource_id())?; + Ok(cert.fingerprint::<sha2::Sha256>()) +} + +#[op] +pub fn op_node_x509_fingerprint512( + state: &mut OpState, + rid: u32, +) -> Result<Option<String>, AnyError> { + let cert = state + .resource_table + .get::<Certificate>(rid) + .map_err(|_| bad_resource_id())?; + Ok(cert.fingerprint::<sha2::Sha512>()) +} + +#[op] +pub fn op_node_x509_get_issuer( + state: &mut OpState, + rid: u32, +) -> Result<String, AnyError> { + let cert = state + .resource_table + .get::<Certificate>(rid) + .map_err(|_| bad_resource_id())?; + Ok(x509name_to_string(cert.issuer(), oid_registry())?) +} + +#[op] +pub fn op_node_x509_get_subject( + state: &mut OpState, + rid: u32, +) -> Result<String, AnyError> { + let cert = state + .resource_table + .get::<Certificate>(rid) + .map_err(|_| bad_resource_id())?; + Ok(x509name_to_string(cert.subject(), oid_registry())?) +} + +// Attempt to convert attribute to string. If type is not a string, return value is the hex +// encoding of the attribute value +fn attribute_value_to_string( + attr: &Any, + _attr_type: &Oid, +) -> Result<String, X509Error> { + // TODO: replace this with helper function, when it is added to asn1-rs + match attr.tag() { + Tag::NumericString + | Tag::BmpString + | Tag::VisibleString + | Tag::PrintableString + | Tag::GeneralString + | Tag::ObjectDescriptor + | Tag::GraphicString + | Tag::T61String + | Tag::VideotexString + | Tag::Utf8String + | Tag::Ia5String => { + let s = core::str::from_utf8(attr.data) + .map_err(|_| X509Error::InvalidAttributes)?; + Ok(s.to_owned()) + } + _ => { + // type is not a string, get slice and convert it to base64 + Ok(data_encoding::HEXUPPER.encode(attr.as_bytes())) + } + } +} + +fn x509name_to_string( + name: &X509Name, + oid_registry: &oid_registry::OidRegistry, +) -> Result<String, x509_parser::error::X509Error> { + // Lifted from https://github.com/rusticata/x509-parser/blob/4d618c2ed6b1fc102df16797545895f7c67ee0fe/src/x509.rs#L543-L566 + // since it's a private function (Copyright 2017 Pierre Chifflier) + name.iter_rdn().fold(Ok(String::new()), |acc, rdn| { + acc.and_then(|mut _vec| { + rdn + .iter() + .fold(Ok(String::new()), |acc2, attr| { + acc2.and_then(|mut _vec2| { + let val_str = + attribute_value_to_string(attr.attr_value(), attr.attr_type())?; + // look ABBREV, and if not found, use shortname + let abbrev = match oid2abbrev(attr.attr_type(), oid_registry) { + Ok(s) => String::from(s), + _ => format!("{:?}", attr.attr_type()), + }; + let rdn = format!("{}={}", abbrev, val_str); + match _vec2.len() { + 0 => Ok(rdn), + _ => Ok(_vec2 + " + " + rdn.as_str()), + } + }) + }) + .map(|v| match _vec.len() { + 0 => v, + _ => _vec + "\n" + v.as_str(), + }) + }) + }) +} + +#[op] +pub fn op_node_x509_get_valid_from( + state: &mut OpState, + rid: u32, +) -> Result<String, AnyError> { + let cert = state + .resource_table + .get::<Certificate>(rid) + .map_err(|_| bad_resource_id())?; + Ok(cert.validity().not_before.to_string()) +} + +#[op] +pub fn op_node_x509_get_valid_to( + state: &mut OpState, + rid: u32, +) -> Result<String, AnyError> { + let cert = state + .resource_table + .get::<Certificate>(rid) + .map_err(|_| bad_resource_id())?; + Ok(cert.validity().not_after.to_string()) +} + +#[op] +pub fn op_node_x509_get_serial_number( + state: &mut OpState, + rid: u32, +) -> Result<String, AnyError> { + let cert = state + .resource_table + .get::<Certificate>(rid) + .map_err(|_| bad_resource_id())?; + let mut s = cert.serial.to_str_radix(16); + s.make_ascii_uppercase(); + Ok(s) +} + +#[op] +pub fn op_node_x509_key_usage( + state: &mut OpState, + rid: u32, +) -> Result<u16, AnyError> { + let cert = state + .resource_table + .get::<Certificate>(rid) + .map_err(|_| bad_resource_id())?; + + let key_usage = cert + .extensions() + .iter() + .find(|e| e.oid == x509_parser::oid_registry::OID_X509_EXT_KEY_USAGE) + .and_then(|e| match e.parsed_extension() { + extensions::ParsedExtension::KeyUsage(k) => Some(k), + _ => None, + }); + + Ok(key_usage.map(|k| k.flags).unwrap_or(0)) +} |