mirror of
https://github.com/denoland/deno.git
synced 2025-09-27 04:39:10 +00: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";
|
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() {
|
unitTest(async function testGenerateRSAKey() {
|
||||||
const subtle = window.crypto.subtle;
|
const subtle = window.crypto.subtle;
|
||||||
assert(subtle);
|
assert(subtle);
|
||||||
|
|
|
@ -65,6 +65,10 @@
|
||||||
"ECDSA": "EcdsaParams",
|
"ECDSA": "EcdsaParams",
|
||||||
"HMAC": null,
|
"HMAC": null,
|
||||||
},
|
},
|
||||||
|
"verify": {
|
||||||
|
"RSASSA-PKCS1-v1_5": null,
|
||||||
|
"RSA-PSS": "RsaPssParams",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// See https://www.w3.org/TR/WebCryptoAPI/#dfn-normalize-an-algorithm
|
// See https://www.w3.org/TR/WebCryptoAPI/#dfn-normalize-an-algorithm
|
||||||
|
@ -410,6 +414,113 @@
|
||||||
throw new TypeError("unreachable");
|
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 {string} algorithm
|
||||||
* @param {boolean} extractable
|
* @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
|
| DataView
|
||||||
| ArrayBuffer,
|
| ArrayBuffer,
|
||||||
): Promise<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(
|
digest(
|
||||||
algorithm: AlgorithmIdentifier,
|
algorithm: AlgorithmIdentifier,
|
||||||
data:
|
data:
|
||||||
|
|
|
@ -34,7 +34,9 @@ use ring::signature::EcdsaSigningAlgorithm;
|
||||||
use rsa::padding::PaddingScheme;
|
use rsa::padding::PaddingScheme;
|
||||||
use rsa::BigUint;
|
use rsa::BigUint;
|
||||||
use rsa::PrivateKeyEncoding;
|
use rsa::PrivateKeyEncoding;
|
||||||
|
use rsa::PublicKey;
|
||||||
use rsa::RSAPrivateKey;
|
use rsa::RSAPrivateKey;
|
||||||
|
use rsa::RSAPublicKey;
|
||||||
use sha1::Sha1;
|
use sha1::Sha1;
|
||||||
use sha2::Digest;
|
use sha2::Digest;
|
||||||
use sha2::Sha256;
|
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_generate_key", op_async(op_crypto_generate_key)),
|
||||||
("op_crypto_sign_key", op_async(op_crypto_sign_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_subtle_digest", op_async(op_crypto_subtle_digest)),
|
||||||
("op_crypto_random_uuid", op_sync(op_crypto_random_uuid)),
|
("op_crypto_random_uuid", op_sync(op_crypto_random_uuid)),
|
||||||
])
|
])
|
||||||
|
@ -378,6 +381,136 @@ pub async fn op_crypto_sign_key(
|
||||||
Ok(signature.into())
|
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(
|
pub fn op_crypto_random_uuid(
|
||||||
state: &mut OpState,
|
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: 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: 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: 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: 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: 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",
|
"SubtleCrypto interface: crypto.subtle must inherit property \"deriveBits(AlgorithmIdentifier, CryptoKey, unsigned long)\" with the proper type",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue