// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
import { wrapInTests } from "./testHelpers";
import { assert, expect } from "chai";
import { action, query } from "../_generated/server.js";
import { generateData, consumeData } from "../../src/crypto_interop";
// -----------------------------------------------------------------------------
// Begin tests from Deno
// -----------------------------------------------------------------------------
function getRandomValuesInt8Array() {
const arr = new Int8Array(32);
crypto.getRandomValues(arr);
assert.notDeepEqual(arr, new Int8Array(32));
}
function getRandomValuesUint8Array() {
const arr = new Uint8Array(32);
crypto.getRandomValues(arr);
assert.notDeepEqual(arr, new Uint8Array(32));
}
function getRandomValuesUint8ClampedArray() {
const arr = new Uint8ClampedArray(32);
crypto.getRandomValues(arr);
assert.notDeepEqual(arr, new Uint8ClampedArray(32));
}
function getRandomValuesInt16Array() {
const arr = new Int16Array(4);
crypto.getRandomValues(arr);
assert.notDeepEqual(arr, new Int16Array(4));
}
function getRandomValuesUint16Array() {
const arr = new Uint16Array(4);
crypto.getRandomValues(arr);
assert.notDeepEqual(arr, new Uint16Array(4));
}
function getRandomValuesInt32Array() {
const arr = new Int32Array(8);
crypto.getRandomValues(arr);
assert.notDeepEqual(arr, new Int32Array(8));
}
function getRandomValuesBigInt64Array() {
const arr = new BigInt64Array(8);
crypto.getRandomValues(arr);
assert.notDeepEqual(arr, new BigInt64Array(8));
}
function getRandomValuesUint32Array() {
const arr = new Uint32Array(8);
crypto.getRandomValues(arr);
assert.notDeepEqual(arr, new Uint32Array(8));
}
function getRandomValuesBigUint64Array() {
const arr = new BigUint64Array(8);
crypto.getRandomValues(arr);
assert.notDeepEqual(arr, new BigUint64Array(8));
}
function getRandomValuesReturnValue() {
const arr = new Uint32Array(8);
const rtn = crypto.getRandomValues(arr);
assert.notDeepEqual(arr, new Uint32Array(8));
assert(rtn === arr, "Returned array did not strict equal original array");
}
function getRandomValuesFloatArray() {
const arr = new Float32Array(32);
assert.throws(
() => crypto.getRandomValues(arr),
DOMException,
/not an integer array type/,
);
}
function randomUUID() {
// vaguely resembles what browsers do
assert.strictEqual(crypto.randomUUID().length, 36);
assert.isString(crypto.randomUUID());
}
async function testX25519GenerateKey() {
const { privateKey, publicKey } = (await crypto.subtle.generateKey(
"X25519",
false,
["deriveBits"],
)) as CryptoKeyPair;
assert.strictEqual(privateKey.extractable, false);
assert.deepEqual(privateKey.usages, ["deriveBits"]);
assert.deepEqual(privateKey.algorithm, { name: "X25519" });
assert.deepEqual(privateKey.type, "private");
assert.strictEqual(publicKey.extractable, true);
assert.deepEqual(publicKey.usages, []);
assert.deepEqual(publicKey.algorithm, { name: "X25519" });
assert.deepEqual(publicKey.type, "public");
}
// https://github.com/denoland/deno/issues/11664
async function testImportArrayBufferKey() {
const subtle = 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));
}
async function testSignVerify() {
const subtle = 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.strictEqual(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),
},
];
async function testOaepEncryptDecrypt() {
const subtle = 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);
assert.equal(cipherText.byteLength * 8, 2048);
assert.instanceOf(cipherText, ArrayBuffer);
const decrypted = await subtle.decrypt(
encryptAlgorithm,
keyPair.privateKey,
cipherText,
);
assert(decrypted);
assert.instanceOf(decrypted, ArrayBuffer);
assert.deepEqual(new Uint8Array(decrypted), plainText);
await expect(
subtle.decrypt(
encryptAlgorithm,
keyPair.privateKey,
new ArrayBuffer(256),
),
)
.to.eventually.be.rejectedWith(DOMException, "OAEP decryption failed")
.and.have.property("name", "OperationError");
const badPlainText = new Uint8Array(plainText.byteLength + 1);
badPlainText.set(plainText, 0);
badPlainText.set(new Uint8Array([32]), plainText.byteLength);
await expect(
subtle.encrypt(encryptAlgorithm, keyPair.publicKey, badPlainText),
)
.to.eventually.be.rejectedWith(DOMException, "OAEP encryption failed")
.and.have.property("name", "OperationError");
}
}
async function testGenerateRSAKey() {
const subtle = 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);
assert.equal(keyPair.privateKey.extractable, true);
assert(keyPair.privateKey.usages.includes("sign"));
}
async function testGenerateHMACKey() {
const key = await crypto.subtle.generateKey(
{
name: "HMAC",
hash: "SHA-512",
},
true,
["sign", "verify"],
);
assert(key);
assert.equal(key.extractable, true);
assert(key.usages.includes("sign"));
}
async function testECDSASignVerify() {
const key = await crypto.subtle.generateKey(
{
name: "ECDSA",
namedCurve: "P-384",
},
true,
["sign", "verify"],
);
const encoder = new TextEncoder();
const encoded = encoder.encode("Hello, World!");
const signature = await crypto.subtle.sign(
{ name: "ECDSA", hash: "SHA-384" },
key.privateKey,
encoded,
);
assert(signature);
assert(signature instanceof ArrayBuffer);
const verified = await crypto.subtle.verify(
{ hash: { name: "SHA-384" }, name: "ECDSA" },
key.publicKey,
signature,
encoded,
);
assert(verified);
// A signature of the wrong length should fail to verify
assert.isFalse(
await crypto.subtle.verify(
{ hash: { name: "SHA-384" }, name: "ECDSA" },
key.publicKey,
new Uint8Array([1]),
encoded,
),
);
}
// Tests the "bad paths" as a temporary replacement for sign_verify/ecdsa WPT.
async function testECDSASignVerifyFail() {
const key = await crypto.subtle.generateKey(
{
name: "ECDSA",
namedCurve: "P-384",
},
true,
["sign", "verify"],
);
const encoded = new Uint8Array([1]);
// Signing with a public key (InvalidAccessError)
await expect(
crypto.subtle.sign(
{ name: "ECDSA", hash: "SHA-384" },
key.publicKey,
new Uint8Array([1]),
),
)
.to.eventually.be.rejectedWith(DOMException)
.and.have.property("name", "InvalidAccessError");
// Do a valid sign for later verifying.
const signature = await crypto.subtle.sign(
{ name: "ECDSA", hash: "SHA-384" },
key.privateKey,
encoded,
);
// Verifying with a private key (InvalidAccessError)
await expect(
crypto.subtle.verify(
{ hash: { name: "SHA-384" }, name: "ECDSA" },
key.privateKey,
signature,
encoded,
),
)
.to.eventually.be.rejectedWith(DOMException)
.and.have.property("name", "InvalidAccessError");
}
// https://github.com/denoland/deno/issues/11313
async function testSignRSASSAKey() {
const subtle = 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);
assert.equal(keyPair.privateKey.extractable, true);
assert(keyPair.privateKey.usages.includes("sign"));
const encoder = new TextEncoder();
const encoded = encoder.encode("Hello, World!");
const signature = await crypto.subtle.sign(
{ name: "RSASSA-PKCS1-v1_5" },
keyPair.privateKey,
encoded,
);
assert(signature);
}
const jwk: JsonWebKey = {
kty: "oct",
// unpadded base64 for rawKey.
k: "AQIDBAUGBwgJCgsMDQ4PEA",
alg: "HS256",
ext: true,
key_ops: ["sign"],
};
async function subtleCryptoHmacImportExport() {
const rawKey = new Uint8Array([
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
]);
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,
]);
assert.deepEqual(actual1, expected.buffer);
assert.deepEqual(actual2, expected.buffer);
const exportedKey1 = await crypto.subtle.exportKey("raw", key1);
assert.deepEqual(new Uint8Array(exportedKey1), rawKey);
const exportedKey2 = await crypto.subtle.exportKey("jwk", key2);
assert.deepEqual(exportedKey2, jwk);
}
// 2048-bits publicExponent=65537
// const pkcs8TestVectors = [
// // rsaEncryption
// { pem: "cli/tests/testdata/webcrypto/id_rsaEncryption.pem", hash: "SHA-256" },
// ];
const rsaPemFile = `-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDYKBqT674QjFgP
LciOxf8UNtfIligtwGc+RjuNRdzT2z7sduGPqxfW7y6MYm/tP92GjUKNf2/XzLkI
Ay5pPtHKv21YTAH4GOFqNJL3FzYVJtf8zZmvFhXY2BVIhu6JFkaFCBYC4+Is5nMM
kP12FZvf6LbTTVQVMRV5wyRXP1gzQ16fHE9c115mhzJSVkohtqHw5mFfEV/I7DEB
QRTn+hvv5r4gGc7qn9gewwmlOh+6ow1lc7WNWNn7kigsW9hgLGsGZvkaUge0jOLs
QPzEWSdzmiOnpMxYLK1XjC73G3+0OHUjtRdG9q3cr7/yqKO5uI0Ays1q40t2UZrH
rMBeFSi/AgMBAAECggEAE7NMANFKiE2SNQfyMHkBL4F0FzcAQHM5taZHBTAp2TEx
QfHvyt1IFfHEp0zNcK0SbpHvT+AefGePMZjAoRz1l+nseFCtGUSDPt+9yUFXT4Qz
yTmf2SJFKXdAMVUC5oGeOb+r6eWFFpyGPc31G88KXtTh3M4+bJQFpgxQApemXT2K
vvkUppWr/LKIrLQ9TboBAWOOMtuZ0+nDbCzFwdRvQ1itR/Jbq4swt4+PTXKkWrHN
gXoxCoMXaS/QW4BxzlnCAFelEvguF95VKPFgzf6AvHp7mun4tHYgePxugsG4xpRR
U3fCBIqHp8LunG8TpCIY4jeUE4vfNgxWgJ/E+oSOWQKBgQDundzft515Ul2k+wb2
HaQbaVafYyDmwK/Ji8tthFVJqYPo7eWFbIsedAEGUBzxYd8WRJS59/jyV/MQ3mGS
Y2leCOVo/oGIxqugJNQl0NSnUYleXeweTWMeUhbbMPGutMHLe9hGByzOBwE4G1Oj
Yb8bu41WMzT9T1YBm8cuBQS02wKBgQDn5145sxlMDezj0kcOU+gQLXJfsIPhVIsf
aiLlH78BLDOTH3J+INMYnI869r1bCOlUjNjf3bdBgPA+YQQIPExmhQARAPk1riEp
x7O9egpcxlYzE7yNtR5LeEh5kW+nlSr6EcPPONJlHYgyv9DGxjhnA3lI93rBcqYy
0xSFF41O7QKBgQCiW2deEWFkm1Z8WxFxhNmUjSgTay+H0rPJPwU7jz84z86hPr1c
+23tWqEX2orW8vEIBcHsh30r6AvK/oUFRf77rLHrrsAUgJlmbair0lvfPOtq+h0e
wSkgCFrk6XiIlxUFj06o11j1Fm8N7goKsQeHpyWT8WOst76deZEdDu0U4QKBgEs/
HqrYO0AbUJ9HrriubyE4reDwtIob1ZyW4sW3vFFUF1QIoyzb18Mnoa3/O8fbJ2LA
5OoW1gySGuIStq05a4zkYPYje7l4S9hzrRWxEMWzsWqXX9oXR8IzQEj58OHOnAhS
VVfa1yHqKDRXWxX0YX7DeMo9Sv6UBet95C2fS8GtAoGALvUAEM/LEj/NY+J0542F
+b8FyXtosQ1mSKBV03JmmglDfQW/+O0Illuy8CmfIuqftLwHyc15u5cAIDd5a8jm
S8l+7/KYETXYGCA1mwB/Dr/LJwbVEQ/mw1ne6FrJ4MORd/2GWuvzrqTKrwRcJ+O9
mZ3aPB+XECOydno9s4rsxII=
-----END PRIVATE KEY-----`;
async function importRsaPkcs8() {
const pemHeader = "-----BEGIN PRIVATE KEY-----";
const pemFooter = "-----END PRIVATE KEY-----";
const hash = "SHA-256";
const keyFile = rsaPemFile;
const pemContents = keyFile.substring(
pemHeader.length,
keyFile.length - pemFooter.length,
);
const binaryDer = Uint8Array.fromBase64(pemContents);
const key = await crypto.subtle.importKey(
"pkcs8",
binaryDer,
{ name: "RSA-PSS", hash },
true,
["sign"],
);
assert(key);
assert.strictEqual(key.type, "private");
assert.strictEqual(key.extractable, true);
assert.deepEqual(key.usages, ["sign"]);
const algorithm = key.algorithm as RsaHashedKeyAlgorithm;
assert.strictEqual(algorithm.name, "RSA-PSS");
assert.strictEqual(algorithm.hash.name, hash);
assert.strictEqual(algorithm.modulusLength, 2048);
assert.deepEqual(algorithm.publicExponent, new Uint8Array([1, 0, 1]));
}
const rsaSpkiPem = `-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwhYOFK2Ocbbpb/zVypi9
SeKiNUqKQH0zTKN1+6fpCTu6ZalGI82s7XK3tan4dJt90ptUPKD2zvxqTzFNfx4H
HHsrYCf2+FMLn1VTJfQazA2BvJqAwcpW1bqRUEty8tS/Yv4hRvWfQPcc2Gc3+/fQ
OOW57zVy+rNoJc744kb30NjQxdGp03J2S3GLQu7oKtSDDPooQHD38PEMNnITf0pj
+KgDPjymkMGoJlO3aKppsjfbt/AH6GGdRghYRLOUwQU+h+ofWHR3lbYiKtXPn5dN
24kiHy61e3VAQ9/YAZlwXC/99GGtw/NpghFAuM4P1JDn0DppJldy3PGFC0GfBCZA
SwIDAQAB
-----END PUBLIC KEY-----`;
// (not from Deno)
// Regression test for SPKI importKey.
async function importRsaSpki() {
const pemHeader = "-----BEGIN PUBLIC KEY-----";
const pemFooter = "-----END PUBLIC KEY-----";
const hash = "SHA-256";
const keyFile = rsaSpkiPem;
const pemContents = keyFile.substring(
pemHeader.length,
keyFile.length - pemFooter.length,
);
const binaryDer = Uint8Array.fromBase64(pemContents);
const key = await crypto.subtle.importKey(
"spki",
binaryDer,
{ name: "RSASSA-PKCS1-v1_5", hash },
true,
["verify"],
);
assert(key);
assert.strictEqual(key.type, "public");
assert.strictEqual(key.extractable, true);
assert.deepEqual(key.usages, ["verify"]);
const algorithm = key.algorithm as RsaHashedKeyAlgorithm;
assert.strictEqual(algorithm.name, "RSASSA-PKCS1-v1_5");
assert.strictEqual(algorithm.hash.name, hash);
assert.strictEqual(algorithm.modulusLength, 2048);
assert.deepEqual(algorithm.publicExponent, new Uint8Array([1, 0, 1]));
}
// (not from Deno)
// Regression test for PKCS8 importKey.
async function importRsaPKCS8Regression() {
const privateKey = new Uint8Array([
48, 130, 4, 188, 2, 1, 0, 48, 13, 6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 1,
5, 0, 4, 130, 4, 166, 48, 130, 4, 162, 2, 1, 0, 2, 130, 1, 1, 0, 184, 88,
246, 32, 206, 209, 185, 156, 139, 115, 244, 51, 207, 16, 151, 38, 55, 53,
18, 244, 83, 233, 101, 45, 180, 223, 24, 250, 195, 220, 145, 144, 122, 213,
86, 219, 252, 149, 109, 180, 34, 224, 155, 134, 68, 64, 7, 172, 223, 141,
70, 107, 248, 104, 162, 16, 202, 155, 67, 46, 48, 227, 113, 167, 49, 28,
121, 253, 184, 163, 89, 26, 71, 146, 94, 112, 252, 76, 29, 67, 192, 139, 90,
150, 197, 184, 149, 26, 118, 210, 232, 192, 237, 75, 182, 64, 193, 128, 90,
100, 227, 7, 66, 118, 48, 115, 148, 128, 152, 27, 155, 47, 254, 179, 71, 7,
250, 249, 57, 57, 51, 44, 130, 131, 94, 120, 159, 80, 86, 227, 0, 48, 200,
130, 82, 73, 186, 166, 59, 226, 137, 207, 30, 84, 56, 165, 60, 126, 95, 246,
152, 199, 187, 150, 97, 140, 99, 177, 60, 88, 121, 197, 235, 50, 147, 208,
194, 107, 47, 100, 224, 116, 221, 195, 97, 61, 209, 10, 248, 217, 53, 42,
104, 149, 31, 250, 59, 234, 111, 141, 157, 100, 96, 39, 29, 94, 92, 3, 114,
59, 169, 243, 139, 42, 102, 252, 71, 16, 199, 183, 15, 138, 17, 158, 186,
87, 77, 140, 163, 156, 23, 240, 37, 198, 78, 8, 152, 232, 48, 172, 233, 205,
206, 100, 7, 30, 92, 148, 29, 148, 3, 197, 3, 183, 104, 203, 14, 206, 86,
97, 152, 95, 251, 31, 242, 143, 2, 3, 1, 0, 1, 2, 130, 1, 0, 15, 88, 161,
87, 22, 112, 129, 71, 159, 101, 129, 60, 69, 174, 13, 200, 188, 241, 52, 9,
61, 131, 109, 242, 235, 106, 253, 227, 65, 115, 3, 220, 90, 229, 99, 12, 51,
92, 49, 143, 111, 225, 216, 154, 47, 229, 182, 170, 168, 49, 33, 68, 141,
31, 171, 37, 93, 77, 63, 191, 39, 190, 133, 201, 224, 49, 12, 199, 241, 65,
112, 181, 91, 11, 85, 95, 35, 25, 167, 101, 157, 100, 15, 160, 2, 136, 176,
16, 26, 108, 100, 33, 178, 152, 253, 108, 144, 2, 104, 170, 104, 196, 68,
62, 209, 236, 190, 215, 34, 223, 72, 167, 17, 72, 111, 40, 111, 54, 216, 69,
41, 162, 184, 109, 125, 5, 81, 102, 135, 77, 2, 158, 221, 112, 106, 41, 246,
113, 76, 170, 230, 227, 141, 69, 222, 218, 252, 15, 35, 99, 5, 232, 69, 50,
39, 192, 73, 74, 76, 64, 218, 35, 2, 31, 203, 15, 77, 66, 139, 180, 104,
170, 75, 126, 196, 168, 197, 124, 155, 86, 45, 114, 30, 108, 232, 0, 231,
237, 203, 38, 144, 21, 65, 207, 238, 142, 53, 89, 190, 82, 200, 214, 136,
227, 68, 248, 236, 241, 63, 122, 204, 142, 242, 63, 134, 180, 47, 169, 253,
131, 149, 178, 153, 178, 251, 255, 193, 252, 223, 51, 124, 152, 239, 246,
34, 52, 148, 140, 81, 2, 188, 0, 231, 170, 196, 239, 167, 104, 111, 7, 120,
69, 116, 136, 212, 117, 2, 129, 129, 0, 250, 142, 86, 177, 204, 179, 222,
171, 122, 121, 232, 185, 144, 121, 93, 130, 10, 0, 194, 159, 118, 156, 153,
3, 95, 234, 99, 234, 188, 27, 195, 100, 43, 74, 221, 41, 202, 145, 202, 115,
174, 59, 131, 115, 252, 80, 63, 143, 100, 74, 22, 76, 213, 130, 45, 170, 85,
220, 219, 121, 24, 234, 252, 92, 144, 209, 124, 132, 112, 13, 28, 221, 150,
167, 109, 252, 154, 163, 0, 5, 148, 193, 216, 190, 124, 234, 206, 159, 145,
47, 192, 11, 245, 232, 95, 176, 225, 143, 25, 95, 41, 104, 237, 205, 125,
239, 185, 49, 211, 66, 164, 102, 68, 252, 106, 102, 108, 237, 128, 62, 23,
53, 0, 173, 58, 108, 89, 51, 2, 129, 129, 0, 188, 90, 90, 87, 148, 72, 245,
136, 7, 6, 227, 141, 124, 102, 30, 0, 30, 110, 187, 231, 172, 182, 217, 244,
218, 112, 91, 184, 102, 177, 186, 74, 245, 225, 188, 4, 79, 116, 222, 92,
249, 46, 0, 48, 83, 167, 84, 207, 217, 211, 242, 230, 225, 94, 32, 24, 52,
143, 86, 139, 238, 77, 154, 44, 143, 126, 231, 86, 115, 146, 184, 147, 137,
122, 74, 67, 64, 228, 131, 59, 207, 135, 152, 49, 39, 216, 180, 206, 176, 2,
79, 27, 161, 134, 209, 171, 119, 81, 14, 54, 191, 171, 249, 225, 16, 178,
55, 120, 112, 209, 215, 248, 144, 248, 109, 230, 58, 111, 205, 13, 75, 191,
222, 251, 173, 22, 153, 53, 2, 129, 128, 7, 47, 254, 28, 171, 154, 157, 80,
157, 250, 209, 74, 65, 114, 185, 211, 249, 37, 124, 111, 198, 159, 71, 100,
105, 99, 247, 233, 203, 235, 159, 247, 71, 166, 166, 33, 132, 198, 25, 224,
167, 166, 221, 102, 126, 94, 110, 244, 86, 20, 41, 255, 154, 64, 89, 191, 1,
39, 140, 196, 52, 138, 201, 34, 126, 165, 3, 197, 104, 209, 119, 122, 131,
207, 217, 191, 221, 79, 191, 184, 105, 68, 6, 75, 176, 153, 171, 195, 184,
14, 126, 155, 217, 58, 9, 68, 177, 179, 193, 46, 145, 169, 136, 232, 212,
44, 4, 76, 1, 155, 111, 203, 223, 62, 190, 110, 161, 193, 78, 100, 121, 149,
243, 167, 4, 126, 7, 49, 2, 129, 128, 52, 179, 79, 45, 204, 6, 177, 244,
114, 138, 225, 230, 119, 149, 22, 245, 207, 142, 10, 51, 99, 102, 242, 11,
9, 135, 128, 146, 82, 225, 141, 143, 101, 198, 216, 85, 152, 105, 201, 193,
215, 210, 160, 40, 229, 111, 31, 82, 220, 206, 233, 218, 225, 217, 245, 62,
240, 141, 222, 152, 94, 128, 6, 16, 75, 194, 37, 54, 82, 54, 14, 64, 241,
169, 110, 215, 236, 115, 67, 168, 219, 131, 67, 249, 20, 254, 20, 112, 244,
92, 97, 8, 9, 36, 240, 203, 122, 34, 10, 201, 20, 206, 40, 167, 105, 133,
131, 241, 198, 23, 96, 199, 98, 192, 175, 247, 72, 8, 122, 38, 43, 56, 175,
74, 89, 254, 197, 181, 2, 129, 128, 40, 144, 147, 99, 34, 203, 195, 186,
196, 188, 19, 32, 254, 129, 26, 138, 172, 40, 30, 3, 87, 124, 149, 234, 23,
144, 105, 229, 28, 192, 71, 45, 149, 10, 51, 53, 37, 54, 49, 156, 106, 150,
200, 26, 222, 1, 157, 30, 202, 255, 245, 74, 252, 96, 210, 20, 224, 110, 21,
7, 250, 150, 10, 198, 222, 107, 51, 55, 159, 12, 64, 56, 46, 162, 30, 52,
68, 53, 229, 224, 93, 139, 192, 180, 126, 183, 215, 171, 136, 176, 147, 201,
223, 8, 91, 49, 139, 100, 220, 93, 151, 118, 118, 73, 118, 193, 154, 120,
158, 237, 139, 20, 77, 250, 207, 188, 140, 189, 120, 30, 227, 178, 23, 132,
216, 7, 64, 71,
]);
const hash = "SHA-256";
const key = await crypto.subtle.importKey(
"pkcs8",
privateKey,
{
name: "RSASSA-PKCS1-v1_5",
hash,
},
true,
["sign"],
);
assert(key);
assert.strictEqual(key.type, "private");
assert.strictEqual(key.extractable, true);
assert.deepEqual(key.usages, ["sign"]);
const algorithm = key.algorithm as RsaHashedKeyAlgorithm;
assert.strictEqual(algorithm.name, "RSASSA-PKCS1-v1_5");
assert.strictEqual(algorithm.hash.name, hash);
assert.strictEqual(algorithm.modulusLength, 2048);
assert.deepEqual(algorithm.publicExponent, new Uint8Array([1, 0, 1]));
}
const pem1 = `-----BEGIN PRIVATE KEY-----
MIIE7gIBADA9BgkqhkiG9w0BAQowMKANMAsGCWCGSAFlAwQCAaEaMBgGCSqGSIb3
DQEBCDALBglghkgBZQMEAgGiAwIBHgSCBKgwggSkAgEAAoIBAQClbSUIKRfpCk0W
WuKv4vzTnAW98n1LDwSbU0larwckrBe19waFaCrh1jiIaSzTjCR9UtHDMDaUuOu3
0Z33Yrsa3U/t6vVl+JVsSeqXTBbGXTZ1iXuCk1DywIAiBwrZJ4IH00p7zMJ7dN0B
peQ6oirGAiaEBGonq6cdAvE0WpDkEQ1lZ3YjSWggwMpkOv24dl1SwbANQzvX4s66
WD70LhmS2MwC9EyUz3ddV3Ha5S3pZrGis8CJ5kBMR65BMYwsaDEWjDkNd7fNFC2D
HiINO8GUm3Tb7FSqzqVCcrOPQYcb/3gBQIIFrGS5zk9KXIcJ6382iomN+f9EGhDQ
S9FQRgF9AgMBAAECggEAIt3HQVoP7RE7wCt1veaUrTEkUK2sVMacjPRX8KIVWlhf
Qv4GxNV4vFK/ZZjtVsUh57wt8/rUdjInlH280qhfkUD2GMo94LktHT7TEAJ8hmCd
GtbYTmQoWpHSL9nWIoDeOjIBb+hvUUAHWNKTMPx/EW7gmVCo4yWdulKlbghso3TR
1M4jbryhplLDzElPPUhmahT2De64CAt9OkDZV8b+47E6CU33qrlFCBOgurQGiIVK
GgmrIxu0efR9n9Df5BqLHTI5v9nqyugrnWYN6vdk/YWZK+RsHo1iqEhge5CPrI48
M6XIES1EQeSF2cdJaob9gsmyzszODw/w8smT5DgNwQKBgQDVZuvgqR9zcpIpMjtI
gFQvBIrHFyO6C48lQ37hlGpSjcKsWevTVRMw6Ywrm6Ls1AlKQ02tMETA9rcnOgj5
lHwOB1Neb41XsBkNgqwelqSAZkxmIKESN4/EBdVX89hdv6lLZ58IniWYfzEAZKoc
oZKelrU+wuk58ny4H7WwdBQjEQKBgQDGcpzdf/Hw60KWdq5fydlG0OwzK6t9TIfB
AZsxcEpHjckfe8/WJLcSZ4zD6kBSiBuLJ4G0l/rMYiF3I6dFfta+2tv8GuQ6n6ji
G859GMBJvqwLbHFFZb56PCtTtoYuI94Qiv1i0CjAfpR9uajLFJ/4aBmKBP04higM
mOQkDTdfrQKBgQDFedZxMU+/X5hNswQVFVvRnpxlm84dzbCLRShWyyaQekpZf2Sx
TZrSumxRtlZQXe6y4BEzYOVew2+9RwEjI/qgaLsHOqdLK9QjInbwX2qevwuXvj4P
Q4cwWE2SdD4WktOwyZTrPp1/vsOzn3OjxwiM5N2X1HpKe1Baek2BmglAYQKBgDOe
Zm07NZycJVLsBgmGgIzqFTZuOoo6GOm8WDjw43FfURxuYS5rmG8iHjxrt1WAb+Gv
Yg6upZ76O4g47u6lwogcd7JI8GrLUuuVng1uHb5Q6YGDMKeDgptbAU4iIR7pV37o
GGbWjHMdudRGMcC5Wa8MrO/4wkEsrEgykM1L3sZ1AoGBAMUtQvrtTDIQml38V+O5
2j3VFv/hmhE/+XMKd2DXl9aRiG6QMe3BVWcLP/rq2ZaxwRjAZg5TO0S9Dptn9eX+
0yASf6VghLGs+wVnWGseyZRcYHRv2O6+I8zzpGd4uSaB7uoBMFsEEp+lUTRe52EW
gFHhVC1yOzlseecZ7wWszDeO
-----END PRIVATE KEY-----`;
const pem2 = `-----BEGIN PRIVATE KEY-----
MIIEwwIBADASBgkqhkiG9w0BAQowBaIDAgEABIIEqDCCBKQCAQACggEBAMPqex5m
1JO1SOxN6F+yNN9AV1bhoGHpx7rqvOAjIadTSmK9g17GA8r5vYk/J/wVdPLOnVG2
ARAzk9T554rYd4fAN3uL9CzsK2eBb7CD/OAbq1a3RLGhFrNILot7rK2QnTPuDbAh
HUWGwsW+PQYhPW1JzdnOvfRD4DEZfzz5in6SZWmHvXCMdpewgWdjzQ9O1+MxNVe4
diw43q3fvDEZSGGgnV3eVp+adQxIq0pfNq6jB71CE82vZYMXAJ+MeypX6+xn9OG0
NzXXUsYTQjchNUALZTG4YM5ErGJCB/cikEwqscktirmU3l1lAN193wgykYUDGV8a
JdlMUwRe68vsoI8CAwEAAQKCAQABnH0Uu+3FpTkLUHy3xMRwjZvqSALEq2KMJAAX
q9JMCQBUnZBmCCTh13n6lf1jMl363T4n/OI3WuU9XCzOVIdvI2KRbo48jFizCjp7
7in3QeL/3JQBDf0czlwro4HBD65rTero5uzRtJEHhVAFK+LQcknyH6QqTSCb5sTI
IJlF/zSMc596zLsSdo/CdNBSvdlOXLoypJ7APW/dKs0nQ58dJ2yhNVkFfqnsckg1
i1M12WMVUlwQDyqb7OlodLr45SN9CM4EVXES6/D1I4WTHQQgVK+nyVv/1UgGWn8w
ZJv7z5WbA+fdCPRXNf9VRkVpL2TDF32Aouu9Ck729U/KesghAoGBAOl2SBiyeK+Y
z5G/XXNOUWvVoLKv70Fy8pevvv9uyIOvxriADLRFRxcObG0rgLrCYQhjsWzhXBss
5lY8Cnm4dBFxZsAMHYBB5C99bT/OX+D4Gh7JG302wehp/2nrl9Wqs5NV6p9r0nyH
PQdPzGTBRnHG2uzor9pawU6Sb7FoIfxfAoGBANbUS4zLRqiBqjXw5TVkxGetm6St
GM+gA0JuYDqXM53a6p6/brwTO2u2hQPIphTGc+ZQzjaimUQeX4cRRm7cs7Nzvb0U
iiwYCf4zDEKzhGvdKsA/SOyGR3a5yyV1jWZdRvNra1kL08bsUYMpMOomPlDoTtZv
kJaOMblGlRskWcnRAoGBAMOeqraXBO0N/A9B7Anr++mBbU+Mf8u1h3R2fHIH39gH
91ktYnfC/Nhi65NmOk1DBo9DCa4T/1t9+dsUICrx1b+v58rP3ABWNd9dF6e5Qzl8
alaVaIU41q3p82xLTkRo7mNsQFYowIw7vXVc9gUOtfosB9Eu//rUxNkTdqeOe7u9
AoGBAMEW9YTp3GtuiCeNiubP2Hx7nU6JSqirYOKfxJxE9N7oOkNPOt+OxbTNy4aY
HTbFHL0hWgffY7THPANxsoXIlzgvSpYdVQfG34x8I4P8SISOuHMtLoVxN+BtpDra
Cqq8Ih5+KXFS4RmFpMooBtAeaZpdAydYBXRdADJQT4qixJVRAoGAOIF7KIGi0TBs
nr/u4Hqyj+wc0DkQFJa9ILvLV7r+k33KYjiVZeBfbDKDLMSXCHMyTSfLjmDI9ZVd
HCo6+cUw4dwXsquKn3i+nmjMc4G82o48EQYfGoRlNb7fnyBVdULIV0lwRRsXn29A
dfNGr0wbasKwbrjL6XZerA0saoNdEOw=
-----END PRIVATE KEY-----`;
const pem3 = `-----BEGIN PRIVATE KEY-----
MIIEwQIBADASBgkqhkiG9w0BAQowBaIDAgEeBIIEpjCCBKICAQACggEBAMEWS3x9
dCE5KWAoh5UAXeq2E0uPWBJ2UrtX7asgrOX1Sj4Boak/zxpsgPGQeA3kQPlcD9xi
wxnlBMFqrMwoY1Trb5quX1ZU8NVE/w3kWkF2EhHKgbWt9LyoNsNkj7liw5Aez4fN
O2v+IIsqeVJslXa6+6jfe1xWSEcRLOpfjCQ76bA9I5i9wls/ThvPnlEuYD7jFaXb
EJiKcaYxQ+L+Ys0CC5lPLGnLb3NRgeQhtBjmaTOMh2PrXoaZ9RuuzpDHJ7Iw3Inl
qh9AbH5um2TEh0ZpIhPaC5grwlbMpoTLW46MAYynme2+FsD20paszPQL9afiuUll
9PjLO3Lr3JNWxyECAwEAAQKCAQAhdReHbsWcrCb7Poqfyvx7GymkwiXkrRQQ2l+Y
c4UCI8rFi8rTZPciAQMm2H21CoQnsUgeTA66gfCdAzsF9UmhAVrJVsW2U+mXSulX
IuZwyWDALHLlZEswFYXHvbkZyn9QjcYwJePSBqrk8msrWR1dAXFyBad+jIThq5w4
0G2cKWh1sHKTcIa3UZ6jnAB0WC/Fkv3hjybVn601Axq9YdVTbKAxfBPdfTmBXqc5
P98ghRqPrjnFkDET8LZsDJjAuwSL9S79QAQmPmOlKuNCWB2gQ7/4aIcgtiyGOTvK
3sD4f3aL+E8caTpJQQDQanQO7DRghci+grb2bVDAkhg7/cdBAoGBAOQRDc3KSQlT
Jl8W2pa8PvtTYcK+QQ0GCKMDeFyAMqvtFo216NjgTh92WZArTTQVFMzorXNSWUbq
tAqMa9lc6f7CtM/hhuxkKL1o93Hbb69pk4eohzhk5uAleQ9jhOi4ABTwONV9HDt6
xl8CDoZiBpqjR1hlHFSfVLDGKtG+8I/NAoGBANi8eHWMvYicyND21Kf9s4RZuVVT
sf2xj1wCv8M3Or72PRtINOGdH2ASELpyyU9nLwzPaEnQ9nwmAI3YUs9j7lqkyFv0
aHK/F9JZ8IZi7paRsIl2lXkXBAWICG5eoBVvpl3KWzh50n0rEDaXhTJuToBVQ0BV
MiX1or6pcZUEanilAoGASg2/jbLBRGXbb8Tb9VXqnXDVrYZZWQE8jLHzwxVdXrX9
PMZ0dPdgZpbnPgjRaLfvqRlkOK3kj0Jmc4Zk/o9M64wNafKw/NEI6XfL4Qx/l1WQ
sdvnDEi3LtD8HiMSZP5aCHJ4Ado98JJNF0xzqu7pdgzOfcVXDaMuvLeb778wHYEC
gYA0bjN9zFQ1biguVOfQ09DPnZz2BU8znfaePZQCN6QgehUCOo+AXLAwX25ojEgi
y0VYhfwmj0RxeAf+SGyP+w64ItDNXey+hXfPzS4gdGJfTlM0jdlO98BjTisr9/wl
82J9oew7V00SNo6vhiwUrRaUeQvRzkpZYHjEQt1VPUI8eQKBgFkjt/rEEgpojPIR
iC4qR35yRoTvFjOLj5WQPZmgEjeqWoE/xXMg58UXo74ViSavEP+8xQH+uSXXAG4q
G4298Uc9jNF0jTekEKI8yyi2MBThDOtQOTNZb/exAgrXkff/g1RG+XIbgHgx8qRR
XII4DFaG+/+toV/27x4mfuddXWNu
-----END PRIVATE KEY-----`;
const nonInteroperableVectors = [
// id-RSASSA-PSS (sha256)
// `openssl genpkey -algorithm rsa-pss -pkeyopt rsa_pss_keygen_md:sha256 -out id_rsassaPss.pem`
{ pem: pem1, hash: "SHA-256" },
// id-RSASSA-PSS (default parameters)
// `openssl genpkey -algorithm rsa-pss -out id_rsassaPss.pem`
{
pem: pem2,
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: pem3,
hash: "SHA-1",
},
];
async function importNonInteroperableRsaPkcs8() {
const pemHeader = "-----BEGIN PRIVATE KEY-----";
const pemFooter = "-----END PRIVATE KEY-----";
for (const { pem, hash } of nonInteroperableVectors) {
const keyFile = pem;
const pemContents = keyFile.substring(
pemHeader.length,
keyFile.length - pemFooter.length,
);
const binaryDer = Uint8Array.fromBase64(pemContents);
await expect(
crypto.subtle.importKey(
"pkcs8",
binaryDer,
{ name: "RSA-PSS", hash },
true,
["sign"],
),
)
.to.eventually.be.rejectedWith(
DOMException,
"algorithm is not rsaEncryption",
)
.and.have.property("name", "DataError");
}
}
const jwtRSAKeys = {
"1024": {
size: 1024,
publicJWK: {
kty: "RSA",
n: "zZn4sRGfjQos56yL_Qy1R9NI-THMnFynn94g5RxA6wGrJh4BJT3x6I9x0IbpS3q-d4ORA6R2vuDMh8dDFRr9RDH6XY-gUScc9U5Jz3UA2KmVfsCbnUPvcAmMV_ENA7_TF0ivVjuIFodyDTx7EKHNVTrHHSlrbt7spbmcivs23Zc",
e: "AQAB",
},
privateJWK: {
kty: "RSA",
n: "zZn4sRGfjQos56yL_Qy1R9NI-THMnFynn94g5RxA6wGrJh4BJT3x6I9x0IbpS3q-d4ORA6R2vuDMh8dDFRr9RDH6XY-gUScc9U5Jz3UA2KmVfsCbnUPvcAmMV_ENA7_TF0ivVjuIFodyDTx7EKHNVTrHHSlrbt7spbmcivs23Zc",
e: "AQAB",
d: "YqIK_GdH85F-GWZdgfgmv15NE78gOaL5h2g4v7DeM9-JC7A5PHSLKNYn87HFGcC4vv0PBIBRtyCA_mJJfEaGWORVCOXSBpWNepMYpio52n3w5uj5UZEsBnbtZc0EtWhVF2Auqa7VbiKrWcQUEgEI8V0gE5D4tyBg8GXv9975dQE",
p: "9BrAg5L1zfqGPuWJDuDCBX-TmtZdrOI3Ys4ZaN-yMPlTjwWSEPO0qnfjEZcw2VgXHgJJmbVco6TxckJCmEYqeQ",
q: "157jDJ1Ya5nmQvTPbhKAPAeMWogxCyaQTkBrp30pEKd6mGSB385hqr4BIk8s3f7MdXpM-USpaZgUoT4o_2VEjw",
dp: "qdd_QUzcaB-6jkKo1Ug-1xKIAgDLFsIjJUUfWt_iHL8ti2Kl2dOnTcCypgebPm5TT1bqHN-agGYAdK5zpX2UiQ",
dq: "hNRfwOSplNfhLvxLUN7a2qA3yYm-1MSz_1DWQP7srlLORlUcYPht2FZmsnEeDcAqynBGPQUcbG2Av_hgHz2OZw",
qi: "zbpJQAhinrxSbVKxBQ2EZGFUD2e3WCXbAJRYpk8HVQ5AA52OhKTicOye2hEHnrgpFKzC8iznTsCG3FMkvwcj4Q",
},
signatures: {
PS1: "aOn+h+tNHkk6B948btJoS4ydgbFHiT28LbAXA2liFRF0Ef/WBdjptCuYE8I96fA7nEB+yRFTzsP+hYSom5HNGeVZx+Nt1O+hzcWwgzNcJttPbcXk+2HFv0FCD3coLjF2wjGfKNbTB1w6jj87BnmXrkrVAyStJciC+hSoblNEgn4=",
PS256:
"ChAa0ltw+KN3jER/b0xyKkc39WtQnLmYu+6/1eI0KDVmjqaKlgJeCchfJN/p/TQWSPG3ZwoBklTbUnRyAJSn2Ibrmt1d2yK4wSHTbXr3fNPtfiYZ6o5zwGRKfM7G5wsdVaC0Ur+xK5WxAd5mJj/zP0nCXIqOScmRAQ0j9Rbc8Kw=",
PS384:
"BdGI8SR9zuvuAbGlS+V0lbRVv0MrKd3GX1bD/zgHVVNnJHNQHK/c7L9DYrS7ZS3As4dS5VA1+EY7APjFyvWO7u9ghbX8VfctBisJEXkA+09xWRDjdbCWi/NEK+S/kzyDshzoapzCMwfundWOhUswldg6gAsk4d+sHhi9RD/KzFE=",
PS512:
"aHkYwDFS5XlCHP8lDNZCWVLlb6m88HbFDgIRmeXndoGhFhkOjwG3cWKzNcXcQom9avyHCdIhPTPm7bbdeVzA7QEZRZoOKKFDtrfBFtMMd3qrupYEEzkNjAdkkOopZSxEFV39siloR2MzJsbx7lOQltlYuFf1OBkLvJ8Ts0uUiTw=",
RS1: "qGsvGdj6HfALDHHUcJwZ/UMUhW4Q8nlyZnnHuEdVo0cjhENWGS9+xu2jzJwbZUXsZfXl3aJIzc8m1eat44G/aAntn4+YHGq48eqvhPZg4ecvFrMi/8MBe8cAD0KleYGS72SZYs1wxcVgDIrONF8+cWxpsQPT1HL4BPGW1s0Rkno=",
RS256:
"miOVQjzG3cxFCZXCx7DE/zQ9K+WqhPWfsqY8Gwr6ZoawrV2PspzfWZoDQXu8Y66Bqt+6zwOoQKRXBQORojLDV6vT1LQFo5G6qfNGYS0RLGOJyZ095831rvjC19H/8ky6gZsjiMyYrs4v93GcBA9FM+oBcvpKfQUzvcqGh+f59i0=",
RS384:
"M/K8fTsMcWE1OHkqlHYh6OwsjqKqR+10lnyJAR4HjMbqSNh/Li7mGUzNyi9fota9BykGii0zOkoOWnVFUUJH+8rNOEV5EubPjf4hiTiMYZMArPnqZCTvFIQat+o6Y/CaK1zedzOyPPc9zLCdHhRqXadhe8oEcHDtz83G6KVzn7s=",
RS512:
"wnzWMG9C5W5qRlmJyvQzfV6K9n3lo5whOd/BzhrKOJck0WAcVgxugrzFeK3YxjiJddpgdaUccmEqJ96fHpXaOmHmWuhUZV5StndJvCT/TEledFD1YW9amNnYCqW5vkQT4er1gr4HSeAF4DV5lhnvW4OiS0uVDamBQ5zHx3LyDVM=",
},
},
"2048": {
size: 2048,
publicJWK: {
kty: "RSA",
// unpadded base64 for rawKey.
n: "09eVwAhT9SPBxdEN-74BBeEANGaVGwqH-YglIc4VV7jfhR2by5ivzVq8NCeQ1_ACDIlTDY8CTMQ5E1c1SEXmo_T7q84XUGXf8U9mx6uRg46sV7fF-hkwJR80BFVsvWxp4ahPlVJYj__94ft7rIVvchb5tyalOjrYFCJoFnSgq-i3ZjU06csI9XnO5klINucD_Qq0vUhO23_Add2HSYoRjab8YiJJR_Eths7Pq6HHd2RSXmwYp5foRnwe0_U75XmesHWDJlJUHYbwCZo0kP9G8g4QbucwU-MSNBkZOO2x2ZtZNexpHd0ThkATbnNlpVG_z2AGNORp_Ve3rlXwrGIXXw",
e: "AQAB",
},
privateJWK: {
kty: "RSA",
// unpadded base64 for rawKey.
n: "09eVwAhT9SPBxdEN-74BBeEANGaVGwqH-YglIc4VV7jfhR2by5ivzVq8NCeQ1_ACDIlTDY8CTMQ5E1c1SEXmo_T7q84XUGXf8U9mx6uRg46sV7fF-hkwJR80BFVsvWxp4ahPlVJYj__94ft7rIVvchb5tyalOjrYFCJoFnSgq-i3ZjU06csI9XnO5klINucD_Qq0vUhO23_Add2HSYoRjab8YiJJR_Eths7Pq6HHd2RSXmwYp5foRnwe0_U75XmesHWDJlJUHYbwCZo0kP9G8g4QbucwU-MSNBkZOO2x2ZtZNexpHd0ThkATbnNlpVG_z2AGNORp_Ve3rlXwrGIXXw",
e: "AQAB",
d: "H4xboN2co0VP9kXL71G8lUOM5EDis8Q9u8uqu_4U75t4rjpamVeD1vFMVfgOehokM_m_hKVnkkcmuNqj9L90ObaiRFPM5QxG7YkFpXbHlPAKeoXD1hsqMF0VQg_2wb8DhberInHA_rEA_kaVhHvavQLu7Xez45gf1d_J4I4931vjlCB6cupbLL0H5hHsxbMsX_5nnmAJdL_U3gD-U7ZdQheUPhDBJR2KeGzvnTm3KVKpOnwn-1Cd45MU4-KDdP0FcBVEuBsSrsQHliTaciBgkbyj__BangPj3edDxTkb-fKkEvhkXRjAoJs1ixt8nfSGDce9cM_GqAX9XGb4s2QkAQ",
dp: "mM82RBwzGzi9LAqjGbi-badLtHRRBoH9sfMrJuOtzxRnmwBFccg_lwy-qAhUTqnN9kvD0H1FzXWzoFPFJbyi-AOmumYGpWm_PvzQGldne5CPJ02pYaeg-t1BePsT3OpIq0Am8E2Kjf9polpRJwIjO7Kx8UJKkhg5bISnsy0V8wE",
dq: "ZlM4AvrWIpXwqsH_5Q-6BsLJdbnN_GypFCXoT9VXniXncSBZIWCkgDndBdWkSzyzIN65NiMRBfZaf9yduTFj4kvOPwb3ch3J0OxGJk0Ary4OGSlS1zNwMl93ALGal1FzpWUuiia9L9RraGqXAUr13L7TIIMRobRjpAV-z7M-ruM",
p: "7VwGt_tJcAFQHrmDw5dM1EBru6fidM45NDv6VVOEbxKuD5Sh2EfAHfm5c6oouA1gZqwvKH0sn_XpB1NsyYyHEQd3sBVdK0zRjTo-E9mRP-1s-LMd5YDXVq6HE339nxpXsmO25slQEF6zBrj1bSNNXBFc7fgDnlq-HIeleMvsY_E",
q: "5HqMHLzb4IgXhUl4pLz7E4kjY8PH2YGzaQfK805zJMbOXzmlZK0hizKo34Qqd2nB9xos7QgzOYQrNfSWheARwVsSQzAE0vGvw3zHIPP_lTtChBlCTPctQcURjw4dXcnK1oQ-IT321FNOW3EO-YTsyGcypJqJujlZrLbxYjOjQE8",
qi: "OQXzi9gypDnpdHatIi0FaUGP8LSzfVH0AUugURJXs4BTJpvA9y4hcpBQLrcl7H_vq6kbGmvC49V-9I5HNVX_AuxGIXKuLZr5WOxPq8gLTqHV7X5ZJDtWIP_nq2NNgCQQyNNRrxebiWlwGK9GnX_unewT6jopI_oFhwp0Q13rBR0",
},
signatures: {
PS1: "F2BNRubNQEV2UQmCbXfvbP9SVouSapg8jk9FstJGIWVG+0aJT7kZ18axS2hu0vYNqLPPxTecbVCLev38NCOLyOmtG27EFE0fAC57bEA8j/Vdf2ld619XTCMHCHHWq6jvyElpcC0MaERn1CgEseICIskOKYyhYZ9llF+7hcoEqA5h2R9Gw4Ki/14h7gQxi6VkyRxagDR5NH2CIFtFrcO9UaBrncpQvDMq82E+02ut+ytFq2sX/hR3hv1HfcFeiVIAS8OMc5QZHbJaQHSMq07J8v1IXIAl/M0qIBNgJLJYEm3I+k74dRGcYhbKKTRzNgkn7NS6//+w29npjh04P4IxEg==",
PS256:
"GNk2xMfkt1zvug6d7qWDvW3jDo9AL3ja3HWSits2u+AJ7KoIEai7niiRfqraC7pcV9z4sDiZBSQGBAItWqTRhAh/sBxl/e1PGWxOBtlHLWdk8k3pQACacBfHpLeG3oHoOa3PSmsxA38ltYlLE0Gblu8VW4vMstd8rJPLi441eJn/hv29olubCqxcgxKibvXpYRwJ/fS+TeI1IVFFB0H5yQjOdRFlTClOyaIhSyojRdtXbrw8SXg5qB+ZBP3qno4b8la/S8tiKd4fv8TsyhmhFkTCYeUJlgo0IaNw3ZYxwoNpbDxxD4hrkagRjTe9nfdWGOyHphA6A9IxDjU7nklh2g==",
PS384:
"T9b52Ue9/M57xWUXBTGGkWGKvEi8+GXxxRQMCM7MUF+tf7jg60NU22RHkLU6HWdsKsagT3nZ5A/DMEUvgNgWdYh39aeoB7nO4KkVEBEbVM8GmdsKE4CjPJIQG/0aKfb5uaYoGDdVoxUEqnp94+P+0jPD4hk8281kEEYw+ZdDWEhXb6oODLTM3nDD2Wp6aA1h/qjvogQ+H/TkuyexWKFU6AatfMkZxrt7QQoO27Zv3TFPpAVgvFeSqQx2UZl5PCroNOyEoIecnC2p2kS72DjLDuIj3K/3TKkuPI2YpRcyP7xsb7EQSEzxzR+ATIY08YL3NitEPFEq2i5IRlLIyUeLBQ==",
PS512:
"fVSPVfSplUmfS5LyAsMsjKAJFnCfMUTCRTyWwA7RWA293ULI1VqZdoo7Paw/5pdcUTHYz7IGItsaAqEe/EFK179VFawWaw5ygPFUBTZEtuFToR2kEF8MiXGIY6uRtQER7p7VE23HL1OHFAWN8EZLLuKdJ7JO8pK2NKftDOBZ7Mn9BQ67rpCxelS7ynfqmruoNotNm5zz/Gtq1rPBHqBr7pLGSdFmQMNgVTbyRRgDoh0Lz6IWNWVMzeR9hD/sBC3r8ldd3EH4iGhxx9tJeu4nLnQUD1laAqXZ1OB+P5KFN8Stwd4D7XX71xOkfa8vZN157UlQp+Cc7usPDFTf6vVeBg==",
RS1: "Wgd/3kWFGLIPwWi4ZPdr0U2hNi1zb4Q4xyjFmz+qNyXAAls6s56277+I5yxGAOEx0rsiEZTJs+NK/9JEFIeqPIX1MViytUcHpfSvpnc/ZRnD3V6r3xgUOnrWuCd2kvohj7mOGefFK6D38i4nOZ72j6KmZKU7vsg0o+fN7vKTZxy7B2KJYqQ08BeJjbmfEN+MaNXA+fxcpzMlwvrir5QhS0K3A5d3ioagn3tuPP4HZis2yziOZTO+Nfn02v3oJMhvUABwZyxJG/h7ZwcnbQAKarD7p68rQWQQKA/VpAmAY2IvArjA4dBQZP+xgs+16HeYWk5ZtElC3TjVvxbQfGaPpA==",
RS256:
"Y4nhtBnxNsINRRSVp0g7fnlHsZ5PRm9gmsGN5+tw2Fpm9QWsLXsZXeP5JknLFwmuyaXEMWGlDzZg2SE3EQIuBmYeJaUTRn9C3JE70O6UDB6UrKzRLswWXNZ5CVgsIqIA1CZnLYwaeHp400l97ZsS81aKBWtiDISy/e5ng3DgAx6Pfyel/0alb+2ko/BJtnAhYVCUBbh2vAcmsQIQ4kePtjc/eC7E7H1KVBBoLlAQmZs7EtlJxTrMUk4b1k8CLb4nsxEeS1chK6+Zi9tQTFp7A/jC2Xres/J5wRhyI7EwqBC++asL3U4wyxhYH0FvWrIqnPpRHCYY14kIpEZUawo2Iw==",
RS384:
"IWP/7/V43BVVigSEZKKt+Yn+Ok4NmbrX60Vx8CAdnOwjUgS8Usazmyc104z/gIrsiEeFTCaTyeZik1sGfAFb+rtNIcXdC1bzSKUW5kapjp74O572S4b2unXJr+bq8ayxOb+49fEmnb4K+cgvbzYsboJcY3MnqYydA29iNrZMmkMzimL618xIfDzQYe9RwkMGvIexIkHKCXYt9S+m8zgUn9CrDuc51gIPWiT0kORigM2DFmhHl7MeWSo+GFDsCJlwFKDY56QmHti2l98z9Sm0cSv7Ckq3Fo858u2Zyf/FbzJfYynNO8NIfvXWFy51wcYvLzS3esNC+02Fx2k4Nkvdxw==",
RS512:
"hGhPzYh9FtA0WPygZf8E7TXBBKJk/ybTKGp8S+X9CcGhT0fgHEvlgMZPEx7mXhYxOpPMbzF2SPBAx10WAxshZora4fO/2gDUrGFKvuVLltxd0H7pnU2U6Ciyi1ZbI9inZKWk53sbp4VANJlzDopISuqLPUZPxLpvXD+mZZb/RLCHaCV6Bx7DEvpvPE62mcKlv5yottmFSuu5O8T/pQjL7BdSf8dZHMhxg/mpoDZSp0cvKW5H4FglXU6WcvxACYtT6vHwYRCsmqb4bx/ImP6UOt2wGEQ5o1oysglxEj+tW02ihpZHABDX8AKAYcOISurB2qrOG4G7eLzmxFo8KhbRdA==",
},
},
"4096": {
size: 4096,
publicJWK: {
kty: "RSA",
n: "2qr2TL2c2JmbsN0OLIRnaAB_ZKb1-Gh9H0qb4lrBuDaqkW_eFPwT-JIsvnNJvDT7BLJ57tTMIj56ZMtv6efSSTWSk9MOoW2J1K_iEretZ2cegB_aRX7qQVjnoFsz9U02BKfAIUT0o_K7b9G08d1rrAUohi_SVQhwObodg7BddMbKUmz70QNIS487LN44WUVnn9OgE9atTYUARNukT0DuQb3J-K20ksTuVujXbSelohDmLobqlGoi5sY_548Qs9BtFmQ2nGuEHNB2zdlZ5EvEqbUFVZ2QboG6jXdoos6qcwdgUvAhj1Hz10Ngic_RFqL7bNDoIOzNp66hdA35uxbwuaygZ16ikxoPj7eTYud1hrkyQCgeGw2YhCiKIE6eos_U5dL7WHRD5aSkkzsgXtnF8pVmStsuf0QcdAoC-eeCex0tSTgRw9AtGTz8Yr1tGQD9l_580zAXnE6jmrwRRQ68EEA7vohGov3tnG8pGyg_zcxeADLtPlfTc1tEwmh3SGrioDClioYCipm1JvkweEgP9eMPpEC8SgRU1VNDSVe1SF4uNsH8vA7PHFKfg6juqJEc5ht-l10FYER-Qq6bZXsU2oNcfE5SLDeLTWmxiHmxK00M8ABMFIV5gUkPoMiWcl87O6XwzA2chsIERp7Vb-Vn2O-EELiXzv7lPhc6fTGQ0Nc",
e: "AQAB",
},
privateJWK: {
kty: "RSA",
n: "2qr2TL2c2JmbsN0OLIRnaAB_ZKb1-Gh9H0qb4lrBuDaqkW_eFPwT-JIsvnNJvDT7BLJ57tTMIj56ZMtv6efSSTWSk9MOoW2J1K_iEretZ2cegB_aRX7qQVjnoFsz9U02BKfAIUT0o_K7b9G08d1rrAUohi_SVQhwObodg7BddMbKUmz70QNIS487LN44WUVnn9OgE9atTYUARNukT0DuQb3J-K20ksTuVujXbSelohDmLobqlGoi5sY_548Qs9BtFmQ2nGuEHNB2zdlZ5EvEqbUFVZ2QboG6jXdoos6qcwdgUvAhj1Hz10Ngic_RFqL7bNDoIOzNp66hdA35uxbwuaygZ16ikxoPj7eTYud1hrkyQCgeGw2YhCiKIE6eos_U5dL7WHRD5aSkkzsgXtnF8pVmStsuf0QcdAoC-eeCex0tSTgRw9AtGTz8Yr1tGQD9l_580zAXnE6jmrwRRQ68EEA7vohGov3tnG8pGyg_zcxeADLtPlfTc1tEwmh3SGrioDClioYCipm1JvkweEgP9eMPpEC8SgRU1VNDSVe1SF4uNsH8vA7PHFKfg6juqJEc5ht-l10FYER-Qq6bZXsU2oNcfE5SLDeLTWmxiHmxK00M8ABMFIV5gUkPoMiWcl87O6XwzA2chsIERp7Vb-Vn2O-EELiXzv7lPhc6fTGQ0Nc",
e: "AQAB",
d: "uXPRXBhcE5-DWabBRKQuhxgU8ype5gTISWefeYP7U96ZHqu_sBByZ5ihdgyU9pgAZGVx4Ep9rnVKnH2lNr2zrP9Qhyqy99nM0aMxmypIWLAuP__DwLj4t99M4sU29c48CAq1egHfccSFjzpNuetOTCA71EJuokt70pm0OmGzgTyvjuR7VTLxd5PMXitBowSn8_cphmnFpT8tkTiuy8CH0R3DU7MOuINomDD1s8-yPBcVAVTPUnwJiauNuzestLQKMLlhT5wn-cAbYk36XRKdgkjSc2AkhHRl4WDqT1nzWYdh_DVIYSLiKSktkPO9ovMrRYiPtozfhl0m9SR9Ll0wXtcnnDlWXc_MSGpw18vmUBSJ4PIhkiFsvLn-db3wUkA8uve-iqqfk0sxlGWughWx03kGmZDmprWbXugCBHfsI4X93w4exznXH_tapxPnmjbhVUQR6p41MvO2lcHWPLwGJgLIoejBHpnn3TmMN0UjFZki7q9B_dJ3fXh0mX9DzAlC0sil1NgCPhMPq02393_giinQquMknrBvgKxGSfGUrDKuflCx611ZZlRM3R7YMX2OIy1g4DyhPzBVjxRMtm8PnIs3m3Hi-O-C_PHF93w9J8Wqd0yIw7SpavDqZXLPC6Cqi8K7MBZyVECXHtRj1bBqT-h_xZmFCDjSU0NqfOdgApE",
p: "9NrXwq4kY9kBBOwLoFZVQc4kJI_NbKa_W9FLdQdRIbMsZZHXJ3XDUR9vJAcaaR75WwIC7X6N55nVtWTq28Bys9flJ9RrCTfciOntHEphBhYaL5ZTUl-6khYmsOf_psff2VaOOCvHGff5ejuOmBQxkw2E-cv7knRgWFHoLWpku2NJIMuGHt9ks7OAUfIZVYl9YJnw4FYUzhgaxemknjLeZ8XTkGW2zckzF-d95YI9i8zD80Umubsw-YxriSfqFQ0rGHBsbQ8ZOTd_KJju42BWnXIjNDYmjFUqdzVjI4XQ8EGrCEf_8_iwphGyXD7LOJ4fqd97B3bYpoRTPnCgY_SEHQ",
q: "5J758_NeKr1XPZiLxXohYQQnh0Lb4QtGZ1xzCgjhBQLcIBeTOG_tYjCues9tmLt93LpJfypSJ-SjDLwkR2s069_IByYGpxyeGtV-ulqYhSw1nD2CXKMDGyO5jXDs9tJrS_UhfobXKQH03CRdFugyPkSNmXY-AafFynG7xLr7oYBC05FnhUXPm3VBTPt9K-BpqwYd_h9vkAWeprSPo83UlwcLMupSJY9LaHxhRdz2yi0ZKNwXXHRwcszGjDBvvzUcCYbqWqjzbEvFY6KtH8Jh4LhM46rHaoEOTernJsDF6a6W8Df88RthqTExcwnaQf0O_dlbjSxEIPfbxx8t1EQugw",
dp: "4Y7Hu5tYAnLhMXuQqj9dgqU3PkcKYdCp7xc6f7Ah2P2JJHfYz4z4RD7Ez1eLyNKzulZ8A_PVHUjlSZiRkaYTBAEaJDrV70P6cFWuC6WpA0ZREQ1V7EgrQnANbGILa8QsPbYyhSQu4YlB1IwQq5_OmzyVBtgWA7AZIMMzMsMT0FuB_if-gWohBjmRN-vh0p45VUf6UW568-_YmgDFmMYbg1UFs7s_TwrNenPR0h7MO4CB8hP9vJLoZrooRczzIjljPbwy5bRG9CJfjTJ0vhj9MUT3kR1hHV1HJVGU5iBbfTfBKnvJGSI6-IDM4ZUm-B0R5hbs6s9cfOjhFmACIJIbMQ",
dq: "gT4iPbfyHyVEwWyQb4X4grjvg7bXSKSwG1SXMDAOzV9tg7LwJjKYNy8gJAtJgNNVdsfVLs-E_Epzpoph1AIWO9YZZXkov6Yc9zyEVONMX9S7ReU74hTBd8E9b2lMfMg9ogYk9jtSPTt-6kigW4fOh4cHqZ6_tP3cgfLD3JZ8FDPHE4WaySvLDq49yUBO5dQKyIU_xV6OGhQjOUjP_yEoMmzn9tOittsIHTxbXTxqQ6c1FvU9O6YTv8Jl5_Cl66khfX1I1RG38xvurcHULyUbYgeuZ_Iuo9XreT73h9_owo9RguGT29XH4vcNZmRGf5GIvRb4e5lvtleIZkwJA3u78w",
qi: "JHmVKb1zwW5iRR6RCeexYnh2fmY-3DrPSdM8Dxhr0F8dayi-tlRqEdnG0hvp45n8gLUskWWcB9EXlUJObZGKDfGuxgMa3g_xeLA2vmFQ12MxPsyH4iCNZvsgmGxx7TuOHrnDh5EBVnM4_de63crEJON2sYI8Ozi-xp2OEmAr2seWKq4sxkFni6exLhqb-NE4m9HMKlng1EtQh2rLBFG1VYD3SYYpMLc5fxzqGvSxn3Fa-Xgg-IZPY3ubrcm52KYgmLUGmnYStfVqGSWSdhDXHlNgI5pdAA0FzpyBk3ZX-JsxhwcnneKrYBBweq06kRMGWgvdbdAQ-7wSeGqqj5VPwA",
},
signatures: {
PS1: "a38dFvL/hQ0qjBerIhwWynGC+6Yjj+QgdlrnvPcBCHidhy1rEKJ/nUqopaiC/M9FLSXexPAyY5++WgQwhDTOOqN6QnrcNAqAecGK9HnW14Gqybm0aJqRKYTLe2l+LC6r2Nxkjbc1EBl8vnMcUBBcNDyPOko+IxwIOZz4VYEuauPlNVi1RXA6Hjfbi8ISZHazosLRDOeG/beGzqZbjRAdQWA1I0MrxtIWgbo2Qq0iesRH5MyDm58vC+f7+7BnFztu6oZH5O9Q/UhsyFVYiACN4Tm8smEVtSr4mQSyPSJ9FfA9sRzQz7SQmD9l7qeS3Cup7J3QmlRRAPMDT2UZrNS+hFBGbPfAjWXaOdiEeZ8fx4vmzaSjwPn1/zgsvr2VCgXBrFLsWJJdzZrYLZtcntlR8Hypyf4D9C2o6Cj4YF/gBRDM49w22+JnJY0lsKypdWToe5lz7e+n2gFH62/I+grLF9okYUNedO6Mf6HlfqK8T0FnHEjXgf2F1+mcLPEQg6nNA8PKXv7bFmyhGUUSleyknYFlkhXLLuM50ESi50jyo+sWPzkd5URcxhQKsSPzdLu9zekXGQBOwS+Lj/fLmqmm5/v4dL+l4BEVPYAl3gKkbOIZhRIL4krrfKQxhuxVkKIPUO/OYjY4w0ZS7KznjK7GW71Mjy6jmqUEbHfipK7h3Ys=",
PS256:
"15bjguWMKU7Oig7k66IuL3nX/uaqgfOO83LBci3hSyzV1oiUQDS2noIAqb6EKnI9SWoSxHJlxMoq5FQKf+5+H3GCSgxvia28heBVAQycAPdAc1pTBAPsJqDYCTCXhrLFTR7UgQi8V/e2wBlf5SrmWRdBkJ5wxckHMvi3V//G4L4I7iUEdCOv3Bd4O197NpOYa0F+Ig3MFAIrufJu+y58Fs72ro07HD2UB7nJ43nFbTl1Yeda7uzaUzXZw+o2RfLHf2oa7w86P8BsYEOdz8dcVnepFnCqfu+aW1UlnwCCB5jmyixDrqxhqdiHiuug+rBiJOzuRQIIUdbRY/dU/ud7v5CGeZhk/V1R+6dq7FGL9UQPVPpkTwpQCdomLLYFyrrD46v9p19i3Le0k2ipvRJtLYX3OaAlozBcWQmymeaTBc4pAFhfp7DdfIUmtSrr2fmvx5zbQjPU82VXDo8AIJAnP+teep7pfcq+e9Tom9Yb1yyX0wty0IYqXrucpFgBaEzpyFtw+N4aPzuZIUzRkSsZMnuTpoQfkAiJURx7lhFU19e9zZW2phaSzhilVTfUcWXMW7/v6jqKK+ZS7e6EUcr0fbjOzrIQttfL5UrBDhPAmjo6FkFQ9VH9Ph9ABcWNhJYqfBy/C3Va0fln34ALKp0r1G9Z5ZNBCeNeOprHmWPKAbQ=",
PS384:
"IO5qJWTFlnm5LmgPMqoQ9rBbTOkj/GBLOhIM5xmxZtC0bZil1FUeGYnkNotGP91br8RTLtdUJ6yBJ1JzekOLZClMbFB4nC2i7cx8+O0YLeThxRkNbzOxUxYnlSCJkh+cW9oA0OSS5Bm/Wq8dDx/F5m7mkDeSEfLObkGNuEJ6aGRVNZPaYChZyGvu4saysB5L9d0rJFpf0LbutOhx2o+WCuR3HvkYv9kDlbIHWPuwAXMdvJjfS5GK8SgOlLr20+Ex2iPCOR/PZGIi8iC1XaGchicVfouE2Zsgxzbq2+BPXqHp63LwtTNHOrcP6wrp8zBbTlOKN/I+pR811lGcsr4jur2dNvZtsTszolrvyqg1i0YDd3Qb5AiDW6M6dh4/5tRMVqM0o8vCi84acHlDSdGCZKCKELlJxtPI4h5sSO2pv+IcfGLfJV16ZDquWOQCsYULt5UWvsjpT7C7ZgGh2ULnd5J5xbdTQ/ZRRme7lFR2cUgjQacuyDUSxguqwA9RypiGewz+FUZ/yzqlKeLLTxQbVsU800CANUxYsQXzKMV4VxArD19ZY5UjljC5t5jnfRwIQSbGri5UdUOgaxveLY0q/2gUJr/eryYw18QZ1KTWzInR+k1T2J4Q0toNBl7UcsD1qOcWeKSg2cv0Hn0W0XHDOnulsae6VHD+yQSX6XAikPI=",
PS512:
"uDxVnOzdKyy364+Z3eXEoLz3WPzyJxziQA9SIe37JZD2sARndTCPDDZesWg0V0/YVoEtsJnF+btG/lCIcUoRsklVKzahPEKfwrE0ABmy7F3Z4tv3C59SF4c5Pof7BaShHCCea+GFl22E5YZmrzLH0qpbt6Zd1fhYGGKF+r3E2qiYfoKmwMICvXu3Nn2bI5KUpjRVOYz1hmgKDeTf+6irmT58o30thO9peRukgB5DIOhq+227sN6qTbzCIWWIn4JIsv7p5T2pFl/JyKkWQV11+7stGzV1hQBL/SS1AqkiskZpm0756cyege3QnM8gPp/weUcsXeizay805qYCTXL9r+9p4unltlIkr9fL/c/HKJ01w6UN3FgfT0UqH8TdtoXUkW2LOxnQEtW9LuckYWMOPhQEeq45dTz9T9IMuQuZ6S84WN9ziAzF1xhpvEeas+LOy+h2G/y5+ggrrLnzoEWTTT3rZBrrLq31/3FHLl4BqK966zkMnCDooHB1i7Vr4UAsqQcG8lGd7+5q4irkNennmZkmXUdpW++ZxjCUk2e6dVDVp6Pi+IMEt7Fq8EmvevlBGWDTkRsynQsA7lIkc0JljECst/T29d6Pix5wqjLyWMMUyrdJeeGga8YKBRmP97IyTKwBOb5uSOFYngtZimDfPQ1Q53iIsc6RSBE6yltTIrs=",
RS1: "D2fun2Te/MX3iW6KJcLkmFiaCFqZKP6AFLvnLZUmEpHtYjJlJS2LQSBdmlgVmeNxiM+o8dVfaGbldlxjyd/uSZb/4ZNNJ00zubswfYOEQOSZ5qUTDSc5yHu3/nAiul922yhSQ4puFiuG4mnquZxxvoZTtgq8FFnOW6l0RLj/HtG2zZplGgk/Ax/Zbmlc1Em7os0BpyPY7TE1kWgTduh2AcwIFgLB+4gjdftzHFypPlLxKEjcGRhnsIMxXoJljfagTUjB1BmHvYPr8/hSmlDLPfhxkEsz7ZfNOrT3CaMgmEPOGFFDlkzYV+hK4HGGHeLXq4i/sefcdUX6LsOw8vfENHbGQDXXCjcjutsPUylll0OCluvAUYY+av2O++s1uR15c0Ef7qfP1WuNcQjk0UF9dkOYiMEuFuTZZ+r2FwGoxZ92wBXEZiUv5YOkccXRH27WHFaEz1IQZOQSMz43Nc4bt+f5wh/1ml6+8jAAiuUirTBb4B7UZw+5kTjd+G17g8oGXQs+V88E/p6Q3l42yL7e6Y2jau9Jd/uXmPfhmDr+aVM3ecnZPrhJjA73N+HRoEgTpSOx6nAwivsyMFcivPHvFXXh0C5Gx/tnbP2NBcm9492352NwuBOe3o5iBfY/tB4921m6AKr6u+j32isa4LYRaUhQOFEO3VlhxDf+D5Qa0Xs=",
RS256:
"1/SNd+ZxGQ1oDypzENJK/uORQpHamObdSa/sGo8R2O9XXC8COQKgxRRauhcRvcAxoDF+ApZc7Ua5hAuWJ9AGy8OmfguxIPQHcPGPZo8rwAC+MNMDR5xXtiW6ZUwZDDID4rT7dmlRYJfGBuTk1KRwH6dF9jiZwy8JplGVa+pO9YlI0ZnCCz1y8K1uaqk49tha6w8ZSMeQejtsLBTPQ+H4zqa+cCGPFN/6eclMxJR0nZgVv5kDnf+ZUJWnhYCdCp3Xm7vH/oOK4rOlsTCpJfxP6k+Ojnf+gVEcqQeM1GSqZlrdzWJk8cdneQIDJ531IsTMxQzlxwciICp6bN/qYMmANQdtEUqM8MBwFnu2nYozr9m6TQ/dXCqMy3c9A1Pyijc6xI1sWNFtDPe9k7oSv+/DblD0SLeshs+H2eiBoKUBn/XcWkigthNd/BC36r/70iJHcs6MNQZCH7Ti15YoI1G8NZvwJRs3rLsLLLN72g0YAhs2s5A5QVWB6inNTHMU7a2F2n0DpHswkezun/j+jfS3pVcvzt4awsc8C9llnIhvsujZ0EfPj4GlHUYiV2tku6fsUbbJ2MCzJbqCuXmc6p+2baNOGSkUqDM/pDoNoi3j5nn6R5JWfWSl+jGnPB/GkwrTQVQ86qjiQ5jQ44JN3RqJ7EzkFOe5uu6xiu+4z9k2+U4=",
RS384:
"PpiTKV8MXYlCVbEi1t4N8QSjZToXPrVLnh7Kt3HXdTkf88m5Sg4mU3ns/rQDqcYZcZTIYnGlW7w8lK4mHxAvB099dQNTXA5D3N8mXy/zy5eY2wzbTng09DgIBQmiNkNYzBOeJaeJWclTF/8NCJXbV6WlIyKc/xsJJ7VJ3btSm59qj2Nicqp8USdBVrBw9lIBUryc00iyWf1BuAnIWdI3jMyyC0lO9pulj4mROykkTbdGJN3EEiJQEV41Y2CFrenGqtgL0gj2BsR1RgcmUso/2ZEKUhe9Jnsfytj7vEYToxNH66QfzBZpw1UuwOiUpK7hf2UZ5Qqu1SWkJb5ZoG/nHx2L9xJsZEEPc2J0NjNB74THcw2TBsnYDzXjf6l0uAOgtFvnNv5ZxRCvjZKhZ6Kw1KpeK5UDmFTwL9V0ubmzZor6Gt7kXGLTWW/zo/lPMvLwgUc95DK0lE2teVgcODX/7DNN3JDSy9JQ/EKkplKTr9rDApYPYor2l5Ku6lDNwuwcS9x8rKtRaWwDuaoGn9co5YQLJwgApb+C0FDBX75Y0JGO8K+8T5v+ZZfQZRmxMJuiaDonGC5qJHnXKEmjFwJzgu91mBGrtQ+lToHGOxGSsJ4dtbQJS/BSz2exKCgWwwJsRmAGkU4/dtnXed4+D6zpQY5XWRPnMdwx96ALYe6rgR0=",
RS512:
"XZG8gmxVcqTqECrhxZD/7vObvxeJVWW5kKvSw8FGdeRacRvO4FinFoOb44XmaqbUjGAeufPhfpiyH4mvvGKD4sfzIwOJM1Jz2tHdwjkvbpwjzOm8xElzlosx4FKO1VMOs/Zj5n6B0n96Hlx17+Gaw2MALMZNRR5R3vBV962dIA3fXTFPWAuWykQrMplyN1A2DWlL9qCkh/Gl5Nt1PI546vf6ZxsY+JDyZ60FgzkSZA1X4JXbHsPEl0hOsDNdQXo9lmPDy8pYlZyRvlCWHArMKOkLhzwPpm8tOTLy3C2o/QOeL3f+jT15I9TGbKHOLuBm8RSJVpwH0/ene97vfG9q9nTsNEm61wifkoRCDdKI7sJ3FHflGECdlab1o7H8PK93D75NSHziLnyNgzY+BIoeYEWBaGIbGEYtSmeje3c0onKcyLtd60WWxhGk9VGvEVfwFDyZFvR5hthOrD/S4odExJ9JxUooZAOVzN3yguTLVOTCxv0p+J3PvviSMvlnYcrxs+aiddOt3T4nu0Wp0INmZ15E4oWX/Ic7lkqsNBr2a8sTVwSGgu45IIyJKj+ABSteKw8GrpzyuyYdU/a+Z/LJF8mVDP3FdllSkDnpJSrXVHAl9Xivvws0WqbsZlNEYDWZxRDpUrDObAb6bG9/93ZWFRZcoHHTxknBGwblCfA9rik=",
},
},
};
async function testImportRsaJwk() {
const subtle = crypto.subtle;
assert(subtle);
for (const [_key, jwkData] of Object.entries(jwtRSAKeys)) {
const { size, publicJWK, privateJWK, signatures } = jwkData;
if (size < 2048) {
continue;
}
// 1. Test import PSS
for (const hash of ["SHA-1", "SHA-256", "SHA-384", "SHA-512"] as const) {
const hashMapPSS = {
"SHA-1": "PS1",
"SHA-256": "PS256",
"SHA-384": "PS384",
"SHA-512": "PS512",
} as const;
if (size === 1024 && hash === "SHA-512") {
continue;
}
const _privateKeyPSS = await crypto.subtle.importKey(
"jwk",
{
alg: hashMapPSS[hash],
...privateJWK,
ext: true,
key_ops: ["sign"],
},
{ name: "RSA-PSS", hash },
true,
["sign"],
);
const publicKeyPSS = await crypto.subtle.importKey(
"jwk",
{
alg: hashMapPSS[hash],
...publicJWK,
ext: true,
key_ops: ["verify"],
},
{ name: "RSA-PSS", hash },
true,
["verify"],
);
// const signaturePSS = await crypto.subtle.sign(
// { name: "RSA-PSS", saltLength: 32 },
// privateKeyPSS,
// new Uint8Array([1, 2, 3, 4]),
//);
// Check that an externally generated signature verifies
const signaturePSS = Uint8Array.fromBase64(signatures[hashMapPSS[hash]]);
const verifyPSS = await crypto.subtle.verify(
{ name: "RSA-PSS", saltLength: 32 },
publicKeyPSS,
signaturePSS,
new Uint8Array([1, 2, 3, 4]),
);
assert(verifyPSS);
}
// 2. Test import PKCS1
for (const hash of ["SHA-1", "SHA-256", "SHA-384", "SHA-512"] as const) {
const hashMapPKCS1 = {
"SHA-1": "RS1",
"SHA-256": "RS256",
"SHA-384": "RS384",
"SHA-512": "RS512",
} as const;
if (size === 1024 && hash === "SHA-512") {
continue;
}
const privateKeyPKCS1 = await crypto.subtle.importKey(
"jwk",
{
alg: hashMapPKCS1[hash],
...privateJWK,
ext: true,
key_ops: ["sign"],
},
{ name: "RSASSA-PKCS1-v1_5", hash },
true,
["sign"],
);
const publicKeyPKCS1 = await crypto.subtle.importKey(
"jwk",
{
alg: hashMapPKCS1[hash],
...publicJWK,
ext: true,
key_ops: ["verify"],
},
{ name: "RSASSA-PKCS1-v1_5", hash },
true,
["verify"],
);
const signaturePKCS1 = await crypto.subtle.sign(
{ name: "RSASSA-PKCS1-v1_5", saltLength: 32 },
privateKeyPKCS1,
new Uint8Array([1, 2, 3, 4]),
);
// These signatures are deterministic, assert exact equality
const expectedSignature = Uint8Array.fromBase64(
signatures[hashMapPKCS1[hash]],
);
assert.deepEqual(new Uint8Array(signaturePKCS1), expectedSignature);
const verifyPKCS1 = await crypto.subtle.verify(
{ name: "RSASSA-PKCS1-v1_5", saltLength: 32 },
publicKeyPKCS1,
signaturePKCS1,
new Uint8Array([1, 2, 3, 4]),
);
assert(verifyPKCS1);
}
// 3. Test import OAEP
for (const { hash, plainText: _plainText } of hashPlainTextVector) {
const hashMapOAEP: Record<string, string> = {
"SHA-1": "RSA-OAEP",
"SHA-256": "RSA-OAEP-256",
"SHA-384": "RSA-OAEP-384",
"SHA-512": "RSA-OAEP-512",
};
if (size === 1024 && hash === "SHA-512") {
continue;
}
const encryptAlgorithm = { name: "RSA-OAEP" };
const _privateKeyOAEP = await crypto.subtle.importKey(
"jwk",
{
alg: hashMapOAEP[hash],
...privateJWK,
ext: true,
key_ops: ["decrypt"],
},
{ ...encryptAlgorithm, hash },
true,
["decrypt"],
);
const _publicKeyOAEP = await crypto.subtle.importKey(
"jwk",
{
alg: hashMapOAEP[hash],
...publicJWK,
ext: true,
key_ops: ["encrypt"],
},
{ ...encryptAlgorithm, hash },
true,
["encrypt"],
);
// const cipherText = await subtle.encrypt(
// encryptAlgorithm,
// publicKeyOAEP,
// plainText,
// );
// assert(cipherText);
// assert(cipherText.byteLength > 0);
// assert.strictEqual(cipherText.byteLength * 8, size);
// assert(cipherText instanceof ArrayBuffer);
// const decrypted = await subtle.decrypt(
// encryptAlgorithm,
// privateKeyOAEP,
// cipherText,
// );
// assert(decrypted);
// assert(decrypted instanceof ArrayBuffer);
// assert.deepEqual(new Uint8Array(decrypted), plainText);
}
}
}
const jwtECKeys = {
"256": {
size: 256,
algo: "ES256",
publicJWK: {
kty: "EC",
crv: "P-256",
x: "0hCwpvnZ8BKGgFi0P6T0cQGFQ7ugDJJQ35JXwqyuXdE",
y: "zgN1UtSBRQzjm00QlXAbF1v6s0uObAmeGPHBmDWDYeg",
},
privateJWK: {
kty: "EC",
crv: "P-256",
x: "0hCwpvnZ8BKGgFi0P6T0cQGFQ7ugDJJQ35JXwqyuXdE",
y: "zgN1UtSBRQzjm00QlXAbF1v6s0uObAmeGPHBmDWDYeg",
d: "E9M6LVq_nPnrsh_4YNSu_m5W53eQ9N7ptAiE69M1ROo",
},
signatures: {
"SHA-1":
"zEt4WGwNPRck686n4ZBspknRTsBM0pT3QfBFMzqkfs15uUkUUs/u9GkQ4V7QjTHxG45Fz0R9Jy4e7ya39jyHLg==",
"SHA-256":
"9NxsYdXOfT4xm70VSTUkUSGBRwr030nQN0Hkj9+9uPfPSUF77xhhCenC7L8kpW0B5dwqvkqgJPZ1d6DJk/IFYQ==",
"SHA-384":
"pKZOl30i2WDPNvtC6aCobMaG6AjtRi+NThHSO2L1uEstQvMM2yUR/PPxPFDgsXZbDt6/aZYlHd3lOIDWsllcpA==",
"SHA-512":
"l/b2SZQiga4ZOhS1Z1uNjX9C6jl65CRi3XnSc5rOauMgzzKOMnQBR9PKx1iOxQfAHbeaznyp4moZvmidmDLQCA==",
},
},
"384": {
size: 384,
algo: "ES384",
publicJWK: {
kty: "EC",
crv: "P-384",
x: "IZwU1mYXs27G2IVrOFtzp000T9iude8EZDXdpU47RL1fvevR0I3Wni19wdwhjLQ1",
y: "vSgTjMd4M3qEL2vWGyQOdCSfJGZ8KlgQp2v8KOAzX4imUB3sAZdtqFr7AIactqzo",
},
privateJWK: {
kty: "EC",
crv: "P-384",
x: "IZwU1mYXs27G2IVrOFtzp000T9iude8EZDXdpU47RL1fvevR0I3Wni19wdwhjLQ1",
y: "vSgTjMd4M3qEL2vWGyQOdCSfJGZ8KlgQp2v8KOAzX4imUB3sAZdtqFr7AIactqzo",
d: "RTe1mQeE08LSLpao-S-hqkku6HPldqQVguFEGDyYiNEOa560ztSyzEAS5KxeqEBz",
},
signatures: {
"SHA-1":
"VYSjTIjZTif5KtbjzZQ3eyFqCSxHswImqnXzgbQLSeL7T3aY8EHCeOq9RvbAMR3JB9qy8Us1dF15so1kUbgSW34T1p12l9ziYQ+dwDO/A+Wt4I3NuSyHi+Tgy5L37OJO",
"SHA-256":
"RvDh+/LitQkKMPMSmSFtMoPPYVBvXlr9Sj3UKgIvhojmskh54ZSvO1wKVgrZBbxGo7Wco2I+gMdVhwMfEcREAstpDgffnK3SF+XnWzfRG3w2vDeI+ZvyGfCo4HTSIe7Y",
"SHA-384":
"IClgZJxB/Z8zqyaFw8mnD37c5moC0XS6AvCYWp6AiYz7K0ijEdbbainol8lF7nMyxlRMsYtKSYjX/6tS33DyeONzeOa5VEClD4tSroTbdcKRGz+gtq1Z0ChHPX2/0a7x",
"SHA-512":
"JzSnhUW6MD5HYcAXG5EMXuno76ThQpmXWUI/ub1ddx11PQQ1KVvB0mwoC2BNvLk865CsTI97jBkBXTyHk38wzfMuG7oDQZzhnszbdzcR31xVDh20E1eG72s1Cw/DPnR7",
},
},
"521": {
size: 512,
algo: "ES512",
privateJWK: {
kty: "EC",
crv: "P-521",
x: "ACG0NaV-sbtGdWpFGNbA39GPMDB-nH2-o2RJJow88d7AHB_KtXpqAZzPRYOed5mVdDFUpkcF6St31vQjYUvTulDQ",
y: "AWcRV4X9KS6rzQrJtAfcwilYkxKath5NRku80dsqOijgT9VgDrfBVEDenyFFwqqVVdEJC_q24EMW_A9Oqjx_Ehbx",
d: "Aefg_fgqAYiHiJh5ZqFjjA6doYfgxfu8rzQ3_En-CIgeWu95gEwjFftGcBGa0jBS1-XWOFmNVvw_rpyAFiufso6C",
},
publicJWK: {
kty: "EC",
crv: "P-521",
x: "ACG0NaV-sbtGdWpFGNbA39GPMDB-nH2-o2RJJow88d7AHB_KtXpqAZzPRYOed5mVdDFUpkcF6St31vQjYUvTulDQ",
y: "AWcRV4X9KS6rzQrJtAfcwilYkxKath5NRku80dsqOijgT9VgDrfBVEDenyFFwqqVVdEJC_q24EMW_A9Oqjx_Ehbx",
},
signatures: {
"SHA-1":
"AbEA7jn+iB5SQQSZ6/ugCP3hbYuQQLpRYjHwowfp1wcKSC3/oJ/p/RRGAXg/fExJkL86yd6ual0RwU7c4TyscoyiAc0Q5gwdCBIyvQ/e+i8OBDPybDnW7Tr0c+DFIDSGIYi6ZUgrJIQUby0iuNvOr5p2RNa1Vz2FBFloed44AI6wSASY",
"SHA-256":
"AOXDoJfZ52MhtpognmW/FNqotjCjqag2XO53EcHaS7aBvYnbZFLxCPsrL9Ro9RG3ceclqBKeIIIXFTAoFq6GrxZ/AHteZVQ2NDYOCAdOhVjeVMHHqCf6sNpPZWPFwv8EFK4DqxsfF1xktE+PAxZ6S8YE+Ifb2FOqPrU483oDIDvl/MMe",
"SHA-384":
"AGX8dVP+eOJf5YID0T3sKeQWVSmL9pw/nzOYCNsyZQE6/4A4mAP1s5pm1yXSA1KvVelXvmJ08AaxHsYPPwoDfrklAVTMOp2Ho2bzWCJgRMD1i50AnNFYwI83q9eWYUfSs81losKNnLrcORwnz8Y9lftN7cok32+Se3pGwpEAk4VV29pv",
"SHA-512":
"ADX9qT79Bk91zplGfoIuBC0EuVZupj8OJwTqsPZFch0nQoFkifqFjAcOUNIjFxnIP5LigP1YMKwQ4/0qA+3oXjRjALoJmUNEynJv6DbMQuBEinioN71Q4ZmoRlfJJ3hN3Dio+O4z+FIeJ9V4/0ChK5ZDXLhFEkAiOgFpdfuU3V8a9ZLz",
},
},
};
type JWK = Record<string, string>;
function equalJwk(expected: JWK, got: JWK): boolean {
const fields = Object.keys(expected);
for (let i = 0; i < fields.length; i++) {
const fieldName = fields[i];
if (!(fieldName in got)) {
return false;
}
if (expected[fieldName] !== got[fieldName]) {
return false;
}
}
return true;
}
async function testImportExportEcDsaJwk() {
const subtle = crypto.subtle;
assert(subtle);
for (const [_key, keyData] of Object.entries(jwtECKeys)) {
const {
publicJWK,
privateJWK,
algo,
signatures: externalSignatures,
} = keyData;
// 1. Test import EcDsa
const privateKeyECDSA = await subtle.importKey(
"jwk",
{
alg: algo,
...privateJWK,
ext: true,
key_ops: ["sign"],
},
{ name: "ECDSA", namedCurve: privateJWK.crv },
true,
["sign"],
);
const expPrivateKeyJWK = await subtle.exportKey("jwk", privateKeyECDSA);
assert(equalJwk(privateJWK, expPrivateKeyJWK as JWK));
const publicKeyECDSA = await subtle.importKey(
"jwk",
{
alg: algo,
...publicJWK,
ext: true,
key_ops: ["verify"],
},
{ name: "ECDSA", namedCurve: publicJWK.crv },
true,
["verify"],
);
const expPublicKeyJWK = await subtle.exportKey("jwk", publicKeyECDSA);
assert(equalJwk(publicJWK, expPublicKeyJWK as JWK));
const alg = { name: "ECDSA", hash: `SHA-${keyData.size}` };
const data = new Uint8Array([1, 2, 3, 4]);
const signatureECDSA = await subtle.sign(alg, privateKeyECDSA, data);
const verifyECDSA = await subtle.verify(
alg,
publicKeyECDSA,
signatureECDSA,
data,
);
assert(verifyECDSA, "should verify generated signature");
new Uint8Array(signatureECDSA).set([0, 0, 0, 0, 0, 0, 0, 0], 0);
assert.isFalse(
await subtle.verify(alg, publicKeyECDSA, signatureECDSA, data),
"should not verify corrupted signature",
);
for (const [hash, externalSignature] of Object.entries(
externalSignatures,
)) {
const verifyExternalECDSA = await subtle.verify(
{ name: "ECDSA", hash },
publicKeyECDSA,
Uint8Array.fromBase64(externalSignature),
data,
);
assert(
verifyExternalECDSA,
"should verify externally generated signature",
);
}
}
}
async function testImportEcDhJwk() {
const subtle = crypto.subtle;
assert(subtle);
for (const [_key, jwkData] of Object.entries(jwtECKeys)) {
const { size, publicJWK, privateJWK } = jwkData;
// 1. Test import EcDsa
const privateKeyECDH = await subtle.importKey(
"jwk",
{
...privateJWK,
ext: true,
key_ops: ["deriveBits"],
},
{ name: "ECDH", namedCurve: privateJWK.crv },
true,
["deriveBits"],
);
const expPrivateKeyJWK = await subtle.exportKey("jwk", privateKeyECDH);
assert(equalJwk(privateJWK, expPrivateKeyJWK as JWK));
const publicKeyECDH = await subtle.importKey(
"jwk",
{
...publicJWK,
ext: true,
key_ops: [],
},
{ name: "ECDH", namedCurve: publicJWK.crv },
true,
[],
);
const expPublicKeyJWK = await subtle.exportKey("jwk", publicKeyECDH);
assert(equalJwk(publicJWK, expPublicKeyJWK as JWK));
const _size = size;
// const derivedKey = await subtle.deriveBits(
// {
// name: "ECDH",
// public: publicKeyECDH,
// },
// privateKeyECDH,
// size,
// );
// assert(derivedKey instanceof ArrayBuffer);
// assert.strictEqual(derivedKey.byteLength, size / 8);
}
}
const ecTestKeys = [
{
size: 256,
namedCurve: "P-256",
signatureLength: 64,
// deno-fmt-ignore
raw: new Uint8Array([
4, 210, 16, 176, 166, 249, 217, 240, 18, 134, 128, 88, 180, 63, 164, 244,
113, 1, 133, 67, 187, 160, 12, 146, 80, 223, 146, 87, 194, 172, 174, 93,
209, 206, 3, 117, 82, 212, 129, 69, 12, 227, 155, 77, 16, 149, 112, 27,
23, 91, 250, 179, 75, 142, 108, 9, 158, 24, 241, 193, 152, 53, 131, 97,
232,
]),
// deno-fmt-ignore
spki: new Uint8Array([
48, 89, 48, 19, 6, 7, 42, 134, 72, 206, 61, 2, 1, 6, 8, 42, 134, 72, 206,
61, 3, 1, 7, 3, 66, 0, 4, 210, 16, 176, 166, 249, 217, 240, 18, 134, 128,
88, 180, 63, 164, 244, 113, 1, 133, 67, 187, 160, 12, 146, 80, 223, 146,
87, 194, 172, 174, 93, 209, 206, 3, 117, 82, 212, 129, 69, 12, 227, 155,
77, 16, 149, 112, 27, 23, 91, 250, 179, 75, 142, 108, 9, 158, 24, 241,
193, 152, 53, 131, 97, 232,
]),
// deno-fmt-ignore
pkcs8: new Uint8Array([
48, 129, 135, 2, 1, 0, 48, 19, 6, 7, 42, 134, 72, 206, 61, 2, 1, 6, 8, 42,
134, 72, 206, 61, 3, 1, 7, 4, 109, 48, 107, 2, 1, 1, 4, 32, 19, 211, 58,
45, 90, 191, 156, 249, 235, 178, 31, 248, 96, 212, 174, 254, 110, 86, 231,
119, 144, 244, 222, 233, 180, 8, 132, 235, 211, 53, 68, 234, 161, 68, 3,
66, 0, 4, 210, 16, 176, 166, 249, 217, 240, 18, 134, 128, 88, 180, 63,
164, 244, 113, 1, 133, 67, 187, 160, 12, 146, 80, 223, 146, 87, 194, 172,
174, 93, 209, 206, 3, 117, 82, 212, 129, 69, 12, 227, 155, 77, 16, 149,
112, 27, 23, 91, 250, 179, 75, 142, 108, 9, 158, 24, 241, 193, 152, 53,
131, 97, 232,
]),
},
{
size: 384,
namedCurve: "P-384",
signatureLength: 96,
// deno-fmt-ignore
raw: new Uint8Array([
4, 118, 64, 176, 165, 100, 177, 112, 49, 254, 58, 53, 158, 63, 73, 200,
148, 248, 242, 216, 186, 80, 92, 160, 53, 64, 232, 157, 19, 1, 12, 226,
115, 51, 42, 143, 98, 206, 55, 220, 108, 78, 24, 71, 157, 21, 120, 126,
104, 157, 86, 48, 226, 110, 96, 52, 48, 77, 170, 9, 231, 159, 26, 165,
200, 26, 164, 99, 46, 227, 169, 105, 172, 225, 60, 102, 141, 145, 139,
165, 47, 72, 53, 17, 17, 246, 161, 220, 26, 21, 23, 219, 1, 107, 185, 163,
215,
]),
// deno-fmt-ignore
spki: new Uint8Array([
48, 118, 48, 16, 6, 7, 42, 134, 72, 206, 61, 2, 1, 6, 5, 43, 129, 4, 0,
34, 3, 98, 0, 4, 118, 64, 176, 165, 100, 177, 112, 49, 254, 58, 53, 158,
63, 73, 200, 148, 248, 242, 216, 186, 80, 92, 160, 53, 64, 232, 157, 19,
1, 12, 226, 115, 51, 42, 143, 98, 206, 55, 220, 108, 78, 24, 71, 157, 21,
120, 126, 104, 157, 86, 48, 226, 110, 96, 52, 48, 77, 170, 9, 231, 159,
26, 165, 200, 26, 164, 99, 46, 227, 169, 105, 172, 225, 60, 102, 141, 145,
139, 165, 47, 72, 53, 17, 17, 246, 161, 220, 26, 21, 23, 219, 1, 107, 185,
163, 215,
]),
// deno-fmt-ignore
pkcs8: new Uint8Array([
48, 129, 182, 2, 1, 0, 48, 16, 6, 7, 42, 134, 72, 206, 61, 2, 1, 6, 5, 43,
129, 4, 0, 34, 4, 129, 158, 48, 129, 155, 2, 1, 1, 4, 48, 202, 7, 195,
169, 124, 170, 81, 169, 253, 127, 56, 28, 98, 90, 255, 165, 72, 142, 133,
138, 237, 200, 176, 92, 179, 192, 83, 28, 47, 118, 157, 152, 47, 65, 133,
140, 50, 83, 182, 191, 224, 96, 216, 179, 59, 150, 15, 233, 161, 100, 3,
98, 0, 4, 118, 64, 176, 165, 100, 177, 112, 49, 254, 58, 53, 158, 63, 73,
200, 148, 248, 242, 216, 186, 80, 92, 160, 53, 64, 232, 157, 19, 1, 12,
226, 115, 51, 42, 143, 98, 206, 55, 220, 108, 78, 24, 71, 157, 21, 120,
126, 104, 157, 86, 48, 226, 110, 96, 52, 48, 77, 170, 9, 231, 159, 26,
165, 200, 26, 164, 99, 46, 227, 169, 105, 172, 225, 60, 102, 141, 145,
139, 165, 47, 72, 53, 17, 17, 246, 161, 220, 26, 21, 23, 219, 1, 107, 185,
163, 215,
]),
},
];
async function testImportEcSpkiPkcs8() {
const subtle = crypto.subtle;
assert(subtle);
for (const { namedCurve, raw, spki, pkcs8, signatureLength } of ecTestKeys) {
const rawPublicKeyECDSA = await subtle.importKey(
"raw",
raw,
{ name: "ECDSA", namedCurve },
true,
["verify"],
);
const expPublicKeyRaw = await subtle.exportKey("raw", rawPublicKeyECDSA);
assert.deepEqual(new Uint8Array(expPublicKeyRaw), raw);
const privateKeyECDSA = await subtle.importKey(
"pkcs8",
pkcs8,
{ name: "ECDSA", namedCurve },
true,
["sign"],
);
const expPrivateKeyPKCS8 = await subtle.exportKey("pkcs8", privateKeyECDSA);
assert.deepEqual(new Uint8Array(expPrivateKeyPKCS8), pkcs8);
const expPrivateKeyJWK = await subtle.exportKey("jwk", privateKeyECDSA);
assert.strictEqual(expPrivateKeyJWK.crv, namedCurve);
const publicKeyECDSA = await subtle.importKey(
"spki",
spki,
{ name: "ECDSA", namedCurve },
true,
["verify"],
);
const expPublicKeySPKI = await subtle.exportKey("spki", publicKeyECDSA);
assert.deepEqual(new Uint8Array(expPublicKeySPKI), spki);
const expPublicKeyJWK = await subtle.exportKey("jwk", publicKeyECDSA);
assert.strictEqual(expPublicKeyJWK.crv, namedCurve);
for (const hash of ["SHA-1", "SHA-256", "SHA-384", "SHA-512"]) {
if (
(hash === "SHA-256" && namedCurve === "P-256") ||
(hash === "SHA-384" && namedCurve === "P-384")
) {
const signatureECDSA = await subtle.sign(
{ name: "ECDSA", hash },
privateKeyECDSA,
new Uint8Array([1, 2, 3, 4]),
);
const verifyECDSA = await subtle.verify(
{ name: "ECDSA", hash },
publicKeyECDSA,
signatureECDSA,
new Uint8Array([1, 2, 3, 4]),
);
assert(verifyECDSA);
} else {
const data = new Uint8Array([1, 2, 3, 4]);
const validSignature = await subtle.sign(
{ name: "ECDSA", hash },
privateKeyECDSA,
data,
);
assert(
await subtle.verify(
{ name: "ECDSA", hash },
publicKeyECDSA,
validSignature,
data,
),
);
assert.deepEqual(
await subtle.verify(
{ name: "ECDSA", hash },
publicKeyECDSA,
new Uint8Array(signatureLength),
data,
),
false,
);
}
}
}
}
async function testAesGcmEncrypt() {
const key = await crypto.subtle.importKey(
"raw",
new Uint8Array(16),
{ name: "AES-GCM", length: 256 },
true,
["encrypt", "decrypt"],
);
const nonces = [
{
iv: new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]),
ciphertext: new Uint8Array([
50, 223, 112, 178, 166, 156, 255, 110, 125, 138, 95, 141, 82, 47, 14,
164, 134, 247, 22,
]),
},
// TODO: support other IV lengths
// {
// iv: new Uint8Array([
// 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
// ]),
// ciphertext: new Uint8Array([
// 210, 101, 81, 216, 151, 9, 192, 197, 62, 254, 28, 132, 89, 106, 40, 29,
// 175, 232, 201,
// ]),
// },
];
for (const { iv, ciphertext: fixture } of nonces) {
const data = new Uint8Array([1, 2, 3]);
const cipherText = await crypto.subtle.encrypt(
{ name: "AES-GCM", iv },
key,
data,
);
assert(cipherText instanceof ArrayBuffer);
assert.strictEqual(cipherText.byteLength, 19);
assert.deepEqual(new Uint8Array(cipherText), fixture);
const plainText = await crypto.subtle.decrypt(
{ name: "AES-GCM", iv },
key,
cipherText,
);
assert(plainText instanceof ArrayBuffer);
assert.strictEqual(plainText.byteLength, 3);
assert.deepEqual(new Uint8Array(plainText), data);
const wrongCiphertext = new Uint8Array(cipherText);
wrongCiphertext[0] = (wrongCiphertext[0] + 1) & 0xff;
await expect(
crypto.subtle.decrypt({ name: "AES-GCM", iv }, key, cipherText),
)
.to.eventually.be.rejectedWith(DOMException, "Decryption failed")
.and.have.property("name", "OperationError");
// missing tag
await expect(
crypto.subtle.decrypt({ name: "AES-GCM", iv }, key, new Uint8Array()),
)
.to.eventually.be.rejectedWith(
DOMException,
"The provided data is too small.",
)
.and.have.property("name", "OperationError");
}
}
async function roundTripSecretJwk(
jwk: JsonWebKey,
algId: AlgorithmIdentifier | HmacImportParams,
ops: KeyUsage[],
validateKeys: (
key: CryptoKey,
originalJwk: JsonWebKey,
exportedJwk: JsonWebKey,
) => void,
) {
const key = await crypto.subtle.importKey("jwk", jwk, algId, true, ops);
assert(key instanceof CryptoKey);
assert.strictEqual(key.type, "secret");
const exportedKey = await crypto.subtle.exportKey("jwk", key);
validateKeys(key, jwk, exportedKey);
}
async function testSecretJwkBase64Url() {
// Test 16bits with "overflow" in 3rd pos of 'quartet', no padding
const keyData = `{
"kty": "oct",
"k": "xxx",
"alg": "HS512",
"key_ops": ["sign", "verify"],
"ext": true
}`;
await roundTripSecretJwk(
JSON.parse(keyData),
{ name: "HMAC", hash: "SHA-512" },
["sign", "verify"],
(key, _orig, exp) => {
assert.strictEqual((key.algorithm as HmacKeyAlgorithm).length, 16);
assert.strictEqual(exp.k, "xxw");
},
);
// HMAC 128bits with base64url characters (-_)
await roundTripSecretJwk(
{
kty: "oct",
k: "HnZXRyDKn-_G5Fx4JWR1YA",
alg: "HS256",
key_ops: ["sign", "verify"],
ext: true,
},
{ name: "HMAC", hash: "SHA-256" },
["sign", "verify"],
(key, orig, exp) => {
assert.strictEqual((key.algorithm as HmacKeyAlgorithm).length, 128);
assert.strictEqual(orig.k, exp.k);
},
);
// HMAC 104bits/(12+1) bytes with base64url characters (-_), padding and overflow in 2rd pos of "quartet"
await roundTripSecretJwk(
{
kty: "oct",
k: "a-_AlFa-2-OmEGa_-z==",
alg: "HS384",
key_ops: ["sign", "verify"],
ext: true,
},
{ name: "HMAC", hash: "SHA-384" },
["sign", "verify"],
(key, _orig, exp) => {
assert.strictEqual((key.algorithm as HmacKeyAlgorithm).length, 104);
assert.strictEqual("a-_AlFa-2-OmEGa_-w", exp.k);
},
);
// AES-CBC 128bits with base64url characters (-_) no padding
await roundTripSecretJwk(
{
kty: "oct",
k: "_u3K_gEjRWf-7cr-ASNFZw",
alg: "A128CBC",
key_ops: ["encrypt", "decrypt"],
ext: true,
},
{ name: "AES-CBC" },
["encrypt", "decrypt"],
(_key, orig, exp) => {
assert.strictEqual(orig.k, exp.k);
},
);
// AES-CBC 128bits of '1' with padding chars
await roundTripSecretJwk(
{
kty: "oct",
k: "_____________________w==",
alg: "A128CBC",
key_ops: ["encrypt", "decrypt"],
ext: true,
},
{ name: "AES-CBC" },
["encrypt", "decrypt"],
(_key, _orig, exp) => {
assert.strictEqual(exp.k, "_____________________w");
},
);
}
// async function testAESWrapKey() {
// const key = await crypto.subtle.generateKey(
// {
// name: "AES-KW",
// length: 128,
// },
// true,
// ["wrapKey", "unwrapKey"],
// );
// const hmacKey = await crypto.subtle.generateKey(
// {
// name: "HMAC",
// hash: "SHA-256",
// length: 128,
// },
// true,
// ["sign"],
// );
// //round-trip
// // wrap-unwrap-export compare
// const wrappedKey = await crypto.subtle.wrapKey(
// "raw",
// hmacKey,
// key,
// {
// name: "AES-KW",
// },
// );
// assert(wrappedKey instanceof ArrayBuffer);
// assertEquals(wrappedKey.byteLength, 16 + 8); // 8 = 'auth tag'
// const unwrappedKey = await crypto.subtle.unwrapKey(
// "raw",
// wrappedKey,
// key,
// {
// name: "AES-KW",
// },
// {
// name: "HMAC",
// hash: "SHA-256",
// },
// true,
// ["sign"],
// );
// assert(unwrappedKey instanceof CryptoKey);
// assertEquals((unwrappedKey.algorithm as HmacKeyAlgorithm).length, 128);
// const hmacKeyBytes = await crypto.subtle.exportKey("raw", hmacKey);
// const unwrappedKeyBytes = await crypto.subtle.exportKey("raw", unwrappedKey);
// assertEquals(new Uint8Array(hmacKeyBytes), new Uint8Array(unwrappedKeyBytes));
// }
// https://github.com/denoland/deno/issues/13534
async function testAesGcmTagLength() {
const key = await crypto.subtle.importKey(
"raw",
new Uint8Array(32),
"AES-GCM",
false,
["encrypt", "decrypt"],
);
const iv = crypto.getRandomValues(new Uint8Array(12));
// We don't support tag lengths other than 128 bits
await expect(
crypto.subtle.encrypt(
{ name: "AES-GCM", iv, tagLength: 96 },
key,
new Uint8Array(32),
),
)
.to.eventually.be.rejectedWith(DOMException, "tag length must be 128 bits")
.and.have.property("name", "NotSupportedError");
await expect(
crypto.subtle.decrypt(
{ name: "AES-GCM", iv, tagLength: 96 },
key,
new Uint8Array(32),
),
)
.to.eventually.be.rejectedWith(DOMException, "tag length must be 128 bits")
.and.have.property("name", "NotSupportedError");
}
async function ecPrivateKeyMaterialExportSpki() {
// `generateKey` generates a key pair internally stored as "private" key.
const keys = await crypto.subtle.generateKey(
{ name: "ECDSA", namedCurve: "P-256" },
true,
["sign", "verify"],
);
assert(keys.privateKey instanceof CryptoKey);
assert(keys.publicKey instanceof CryptoKey);
// `exportKey` should be able to perform necessary conversion to export spki.
const spki = await crypto.subtle.exportKey("spki", keys.publicKey);
assert(spki instanceof ArrayBuffer);
}
// https://github.com/denoland/deno/issues/13911
async function importJwkWithUse() {
const jwk = {
kty: "EC",
use: "sig",
crv: "P-256",
x: "FWZ9rSkLt6Dx9E3pxLybhdM6xgR5obGsj5_pqmnz5J4",
y: "_n8G69C-A2Xl4xUW2lF0i8ZGZnk_KPYrhv4GbTGu5G4",
};
const algorithm = { name: "ECDSA", namedCurve: "P-256" };
const key = await crypto.subtle.importKey("jwk", jwk, algorithm, true, [
"verify",
]);
assert(key instanceof CryptoKey);
}
// https://github.com/denoland/deno/issues/14215
async function exportKeyNotExtractable() {
const key = await crypto.subtle.generateKey(
{
name: "HMAC",
hash: "SHA-512",
},
false,
["sign", "verify"],
);
assert(key);
assert.equal(key.extractable, false);
// Should fail
await expect(crypto.subtle.exportKey("raw", key))
.to.eventually.be.rejectedWith(DOMException)
.and.have.property("name", "InvalidAccessError");
}
// https://github.com/denoland/deno/issues/15126
async function testImportLeadingZeroesKey() {
const alg = { name: "ECDSA", namedCurve: "P-256" };
const jwk = {
kty: "EC",
crv: "P-256",
alg: "ES256",
x: "EvidcdFB1xC6tgfakqZsU9aIURxAJkcX62zHe1Nt6xU",
y: "AHsk6BioGM7MZWeXOE_49AGmtuaXFT3Ill3DYtz9uYg",
d: "WDeYo4o1heCF9l_2VIaClRyIeO16zsMlN8UG6Le9dU8",
key_ops: ["sign"],
ext: true,
};
const key = await crypto.subtle.importKey("jwk", jwk, alg, true, ["sign"]);
assert(key instanceof CryptoKey);
assert.strictEqual(key.type, "private");
}
// https://github.com/denoland/deno/issues/15523
async function testECspkiRoundTrip() {
const alg = { name: "ECDH", namedCurve: "P-256" };
const { publicKey } = await crypto.subtle.generateKey(alg, true, [
"deriveBits",
]);
const spki = await crypto.subtle.exportKey("spki", publicKey);
await crypto.subtle.importKey("spki", spki, alg, true, []);
}
async function testHmacJwkImport() {
await crypto.subtle.importKey(
"jwk",
{
kty: "oct",
use: "sig",
alg: "HS256",
k: "hJtXIZ2uSN5kbQfbtTNWbpdmhkV8FJG-Onbc6mxCcYg",
},
{ name: "HMAC", hash: "SHA-256" },
false,
["sign", "verify"],
);
}
// -----------------------------------------------------------------------------
// End tests from Deno
// -----------------------------------------------------------------------------
async function testHMACSign() {
// This is more or less what the Stripe SDK does
// https://github.com/stripe/stripe-node/blob/68b32428a98f966eee19b54551cb4c3706ca3414/src/crypto/SubtleCryptoProvider.ts#L48C1-L52C7
const subtle = crypto.subtle;
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: { name: "SHA-256" } },
false,
["sign"],
);
const actual = await subtle.sign(
{ name: "HMAC" },
cryptoKey,
new Uint8Array(8),
);
const actualBase64String = new Uint8Array(actual).toBase64();
// This value is from running the above code in a browser
const expected = "SdJ6jecfYHT1LzY/Vh03WauHRbzWZeVjDFL4ietJNCw=";
assert.strictEqual(actualBase64String, expected);
}
async function testHMACVerify() {
// Verify signature from testHMACSign.
const base64Signature = "SdJ6jecfYHT1LzY/Vh03WauHRbzWZeVjDFL4ietJNCw=";
const subtle = crypto.subtle;
const key = new Uint8Array([
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
]);
// Import the key for verification
const cryptoKey = await subtle.importKey(
"raw",
key.buffer,
{ name: "HMAC", hash: { name: "SHA-256" } },
false,
["verify"],
);
const signature = Uint8Array.fromBase64(base64Signature);
const isVerified = await subtle.verify(
{ name: "HMAC" },
cryptoKey,
signature,
new Uint8Array(8),
);
assert.isTrue(isVerified, "signature should be verified");
}
async function testHMACSignAlternativeSyntax() {
const subtle = crypto.subtle;
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,
// This should also work
{ name: "hmac", hash: "sha-256" },
false,
["sign"],
);
// Specifying an extra property should also be fine
const importParams = { name: "hmac", hash: "sha-256", foo: "bar" };
await subtle.importKey("raw", key.buffer, importParams, false, ["sign"]);
const actual = await subtle.sign("hmac", cryptoKey, new Uint8Array(8));
const actualBase64String = new Uint8Array(actual).toBase64();
// This value is from running the above code in a browser
const expected = "SdJ6jecfYHT1LzY/Vh03WauHRbzWZeVjDFL4ietJNCw=";
assert.strictEqual(actualBase64String, expected);
}
async function testInvalidAlgorithm() {
const subtle = crypto.subtle;
const key = new Uint8Array([
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
]);
await expect(
subtle.importKey(
"raw",
key.buffer,
{ name: "foo", hash: "sha-256" },
false,
["sign"],
),
)
.to.eventually.be.rejectedWith(
DOMException,
/Unrecognized or invalid algorithm/,
)
.and.have.property("name", "NotSupportedError");
await expect(subtle.sign({ name: "foo" }, {} as any, new Uint8Array()))
.to.eventually.be.rejectedWith(DOMException, /invalid algorithm for key/)
.and.have.property("name", "InvalidAccessError");
await expect(
subtle.verify(
{ name: "foo" },
{} as any,
new Uint8Array(),
new Uint8Array(),
),
)
.to.eventually.be.rejectedWith(DOMException, /invalid algorithm for key/)
.and.have.property("name", "InvalidAccessError");
await expect(subtle.deriveBits({ name: "foo" }, {} as any, 128))
.to.eventually.be.rejectedWith(DOMException, /invalid algorithm for key/)
.and.have.property("name", "InvalidAccessError");
await expect(subtle.generateKey({ name: "foo" }, false, ["sign"]))
.to.eventually.be.rejectedWith(
DOMException,
/Unrecognized or invalid algorithm/,
)
.and.have.property("name", "NotSupportedError");
}
async function testDeriveBitsPBKDF2() {
const rawKey = crypto.getRandomValues(new Uint8Array(16));
const key = await crypto.subtle.importKey("raw", rawKey, "PBKDF2", false, [
"deriveBits",
]);
const salt = crypto.getRandomValues(new Uint8Array(16));
const alg = {
name: "PBKDF2",
hash: "SHA-256",
salt,
iterations: 1000,
};
const bits = await crypto.subtle.deriveBits(alg, key, 16 * 8);
assert.instanceOf(bits, ArrayBuffer);
assert.strictEqual(bits.byteLength, 16);
// length=0 is allowed
const emptyBits = await crypto.subtle.deriveBits(alg, key, 0);
assert.strictEqual(emptyBits.byteLength, 0);
// iterations=0
await expect(crypto.subtle.deriveBits({ ...alg, iterations: 0 }, key, 8))
.to.eventually.be.rejectedWith(DOMException)
.and.have.property("name", "OperationError");
// length=null
await expect(crypto.subtle.deriveBits(alg, key, null as any))
.to.eventually.be.rejectedWith(DOMException)
.and.have.property("name", "OperationError");
// check interop with a fixed answer from another JS engine
const knownKey = await crypto.subtle.importKey(
"raw",
new Uint8Array([1, 2, 3, 4]),
"PBKDF2",
false,
["deriveBits"],
);
const knownBits = await crypto.subtle.deriveBits(
{
name: "PBKDF2",
hash: "SHA-256",
salt: new Uint8Array([5, 6, 7, 8]),
iterations: 1000,
},
knownKey,
16 * 8,
);
assert.deepEqual(
knownBits,
Uint8Array.fromBase64("H9zMJROqDKzbGvnmhZv13g==").buffer,
);
}
async function testDeriveKeyPBKDF2() {
// Test deriveKey
const rawKey = crypto.getRandomValues(new Uint8Array(16));
const key = await crypto.subtle.importKey("raw", rawKey, "PBKDF2", false, [
"deriveKey",
]);
const salt = 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);
assert.strictEqual(derivedKey.type, "secret");
assert.strictEqual(derivedKey.extractable, true);
assert.deepEqual(derivedKey.usages, ["sign"]);
const algorithm = derivedKey.algorithm as HmacKeyAlgorithm;
assert.strictEqual(algorithm.name, "HMAC");
assert.strictEqual(algorithm.hash.name, "SHA-256");
assert.strictEqual(algorithm.length, 512);
}
async function testDigest() {
const digest = await crypto.subtle.digest(
"SHA-256",
new TextEncoder().encode("hello"),
);
const digestBase64 = new Uint8Array(digest).toBase64();
assert.strictEqual(
digestBase64,
"LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ=",
);
}
async function testEd25519Sign() {
const privateKey = await crypto.subtle.importKey(
"jwk",
{
crv: "Ed25519",
d: "htmE5Wj4_ZI2QGaxNEW8LhdzZnfxdUS1YIEdX2dNIYw",
x: "FC7V7YKGUTrqEax4wutuK8qkwRcQjhP_ahUDDtgYdYM",
kty: "OKP",
},
{ name: "Ed25519" },
true,
["sign"],
);
const message = new Uint8Array([1, 2, 3, 4, 5]);
const signature = await crypto.subtle.sign("Ed25519", privateKey, message);
// N.B.: Ed25519 signature generation is deterministic. This signature was generated by node.
assert.deepEqual(
signature,
new Uint8Array([
80, 12, 43, 3, 76, 224, 170, 78, 6, 161, 124, 191, 55, 146, 206, 86, 168,
205, 130, 220, 5, 182, 255, 55, 203, 252, 149, 71, 124, 105, 105, 42, 56,
144, 12, 237, 132, 132, 39, 209, 187, 236, 138, 37, 137, 146, 24, 246,
149, 73, 154, 220, 123, 29, 62, 65, 57, 229, 141, 193, 31, 21, 182, 10,
]).buffer,
);
const publicKey = await crypto.subtle.importKey(
"jwk",
{
crv: "Ed25519",
x: "FC7V7YKGUTrqEax4wutuK8qkwRcQjhP_ahUDDtgYdYM",
kty: "OKP",
},
{ name: "Ed25519" },
true,
["verify"],
);
assert.isTrue(
await crypto.subtle.verify("Ed25519", publicKey, signature, message),
);
const wrongSignature = new Uint8Array(signature);
wrongSignature[0] = (wrongSignature[0] + 1) & 0xff;
assert.isFalse(
await crypto.subtle.verify("Ed25519", publicKey, wrongSignature, message),
);
}
async function testAesCtrEncrypt() {
const key = await crypto.subtle.importKey(
"raw",
new ArrayBuffer(16),
{ name: "AES-CTR", length: 128 },
false,
["encrypt"],
);
const counter = new Uint8Array(16);
counter.fill(0xff);
expect(
crypto.subtle.encrypt(
{ name: "AES-CTR", counter, length: 0 },
key,
new ArrayBuffer(1),
),
)
.to.eventually.be.rejectedWith(DOMException, "invalid counter length")
.and.have.property("name", "OperationError");
expect(
crypto.subtle.encrypt(
{ name: "AES-CTR", counter, length: 1 },
key,
new ArrayBuffer(33),
),
)
.to.eventually.be.rejectedWith(DOMException, "too much data")
.and.have.property("name", "OperationError");
// At different lengths, different suffixes of the counter overflow
const expected = {
"1": "P1uMyeqFWgr6c0fSPo1mTlwAXnLBQYxE9Wny6jO6VPM=",
"2": "P1uMyeqFWgr6c0fSPo1mTjm95n1cjtioscN+uPqfWsA=",
"3": "P1uMyeqFWgr6c0fSPo1mToreiVkTaFxnxSafiq5CmD4=",
"4": "P1uMyeqFWgr6c0fSPo1mTvmw/aDEqJj1ueb2YcTOTQc=",
"32": "P1uMyeqFWgr6c0fSPo1mThI8H0rzE62MLOZIsucftuE=",
"64": "P1uMyeqFWgr6c0fSPo1mTvgHw+eYX+D1pQ4s2yXFEJ4=",
"127": "P1uMyeqFWgr6c0fSPo1mTjrXjnJsHsArfr/pKyPZ7DQ=",
"128": "P1uMyeqFWgr6c0fSPo1mTmbpS9Tviiw7iEz6Wco0Ky4=",
};
for (const [length, expectedBase64] of Object.entries(expected)) {
assert.deepEqual(
new Uint8Array(
await crypto.subtle.encrypt(
{ name: "AES-CTR", counter, length: parseInt(length) },
key,
new ArrayBuffer(32),
),
),
Uint8Array.fromBase64(expectedBase64),
);
}
}
async function testEd25519GenerateKey() {
const { privateKey, publicKey } = (await crypto.subtle.generateKey(
"Ed25519",
true,
["sign", "verify"],
)) as CryptoKeyPair; // our typescript definitions are missing an overload for Ed25519
const message = new Uint8Array([1, 2, 3, 4, 5]);
const signature = new Uint8Array(
await crypto.subtle.sign("Ed25519", privateKey, message),
);
assert.isTrue(
await crypto.subtle.verify("Ed25519", publicKey, signature, message),
"generated public key should verify signature",
);
const wrongSignature = new Uint8Array(signature);
wrongSignature[0] = (wrongSignature[0] + 1) & 0xff;
assert.isFalse(
await crypto.subtle.verify("Ed25519", publicKey, wrongSignature, message),
"generated public key should reject wrong signature",
);
const exportedPrivate = await crypto.subtle.exportKey("jwk", privateKey);
const exportedPublic = await crypto.subtle.exportKey("jwk", publicKey);
const importedPrivate = await crypto.subtle.importKey(
"jwk",
exportedPrivate,
"Ed25519",
false,
["sign"],
);
const importedPublic = await crypto.subtle.importKey(
"jwk",
exportedPublic,
"Ed25519",
false,
["verify"],
);
assert.deepEqual(
new Uint8Array(
await crypto.subtle.sign("Ed25519", importedPrivate, message),
),
signature,
"reimported private key generates the same signature",
);
assert.isTrue(
await crypto.subtle.verify("Ed25519", importedPublic, signature, message),
"reimported public key should still verify correct signature",
);
assert.isFalse(
await crypto.subtle.verify(
"Ed25519",
importedPublic,
wrongSignature,
message,
),
"reimported public key should still reject wrong signature",
);
}
async function testCryptoKey() {
assert.strictEqual(
(CryptoKey.prototype as any)[Symbol.toStringTag],
"CryptoKey",
);
const key = await crypto.subtle.importKey(
"raw",
new ArrayBuffer(32),
"Ed25519",
true,
["verify"],
);
assert.strictEqual(Object.prototype.toString.call(key), "[object CryptoKey]");
// N.B.: we do not implement CryptoKey's properties as getters, so CryptoKey.prototype does not have them.
// TODO: add a test for what a CryptoKey looks like when `console.log`ged.
}
export const methodNotImplemented = query({
args: {},
handler: async () => {
await crypto.subtle.wrapKey(
{} as any,
null as any,
null as any,
"RSA-OAEP",
);
},
});
export const test = query({
args: {},
handler: async () => {
return await wrapInTests({
getRandomValuesInt8Array,
getRandomValuesUint8Array,
getRandomValuesUint8ClampedArray,
getRandomValuesInt16Array,
getRandomValuesUint16Array,
getRandomValuesInt32Array,
getRandomValuesBigInt64Array,
getRandomValuesUint32Array,
getRandomValuesBigUint64Array,
getRandomValuesReturnValue,
getRandomValuesFloatArray,
randomUUID,
testImportArrayBufferKey,
subtleCryptoHmacImportExport,
importRsaPkcs8,
importRsaSpki,
importRsaPKCS8Regression,
importNonInteroperableRsaPkcs8,
testImportRsaJwk,
testImportEcDhJwk,
testAesGcmEncrypt,
testSecretJwkBase64Url,
// testAESWrapKey,
testAesGcmTagLength,
importJwkWithUse,
testImportLeadingZeroesKey,
testHmacJwkImport,
// End of Deno tests.
testHMACSign,
testHMACVerify,
testHMACSignAlternativeSyntax,
testInvalidAlgorithm,
testDeriveBitsPBKDF2,
testDeriveKeyPBKDF2,
testDigest,
testEd25519Sign,
testAesCtrEncrypt,
testCryptoKey,
});
},
});
export const testAction = action({
args: {},
handler: async () => {
return await wrapInTests({
// Deno tests (that require randomness)
testSignVerify,
testOaepEncryptDecrypt,
testImportExportEcDsaJwk,
testImportEcSpkiPkcs8,
testGenerateRSAKey,
testGenerateHMACKey,
testECDSASignVerify,
testECDSASignVerifyFail,
testSignRSASSAKey,
exportKeyNotExtractable,
ecPrivateKeyMaterialExportSpki,
testECspkiRoundTrip,
// end of Deno tests
testEd25519GenerateKey,
testX25519GenerateKey,
});
},
});
export const consumeInteropData = query({
handler: async (_ctx, { json }: { json: string }) => {
await consumeData(json);
},
});
export const generateInteropData = action({
handler: async () => {
return await generateData();
},
});