summaryrefslogtreecommitdiff
path: root/ext/crypto
diff options
context:
space:
mode:
Diffstat (limited to 'ext/crypto')
-rw-r--r--ext/crypto/00_crypto.js240
-rw-r--r--ext/crypto/01_webidl.js118
-rw-r--r--ext/crypto/lib.deno_crypto.d.ts35
3 files changed, 348 insertions, 45 deletions
diff --git a/ext/crypto/00_crypto.js b/ext/crypto/00_crypto.js
index 5c80ac0ca..375170ad9 100644
--- a/ext/crypto/00_crypto.js
+++ b/ext/crypto/00_crypto.js
@@ -12,13 +12,18 @@
const core = window.Deno.core;
const webidl = window.__bootstrap.webidl;
const { DOMException } = window.__bootstrap.domException;
+ const { atob } = window.__bootstrap.base64;
const {
ArrayPrototypeFind,
- ArrayBufferIsView,
+ ArrayPrototypeEvery,
ArrayPrototypeIncludes,
+ ArrayBuffer,
+ ArrayBufferIsView,
BigInt64Array,
StringPrototypeToUpperCase,
+ StringPrototypeReplace,
+ StringPrototypeCharCodeAt,
Symbol,
SymbolFor,
SymbolToStringTag,
@@ -100,6 +105,23 @@
},
};
+ // Decodes the unpadded base64 to the octet sequence containing key value `k` defined in RFC7518 Section 6.4
+ function decodeSymmetricKey(key) {
+ // Decode from base64url without `=` padding.
+ const base64 = StringPrototypeReplace(
+ StringPrototypeReplace(key, /\-/g, "+"),
+ /\_/g,
+ "/",
+ );
+ const decodedKey = atob(base64);
+ const keyLength = decodedKey.length;
+ const keyBytes = new Uint8Array(keyLength);
+ for (let i = 0; i < keyLength; i++) {
+ keyBytes[i] = StringPrototypeCharCodeAt(decodedKey, i);
+ }
+ return keyBytes;
+ }
+
// See https://www.w3.org/TR/WebCryptoAPI/#dfn-normalize-an-algorithm
// 18.4.4
function normalizeAlgorithm(algorithm, op) {
@@ -631,7 +653,7 @@
prefix,
context: "Argument 1",
});
- keyData = webidl.converters.BufferSource(keyData, {
+ keyData = webidl.converters["BufferSource or JsonWebKey"](keyData, {
prefix,
context: "Argument 2",
});
@@ -649,22 +671,32 @@
});
// 2.
- if (ArrayBufferIsView(keyData)) {
- keyData = new Uint8Array(
- keyData.buffer,
- keyData.byteOffset,
- keyData.byteLength,
- );
+ if (format !== "jwk") {
+ if (ArrayBufferIsView(keyData) || keyData instanceof ArrayBuffer) {
+ if (ArrayBufferIsView(keyData)) {
+ keyData = new Uint8Array(
+ keyData.buffer,
+ keyData.byteOffset,
+ keyData.byteLength,
+ );
+ } else {
+ keyData = new Uint8Array(keyData);
+ }
+ keyData = TypedArrayPrototypeSlice(keyData);
+ } else {
+ throw new TypeError("keyData is a JsonWebKey");
+ }
} else {
- keyData = new Uint8Array(keyData);
+ if (ArrayBufferIsView(keyData) || keyData instanceof ArrayBuffer) {
+ throw new TypeError("keyData is not a JsonWebKey");
+ }
}
- keyData = TypedArrayPrototypeSlice(keyData);
const normalizedAlgorithm = normalizeAlgorithm(algorithm, "importKey");
switch (normalizedAlgorithm.name) {
- // https://w3c.github.io/webcrypto/#hmac-operations
case "HMAC": {
+ // 2.
if (
ArrayPrototypeFind(
keyUsages,
@@ -674,59 +706,177 @@
throw new DOMException("Invalid key usages", "SyntaxError");
}
+ // 3.
+ let hash;
+ let data;
+
+ // 4. https://w3c.github.io/webcrypto/#hmac-operations
switch (format) {
case "raw": {
- const hash = normalizedAlgorithm.hash;
+ data = keyData;
+ hash = normalizedAlgorithm.hash;
+ break;
+ }
+ case "jwk": {
+ // TODO(@littledivy): Why does the spec validate JWK twice?
+ const jwk = keyData;
+ // 2.
+ if (jwk.kty !== "oct") {
+ throw new DOMException(
+ "`kty` member of JsonWebKey must be `oct`",
+ "DataError",
+ );
+ }
+
+ // Section 6.4.1 of RFC7518
+ if (!jwk.k) {
+ throw new DOMException(
+ "`k` member of JsonWebKey must be present",
+ "DataError",
+ );
+ }
+
+ // 4.
+ data = decodeSymmetricKey(jwk.k);
// 5.
- let length = keyData.byteLength * 8;
+ hash = normalizedAlgorithm.hash;
// 6.
- if (length === 0) {
- throw new DOMException("Key length is zero", "DataError");
+ switch (hash.name) {
+ case "SHA-1": {
+ if (jwk.alg !== undefined && jwk.alg !== "HS1") {
+ throw new DOMException(
+ "`alg` member of JsonWebKey must be `HS1`",
+ "DataError",
+ );
+ }
+ break;
+ }
+ case "SHA-256": {
+ if (jwk.alg !== undefined && jwk.alg !== "HS256") {
+ throw new DOMException(
+ "`alg` member of JsonWebKey must be `HS256`",
+ "DataError",
+ );
+ }
+ break;
+ }
+ case "SHA-384": {
+ if (jwk.alg !== undefined && jwk.alg !== "HS384") {
+ throw new DOMException(
+ "`alg` member of JsonWebKey must be `HS384`",
+ "DataError",
+ );
+ }
+ break;
+ }
+ case "SHA-512": {
+ if (jwk.alg !== undefined && jwk.alg !== "HS512") {
+ throw new DOMException(
+ "`alg` member of JsonWebKey must be `HS512`",
+ "DataError",
+ );
+ }
+ break;
+ }
+ default:
+ throw new TypeError("unreachable");
}
- if (normalizeAlgorithm.length) {
- // 7.
+
+ // 7.
+ if (keyUsages.length > 0 && jwk.use && jwk.use !== "sign") {
+ throw new DOMException(
+ "`use` member of JsonWebKey must be `sign`",
+ "DataError",
+ );
+ }
+
+ // 8.
+ // Section 4.3 of RFC7517
+ if (jwk.key_ops) {
if (
- normalizedAlgorithm.length > length ||
- normalizedAlgorithm.length <= (length - 8)
+ ArrayPrototypeFind(
+ jwk.key_ops,
+ (u) => !ArrayPrototypeIncludes(recognisedUsages, u),
+ ) !== undefined
) {
throw new DOMException(
- "Key length is invalid",
+ "`key_ops` member of JsonWebKey is invalid",
"DataError",
);
}
- length = normalizeAlgorithm.length;
- }
- if (keyUsages.length == 0) {
- throw new DOMException("Key usage is empty", "SyntaxError");
+ if (
+ !ArrayPrototypeEvery(
+ jwk.key_ops,
+ (u) => ArrayPrototypeIncludes(keyUsages, u),
+ )
+ ) {
+ throw new DOMException(
+ "`key_ops` member of JsonWebKey is invalid",
+ "DataError",
+ );
+ }
}
- const handle = {};
- WeakMapPrototypeSet(KEY_STORE, handle, {
- type: "raw",
- data: keyData,
- });
-
- const algorithm = {
- name: "HMAC",
- length,
- hash,
- };
-
- const key = constructKey(
- "secret",
- extractable,
- usageIntersection(keyUsages, recognisedUsages),
- algorithm,
- handle,
- );
+ // 9.
+ if (jwk.ext === false && extractable == true) {
+ throw new DOMException(
+ "`ext` member of JsonWebKey is invalid",
+ "DataError",
+ );
+ }
- return key;
+ break;
}
- // TODO(@littledivy): jwk
default:
throw new DOMException("Not implemented", "NotSupportedError");
}
+
+ // 5.
+ let length = data.byteLength * 8;
+ // 6.
+ if (length === 0) {
+ throw new DOMException("Key length is zero", "DataError");
+ }
+ // 7.
+ if (normalizedAlgorithm.length !== undefined) {
+ if (
+ normalizedAlgorithm.length > length ||
+ normalizedAlgorithm.length <= (length - 8)
+ ) {
+ throw new DOMException(
+ "Key length is invalid",
+ "DataError",
+ );
+ }
+ length = normalizedAlgorithm.length;
+ }
+
+ if (keyUsages.length == 0) {
+ throw new DOMException("Key usage is empty", "SyntaxError");
+ }
+
+ const handle = {};
+ WeakMapPrototypeSet(KEY_STORE, handle, {
+ type: "raw",
+ data,
+ });
+
+ const algorithm = {
+ name: "HMAC",
+ length,
+ hash,
+ };
+
+ const key = constructKey(
+ "secret",
+ extractable,
+ usageIntersection(keyUsages, recognisedUsages),
+ algorithm,
+ handle,
+ );
+
+ return key;
}
// TODO(@littledivy): RSASSA-PKCS1-v1_5
// TODO(@littledivy): RSA-PSS
diff --git a/ext/crypto/01_webidl.js b/ext/crypto/01_webidl.js
index 43bc5e822..e781e4334 100644
--- a/ext/crypto/01_webidl.js
+++ b/ext/crypto/01_webidl.js
@@ -9,6 +9,7 @@
((window) => {
const webidl = window.__bootstrap.webidl;
const { CryptoKey } = window.__bootstrap.crypto;
+ const { ArrayBufferIsView, ArrayBuffer } = window.__bootstrap.primordials;
webidl.converters.AlgorithmIdentifier = (V, opts) => {
// Union for (object or DOMString)
@@ -18,6 +19,14 @@
return webidl.converters.DOMString(V, opts);
};
+ webidl.converters["BufferSource or JsonWebKey"] = (V, opts) => {
+ // Union for (BufferSource or JsonWebKey)
+ if (ArrayBufferIsView(V) || V instanceof ArrayBuffer) {
+ return webidl.converters.BufferSource(V, opts);
+ }
+ return webidl.converters.JsonWebKey(V, opts);
+ };
+
webidl.converters.KeyType = webidl.createEnumConverter("KeyType", [
"public",
"private",
@@ -178,6 +187,115 @@
webidl.converters.HmacImportParams = webidl
.createDictionaryConverter("HmacImportParams", dictHmacImportParams);
+ const dictRsaOtherPrimesInfo = [
+ {
+ key: "r",
+ converter: webidl.converters["DOMString"],
+ },
+ {
+ key: "d",
+ converter: webidl.converters["DOMString"],
+ },
+ {
+ key: "t",
+ converter: webidl.converters["DOMString"],
+ },
+ ];
+
+ webidl.converters.RsaOtherPrimesInfo = webidl.createDictionaryConverter(
+ "RsaOtherPrimesInfo",
+ dictRsaOtherPrimesInfo,
+ );
+ webidl.converters["sequence<RsaOtherPrimesInfo>"] = webidl
+ .createSequenceConverter(
+ webidl.converters.RsaOtherPrimesInfo,
+ );
+
+ const dictJsonWebKey = [
+ // Sections 4.2 and 4.3 of RFC7517.
+ // https://datatracker.ietf.org/doc/html/rfc7517#section-4
+ {
+ key: "kty",
+ converter: webidl.converters["DOMString"],
+ },
+ {
+ key: "use",
+ converter: webidl.converters["DOMString"],
+ },
+ {
+ key: "key_ops",
+ converter: webidl.converters["sequence<DOMString>"],
+ },
+ {
+ key: "alg",
+ converter: webidl.converters["DOMString"],
+ },
+ // JSON Web Key Parameters Registration
+ {
+ key: "ext",
+ converter: webidl.converters["boolean"],
+ },
+ // Section 6 of RFC7518 JSON Web Algorithms
+ // https://datatracker.ietf.org/doc/html/rfc7518#section-6
+ {
+ key: "crv",
+ converter: webidl.converters["DOMString"],
+ },
+ {
+ key: "x",
+ converter: webidl.converters["DOMString"],
+ },
+ {
+ key: "y",
+ converter: webidl.converters["DOMString"],
+ },
+ {
+ key: "d",
+ converter: webidl.converters["DOMString"],
+ },
+ {
+ key: "n",
+ converter: webidl.converters["DOMString"],
+ },
+ {
+ key: "e",
+ converter: webidl.converters["DOMString"],
+ },
+ {
+ key: "p",
+ converter: webidl.converters["DOMString"],
+ },
+ {
+ key: "q",
+ converter: webidl.converters["DOMString"],
+ },
+ {
+ key: "dp",
+ converter: webidl.converters["DOMString"],
+ },
+ {
+ key: "dq",
+ converter: webidl.converters["DOMString"],
+ },
+ {
+ key: "qi",
+ converter: webidl.converters["DOMString"],
+ },
+ {
+ key: "oth",
+ converter: webidl.converters["sequence<RsaOtherPrimesInfo>"],
+ },
+ {
+ key: "k",
+ converter: webidl.converters["DOMString"],
+ },
+ ];
+
+ webidl.converters.JsonWebKey = webidl.createDictionaryConverter(
+ "JsonWebKey",
+ dictJsonWebKey,
+ );
+
const dictPbkdf2Params = [
...dictAlgorithm,
{
diff --git a/ext/crypto/lib.deno_crypto.d.ts b/ext/crypto/lib.deno_crypto.d.ts
index 6b1901611..2e8d2f8b2 100644
--- a/ext/crypto/lib.deno_crypto.d.ts
+++ b/ext/crypto/lib.deno_crypto.d.ts
@@ -28,6 +28,34 @@ type KeyUsage =
type NamedCurve = string;
+interface RsaOtherPrimesInfo {
+ d?: string;
+ r?: string;
+ t?: string;
+}
+
+interface JsonWebKey {
+ alg?: string;
+ crv?: string;
+ d?: string;
+ dp?: string;
+ dq?: string;
+ e?: string;
+ ext?: boolean;
+ k?: string;
+ // deno-lint-ignore camelcase
+ key_ops?: string[];
+ kty?: string;
+ n?: string;
+ oth?: RsaOtherPrimesInfo[];
+ p?: string;
+ q?: string;
+ qi?: string;
+ use?: string;
+ x?: string;
+ y?: string;
+}
+
interface HmacKeyGenParams extends Algorithm {
hash: HashAlgorithmIdentifier;
length?: number;
@@ -123,6 +151,13 @@ interface SubtleCrypto {
keyUsages: KeyUsage[],
): Promise<CryptoKeyPair | CryptoKey>;
importKey(
+ format: "jwk",
+ keyData: JsonWebKey,
+ algorithm: AlgorithmIdentifier | HmacImportParams,
+ extractable: boolean,
+ keyUsages: KeyUsage[],
+ ): Promise<CryptoKey>;
+ importKey(
format: "raw",
keyData: BufferSource,
algorithm: AlgorithmIdentifier | HmacImportParams,