Skip to content

Commit

Permalink
feat(ext/crypto): implement pkcs8/spki/jwk exportKey for ECDSA and EC…
Browse files Browse the repository at this point in the history
…DH (#13104)
  • Loading branch information
cryptographix authored Jan 19, 2022
1 parent b3545dd commit 77e58fe
Show file tree
Hide file tree
Showing 7 changed files with 554 additions and 78 deletions.
63 changes: 34 additions & 29 deletions cli/tests/unit/webcrypto_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1176,7 +1176,7 @@ const jwtECKeys = {

type JWK = Record<string, string>;

function _equalJwk(expected: JWK, got: JWK): boolean {
function equalJwk(expected: JWK, got: JWK): boolean {
const fields = Object.keys(expected);

for (let i = 0; i < fields.length; i++) {
Expand Down Expand Up @@ -1218,11 +1218,11 @@ Deno.test(async function testImportExportEcDsaJwk() {
true,
["sign"],
);
/*const expPrivateKeyJWK = await subtle.exportKey(
const expPrivateKeyJWK = await subtle.exportKey(
"jwk",
privateKeyECDSA,
);
assert(equalJwk(privateJWK, expPrivateKeyJWK as JWK));*/
assert(equalJwk(privateJWK, expPrivateKeyJWK as JWK));

const publicKeyECDSA = await subtle.importKey(
"jwk",
Expand All @@ -1237,12 +1237,12 @@ Deno.test(async function testImportExportEcDsaJwk() {
["verify"],
);

/*const expPublicKeyJWK = await subtle.exportKey(
const expPublicKeyJWK = await subtle.exportKey(
"jwk",
publicKeyECDSA,
);

assert(equalJwk(publicJWK, expPublicKeyJWK as JWK));*/
assert(equalJwk(publicJWK, expPublicKeyJWK as JWK));

const signatureECDSA = await subtle.sign(
{ name: "ECDSA", hash: "SHA-256" },
Expand Down Expand Up @@ -1285,11 +1285,11 @@ Deno.test(async function testImportEcDhJwk() {
["deriveBits"],
);

/* const expPrivateKeyJWK = await subtle.exportKey(
const expPrivateKeyJWK = await subtle.exportKey(
"jwk",
privateKeyECDH,
);
assert(equalJwk(privateJWK, expPrivateKeyJWK as JWK));*/
assert(equalJwk(privateJWK, expPrivateKeyJWK as JWK));

const publicKeyECDH = await subtle.importKey(
"jwk",
Expand All @@ -1302,11 +1302,11 @@ Deno.test(async function testImportEcDhJwk() {
true,
[],
);
/* const expPublicKeyJWK = await subtle.exportKey(
const expPublicKeyJWK = await subtle.exportKey(
"jwk",
publicKeyECDH,
);
assert(equalJwk(publicJWK, expPublicKeyJWK as JWK));*/
assert(equalJwk(publicJWK, expPublicKeyJWK as JWK));

const derivedKey = await subtle.deriveBits(
{
Expand Down Expand Up @@ -1357,10 +1357,7 @@ Deno.test(async function testImportEcSpkiPkcs8() {
for (
const [_key, keyData] of Object.entries(ecTestKeys)
) {
const { size, namedCurve, spki, pkcs8 } = keyData;
if (size != 256) {
continue;
}
const { namedCurve, spki, pkcs8 } = keyData;

const privateKeyECDSA = await subtle.importKey(
"pkcs8",
Expand All @@ -1370,12 +1367,19 @@ Deno.test(async function testImportEcSpkiPkcs8() {
["sign"],
);

/*const expPrivateKeyPKCS8 = await subtle.exportKey(
const expPrivateKeyPKCS8 = await subtle.exportKey(
"pkcs8",
privateKeyECDSA,
);

assertEquals(new Uint8Array(expPrivateKeyPKCS8), pkcs8);*/
assertEquals(new Uint8Array(expPrivateKeyPKCS8), pkcs8);

const expPrivateKeyJWK = await subtle.exportKey(
"jwk",
privateKeyECDSA,
);

assertEquals(expPrivateKeyJWK.crv, namedCurve);

const publicKeyECDSA = await subtle.importKey(
"spki",
Expand All @@ -1385,8 +1389,22 @@ Deno.test(async function testImportEcSpkiPkcs8() {
["verify"],
);

const expPublicKeySPKI = await subtle.exportKey(
"spki",
publicKeyECDSA,
);

assertEquals(new Uint8Array(expPublicKeySPKI), spki);

const expPublicKeyJWK = await subtle.exportKey(
"jwk",
publicKeyECDSA,
);

assertEquals(expPublicKeyJWK.crv, namedCurve);

for (
const hash of [/*"SHA-1", */ "SHA-256" /*"SHA-384", "SHA-512"*/]
const hash of [/*"SHA-1", */ "SHA-256", "SHA-384" /*"SHA-512"*/]
) {
const signatureECDSA = await subtle.sign(
{ name: "ECDSA", hash },
Expand All @@ -1402,19 +1420,6 @@ Deno.test(async function testImportEcSpkiPkcs8() {
);
assert(verifyECDSA);
}

/*const expPublicKeySPKI = await subtle.exportKey(
"spki",
publicKeyECDSA,
);
assertEquals(new Uint8Array(expPublicKeySPKI), spki);
/*const expPrivateKeySPKI = await subtle.exportKey(
"spki",
privateKeyECDSA,
);
assertEquals(new Uint8Array(expPrivateKeySPKI), spki);*/
}
});

Expand Down
126 changes: 126 additions & 0 deletions ext/crypto/00_crypto.js
Original file line number Diff line number Diff line change
Expand Up @@ -975,6 +975,10 @@
case "RSA-OAEP": {
return exportKeyRSA(format, key, innerKey);
}
case "ECDH":
case "ECDSA": {
return exportKeyEC(format, key, innerKey);
}
case "AES-CTR":
case "AES-CBC":
case "AES-GCM":
Expand Down Expand Up @@ -3377,6 +3381,128 @@
}
}

function exportKeyEC(format, key, innerKey) {
switch (format) {
case "pkcs8": {
// 1.
if (key[_type] !== "private") {
throw new DOMException(
"Key is not a private key",
"InvalidAccessError",
);
}

// 2.
const data = core.opSync("op_crypto_export_key", {
algorithm: key[_algorithm].name,
namedCurve: key[_algorithm].namedCurve,
format: "pkcs8",
}, innerKey);

return data.buffer;
}
case "spki": {
// 1.
if (key[_type] !== "public") {
throw new DOMException(
"Key is not a public key",
"InvalidAccessError",
);
}

// 2.
const data = core.opSync("op_crypto_export_key", {
algorithm: key[_algorithm].name,
namedCurve: key[_algorithm].namedCurve,
format: "spki",
}, innerKey);

return data.buffer;
}
case "jwk": {
if (key[_algorithm].name == "ECDSA") {
// 1-2.
const jwk = {
kty: "EC",
};

// 3.1
jwk.crv = key[_algorithm].namedCurve;

// Missing from spec
let algNamedCurve;

switch (key[_algorithm].namedCurve) {
case "P-256": {
algNamedCurve = "ES256";
break;
}
case "P-384": {
algNamedCurve = "ES384";
break;
}
case "P-521": {
algNamedCurve = "ES512";
break;
}
default:
throw new DOMException(
"Curve algorithm not supported",
"DataError",
);
}

jwk.alg = algNamedCurve;

// 3.2 - 3.4.
const data = core.opSync("op_crypto_export_key", {
format: key[_type] === "private" ? "jwkprivate" : "jwkpublic",
algorithm: key[_algorithm].name,
namedCurve: key[_algorithm].namedCurve,
}, innerKey);
ObjectAssign(jwk, data);

// 4.
jwk.key_ops = key.usages;

// 5.
jwk.ext = key[_extractable];

return jwk;
} else { // ECDH
// 1-2.
const jwk = {
kty: "EC",
};

// missing step from spec
jwk.alg = "ECDH";

// 3.1
jwk.crv = key[_algorithm].namedCurve;

// 3.2 - 3.4
const data = core.opSync("op_crypto_export_key", {
format: key[_type] === "private" ? "jwkprivate" : "jwkpublic",
algorithm: key[_algorithm].name,
namedCurve: key[_algorithm].namedCurve,
}, innerKey);
ObjectAssign(jwk, data);

// 4.
jwk.key_ops = key.usages;

// 5.
jwk.ext = key[_extractable];

return jwk;
}
}
default:
throw new DOMException("Not implemented", "NotSupportedError");
}
}

async function generateKeyAES(normalizedAlgorithm, extractable, usages) {
const algorithmName = normalizedAlgorithm.name;

Expand Down
Loading

0 comments on commit 77e58fe

Please sign in to comment.