Skip to content

Commit

Permalink
Merge pull request #432 from dajiaji/add-x448-to-core
Browse files Browse the repository at this point in the history
Add x448 to core.
  • Loading branch information
dajiaji authored Oct 13, 2024
2 parents 7daa676 + 5c49d88 commit addc2e6
Show file tree
Hide file tree
Showing 8 changed files with 1,803 additions and 317 deletions.
1 change: 1 addition & 0 deletions packages/core/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,4 @@ export {
} from "./src/native.ts";

export { DhkemX25519HkdfSha256 } from "./src/kems/dhkemX25519.ts";
export { DhkemX448HkdfSha512 } from "./src/kems/dhkemX448.ts";
11 changes: 1 addition & 10 deletions packages/core/src/kems/dhkemPrimitives/x25519.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { DhkemPrimitives, KdfInterface } from "@hpke/common";

import {
base64UrlToBytes,
DeriveKeyPairError,
DeserializeError,
EMPTY,
Expand All @@ -20,16 +21,6 @@ const PKCS8_ALG_ID_X25519 = new Uint8Array([
0x03, 0x2b, 0x65, 0x6e, 0x04, 0x22, 0x04, 0x20,
]);

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;
}

export class X25519 extends NativeAlgorithm implements DhkemPrimitives {
private _hkdf: KdfInterface;
private _alg: KeyAlgorithm;
Expand Down
246 changes: 246 additions & 0 deletions packages/core/src/kems/dhkemPrimitives/x448.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
import type { DhkemPrimitives, KdfInterface } from "@hpke/common";

import {
base64UrlToBytes,
DeriveKeyPairError,
DeserializeError,
EMPTY,
KEM_USAGES,
LABEL_DKP_PRK,
LABEL_SK,
NativeAlgorithm,
NotSupportedError,
SerializeError,
} from "@hpke/common";

const ALG_NAME = "X448";

// deno-fmt-ignore
const PKCS8_ALG_ID_X448 = new Uint8Array([
0x30, 0x46, 0x02, 0x01, 0x00, 0x30, 0x05, 0x06,
0x03, 0x2b, 0x65, 0x6f, 0x04, 0x3a, 0x04, 0x38,
]);

export class X448 extends NativeAlgorithm implements DhkemPrimitives {
private _hkdf: KdfInterface;
private _alg: KeyAlgorithm;
private _nPk: number;
private _nSk: number;
private _nDh: number;
private _pkcs8AlgId: Uint8Array;

constructor(hkdf: KdfInterface) {
super();
this._alg = { name: ALG_NAME };
this._hkdf = hkdf;
this._nPk = 56;
this._nSk = 56;
this._nDh = 56;
this._pkcs8AlgId = PKCS8_ALG_ID_X448;
}

public async serializePublicKey(key: CryptoKey): Promise<ArrayBuffer> {
await this._setup();
try {
return await (this._api as SubtleCrypto).exportKey("raw", key);
} catch (e: unknown) {
throw new SerializeError(e);
}
}

public async deserializePublicKey(key: ArrayBuffer): Promise<CryptoKey> {
await this._setup();
try {
return await this._importRawKey(key, true);
} catch (e: unknown) {
throw new DeserializeError(e);
}
}

public async serializePrivateKey(key: CryptoKey): Promise<ArrayBuffer> {
await this._setup();
try {
const jwk = await (this._api as SubtleCrypto).exportKey("jwk", key);
if (!("d" in jwk)) {
throw new Error("Not private key");
}
return base64UrlToBytes(jwk["d"] as string);
} catch (e: unknown) {
throw new SerializeError(e);
}
}

public async deserializePrivateKey(key: ArrayBuffer): Promise<CryptoKey> {
await this._setup();
try {
return await this._importRawKey(key, false);
} catch (e: unknown) {
throw new DeserializeError(e);
}
}

public async importKey(
format: "raw" | "jwk",
key: ArrayBuffer | JsonWebKey,
isPublic: boolean,
): Promise<CryptoKey> {
await this._setup();
try {
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);
} catch (e: unknown) {
throw new DeserializeError(e);
}
}

public async generateKeyPair(): Promise<CryptoKeyPair> {
await this._setup();
try {
return await (this._api as SubtleCrypto).generateKey(
ALG_NAME,
true,
KEM_USAGES,
) as CryptoKeyPair;
} catch (e: unknown) {
throw new NotSupportedError(e);
}
}

public async deriveKeyPair(ikm: ArrayBuffer): Promise<CryptoKeyPair> {
await this._setup();
try {
const dkpPrk = await this._hkdf.labeledExtract(
EMPTY,
LABEL_DKP_PRK,
new Uint8Array(ikm),
);
const rawSk = await this._hkdf.labeledExpand(
dkpPrk,
LABEL_SK,
EMPTY,
this._nSk,
);
const rawSkBytes = new Uint8Array(rawSk);
const sk = await this._deserializePkcs8Key(rawSkBytes);
rawSkBytes.fill(0);
return {
privateKey: sk,
publicKey: await this.derivePublicKey(sk),
};
} catch (e: unknown) {
throw new DeriveKeyPairError(e);
}
}

public async derivePublicKey(key: CryptoKey): Promise<CryptoKey> {
await this._setup();
try {
const jwk = await (this._api as SubtleCrypto).exportKey("jwk", key);
delete jwk["d"];
delete jwk["key_ops"];
return await (this._api as SubtleCrypto).importKey(
"jwk",
jwk,
this._alg,
true,
[],
);
} catch (e: unknown) {
throw new DeserializeError(e);
}
}

public async dh(sk: CryptoKey, pk: CryptoKey): Promise<ArrayBuffer> {
await this._setup();
try {
const bits = await (this._api as SubtleCrypto).deriveBits(
{
name: ALG_NAME,
public: pk,
},
sk,
this._nDh * 8,
);
return bits;
} catch (e: unknown) {
throw new SerializeError(e);
}
}

private async _importRawKey(
key: ArrayBuffer,
isPublic: boolean,
): Promise<CryptoKey> {
if (isPublic && key.byteLength !== this._nPk) {
throw new Error("Invalid public key for the ciphersuite");
}
if (!isPublic && key.byteLength !== this._nSk) {
throw new Error("Invalid private key for the ciphersuite");
}
if (isPublic) {
return await (this._api as SubtleCrypto).importKey(
"raw",
key,
this._alg,
true,
[],
);
}
return await this._deserializePkcs8Key(new Uint8Array(key));
}

private async _importJWK(
key: JsonWebKey,
isPublic: boolean,
): Promise<CryptoKey> {
if (typeof key.kty === "undefined" || key.kty !== "OKP") {
throw new Error(`Invalid kty: ${key.crv}`);
}
if (typeof key.crv === "undefined" || key.crv !== ALG_NAME) {
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 as SubtleCrypto).importKey(
"jwk",
key,
this._alg,
true,
[],
);
}
if (typeof key.d === "undefined") {
throw new Error("Invalid key: `d` not found");
}
return await (this._api as SubtleCrypto).importKey(
"jwk",
key,
this._alg,
true,
KEM_USAGES,
);
}

