summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cli/tests/unit/webcrypto_test.ts49
-rw-r--r--extensions/crypto/00_crypto.js111
-rw-r--r--extensions/crypto/lib.deno_crypto.d.ts28
-rw-r--r--extensions/crypto/lib.rs133
-rw-r--r--tools/wpt/expectation.json2
5 files changed, 321 insertions, 2 deletions
diff --git a/cli/tests/unit/webcrypto_test.ts b/cli/tests/unit/webcrypto_test.ts
index b507a0c58..4af31789a 100644
--- a/cli/tests/unit/webcrypto_test.ts
+++ b/cli/tests/unit/webcrypto_test.ts
@@ -1,5 +1,54 @@
import { assert, assertEquals, unitTest } from "./test_util.ts";
+// TODO(@littledivy): Remove this when we enable WPT for sign_verify
+unitTest(async function testSignVerify() {
+ const subtle = window.crypto.subtle;
+ assert(subtle);
+ for (const algorithm of ["RSA-PSS", "RSASSA-PKCS1-v1_5"]) {
+ for (
+ const hash of [
+ "SHA-1",
+ "SHA-256",
+ "SHA-384",
+ "SHA-512",
+ ]
+ ) {
+ const keyPair = await subtle.generateKey(
+ {
+ name: algorithm,
+ modulusLength: 2048,
+ publicExponent: new Uint8Array([1, 0, 1]),
+ hash,
+ },
+ true,
+ ["sign", "verify"],
+ );
+
+ const data = new Uint8Array([1, 2, 3]);
+ const signAlgorithm = { name: algorithm, saltLength: 32 };
+
+ const signature = await subtle.sign(
+ signAlgorithm,
+ keyPair.privateKey,
+ data,
+ );
+
+ assert(signature);
+ assert(signature.byteLength > 0);
+ assert(signature.byteLength % 8 == 0);
+ assert(signature instanceof ArrayBuffer);
+
+ const verified = await subtle.verify(
+ signAlgorithm,
+ keyPair.publicKey,
+ signature,
+ data,
+ );
+ assert(verified);
+ }
+ }
+});
+
unitTest(async function testGenerateRSAKey() {
const subtle = window.crypto.subtle;
assert(subtle);
diff --git a/extensions/crypto/00_crypto.js b/extensions/crypto/00_crypto.js
index 936bf7cff..896a11570 100644
--- a/extensions/crypto/00_crypto.js
+++ b/extensions/crypto/00_crypto.js
@@ -65,6 +65,10 @@
"ECDSA": "EcdsaParams",
"HMAC": null,
},
+ "verify": {
+ "RSASSA-PKCS1-v1_5": null,
+ "RSA-PSS": "RsaPssParams",
+ },
};
// See https://www.w3.org/TR/WebCryptoAPI/#dfn-normalize-an-algorithm
@@ -412,6 +416,113 @@
/**
* @param {string} algorithm
+ * @param {CryptoKey} key
+ * @param {BufferSource} signature
+ * @param {BufferSource} data
+ * @returns {Promise<boolean>}
+ */
+ async verify(algorithm, key, signature, data) {
+ webidl.assertBranded(this, SubtleCrypto);
+ const prefix = "Failed to execute 'verify' on 'SubtleCrypto'";
+ webidl.requiredArguments(arguments.length, 4, { prefix });
+ algorithm = webidl.converters.AlgorithmIdentifier(algorithm, {
+ prefix,
+ context: "Argument 1",
+ });
+ key = webidl.converters.CryptoKey(key, {
+ prefix,
+ context: "Argument 2",
+ });
+ signature = webidl.converters.BufferSource(signature, {
+ prefix,
+ context: "Argument 3",
+ });
+ data = webidl.converters.BufferSource(data, {
+ prefix,
+ context: "Argument 4",
+ });
+
+ // 2.
+ if (ArrayBuffer.isView(signature)) {
+ signature = new Uint8Array(
+ signature.buffer,
+ signature.byteOffset,
+ signature.byteLength,
+ );
+ } else {
+ signature = new Uint8Array(signature);
+ }
+ signature = signature.slice();
+
+ // 3.
+ if (ArrayBuffer.isView(data)) {
+ data = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
+ } else {
+ data = new Uint8Array(data);
+ }
+ data = data.slice();
+
+ const normalizedAlgorithm = normalizeAlgorithm(algorithm, "verify");
+
+ const handle = key[_handle];
+ const keyData = KEY_STORE.get(handle);
+
+ if (normalizedAlgorithm.name !== key[_algorithm].name) {
+ throw new DOMException(
+ "Verifying algorithm doesn't match key algorithm.",
+ "InvalidAccessError",
+ );
+ }
+
+ if (!key[_usages].includes("verify")) {
+ throw new DOMException(
+ "Key does not support the 'verify' operation.",
+ "InvalidAccessError",
+ );
+ }
+
+ switch (normalizedAlgorithm.name) {
+ case "RSASSA-PKCS1-v1_5": {
+ if (key[_type] !== "public") {
+ throw new DOMException(
+ "Key type not supported",
+ "InvalidAccessError",
+ );
+ }
+
+ const hashAlgorithm = key[_algorithm].hash.name;
+ return await core.opAsync("op_crypto_verify_key", {
+ key: keyData,
+ algorithm: "RSASSA-PKCS1-v1_5",
+ hash: hashAlgorithm,
+ signature,
+ }, data);
+ }
+ case "RSA-PSS": {
+ if (key[_type] !== "public") {
+ throw new DOMException(
+ "Key type not supported",
+ "InvalidAccessError",
+ );
+ }
+
+ const hashAlgorithm = key[_algorithm].hash.name;
+ const saltLength = normalizedAlgorithm.saltLength;
+ return await core.opAsync("op_crypto_verify_key", {
+ key: keyData,
+ algorithm: "RSA-PSS",
+ hash: hashAlgorithm,
+ saltLength,
+ signature,
+ }, data);
+ }
+ }
+
+ throw new TypeError("unreachable");
+ }
+
+ /**
+ * @param {string} algorithm
* @param {boolean} extractable
* @param {KeyUsage[]} keyUsages
* @returns {Promise<any>}
diff --git a/extensions/crypto/lib.deno_crypto.d.ts b/extensions/crypto/lib.deno_crypto.d.ts
index 0798af59f..d4d42e581 100644
--- a/extensions/crypto/lib.deno_crypto.d.ts
+++ b/extensions/crypto/lib.deno_crypto.d.ts
@@ -111,6 +111,34 @@ interface SubtleCrypto {
| DataView
| ArrayBuffer,
): Promise<ArrayBuffer>;
+ verify(
+ algorithm: AlgorithmIdentifier | RsaPssParams,
+ key: CryptoKey,
+ signature:
+ | Int8Array
+ | Int16Array
+ | Int32Array
+ | Uint8Array
+ | Uint16Array
+ | Uint32Array
+ | Uint8ClampedArray
+ | Float32Array
+ | Float64Array
+ | DataView
+ | ArrayBuffer,
+ data:
+ | Int8Array
+ | Int16Array
+ | Int32Array
+ | Uint8Array
+ | Uint16Array
+ | Uint32Array
+ | Uint8ClampedArray
+ | Float32Array
+ | Float64Array
+ | DataView
+ | ArrayBuffer,
+ ): Promise<boolean>;
digest(
algorithm: AlgorithmIdentifier,
data:
diff --git a/extensions/crypto/lib.rs b/extensions/crypto/lib.rs
index 66e576c6a..d390afdab 100644
--- a/extensions/crypto/lib.rs
+++ b/extensions/crypto/lib.rs
@@ -34,7 +34,9 @@ use ring::signature::EcdsaSigningAlgorithm;
use rsa::padding::PaddingScheme;
use rsa::BigUint;
use rsa::PrivateKeyEncoding;
+use rsa::PublicKey;
use rsa::RSAPrivateKey;
+use rsa::RSAPublicKey;
use sha1::Sha1;
use sha2::Digest;
use sha2::Sha256;
@@ -70,6 +72,7 @@ pub fn init(maybe_seed: Option<u64>) -> Extension {
),
("op_crypto_generate_key", op_async(op_crypto_generate_key)),
("op_crypto_sign_key", op_async(op_crypto_sign_key)),
+ ("op_crypto_verify_key", op_async(op_crypto_verify_key)),
("op_crypto_subtle_digest", op_async(op_crypto_subtle_digest)),
("op_crypto_random_uuid", op_sync(op_crypto_random_uuid)),
])
@@ -378,6 +381,136 @@ pub async fn op_crypto_sign_key(
Ok(signature.into())
}
+#[derive(Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct VerifyArg {
+ key: KeyData,
+ algorithm: Algorithm,
+ salt_length: Option<u32>,
+ hash: Option<CryptoHash>,
+ signature: ZeroCopyBuf,
+}
+
+pub async fn op_crypto_verify_key(
+ _state: Rc<RefCell<OpState>>,
+ args: VerifyArg,
+ zero_copy: Option<ZeroCopyBuf>,
+) -> Result<bool, AnyError> {
+ let zero_copy = zero_copy.ok_or_else(null_opbuf)?;
+ let data = &*zero_copy;
+ let algorithm = args.algorithm;
+
+ let verification = match algorithm {
+ Algorithm::RsassaPkcs1v15 => {
+ let public_key: RSAPublicKey =
+ RSAPrivateKey::from_pkcs8(&*args.key.data)?.to_public_key();
+ let (padding, hashed) = match args
+ .hash
+ .ok_or_else(|| type_error("Missing argument hash".to_string()))?
+ {
+ CryptoHash::Sha1 => {
+ let mut hasher = Sha1::new();
+ hasher.update(&data);
+ (
+ PaddingScheme::PKCS1v15Sign {
+ hash: Some(rsa::hash::Hash::SHA1),
+ },
+ hasher.finalize()[..].to_vec(),
+ )
+ }
+ CryptoHash::Sha256 => {
+ let mut hasher = Sha256::new();
+ hasher.update(&data);
+ (
+ PaddingScheme::PKCS1v15Sign {
+ hash: Some(rsa::hash::Hash::SHA2_256),
+ },
+ hasher.finalize()[..].to_vec(),
+ )
+ }
+ CryptoHash::Sha384 => {
+ let mut hasher = Sha384::new();
+ hasher.update(&data);
+ (
+ PaddingScheme::PKCS1v15Sign {
+ hash: Some(rsa::hash::Hash::SHA2_384),
+ },
+ hasher.finalize()[..].to_vec(),
+ )
+ }
+ CryptoHash::Sha512 => {
+ let mut hasher = Sha512::new();
+ hasher.update(&data);
+ (
+ PaddingScheme::PKCS1v15Sign {
+ hash: Some(rsa::hash::Hash::SHA2_512),
+ },
+ hasher.finalize()[..].to_vec(),
+ )
+ }
+ };
+
+ public_key
+ .verify(padding, &hashed, &*args.signature)
+ .is_ok()
+ }
+ Algorithm::RsaPss => {
+ let salt_len = args
+ .salt_length
+ .ok_or_else(|| type_error("Missing argument saltLength".to_string()))?
+ as usize;
+ let public_key: RSAPublicKey =
+ RSAPrivateKey::from_pkcs8(&*args.key.data)?.to_public_key();
+
+ let rng = OsRng;
+ let (padding, hashed) = match args
+ .hash
+ .ok_or_else(|| type_error("Missing argument hash".to_string()))?
+ {
+ CryptoHash::Sha1 => {
+ let mut hasher = Sha1::new();
+ hasher.update(&data);
+ (
+ PaddingScheme::new_pss_with_salt::<Sha1, _>(rng, salt_len),
+ hasher.finalize()[..].to_vec(),
+ )
+ }
+ CryptoHash::Sha256 => {
+ let mut hasher = Sha256::new();
+ hasher.update(&data);
+ (
+ PaddingScheme::new_pss_with_salt::<Sha256, _>(rng, salt_len),
+ hasher.finalize()[..].to_vec(),
+ )
+ }
+ CryptoHash::Sha384 => {
+ let mut hasher = Sha384::new();
+ hasher.update(&data);
+ (
+ PaddingScheme::new_pss_with_salt::<Sha384, _>(rng, salt_len),
+ hasher.finalize()[..].to_vec(),
+ )
+ }
+ CryptoHash::Sha512 => {
+ let mut hasher = Sha512::new();
+ hasher.update(&data);
+ (
+ PaddingScheme::new_pss_with_salt::<Sha512, _>(rng, salt_len),
+ hasher.finalize()[..].to_vec(),
+ )
+ }
+ };
+
+ public_key
+ .verify(padding, &hashed, &*args.signature)
+ .is_ok()
+ }
+ _ => return Err(type_error("Unsupported algorithm".to_string())),
+ };
+
+ Ok(verification)
+}
+
pub fn op_crypto_random_uuid(
state: &mut OpState,
_: (),
diff --git a/tools/wpt/expectation.json b/tools/wpt/expectation.json
index cf03ced80..fbab59d68 100644
--- a/tools/wpt/expectation.json
+++ b/tools/wpt/expectation.json
@@ -1932,8 +1932,6 @@
"SubtleCrypto interface: calling encrypt(AlgorithmIdentifier, CryptoKey, BufferSource) on crypto.subtle with too few arguments must throw TypeError",
"SubtleCrypto interface: crypto.subtle must inherit property \"decrypt(AlgorithmIdentifier, CryptoKey, BufferSource)\" with the proper type",
"SubtleCrypto interface: calling decrypt(AlgorithmIdentifier, CryptoKey, BufferSource) on crypto.subtle with too few arguments must throw TypeError",
- "SubtleCrypto interface: crypto.subtle must inherit property \"verify(AlgorithmIdentifier, CryptoKey, BufferSource, BufferSource)\" with the proper type",
- "SubtleCrypto interface: calling verify(AlgorithmIdentifier, CryptoKey, BufferSource, BufferSource) on crypto.subtle with too few arguments must throw TypeError",
"SubtleCrypto interface: crypto.subtle must inherit property \"deriveKey(AlgorithmIdentifier, CryptoKey, AlgorithmIdentifier, boolean, sequence<KeyUsage>)\" with the proper type",
"SubtleCrypto interface: calling deriveKey(AlgorithmIdentifier, CryptoKey, AlgorithmIdentifier, boolean, sequence<KeyUsage>) on crypto.subtle with too few arguments must throw TypeError",
"SubtleCrypto interface: crypto.subtle must inherit property \"deriveBits(AlgorithmIdentifier, CryptoKey, unsigned long)\" with the proper type",