Skip to content

Commit

Permalink
Set Ed25519 CryptoKey#algorithm.name to NODE-ED25519
Browse files Browse the repository at this point in the history
The `Ed25519` algorithm was leaking to user code via
`CryptoKey#algorithm.name`, but Cloudflare Workers only support the
`NODE-ED25519` algorithm. This change plugs the leak, by overriding
`algorithm.name`, and then using `Proxy`s before passing `CryptoKey`s
back to Node.

Closes panva/jose#446.
  • Loading branch information
mrbbot committed Sep 3, 2022
1 parent 5ab7cb3 commit b3e187d
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 8 deletions.
58 changes: 50 additions & 8 deletions packages/core/src/standards/crypto.ts
Expand Up @@ -63,7 +63,7 @@ const usesModernEd25519 = (async () => {
}
})();

async function ensureValidAlgorithm(
async function ensureValidNodeAlgorithm(
algorithm: webcrypto.AlgorithmIdentifier | webcrypto.EcKeyAlgorithm
): Promise<webcrypto.AlgorithmIdentifier | webcrypto.EcKeyAlgorithm> {
if (
Expand All @@ -78,6 +78,27 @@ async function ensureValidAlgorithm(
return algorithm;
}

function ensureValidWorkerKey(key: webcrypto.CryptoKey): webcrypto.CryptoKey {

This comment has been minimized.

Copy link
@panva

panva Apr 30, 2023

@mrbbot since both workerd and the live service now have Ed25519 the jose library will not fallback to NODE-ED25519 during generation or imports anymore.

This can/should be removed.

// Users' workers will expect to see the `NODE-ED25519` algorithm, even if
// we're using "Ed25519" internally (https://github.com/panva/jose/issues/446)
if (key.algorithm.name === "Ed25519") key.algorithm.name = "NODE-ED25519";
return key;
}

async function ensureValidNodeKey(
key: webcrypto.CryptoKey
): Promise<webcrypto.CryptoKey> {
if (key.algorithm.name === "NODE-ED25519" && (await usesModernEd25519)) {
return new Proxy(key, {
get(target, property, receiver) {
if (property === "algorithm") return { name: "Ed25519" };
return Reflect.get(target, property, receiver);
},
});
}
return key;
}

// Workers support non-standard MD5 digests, see
// https://developers.cloudflare.com/workers/runtime-apis/web-crypto/#supported-algorithms
const digest: typeof webcrypto.subtle.digest = function (algorithm, data) {
Expand All @@ -99,9 +120,18 @@ const generateKey: typeof webcrypto.subtle.generateKey = async function (
extractable,
keyUsages
) {
algorithm = await ensureValidAlgorithm(algorithm);
// @ts-expect-error TypeScript cannot infer the correct overload here
return webcrypto.subtle.generateKey(algorithm, extractable, keyUsages);
algorithm = await ensureValidNodeAlgorithm(algorithm);
const key: webcrypto.CryptoKey | webcrypto.CryptoKeyPair =
// @ts-expect-error TypeScript cannot infer the correct overload here
await webcrypto.subtle.generateKey(algorithm, extractable, keyUsages);
// noinspection SuspiciousTypeOfGuard
if (key instanceof webcrypto.CryptoKey) {
return ensureValidWorkerKey(key);
} else {
key.publicKey = ensureValidWorkerKey(key.publicKey);
key.privateKey = ensureValidWorkerKey(key.privateKey);
return key as any;
}
};
const importKey: typeof webcrypto.subtle.importKey = async function (
format,
Expand All @@ -119,27 +149,37 @@ const importKey: typeof webcrypto.subtle.importKey = async function (
"namedCurve" in algorithm &&
algorithm.namedCurve === "NODE-ED25519";

algorithm = await ensureValidAlgorithm(algorithm);
algorithm = await ensureValidNodeAlgorithm(algorithm);

// @ts-expect-error `public` isn't included in the definitions, but required
// for marking `keyData` as public key material
if (forcePublic) algorithm.public = true;

return webcrypto.subtle.importKey(
const key = await webcrypto.subtle.importKey(
// @ts-expect-error TypeScript cannot infer the correct overload here
format,
keyData,
algorithm,
extractable,
keyUsages
);
return ensureValidWorkerKey(key);
};
const exportKey: typeof webcrypto.subtle.exportKey = async function (
format,
key
) {
key = await ensureValidNodeKey(key);
// @ts-expect-error TypeScript cannot infer the correct overload here
return webcrypto.subtle.exportKey(format, key);
};
const sign: typeof webcrypto.subtle.sign = async function (
algorithm,
key,
data
) {
algorithm = await ensureValidAlgorithm(algorithm);
algorithm = await ensureValidNodeAlgorithm(algorithm);
key = await ensureValidNodeKey(key);
return webcrypto.subtle.sign(algorithm, key, data);
};
const verify: typeof webcrypto.subtle.verify = async function (
Expand All @@ -148,7 +188,8 @@ const verify: typeof webcrypto.subtle.verify = async function (
signature,
data
) {
algorithm = await ensureValidAlgorithm(algorithm);
algorithm = await ensureValidNodeAlgorithm(algorithm);
key = await ensureValidNodeKey(key);
return webcrypto.subtle.verify(algorithm, key, signature, data);
};

Expand All @@ -168,6 +209,7 @@ export function createCrypto(blockGlobalRandom = false): WorkerCrypto {
if (propertyKey === "digest") return digest;
if (propertyKey === "generateKey") return assertingGenerateKey;
if (propertyKey === "importKey") return importKey;
if (propertyKey === "exportKey") return exportKey;
if (propertyKey === "sign") return sign;
if (propertyKey === "verify") return verify;

Expand Down
5 changes: 5 additions & 0 deletions packages/core/test/standards/crypto.spec.ts
Expand Up @@ -100,6 +100,8 @@ test("crypto: generateKey/exportKey: supports NODE-ED25519 algorithm", async (t)
true,
["sign", "verify"]
);
t.is(keyPair.publicKey.algorithm.name, "NODE-ED25519");
t.is(keyPair.privateKey.algorithm.name, "NODE-ED25519");
const exported = await crypto.subtle.exportKey("raw", keyPair.publicKey);
t.is(exported.byteLength, 32);
});
Expand All @@ -109,6 +111,7 @@ test("crypto: generateKey/exportKey: supports other algorithms", async (t) => {
true,
["encrypt", "decrypt"]
);
t.is(key.algorithm.name, "AES-GCM");
const exported = await crypto.subtle.exportKey("raw", key);
t.is(exported.byteLength, 32);
});
Expand All @@ -123,6 +126,7 @@ test("crypto: importKey/exportKey: supports NODE-ED25519 public keys", async (t)
true,
["verify"]
);
t.is(publicKey.algorithm.name, "NODE-ED25519");
const exported = await crypto.subtle.exportKey("raw", publicKey);
t.is(Buffer.from(exported).toString("hex"), keyData);
});
Expand All @@ -149,6 +153,7 @@ test("crypto: importKey/exportKey: supports other algorithms", async (t) => {
true,
["encrypt", "decrypt"]
);
t.is(key.algorithm.name, "AES-GCM");
const exported = await crypto.subtle.exportKey("raw", key);
t.is(Buffer.from(exported).toString("hex"), keyData);
});
Expand Down

0 comments on commit b3e187d

Please sign in to comment.