private async _deserializePkcs8Key(k: Uint8Array): Promise<CryptoKey> {
const pkcs8Key = new Uint8Array(
this._pkcs8AlgId.length + k.length,
);
pkcs8Key.set(this._pkcs8AlgId, 0);
pkcs8Key.set(k, this._pkcs8AlgId.length);
return await (this._api as SubtleCrypto).importKey(
"pkcs8",
pkcs8Key,
this._alg,
true,
KEM_USAGES,
);
}
}
44 changes: 44 additions & 0 deletions packages/core/src/kems/dhkemX448.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { Dhkem, HkdfSha512Native, KemId } from "@hpke/common";

import { X448 } from "./dhkemPrimitives/x448.ts";

/**
* The DHKEM(X448, HKDF-SHA512) for HPKE KEM implementing {@link KemInterface}.
*
* The instance of this class can be specified to the
* {@link https://jsr.io/@hpke/core/doc/~/CipherSuiteParams | CipherSuiteParams} as follows:
*
* @example
*
* ```ts
* import {
* Aes256Gcm,
* CipherSuite,
* HkdfSha512,
* DhkemX448HkdfSha512,
* } from "@hpke/core";
*
* const suite = new CipherSuite({
* kem: new DhkemX448HkdfSha512(),
* kdf: new HkdfSha512(),
* aead: new Aes256Gcm(),
* });
* ```
*/
export class DhkemX448HkdfSha512 extends Dhkem {
/** KemId.DhkemX448HkdfSha512 (0x0021) */
override id: KemId = KemId.DhkemX448HkdfSha512;
/** 64 */
override secretSize: number = 64;
/** 56 */
override encSize: number = 56;
/** 56 */
override publicKeySize: number = 56;
/** 56 */
override privateKeySize: number = 56;

constructor() {
const kdf = new HkdfSha512Native();
super(KemId.DhkemX448HkdfSha512, new X448(kdf), kdf);
}
}
Loading

0 comments on commit addc2e6

Please sign in to comment.