diff --git a/mod.ts b/mod.ts index 3fa3f3efd..9623f2337 100644 --- a/mod.ts +++ b/mod.ts @@ -1,3 +1,4 @@ +export type { AeadInterface } from "./src/interfaces/aeadInterface.ts"; export type { AeadKey } from "./src/interfaces/aeadKey.ts"; export type { CipherSuiteParams } from "./src/interfaces/cipherSuiteParams.ts"; export type { KdfInterface } from "./src/interfaces/kdfInterface.ts"; diff --git a/src/aeadKeys/aesGcmKey.ts b/src/aeadKeys/aesGcmKey.ts index 74ab5f33b..031cd0a28 100644 --- a/src/aeadKeys/aesGcmKey.ts +++ b/src/aeadKeys/aesGcmKey.ts @@ -1,20 +1,21 @@ import type { AeadKey } from "../interfaces/aeadKey.ts"; +import type { AeadInterface } from "../interfaces/aeadInterface.ts"; import { Algorithm } from "../algorithm.ts"; import { AeadId } from "../identifiers.ts"; import * as consts from "../consts.ts"; -export class AesGcmKey extends Algorithm implements AeadKey { +export class AesGcmKey implements AeadKey { public readonly id: AeadId = AeadId.Aes128Gcm; public readonly keySize: number = 0; public readonly nonceSize: number = 0; public readonly tagSize: number = 0; private _rawKey: ArrayBuffer; private _key: CryptoKey | undefined = undefined; - // private _api: SubtleCrypto; + private _api: SubtleCrypto; - public constructor(key: ArrayBuffer) { - super(); + public constructor(api: SubtleCrypto, key: ArrayBuffer) { + this._api = api; this._rawKey = key; } @@ -23,7 +24,6 @@ export class AesGcmKey extends Algorithm implements AeadKey { data: ArrayBuffer, aad: ArrayBuffer, ): Promise { - this.checkInit(); if (this._key === undefined) { this._key = await this.importKey(this._rawKey); (new Uint8Array(this._rawKey)).fill(0); @@ -33,7 +33,7 @@ export class AesGcmKey extends Algorithm implements AeadKey { iv: iv, additionalData: aad, }; - const ct: ArrayBuffer = await (this._api as SubtleCrypto).encrypt( + const ct: ArrayBuffer = await this._api.encrypt( alg, this._key, data, @@ -46,7 +46,6 @@ export class AesGcmKey extends Algorithm implements AeadKey { data: ArrayBuffer, aad: ArrayBuffer, ): Promise { - this.checkInit(); if (this._key === undefined) { this._key = await this.importKey(this._rawKey); (new Uint8Array(this._rawKey)).fill(0); @@ -56,7 +55,7 @@ export class AesGcmKey extends Algorithm implements AeadKey { iv: iv, additionalData: aad, }; - const pt: ArrayBuffer = await (this._api as SubtleCrypto).decrypt( + const pt: ArrayBuffer = await this._api.decrypt( alg, this._key, data, @@ -65,7 +64,7 @@ export class AesGcmKey extends Algorithm implements AeadKey { } private async importKey(key: ArrayBuffer): Promise { - return await (this._api as SubtleCrypto).importKey( + return await this._api.importKey( "raw", key, { name: "AES-GCM" }, @@ -88,3 +87,27 @@ export class Aes256GcmKey extends AesGcmKey { public readonly nonceSize: number = 12; public readonly tagSize: number = 16; } + +export class Aes128Gcm extends Algorithm implements AeadInterface { + public readonly id: AeadId = AeadId.Aes128Gcm; + public readonly keySize: number = 16; + public readonly nonceSize: number = 12; + public readonly tagSize: number = 16; + + public createAeadKey(key: ArrayBuffer): AeadKey { + this.checkInit(); + return new Aes128GcmKey(this._api as SubtleCrypto, key); + } +} + +export class Aes256Gcm extends Algorithm implements AeadInterface { + public readonly id: AeadId = AeadId.Aes256Gcm; + public readonly keySize: number = 32; + public readonly nonceSize: number = 12; + public readonly tagSize: number = 16; + + public createAeadKey(key: ArrayBuffer): AeadKey { + this.checkInit(); + return new Aes256GcmKey(this._api as SubtleCrypto, key); + } +} diff --git a/src/aeadKeys/chacha20Poly1305Key.ts b/src/aeadKeys/chacha20Poly1305Key.ts index 8f0f028df..3722a3f5c 100644 --- a/src/aeadKeys/chacha20Poly1305Key.ts +++ b/src/aeadKeys/chacha20Poly1305Key.ts @@ -1,18 +1,21 @@ import { chacha20_poly1305 } from "npm:@noble/ciphers@0.1.4/chacha"; -import { Algorithm } from "../algorithm.ts"; import type { AeadKey } from "../interfaces/aeadKey.ts"; +import type { AeadInterface } from "../interfaces/aeadInterface.ts"; + +import { Algorithm } from "../algorithm.ts"; import { AeadId } from "../identifiers.ts"; -export class Chacha20Poly1305Key extends Algorithm implements AeadKey { +export class Chacha20Poly1305Key implements AeadKey { public readonly id: AeadId = AeadId.Chacha20Poly1305; public readonly keySize: number = 32; public readonly nonceSize: number = 12; public readonly tagSize: number = 16; private _key: Uint8Array; + private _api: SubtleCrypto; - public constructor(key: ArrayBuffer) { - super(); + public constructor(api: SubtleCrypto, key: ArrayBuffer) { + this._api = api; this._key = new Uint8Array(key); } @@ -62,3 +65,14 @@ export class Chacha20Poly1305Key extends Algorithm implements AeadKey { }); } } + +export class Chacha20Poly1305 extends Algorithm implements AeadInterface { + public readonly id: AeadId = AeadId.Chacha20Poly1305; + public readonly keySize: number = 32; + public readonly nonceSize: number = 12; + public readonly tagSize: number = 16; + + public createAeadKey(key: ArrayBuffer): AeadKey { + return new Chacha20Poly1305Key(this._api as SubtleCrypto, key); + } +} diff --git a/src/aeadKeys/exportOnly.ts b/src/aeadKeys/exportOnly.ts new file mode 100644 index 000000000..307adf2b5 --- /dev/null +++ b/src/aeadKeys/exportOnly.ts @@ -0,0 +1,20 @@ +import type { AeadKey } from "../interfaces/aeadKey.ts"; +import type { AeadInterface } from "../interfaces/aeadInterface.ts"; + +import { Algorithm } from "../algorithm.ts"; +import { AeadId } from "../identifiers.ts"; + +import { NotSupportedError } from "../errors.ts"; + +export class ExportOnly extends Algorithm implements AeadInterface { + public readonly id: AeadId = AeadId.ExportOnly; + public readonly keySize: number = 0; + public readonly nonceSize: number = 0; + public readonly tagSize: number = 0; + + public createAeadKey(_key: ArrayBuffer): AeadKey { + throw new NotSupportedError( + "createAeadKey() is not supported on ExportOnly", + ); + } +} diff --git a/src/cipherSuite.ts b/src/cipherSuite.ts index 6e889143e..7657d33d1 100644 --- a/src/cipherSuite.ts +++ b/src/cipherSuite.ts @@ -1,4 +1,5 @@ import type { AeadKey } from "./interfaces/aeadKey.ts"; +import type { AeadInterface } from "./interfaces/aeadInterface.ts"; import type { AeadParams } from "./interfaces/aeadParams.ts"; import type { CipherSuiteParams } from "./interfaces/cipherSuiteParams.ts"; import type { KdfInterface } from "./interfaces/kdfInterface.ts"; @@ -16,8 +17,10 @@ import { RecipientExporterContext, SenderExporterContext, } from "./exporterContext.ts"; -import { createAeadKey } from "./encryptionContext.ts"; import { AeadId, KdfId, KemId, Mode } from "./identifiers.ts"; +import { Aes128Gcm, Aes256Gcm } from "./aeadKeys/aesGcmKey.ts"; +import { ExportOnly } from "./aeadKeys/exportOnly.ts"; +import { Chacha20Poly1305 } from "./aeadKeys/chacha20Poly1305Key.ts"; import { HkdfSha256, HkdfSha384, HkdfSha512 } from "./kdfs/hkdf.ts"; import { RecipientContext } from "./recipientContext.ts"; import { SenderContext } from "./senderContext.ts"; @@ -57,16 +60,10 @@ export class CipherSuite { /** The AEAD id of the cipher suite. */ public readonly aead: AeadId; - /** The length in bytes of an AEAD key (Nk). */ - public readonly aeadKeySize: number = 0; - /** The length in bytes of an AEAD nonce (Nn). */ - public readonly aeadNonceSize: number = 0; - /** The length in bytes of an AEAD authentication tag (Nt). */ - public readonly aeadTagSize: number = 0; - private _api: SubtleCrypto | undefined = undefined; private _kem: KemInterface; private _kdf: KdfInterface; + private _aead: AeadInterface; private _suiteId: Uint8Array; /** @@ -77,6 +74,7 @@ export class CipherSuite { * @throws {@link InvalidParamError} */ constructor(params: CipherSuiteParams) { + // KEM if (typeof params.kem !== "number") { this._kem = params.kem; } else { @@ -96,15 +94,15 @@ export class CipherSuite { case KemId.DhkemX25519HkdfSha256: this._kem = new DhkemX25519HkdfSha256(); break; - case KemId.DhkemX448HkdfSha512: + default: + // case KemId.DhkemX448HkdfSha512: this._kem = new DhkemX448HkdfSha512(); break; - default: - throw new errors.InvalidParamError("Invalid KEM id"); } } this.kem = this._kem.id; + // KDF if (typeof params.kdf !== "number") { this._kdf = params.kdf; } else { @@ -115,37 +113,36 @@ export class CipherSuite { case KdfId.HkdfSha384: this._kdf = new HkdfSha384(); break; - case KdfId.HkdfSha512: + default: + // case KdfId.HkdfSha512: this._kdf = new HkdfSha512(); break; - default: - throw new errors.InvalidParamError("Invalid KDF id"); } } this.kdf = this._kdf.id; - switch (params.aead) { - case AeadId.Aes128Gcm: - this.aeadKeySize = 16; - this.aeadNonceSize = 12; - this.aeadTagSize = 16; - break; - case AeadId.Aes256Gcm: - this.aeadKeySize = 32; - this.aeadNonceSize = 12; - this.aeadTagSize = 16; - break; - case AeadId.Chacha20Poly1305: - this.aeadKeySize = 32; - this.aeadNonceSize = 12; - this.aeadTagSize = 16; - break; - case AeadId.ExportOnly: - break; - default: - throw new errors.InvalidParamError("Invalid AEAD id"); + // AEAD + if (typeof params.aead !== "number") { + this._aead = params.aead; + } else { + switch (params.aead) { + case AeadId.Aes128Gcm: + this._aead = new Aes128Gcm(); + break; + case AeadId.Aes256Gcm: + this._aead = new Aes256Gcm(); + break; + case AeadId.Chacha20Poly1305: + this._aead = new Chacha20Poly1305(); + break; + default: + // case AeadId.ExportOnly: + this._aead = new ExportOnly(); + break; + } } - this.aead = params.aead; + this.aead = this._aead.id; + this._suiteId = new Uint8Array(consts.SUITE_ID_HEADER_HPKE); this._suiteId.set(i2Osp(this.kem, 2), 4); this._suiteId.set(i2Osp(this.kdf, 2), 6); @@ -180,26 +177,26 @@ export class CipherSuite { return this._kem.privateKeySize; } - // /** - // * The length in bytes of an AEAD key (Nk). - // */ - // public get aeadKeySize() { - // return this._aead.keySize; - // } + /** + * The length in bytes of an AEAD key (Nk). + */ + public get aeadKeySize() { + return this._aead.keySize; + } - // /** - // * The length in bytes of an AEAD nonce (Nn). - // */ - // public get aeadNonceSize() { - // return this._aead.nonceSize; - // } + /** + * The length in bytes of an AEAD nonce (Nn). + */ + public get aeadNonceSize() { + return this._aead.nonceSize; + } - // /** - // * The length in bytes of an AEAD authentication tag (Nt). - // */ - // public get aeadTagSize() { - // return this._aead.tagSize; - // } + /** + * The length in bytes of an AEAD authentication tag (Nt). + */ + public get aeadTagSize() { + return this._aead.tagSize; + } /** * Gets a suite-specific KEM context. @@ -230,9 +227,7 @@ export class CipherSuite { */ public async createAeadKey(key: ArrayBuffer): Promise { await this.setup(); - const ret = createAeadKey(this.aead, key); - ret.init(this._api as SubtleCrypto); - return ret; + return this._aead.createAeadKey(key); } /** @@ -392,6 +387,7 @@ export class CipherSuite { const api = await loadSubtleCrypto(); this._kem.init(api as SubtleCrypto); this._kdf.init(api as SubtleCrypto, this._suiteId); + this._aead.init(api as SubtleCrypto); this._api = api; return; } @@ -463,36 +459,36 @@ export class CipherSuite { this._kdf.hashSize, ); - if (this.aead === AeadId.ExportOnly) { - return { aead: this.aead, exporterSecret: exporterSecret }; + if (this._aead.id === AeadId.ExportOnly) { + return { aead: this._aead, exporterSecret: exporterSecret }; } const keyInfo = this._kdf.buildLabeledInfo( consts.LABEL_KEY, keyScheduleContext, - this.aeadKeySize, + this._aead.keySize, ); const key = await this._kdf.extractAndExpand( sharedSecret, ikm, keyInfo, - this.aeadKeySize, + this._aead.keySize, ); const baseNonceInfo = this._kdf.buildLabeledInfo( consts.LABEL_BASE_NONCE, keyScheduleContext, - this.aeadNonceSize, + this._aead.nonceSize, ); const baseNonce = await this._kdf.extractAndExpand( sharedSecret, ikm, baseNonceInfo, - this.aeadNonceSize, + this._aead.nonceSize, ); return { - aead: this.aead, + aead: this._aead, exporterSecret: exporterSecret, key: key, baseNonce: new Uint8Array(baseNonce), diff --git a/src/encryptionContext.ts b/src/encryptionContext.ts index 3a79c4c05..908eb9d17 100644 --- a/src/encryptionContext.ts +++ b/src/encryptionContext.ts @@ -1,12 +1,9 @@ -import type { AeadKey } from "./interfaces/aeadKey.ts"; +import type { AeadInterface } from "./interfaces/aeadInterface.ts"; import type { AeadParams } from "./interfaces/aeadParams.ts"; import type { KeyInfo } from "./interfaces/keyInfo.ts"; import type { KdfInterface } from "./interfaces/kdfInterface.ts"; -import { Aes128GcmKey, Aes256GcmKey } from "./aeadKeys/aesGcmKey.ts"; -import { Chacha20Poly1305Key } from "./aeadKeys/chacha20Poly1305Key.ts"; import { ExporterContext } from "./exporterContext.ts"; -import { AeadId } from "./identifiers.ts"; import { i2Osp, xor } from "./utils/misc.ts"; import * as consts from "./consts.ts"; @@ -14,7 +11,7 @@ import * as errors from "./errors.ts"; export class EncryptionContext extends ExporterContext { // AEAD id. - protected _aead: AeadId; + protected _aead: AeadInterface; // The length in bytes of a key for the algorithm. protected _nK: number; // The length in bytes of a nonce for the algorithm. @@ -36,33 +33,11 @@ export class EncryptionContext extends ExporterContext { throw new Error("Required parameters are missing"); } this._aead = params.aead; - switch (this._aead) { - case AeadId.Aes128Gcm: - this._nK = 16; - this._nN = 12; - this._nT = 16; - break; - case AeadId.Aes256Gcm: - this._nK = 32; - this._nN = 12; - this._nT = 16; - break; - case AeadId.Chacha20Poly1305: - this._nK = 32; - this._nN = 12; - this._nT = 16; - break; - default: - // case AeadId.ExportOnly: - this._nK = 0; - this._nN = 0; - this._nT = 0; - break; - } - - const key = createAeadKey(this._aead, params.key); - key.init(this._api); + this._nK = this._aead.keySize; + this._nN = this._aead.nonceSize; + this._nT = this._aead.tagSize; + const key = this._aead.createAeadKey(params.key); this._f = { key: key, baseNonce: params.baseNonce, @@ -98,8 +73,7 @@ export class EncryptionContext extends ExporterContext { await this.export(nonceSeed, this._nN), ); const key = await this.export(keySeed, this._nK); - this._r.key = createAeadKey(this._aead, key); - this._r.key.init(this._api); + this._r.key = this._aead.createAeadKey(key); this._r.seq = 0; } catch (e: unknown) { this._r.baseNonce = consts.EMPTY; @@ -107,19 +81,3 @@ export class EncryptionContext extends ExporterContext { } } } - -export function createAeadKey( - aead: AeadId, - key: ArrayBuffer, -): AeadKey { - switch (aead) { - case AeadId.Aes128Gcm: - return new Aes128GcmKey(key); - case AeadId.Aes256Gcm: - return new Aes256GcmKey(key); - case AeadId.Chacha20Poly1305: - return new Chacha20Poly1305Key(key); - default: - throw new Error("Invalid or unsupported AEAD id"); - } -} diff --git a/src/interfaces/aeadInterface.ts b/src/interfaces/aeadInterface.ts new file mode 100644 index 000000000..58f073fc7 --- /dev/null +++ b/src/interfaces/aeadInterface.ts @@ -0,0 +1,27 @@ +import type { AeadKey } from "./aeadKey.ts"; + +import { AeadId } from "../identifiers.ts"; + +/** + * The AEAD interface. + */ +export interface AeadInterface { + /** The KDF identifier. */ + readonly id: AeadId; + /** The length in bytes of an AEAD key (Nk). */ + readonly keySize: number; + /** The length in bytes of an AEAD nonce (Nn). */ + readonly nonceSize: number; + /** The length in bytes of an AEAD authentication tag (Nt). */ + readonly tagSize: number; + + /** + * Initializes the key by setting the SubtleCrypto. + */ + init(api: SubtleCrypto): void; + + /** + * Creates an AeadKey which has seal/open operation. + */ + createAeadKey(key: ArrayBuffer): AeadKey; +} diff --git a/src/interfaces/aeadKey.ts b/src/interfaces/aeadKey.ts index d5a48ae46..8ce80385e 100644 --- a/src/interfaces/aeadKey.ts +++ b/src/interfaces/aeadKey.ts @@ -13,11 +13,6 @@ export interface AeadKey { /** The length in bytes of an AEAD authentication tag (Nt). */ readonly tagSize: number; - /** - * Initializes the key by setting the SubtleCrypto. - */ - init(api: SubtleCrypto): void; - /** * Encrypts data with initial vector and additional authenticated data. */ diff --git a/src/interfaces/aeadParams.ts b/src/interfaces/aeadParams.ts index cbc906ba4..6452a4d28 100644 --- a/src/interfaces/aeadParams.ts +++ b/src/interfaces/aeadParams.ts @@ -1,11 +1,11 @@ -import type { AeadId } from "../identifiers.ts"; +import type { AeadInterface } from "./aeadInterface.ts"; /** * The AEAD parameters for building a encryption context. */ export interface AeadParams { /** The Aead indentifier. */ - aead: AeadId; + aead: AeadInterface; /** A secret used for the secret export interface. */ exporterSecret: ArrayBuffer; diff --git a/src/interfaces/cipherSuiteParams.ts b/src/interfaces/cipherSuiteParams.ts index 43f45be1d..d1db7672f 100644 --- a/src/interfaces/cipherSuiteParams.ts +++ b/src/interfaces/cipherSuiteParams.ts @@ -1,4 +1,5 @@ import type { AeadId, KdfId, KemId } from "../identifiers.ts"; +import type { AeadInterface } from "./aeadInterface.ts"; import type { KdfInterface } from "./kdfInterface.ts"; import type { KemInterface } from "./kemInterface.ts"; @@ -13,5 +14,5 @@ export interface CipherSuiteParams { kdf: KdfId | KdfInterface; /** The AEAD (Authenticated Encryption with Addtional Data) identifier. */ - aead: AeadId; + aead: AeadId | AeadInterface; } diff --git a/test/cipherSuite.test.ts b/test/cipherSuite.test.ts index 4f56cadbc..969b233ae 100644 --- a/test/cipherSuite.test.ts +++ b/test/cipherSuite.test.ts @@ -37,6 +37,9 @@ describe("CipherSuite", () => { assertEquals(suite.kemEncSize, 32); assertEquals(suite.kemPublicKeySize, 32); assertEquals(suite.kemPrivateKeySize, 32); + assertEquals(suite.aeadKeySize, 16); + assertEquals(suite.aeadNonceSize, 12); + assertEquals(suite.aeadTagSize, 16); assertEquals(suite.kdf, KdfId.HkdfSha256); assertEquals(suite.kdf, 0x0001); assertEquals(suite.aead, AeadId.Aes128Gcm); @@ -54,7 +57,7 @@ describe("CipherSuite", () => { // RFC9180 A.2. describe("constructor with DhkemX25519HkdfSha256/HkdfSha256/ChaCha20Poly1305", () => { - it("should have ciphersuites", () => { + it("should have a correct ciphersuite", () => { const suite: CipherSuite = new CipherSuite({ kem: KemId.DhkemX25519HkdfSha256, kdf: KdfId.HkdfSha256, diff --git a/test/encryptionContext.test.ts b/test/encryptionContext.test.ts index ce52d974b..e285c7693 100644 --- a/test/encryptionContext.test.ts +++ b/test/encryptionContext.test.ts @@ -8,6 +8,8 @@ import { AeadId, KdfId, KemId } from "../src/identifiers.ts"; import { HkdfSha256 } from "../src/kdfs/hkdf.ts"; import { loadSubtleCrypto } from "../src/webCrypto.ts"; import { i2Osp } from "../src/utils/misc.ts"; +import { ExportOnly } from "../src/aeadKeys/exportOnly.ts"; +import { Aes128Gcm } from "../src/aeadKeys/aesGcmKey.ts"; import * as consts from "../src/consts.ts"; import * as errors from "../src/errors.ts"; @@ -33,7 +35,7 @@ describe("constructor", () => { const seq = 0; const params = { - aead: AeadId.Aes128Gcm, + aead: new Aes128Gcm(), nK: 16, nN: 12, nT: 16, @@ -44,6 +46,7 @@ describe("constructor", () => { }; // assert + params.aead.init(api); assertEquals(typeof new EncryptionContext(api, kdf, params), "object"); }); }); @@ -63,7 +66,7 @@ describe("constructor", () => { const seq = 0; const params = { - aead: AeadId.ExportOnly, // invalid + aead: new ExportOnly(), // invalid nK: 16, nN: 12, nT: 16, @@ -74,12 +77,13 @@ describe("constructor", () => { }; // assert + params.aead.init(api); assertThrows( () => { new EncryptionContext(api, kdf, params); }, Error, - "Invalid or unsupported AEAD id", + "NotSupportedError: createAeadKey() is not supported on ExportOnly", ); }); }); @@ -451,7 +455,7 @@ describe("createRecipientContext", () => { const kdf = new HkdfSha256(); kdf.init(api, suiteId); const params = { - aead: AeadId.Aes128Gcm, + aead: new Aes128Gcm(), nK: 16, nN: 12, nT: 16, @@ -533,7 +537,7 @@ describe("setupBidirectional", () => { const seq = 0; const params = { - aead: AeadId.Aes128Gcm, + aead: new Aes128Gcm(), nK: -1, // invalid nN: 12, nT: 16, @@ -544,6 +548,7 @@ describe("setupBidirectional", () => { }; const te = new TextEncoder(); + params.aead.init(api); const ec = new EncryptionContext(api, kdf, params); // assert @@ -569,7 +574,7 @@ describe("setupBidirectional", () => { const seq = 0; const params = { - aead: AeadId.Aes128Gcm, + aead: new Aes128Gcm(), nK: 16, nN: -1, // invalid nT: 16, @@ -580,6 +585,7 @@ describe("setupBidirectional", () => { }; const te = new TextEncoder(); + params.aead.init(api); const ec = new EncryptionContext(api, kdf, params); // assert