Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(extensions/crypto): implement verify() for RSA #11312

Merged
merged 14 commits into from
Jul 12, 2021
Merged
49 changes: 49 additions & 0 deletions cli/tests/unit/webcrypto_test.ts
Original file line number Diff line number Diff line change
@@ -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);
Expand Down
111 changes: 111 additions & 0 deletions extensions/crypto/00_crypto.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,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
Expand Down Expand Up @@ -383,6 +387,113 @@
throw new TypeError("unreachable");
}

/**
* @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, 3, { prefix });
littledivy marked this conversation as resolved.
Show resolved Hide resolved
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
Expand Down
28 changes: 28 additions & 0 deletions extensions/crypto/lib.deno_crypto.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
133 changes: 133 additions & 0 deletions extensions/crypto/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)),
])
Expand Down Expand Up @@ -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,
_: (),
Expand Down
2 changes: 0 additions & 2 deletions tools/wpt/expectation.json
Original file line number Diff line number Diff line change
Expand Up @@ -1933,8 +1933,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",
Expand Down