Skip to content

Commit

Permalink
Merge pull request #183 from dajiaji/add-support-for-import-jwk
Browse files Browse the repository at this point in the history
Add support for importKey('jwk').
  • Loading branch information
dajiaji authored Jul 17, 2023
2 parents a50325b + 82aaa4d commit 04ffeaa
Show file tree
Hide file tree
Showing 11 changed files with 1,058 additions and 58 deletions.
4 changes: 2 additions & 2 deletions src/cipherSuite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,8 +242,8 @@ export class CipherSuite {
* @throws {@link DeserializeError}
*/
public async importKey(
format: "raw",
key: ArrayBuffer,
format: "raw" | "jwk",
key: ArrayBuffer | JsonWebKey,
isPublic = true,
): Promise<CryptoKey> {
await this.setup();
Expand Down
4 changes: 2 additions & 2 deletions src/interfaces/kemInterface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ export interface KemInterface {
* Imports a key for the KEM.
*/
importKey(
format: "raw",
key: ArrayBuffer,
format: "raw" | "jwk",
key: ArrayBuffer | JsonWebKey,
isPublic: boolean,
): Promise<CryptoKey>;

Expand Down
4 changes: 2 additions & 2 deletions src/interfaces/kemPrimitives.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ export interface KemPrimitives {
deserializePublicKey(key: ArrayBuffer): Promise<CryptoKey>;

importKey(
format: "raw",
key: ArrayBuffer,
format: "raw" | "jwk",
key: ArrayBuffer | JsonWebKey,
isPublic: boolean,
): Promise<CryptoKey>;

Expand Down
4 changes: 2 additions & 2 deletions src/kems/dhkem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ export class Dhkem extends WebCrypto implements KemInterface {
}

public async importKey(
format: "raw",
key: ArrayBuffer,
format: "raw" | "jwk",
key: ArrayBuffer | JsonWebKey,
isPublic: boolean,
): Promise<CryptoKey> {
try {
Expand Down
46 changes: 41 additions & 5 deletions src/kems/dhkemPrimitives/ec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,13 +185,24 @@ export class Ec implements KemPrimitives {
}

public async importKey(
format: "raw",
key: ArrayBuffer,
format: "raw" | "jwk",
key: ArrayBuffer | JsonWebKey,
isPublic: boolean,
): Promise<CryptoKey> {
if (format !== "raw") {
throw new Error("Unsupported format");
if (format === "raw") {
return await this._importRawKey(key as ArrayBuffer, isPublic);
}
// jwk
if (key instanceof ArrayBuffer) {
throw new Error("Invalid jwk key format");
}
return await this._importJWK(key as JsonWebKey, isPublic);
}

private async _importRawKey(
key: ArrayBuffer,
isPublic: boolean,
): Promise<CryptoKey> {
if (isPublic && key.byteLength !== this._nPk) {
throw new Error("Invalid public key for the ciphersuite");
}
Expand All @@ -201,7 +212,7 @@ export class Ec implements KemPrimitives {
try {
if (isPublic) {
// return await this._api.importKey(format, key, this._alg, true, consts.KEM_USAGES);
return await this._api.importKey(format, key, this._alg, true, []);
return await this._api.importKey("raw", key, this._alg, true, []);
}
const k = new Uint8Array(key);
const pkcs8Key = new Uint8Array(this._pkcs8AlgId.length + k.length);
Expand All @@ -219,6 +230,31 @@ export class Ec implements KemPrimitives {
}
}

private async _importJWK(
key: JsonWebKey,
isPublic: boolean,
): Promise<CryptoKey> {
if (typeof key.crv === "undefined" || key.crv !== this._alg.namedCurve) {
throw new Error(`Invalid crv: ${key.crv}`);
}
if (isPublic) {
if (typeof key.d !== "undefined") {
throw new Error("Invalid key: `d` should not be set");
}
return await this._api.importKey("jwk", key, this._alg, true, []);
}
if (typeof key.d === "undefined") {
throw new Error("Invalid key: `d` not found");
}
return await this._api.importKey(
"jwk",
key,
this._alg,
true,
consts.KEM_USAGES,
);
}

public async derivePublicKey(key: CryptoKey): Promise<CryptoKey> {
const jwk = await this._api.exportKey("jwk", key);
delete jwk["d"];
Expand Down
57 changes: 51 additions & 6 deletions src/kems/dhkemPrimitives/x25519.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type { KdfInterface } from "../../interfaces/kdfInterface.ts";
import { XCryptoKey } from "../../xCryptoKey.ts";

import * as consts from "../../consts.ts";
import { base64UrlToBytes } from "../../utils/misc.ts";

const ALG_NAME = "X25519";

Expand All @@ -29,14 +30,18 @@ export class X25519 implements KemPrimitives {
}

public async importKey(
format: "raw",
key: ArrayBuffer,
format: "raw" | "jwk",
key: ArrayBuffer | JsonWebKey,
isPublic: boolean,
): Promise<CryptoKey> {
if (format !== "raw") {
throw new Error("Unsupported format");
if (format === "raw") {
return await this._importRawKey(key as ArrayBuffer, isPublic);
}
// jwk
if (key instanceof ArrayBuffer) {
throw new Error("Invalid jwk key format");
}
return await this._importKey(key, isPublic);
return await this._importJWK(key as JsonWebKey, isPublic);
}

public async derivePublicKey(key: CryptoKey): Promise<CryptoKey> {
Expand Down Expand Up @@ -89,7 +94,10 @@ export class X25519 implements KemPrimitives {
});
}

private _importKey(key: ArrayBuffer, isPublic: boolean): Promise<CryptoKey> {
private _importRawKey(
key: ArrayBuffer,
isPublic: boolean,
): Promise<CryptoKey> {
return new Promise((resolve, reject) => {
if (isPublic && key.byteLength !== this._nPk) {
reject(new Error("Invalid public key for the ciphersuite"));
Expand All @@ -107,6 +115,43 @@ export class X25519 implements KemPrimitives {
});
}

private _importJWK(key: JsonWebKey, isPublic: boolean): Promise<CryptoKey> {
return new Promise((resolve, reject) => {
if (typeof key.kty === "undefined" || key.kty !== "OKP") {
reject(new Error(`Invalid kty: ${key.kty}`));
}
if (typeof key.crv === "undefined" || key.crv !== "X25519") {
reject(new Error(`Invalid crv: ${key.crv}`));
}
if (isPublic) {
if (typeof key.d !== "undefined") {
reject(new Error("Invalid key: `d` should not be set"));
}
if (typeof key.x === "undefined") {
reject(new Error("Invalid key: `x` not found"));
}
resolve(
new XCryptoKey(
ALG_NAME,
base64UrlToBytes(key.x as string),
"public",
),
);
} else {
if (typeof key.d !== "string") {
reject(new Error("Invalid key: `d` not found"));
}
resolve(
new XCryptoKey(
ALG_NAME,
base64UrlToBytes(key.d as string),
"private",
),
);
}
});
}

private _derivePublicKey(k: XCryptoKey): Promise<CryptoKey> {
return new Promise((resolve) => {
const pk = x25519.getPublicKey(k.key);
Expand Down
57 changes: 51 additions & 6 deletions src/kems/dhkemPrimitives/x448.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type { KdfInterface } from "../../interfaces/kdfInterface.ts";
import { XCryptoKey } from "../../xCryptoKey.ts";

import * as consts from "../../consts.ts";
import { base64UrlToBytes } from "../../utils/misc.ts";

const ALG_NAME = "X448";

Expand All @@ -29,14 +30,18 @@ export class X448 implements KemPrimitives {
}

public async importKey(
format: "raw",
key: ArrayBuffer,
format: "raw" | "jwk",
key: ArrayBuffer | JsonWebKey,
isPublic: boolean,
): Promise<CryptoKey> {
if (format !== "raw") {
throw new Error("Unsupported format");
if (format === "raw") {
return await this._importRawKey(key as ArrayBuffer, isPublic);
}
// jwk
if (key instanceof ArrayBuffer) {
throw new Error("Invalid jwk key format");
}
return await this._importKey(key, isPublic);
return await this._importJWK(key as JsonWebKey, isPublic);
}

public async derivePublicKey(key: CryptoKey): Promise<CryptoKey> {
Expand Down Expand Up @@ -89,7 +94,10 @@ export class X448 implements KemPrimitives {
});
}

private _importKey(key: ArrayBuffer, isPublic: boolean): Promise<CryptoKey> {
private _importRawKey(
key: ArrayBuffer,
isPublic: boolean,
): Promise<CryptoKey> {
return new Promise((resolve, reject) => {
if (isPublic && key.byteLength !== this._nPk) {
reject(new Error("Invalid public key for the ciphersuite"));
Expand All @@ -107,6 +115,43 @@ export class X448 implements KemPrimitives {
});
}

private _importJWK(key: JsonWebKey, isPublic: boolean): Promise<CryptoKey> {
return new Promise((resolve, reject) => {
if (key.kty !== "OKP") {
reject(new Error(`Invalid kty: ${key.kty}`));
}
if (key.crv !== "X448") {
reject(new Error(`Invalid crv: ${key.crv}`));
}
if (isPublic) {
if (typeof key.d !== "undefined") {
reject(new Error("Invalid key: `d` should not be set"));
}
if (typeof key.x !== "string") {
reject(new Error("Invalid key: `x` not found"));
}
resolve(
new XCryptoKey(
ALG_NAME,
base64UrlToBytes(key.x as string),
"public",
),
);
} else {
if (typeof key.d !== "string") {
reject(new Error("Invalid key: `d` not found"));
}
resolve(
new XCryptoKey(
ALG_NAME,
base64UrlToBytes(key.d as string),
"private",
),
);
}
});
}

private _derivePublicKey(k: XCryptoKey): Promise<CryptoKey> {
return new Promise((resolve) => {
const pk = x448.getPublicKey(k.key);
Expand Down
15 changes: 14 additions & 1 deletion src/utils/misc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,19 @@ export function hexToBytes(v: string): Uint8Array {
/**
* Converts bytes to hex string.
*/
export function bytesToHex(v: Uint8Array) {
export function bytesToHex(v: Uint8Array): string {
return [...v].map((x) => x.toString(16).padStart(2, "0")).join("");
}

/**
* Decodes Base64Url-encoded data.
*/
export function base64UrlToBytes(v: string): Uint8Array {
const base64 = v.replace(/-/g, "+").replace(/_/g, "/");
const byteString = atob(base64);
const ret = new Uint8Array(byteString.length);
for (let i = 0; i < byteString.length; i++) {
ret[i] = byteString.charCodeAt(i);
}
return ret;
}
3 changes: 3 additions & 0 deletions src/xCryptoKey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,8 @@ export class XCryptoKey implements CryptoKey {
this.key = key;
this.type = type;
this.algorithm = { name: name };
if (type === "public") {
this.usages = [];
}
}
}
Loading

0 comments on commit 04ffeaa

Please sign in to comment.