mirror of
https://github.com/denoland/deno.git
synced 2024-11-21 15:04:11 -05:00
fix(ext/node): implement asymmetric keygen (#18651)
Towards #18455 This commit implements the keypair generation for asymmetric keys for the `generateKeyPair` API. See how key material is managed in this implementation: https://www.notion.so/denolandinc/node-crypto-design-99fc33f568d24e47a5e4b36002c5325d?pvs=4 Private and public key encoding depend on `KeyObject#export` which is not implemented. I've also skipped ED448 and X448 since we need a crate for that in WebCrypto too.
This commit is contained in:
parent
53c9f5918c
commit
9496dfc685
10 changed files with 1120 additions and 33 deletions
2
.github/workflows/ci.generate.ts
vendored
2
.github/workflows/ci.generate.ts
vendored
|
@ -17,7 +17,7 @@ const Runners = (() => {
|
|||
})();
|
||||
// bump the number at the start when you want to purge the cache
|
||||
const prCacheKeyPrefix =
|
||||
"19-cargo-target-${{ matrix.os }}-${{ matrix.profile }}-${{ matrix.job }}-";
|
||||
"20-cargo-target-${{ matrix.os }}-${{ matrix.profile }}-${{ matrix.job }}-";
|
||||
|
||||
const installPkgsCommand =
|
||||
"sudo apt-get install --no-install-recommends debootstrap clang-15 lld-15";
|
||||
|
|
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
|
@ -302,7 +302,7 @@ jobs:
|
|||
!./target/*/*.zip
|
||||
!./target/*/*.tar.gz
|
||||
key: never_saved
|
||||
restore-keys: '19-cargo-target-${{ matrix.os }}-${{ matrix.profile }}-${{ matrix.job }}-'
|
||||
restore-keys: '20-cargo-target-${{ matrix.os }}-${{ matrix.profile }}-${{ matrix.job }}-'
|
||||
- name: Apply and update mtime cache
|
||||
if: '!(github.event_name == ''pull_request'' && matrix.skip_pr) && (!startsWith(github.ref, ''refs/tags/''))'
|
||||
uses: ./.github/mtime_cache
|
||||
|
@ -578,7 +578,7 @@ jobs:
|
|||
!./target/*/gn_out
|
||||
!./target/*/*.zip
|
||||
!./target/*/*.tar.gz
|
||||
key: '19-cargo-target-${{ matrix.os }}-${{ matrix.profile }}-${{ matrix.job }}-${{ github.sha }}'
|
||||
key: '20-cargo-target-${{ matrix.os }}-${{ matrix.profile }}-${{ matrix.job }}-${{ github.sha }}'
|
||||
publish-canary:
|
||||
name: publish canary
|
||||
runs-on: ubuntu-22.04
|
||||
|
|
107
Cargo.lock
generated
107
Cargo.lock
generated
|
@ -927,8 +927,8 @@ dependencies = [
|
|||
"serde_bytes",
|
||||
"sha1",
|
||||
"sha2",
|
||||
"signature",
|
||||
"spki",
|
||||
"signature 1.6.4",
|
||||
"spki 0.6.0",
|
||||
"tokio",
|
||||
"uuid",
|
||||
"x25519-dalek",
|
||||
|
@ -1152,6 +1152,7 @@ dependencies = [
|
|||
"data-encoding",
|
||||
"deno_core",
|
||||
"digest 0.10.6",
|
||||
"dsa",
|
||||
"ecb",
|
||||
"hex",
|
||||
"hkdf",
|
||||
|
@ -1162,6 +1163,7 @@ dependencies = [
|
|||
"md-5",
|
||||
"md4",
|
||||
"num-bigint",
|
||||
"num-bigint-dig",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"once_cell",
|
||||
|
@ -1169,6 +1171,7 @@ dependencies = [
|
|||
"pbkdf2",
|
||||
"rand",
|
||||
"regex",
|
||||
"ring",
|
||||
"ripemd",
|
||||
"rsa",
|
||||
"scrypt",
|
||||
|
@ -1176,9 +1179,10 @@ dependencies = [
|
|||
"sha-1 0.10.0",
|
||||
"sha2",
|
||||
"sha3",
|
||||
"signature",
|
||||
"signature 1.6.4",
|
||||
"tokio",
|
||||
"typenum",
|
||||
"x25519-dalek",
|
||||
"x509-parser",
|
||||
]
|
||||
|
||||
|
@ -1386,6 +1390,16 @@ dependencies = [
|
|||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "der"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "82b10af9f9f9f2134a42d3f8aa74658660f2e0234b0eb81bd171df8aa32779ed"
|
||||
dependencies = [
|
||||
"const-oid",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "der-parser"
|
||||
version = "8.2.0"
|
||||
|
@ -1565,6 +1579,22 @@ dependencies = [
|
|||
"text_lines",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dsa"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5638f6d17447bc0ffc46354949ee366847e83450e2a07895862942085cc9761"
|
||||
dependencies = [
|
||||
"digest 0.10.6",
|
||||
"num-bigint-dig",
|
||||
"num-traits",
|
||||
"pkcs8 0.10.2",
|
||||
"rfc6979 0.4.0",
|
||||
"sha2",
|
||||
"signature 2.1.0",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dyn-clone"
|
||||
version = "1.0.11"
|
||||
|
@ -1612,10 +1642,10 @@ version = "0.14.8"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c"
|
||||
dependencies = [
|
||||
"der",
|
||||
"der 0.6.1",
|
||||
"elliptic-curve",
|
||||
"rfc6979",
|
||||
"signature",
|
||||
"rfc6979 0.3.1",
|
||||
"signature 1.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1632,14 +1662,14 @@ checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3"
|
|||
dependencies = [
|
||||
"base16ct",
|
||||
"crypto-bigint",
|
||||
"der",
|
||||
"der 0.6.1",
|
||||
"digest 0.10.6",
|
||||
"ff",
|
||||
"generic-array 0.14.6",
|
||||
"group",
|
||||
"hkdf",
|
||||
"pem-rfc7468",
|
||||
"pkcs8",
|
||||
"pkcs8 0.9.0",
|
||||
"rand_core 0.6.4",
|
||||
"sec1",
|
||||
"subtle",
|
||||
|
@ -2976,6 +3006,7 @@ dependencies = [
|
|||
"num-iter",
|
||||
"num-traits",
|
||||
"rand",
|
||||
"serde",
|
||||
"smallvec",
|
||||
"zeroize",
|
||||
]
|
||||
|
@ -3307,9 +3338,9 @@ version = "0.4.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eff33bdbdfc54cc98a2eca766ebdec3e1b8fb7387523d5c9c9a2891da856f719"
|
||||
dependencies = [
|
||||
"der",
|
||||
"pkcs8",
|
||||
"spki",
|
||||
"der 0.6.1",
|
||||
"pkcs8 0.9.0",
|
||||
"spki 0.6.0",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
|
@ -3319,8 +3350,18 @@ version = "0.9.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba"
|
||||
dependencies = [
|
||||
"der",
|
||||
"spki",
|
||||
"der 0.6.1",
|
||||
"spki 0.6.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkcs8"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
|
||||
dependencies = [
|
||||
"der 0.7.3",
|
||||
"spki 0.7.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3649,6 +3690,16 @@ dependencies = [
|
|||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rfc6979"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2"
|
||||
dependencies = [
|
||||
"hmac",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.16.20"
|
||||
|
@ -3686,9 +3737,9 @@ dependencies = [
|
|||
"num-iter",
|
||||
"num-traits",
|
||||
"pkcs1",
|
||||
"pkcs8",
|
||||
"pkcs8 0.9.0",
|
||||
"rand_core 0.6.4",
|
||||
"signature",
|
||||
"signature 1.6.4",
|
||||
"smallvec",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
|
@ -3901,9 +3952,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928"
|
||||
dependencies = [
|
||||
"base16ct",
|
||||
"der",
|
||||
"der 0.6.1",
|
||||
"generic-array 0.14.6",
|
||||
"pkcs8",
|
||||
"pkcs8 0.9.0",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
@ -4123,6 +4174,16 @@ dependencies = [
|
|||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signature"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500"
|
||||
dependencies = [
|
||||
"digest 0.10.6",
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simdutf8"
|
||||
version = "0.1.4"
|
||||
|
@ -4199,7 +4260,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
"der",
|
||||
"der 0.6.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spki"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37a5be806ab6f127c3da44b7378837ebf01dadca8510a0e572460216b228bd0e"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
"der 0.7.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
@ -1,9 +1,39 @@
|
|||
// deno-lint-ignore-file no-explicit-any
|
||||
|
||||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
import { createSecretKey, randomBytes } from "node:crypto";
|
||||
import {
|
||||
createSecretKey,
|
||||
generateKeyPair,
|
||||
generateKeyPairSync,
|
||||
KeyObject,
|
||||
randomBytes,
|
||||
} from "node:crypto";
|
||||
import { promisify } from "node:util";
|
||||
import { Buffer } from "node:buffer";
|
||||
import { assertEquals } from "../../../test_util/std/testing/asserts.ts";
|
||||
import {
|
||||
assertEquals,
|
||||
assertThrows,
|
||||
} from "../../../test_util/std/testing/asserts.ts";
|
||||
import { createHmac } from "node:crypto";
|
||||
|
||||
const generateKeyPairAsync = promisify(
|
||||
(
|
||||
type: any,
|
||||
options: any,
|
||||
callback: (
|
||||
err: Error | null,
|
||||
key: { publicKey: KeyObject; privateKey: KeyObject },
|
||||
) => void,
|
||||
) =>
|
||||
generateKeyPair(
|
||||
type,
|
||||
options,
|
||||
(err: Error | null, publicKey: KeyObject, privateKey: KeyObject) => {
|
||||
callback(err, { publicKey, privateKey });
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
Deno.test({
|
||||
name: "create secret key",
|
||||
fn() {
|
||||
|
@ -45,3 +75,130 @@ Deno.test({
|
|||
);
|
||||
},
|
||||
});
|
||||
|
||||
for (const type of ["rsa", "rsa-pss", "dsa"]) {
|
||||
for (const modulusLength of [2048, 3072]) {
|
||||
Deno.test({
|
||||
name: `generate ${type} key`,
|
||||
fn() {
|
||||
const { publicKey, privateKey } = generateKeyPairSync(type as any, {
|
||||
modulusLength,
|
||||
});
|
||||
|
||||
assertEquals(publicKey.type, "public");
|
||||
assertEquals(privateKey.type, "private");
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: `generate ${type} key async`,
|
||||
async fn() {
|
||||
const x = await generateKeyPairAsync(type as any, {
|
||||
modulusLength,
|
||||
});
|
||||
const { publicKey, privateKey } = x;
|
||||
assertEquals(publicKey.type, "public");
|
||||
assertEquals(privateKey.type, "private");
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (const namedCurve of ["P-384", "P-256"]) {
|
||||
Deno.test({
|
||||
name: `generate ec key ${namedCurve}`,
|
||||
fn() {
|
||||
const { publicKey, privateKey } = generateKeyPairSync("ec", {
|
||||
namedCurve,
|
||||
});
|
||||
|
||||
assertEquals(publicKey.type, "public");
|
||||
assertEquals(privateKey.type, "private");
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: `generate ec key ${namedCurve} async`,
|
||||
async fn() {
|
||||
const { publicKey, privateKey } = await generateKeyPairAsync("ec", {
|
||||
namedCurve,
|
||||
});
|
||||
|
||||
assertEquals(publicKey.type, "public");
|
||||
assertEquals(privateKey.type, "private");
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: `generate ec key ${namedCurve} paramEncoding=explicit fails`,
|
||||
fn() {
|
||||
assertThrows(() => {
|
||||
// @ts-ignore: @types/node is broken?
|
||||
generateKeyPairSync("ec", {
|
||||
namedCurve,
|
||||
paramEncoding: "explicit",
|
||||
});
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
for (
|
||||
const groupName of ["modp5", "modp14", "modp15", "modp16", "modp17", "modp18"]
|
||||
) {
|
||||
Deno.test({
|
||||
name: `generate dh key ${groupName}`,
|
||||
fn() {
|
||||
// @ts-ignore: @types/node is broken?
|
||||
const { publicKey, privateKey } = generateKeyPairSync("dh", {
|
||||
group: groupName,
|
||||
});
|
||||
|
||||
assertEquals(publicKey.type, "public");
|
||||
assertEquals(privateKey.type, "private");
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: `generate dh key ${groupName} async`,
|
||||
async fn() {
|
||||
// @ts-ignore: @types/node is broken?
|
||||
const { publicKey, privateKey } = await generateKeyPairAsync("dh", {
|
||||
group: groupName,
|
||||
});
|
||||
|
||||
assertEquals(publicKey.type, "public");
|
||||
assertEquals(privateKey.type, "private");
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
for (const primeLength of [1024, 2048, 4096]) {
|
||||
Deno.test({
|
||||
name: `generate dh key ${primeLength}`,
|
||||
fn() {
|
||||
// @ts-ignore: @types/node is broken?
|
||||
const { publicKey, privateKey } = generateKeyPairSync("dh", {
|
||||
primeLength,
|
||||
generator: 2,
|
||||
});
|
||||
|
||||
assertEquals(publicKey.type, "public");
|
||||
assertEquals(privateKey.type, "private");
|
||||
},
|
||||
});
|
||||
|
||||
Deno.test({
|
||||
name: `generate dh key ${primeLength} async`,
|
||||
async fn() {
|
||||
// @ts-ignore: @types/node is broken?
|
||||
const { publicKey, privateKey } = await generateKeyPairAsync("dh", {
|
||||
primeLength,
|
||||
generator: 2,
|
||||
});
|
||||
|
||||
assertEquals(publicKey.type, "public");
|
||||
assertEquals(privateKey.type, "private");
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ cbc.workspace = true
|
|||
data-encoding = "2.3.3"
|
||||
deno_core.workspace = true
|
||||
digest = { version = "0.10.5", features = ["core-api", "std"] }
|
||||
dsa = "0.6.1"
|
||||
ecb.workspace = true
|
||||
hex.workspace = true
|
||||
hkdf.workspace = true
|
||||
|
@ -29,6 +30,7 @@ libz-sys = { version = "1.1.8", features = ["static"] }
|
|||
md-5 = "0.10.5"
|
||||
md4 = "0.10.2"
|
||||
num-bigint.workspace = true
|
||||
num-bigint-dig = "0.8.2"
|
||||
num-integer = "0.1.45"
|
||||
num-traits = "0.2.14"
|
||||
once_cell.workspace = true
|
||||
|
@ -36,6 +38,7 @@ path-clean = "=0.1.0"
|
|||
pbkdf2 = "0.12.1"
|
||||
rand.workspace = true
|
||||
regex.workspace = true
|
||||
ring.workspace = true
|
||||
ripemd = "0.1.3"
|
||||
rsa.workspace = true
|
||||
scrypt = "0.11.0"
|
||||
|
@ -46,4 +49,6 @@ sha3 = "0.10.5"
|
|||
signature.workspace = true
|
||||
tokio.workspace = true
|
||||
typenum = "1.15.0"
|
||||
# https://github.com/dalek-cryptography/x25519-dalek/pull/89
|
||||
x25519-dalek = "2.0.0-pre.1"
|
||||
x509-parser = "0.15.0"
|
||||
|
|
288
ext/node/crypto/dh.rs
Normal file
288
ext/node/crypto/dh.rs
Normal file
|
@ -0,0 +1,288 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
use super::primes::Prime;
|
||||
use num_bigint_dig::BigUint;
|
||||
use num_bigint_dig::RandBigInt;
|
||||
use num_traits::FromPrimitive;
|
||||
|
||||
pub struct PublicKey(BigUint);
|
||||
|
||||
impl PublicKey {
|
||||
pub fn into_vec(self) -> Vec<u8> {
|
||||
self.0.to_bytes_be()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PrivateKey(BigUint);
|
||||
|
||||
impl PrivateKey {
|
||||
pub fn new(exponent_size: usize) -> Self {
|
||||
let mut rng = rand::thread_rng();
|
||||
let exponent = rng.gen_biguint(exponent_size);
|
||||
Self(exponent)
|
||||
}
|
||||
|
||||
/// Diffie-Hellman modular exponentiation.
|
||||
/// s = g^x mod p
|
||||
pub fn compute_public_key(
|
||||
&self,
|
||||
generator: &BigUint,
|
||||
modulus: &BigUint,
|
||||
) -> PublicKey {
|
||||
let public_key = generator.modpow(&self.0, modulus);
|
||||
PublicKey(public_key)
|
||||
}
|
||||
|
||||
pub fn into_vec(self) -> Vec<u8> {
|
||||
self.0.to_bytes_be()
|
||||
}
|
||||
}
|
||||
|
||||
/// Classic DH
|
||||
pub struct DiffieHellman {
|
||||
pub private_key: PrivateKey,
|
||||
pub public_key: PublicKey,
|
||||
}
|
||||
|
||||
impl DiffieHellman {
|
||||
pub fn group<G>() -> Self
|
||||
where
|
||||
G: DiffieHellmanGroup,
|
||||
{
|
||||
let private_key = PrivateKey::new(G::EXPONENT_SIZE / 8);
|
||||
|
||||
let generator = BigUint::from_usize(G::GENERATOR).unwrap();
|
||||
let modulus = BigUint::from_slice(G::MODULUS);
|
||||
|
||||
let public_key = private_key.compute_public_key(&generator, &modulus);
|
||||
|
||||
Self {
|
||||
private_key,
|
||||
public_key,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(prime: Prime, generator: usize) -> Self {
|
||||
let private_key = PrivateKey::new(32);
|
||||
|
||||
let generator = BigUint::from_usize(generator).unwrap();
|
||||
let public_key = private_key.compute_public_key(&generator, &prime);
|
||||
|
||||
Self {
|
||||
private_key,
|
||||
public_key,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Well-known modp groups
|
||||
//
|
||||
/// More Modular Exponential (MODP) Diffie-Hellman groups for Internet Key Exchange (IKE)
|
||||
/// https://www.rfc-editor.org/rfc/rfc3526
|
||||
///
|
||||
/// Insecure groups `modp1` and `modp2` from https://www.rfc-editor.org/rfc/rfc2409.txt
|
||||
/// are deprecated in Node.js. We don't support them.
|
||||
pub trait DiffieHellmanGroup {
|
||||
const GENERATOR: usize;
|
||||
const MODULUS: &'static [u32];
|
||||
/// Size of the exponent in bits
|
||||
const EXPONENT_SIZE: usize;
|
||||
}
|
||||
|
||||
/// 1536-bit MODP Group
|
||||
/// https://www.rfc-editor.org/rfc/rfc3526#section-2
|
||||
pub struct Modp1536;
|
||||
impl DiffieHellmanGroup for Modp1536 {
|
||||
const GENERATOR: usize = 2;
|
||||
const EXPONENT_SIZE: usize = 192;
|
||||
|
||||
const MODULUS: &'static [u32] = &[
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xC90FDAA2, 0x2168C234, 0xC4C6628B, 0x80DC1CD1,
|
||||
0x29024E08, 0x8A67CC74, 0x020BBEA6, 0x3B139B22, 0x514A0879, 0x8E3404DD,
|
||||
0xEF9519B3, 0xCD3A431B, 0x302B0A6D, 0xF25F1437, 0x4FE1356D, 0x6D51C245,
|
||||
0xE485B576, 0x625E7EC6, 0xF44C42E9, 0xA637ED6B, 0x0BFF5CB6, 0xF406B7ED,
|
||||
0xEE386BFB, 0x5A899FA5, 0xAE9F2411, 0x7C4B1FE6, 0x49286651, 0xECE45B3D,
|
||||
0xC2007CB8, 0xA163BF05, 0x98DA4836, 0x1C55D39A, 0x69163FA8, 0xFD24CF5F,
|
||||
0x83655D23, 0xDCA3AD96, 0x1C62F356, 0x208552BB, 0x9ED52907, 0x7096966D,
|
||||
0x670C354E, 0x4ABC9804, 0xF1746C08, 0xCA237327, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
];
|
||||
}
|
||||
|
||||
/// 2048-bit MODP Group
|
||||
/// https://www.rfc-editor.org/rfc/rfc3526#section-3
|
||||
pub struct Modp2048;
|
||||
impl DiffieHellmanGroup for Modp2048 {
|
||||
const GENERATOR: usize = 2;
|
||||
const EXPONENT_SIZE: usize = 256;
|
||||
|
||||
const MODULUS: &'static [u32] = &[
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xC90FDAA2, 0x2168C234, 0xC4C6628B, 0x80DC1CD1,
|
||||
0x29024E08, 0x8A67CC74, 0x020BBEA6, 0x3B139B22, 0x514A0879, 0x8E3404DD,
|
||||
0xEF9519B3, 0xCD3A431B, 0x302B0A6D, 0xF25F1437, 0x4FE1356D, 0x6D51C245,
|
||||
0xE485B576, 0x625E7EC6, 0xF44C42E9, 0xA637ED6B, 0x0BFF5CB6, 0xF406B7ED,
|
||||
0xEE386BFB, 0x5A899FA5, 0xAE9F2411, 0x7C4B1FE6, 0x49286651, 0xECE45B3D,
|
||||
0xC2007CB8, 0xA163BF05, 0x98DA4836, 0x1C55D39A, 0x69163FA8, 0xFD24CF5F,
|
||||
0x83655D23, 0xDCA3AD96, 0x1C62F356, 0x208552BB, 0x9ED52907, 0x7096966D,
|
||||
0x670C354E, 0x4ABC9804, 0xF1746C08, 0xCA18217C, 0x32905E46, 0x2E36CE3B,
|
||||
0xE39E772C, 0x180E8603, 0x9B2783A2, 0xEC07A28F, 0xB5C55DF0, 0x6F4C52C9,
|
||||
0xDE2BCBF6, 0x95581718, 0x3995497C, 0xEA956AE5, 0x15D22618, 0x98FA0510,
|
||||
0x15728E5A, 0x8AACAA68, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
];
|
||||
}
|
||||
|
||||
/// 3072-bit MODP Group
|
||||
/// https://www.rfc-editor.org/rfc/rfc3526#section-4
|
||||
pub struct Modp3072;
|
||||
impl DiffieHellmanGroup for Modp3072 {
|
||||
const GENERATOR: usize = 2;
|
||||
const EXPONENT_SIZE: usize = 384;
|
||||
|
||||
const MODULUS: &'static [u32] = &[
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xC90FDAA2, 0x2168C234, 0xC4C6628B, 0x80DC1CD1,
|
||||
0x29024E08, 0x8A67CC74, 0x020BBEA6, 0x3B139B22, 0x514A0879, 0x8E3404DD,
|
||||
0xEF9519B3, 0xCD3A431B, 0x302B0A6D, 0xF25F1437, 0x4FE1356D, 0x6D51C245,
|
||||
0xE485B576, 0x625E7EC6, 0xF44C42E9, 0xA637ED6B, 0x0BFF5CB6, 0xF406B7ED,
|
||||
0xEE386BFB, 0x5A899FA5, 0xAE9F2411, 0x7C4B1FE6, 0x49286651, 0xECE45B3D,
|
||||
0xC2007CB8, 0xA163BF05, 0x98DA4836, 0x1C55D39A, 0x69163FA8, 0xFD24CF5F,
|
||||
0x83655D23, 0xDCA3AD96, 0x1C62F356, 0x208552BB, 0x9ED52907, 0x7096966D,
|
||||
0x670C354E, 0x4ABC9804, 0xF1746C08, 0xCA18217C, 0x32905E46, 0x2E36CE3B,
|
||||
0xE39E772C, 0x180E8603, 0x9B2783A2, 0xEC07A28F, 0xB5C55DF0, 0x6F4C52C9,
|
||||
0xDE2BCBF6, 0x95581718, 0x3995497C, 0xEA956AE5, 0x15D22618, 0x98FA0510,
|
||||
0x15728E5A, 0x8AAAC42D, 0xAD33170D, 0x04507A33, 0xA85521AB, 0xDF1CBA64,
|
||||
0xECFB8504, 0x58DBEF0A, 0x8AEA7157, 0x5D060C7D, 0xB3970F85, 0xA6E1E4C7,
|
||||
0xABF5AE8C, 0xDB0933D7, 0x1E8C94E0, 0x4A25619D, 0xCEE3D226, 0x1AD2EE6B,
|
||||
0xF12FFA06, 0xD98A0864, 0xD8760273, 0x3EC86A64, 0x521F2B18, 0x177B200C,
|
||||
0xBBE11757, 0x7A615D6C, 0x770988C0, 0xBAD946E2, 0x08E24FA0, 0x74E5AB31,
|
||||
0x43DB5BFC, 0xE0FD108E, 0x4B82D120, 0xA93AD2CA, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
];
|
||||
}
|
||||
|
||||
/// 4096-bit MODP Group
|
||||
/// https://www.rfc-editor.org/rfc/rfc3526#section-5
|
||||
pub struct Modp4096;
|
||||
impl DiffieHellmanGroup for Modp4096 {
|
||||
const GENERATOR: usize = 2;
|
||||
const EXPONENT_SIZE: usize = 512;
|
||||
|
||||
const MODULUS: &'static [u32] = &[
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xC90FDAA2, 0x2168C234, 0xC4C6628B, 0x80DC1CD1,
|
||||
0x29024E08, 0x8A67CC74, 0x020BBEA6, 0x3B139B22, 0x514A0879, 0x8E3404DD,
|
||||
0xEF9519B3, 0xCD3A431B, 0x302B0A6D, 0xF25F1437, 0x4FE1356D, 0x6D51C245,
|
||||
0xE485B576, 0x625E7EC6, 0xF44C42E9, 0xA637ED6B, 0x0BFF5CB6, 0xF406B7ED,
|
||||
0xEE386BFB, 0x5A899FA5, 0xAE9F2411, 0x7C4B1FE6, 0x49286651, 0xECE45B3D,
|
||||
0xC2007CB8, 0xA163BF05, 0x98DA4836, 0x1C55D39A, 0x69163FA8, 0xFD24CF5F,
|
||||
0x83655D23, 0xDCA3AD96, 0x1C62F356, 0x208552BB, 0x9ED52907, 0x7096966D,
|
||||
0x670C354E, 0x4ABC9804, 0xF1746C08, 0xCA18217C, 0x32905E46, 0x2E36CE3B,
|
||||
0xE39E772C, 0x180E8603, 0x9B2783A2, 0xEC07A28F, 0xB5C55DF0, 0x6F4C52C9,
|
||||
0xDE2BCBF6, 0x95581718, 0x3995497C, 0xEA956AE5, 0x15D22618, 0x98FA0510,
|
||||
0x15728E5A, 0x8AAAC42D, 0xAD33170D, 0x04507A33, 0xA85521AB, 0xDF1CBA64,
|
||||
0xECFB8504, 0x58DBEF0A, 0x8AEA7157, 0x5D060C7D, 0xB3970F85, 0xA6E1E4C7,
|
||||
0xABF5AE8C, 0xDB0933D7, 0x1E8C94E0, 0x4A25619D, 0xCEE3D226, 0x1AD2EE6B,
|
||||
0xF12FFA06, 0xD98A0864, 0xD8760273, 0x3EC86A64, 0x521F2B18, 0x177B200C,
|
||||
0xBBE11757, 0x7A615D6C, 0x770988C0, 0xBAD946E2, 0x08E24FA0, 0x74E5AB31,
|
||||
0x43DB5BFC, 0xE0FD108E, 0x4B82D120, 0xA9210801, 0x1A723C12, 0xA787E6D7,
|
||||
0x88719A10, 0xBDBA5B26, 0x99C32718, 0x6AF4E23C, 0x1A946834, 0xB6150BDA,
|
||||
0x2583E9CA, 0x2AD44CE8, 0xDBBBC2DB, 0x04DE8EF9, 0x2E8EFC14, 0x1FBECAA6,
|
||||
0x287C5947, 0x4E6BC05D, 0x99B2964F, 0xA090C3A2, 0x233BA186, 0x515BE7ED,
|
||||
0x1F612970, 0xCEE2D7AF, 0xB81BDD76, 0x2170481C, 0xD0069127, 0xD5B05AA9,
|
||||
0x93B4EA98, 0x8D8FDDC1, 0x86FFB7DC, 0x90A6C08F, 0x4DF435C9, 0x34063199,
|
||||
0xFFFFFFFF, 0xFFFFFFFF,
|
||||
];
|
||||
}
|
||||
|
||||
/// 6144-bit MODP Group
|
||||
/// https://www.rfc-editor.org/rfc/rfc3526#section-6
|
||||
pub struct Modp6144;
|
||||
impl DiffieHellmanGroup for Modp6144 {
|
||||
const GENERATOR: usize = 2;
|
||||
const EXPONENT_SIZE: usize = 768;
|
||||
|
||||
const MODULUS: &'static [u32] = &[
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xC90FDAA2, 0x2168C234, 0xC4C6628B, 0x80DC1CD1,
|
||||
0x29024E08, 0x8A67CC74, 0x020BBEA6, 0x3B139B22, 0x514A0879, 0x8E3404DD,
|
||||
0xEF9519B3, 0xCD3A431B, 0x302B0A6D, 0xF25F1437, 0x4FE1356D, 0x6D51C245,
|
||||
0xE485B576, 0x625E7EC6, 0xF44C42E9, 0xA637ED6B, 0x0BFF5CB6, 0xF406B7ED,
|
||||
0xEE386BFB, 0x5A899FA5, 0xAE9F2411, 0x7C4B1FE6, 0x49286651, 0xECE45B3D,
|
||||
0xC2007CB8, 0xA163BF05, 0x98DA4836, 0x1C55D39A, 0x69163FA8, 0xFD24CF5F,
|
||||
0x83655D23, 0xDCA3AD96, 0x1C62F356, 0x208552BB, 0x9ED52907, 0x7096966D,
|
||||
0x670C354E, 0x4ABC9804, 0xF1746C08, 0xCA18217C, 0x32905E46, 0x2E36CE3B,
|
||||
0xE39E772C, 0x180E8603, 0x9B2783A2, 0xEC07A28F, 0xB5C55DF0, 0x6F4C52C9,
|
||||
0xDE2BCBF6, 0x95581718, 0x3995497C, 0xEA956AE5, 0x15D22618, 0x98FA0510,
|
||||
0x15728E5A, 0x8AAAC42D, 0xAD33170D, 0x04507A33, 0xA85521AB, 0xDF1CBA64,
|
||||
0xECFB8504, 0x58DBEF0A, 0x8AEA7157, 0x5D060C7D, 0xB3970F85, 0xA6E1E4C7,
|
||||
0xABF5AE8C, 0xDB0933D7, 0x1E8C94E0, 0x4A25619D, 0xCEE3D226, 0x1AD2EE6B,
|
||||
0xF12FFA06, 0xD98A0864, 0xD8760273, 0x3EC86A64, 0x521F2B18, 0x177B200C,
|
||||
0xBBE11757, 0x7A615D6C, 0x770988C0, 0xBAD946E2, 0x08E24FA0, 0x74E5AB31,
|
||||
0x43DB5BFC, 0xE0FD108E, 0x4B82D120, 0xA9210801, 0x1A723C12, 0xA787E6D7,
|
||||
0x88719A10, 0xBDBA5B26, 0x99C32718, 0x6AF4E23C, 0x1A946834, 0xB6150BDA,
|
||||
0x2583E9CA, 0x2AD44CE8, 0xDBBBC2DB, 0x04DE8EF9, 0x2E8EFC14, 0x1FBECAA6,
|
||||
0x287C5947, 0x4E6BC05D, 0x99B2964F, 0xA090C3A2, 0x233BA186, 0x515BE7ED,
|
||||
0x1F612970, 0xCEE2D7AF, 0xB81BDD76, 0x2170481C, 0xD0069127, 0xD5B05AA9,
|
||||
0x93B4EA98, 0x8D8FDDC1, 0x86FFB7DC, 0x90A6C08F, 0x4DF435C9, 0x34028492,
|
||||
0x36C3FAB4, 0xD27C7026, 0xC1D4DCB2, 0x602646DE, 0xC9751E76, 0x3DBA37BD,
|
||||
0xF8FF9406, 0xAD9E530E, 0xE5DB382F, 0x413001AE, 0xB06A53ED, 0x9027D831,
|
||||
0x179727B0, 0x865A8918, 0xDA3EDBEB, 0xCF9B14ED, 0x44CE6CBA, 0xCED4BB1B,
|
||||
0xDB7F1447, 0xE6CC254B, 0x33205151, 0x2BD7AF42, 0x6FB8F401, 0x378CD2BF,
|
||||
0x5983CA01, 0xC64B92EC, 0xF032EA15, 0xD1721D03, 0xF482D7CE, 0x6E74FEF6,
|
||||
0xD55E702F, 0x46980C82, 0xB5A84031, 0x900B1C9E, 0x59E7C97F, 0xBEC7E8F3,
|
||||
0x23A97A7E, 0x36CC88BE, 0x0F1D45B7, 0xFF585AC5, 0x4BD407B2, 0x2B4154AA,
|
||||
0xCC8F6D7E, 0xBF48E1D8, 0x14CC5ED2, 0x0F8037E0, 0xA79715EE, 0xF29BE328,
|
||||
0x06A1D58B, 0xB7C5DA76, 0xF550AA3D, 0x8A1FBFF0, 0xEB19CCB1, 0xA313D55C,
|
||||
0xDA56C9EC, 0x2EF29632, 0x387FE8D7, 0x6E3C0468, 0x043E8F66, 0x3F4860EE,
|
||||
0x12BF2D5B, 0x0B7474D6, 0xE694F91E, 0x6DCC4024, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
];
|
||||
}
|
||||
|
||||
/// 8192-bit MODP Group
|
||||
/// https://www.rfc-editor.org/rfc/rfc3526#section-7
|
||||
pub struct Modp8192;
|
||||
impl DiffieHellmanGroup for Modp8192 {
|
||||
const GENERATOR: usize = 2;
|
||||
const EXPONENT_SIZE: usize = 1024;
|
||||
|
||||
const MODULUS: &'static [u32] = &[
|
||||
0xFFFFFFFF, 0xFFFFFFFF, 0xC90FDAA2, 0x2168C234, 0xC4C6628B, 0x80DC1CD1,
|
||||
0x29024E08, 0x8A67CC74, 0x020BBEA6, 0x3B139B22, 0x514A0879, 0x8E3404DD,
|
||||
0xEF9519B3, 0xCD3A431B, 0x302B0A6D, 0xF25F1437, 0x4FE1356D, 0x6D51C245,
|
||||
0xE485B576, 0x625E7EC6, 0xF44C42E9, 0xA637ED6B, 0x0BFF5CB6, 0xF406B7ED,
|
||||
0xEE386BFB, 0x5A899FA5, 0xAE9F2411, 0x7C4B1FE6, 0x49286651, 0xECE45B3D,
|
||||
0xC2007CB8, 0xA163BF05, 0x98DA4836, 0x1C55D39A, 0x69163FA8, 0xFD24CF5F,
|
||||
0x83655D23, 0xDCA3AD96, 0x1C62F356, 0x208552BB, 0x9ED52907, 0x7096966D,
|
||||
0x670C354E, 0x4ABC9804, 0xF1746C08, 0xCA18217C, 0x32905E46, 0x2E36CE3B,
|
||||
0xE39E772C, 0x180E8603, 0x9B2783A2, 0xEC07A28F, 0xB5C55DF0, 0x6F4C52C9,
|
||||
0xDE2BCBF6, 0x95581718, 0x3995497C, 0xEA956AE5, 0x15D22618, 0x98FA0510,
|
||||
0x15728E5A, 0x8AAAC42D, 0xAD33170D, 0x04507A33, 0xA85521AB, 0xDF1CBA64,
|
||||
0xECFB8504, 0x58DBEF0A, 0x8AEA7157, 0x5D060C7D, 0xB3970F85, 0xA6E1E4C7,
|
||||
0xABF5AE8C, 0xDB0933D7, 0x1E8C94E0, 0x4A25619D, 0xCEE3D226, 0x1AD2EE6B,
|
||||
0xF12FFA06, 0xD98A0864, 0xD8760273, 0x3EC86A64, 0x521F2B18, 0x177B200C,
|
||||
0xBBE11757, 0x7A615D6C, 0x770988C0, 0xBAD946E2, 0x08E24FA0, 0x74E5AB31,
|
||||
0x43DB5BFC, 0xE0FD108E, 0x4B82D120, 0xA9210801, 0x1A723C12, 0xA787E6D7,
|
||||
0x88719A10, 0xBDBA5B26, 0x99C32718, 0x6AF4E23C, 0x1A946834, 0xB6150BDA,
|
||||
0x2583E9CA, 0x2AD44CE8, 0xDBBBC2DB, 0x04DE8EF9, 0x2E8EFC14, 0x1FBECAA6,
|
||||
0x287C5947, 0x4E6BC05D, 0x99B2964F, 0xA090C3A2, 0x233BA186, 0x515BE7ED,
|
||||
0x1F612970, 0xCEE2D7AF, 0xB81BDD76, 0x2170481C, 0xD0069127, 0xD5B05AA9,
|
||||
0x93B4EA98, 0x8D8FDDC1, 0x86FFB7DC, 0x90A6C08F, 0x4DF435C9, 0x34028492,
|
||||
0x36C3FAB4, 0xD27C7026, 0xC1D4DCB2, 0x602646DE, 0xC9751E76, 0x3DBA37BD,
|
||||
0xF8FF9406, 0xAD9E530E, 0xE5DB382F, 0x413001AE, 0xB06A53ED, 0x9027D831,
|
||||
0x179727B0, 0x865A8918, 0xDA3EDBEB, 0xCF9B14ED, 0x44CE6CBA, 0xCED4BB1B,
|
||||
0xDB7F1447, 0xE6CC254B, 0x33205151, 0x2BD7AF42, 0x6FB8F401, 0x378CD2BF,
|
||||
0x5983CA01, 0xC64B92EC, 0xF032EA15, 0xD1721D03, 0xF482D7CE, 0x6E74FEF6,
|
||||
0xD55E702F, 0x46980C82, 0xB5A84031, 0x900B1C9E, 0x59E7C97F, 0xBEC7E8F3,
|
||||
0x23A97A7E, 0x36CC88BE, 0x0F1D45B7, 0xFF585AC5, 0x4BD407B2, 0x2B4154AA,
|
||||
0xCC8F6D7E, 0xBF48E1D8, 0x14CC5ED2, 0x0F8037E0, 0xA79715EE, 0xF29BE328,
|
||||
0x06A1D58B, 0xB7C5DA76, 0xF550AA3D, 0x8A1FBFF0, 0xEB19CCB1, 0xA313D55C,
|
||||
0xDA56C9EC, 0x2EF29632, 0x387FE8D7, 0x6E3C0468, 0x043E8F66, 0x3F4860EE,
|
||||
0x12BF2D5B, 0x0B7474D6, 0xE694F91E, 0x6DBE1159, 0x74A3926F, 0x12FEE5E4,
|
||||
0x38777CB6, 0xA932DF8C, 0xD8BEC4D0, 0x73B931BA, 0x3BC832B6, 0x8D9DD300,
|
||||
0x741FA7BF, 0x8AFC47ED, 0x2576F693, 0x6BA42466, 0x3AAB639C, 0x5AE4F568,
|
||||
0x3423B474, 0x2BF1C978, 0x238F16CB, 0xE39D652D, 0xE3FDB8BE, 0xFC848AD9,
|
||||
0x22222E04, 0xA4037C07, 0x13EB57A8, 0x1A23F0C7, 0x3473FC64, 0x6CEA306B,
|
||||
0x4BCBC886, 0x2F8385DD, 0xFA9D4B7F, 0xA2C087E8, 0x79683303, 0xED5BDD3A,
|
||||
0x062B3CF5, 0xB3A278A6, 0x6D2A13F8, 0x3F44F82D, 0xDF310EE0, 0x74AB6A36,
|
||||
0x4597E899, 0xA0255DC1, 0x64F31CC5, 0x0846851D, 0xF9AB4819, 0x5DED7EA1,
|
||||
0xB1D510BD, 0x7EE74D73, 0xFAF36BC3, 0x1ECFA268, 0x359046F4, 0xEB879F92,
|
||||
0x4009438B, 0x481C6CD7, 0x889A002E, 0xD5EE382B, 0xC9190DA6, 0xFC026E47,
|
||||
0x9558E447, 0x5677E9AA, 0x9E3050E2, 0x765694DF, 0xC81F56E8, 0x80B96E71,
|
||||
0x60C980DD, 0x98EDD3DF, 0xFFFFFFFF, 0xFFFFFFFF,
|
||||
];
|
||||
}
|
|
@ -10,8 +10,10 @@ use deno_core::StringOrBuffer;
|
|||
use deno_core::ZeroCopyBuf;
|
||||
use hkdf::Hkdf;
|
||||
use num_bigint::BigInt;
|
||||
use num_traits::FromPrimitive;
|
||||
use rand::distributions::Distribution;
|
||||
use rand::distributions::Uniform;
|
||||
use rand::thread_rng;
|
||||
use rand::Rng;
|
||||
use std::future::Future;
|
||||
use std::rc::Rc;
|
||||
|
@ -24,6 +26,7 @@ use rsa::RsaPrivateKey;
|
|||
use rsa::RsaPublicKey;
|
||||
|
||||
mod cipher;
|
||||
mod dh;
|
||||
mod digest;
|
||||
mod primes;
|
||||
pub mod x509;
|
||||
|
@ -534,6 +537,275 @@ pub async fn op_node_hkdf_async(
|
|||
.await?
|
||||
}
|
||||
|
||||
use rsa::pkcs1::EncodeRsaPrivateKey;
|
||||
use rsa::pkcs1::EncodeRsaPublicKey;
|
||||
|
||||
use self::primes::Prime;
|
||||
|
||||
fn generate_rsa(
|
||||
modulus_length: usize,
|
||||
public_exponent: usize,
|
||||
) -> Result<(ZeroCopyBuf, ZeroCopyBuf), AnyError> {
|
||||
let mut rng = rand::thread_rng();
|
||||
let private_key = RsaPrivateKey::new_with_exp(
|
||||
&mut rng,
|
||||
modulus_length,
|
||||
&rsa::BigUint::from_usize(public_exponent).unwrap(),
|
||||
)?;
|
||||
let public_key = private_key.to_public_key();
|
||||
let private_key_der = private_key.to_pkcs1_der()?.as_bytes().to_vec();
|
||||
let public_key_der = public_key.to_pkcs1_der()?.to_vec();
|
||||
|
||||
Ok((private_key_der.into(), public_key_der.into()))
|
||||
}
|
||||
|
||||
#[op]
|
||||
pub fn op_node_generate_rsa(
|
||||
modulus_length: usize,
|
||||
public_exponent: usize,
|
||||
) -> Result<(ZeroCopyBuf, ZeroCopyBuf), AnyError> {
|
||||
generate_rsa(modulus_length, public_exponent)
|
||||
}
|
||||
|
||||
#[op]
|
||||
pub async fn op_node_generate_rsa_async(
|
||||
modulus_length: usize,
|
||||
public_exponent: usize,
|
||||
) -> Result<(ZeroCopyBuf, ZeroCopyBuf), AnyError> {
|
||||
tokio::task::spawn_blocking(move || {
|
||||
generate_rsa(modulus_length, public_exponent)
|
||||
})
|
||||
.await?
|
||||
}
|
||||
|
||||
fn dsa_generate(
|
||||
modulus_length: usize,
|
||||
divisor_length: usize,
|
||||
) -> Result<(ZeroCopyBuf, ZeroCopyBuf), AnyError> {
|
||||
let mut rng = rand::thread_rng();
|
||||
use dsa::pkcs8::EncodePrivateKey;
|
||||
use dsa::pkcs8::EncodePublicKey;
|
||||
use dsa::Components;
|
||||
use dsa::KeySize;
|
||||
use dsa::SigningKey;
|
||||
|
||||
let key_size = match (modulus_length, divisor_length) {
|
||||
#[allow(deprecated)]
|
||||
(1024, 160) => KeySize::DSA_1024_160,
|
||||
(2048, 224) => KeySize::DSA_2048_224,
|
||||
(2048, 256) => KeySize::DSA_2048_256,
|
||||
(3072, 256) => KeySize::DSA_3072_256,
|
||||
_ => return Err(type_error("Invalid modulus_length or divisor_length")),
|
||||
};
|
||||
let components = Components::generate(&mut rng, key_size);
|
||||
let signing_key = SigningKey::generate(&mut rng, components);
|
||||
let verifying_key = signing_key.verifying_key();
|
||||
|
||||
Ok((
|
||||
signing_key
|
||||
.to_pkcs8_der()
|
||||
.map_err(|_| type_error("Not valid pkcs8"))?
|
||||
.as_bytes()
|
||||
.to_vec()
|
||||
.into(),
|
||||
verifying_key
|
||||
.to_public_key_der()
|
||||
.map_err(|_| type_error("Not valid spki"))?
|
||||
.to_vec()
|
||||
.into(),
|
||||
))
|
||||
}
|
||||
|
||||
#[op]
|
||||
pub fn op_node_dsa_generate(
|
||||
modulus_length: usize,
|
||||
divisor_length: usize,
|
||||
) -> Result<(ZeroCopyBuf, ZeroCopyBuf), AnyError> {
|
||||
dsa_generate(modulus_length, divisor_length)
|
||||
}
|
||||
|
||||
#[op]
|
||||
pub async fn op_node_dsa_generate_async(
|
||||
modulus_length: usize,
|
||||
divisor_length: usize,
|
||||
) -> Result<(ZeroCopyBuf, ZeroCopyBuf), AnyError> {
|
||||
tokio::task::spawn_blocking(move || {
|
||||
dsa_generate(modulus_length, divisor_length)
|
||||
})
|
||||
.await?
|
||||
}
|
||||
|
||||
fn ec_generate(
|
||||
named_curve: &str,
|
||||
) -> Result<(ZeroCopyBuf, ZeroCopyBuf), AnyError> {
|
||||
use ring::signature::EcdsaKeyPair;
|
||||
use ring::signature::KeyPair;
|
||||
|
||||
let curve = match named_curve {
|
||||
"P-256" => &ring::signature::ECDSA_P256_SHA256_FIXED_SIGNING,
|
||||
"P-384" => &ring::signature::ECDSA_P384_SHA384_FIXED_SIGNING,
|
||||
_ => return Err(type_error("Unsupported named curve")),
|
||||
};
|
||||
|
||||
let rng = ring::rand::SystemRandom::new();
|
||||
|
||||
let pkcs8 = EcdsaKeyPair::generate_pkcs8(curve, &rng)
|
||||
.map_err(|_| type_error("Failed to generate EC key"))?;
|
||||
|
||||
let public_key = EcdsaKeyPair::from_pkcs8(curve, pkcs8.as_ref())
|
||||
.map_err(|_| type_error("Failed to generate EC key"))?
|
||||
.public_key()
|
||||
.as_ref()
|
||||
.to_vec();
|
||||
Ok((pkcs8.as_ref().to_vec().into(), public_key.into()))
|
||||
}
|
||||
|
||||
#[op]
|
||||
pub fn op_node_ec_generate(
|
||||
named_curve: &str,
|
||||
) -> Result<(ZeroCopyBuf, ZeroCopyBuf), AnyError> {
|
||||
ec_generate(named_curve)
|
||||
}
|
||||
|
||||
#[op]
|
||||
pub async fn op_node_ec_generate_async(
|
||||
named_curve: String,
|
||||
) -> Result<(ZeroCopyBuf, ZeroCopyBuf), AnyError> {
|
||||
tokio::task::spawn_blocking(move || ec_generate(&named_curve)).await?
|
||||
}
|
||||
|
||||
fn ed25519_generate() -> Result<(ZeroCopyBuf, ZeroCopyBuf), AnyError> {
|
||||
use ring::signature::Ed25519KeyPair;
|
||||
use ring::signature::KeyPair;
|
||||
|
||||
let mut rng = thread_rng();
|
||||
let mut seed = vec![0u8; 32];
|
||||
rng.fill(seed.as_mut_slice());
|
||||
|
||||
let pair = Ed25519KeyPair::from_seed_unchecked(&seed)
|
||||
.map_err(|_| type_error("Failed to generate Ed25519 key"))?;
|
||||
|
||||
let public_key = pair.public_key().as_ref().to_vec();
|
||||
Ok((seed.into(), public_key.into()))
|
||||
}
|
||||
|
||||
#[op]
|
||||
pub fn op_node_ed25519_generate() -> Result<(ZeroCopyBuf, ZeroCopyBuf), AnyError>
|
||||
{
|
||||
ed25519_generate()
|
||||
}
|
||||
|
||||
#[op]
|
||||
pub async fn op_node_ed25519_generate_async(
|
||||
) -> Result<(ZeroCopyBuf, ZeroCopyBuf), AnyError> {
|
||||
tokio::task::spawn_blocking(ed25519_generate).await?
|
||||
}
|
||||
|
||||
fn x25519_generate() -> Result<(ZeroCopyBuf, ZeroCopyBuf), AnyError> {
|
||||
// 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 pkey = [0; 32];
|
||||
|
||||
let mut rng = thread_rng();
|
||||
rng.fill(pkey.as_mut_slice());
|
||||
|
||||
let pkey_copy = pkey.to_vec();
|
||||
// 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 pubkey = x25519_dalek::x25519(pkey, X25519_BASEPOINT_BYTES);
|
||||
|
||||
Ok((pkey_copy.into(), pubkey.to_vec().into()))
|
||||
}
|
||||
|
||||
#[op]
|
||||
pub fn op_node_x25519_generate() -> Result<(ZeroCopyBuf, ZeroCopyBuf), AnyError>
|
||||
{
|
||||
x25519_generate()
|
||||
}
|
||||
|
||||
#[op]
|
||||
pub async fn op_node_x25519_generate_async(
|
||||
) -> Result<(ZeroCopyBuf, ZeroCopyBuf), AnyError> {
|
||||
tokio::task::spawn_blocking(x25519_generate).await?
|
||||
}
|
||||
|
||||
fn dh_generate_group(
|
||||
group_name: &str,
|
||||
) -> Result<(ZeroCopyBuf, ZeroCopyBuf), AnyError> {
|
||||
let dh = match group_name {
|
||||
"modp5" => dh::DiffieHellman::group::<dh::Modp1536>(),
|
||||
"modp14" => dh::DiffieHellman::group::<dh::Modp2048>(),
|
||||
"modp15" => dh::DiffieHellman::group::<dh::Modp3072>(),
|
||||
"modp16" => dh::DiffieHellman::group::<dh::Modp4096>(),
|
||||
"modp17" => dh::DiffieHellman::group::<dh::Modp6144>(),
|
||||
"modp18" => dh::DiffieHellman::group::<dh::Modp8192>(),
|
||||
_ => return Err(type_error("Unsupported group name")),
|
||||
};
|
||||
|
||||
Ok((
|
||||
dh.private_key.into_vec().into(),
|
||||
dh.public_key.into_vec().into(),
|
||||
))
|
||||
}
|
||||
|
||||
#[op]
|
||||
pub fn op_node_dh_generate_group(
|
||||
group_name: &str,
|
||||
) -> Result<(ZeroCopyBuf, ZeroCopyBuf), AnyError> {
|
||||
dh_generate_group(group_name)
|
||||
}
|
||||
|
||||
#[op]
|
||||
pub async fn op_node_dh_generate_group_async(
|
||||
group_name: String,
|
||||
) -> Result<(ZeroCopyBuf, ZeroCopyBuf), AnyError> {
|
||||
tokio::task::spawn_blocking(move || dh_generate_group(&group_name)).await?
|
||||
}
|
||||
|
||||
fn dh_generate(
|
||||
prime: Option<&[u8]>,
|
||||
prime_len: usize,
|
||||
generator: usize,
|
||||
) -> Result<(ZeroCopyBuf, ZeroCopyBuf), AnyError> {
|
||||
let prime = prime
|
||||
.map(|p| p.into())
|
||||
.unwrap_or_else(|| Prime::generate(prime_len));
|
||||
let dh = dh::DiffieHellman::new(prime, generator);
|
||||
|
||||
Ok((
|
||||
dh.private_key.into_vec().into(),
|
||||
dh.public_key.into_vec().into(),
|
||||
))
|
||||
}
|
||||
|
||||
#[op]
|
||||
pub fn op_node_dh_generate(
|
||||
prime: Option<&[u8]>,
|
||||
prime_len: usize,
|
||||
generator: usize,
|
||||
) -> Result<(ZeroCopyBuf, ZeroCopyBuf), AnyError> {
|
||||
dh_generate(prime, prime_len, generator)
|
||||
}
|
||||
|
||||
#[op]
|
||||
pub async fn op_node_dh_generate_async(
|
||||
prime: Option<ZeroCopyBuf>,
|
||||
prime_len: usize,
|
||||
generator: usize,
|
||||
) -> Result<(ZeroCopyBuf, ZeroCopyBuf), AnyError> {
|
||||
tokio::task::spawn_blocking(move || {
|
||||
dh_generate(prime.as_deref(), prime_len, generator)
|
||||
})
|
||||
.await?
|
||||
}
|
||||
|
||||
#[op]
|
||||
pub fn op_node_random_int(min: i32, max: i32) -> Result<i32, AnyError> {
|
||||
let mut rng = rand::thread_rng();
|
||||
|
|
|
@ -1,10 +1,35 @@
|
|||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
use num_bigint::BigInt;
|
||||
use num_bigint_dig::RandPrime;
|
||||
use num_integer::Integer;
|
||||
use num_traits::One;
|
||||
use num_traits::Zero;
|
||||
use rand::Rng;
|
||||
use std::ops::Deref;
|
||||
|
||||
pub struct Prime(num_bigint_dig::BigUint);
|
||||
|
||||
impl Prime {
|
||||
pub fn generate(n: usize) -> Self {
|
||||
let mut rng = rand::thread_rng();
|
||||
Self(rng.gen_prime(n))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&[u8]> for Prime {
|
||||
fn from(value: &[u8]) -> Self {
|
||||
Self(num_bigint_dig::BigUint::from_bytes_be(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Prime {
|
||||
type Target = num_bigint_dig::BigUint;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
struct Witness {
|
||||
pow: BigInt,
|
||||
|
|
|
@ -194,6 +194,20 @@ deno_core::extension!(deno_node,
|
|||
crypto::op_node_generate_secret,
|
||||
crypto::op_node_generate_secret_async,
|
||||
crypto::op_node_sign,
|
||||
crypto::op_node_generate_rsa,
|
||||
crypto::op_node_generate_rsa_async,
|
||||
crypto::op_node_dsa_generate,
|
||||
crypto::op_node_dsa_generate_async,
|
||||
crypto::op_node_ec_generate,
|
||||
crypto::op_node_ec_generate_async,
|
||||
crypto::op_node_ed25519_generate,
|
||||
crypto::op_node_ed25519_generate_async,
|
||||
crypto::op_node_x25519_generate,
|
||||
crypto::op_node_x25519_generate_async,
|
||||
crypto::op_node_dh_generate_group,
|
||||
crypto::op_node_dh_generate_group_async,
|
||||
crypto::op_node_dh_generate,
|
||||
crypto::op_node_dh_generate_async,
|
||||
crypto::op_node_verify,
|
||||
crypto::op_node_random_int,
|
||||
crypto::op_node_scrypt_sync,
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
// deno-lint-ignore-file no-explicit-any
|
||||
|
||||
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
||||
// Copyright Joyent, Inc. and Node.js contributors. All rights reserved. MIT license.
|
||||
|
||||
|
@ -8,13 +10,20 @@ import {
|
|||
setOwnedKey,
|
||||
} from "ext:deno_node/internal/crypto/keys.ts";
|
||||
import { notImplemented } from "ext:deno_node/_utils.ts";
|
||||
import { ERR_INVALID_ARG_VALUE } from "ext:deno_node/internal/errors.ts";
|
||||
import {
|
||||
ERR_INCOMPATIBLE_OPTION_PAIR,
|
||||
ERR_INVALID_ARG_VALUE,
|
||||
ERR_MISSING_OPTION,
|
||||
} from "ext:deno_node/internal/errors.ts";
|
||||
import {
|
||||
validateBuffer,
|
||||
validateFunction,
|
||||
validateInt32,
|
||||
validateInteger,
|
||||
validateObject,
|
||||
validateOneOf,
|
||||
validateString,
|
||||
validateUint32,
|
||||
} from "ext:deno_node/internal/validators.mjs";
|
||||
import { Buffer } from "ext:deno_node/buffer.ts";
|
||||
import { KeyFormat, KeyType } from "ext:deno_node/internal/crypto/types.ts";
|
||||
|
@ -529,17 +538,34 @@ export function generateKeyPair(
|
|||
) => void,
|
||||
): void;
|
||||
export function generateKeyPair(
|
||||
_type: KeyType,
|
||||
_options: unknown,
|
||||
_callback: (
|
||||
type: KeyType,
|
||||
options: unknown,
|
||||
callback: (
|
||||
err: Error | null,
|
||||
// deno-lint-ignore no-explicit-any
|
||||
publicKey: any,
|
||||
// deno-lint-ignore no-explicit-any
|
||||
privateKey: any,
|
||||
) => void,
|
||||
) {
|
||||
notImplemented("crypto.generateKeyPair");
|
||||
createJob(kAsync, type, options).then(([privateKey, publicKey]) => {
|
||||
privateKey = new KeyObject("private", setOwnedKey(privateKey));
|
||||
publicKey = new KeyObject("public", setOwnedKey(publicKey));
|
||||
|
||||
if (typeof options === "object" && options !== null) {
|
||||
const { publicKeyEncoding, privateKeyEncoding } = options as any;
|
||||
|
||||
if (publicKeyEncoding) {
|
||||
publicKey = publicKey.export(publicKeyEncoding);
|
||||
}
|
||||
|
||||
if (privateKeyEncoding) {
|
||||
privateKey = privateKey.export(privateKeyEncoding);
|
||||
}
|
||||
}
|
||||
|
||||
callback(null, publicKey, privateKey);
|
||||
}).catch((err) => {
|
||||
callback(err, null, null);
|
||||
});
|
||||
}
|
||||
|
||||
export interface KeyPairKeyObjectResult {
|
||||
|
@ -716,12 +742,241 @@ export function generateKeyPairSync(
|
|||
options?: X448KeyPairKeyObjectOptions,
|
||||
): KeyPairKeyObjectResult;
|
||||
export function generateKeyPairSync(
|
||||
_type: KeyType,
|
||||
_options: unknown,
|
||||
type: KeyType,
|
||||
options: unknown,
|
||||
):
|
||||
| KeyPairKeyObjectResult
|
||||
| KeyPairSyncResult<string | Buffer, string | Buffer> {
|
||||
notImplemented("crypto.generateKeyPairSync");
|
||||
let [privateKey, publicKey] = createJob(kSync, type, options);
|
||||
|
||||
privateKey = new KeyObject("private", setOwnedKey(privateKey));
|
||||
publicKey = new KeyObject("public", setOwnedKey(publicKey));
|
||||
|
||||
if (typeof options === "object" && options !== null) {
|
||||
const { publicKeyEncoding, privateKeyEncoding } = options as any;
|
||||
|
||||
if (publicKeyEncoding) {
|
||||
publicKey = publicKey.export(publicKeyEncoding);
|
||||
}
|
||||
|
||||
if (privateKeyEncoding) {
|
||||
privateKey = privateKey.export(privateKeyEncoding);
|
||||
}
|
||||
}
|
||||
|
||||
return { publicKey, privateKey };
|
||||
}
|
||||
|
||||
const kSync = 0;
|
||||
const kAsync = 1;
|
||||
|
||||
function createJob(mode, type, options) {
|
||||
validateString(type, "type");
|
||||
|
||||
if (options !== undefined) {
|
||||
validateObject(options, "options");
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case "rsa":
|
||||
case "rsa-pss": {
|
||||
validateObject(options, "options");
|
||||
const { modulusLength } = options;
|
||||
validateUint32(modulusLength, "options.modulusLength");
|
||||
|
||||
let { publicExponent } = options;
|
||||
if (publicExponent == null) {
|
||||
publicExponent = 0x10001;
|
||||
} else {
|
||||
validateUint32(publicExponent, "options.publicExponent");
|
||||
}
|
||||
|
||||
if (type === "rsa") {
|
||||
if (mode === kSync) {
|
||||
return ops.op_node_generate_rsa(
|
||||
modulusLength,
|
||||
publicExponent,
|
||||
);
|
||||
} else {
|
||||
return core.opAsync(
|
||||
"op_node_generate_rsa_async",
|
||||
modulusLength,
|
||||
publicExponent,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
hash,
|
||||
mgf1Hash,
|
||||
hashAlgorithm,
|
||||
mgf1HashAlgorithm,
|
||||
saltLength,
|
||||
} = options;
|
||||
|
||||
if (saltLength !== undefined) {
|
||||
validateInt32(saltLength, "options.saltLength", 0);
|
||||
}
|
||||
if (hashAlgorithm !== undefined) {
|
||||
validateString(hashAlgorithm, "options.hashAlgorithm");
|
||||
}
|
||||
if (mgf1HashAlgorithm !== undefined) {
|
||||
validateString(mgf1HashAlgorithm, "options.mgf1HashAlgorithm");
|
||||
}
|
||||
if (hash !== undefined) {
|
||||
process.emitWarning(
|
||||
'"options.hash" is deprecated, ' +
|
||||
'use "options.hashAlgorithm" instead.',
|
||||
"DeprecationWarning",
|
||||
"DEP0154",
|
||||
);
|
||||
validateString(hash, "options.hash");
|
||||
if (hashAlgorithm && hash !== hashAlgorithm) {
|
||||
throw new ERR_INVALID_ARG_VALUE("options.hash", hash);
|
||||
}
|
||||
}
|
||||
if (mgf1Hash !== undefined) {
|
||||
process.emitWarning(
|
||||
'"options.mgf1Hash" is deprecated, ' +
|
||||
'use "options.mgf1HashAlgorithm" instead.',
|
||||
"DeprecationWarning",
|
||||
"DEP0154",
|
||||
);
|
||||
validateString(mgf1Hash, "options.mgf1Hash");
|
||||
if (mgf1HashAlgorithm && mgf1Hash !== mgf1HashAlgorithm) {
|
||||
throw new ERR_INVALID_ARG_VALUE("options.mgf1Hash", mgf1Hash);
|
||||
}
|
||||
}
|
||||
|
||||
if (mode === kSync) {
|
||||
return ops.op_node_generate_rsa(
|
||||
modulusLength,
|
||||
publicExponent,
|
||||
);
|
||||
} else {
|
||||
return core.opAsync(
|
||||
"op_node_generate_rsa_async",
|
||||
modulusLength,
|
||||
publicExponent,
|
||||
);
|
||||
}
|
||||
}
|
||||
case "dsa": {
|
||||
validateObject(options, "options");
|
||||
const { modulusLength } = options;
|
||||
validateUint32(modulusLength, "options.modulusLength");
|
||||
|
||||
let { divisorLength } = options;
|
||||
if (divisorLength == null) {
|
||||
divisorLength = 256;
|
||||
} else {
|
||||
validateInt32(divisorLength, "options.divisorLength", 0);
|
||||
}
|
||||
|
||||
if (mode === kSync) {
|
||||
return ops.op_node_dsa_generate(modulusLength, divisorLength);
|
||||
}
|
||||
return core.opAsync(
|
||||
"op_node_dsa_generate_async",
|
||||
modulusLength,
|
||||
divisorLength,
|
||||
);
|
||||
}
|
||||
case "ec": {
|
||||
validateObject(options, "options");
|
||||
const { namedCurve } = options;
|
||||
validateString(namedCurve, "options.namedCurve");
|
||||
const { paramEncoding } = options;
|
||||
if (paramEncoding == null || paramEncoding === "named") {
|
||||
// pass.
|
||||
} else if (paramEncoding === "explicit") {
|
||||
// TODO(@littledivy): Explicit param encoding is very rarely used, and not supported by the ring crate.
|
||||
throw new TypeError("Explicit encoding is not supported");
|
||||
} else {
|
||||
throw new ERR_INVALID_ARG_VALUE("options.paramEncoding", paramEncoding);
|
||||
}
|
||||
|
||||
if (mode === kSync) {
|
||||
return ops.op_node_ec_generate(namedCurve);
|
||||
} else {
|
||||
return core.opAsync("op_node_ec_generate_async", namedCurve);
|
||||
}
|
||||
}
|
||||
case "ed25519": {
|
||||
if (mode === kSync) {
|
||||
return ops.op_node_ed25519_generate();
|
||||
}
|
||||
return core.opAsync("op_node_ed25519_generate_async");
|
||||
}
|
||||
case "x25519": {
|
||||
if (mode === kSync) {
|
||||
return ops.op_node_x25519_generate();
|
||||
}
|
||||
return core.opAsync("op_node_x25519_generate_async");
|
||||
}
|
||||
case "ed448":
|
||||
case "x448": {
|
||||
notImplemented(type);
|
||||
break;
|
||||
}
|
||||
case "dh": {
|
||||
validateObject(options, "options");
|
||||
const { group, primeLength, prime, generator } = options;
|
||||
if (group != null) {
|
||||
if (prime != null) {
|
||||
throw new ERR_INCOMPATIBLE_OPTION_PAIR("group", "prime");
|
||||
}
|
||||
if (primeLength != null) {
|
||||
throw new ERR_INCOMPATIBLE_OPTION_PAIR("group", "primeLength");
|
||||
}
|
||||
if (generator != null) {
|
||||
throw new ERR_INCOMPATIBLE_OPTION_PAIR("group", "generator");
|
||||
}
|
||||
|
||||
validateString(group, "options.group");
|
||||
|
||||
if (mode === kSync) {
|
||||
return ops.op_node_dh_generate_group(group);
|
||||
} else {
|
||||
return core.opAsync("op_node_dh_generate_group_async", group);
|
||||
}
|
||||
}
|
||||
|
||||
if (prime != null) {
|
||||
if (primeLength != null) {
|
||||
throw new ERR_INCOMPATIBLE_OPTION_PAIR("prime", "primeLength");
|
||||
}
|
||||
|
||||
validateBuffer(prime, "options.prime");
|
||||
} else if (primeLength != null) {
|
||||
validateInt32(primeLength, "options.primeLength", 0);
|
||||
} else {
|
||||
throw new ERR_MISSING_OPTION(
|
||||
"At least one of the group, prime, or primeLength options",
|
||||
);
|
||||
}
|
||||
|
||||
if (generator != null) {
|
||||
validateInt32(generator, "options.generator", 0);
|
||||
}
|
||||
|
||||
const g = generator == null ? 2 : generator;
|
||||
|
||||
if (mode === kSync) {
|
||||
return ops.op_node_dh_generate(prime, primeLength ?? 0, g);
|
||||
} else {
|
||||
return core.opAsync(
|
||||
"op_node_dh_generate_async",
|
||||
prime,
|
||||
primeLength ?? 0,
|
||||
g,
|
||||
);
|
||||
}
|
||||
}
|
||||
default:
|
||||
// Fall through
|
||||
}
|
||||
throw new ERR_INVALID_ARG_VALUE("type", type, "must be a supported key type");
|
||||
}
|
||||
|
||||
export default {
|
||||
|
|
Loading…
Reference in a new issue