summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock1
-rw-r--r--Cargo.toml1
-rw-r--r--ext/crypto/Cargo.toml2
-rw-r--r--ext/node/Cargo.toml1
-rw-r--r--ext/node/lib.rs1
-rw-r--r--ext/node/ops/crypto/mod.rs108
-rw-r--r--ext/node/polyfills/internal/crypto/keys.ts24
-rw-r--r--tests/unit_node/crypto/crypto_key_test.ts28
-rw-r--r--tests/unit_node/testdata/ec_prime256v1_public.pem4
9 files changed, 164 insertions, 6 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 001649627..bedb88752 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1687,6 +1687,7 @@ dependencies = [
"sha2",
"signature",
"simd-json",
+ "spki",
"tokio",
"typenum",
"url",
diff --git a/Cargo.toml b/Cargo.toml
index 0562fd919..38f04c5ae 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -163,6 +163,7 @@ signature = "2.1"
slab = "0.4"
smallvec = "1.8"
socket2 = { version = "0.5.3", features = ["all"] }
+spki = "0.7.2"
tar = "=0.4.40"
tempfile = "3.4.0"
termcolor = "1.1.3"
diff --git a/ext/crypto/Cargo.toml b/ext/crypto/Cargo.toml
index 89447cb8a..7369e0a84 100644
--- a/ext/crypto/Cargo.toml
+++ b/ext/crypto/Cargo.toml
@@ -39,7 +39,7 @@ serde_bytes.workspace = true
sha1 = { version = "0.10.6", features = ["oid"] }
sha2.workspace = true
signature.workspace = true
-spki = "0.7.2"
+spki.workspace = true
tokio.workspace = true
uuid.workspace = true
x25519-dalek = "2.0.0"
diff --git a/ext/node/Cargo.toml b/ext/node/Cargo.toml
index 053286053..8e3e695d6 100644
--- a/ext/node/Cargo.toml
+++ b/ext/node/Cargo.toml
@@ -68,6 +68,7 @@ sha-1 = "0.10.0"
sha2.workspace = true
signature.workspace = true
simd-json = "0.13.4"
+spki.workspace = true
tokio.workspace = true
typenum = "1.15.0"
url.workspace = true
diff --git a/ext/node/lib.rs b/ext/node/lib.rs
index f9553a038..f8c9dfc88 100644
--- a/ext/node/lib.rs
+++ b/ext/node/lib.rs
@@ -329,6 +329,7 @@ deno_core::extension!(deno_node,
ops::require::op_require_break_on_next_statement,
ops::util::op_node_guess_handle_type,
ops::crypto::op_node_create_private_key,
+ ops::crypto::op_node_create_public_key,
ops::ipc::op_node_child_ipc_pipe,
ops::ipc::op_node_ipc_write,
ops::ipc::op_node_ipc_read,
diff --git a/ext/node/ops/crypto/mod.rs b/ext/node/ops/crypto/mod.rs
index 8db562eef..c2a9b1ab7 100644
--- a/ext/node/ops/crypto/mod.rs
+++ b/ext/node/ops/crypto/mod.rs
@@ -20,6 +20,7 @@ use rand::distributions::Uniform;
use rand::thread_rng;
use rand::Rng;
use rsa::pkcs1::DecodeRsaPrivateKey;
+use rsa::pkcs1::DecodeRsaPublicKey;
use rsa::pkcs8;
use rsa::pkcs8::der::asn1;
use rsa::pkcs8::der::Decode;
@@ -1459,3 +1460,110 @@ pub fn op_node_create_private_key(
_ => Err(type_error("Unsupported algorithm")),
}
}
+
+fn parse_public_key(
+ key: &[u8],
+ format: &str,
+ type_: &str,
+) -> Result<pkcs8::Document, AnyError> {
+ match format {
+ "pem" => {
+ let (label, doc) =
+ pkcs8::Document::from_pem(std::str::from_utf8(key).unwrap())?;
+ if label != "PUBLIC KEY" {
+ return Err(type_error("Invalid PEM label"));
+ }
+ Ok(doc)
+ }
+ "der" => {
+ match type_ {
+ "pkcs1" => pkcs8::Document::from_pkcs1_der(key)
+ .map_err(|_| type_error("Invalid PKCS1 public key")),
+ // TODO(@iuioiua): spki type
+ _ => Err(type_error(format!("Unsupported key type: {}", type_))),
+ }
+ }
+ _ => Err(type_error(format!("Unsupported key format: {}", format))),
+ }
+}
+
+#[op2]
+#[serde]
+pub fn op_node_create_public_key(
+ #[buffer] key: &[u8],
+ #[string] format: &str,
+ #[string] type_: &str,
+) -> Result<AsymmetricKeyDetails, AnyError> {
+ let doc = parse_public_key(key, format, type_)?;
+ let pk_info = spki::SubjectPublicKeyInfoRef::try_from(doc.as_bytes())?;
+
+ let alg = pk_info.algorithm.oid;
+
+ match alg {
+ RSA_ENCRYPTION_OID => {
+ let public_key = rsa::pkcs1::RsaPublicKey::from_der(
+ pk_info.subject_public_key.raw_bytes(),
+ )?;
+ let modulus_length = public_key.modulus.as_bytes().len() * 8;
+
+ Ok(AsymmetricKeyDetails::Rsa {
+ modulus_length,
+ public_exponent: BigInt::from_bytes_be(
+ num_bigint::Sign::Plus,
+ public_key.public_exponent.as_bytes(),
+ )
+ .into(),
+ })
+ }
+ RSASSA_PSS_OID => {
+ let params = PssPrivateKeyParameters::try_from(
+ pk_info
+ .algorithm
+ .parameters
+ .ok_or_else(|| type_error("Malformed parameters".to_string()))?,
+ )
+ .map_err(|_| type_error("Malformed parameters".to_string()))?;
+
+ let hash_alg = params.hash_algorithm;
+ let hash_algorithm = match hash_alg.oid {
+ ID_SHA1_OID => "sha1",
+ ID_SHA256_OID => "sha256",
+ ID_SHA384_OID => "sha384",
+ ID_SHA512_OID => "sha512",
+ _ => return Err(type_error("Unsupported hash algorithm")),
+ };
+
+ let public_key = rsa::pkcs1::RsaPublicKey::from_der(
+ pk_info.subject_public_key.raw_bytes(),
+ )?;
+ let modulus_length = public_key.modulus.as_bytes().len() * 8;
+ Ok(AsymmetricKeyDetails::RsaPss {
+ modulus_length,
+ public_exponent: BigInt::from_bytes_be(
+ num_bigint::Sign::Plus,
+ public_key.public_exponent.as_bytes(),
+ )
+ .into(),
+ hash_algorithm: hash_algorithm.to_string(),
+ salt_length: params.salt_length,
+ })
+ }
+ EC_OID => {
+ let named_curve = pk_info
+ .algorithm
+ .parameters_oid()
+ .map_err(|_| type_error("malformed parameters"))?;
+ let named_curve = match named_curve {
+ ID_SECP256R1_OID => "p256",
+ ID_SECP384R1_OID => "p384",
+ ID_SECP521R1_OID => "p521",
+ _ => return Err(type_error("Unsupported named curve")),
+ };
+
+ Ok(AsymmetricKeyDetails::Ec {
+ named_curve: named_curve.to_string(),
+ })
+ }
+ _ => Err(type_error("Unsupported algorithm")),
+ }
+}
diff --git a/ext/node/polyfills/internal/crypto/keys.ts b/ext/node/polyfills/internal/crypto/keys.ts
index ab753582f..33034d824 100644
--- a/ext/node/polyfills/internal/crypto/keys.ts
+++ b/ext/node/polyfills/internal/crypto/keys.ts
@@ -4,7 +4,10 @@
// TODO(petamoriken): enable prefer-primordials for node polyfills
// deno-lint-ignore-file prefer-primordials
-import { op_node_create_private_key } from "ext:core/ops";
+import {
+ op_node_create_private_key,
+ op_node_create_public_key,
+} from "ext:core/ops";
import {
kHandle,
@@ -239,9 +242,12 @@ export function createPrivateKey(
}
export function createPublicKey(
- _key: PublicKeyInput | string | Buffer | KeyObject | JsonWebKeyInput,
-): KeyObject {
- notImplemented("crypto.createPublicKey");
+ key: PublicKeyInput | string | Buffer | JsonWebKeyInput,
+): PublicKeyObject {
+ const { data, format, type } = prepareAsymmetricKey(key);
+ const details = op_node_create_public_key(data, format, type);
+ const handle = setOwnedKey(copyBuffer(data));
+ return new PublicKeyObject(handle, details);
}
function getKeyTypes(allowKeyObject: boolean, bufferOnly = false) {
@@ -358,6 +364,16 @@ class PrivateKeyObject extends AsymmetricKeyObject {
}
}
+class PublicKeyObject extends AsymmetricKeyObject {
+ constructor(handle: unknown, details: unknown) {
+ super("public", handle, details);
+ }
+
+ export(_options: unknown) {
+ notImplemented("crypto.PublicKeyObject.prototype.export");
+ }
+}
+
export function setOwnedKey(key: Uint8Array): unknown {
const handle = {};
KEY_STORE.set(handle, key);
diff --git a/tests/unit_node/crypto/crypto_key_test.ts b/tests/unit_node/crypto/crypto_key_test.ts
index 6dae79336..9b8224cd1 100644
--- a/tests/unit_node/crypto/crypto_key_test.ts
+++ b/tests/unit_node/crypto/crypto_key_test.ts
@@ -2,7 +2,9 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
import {
+ createHmac,
createPrivateKey,
+ createPublicKey,
createSecretKey,
generateKeyPair,
generateKeyPairSync,
@@ -12,7 +14,6 @@ import {
import { promisify } from "node:util";
import { Buffer } from "node:buffer";
import { assertEquals, assertThrows } from "@std/assert/mod.ts";
-import { createHmac } from "node:crypto";
const RUN_SLOW_TESTS = Deno.env.get("SLOW_TESTS") === "1";
@@ -240,3 +241,28 @@ Deno.test("createPrivateKey ec", function () {
assertEquals(key.asymmetricKeyType, "ec");
assertEquals(key.asymmetricKeyDetails?.namedCurve, "p256");
});
+
+const rsaPublicKey = Deno.readTextFileSync(
+ new URL("../testdata/rsa_public.pem", import.meta.url),
+);
+
+Deno.test("createPublicKey() RSA", () => {
+ const key = createPublicKey(rsaPublicKey);
+ assertEquals(key.type, "public");
+ assertEquals(key.asymmetricKeyType, "rsa");
+ assertEquals(key.asymmetricKeyDetails?.modulusLength, 2048);
+ assertEquals(key.asymmetricKeyDetails?.publicExponent, 65537n);
+});
+
+// openssl ecparam -name prime256v1 -genkey -noout -out a.pem
+// openssl ec -in a.pem -pubout -out b.pem
+const ecPublicKey = Deno.readTextFileSync(
+ new URL("../testdata/ec_prime256v1_public.pem", import.meta.url),
+);
+
+Deno.test("createPublicKey() EC", function () {
+ const key = createPublicKey(ecPublicKey);
+ assertEquals(key.type, "public");
+ assertEquals(key.asymmetricKeyType, "ec");
+ assertEquals(key.asymmetricKeyDetails?.namedCurve, "p256");
+});
diff --git a/tests/unit_node/testdata/ec_prime256v1_public.pem b/tests/unit_node/testdata/ec_prime256v1_public.pem
new file mode 100644
index 000000000..0b8b66dbe
--- /dev/null
+++ b/tests/unit_node/testdata/ec_prime256v1_public.pem
@@ -0,0 +1,4 @@
+-----BEGIN PUBLIC KEY-----
+MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEvk2xDvFKR/q/jqE5pjFk0afU5Ybe
+83GsRx0PBXXFVE4yO1vE7ftaOp9Jqt3edpVyXIEyyrilnonNKITGxkB2Uw==
+-----END PUBLIC KEY-----