summaryrefslogtreecommitdiff
path: root/ext/node
diff options
context:
space:
mode:
Diffstat (limited to 'ext/node')
-rw-r--r--ext/node/Cargo.toml2
-rw-r--r--ext/node/crypto/mod.rs1
-rw-r--r--ext/node/crypto/x509.rs315
-rw-r--r--ext/node/lib.rs12
-rw-r--r--ext/node/polyfills/internal/crypto/x509.ts76
5 files changed, 366 insertions, 40 deletions
diff --git a/ext/node/Cargo.toml b/ext/node/Cargo.toml
index e74cf3805..d87a2f91d 100644
--- a/ext/node/Cargo.toml
+++ b/ext/node/Cargo.toml
@@ -16,6 +16,7 @@ path = "lib.rs"
[dependencies]
aes.workspace = true
cbc.workspace = true
+data-encoding = "2.3.3"
deno_core.workspace = true
digest = { version = "0.10.5", features = ["core-api", "std"] }
ecb.workspace = true
@@ -43,3 +44,4 @@ sha3 = "0.10.5"
signature.workspace = true
tokio.workspace = true
typenum = "1.15.0"
+x509-parser = "0.15.0"
diff --git a/ext/node/crypto/mod.rs b/ext/node/crypto/mod.rs
index adacdf6d6..55a7a5870 100644
--- a/ext/node/crypto/mod.rs
+++ b/ext/node/crypto/mod.rs
@@ -23,6 +23,7 @@ use rsa::RsaPublicKey;
mod cipher;
mod digest;
mod primes;
+pub mod x509;
#[op]
pub fn op_node_check_prime(num: serde_v8::BigInt, checks: usize) -> bool {
diff --git a/ext/node/crypto/x509.rs b/ext/node/crypto/x509.rs
new file mode 100644
index 000000000..c5f3ef206
--- /dev/null
+++ b/ext/node/crypto/x509.rs
@@ -0,0 +1,315 @@
+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)
+ .or_else(|_| 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)
+ .or_else(|_| 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)
+ .or_else(|_| 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)
+ .or_else(|_| 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)
+ .or_else(|_| 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)
+ .or_else(|_| 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)
+ .or_else(|_| 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> {
+ 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),
+ }
+ })
+ })
+ .map(|v| match _vec.len() {
+ 0 => v,
+ _ => _vec + "\n" + &v,
+ })
+ })
+ })
+}
+
+#[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)
+ .or_else(|_| 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)
+ .or_else(|_| 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)
+ .or_else(|_| 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)
+ .or_else(|_| 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))
+}
diff --git a/ext/node/lib.rs b/ext/node/lib.rs
index bf947f5e8..3ef761cb7 100644
--- a/ext/node/lib.rs
+++ b/ext/node/lib.rs
@@ -194,6 +194,18 @@ deno_core::extension!(deno_node,
crypto::op_node_generate_secret,
crypto::op_node_generate_secret_async,
crypto::op_node_sign,
+ crypto::x509::op_node_x509_parse,
+ crypto::x509::op_node_x509_ca,
+ crypto::x509::op_node_x509_check_email,
+ crypto::x509::op_node_x509_fingerprint,
+ crypto::x509::op_node_x509_fingerprint256,
+ crypto::x509::op_node_x509_fingerprint512,
+ crypto::x509::op_node_x509_get_issuer,
+ crypto::x509::op_node_x509_get_subject,
+ crypto::x509::op_node_x509_get_valid_from,
+ crypto::x509::op_node_x509_get_valid_to,
+ crypto::x509::op_node_x509_get_serial_number,
+ crypto::x509::op_node_x509_key_usage,
winerror::op_node_sys_to_uv_error,
v8::op_v8_cached_data_version_tag,
v8::op_v8_get_heap_statistics,
diff --git a/ext/node/polyfills/internal/crypto/x509.ts b/ext/node/polyfills/internal/crypto/x509.ts
index e18d4fe68..7a8ee773b 100644
--- a/ext/node/polyfills/internal/crypto/x509.ts
+++ b/ext/node/polyfills/internal/crypto/x509.ts
@@ -5,9 +5,12 @@ import { KeyObject } from "ext:deno_node/internal/crypto/keys.ts";
import { Buffer } from "ext:deno_node/buffer.ts";
import { ERR_INVALID_ARG_TYPE } from "ext:deno_node/internal/errors.ts";
import { isArrayBufferView } from "ext:deno_node/internal/util/types.ts";
+import { validateString } from "ext:deno_node/internal/validators.mjs";
import { notImplemented } from "ext:deno_node/_utils.ts";
import { BinaryLike } from "ext:deno_node/internal/crypto/types.ts";
+const { ops } = globalThis.__bootstrap.core;
+
// deno-lint-ignore no-explicit-any
export type PeerCertificate = any;
@@ -35,6 +38,8 @@ export interface X509CheckOptions {
}
export class X509Certificate {
+ #handle: number;
+
constructor(buffer: BinaryLike) {
if (typeof buffer === "string") {
buffer = Buffer.from(buffer);
@@ -48,20 +53,21 @@ export class X509Certificate {
);
}
- notImplemented("crypto.X509Certificate");
+ this.#handle = ops.op_node_x509_parse(buffer);
}
get ca(): boolean {
- notImplemented("crypto.X509Certificate.prototype.ca");
-
- return false;
+ return ops.op_node_x509_ca(this.#handle);
}
checkEmail(
- _email: string,
+ email: string,
_options?: Pick<X509CheckOptions, "subject">,
): string | undefined {
- notImplemented("crypto.X509Certificate.prototype.checkEmail");
+ validateString(email, "email");
+ if (ops.op_node_x509_check_email(this.#handle, email)) {
+ return email;
+ }
}
checkHost(_name: string, _options?: X509CheckOptions): string | undefined {
@@ -81,21 +87,15 @@ export class X509Certificate {
}
get fingerprint(): string {
- notImplemented("crypto.X509Certificate.prototype.fingerprint");
-
- return "";
+ return ops.op_node_x509_fingerprint(this.#handle);
}
get fingerprint256(): string {
- notImplemented("crypto.X509Certificate.prototype.fingerprint256");
-
- return "";
+ return ops.op_node_x509_fingerprint256(this.#handle);
}
get fingerprint512(): string {
- notImplemented("crypto.X509Certificate.prototype.fingerprint512");
-
- return "";
+ return ops.op_node_x509_fingerprint512(this.#handle);
}
get infoAccess(): string | undefined {
@@ -105,21 +105,27 @@ export class X509Certificate {
}
get issuer(): string {
- notImplemented("crypto.X509Certificate.prototype.issuer");
-
- return "";
+ return ops.op_node_x509_get_issuer(this.#handle);
}
get issuerCertificate(): X509Certificate | undefined {
- notImplemented("crypto.X509Certificate.prototype.issuerCertificate");
-
- return {} as X509Certificate;
+ return undefined;
}
- get keyUsage(): string[] {
- notImplemented("crypto.X509Certificate.prototype.keyUsage");
-
- return [];
+ get keyUsage(): string[] | undefined {
+ const flags = ops.op_node_x509_key_usage(this.#handle);
+ if (flags === 0) return undefined;
+ const result: string[] = [];
+ if (flags & 0x01) result.push("DigitalSignature");
+ if (flags >> 1 & 0x01) result.push("NonRepudiation");
+ if (flags >> 2 & 0x01) result.push("KeyEncipherment");
+ if (flags >> 3 & 0x01) result.push("DataEncipherment");
+ if (flags >> 4 & 0x01) result.push("KeyAgreement");
+ if (flags >> 5 & 0x01) result.push("KeyCertSign");
+ if (flags >> 6 & 0x01) result.push("CRLSign");
+ if (flags >> 7 & 0x01) result.push("EncipherOnly");
+ if (flags >> 8 & 0x01) result.push("DecipherOnly");
+ return result;
}
get publicKey(): KeyObject {
@@ -135,21 +141,15 @@ export class X509Certificate {
}
get serialNumber(): string {
- notImplemented("crypto.X509Certificate.prototype.serialNumber");
-
- return "";
+ return ops.op_node_x509_get_serial_number(this.#handle);
}
get subject(): string {
- notImplemented("crypto.X509Certificate.prototype.subject");
-
- return "";
+ return ops.op_node_x509_get_subject(this.#handle);
}
get subjectAltName(): string | undefined {
- notImplemented("crypto.X509Certificate.prototype.subjectAltName");
-
- return "";
+ return undefined;
}
toJSON(): string {
@@ -165,15 +165,11 @@ export class X509Certificate {
}
get validFrom(): string {
- notImplemented("crypto.X509Certificate.prototype.validFrom");
-
- return "";
+ return ops.op_node_x509_get_valid_from(this.#handle);
}
get validTo(): string {
- notImplemented("crypto.X509Certificate.prototype.validTo");
-
- return "";
+ return ops.op_node_x509_get_valid_to(this.#handle);
}
verify(_publicKey: KeyObject): boolean {