1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-29 16:30:56 -05:00
denoland-deno/cli/tests/unit/webcrypto_test.ts

715 lines
18 KiB
TypeScript

import { assert, assertEquals, assertRejects } from "./test_util.ts";
// https://github.com/denoland/deno/issues/11664
Deno.test(async function testImportArrayBufferKey() {
const subtle = window.crypto.subtle;
assert(subtle);
// deno-fmt-ignore
const key = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]);
const cryptoKey = await subtle.importKey(
"raw",
key.buffer,
{ name: "HMAC", hash: "SHA-1" },
true,
["sign"],
);
assert(cryptoKey);
// Test key usage
await subtle.sign({ name: "HMAC" }, cryptoKey, new Uint8Array(8));
});
// TODO(@littledivy): Remove this when we enable WPT for sign_verify
Deno.test(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);
}
}
});
// deno-fmt-ignore
const plainText = new Uint8Array([95, 77, 186, 79, 50, 12, 12, 232, 118, 114, 90, 252, 229, 251, 210, 91, 248, 62, 90, 113, 37, 160, 140, 175, 231, 60, 62, 186, 196, 33, 119, 157, 249, 213, 93, 24, 12, 58, 233, 148, 38, 69, 225, 216, 47, 238, 140, 157, 41, 75, 60, 177, 160, 138, 153, 49, 32, 27, 60, 14, 129, 252, 71, 202, 207, 131, 21, 162, 175, 102, 50, 65, 19, 195, 182, 98, 48, 195, 70, 8, 196, 244, 89, 54, 52, 206, 2, 178, 103, 54, 34, 119, 240, 168, 64, 202, 116, 188, 61, 26, 98, 54, 149, 44, 94, 215, 170, 248, 168, 254, 203, 221, 250, 117, 132, 230, 151, 140, 234, 93, 42, 91, 159, 183, 241, 180, 140, 139, 11, 229, 138, 48, 82, 2, 117, 77, 131, 118, 16, 115, 116, 121, 60, 240, 38, 170, 238, 83, 0, 114, 125, 131, 108, 215, 30, 113, 179, 69, 221, 178, 228, 68, 70, 255, 197, 185, 1, 99, 84, 19, 137, 13, 145, 14, 163, 128, 152, 74, 144, 25, 16, 49, 50, 63, 22, 219, 204, 157, 107, 225, 104, 184, 72, 133, 56, 76, 160, 62, 18, 96, 10, 193, 194, 72, 2, 138, 243, 114, 108, 201, 52, 99, 136, 46, 168, 192, 42, 171]);
// Passing
const hashPlainTextVector = [
{
hash: "SHA-1",
plainText: plainText.slice(0, 214),
},
{
hash: "SHA-256",
plainText: plainText.slice(0, 190),
},
{
hash: "SHA-384",
plainText: plainText.slice(0, 158),
},
{
hash: "SHA-512",
plainText: plainText.slice(0, 126),
},
];
// TODO(@littledivy): Remove this when we enable WPT for encrypt_decrypt
Deno.test(async function testEncryptDecrypt() {
const subtle = window.crypto.subtle;
assert(subtle);
for (
const { hash, plainText } of hashPlainTextVector
) {
const keyPair = await subtle.generateKey(
{
name: "RSA-OAEP",
modulusLength: 2048,
publicExponent: new Uint8Array([1, 0, 1]),
hash,
},
true,
["encrypt", "decrypt"],
);
const encryptAlgorithm = { name: "RSA-OAEP" };
const cipherText = await subtle.encrypt(
encryptAlgorithm,
keyPair.publicKey,
plainText,
);
assert(cipherText);
assert(cipherText.byteLength > 0);
assertEquals(cipherText.byteLength * 8, 2048);
assert(cipherText instanceof ArrayBuffer);
const decrypted = await subtle.decrypt(
encryptAlgorithm,
keyPair.privateKey,
cipherText,
);
assert(decrypted);
assert(decrypted instanceof ArrayBuffer);
assertEquals(new Uint8Array(decrypted), plainText);
const badPlainText = new Uint8Array(plainText.byteLength + 1);
badPlainText.set(plainText, 0);
badPlainText.set(new Uint8Array([32]), plainText.byteLength);
await assertRejects(async () => {
// Should fail
await subtle.encrypt(
encryptAlgorithm,
keyPair.publicKey,
badPlainText,
);
throw new TypeError("unreachable");
}, DOMException);
}
});
Deno.test(async function testGenerateRSAKey() {
const subtle = window.crypto.subtle;
assert(subtle);
const keyPair = await subtle.generateKey(
{
name: "RSA-PSS",
modulusLength: 2048,
publicExponent: new Uint8Array([1, 0, 1]),
hash: "SHA-256",
},
true,
["sign", "verify"],
);
assert(keyPair.privateKey);
assert(keyPair.publicKey);
assertEquals(keyPair.privateKey.extractable, true);
assert(keyPair.privateKey.usages.includes("sign"));
});
Deno.test(async function testGenerateHMACKey() {
const key = await window.crypto.subtle.generateKey(
{
name: "HMAC",
hash: "SHA-512",
},
true,
["sign", "verify"],
);
assert(key);
assertEquals(key.extractable, true);
assert(key.usages.includes("sign"));
});
Deno.test(async function testECDSASignVerify() {
const key = await window.crypto.subtle.generateKey(
{
name: "ECDSA",
namedCurve: "P-384",
},
true,
["sign", "verify"],
);
const encoder = new TextEncoder();
const encoded = encoder.encode("Hello, World!");
const signature = await window.crypto.subtle.sign(
{ name: "ECDSA", hash: "SHA-384" },
key.privateKey,
encoded,
);
assert(signature);
assert(signature instanceof ArrayBuffer);
const verified = await window.crypto.subtle.verify(
{ hash: { name: "SHA-384" }, name: "ECDSA" },
key.publicKey,
signature,
encoded,
);
assert(verified);
});
// Tests the "bad paths" as a temporary replacement for sign_verify/ecdsa WPT.
Deno.test(async function testECDSASignVerifyFail() {
const key = await window.crypto.subtle.generateKey(
{
name: "ECDSA",
namedCurve: "P-384",
},
true,
["sign", "verify"],
);
const encoded = new Uint8Array([1]);
// Signing with a public key (InvalidAccessError)
await assertRejects(async () => {
await window.crypto.subtle.sign(
{ name: "ECDSA", hash: "SHA-384" },
key.publicKey,
new Uint8Array([1]),
);
throw new TypeError("unreachable");
}, DOMException);
// Do a valid sign for later verifying.
const signature = await window.crypto.subtle.sign(
{ name: "ECDSA", hash: "SHA-384" },
key.privateKey,
encoded,
);
// Verifying with a private key (InvalidAccessError)
await assertRejects(async () => {
await window.crypto.subtle.verify(
{ hash: { name: "SHA-384" }, name: "ECDSA" },
key.privateKey,
signature,
encoded,
);
throw new TypeError("unreachable");
}, DOMException);
});
// https://github.com/denoland/deno/issues/11313
Deno.test(async function testSignRSASSAKey() {
const subtle = window.crypto.subtle;
assert(subtle);
const keyPair = await subtle.generateKey(
{
name: "RSASSA-PKCS1-v1_5",
modulusLength: 2048,
publicExponent: new Uint8Array([1, 0, 1]),
hash: "SHA-256",
},
true,
["sign", "verify"],
);
assert(keyPair.privateKey);
assert(keyPair.publicKey);
assertEquals(keyPair.privateKey.extractable, true);
assert(keyPair.privateKey.usages.includes("sign"));
const encoder = new TextEncoder();
const encoded = encoder.encode("Hello, World!");
const signature = await window.crypto.subtle.sign(
{ name: "RSASSA-PKCS1-v1_5" },
keyPair.privateKey,
encoded,
);
assert(signature);
});
// deno-fmt-ignore
const rawKey = new Uint8Array([
1, 2, 3, 4, 5, 6, 7, 8,
9, 10, 11, 12, 13, 14, 15, 16
]);
const jwk: JsonWebKey = {
kty: "oct",
// unpadded base64 for rawKey.
k: "AQIDBAUGBwgJCgsMDQ4PEA",
alg: "HS256",
ext: true,
"key_ops": ["sign"],
};
Deno.test(async function subtleCryptoHmacImportExport() {
const key1 = await crypto.subtle.importKey(
"raw",
rawKey,
{ name: "HMAC", hash: "SHA-256" },
true,
["sign"],
);
const key2 = await crypto.subtle.importKey(
"jwk",
jwk,
{ name: "HMAC", hash: "SHA-256" },
true,
["sign"],
);
const actual1 = await crypto.subtle.sign(
{ name: "HMAC" },
key1,
new Uint8Array([1, 2, 3, 4]),
);
const actual2 = await crypto.subtle.sign(
{ name: "HMAC" },
key2,
new Uint8Array([1, 2, 3, 4]),
);
// deno-fmt-ignore
const expected = new Uint8Array([
59, 170, 255, 216, 51, 141, 51, 194,
213, 48, 41, 191, 184, 40, 216, 47,
130, 165, 203, 26, 163, 43, 38, 71,
23, 122, 222, 1, 146, 46, 182, 87,
]);
assertEquals(
new Uint8Array(actual1),
expected,
);
assertEquals(
new Uint8Array(actual2),
expected,
);
const exportedKey1 = await crypto.subtle.exportKey("raw", key1);
assertEquals(new Uint8Array(exportedKey1), rawKey);
const exportedKey2 = await crypto.subtle.exportKey("jwk", key2);
assertEquals(exportedKey2, jwk);
});
// https://github.com/denoland/deno/issues/12085
Deno.test(async function generateImportHmacJwk() {
const key = await crypto.subtle.generateKey(
{
name: "HMAC",
hash: "SHA-512",
},
true,
["sign"],
);
assert(key);
assertEquals(key.type, "secret");
assertEquals(key.extractable, true);
assertEquals(key.usages, ["sign"]);
const exportedKey = await crypto.subtle.exportKey("jwk", key);
assertEquals(exportedKey.kty, "oct");
assertEquals(exportedKey.alg, "HS512");
assertEquals(exportedKey.key_ops, ["sign"]);
assertEquals(exportedKey.ext, true);
assert(typeof exportedKey.k == "string");
assertEquals(exportedKey.k.length, 171);
});
// 2048-bits publicExponent=65537
const pkcs8TestVectors = [
// rsaEncryption
{ pem: "cli/tests/testdata/webcrypto/id_rsaEncryption.pem", hash: "SHA-256" },
// id-RSASSA-PSS (sha256)
// `openssl genpkey -algorithm rsa-pss -pkeyopt rsa_pss_keygen_md:sha256 -out id_rsassaPss.pem`
{ pem: "cli/tests/testdata/webcrypto/id_rsassaPss.pem", hash: "SHA-256" },
// id-RSASSA-PSS (default parameters)
// `openssl genpkey -algorithm rsa-pss -out id_rsassaPss.pem`
{
pem: "cli/tests/testdata/webcrypto/id_rsassaPss_default.pem",
hash: "SHA-1",
},
// id-RSASSA-PSS (default hash)
// `openssl genpkey -algorithm rsa-pss -pkeyopt rsa_pss_keygen_saltlen:30 -out rsaPss_saltLen_30.pem`
{
pem: "cli/tests/testdata/webcrypto/id_rsassaPss_saltLen_30.pem",
hash: "SHA-1",
},
];
Deno.test({ permissions: { read: true } }, async function importRsaPkcs8() {
const pemHeader = "-----BEGIN PRIVATE KEY-----";
const pemFooter = "-----END PRIVATE KEY-----";
for (const { pem, hash } of pkcs8TestVectors) {
const keyFile = await Deno.readTextFile(pem);
const pemContents = keyFile.substring(
pemHeader.length,
keyFile.length - pemFooter.length,
);
const binaryDerString = atob(pemContents);
const binaryDer = new Uint8Array(binaryDerString.length);
for (let i = 0; i < binaryDerString.length; i++) {
binaryDer[i] = binaryDerString.charCodeAt(i);
}
const key = await crypto.subtle.importKey(
"pkcs8",
binaryDer,
{ name: "RSA-PSS", hash },
true,
["sign"],
);
assert(key);
assertEquals(key.type, "private");
assertEquals(key.extractable, true);
assertEquals(key.usages, ["sign"]);
const algorithm = key.algorithm as RsaHashedKeyAlgorithm;
assertEquals(algorithm.name, "RSA-PSS");
assertEquals(algorithm.hash.name, hash);
assertEquals(algorithm.modulusLength, 2048);
assertEquals(algorithm.publicExponent, new Uint8Array([1, 0, 1]));
}
});
// deno-fmt-ignore
const asn1AlgorithmIdentifier = new Uint8Array([
0x02, 0x01, 0x00, // INTEGER
0x30, 0x0d, // SEQUENCE (2 elements)
0x06, 0x09, // OBJECT IDENTIFIER
0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, // 1.2.840.113549.1.1.1 (rsaEncryption)
0x05, 0x00, // NULL
]);
Deno.test(async function rsaExport() {
for (const algorithm of ["RSASSA-PKCS1-v1_5", "RSA-PSS", "RSA-OAEP"]) {
const keyPair = await crypto.subtle.generateKey(
{
name: algorithm,
modulusLength: 2048,
publicExponent: new Uint8Array([1, 0, 1]),
hash: "SHA-256",
},
true,
algorithm !== "RSA-OAEP" ? ["sign", "verify"] : ["encrypt", "decrypt"],
);
assert(keyPair.privateKey);
assert(keyPair.publicKey);
assertEquals(keyPair.privateKey.extractable, true);
const exportedPrivateKey = await crypto.subtle.exportKey(
"pkcs8",
keyPair.privateKey,
);
assert(exportedPrivateKey);
assert(exportedPrivateKey instanceof ArrayBuffer);
const pkcs8 = new Uint8Array(exportedPrivateKey);
assert(pkcs8.length > 0);
assertEquals(
pkcs8.slice(4, asn1AlgorithmIdentifier.byteLength + 4),
asn1AlgorithmIdentifier,
);
const exportedPublicKey = await crypto.subtle.exportKey(
"spki",
keyPair.publicKey,
);
const spki = new Uint8Array(exportedPublicKey);
assert(spki.length > 0);
assertEquals(
spki.slice(4, asn1AlgorithmIdentifier.byteLength + 1),
asn1AlgorithmIdentifier.slice(3),
);
}
});
Deno.test(async function testHkdfDeriveBits() {
const rawKey = await crypto.getRandomValues(new Uint8Array(16));
const key = await crypto.subtle.importKey(
"raw",
rawKey,
{ name: "HKDF", hash: "SHA-256" },
false,
["deriveBits"],
);
const salt = await crypto.getRandomValues(new Uint8Array(16));
const info = await crypto.getRandomValues(new Uint8Array(16));
const result = await crypto.subtle.deriveBits(
{
name: "HKDF",
hash: "SHA-256",
salt: salt,
info: info,
},
key,
128,
);
assertEquals(result.byteLength, 128 / 8);
});
Deno.test(async function testHkdfDeriveBitsWithLargeKeySize() {
const key = await crypto.subtle.importKey(
"raw",
new Uint8Array([0x00]),
"HKDF",
false,
["deriveBits"],
);
assertRejects(
() =>
crypto.subtle.deriveBits(
{
name: "HKDF",
hash: "SHA-1",
salt: new Uint8Array(),
info: new Uint8Array(),
},
key,
((20 * 255) << 3) + 8,
),
DOMException,
"The length provided for HKDF is too large",
);
});
Deno.test(async function testDeriveKey() {
// Test deriveKey
const rawKey = await crypto.getRandomValues(new Uint8Array(16));
const key = await crypto.subtle.importKey(
"raw",
rawKey,
"PBKDF2",
false,
["deriveKey", "deriveBits"],
);
const salt = await crypto.getRandomValues(new Uint8Array(16));
const derivedKey = await crypto.subtle.deriveKey(
{
name: "PBKDF2",
salt,
iterations: 1000,
hash: "SHA-256",
},
key,
{ name: "HMAC", hash: "SHA-256" },
true,
["sign"],
);
assert(derivedKey instanceof CryptoKey);
assertEquals(derivedKey.type, "secret");
assertEquals(derivedKey.extractable, true);
assertEquals(derivedKey.usages, ["sign"]);
const algorithm = derivedKey.algorithm as HmacKeyAlgorithm;
assertEquals(algorithm.name, "HMAC");
assertEquals(algorithm.hash.name, "SHA-256");
assertEquals(algorithm.length, 256);
});
Deno.test(async function testAesCbcEncryptDecrypt() {
const key = await crypto.subtle.generateKey(
{ name: "AES-CBC", length: 128 },
true,
["encrypt", "decrypt"],
);
const iv = await crypto.getRandomValues(new Uint8Array(16));
const encrypted = await crypto.subtle.encrypt(
{
name: "AES-CBC",
iv,
},
key as CryptoKey,
new Uint8Array([1, 2, 3, 4, 5, 6]),
);
assert(encrypted instanceof ArrayBuffer);
assertEquals(encrypted.byteLength, 16);
const decrypted = await crypto.subtle.decrypt(
{
name: "AES-CBC",
iv,
},
key as CryptoKey,
encrypted,
);
assert(decrypted instanceof ArrayBuffer);
assertEquals(decrypted.byteLength, 6);
assertEquals(new Uint8Array(decrypted), new Uint8Array([1, 2, 3, 4, 5, 6]));
});
// TODO(@littledivy): Enable WPT when we have importKey support
Deno.test(async function testECDH() {
const namedCurve = "P-256";
const keyPair = await crypto.subtle.generateKey(
{
name: "ECDH",
namedCurve,
},
true,
["deriveBits"],
);
const derivedKey = await crypto.subtle.deriveBits(
{
name: "ECDH",
public: keyPair.publicKey,
},
keyPair.privateKey,
256,
);
assert(derivedKey instanceof ArrayBuffer);
assertEquals(derivedKey.byteLength, 256 / 8);
});
Deno.test(async function testWrapKey() {
// Test wrapKey
const key = await crypto.subtle.generateKey(
{
name: "RSA-OAEP",
modulusLength: 4096,
publicExponent: new Uint8Array([1, 0, 1]),
hash: "SHA-256",
},
true,
["wrapKey", "unwrapKey"],
);
const hmacKey = await crypto.subtle.generateKey(
{
name: "HMAC",
hash: "SHA-256",
length: 128,
},
true,
["sign"],
);
const wrappedKey = await crypto.subtle.wrapKey(
"raw",
hmacKey,
key.publicKey,
{
name: "RSA-OAEP",
label: new Uint8Array(8),
},
);
assert(wrappedKey instanceof ArrayBuffer);
assertEquals(wrappedKey.byteLength, 512);
});
// Doesn't need to cover all cases.
// Only for testing types.
Deno.test(async function testAesKeyGen() {
const key = await crypto.subtle.generateKey(
{
name: "AES-GCM",
length: 256,
},
true,
["encrypt", "decrypt"],
);
assert(key);
assertEquals(key.type, "secret");
assertEquals(key.extractable, true);
assertEquals(key.usages, ["encrypt", "decrypt"]);
const algorithm = key.algorithm as AesKeyAlgorithm;
assertEquals(algorithm.name, "AES-GCM");
assertEquals(algorithm.length, 256);
});
Deno.test(async function testDecryptWithInvalidIntializationVector() {
const data = new Uint8Array([42, 42, 42, 42]);
const key = await crypto.subtle.generateKey(
{ name: "AES-CBC", length: 256 },
true,
["encrypt", "decrypt"],
);
const initVector = crypto.getRandomValues(new Uint8Array(16));
const encrypted = await crypto.subtle.encrypt(
{ name: "AES-CBC", iv: initVector },
key,
data,
);
const initVector2 = crypto.getRandomValues(new Uint8Array(16));
assertRejects(async () => {
await crypto.subtle.decrypt(
{ name: "AES-CBC", iv: initVector2 },
key,
encrypted,
);
}, DOMException);
});