Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ext/crypto) - exportKey(pkcs8/spki/jwk) for ECDSA and ECDH #13104

Merged
merged 21 commits into from
Jan 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -914,6 +914,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 @@ -3316,6 +3320,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") {
cryptographix marked this conversation as resolved.
Show resolved Hide resolved
// 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