Skip to content

Commit

Permalink
feat(extensions/crypto): implement verify() for RSA (#11312)
Browse files Browse the repository at this point in the history
  • Loading branch information
littledivy authored Jul 12, 2021
1 parent e95c0c8 commit 00484d2
Show file tree
Hide file tree
Showing 5 changed files with 321 additions and 2 deletions.
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 @@ -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
Expand Down Expand Up @@ -410,6 +414,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, 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
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 @@ -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",
Expand Down

0 comments on commit 00484d2

Please sign in to comment.