Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add serialize/deserializePrivatekey to KemInterface. #249

Merged
merged 4 commits into from
Aug 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading