Skip to content

Commit

Permalink
Merge pull request #249 from dajiaji/add-serialize-deserialize-privat…
Browse files Browse the repository at this point in the history
…ekey

Add serialize/deserializePrivatekey to KemInterface.
  • Loading branch information
dajiaji authored Aug 13, 2023
2 parents c335f58 + 3a65fc0 commit 35f3e7f
Show file tree
Hide file tree
Showing 12 changed files with 275 additions and 80 deletions.
12 changes: 4 additions & 8 deletions src/exporterContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import type { Encapsulator } from "./interfaces/encapsulator.ts";
import type { EncryptionContext } from "./interfaces/encryptionContext.ts";
import type { KdfInterface } from "./interfaces/kdfInterface.ts";

import { emitNotSupported } from "./utils/emitNotSupported.ts";

import * as consts from "./consts.ts";
import * as errors from "./errors.ts";

Expand All @@ -27,14 +29,14 @@ export class ExporterContextImpl implements EncryptionContext {
_data: ArrayBuffer,
_aad: ArrayBuffer,
): Promise<ArrayBuffer> {
return await this._emitError();
return await emitNotSupported<ArrayBuffer>();
}

public async open(
_data: ArrayBuffer,
_aad: ArrayBuffer,
): Promise<ArrayBuffer> {
return await this._emitError();
return await emitNotSupported<ArrayBuffer>();
}

public async export(
Expand All @@ -55,12 +57,6 @@ export class ExporterContextImpl implements EncryptionContext {
throw new errors.ExportError(e);
}
}

private _emitError(): Promise<ArrayBuffer> {
return new Promise((_resolve, reject) => {
reject(new errors.NotSupportedError("Not available"));
});
}
}

export class RecipientExporterContextImpl extends ExporterContextImpl {}
Expand Down
22 changes: 22 additions & 0 deletions src/interfaces/kemInterface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,28 @@ export interface KemInterface {
*/
deserializePublicKey(key: ArrayBuffer): Promise<CryptoKey>;

/**
* Serializes a private key as CryptoKey to a byte string of length `Nsk`.
*
* If the error occurred, throws {@link SerializeError}.
*
* @param key A CryptoKey.
* @returns A key as bytes.
* @throws {@link SerializeError}
*/
serializePrivateKey(key: CryptoKey): Promise<ArrayBuffer>;

/**
* Deserializes a private key as a byte string of length `Nsk` to CryptoKey.
*
* If the error occurred, throws {@link DeserializeError}.
*
* @param key A key as bytes.
* @returns A CryptoKey.
* @throws {@link DeserializeError}
*/
deserializePrivateKey(key: ArrayBuffer): Promise<CryptoKey>;

/**
* Imports a public or private key and converts to a {@link CryptoKey}.
*
Expand Down
4 changes: 4 additions & 0 deletions src/interfaces/kemPrimitives.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ export interface KemPrimitives {

deserializePublicKey(key: ArrayBuffer): Promise<CryptoKey>;

serializePrivateKey(key: CryptoKey): Promise<ArrayBuffer>;

deserializePrivateKey(key: ArrayBuffer): Promise<CryptoKey>;

importKey(
format: "raw" | "jwk",
key: ArrayBuffer | JsonWebKey,
Expand Down
19 changes: 18 additions & 1 deletion src/kems/dhkem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import * as errors from "../errors.ts";

// b"KEM"
const SUITE_ID_HEADER_KEM = new Uint8Array([75, 69, 77, 0, 0]);

// b"eae_prk"
const LABEL_EAE_PRK = new Uint8Array([101, 97, 101, 95, 112, 114, 107]);
// b"shared_secret"
Expand Down Expand Up @@ -88,6 +87,24 @@ export class Dhkem extends Algorithm implements KemInterface {
}
}

public async serializePrivateKey(key: CryptoKey): Promise<ArrayBuffer> {
await this._setup();
try {
return await this._prim.serializePrivateKey(key);
} catch (e: unknown) {
throw new errors.SerializeError(e);
}
}

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

public async importKey(
format: "raw" | "jwk",
key: ArrayBuffer | JsonWebKey,
Expand Down
25 changes: 23 additions & 2 deletions src/kems/dhkemPrimitives/ec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Algorithm } from "../../algorithm.ts";
import { KemId } from "../../identifiers.ts";
import { KEM_USAGES, LABEL_DKP_PRK } from "../../interfaces/kemPrimitives.ts";
import { Bignum } from "../../utils/bignum.ts";
import { i2Osp } from "../../utils/misc.ts";
import { base64UrlToBytes, i2Osp } from "../../utils/misc.ts";

import { EMPTY } from "../../consts.ts";

Expand Down Expand Up @@ -361,6 +361,28 @@ export class Ec extends Algorithm implements KemPrimitives {
}
}

public async serializePrivateKey(key: CryptoKey): Promise<ArrayBuffer> {
this.checkInit();
const jwk = await (this._api as SubtleCrypto).exportKey("jwk", key);
if (!("d" in jwk)) {
throw new Error("Not private key");
}
const ret = base64UrlToBytes(jwk["d"] as string);
// const ret = (await this._api.exportKey('spki', key)).slice(24);
if (ret.byteLength !== this._nSk) {
throw new Error("Invalid length of the key");
}
return ret;
}

public async deserializePrivateKey(key: ArrayBuffer): Promise<CryptoKey> {
this.checkInit();
if (key.byteLength !== this._nSk) {
throw new Error("Invalid length of the key");
}
return await this._importRawKey(key, false);
}

public async importKey(
format: "raw" | "jwk",
key: ArrayBuffer | JsonWebKey,
Expand Down Expand Up @@ -449,7 +471,6 @@ export class Ec extends Algorithm implements KemPrimitives {
const jwk = await (this._api as SubtleCrypto).exportKey("jwk", key);
delete jwk["d"];
delete jwk["key_ops"];
// return await this._api.importKey('jwk', jwk, this._alg, true, KEM_USAGES);
return await (this._api as SubtleCrypto).importKey(
"jwk",
jwk,
Expand Down
26 changes: 26 additions & 0 deletions src/kems/dhkemPrimitives/secp256k1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ export class Secp256k1 extends Algorithm implements KemPrimitives {
return await this._deserializePublicKey(key);
}

public async serializePrivateKey(key: CryptoKey): Promise<ArrayBuffer> {
return await this._serializePrivateKey(key as XCryptoKey);
}

public async deserializePrivateKey(key: ArrayBuffer): Promise<CryptoKey> {
return await this._deserializePrivateKey(key);
}

public async importKey(
format: "raw",
key: ArrayBuffer,
Expand Down Expand Up @@ -102,6 +110,24 @@ export class Secp256k1 extends Algorithm implements KemPrimitives {
});
}

private _serializePrivateKey(k: XCryptoKey): Promise<ArrayBuffer> {
return new Promise((resolve) => {
resolve(k.key.buffer);
});
}

private _deserializePrivateKey(k: ArrayBuffer): Promise<CryptoKey> {
return new Promise((resolve, reject) => {
if (k.byteLength !== this._nSk) {
reject(new Error("Invalid private key for the ciphersuite"));
} else {
resolve(
new XCryptoKey(ALG_NAME, new Uint8Array(k), "private", KEM_USAGES),
);
}
});
}

private _importKey(key: ArrayBuffer, isPublic: boolean): Promise<CryptoKey> {
return new Promise((resolve, reject) => {
if (isPublic && key.byteLength !== this._nPk) {
Expand Down
26 changes: 26 additions & 0 deletions src/kems/dhkemPrimitives/x25519.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@ export class X25519 extends Algorithm implements KemPrimitives {
return await this._deserializePublicKey(key);
}

public async serializePrivateKey(key: CryptoKey): Promise<ArrayBuffer> {
return await this._serializePrivateKey(key as XCryptoKey);
}

public async deserializePrivateKey(key: ArrayBuffer): Promise<CryptoKey> {
return await this._deserializePrivateKey(key);
}

public async importKey(
format: "raw" | "jwk",
key: ArrayBuffer | JsonWebKey,
Expand Down Expand Up @@ -107,6 +115,24 @@ export class X25519 extends Algorithm implements KemPrimitives {
});
}

private _serializePrivateKey(k: XCryptoKey): Promise<ArrayBuffer> {
return new Promise((resolve) => {
resolve(k.key.buffer);
});
}

private _deserializePrivateKey(k: ArrayBuffer): Promise<CryptoKey> {
return new Promise((resolve, reject) => {
if (k.byteLength !== this._nSk) {
reject(new Error("Invalid length of the key"));
} else {
resolve(
new XCryptoKey(ALG_NAME, new Uint8Array(k), "private", KEM_USAGES),
);
}
});
}

private _importRawKey(
key: ArrayBuffer,
isPublic: boolean,
Expand Down
31 changes: 30 additions & 1 deletion src/kems/dhkemPrimitives/x448.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@ export class X448 extends Algorithm implements KemPrimitives {
return await this._deserializePublicKey(key);
}

public async serializePrivateKey(key: CryptoKey): Promise<ArrayBuffer> {
return await this._serializePrivateKey(key as XCryptoKey);
}

public async deserializePrivateKey(key: ArrayBuffer): Promise<CryptoKey> {
return await this._deserializePrivateKey(key);
}

public async importKey(
format: "raw" | "jwk",
key: ArrayBuffer | JsonWebKey,
Expand Down Expand Up @@ -107,6 +115,24 @@ export class X448 extends Algorithm implements KemPrimitives {
});
}

private _serializePrivateKey(k: XCryptoKey): Promise<ArrayBuffer> {
return new Promise((resolve) => {
resolve(k.key.buffer);
});
}

private _deserializePrivateKey(k: ArrayBuffer): Promise<CryptoKey> {
return new Promise((resolve, reject) => {
if (k.byteLength !== this._nSk && k.byteLength !== this._nSk + 1) {
reject(new Error(`Invalid length of the key: ${new Uint8Array(k)}`));
} else {
resolve(
new XCryptoKey(ALG_NAME, new Uint8Array(k), "private", KEM_USAGES),
);
}
});
}

private _importRawKey(
key: ArrayBuffer,
isPublic: boolean,
Expand All @@ -115,7 +141,10 @@ export class X448 extends Algorithm implements KemPrimitives {
if (isPublic && key.byteLength !== this._nPk) {
reject(new Error("Invalid public key for the ciphersuite"));
}
if (!isPublic && key.byteLength !== this._nSk) {
if (
!isPublic &&
(key.byteLength !== this._nSk && key.byteLength !== this._nSk + 1)
) {
reject(new Error("Invalid private key for the ciphersuite"));
}
resolve(
Expand Down
7 changes: 7 additions & 0 deletions src/utils/emitNotSupported.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { NotSupportedError } from "../errors.ts";

export function emitNotSupported<T>(): Promise<T> {
return new Promise((_resolve, reject) => {
reject(new NotSupportedError("Not supported"));
});
}
8 changes: 8 additions & 0 deletions test/conformanceTester.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,14 @@ export class ConformanceTester {
privateKey: await suite.kem.importKey("raw", skRm, false),
publicKey: await suite.kem.importKey("raw", pkRm, true),
};

const dSkR = await suite.kem.deserializePrivateKey(skRm);
const dPkR = await suite.kem.deserializePublicKey(pkRm);
const skRm2 = await suite.kem.serializePrivateKey(dSkR);
const pkRm2 = await suite.kem.serializePublicKey(dPkR);
assertEquals(skRm, new Uint8Array(skRm2));
assertEquals(pkRm, new Uint8Array(pkRm2));

const ekp = {
privateKey: await suite.kem.importKey("raw", skEm, false),
publicKey: await suite.kem.importKey("raw", pkEm), // true can be omitted
Expand Down
Loading

0 comments on commit 35f3e7f

Please sign in to comment.