mirror of
https://github.com/denoland/deno.git
synced 2025-01-21 21:50:00 -05:00
feat(extensions/crypto): implement verify() for RSA (#11312)
This commit is contained in:
parent
e95c0c85fa
commit
00484d24ba
5 changed files with 321 additions and 2 deletions
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
@ -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
|
||||
|
|
28
extensions/crypto/lib.deno_crypto.d.ts
vendored
28
extensions/crypto/lib.deno_crypto.d.ts
vendored
|
@ -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:
|
||||
|
|
|
@ -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,
|
||||
_: (),
|
||||
|
|
|
@ -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",
|
||||
|
|
Loading…
Add table
Reference in a new issue