Skip to content

Commit

Permalink
Merge pull request #40 from dajiaji/add-support-for-x25519
Browse files Browse the repository at this point in the history
Add support for DHKEM(X25519, HKDF-SHA256).
  • Loading branch information
dajiaji authored May 21, 2022
2 parents 158a1df + 5587870 commit e7b4086
Show file tree
Hide file tree
Showing 14 changed files with 641 additions and 35 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ This library works on both web browsers and Node.js (<b>currently, Deno is not s
| DHKEM (P-256, HKDF-SHA256) ||| |
| DHKEM (P-384, HKDF-SHA384) ||| |
| DHKEM (P-521, HKDF-SHA512) ||| |
| DHKEM (X25519, HKDF-SHA256) | | | |
| DHKEM (X25519, HKDF-SHA256) | | | |
| DHKEM (X448, HKDF-SHA512) | | | |

### Key Derivation Functions (KDFs)
Expand Down
67 changes: 66 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
"url": "https://github.com/dajiaji/hpke-js/issues"
},
"dependencies": {
"@stablelib/chacha20poly1305": "^1.0.1"
"@stablelib/chacha20poly1305": "^1.0.1",
"@stablelib/x25519": "^1.0.2"
}
}
1 change: 1 addition & 0 deletions src/cipherSuite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export class CipherSuite {
case Kem.DhkemP256HkdfSha256:
case Kem.DhkemP384HkdfSha384:
case Kem.DhkemP521HkdfSha512:
case Kem.DhkemX25519HkdfSha256:
break;
default:
throw new errors.InvalidParamError('Invalid KEM id');
Expand Down
4 changes: 2 additions & 2 deletions src/identifiers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ export enum Kem {
DhkemP384HkdfSha384 = 0x0011,
/** DHKEM (P-521, HKDF-SHA512). */
DhkemP521HkdfSha512 = 0x0012,
// /** DHKEM (X25519, HKDF-SHA256) */
// DhkemX25519HkdfSha256 = 0x0020,
/** DHKEM (X25519, HKDF-SHA256) */
DhkemX25519HkdfSha256 = 0x0020,
// /** DHKEM (X448, HKDF-SHA512) */
// DhkemX448HkdfSha512 = 0x0021,
}
Expand Down
16 changes: 12 additions & 4 deletions src/kemContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { SenderContextParams } from './interfaces/senderContextParams';
import type { RecipientContextParams } from './interfaces/recipientContextParams';

import { Ec } from './kemPrimitives/ec';
import { X25519 } from './kemPrimitives/x25519';
import { Kem } from './identifiers';
import { KdfCommon } from './kdfCommon';
import { isCryptoKeyPair, i2Osp, concat, concat3 } from './utils/misc';
Expand All @@ -28,10 +29,13 @@ export class KemContext extends KdfCommon {
case Kem.DhkemP384HkdfSha384:
algHash = { name: 'HMAC', hash: 'SHA-384', length: 384 };
break;
default:
// case Kem.DhkemP521HkdfSha512:
case Kem.DhkemP521HkdfSha512:
algHash = { name: 'HMAC', hash: 'SHA-512', length: 512 };
break;
default:
// case Kem.DhkemX25519HkdfSha256:
algHash = { name: 'HMAC', hash: 'SHA-256', length: 256 };
break;
}
super(api, suiteId, algHash);

Expand All @@ -44,11 +48,15 @@ export class KemContext extends KdfCommon {
this._prim = new Ec(kem, this, this._api);
this._nSecret = 48;
break;
default:
// case Kem.DhkemP521HkdfSha512:
case Kem.DhkemP521HkdfSha512:
this._prim = new Ec(kem, this, this._api);
this._nSecret = 64;
break;
default:
// case Kem.DhkemX25519HkdfSha256:
this._prim = new X25519(this);
this._nSecret = 32;
break;
}
return;
}
Expand Down
4 changes: 1 addition & 3 deletions src/kemPrimitives/ec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ const PKCS8_ALG_ID_P_521 = new Uint8Array([

export class Ec implements KemPrimitives {

private _kem: Kem;
private _hkdf: KdfCommon;
private _api: SubtleCrypto;
private _alg: EcKeyGenParams;
Expand All @@ -43,10 +42,9 @@ export class Ec implements KemPrimitives {
private _pkcs8AlgId: Uint8Array;

constructor(kem: Kem, hkdf: KdfCommon, api: SubtleCrypto) {
this._kem = kem;
this._hkdf = hkdf;
this._api = api;
switch (this._kem) {
switch (kem) {
case Kem.DhkemP256HkdfSha256:
this._alg = { name: 'ECDH', namedCurve: 'P-256' };
this._nPk = 65;
Expand Down
108 changes: 108 additions & 0 deletions src/kemPrimitives/x25519.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { generateKeyPair, scalarMultBase, sharedKey } from '@stablelib/x25519';

import type { KemPrimitives } from '../interfaces/kemPrimitives';
import type { KdfCommon } from '../kdfCommon';

import { Kem } from '../identifiers';
import { i2Osp } from '../utils/misc';

import * as consts from '../consts';

export class XCryptoKey implements CryptoKey {

public readonly key: Uint8Array;
public readonly type: 'public' | 'private';
public readonly extractable: boolean = true;
public readonly algorithm: KeyAlgorithm = { name: 'X25519' };
public readonly usages: KeyUsage[] = consts.KEM_USAGES;

constructor(key: Uint8Array, type: 'public' | 'private') {
this.key = key;
this.type = type;
}
}

export class X25519 implements KemPrimitives {

private _hkdf: KdfCommon;
private _nPk: number;
private _nSk: number;

constructor(hkdf: KdfCommon) {
this._hkdf = hkdf;
this._nPk = 32;
this._nSk = 32;
}

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

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

public async derivePublicKey(key: CryptoKey): Promise<CryptoKey> {
return await this._derivePublicKey(key as XCryptoKey);
}

public async generateKeyPair(): Promise<CryptoKeyPair> {
return await this._generateKeyPair();
}

public async deriveKeyPair(ikm: ArrayBuffer): Promise<CryptoKeyPair> {
const dkpPrk = await this._hkdf.labeledExtract(consts.EMPTY, consts.LABEL_DKP_PRK, new Uint8Array(ikm));
const rawSk = await this._hkdf.labeledExpand(dkpPrk, consts.LABEL_SK, consts.EMPTY, this._nSk);
const sk = new XCryptoKey(new Uint8Array(rawSk), 'private');
return {
privateKey: sk,
publicKey: await this.derivePublicKey(sk),
};
}

public async dh(sk: CryptoKey, pk: CryptoKey): Promise<ArrayBuffer> {
return await this._dh(sk as XCryptoKey, pk as XCryptoKey);
}

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

private _deserializePublicKey(k: ArrayBuffer): Promise<CryptoKey> {
return new Promise((resolve, reject) => {
if (k.byteLength !== this._nPk) {
reject(new Error('invalid public key for the ciphersuite'));
} else {
resolve(new XCryptoKey(new Uint8Array(k), 'public'));
}
});
}

private _derivePublicKey(k: XCryptoKey): Promise<CryptoKey> {
return new Promise((resolve) => {
resolve(new XCryptoKey(scalarMultBase(k.key), 'public'));
});
}

private _generateKeyPair(): Promise<CryptoKeyPair> {
return new Promise((resolve) => {
const kp = generateKeyPair();
resolve({
publicKey: new XCryptoKey(kp.publicKey, 'public'),
privateKey: new XCryptoKey(kp.secretKey, 'private'),
});
});
}

private _dh(sk: XCryptoKey, pk: XCryptoKey): Promise<ArrayBuffer> {
return new Promise((resolve, reject) => {
try {
resolve(sharedKey(sk.key, pk.key));
} catch (e: unknown) {
reject(e);
}
});
}
}
Loading

0 comments on commit e7b4086

Please sign in to comment.