diff --git a/ext/node/lib.rs b/ext/node/lib.rs
index bf593ad43..32b652f2e 100644
--- a/ext/node/lib.rs
+++ b/ext/node/lib.rs
@@ -297,12 +297,16 @@ deno_core::extension!(deno_node,
ops::crypto::x509::op_node_x509_fingerprint256,
ops::crypto::x509::op_node_x509_fingerprint512,
ops::crypto::x509::op_node_x509_get_issuer,
+ ops::crypto::x509::op_node_x509_info_access,
ops::crypto::x509::op_node_x509_get_subject,
ops::crypto::x509::op_node_x509_get_valid_from,
ops::crypto::x509::op_node_x509_get_valid_to,
ops::crypto::x509::op_node_x509_get_serial_number,
ops::crypto::x509::op_node_x509_key_usage,
ops::crypto::x509::op_node_x509_public_key,
+ ops::crypto::x509::op_node_x509_raw,
+ ops::crypto::x509::op_node_x509_to_string,
+ ops::crypto::x509::op_node_x509_verify,
ops::fs::op_node_fs_exists_sync
,
ops::fs::op_node_fs_exists
,
ops::fs::op_node_cp_sync
,
diff --git a/ext/node/ops/crypto/testdata/x509-test-cert.pem b/ext/node/ops/crypto/testdata/x509-test-cert.pem
new file mode 100644
index 000000000..9113d866b
--- /dev/null
+++ b/ext/node/ops/crypto/testdata/x509-test-cert.pem
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDQTCCAimgAwIBAgIUCNhWDISbT5DJKeTWc5nlP5O37s8wDQYJKoZIhvcNAQEL
+BQAwFDESMBAGA1UEAwwJeDUwOS10ZXN0MB4XDTI2MTAwODAyNDcxM1oXDTM2MTAw
+NTAyNDcxM1owFDESMBAGA1UEAwwJeDUwOS10ZXN0MIIBIjANBgkqhkiG9w0BAQEF
+AAOCAQ8AMIIBCgKCAQEAjeKeEYUUEPxpkHW2Y+dioOCosSwdYkxbnC+mMSFVFuSy
+IpsnBY4oLpm63Xa9BG1hAYz8M5quozUatSBzWW298i3+a8O6CXMli2G9UgMOr0jr
+eb2mYvR2MQs4FSuaLPt9A3QG9+mbxCWXUxdQ14XqPz/kqaoUiCQgD0zklxsZOT74
+Mdbvsx46cqdU7II6yOd2dmhIXK+BR6o3ntNIYaH4QEr2b7/bhAudozRo9fCygeFK
+T3m2L9D5fmE8vEV2pda5Whnx2VcGfx5k3O2BBmWX6YMicN8YvhqnSX4MWEfAQN/w
+a7vBSXnbV5wSvt9gLcO/3WMWCPX7oJCmqyUi+GlFhQIDAQABo4GKMIGHMB0GA1Ud
+DgQWBBSJssJQcXpDcfr3s6m9QeKccMtmtjAfBgNVHSMEGDAWgBSJssJQcXpDcfr3
+s6m9QeKccMtmtjAPBgNVHRMBAf8EBTADAQH/MDQGCCsGAQUFBwEBBCgwJjAkBggr
+BgEFBQcwAYYYaHR0cDovL29jc3AuZXhhbXBsZS50ZXN0MA0GCSqGSIb3DQEBCwUA
+A4IBAQBXm1Mny5ceQdKfScLNfikw5wIfnerqGsAWOks4mo5Kpdb+AKNDo+piwo4s
+h9HQKTzssWH6PGWkcz7gGU0x6g67NZcKNTBytLX9K2kDNTUsyhbrHcQB4tFv7OUC
+uMYI31+A0TRBngveCEUtfNCi76B7P0JL7+Cdqsqrsx/wb9NVBCt/L8kHGcvnq+yI
+syjsdzOEOBs7fp4+IS/1kDPa4zX+E67e2lwOed1nKLC67K8x5pKJVBsDpBJlHASt
+dlgnNxbw7RPpzbt+9xFndj4Maco/imc0DbNL633eCPnoiVTwIcet/C2iP6Zg6kmo
+QEoIm21twcBieiwKlWj9d4WNu/Aa
+-----END CERTIFICATE-----
diff --git a/ext/node/ops/crypto/x509.rs b/ext/node/ops/crypto/x509.rs
index ab8e52f70..c91c276da 100644
--- a/ext/node/ops/crypto/x509.rs
+++ b/ext/node/ops/crypto/x509.rs
@@ -10,6 +10,7 @@ use x509_parser::extensions;
use x509_parser::pem;
use x509_parser::prelude::*;
+use super::sign::KeyObjectHandlePrehashedSignAndVerifyError;
use super::KeyObjectHandle;
use std::ops::Deref;
@@ -35,6 +36,13 @@ pub(crate) struct Certificate {
impl deno_core::GarbageCollected for Certificate {}
impl Certificate {
+ fn der_bytes(&self) -> &[u8] {
+ match self.inner.backing_cart().as_ref() {
+ CertificateSources::Der(der) => der,
+ CertificateSources::Pem(pem) => &pem.contents,
+ }
+ }
+
fn fingerprint(&self) -> Option {
if let CertificateSources::Pem(pem) = self.inner.backing_cart().as_ref() {
let mut hasher = D::new();
@@ -66,6 +74,10 @@ impl<'a> Deref for CertificateView<'a> {
pub fn op_node_x509_parse(
#[buffer] buf: &[u8],
) -> Result {
+ parse_certificate(buf)
+}
+
+fn parse_certificate(buf: &[u8]) -> Result {
let source = match pem::parse_x509_pem(buf) {
Ok((_, pem)) => CertificateSources::Pem(pem),
Err(_) => CertificateSources::Der(buf.to_vec().into_boxed_slice()),
@@ -181,6 +193,107 @@ pub fn op_node_x509_public_key(
KeyObjectHandle::new_x509_public_key(public_key)
}
+#[op2]
+#[buffer]
+pub fn op_node_x509_raw(#[cppgc] cert: &Certificate) -> Box<[u8]> {
+ cert.der_bytes().to_vec().into_boxed_slice()
+}
+
+#[op2]
+#[string]
+pub fn op_node_x509_to_string(#[cppgc] cert: &Certificate) -> String {
+ certificate_to_pem(cert)
+}
+
+fn certificate_to_pem(cert: &Certificate) -> String {
+ let encoded = data_encoding::BASE64.encode(cert.der_bytes());
+ let mut pem = String::from("-----BEGIN CERTIFICATE-----\n");
+ for line in encoded.as_bytes().chunks(64) {
+ pem.push_str(std::str::from_utf8(line).expect("base64 is ASCII"));
+ pem.push('\n');
+ }
+ pem.push_str("-----END CERTIFICATE-----\n");
+ pem
+}
+
+#[op2]
+#[string]
+pub fn op_node_x509_info_access(#[cppgc] cert: &Certificate) -> Option {
+ certificate_info_access(cert)
+}
+
+fn certificate_info_access(cert: &Certificate) -> Option {
+ let cert = cert.inner.get().deref();
+ let aia = cert.extensions().iter().find_map(|ext| {
+ if let extensions::ParsedExtension::AuthorityInfoAccess(aia) =
+ ext.parsed_extension()
+ {
+ Some(aia)
+ } else {
+ None
+ }
+ })?;
+
+ let descriptions: Vec = aia
+ .iter()
+ .map(|entry| {
+ let method = match entry.access_method.to_id_string().as_str() {
+ "1.3.6.1.5.5.7.48.1" => "OCSP".to_string(),
+ "1.3.6.1.5.5.7.48.2" => "CA Issuers".to_string(),
+ _ => entry.access_method.to_id_string(),
+ };
+ match &entry.access_location {
+ extensions::GeneralName::URI(uri) => format!("{method} - URI:{uri}"),
+ location => format!("{method} - {location}"),
+ }
+ })
+ .collect();
+ Some(descriptions.join("\n"))
+}
+
+#[derive(Debug, thiserror::Error)]
+pub enum X509VerifyError {
+ #[error("unsupported X.509 signature algorithm: {0}")]
+ UnsupportedSignatureAlgorithm(String),
+ #[error(transparent)]
+ Key(#[from] KeyObjectHandlePrehashedSignAndVerifyError),
+}
+
+#[op2(fast)]
+pub fn op_node_x509_verify(
+ #[cppgc] cert: &Certificate,
+ #[cppgc] public_key: &KeyObjectHandle,
+) -> Result {
+ verify_certificate(cert, public_key)
+}
+
+fn verify_certificate(
+ cert: &Certificate,
+ public_key: &KeyObjectHandle,
+) -> Result {
+ let cert = cert.inner.get().deref();
+ if cert.signature_algorithm != cert.tbs_certificate.signature {
+ return Ok(false);
+ }
+ let tbs = cert.tbs_certificate.as_ref();
+ let signature = cert.signature_value.data.as_ref();
+ let oid = cert.signature_algorithm.algorithm.to_id_string();
+ let (digest_name, digest): (&str, Vec) = match oid.as_str() {
+ "1.2.840.113549.1.1.5" => ("sha1", sha1::Sha1::digest(tbs).to_vec()),
+ "1.2.840.113549.1.1.11" | "1.2.840.10045.4.3.2" => {
+ ("sha256", sha2::Sha256::digest(tbs).to_vec())
+ }
+ "1.2.840.113549.1.1.12" | "1.2.840.10045.4.3.3" => {
+ ("sha384", sha2::Sha384::digest(tbs).to_vec())
+ }
+ "1.2.840.113549.1.1.13" | "1.2.840.10045.4.3.4" => {
+ ("sha512", sha2::Sha512::digest(tbs).to_vec())
+ }
+ _ => return Err(X509VerifyError::UnsupportedSignatureAlgorithm(oid)),
+ };
+ Ok(public_key.verify_prehashed(digest_name, &digest, signature, None, 0)?)
+}
+
// 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(
@@ -278,3 +391,34 @@ pub fn op_node_x509_key_usage(#[cppgc] cert: &Certificate) -> u16 {
key_usage.map(|k| k.flags).unwrap_or(0)
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn x509_certificate_encoding_access_and_signature() {
+ let pem = include_bytes!("testdata/x509-test-cert.pem");
+ let cert = parse_certificate(pem).unwrap();
+ let der = cert.der_bytes();
+
+ assert_eq!(certificate_to_pem(&cert).as_bytes(), pem);
+ assert_eq!(
+ certificate_info_access(&cert).as_deref(),
+ Some("OCSP - URI:http://ocsp.example.test")
+ );
+ assert_eq!(parse_certificate(der).unwrap().der_bytes(), der);
+
+ let public_key = KeyObjectHandle::new_x509_public_key(
+ &cert.inner.get().deref().tbs_certificate.subject_pki,
+ )
+ .unwrap();
+ assert!(verify_certificate(&cert, &public_key).unwrap());
+
+ let mut corrupted = der.to_vec();
+ let last = corrupted.last_mut().unwrap();
+ *last ^= 1;
+ let tampered = parse_certificate(&corrupted).unwrap();
+ assert!(!verify_certificate(&tampered, &public_key).unwrap());
+ }
+}
diff --git a/ext/node/polyfills/internal/crypto/x509.ts b/ext/node/polyfills/internal/crypto/x509.ts
index 699e45a51..623cde84f 100644
--- a/ext/node/polyfills/internal/crypto/x509.ts
+++ b/ext/node/polyfills/internal/crypto/x509.ts
@@ -15,12 +15,18 @@ import {
op_node_x509_get_subject,
op_node_x509_get_valid_from,
op_node_x509_get_valid_to,
+ op_node_x509_info_access,
op_node_x509_key_usage,
op_node_x509_parse,
op_node_x509_public_key,
+ op_node_x509_raw,
+ op_node_x509_to_string,
+ op_node_x509_verify,
} from "ext:core/ops";
import {
+ getKeyObjectHandle,
+ kConsumePublic,
KeyObject,
PublicKeyObject,
} from "ext:deno_node/internal/crypto/keys.ts";
@@ -119,9 +125,7 @@ export class X509Certificate {
}
get infoAccess(): string | undefined {
- notImplemented("crypto.X509Certificate.prototype.infoAccess");
-
- return "";
+ return op_node_x509_info_access(this.#handle);
}
get issuer(): string {
@@ -154,9 +158,7 @@ export class X509Certificate {
}
get raw(): Buffer {
- notImplemented("crypto.X509Certificate.prototype.raw");
-
- return {} as Buffer;
+ return Buffer.from(op_node_x509_raw(this.#handle));
}
get serialNumber(): string {
@@ -180,7 +182,7 @@ export class X509Certificate {
}
toString(): string {
- notImplemented("crypto.X509Certificate.prototype.toString");
+ return op_node_x509_to_string(this.#handle);
}
get validFrom(): string {
@@ -191,8 +193,14 @@ export class X509Certificate {
return op_node_x509_get_valid_to(this.#handle);
}
- verify(_publicKey: KeyObject): boolean {
- notImplemented("crypto.X509Certificate.prototype.verify");
+ verify(publicKey: KeyObject): boolean {
+ if (!(publicKey instanceof KeyObject)) {
+ throw new ERR_INVALID_ARG_TYPE("publicKey", "KeyObject", publicKey);
+ }
+ return op_node_x509_verify(
+ this.#handle,
+ getKeyObjectHandle(publicKey, kConsumePublic),
+ );
}
}