summaryrefslogtreecommitdiff
path: root/ext
diff options
context:
space:
mode:
authorDivy Srivastava <dj.srivastava23@gmail.com>2022-09-27 17:43:42 +0530
committerGitHub <noreply@github.com>2022-09-27 17:43:42 +0530
commitf02f2425d5e5947a3cc5a95775c8cae5a6e82881 (patch)
tree06fa5a08cd27d5bb07b9736d072c3f1fb9e556dc /ext
parentf3dd13730c592b76778fa047a098214bc1934216 (diff)
feat(ext/crypto): add x25519 and Ed25519 CFRG curves (#14119)
Diffstat (limited to 'ext')
-rw-r--r--ext/crypto/00_crypto.js686
-rw-r--r--ext/crypto/Cargo.toml4
-rw-r--r--ext/crypto/ed25519.rs128
-rw-r--r--ext/crypto/lib.rs22
-rw-r--r--ext/crypto/x25519.rs89
5 files changed, 928 insertions, 1 deletions
diff --git a/ext/crypto/00_crypto.js b/ext/crypto/00_crypto.js
index d00f29e7c..41c85bdd1 100644
--- a/ext/crypto/00_crypto.js
+++ b/ext/crypto/00_crypto.js
@@ -98,18 +98,22 @@
"AES-GCM": "AesKeyGenParams",
"AES-KW": "AesKeyGenParams",
"HMAC": "HmacKeyGenParams",
+ "X25519": null,
+ "Ed25519": null,
},
"sign": {
"RSASSA-PKCS1-v1_5": null,
"RSA-PSS": "RsaPssParams",
"ECDSA": "EcdsaParams",
"HMAC": null,
+ "Ed25519": null,
},
"verify": {
"RSASSA-PKCS1-v1_5": null,
"RSA-PSS": "RsaPssParams",
"ECDSA": "EcdsaParams",
"HMAC": null,
+ "Ed25519": null,
},
"importKey": {
"RSASSA-PKCS1-v1_5": "RsaHashedImportParams",
@@ -124,11 +128,13 @@
"AES-CBC": null,
"AES-GCM": null,
"AES-KW": null,
+ "X25519": null,
},
"deriveBits": {
"HKDF": "HkdfParams",
"PBKDF2": "Pbkdf2Params",
"ECDH": "EcdhKeyDeriveParams",
+ "X25519": "EcdhKeyDeriveParams",
},
"encrypt": {
"RSA-OAEP": "RsaOaepParams",
@@ -834,6 +840,26 @@
return signature.buffer;
}
+ case "Ed25519": {
+ // 1.
+ if (key[_type] !== "private") {
+ throw new DOMException(
+ "Key type not supported",
+ "InvalidAccessError",
+ );
+ }
+
+ // https://briansmith.org/rustdoc/src/ring/ec/curve25519/ed25519/signing.rs.html#260
+ const SIGNATURE_LEN = 32 * 2; // ELEM_LEN + SCALAR_LEN
+ const signature = new Uint8Array(SIGNATURE_LEN);
+ if (!ops.op_sign_ed25519(keyData, data, signature)) {
+ throw new DOMException(
+ "Failed to sign",
+ "OperationError",
+ );
+ }
+ return signature.buffer;
+ }
}
throw new TypeError("unreachable");
@@ -955,6 +981,22 @@
["wrapKey", "unwrapKey"],
);
}
+ case "X25519": {
+ return importKeyX25519(
+ format,
+ keyData,
+ extractable,
+ keyUsages,
+ );
+ }
+ case "Ed25519": {
+ return importKeyEd25519(
+ format,
+ keyData,
+ extractable,
+ keyUsages,
+ );
+ }
default:
throw new DOMException("Not implemented", "NotSupportedError");
}
@@ -1003,6 +1045,10 @@
result = exportKeyEC(format, key, innerKey);
break;
}
+ case "Ed25519": {
+ result = exportKeyEd25519(format, key, innerKey);
+ break;
+ }
case "AES-CTR":
case "AES-CBC":
case "AES-GCM":
@@ -1283,6 +1329,17 @@
namedCurve: key[_algorithm].namedCurve,
}, data);
}
+ case "Ed25519": {
+ // 1.
+ if (key[_type] !== "public") {
+ throw new DOMException(
+ "Key type not supported",
+ "InvalidAccessError",
+ );
+ }
+
+ return ops.op_verify_ed25519(keyData, data, signature);
+ }
}
throw new TypeError("unreachable");
@@ -1611,7 +1668,6 @@
const usages = keyUsages;
const normalizedAlgorithm = normalizeAlgorithm(algorithm, "generateKey");
-
const result = await generateKey(
normalizedAlgorithm,
extractable,
@@ -1910,6 +1966,95 @@
return generateKeyAES(normalizedAlgorithm, extractable, usages);
}
+ case "X25519": {
+ if (
+ ArrayPrototypeFind(
+ usages,
+ (u) => !ArrayPrototypeIncludes(["deriveKey", "deriveBits"], u),
+ ) !== undefined
+ ) {
+ throw new DOMException("Invalid key usages", "SyntaxError");
+ }
+ const privateKeyData = new Uint8Array(32);
+ const publicKeyData = new Uint8Array(32);
+ ops.op_generate_x25519_keypair(privateKeyData, publicKeyData);
+
+ const handle = {};
+ WeakMapPrototypeSet(KEY_STORE, handle, privateKeyData);
+
+ const publicHandle = {};
+ WeakMapPrototypeSet(KEY_STORE, handle, publicKeyData);
+
+ const algorithm = {
+ name: algorithmName,
+ };
+
+ const publicKey = constructKey(
+ "public",
+ true,
+ usageIntersection(usages, []),
+ algorithm,
+ publicHandle,
+ );
+
+ const privateKey = constructKey(
+ "private",
+ extractable,
+ usageIntersection(usages, ["deriveKey", "deriveBits"]),
+ algorithm,
+ handle,
+ );
+
+ return { publicKey, privateKey };
+ }
+ case "Ed25519": {
+ if (
+ ArrayPrototypeFind(
+ usages,
+ (u) => !ArrayPrototypeIncludes(["sign", "verify"], u),
+ ) !== undefined
+ ) {
+ throw new DOMException("Invalid key usages", "SyntaxError");
+ }
+
+ const ED25519_SEED_LEN = 32;
+ const ED25519_PUBLIC_KEY_LEN = 32;
+ const privateKeyData = new Uint8Array(ED25519_SEED_LEN);
+ const publicKeyData = new Uint8Array(ED25519_PUBLIC_KEY_LEN);
+ if (
+ !ops.op_generate_ed25519_keypair(privateKeyData, publicKeyData)
+ ) {
+ throw new DOMException("Failed to generate key", "OperationError");
+ }
+
+ const handle = {};
+ WeakMapPrototypeSet(KEY_STORE, handle, privateKeyData);
+
+ const publicHandle = {};
+ WeakMapPrototypeSet(KEY_STORE, handle, publicKeyData);
+
+ const algorithm = {
+ name: algorithmName,
+ };
+
+ const publicKey = constructKey(
+ "public",
+ true,
+ usageIntersection(usages, ["verify"]),
+ algorithm,
+ publicHandle,
+ );
+
+ const privateKey = constructKey(
+ "private",
+ extractable,
+ usageIntersection(usages, ["sign"]),
+ algorithm,
+ handle,
+ );
+
+ return { publicKey, privateKey };
+ }
case "HMAC": {
// 1.
if (
@@ -1967,6 +2112,439 @@
}
}
+ function importKeyEd25519(
+ format,
+ keyData,
+ extractable,
+ keyUsages,
+ ) {
+ switch (format) {
+ case "raw": {
+ // 1.
+ if (
+ ArrayPrototypeFind(
+ keyUsages,
+ (u) => !ArrayPrototypeIncludes(["verify"], u),
+ ) !== undefined
+ ) {
+ throw new DOMException("Invalid key usages", "SyntaxError");
+ }
+
+ const handle = {};
+ WeakMapPrototypeSet(KEY_STORE, handle, keyData);
+
+ // 2-3.
+ const algorithm = {
+ name: "Ed25519",
+ };
+
+ // 4-6.
+ return constructKey(
+ "public",
+ extractable,
+ [],
+ algorithm,
+ handle,
+ );
+ }
+ case "spki": {
+ // 1.
+ if (
+ ArrayPrototypeFind(
+ keyUsages,
+ (u) => !ArrayPrototypeIncludes(["verify"], u),
+ ) !== undefined
+ ) {
+ throw new DOMException("Invalid key usages", "SyntaxError");
+ }
+
+ const publicKeyData = new Uint8Array(32);
+ if (!ops.op_import_spki_ed25519(keyData, publicKeyData)) {
+ throw new DOMException("Invalid key data", "DataError");
+ }
+
+ const handle = {};
+ WeakMapPrototypeSet(KEY_STORE, handle, publicKeyData);
+
+ const algorithm = {
+ name: "Ed25519",
+ };
+
+ return constructKey(
+ "public",
+ extractable,
+ [],
+ algorithm,
+ handle,
+ );
+ }
+ case "pkcs8": {
+ // 1.
+ if (
+ ArrayPrototypeFind(
+ keyUsages,
+ (u) => !ArrayPrototypeIncludes(["sign"], u),
+ ) !== undefined
+ ) {
+ throw new DOMException("Invalid key usages", "SyntaxError");
+ }
+
+ const privateKeyData = new Uint8Array(32);
+ if (!ops.op_import_pkcs8_ed25519(keyData, privateKeyData)) {
+ throw new DOMException("Invalid key data", "DataError");
+ }
+
+ const handle = {};
+ WeakMapPrototypeSet(KEY_STORE, handle, privateKeyData);
+
+ const algorithm = {
+ name: "Ed25519",
+ };
+
+ return constructKey(
+ "private",
+ extractable,
+ [],
+ algorithm,
+ handle,
+ );
+ }
+ case "jwk": {
+ // 1.
+ const jwk = keyData;
+
+ // 2.
+ if (jwk.d !== undefined) {
+ if (
+ ArrayPrototypeFind(
+ keyUsages,
+ (u) =>
+ !ArrayPrototypeIncludes(
+ ["sign"],
+ u,
+ ),
+ ) !== undefined
+ ) {
+ throw new DOMException("Invalid key usages", "SyntaxError");
+ }
+ } else {
+ if (
+ ArrayPrototypeFind(
+ keyUsages,
+ (u) =>
+ !ArrayPrototypeIncludes(
+ ["verify"],
+ u,
+ ),
+ ) !== undefined
+ ) {
+ throw new DOMException("Invalid key usages", "SyntaxError");
+ }
+ }
+
+ // 3.
+ if (jwk.kty !== "OKP") {
+ throw new DOMException("Invalid key type", "DataError");
+ }
+
+ // 4.
+ if (jwk.crv !== "Ed25519") {
+ throw new DOMException("Invalid curve", "DataError");
+ }
+
+ // 5.
+ if (jwk.alg !== undefined && jwk.alg !== "EdDSA") {
+ throw new DOMException("Invalid algorithm", "DataError");
+ }
+
+ // 6.
+ if (
+ keyUsages.length > 0 && jwk.use !== undefined && jwk.use !== "sig"
+ ) {
+ throw new DOMException("Invalid key usage", "DataError");
+ }
+
+ // 7.
+ if (jwk.key_ops !== undefined) {
+ if (
+ ArrayPrototypeFind(
+ jwk.key_ops,
+ (u) => !ArrayPrototypeIncludes(recognisedUsages, u),
+ ) !== undefined
+ ) {
+ throw new DOMException(
+ "'key_ops' property of JsonWebKey is invalid",
+ "DataError",
+ );
+ }
+
+ if (
+ !ArrayPrototypeEvery(
+ jwk.key_ops,
+ (u) => ArrayPrototypeIncludes(keyUsages, u),
+ )
+ ) {
+ throw new DOMException(
+ "'key_ops' property of JsonWebKey is invalid",
+ "DataError",
+ );
+ }
+ }
+
+ // 8.
+ if (jwk.ext !== undefined && jwk.ext === false && extractable) {
+ throw new DOMException("Invalid key extractability", "DataError");
+ }
+
+ // 9.
+ if (jwk.d !== undefined) {
+ // https://www.rfc-editor.org/rfc/rfc8037#section-2
+ const privateKeyData = ops.op_crypto_base64url(jwk.d);
+
+ const handle = {};
+ WeakMapPrototypeSet(KEY_STORE, handle, privateKeyData);
+
+ const algorithm = {
+ name: "Ed25519",
+ };
+
+ return constructKey(
+ "private",
+ extractable,
+ [],
+ algorithm,
+ handle,
+ );
+ } else {
+ // https://www.rfc-editor.org/rfc/rfc8037#section-2
+ const publicKeyData = ops.op_crypto_base64url(jwk.d);
+
+ const handle = {};
+ WeakMapPrototypeSet(KEY_STORE, handle, publicKeyData);
+
+ const algorithm = {
+ name: "Ed25519",
+ };
+
+ return constructKey(
+ "public",
+ extractable,
+ [],
+ algorithm,
+ handle,
+ );
+ }
+ }
+ default:
+ throw new DOMException("Not implemented", "NotSupportedError");
+ }
+ }
+
+ function importKeyX25519(
+ format,
+ keyData,
+ extractable,
+ keyUsages,
+ ) {
+ switch (format) {
+ case "raw": {
+ // 1.
+ if (keyUsages.length > 0) {
+ throw new DOMException("Invalid key usages", "SyntaxError");
+ }
+
+ const handle = {};
+ WeakMapPrototypeSet(KEY_STORE, handle, keyData);
+
+ // 2-3.
+ const algorithm = {
+ name: "X25519",
+ };
+
+ // 4-6.
+ return constructKey(
+ "public",
+ extractable,
+ [],
+ algorithm,
+ handle,
+ );
+ }
+ case "spki": {
+ // 1.
+ if (keyUsages.length > 0) {
+ throw new DOMException("Invalid key usages", "SyntaxError");
+ }
+
+ const publicKeyData = new Uint8Array(32);
+ if (!ops.op_import_spki_x25519(keyData, publicKeyData)) {
+ throw new DOMException("Invalid key data", "DataError");
+ }
+
+ const handle = {};
+ WeakMapPrototypeSet(KEY_STORE, handle, publicKeyData);
+
+ const algorithm = {
+ name: "X25519",
+ };
+
+ return constructKey(
+ "public",
+ extractable,
+ [],
+ algorithm,
+ handle,
+ );
+ }
+ case "pkcs8": {
+ // 1.
+ if (
+ ArrayPrototypeFind(
+ keyUsages,
+ (u) => !ArrayPrototypeIncludes(["deriveKey", "deriveBits"], u),
+ ) !== undefined
+ ) {
+ throw new DOMException("Invalid key usages", "SyntaxError");
+ }
+
+ const privateKeyData = new Uint8Array(32);
+ if (!ops.op_import_pkcs8_x25519(keyData, privateKeyData)) {
+ throw new DOMException("Invalid key data", "DataError");
+ }
+
+ const handle = {};
+ WeakMapPrototypeSet(KEY_STORE, handle, privateKeyData);
+
+ const algorithm = {
+ name: "X25519",
+ };
+
+ return constructKey(
+ "private",
+ extractable,
+ [],
+ algorithm,
+ handle,
+ );
+ }
+ case "jwk": {
+ // 1.
+ const jwk = keyData;
+
+ // 2.
+ if (jwk.d !== undefined) {
+ if (
+ ArrayPrototypeFind(
+ keyUsages,
+ (u) =>
+ !ArrayPrototypeIncludes(
+ SUPPORTED_KEY_USAGES["X25519"].private,
+ u,
+ ),
+ ) !== undefined
+ ) {
+ throw new DOMException("Invalid key usages", "SyntaxError");
+ }
+ }
+
+ // 3.
+ if (jwk.d === undefined && keyUsages.length > 0) {
+ throw new DOMException("Invalid key usages", "SyntaxError");
+ }
+
+ // 4.
+ if (jwk.kty !== "OKP") {
+ throw new DOMException("Invalid key type", "DataError");
+ }
+
+ // 5.
+ if (jwk.crv !== "X25519") {
+ throw new DOMException("Invalid curve", "DataError");
+ }
+
+ // 6.
+ if (keyUsages.length > 0 && jwk.use !== undefined) {
+ if (jwk.use !== "enc") {
+ throw new DOMException("Invalid key use", "DataError");
+ }
+ }
+
+ // 7.
+ if (jwk.key_ops !== undefined) {
+ if (
+ ArrayPrototypeFind(
+ jwk.key_ops,
+ (u) => !ArrayPrototypeIncludes(recognisedUsages, u),
+ ) !== undefined
+ ) {
+ throw new DOMException(
+ "'key_ops' property of JsonWebKey is invalid",
+ "DataError",
+ );
+ }
+
+ if (
+ !ArrayPrototypeEvery(
+ jwk.key_ops,
+ (u) => ArrayPrototypeIncludes(keyUsages, u),
+ )
+ ) {
+ throw new DOMException(
+ "'key_ops' property of JsonWebKey is invalid",
+ "DataError",
+ );
+ }
+ }
+
+ // 8.
+ if (jwk.ext !== undefined && jwk.ext === false && extractable) {
+ throw new DOMException("Invalid key extractability", "DataError");
+ }
+
+ // 9.
+ if (jwk.d !== undefined) {
+ // https://www.rfc-editor.org/rfc/rfc8037#section-2
+ const privateKeyData = ops.op_crypto_base64url(jwk.d);
+
+ const handle = {};
+ WeakMapPrototypeSet(KEY_STORE, handle, privateKeyData);
+
+ const algorithm = {
+ name: "X25519",
+ };
+
+ return constructKey(
+ "private",
+ extractable,
+ [],
+ algorithm,
+ handle,
+ );
+ } else {
+ // https://www.rfc-editor.org/rfc/rfc8037#section-2
+ const publicKeyData = ops.op_crypto_base64url(jwk.d);
+
+ const handle = {};
+ WeakMapPrototypeSet(KEY_STORE, handle, publicKeyData);
+
+ const algorithm = {
+ name: "X25519",
+ };
+
+ return constructKey(
+ "public",
+ extractable,
+ [],
+ algorithm,
+ handle,
+ );
+ }
+ }
+ default:
+ throw new DOMException("Not implemented", "NotSupportedError");
+ }
+ }
+
function exportKeyAES(
format,
key,
@@ -2732,6 +3310,9 @@
private: ["deriveKey", "deriveBits"],
jwkUse: "enc",
},
+ "X25519": {
+ private: ["deriveKey", "deriveBits"],
+ },
};
function importKeyRSA(
@@ -3430,6 +4011,66 @@
}
}
+ function exportKeyEd25519(format, key, innerKey) {
+ switch (format) {
+ case "raw": {
+ // 1.
+ if (key[_type] !== "public") {
+ throw new DOMException(
+ "Key is not a public key",
+ "InvalidAccessError",
+ );
+ }
+
+ // 2-3.
+ return innerKey.buffer;
+ }
+ case "spki": {
+ // 1.
+ if (key[_type] !== "public") {
+ throw new DOMException(
+ "Key is not a public key",
+ "InvalidAccessError",
+ );
+ }
+
+ const spkiDer = ops.op_export_spki_ed25519(innerKey);
+ return spkiDer.buffer;
+ }
+ case "pkcs8": {
+ // 1.
+ if (key[_type] !== "private") {
+ throw new DOMException(
+ "Key is not a public key",
+ "InvalidAccessError",
+ );
+ }
+
+ const pkcs8Der = ops.op_export_pkcs8_ed25519(innerKey);
+ return pkcs8Der.buffer;
+ }
+ case "jwk": {
+ const x = key[_type] === "private"
+ ? ops.op_jwk_x_ed25519(innerKey)
+ : ops.op_crypto_base64url(innerKey);
+ const jwk = {
+ kty: "OKP",
+ alg: "EdDSA",
+ crv: "Ed25519",
+ x,
+ "key_ops": key.usages,
+ ext: key[_extractable],
+ };
+ if (key[_type] === "private") {
+ jwk.d = ops.op_crypto_base64url(innerKey);
+ }
+ return jwk;
+ }
+ default:
+ throw new DOMException("Not implemented", "NotSupportedError");
+ }
+ }
+
function exportKeyEC(format, key, innerKey) {
switch (format) {
case "raw": {
@@ -3713,6 +4354,49 @@
return buf.buffer;
}
+ case "X25519": {
+ // 1.
+ if (baseKey[_type] !== "private") {
+ throw new DOMException("Invalid key type", "InvalidAccessError");
+ }
+ // 2.
+ const publicKey = normalizedAlgorithm.public;
+ // 3.
+ if (publicKey[_type] !== "public") {
+ throw new DOMException("Invalid key type", "InvalidAccessError");
+ }
+ // 4.
+ if (publicKey[_algorithm].name !== baseKey[_algorithm].name) {
+ throw new DOMException(
+ "Algorithm mismatch",
+ "InvalidAccessError",
+ );
+ }
+
+ // 5.
+ const kHandle = baseKey[_handle];
+ const k = WeakMapPrototypeGet(KEY_STORE, kHandle);
+
+ const uHandle = publicKey[_handle];
+ const u = WeakMapPrototypeGet(KEY_STORE, uHandle);
+
+ const secret = new Uint8Array(32);
+ const isIdentity = ops.op_derive_bits_x25519(k, u, secret);
+
+ // 6.
+ if (isIdentity) {
+ throw new DOMException("Invalid key", "OperationError");
+ }
+
+ // 7.
+ if (length === null) {
+ return secret.buffer;
+ } else if (secret.length * 8 < length) {
+ throw new DOMException("Invalid length", "OperationError");
+ } else {
+ return secret.subarray(0, length / 8).buffer;
+ }
+ }
default:
throw new DOMException("Not implemented", "NotSupportedError");
}
diff --git a/ext/crypto/Cargo.toml b/ext/crypto/Cargo.toml
index 5828a3e9a..c03a2676e 100644
--- a/ext/crypto/Cargo.toml
+++ b/ext/crypto/Cargo.toml
@@ -22,6 +22,8 @@ block-modes = "0.9.1"
cbc = { version = "0.1.2", features = ["alloc"] }
const-oid = "0.9.0"
ctr = "0.9.1"
+# https://github.com/dalek-cryptography/curve25519-dalek/pull/397
+curve25519-dalek = "2.1.3"
deno_core = { version = "0.152.0", path = "../../core" }
deno_web = { version = "0.101.0", path = "../web" }
elliptic-curve = { version = "0.12.1", features = ["std", "pem"] }
@@ -40,3 +42,5 @@ sha2 = "0.10.2"
spki = "0.6.0"
tokio = { version = "1.21", features = ["full"] }
uuid = { version = "1.0.0", features = ["v4"] }
+# https://github.com/dalek-cryptography/x25519-dalek/pull/89
+x25519-dalek = "2.0.0-pre.1"
diff --git a/ext/crypto/ed25519.rs b/ext/crypto/ed25519.rs
new file mode 100644
index 000000000..a8060cae1
--- /dev/null
+++ b/ext/crypto/ed25519.rs
@@ -0,0 +1,128 @@
+use deno_core::error::AnyError;
+use deno_core::op;
+use deno_core::ZeroCopyBuf;
+use elliptic_curve::pkcs8::PrivateKeyInfo;
+use rand::rngs::OsRng;
+use rand::RngCore;
+use ring::signature::Ed25519KeyPair;
+use ring::signature::KeyPair;
+use spki::der::Decode;
+use spki::der::Encode;
+
+#[op(fast)]
+pub fn op_generate_ed25519_keypair(pkey: &mut [u8], pubkey: &mut [u8]) -> bool {
+ let mut rng = OsRng;
+ rng.fill_bytes(pkey);
+
+ let pair = match Ed25519KeyPair::from_seed_unchecked(pkey) {
+ Ok(p) => p,
+ Err(_) => return false,
+ };
+ pubkey.copy_from_slice(pair.public_key().as_ref());
+ true
+}
+
+#[op(fast)]
+pub fn op_sign_ed25519(key: &[u8], data: &[u8], signature: &mut [u8]) -> bool {
+ let pair = match Ed25519KeyPair::from_seed_unchecked(key) {
+ Ok(p) => p,
+ Err(_) => return false,
+ };
+ signature.copy_from_slice(pair.sign(data).as_ref());
+ true
+}
+
+#[op(fast)]
+pub fn op_verify_ed25519(pubkey: &[u8], data: &[u8], signature: &[u8]) -> bool {
+ ring::signature::UnparsedPublicKey::new(&ring::signature::ED25519, pubkey)
+ .verify(data, signature)
+ .is_ok()
+}
+
+// id-Ed25519 OBJECT IDENTIFIER ::= { 1 3 101 112 }
+pub const ED25519_OID: const_oid::ObjectIdentifier =
+ const_oid::ObjectIdentifier::new_unwrap("1.3.101.112");
+
+#[op(fast)]
+pub fn op_import_spki_ed25519(key_data: &[u8], out: &mut [u8]) -> bool {
+ // 2-3.
+ let pk_info = match spki::SubjectPublicKeyInfo::from_der(key_data) {
+ Ok(pk_info) => pk_info,
+ Err(_) => return false,
+ };
+ // 4.
+ let alg = pk_info.algorithm.oid;
+ if alg != ED25519_OID {
+ return false;
+ }
+ // 5.
+ if pk_info.algorithm.parameters.is_some() {
+ return false;
+ }
+ out.copy_from_slice(pk_info.subject_public_key);
+ true
+}
+
+#[op(fast)]
+pub fn op_import_pkcs8_ed25519(key_data: &[u8], out: &mut [u8]) -> bool {
+ // 2-3.
+ let pk_info = match PrivateKeyInfo::from_der(key_data) {
+ Ok(pk_info) => pk_info,
+ Err(_) => return false,
+ };
+ // 4.
+ let alg = pk_info.algorithm.oid;
+ if alg != ED25519_OID {
+ return false;
+ }
+ // 5.
+ if pk_info.algorithm.parameters.is_some() {
+ return false;
+ }
+ // 6.
+ // CurvePrivateKey ::= OCTET STRING
+ if pk_info.private_key.len() != 32 {
+ return false;
+ }
+ out.copy_from_slice(pk_info.private_key);
+ true
+}
+
+#[op]
+pub fn op_export_spki_ed25519(pubkey: &[u8]) -> Result<ZeroCopyBuf, AnyError> {
+ let key_info = spki::SubjectPublicKeyInfo {
+ algorithm: spki::AlgorithmIdentifier {
+ // id-Ed25519
+ oid: ED25519_OID,
+ parameters: None,
+ },
+ subject_public_key: pubkey,
+ };
+ Ok(key_info.to_vec()?.into())
+}
+
+#[op]
+pub fn op_export_pkcs8_ed25519(pkey: &[u8]) -> Result<ZeroCopyBuf, AnyError> {
+ let pk_info = rsa::pkcs8::PrivateKeyInfo {
+ public_key: None,
+ algorithm: rsa::pkcs8::AlgorithmIdentifier {
+ // id-Ed25519
+ oid: ED25519_OID,
+ parameters: None,
+ },
+ private_key: pkey, // OCTET STRING
+ };
+
+ Ok(pk_info.to_vec()?.into())
+}
+
+// 'x' from Section 2 of RFC 8037
+// https://www.rfc-editor.org/rfc/rfc8037#section-2
+#[op]
+pub fn op_jwk_x_ed25519(pkey: &[u8]) -> Result<String, AnyError> {
+ let pair = Ed25519KeyPair::from_seed_unchecked(pkey)?;
+ Ok(base64::encode_config(
+ pair.public_key().as_ref(),
+ base64::URL_SAFE_NO_PAD,
+ ))
+}
diff --git a/ext/crypto/lib.rs b/ext/crypto/lib.rs
index f4e118626..0f1ee565b 100644
--- a/ext/crypto/lib.rs
+++ b/ext/crypto/lib.rs
@@ -55,12 +55,14 @@ use std::path::PathBuf;
pub use rand; // Re-export rand
mod decrypt;
+mod ed25519;
mod encrypt;
mod export_key;
mod generate_key;
mod import_key;
mod key;
mod shared;
+mod x25519;
pub use crate::decrypt::op_crypto_decrypt;
pub use crate::encrypt::op_crypto_encrypt;
@@ -98,6 +100,19 @@ pub fn init(maybe_seed: Option<u64>) -> Extension {
op_crypto_random_uuid::decl(),
op_crypto_wrap_key::decl(),
op_crypto_unwrap_key::decl(),
+ op_crypto_base64url::decl(),
+ x25519::op_generate_x25519_keypair::decl(),
+ x25519::op_derive_bits_x25519::decl(),
+ x25519::op_import_spki_x25519::decl(),
+ x25519::op_import_pkcs8_x25519::decl(),
+ ed25519::op_generate_ed25519_keypair::decl(),
+ ed25519::op_import_spki_ed25519::decl(),
+ ed25519::op_import_pkcs8_ed25519::decl(),
+ ed25519::op_sign_ed25519::decl(),
+ ed25519::op_verify_ed25519::decl(),
+ ed25519::op_export_spki_ed25519::decl(),
+ ed25519::op_export_pkcs8_ed25519::decl(),
+ ed25519::op_jwk_x_ed25519::decl(),
])
.state(move |state| {
if let Some(seed) = maybe_seed {
@@ -109,6 +124,13 @@ pub fn init(maybe_seed: Option<u64>) -> Extension {
}
#[op]
+pub fn op_crypto_base64url(data: String) -> ZeroCopyBuf {
+ let data: Vec<u8> =
+ base64::encode_config(data, base64::URL_SAFE_NO_PAD).into();
+ data.into()
+}
+
+#[op]
pub fn op_crypto_get_random_values(
state: &mut OpState,
mut zero_copy: ZeroCopyBuf,
diff --git a/ext/crypto/x25519.rs b/ext/crypto/x25519.rs
new file mode 100644
index 000000000..b606f4ca0
--- /dev/null
+++ b/ext/crypto/x25519.rs
@@ -0,0 +1,89 @@
+use curve25519_dalek::montgomery::MontgomeryPoint;
+use deno_core::op;
+use elliptic_curve::pkcs8::PrivateKeyInfo;
+use elliptic_curve::subtle::ConstantTimeEq;
+use rand::rngs::OsRng;
+use rand::RngCore;
+use spki::der::Decode;
+
+#[op(fast)]
+pub fn op_generate_x25519_keypair(pkey: &mut [u8], pubkey: &mut [u8]) {
+ // u-coordinate of the base point.
+ const X25519_BASEPOINT_BYTES: [u8; 32] = [
+ 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0,
+ ];
+ let mut rng = OsRng;
+ rng.fill_bytes(pkey);
+ // https://www.rfc-editor.org/rfc/rfc7748#section-6.1
+ // pubkey = x25519(a, 9) which is constant-time Montgomery ladder.
+ // https://eprint.iacr.org/2014/140.pdf page 4
+ // https://eprint.iacr.org/2017/212.pdf algorithm 8
+ // pubkey is in LE order.
+ let pkey: [u8; 32] = pkey.try_into().expect("Expected byteLength 32");
+ pubkey.copy_from_slice(&x25519_dalek::x25519(pkey, X25519_BASEPOINT_BYTES));
+}
+
+const MONTGOMERY_IDENTITY: MontgomeryPoint = MontgomeryPoint([0; 32]);
+
+#[op(fast)]
+pub fn op_derive_bits_x25519(k: &[u8], u: &[u8], secret: &mut [u8]) -> bool {
+ let k: [u8; 32] = k.try_into().expect("Expected byteLength 32");
+ let u: [u8; 32] = u.try_into().expect("Expected byteLength 32");
+ let sh_sec = x25519_dalek::x25519(k, u);
+ let point = MontgomeryPoint(sh_sec);
+ if point.ct_eq(&MONTGOMERY_IDENTITY).unwrap_u8() == 1 {
+ return false;
+ }
+ secret.copy_from_slice(&sh_sec);
+ true
+}
+
+// id-X25519 OBJECT IDENTIFIER ::= { 1 3 101 110 }
+pub const X25519_OID: const_oid::ObjectIdentifier =
+ const_oid::ObjectIdentifier::new_unwrap("1.3.101.110");
+
+#[op(fast)]
+pub fn op_import_spki_x25519(key_data: &[u8], out: &mut [u8]) -> bool {
+ // 2-3.
+ let pk_info = match spki::SubjectPublicKeyInfo::from_der(key_data) {
+ Ok(pk_info) => pk_info,
+ Err(_) => return false,
+ };
+ // 4.
+ let alg = pk_info.algorithm.oid;
+ if alg != X25519_OID {
+ return false;
+ }
+ // 5.
+ if pk_info.algorithm.parameters.is_some() {
+ return false;
+ }
+ out.copy_from_slice(pk_info.subject_public_key);
+ true
+}
+
+#[op(fast)]
+pub fn op_import_pkcs8_x25519(key_data: &[u8], out: &mut [u8]) -> bool {
+ // 2-3.
+ let pk_info = match PrivateKeyInfo::from_der(key_data) {
+ Ok(pk_info) => pk_info,
+ Err(_) => return false,
+ };
+ // 4.
+ let alg = pk_info.algorithm.oid;
+ if alg != X25519_OID {
+ return false;
+ }
+ // 5.
+ if pk_info.algorithm.parameters.is_some() {
+ return false;
+ }
+ // 6.
+ // CurvePrivateKey ::= OCTET STRING
+ if pk_info.private_key.len() != 32 {
+ return false;
+ }
+ out.copy_from_slice(pk_info.private_key);
+ true
+}