summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDivy Srivastava <dj.srivastava23@gmail.com>2023-04-07 22:54:16 +0530
committerGitHub <noreply@github.com>2023-04-07 22:54:16 +0530
commita0dd0cbcb99ae0e78aeb8493cf7d43b01e0faf55 (patch)
tree2baf48c837ee145798d53f731c4dbaa6fc883ab3
parent5d9172467eee8cdceefa944199459ddd410f7388 (diff)
fix(ext/node): add X509Certificate (#18625)
Towards #18455
-rw-r--r--Cargo.lock128
-rw-r--r--cli/tests/node_compat/config.json1
-rw-r--r--cli/tests/node_compat/test/fixtures/keys/ca1-cert.pem22
-rw-r--r--cli/tests/node_compat/test/parallel/test-crypto-x509.js109
-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
9 files changed, 626 insertions, 40 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 0498b9d2d..2f8b54628 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -110,6 +110,45 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6"
[[package]]
+name = "asn1-rs"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f6fd5ddaf0351dff5b8da21b2fb4ff8e08ddd02857f0bf69c47639106c0fff0"
+dependencies = [
+ "asn1-rs-derive",
+ "asn1-rs-impl",
+ "displaydoc",
+ "nom",
+ "num-traits",
+ "rusticata-macros",
+ "thiserror",
+ "time",
+]
+
+[[package]]
+name = "asn1-rs-derive"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "726535892e8eae7e70657b4c8ea93d26b8553afb1ce617caee529ef96d7dee6c"
+dependencies = [
+ "proc-macro2 1.0.56",
+ "quote 1.0.26",
+ "syn 1.0.109",
+ "synstructure",
+]
+
+[[package]]
+name = "asn1-rs-impl"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2777730b2039ac0f95f093556e61b6d26cebed5393ca6f152717777cec3a42ed"
+dependencies = [
+ "proc-macro2 1.0.56",
+ "quote 1.0.26",
+ "syn 1.0.109",
+]
+
+[[package]]
name = "ast_node"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1108,6 +1147,7 @@ version = "0.34.0"
dependencies = [
"aes",
"cbc",
+ "data-encoding",
"deno_core",
"digest 0.10.6",
"ecb",
@@ -1135,6 +1175,7 @@ dependencies = [
"signature",
"tokio",
"typenum",
+ "x509-parser",
]
[[package]]
@@ -1340,6 +1381,20 @@ dependencies = [
]
[[package]]
+name = "der-parser"
+version = "8.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dbd676fbbab537128ef0278adb5576cf363cff6aa22a7b24effe97347cfab61e"
+dependencies = [
+ "asn1-rs",
+ "displaydoc",
+ "nom",
+ "num-bigint",
+ "num-traits",
+ "rusticata-macros",
+]
+
+[[package]]
name = "derive_more"
version = "0.99.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1389,6 +1444,17 @@ dependencies = [
]
[[package]]
+name = "displaydoc"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3bf95dc3f046b9da4f2d51833c0d3547d8564ef6910f5c1ed130306a75b92886"
+dependencies = [
+ "proc-macro2 1.0.56",
+ "quote 1.0.26",
+ "syn 1.0.109",
+]
+
+[[package]]
name = "dissimilar"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2723,6 +2789,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
[[package]]
+name = "minimal-lexical"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
+
+[[package]]
name = "miniz_oxide"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2813,6 +2885,16 @@ dependencies = [
]
[[package]]
+name = "nom"
+version = "7.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
+dependencies = [
+ "memchr",
+ "minimal-lexical",
+]
+
+[[package]]
name = "notify"
version = "5.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2911,6 +2993,15 @@ dependencies = [
]
[[package]]
+name = "oid-registry"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9bedf36ffb6ba96c2eb7144ef6270557b52e54b20c0a8e1eb2ff99a6c6959bff"
+dependencies = [
+ "asn1-rs",
+]
+
+[[package]]
name = "once_cell"
version = "1.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3602,6 +3693,15 @@ dependencies = [
]
[[package]]
+name = "rusticata-macros"
+version = "4.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632"
+dependencies = [
+ "nom",
+]
+
+[[package]]
name = "rustix"
version = "0.36.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -4745,8 +4845,10 @@ version = "0.3.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890"
dependencies = [
+ "itoa",
"serde",
"time-core",
+ "time-macros",
]
[[package]]
@@ -4756,6 +4858,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd"
[[package]]
+name = "time-macros"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36"
+dependencies = [
+ "time-core",
+]
+
+[[package]]
name = "tinyvec"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -5671,6 +5782,23 @@ dependencies = [
]
[[package]]
+name = "x509-parser"
+version = "0.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bab0c2f54ae1d92f4fcb99c0b7ccf0b1e3451cbd395e5f115ccbdbcb18d4f634"
+dependencies = [
+ "asn1-rs",
+ "data-encoding",
+ "der-parser",
+ "lazy_static",
+ "nom",
+ "oid-registry",
+ "rusticata-macros",
+ "thiserror",
+ "time",
+]
+
+[[package]]
name = "xattr"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/cli/tests/node_compat/config.json b/cli/tests/node_compat/config.json
index fc9a8d131..5ee86a03c 100644
--- a/cli/tests/node_compat/config.json
+++ b/cli/tests/node_compat/config.json
@@ -232,6 +232,7 @@
"test-crypto-hmac.js",
"test-crypto-prime.js",
"test-crypto-secret-keygen.js",
+ "test-crypto-x509.js",
"test-dgram-close-during-bind.js",
"test-dgram-close-signal.js",
"test-diagnostics-channel-has-subscribers.js",
diff --git a/cli/tests/node_compat/test/fixtures/keys/ca1-cert.pem b/cli/tests/node_compat/test/fixtures/keys/ca1-cert.pem
new file mode 100644
index 000000000..4ba203b58
--- /dev/null
+++ b/cli/tests/node_compat/test/fixtures/keys/ca1-cert.pem
@@ -0,0 +1,22 @@
+-----BEGIN CERTIFICATE-----
+MIIDlDCCAnygAwIBAgIUSrFsjf1qfQ0t/KvfnEsOksatAikwDQYJKoZIhvcNAQEL
+BQAwejELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQswCQYDVQQHDAJTRjEPMA0G
+A1UECgwGSm95ZW50MRAwDgYDVQQLDAdOb2RlLmpzMQwwCgYDVQQDDANjYTExIDAe
+BgkqhkiG9w0BCQEWEXJ5QHRpbnljbG91ZHMub3JnMCAXDTIyMDkwMzIxNDAzN1oY
+DzIyOTYwNjE3MjE0MDM3WjB6MQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ0ExCzAJ
+BgNVBAcMAlNGMQ8wDQYDVQQKDAZKb3llbnQxEDAOBgNVBAsMB05vZGUuanMxDDAK
+BgNVBAMMA2NhMTEgMB4GCSqGSIb3DQEJARYRcnlAdGlueWNsb3Vkcy5vcmcwggEi
+MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDNvf4OGGep+ak+4DNjbuNgy0S/
+AZPxahEFp4gpbcvsi9YLOPZ31qpilQeQf7d27scIZ02Qx1YBAzljxELB8H/ZxuYS
+cQK0s+DNP22xhmgwMWznO7TezkHP5ujN2UkbfbUpfUxGFgncXeZf9wR7yFWppeHi
+RWNBOgsvY7sTrS12kXjWGjqntF7xcEDHc7h+KyF6ZjVJZJCnP6pJEQ+rUjd51eCZ
+Xt4WjowLnQiCS1VKzXiP83a++Ma1BKKkUitTR112/Uwd5eGoiByhmLzb/BhxnHJN
+07GXjhlMItZRm/jfbZsx1mwnNOO3tx4r08l+DaqkinIadvazs+1ugCaKQn8xAgMB
+AAGjEDAOMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAFqG0RXURDam
+56x5accdg9sY5zEGP5VQhkK3ZDc2NyNNa25rwvrjCpO+e0OSwKAmm4aX6iIf2woY
+wF2f9swWYzxn9CG4fDlUA8itwlnHxupeL4fGMTYb72vf31plUXyBySRsTwHwBloc
+F7KvAZpYYKN9EMH1S/267By6H2I33BT/Ethv//n8dSfmuCurR1kYRaiOC4PVeyFk
+B3sj8TtolrN0y/nToWUhmKiaVFnDx3odQ00yhmxR3t21iB7yDkko6D8Vf2dVC4j/
+YYBVprXGlTP/hiYRLDoP20xKOYznx5cvHPJ9p+lVcOZUJsJj/Iy750+2n5UiBmXt
+lz88C25ucKA=
+-----END CERTIFICATE----- \ No newline at end of file
diff --git a/cli/tests/node_compat/test/parallel/test-crypto-x509.js b/cli/tests/node_compat/test/parallel/test-crypto-x509.js
new file mode 100644
index 000000000..eeee2f7d7
--- /dev/null
+++ b/cli/tests/node_compat/test/parallel/test-crypto-x509.js
@@ -0,0 +1,109 @@
+// deno-fmt-ignore-file
+// deno-lint-ignore-file
+
+// Copyright Joyent and Node contributors. All rights reserved. MIT license.
+
+'use strict';
+const common = require('../common');
+
+if (!common.hasCrypto)
+ common.skip('missing crypto');
+
+const {
+ X509Certificate,
+} = require('crypto');
+
+const assert = require('assert');
+const fixtures = require('../common/fixtures');
+const { readFileSync } = require('fs');
+
+const cert = readFileSync(fixtures.path('keys', 'agent1-cert.pem'));
+const ca = readFileSync(fixtures.path('keys', 'ca1-cert.pem'));
+
+[1, {}, false, null].forEach((i) => {
+ assert.throws(() => new X509Certificate(i), {
+ code: 'ERR_INVALID_ARG_TYPE'
+ });
+});
+
+const subjectCheck = `C=US
+ST=CA
+L=SF
+O=Joyent
+OU=Node.js
+CN=agent1
+Email=ry@tinyclouds.org`;
+
+const issuerCheck = `C=US
+ST=CA
+L=SF
+O=Joyent
+OU=Node.js
+CN=ca1
+Email=ry@tinyclouds.org`;
+
+let infoAccessCheck = `OCSP - URI:http://ocsp.nodejs.org/
+CA Issuers - URI:http://ca.nodejs.org/ca.cert`;
+if (!common.hasOpenSSL3)
+ infoAccessCheck += '\n';
+
+const der = Buffer.from(
+ '308203e8308202d0a0030201020214147d36c1c2f74206de9fab5f2226d78adb00a42630' +
+ '0d06092a864886f70d01010b0500307a310b3009060355040613025553310b3009060355' +
+ '04080c024341310b300906035504070c025346310f300d060355040a0c064a6f79656e74' +
+ '3110300e060355040b0c074e6f64652e6a73310c300a06035504030c036361313120301e' +
+ '06092a864886f70d010901161172794074696e79636c6f7564732e6f72673020170d3232' +
+ '303930333231343033375a180f32323936303631373231343033375a307d310b30090603' +
+ '55040613025553310b300906035504080c024341310b300906035504070c025346310f30' +
+ '0d060355040a0c064a6f79656e743110300e060355040b0c074e6f64652e6a73310f300d' +
+ '06035504030c066167656e74313120301e06092a864886f70d010901161172794074696e' +
+ '79636c6f7564732e6f726730820122300d06092a864886f70d01010105000382010f0030' +
+ '82010a0282010100d456320afb20d3827093dc2c4284ed04dfbabd56e1ddae529e28b790' +
+ 'cd4256db273349f3735ffd337c7a6363ecca5a27b7f73dc7089a96c6d886db0c62388f1c' +
+ 'dd6a963afcd599d5800e587a11f908960f84ed50ba25a28303ecda6e684fbe7baedc9ce8' +
+ '801327b1697af25097cee3f175e400984c0db6a8eb87be03b4cf94774ba56fffc8c63c68' +
+ 'd6adeb60abbe69a7b14ab6a6b9e7baa89b5adab8eb07897c07f6d4fa3d660dff574107d2' +
+ '8e8f63467a788624c574197693e959cea1362ffae1bba10c8c0d88840abfef103631b2e8' +
+ 'f5c39b5548a7ea57e8a39f89291813f45a76c448033a2b7ed8403f4baa147cf35e2d2554' +
+ 'aa65ce49695797095bf4dc6b0203010001a361305f305d06082b06010505070101045130' +
+ '4f302306082b060105050730018617687474703a2f2f6f6373702e6e6f64656a732e6f72' +
+ '672f302806082b06010505073002861c687474703a2f2f63612e6e6f64656a732e6f7267' +
+ '2f63612e63657274300d06092a864886f70d01010b05000382010100c3349810632ccb7d' +
+ 'a585de3ed51e34ed154f0f7215608cf2701c00eda444dc2427072c8aca4da6472c1d9e68' +
+ 'f177f99a90a8b5dbf3884586d61cb1c14ea7016c8d38b70d1b46b42947db30edc1e9961e' +
+ 'd46c0f0e35da427bfbe52900771817e733b371adf19e12137235141a34347db0dfc05579' +
+ '8b1f269f3bdf5e30ce35d1339d56bb3c570de9096215433047f87ca42447b44e7e6b5d0e' +
+ '48f7894ab186f85b6b1a74561b520952fea888617f32f582afce1111581cd63efcc68986' +
+ '00d248bb684dedb9c3d6710c38de9e9bc21f9c3394b729d5f707d64ea890603e5989f8fa' +
+ '59c19ad1a00732e7adc851b89487cc00799dde068aa64b3b8fd976e8bc113ef2',
+ 'hex');
+
+{
+ const x509 = new X509Certificate(cert);
+
+ assert(!x509.ca);
+ assert.strictEqual(x509.subject, subjectCheck);
+ assert.strictEqual(x509.subjectAltName, undefined);
+ assert.strictEqual(x509.issuer, issuerCheck);
+ assert.strictEqual(x509.validFrom, 'Sep 3 21:40:37 2022 +00:00');
+ assert.strictEqual(x509.validTo, 'Jun 17 21:40:37 2296 +00:00');
+ assert.strictEqual(
+ x509.fingerprint,
+ '8B:89:16:C4:99:87:D2:13:1A:64:94:36:38:A5:32:01:F0:95:3B:53');
+ assert.strictEqual(
+ x509.fingerprint256,
+ '2C:62:59:16:91:89:AB:90:6A:3E:98:88:A6:D3:C5:58:58:6C:AE:FF:9C:33:' +
+ '22:7C:B6:77:D3:34:E7:53:4B:05'
+ );
+ assert.strictEqual(
+ x509.fingerprint512,
+ '0B:6F:D0:4D:6B:22:53:99:66:62:51:2D:2C:96:F2:58:3F:95:1C:CC:4C:44:' +
+ '9D:B5:59:AA:AD:A8:F6:2A:24:8A:BB:06:A5:26:42:52:30:A3:37:61:30:A9:' +
+ '5A:42:63:E0:21:2F:D6:70:63:07:96:6F:27:A7:78:12:08:02:7A:8B'
+ );
+ assert.strictEqual(x509.keyUsage, undefined);
+ assert.strictEqual(x509.serialNumber, '147D36C1C2F74206DE9FAB5F2226D78ADB00A426');
+
+ assert.strictEqual(x509.checkEmail('ry@tinyclouds.org'), 'ry@tinyclouds.org');
+ assert.strictEqual(x509.checkEmail('sally@example.com'), undefined);
+} \ No newline at end of file
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 {