Skip to content

Commit 9ecc06f

Browse files
authored
dhkem-x25519: refine directory structure. (#608)
1 parent aa357e9 commit 9ecc06f

File tree

4 files changed

+249
-251
lines changed

4 files changed

+249
-251
lines changed

packages/dhkem-x25519/mod.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
1-
export { DhkemX25519HkdfSha256 } from "./src/dhkemX25519.ts";
1+
export { DhkemX25519HkdfSha256, X25519 } from "./src/dhkemX25519.ts";
22
export { HkdfSha256 } from "./src/hkdfSha256.ts";
3-
export { X25519 } from "./src/x25519.ts";

packages/dhkem-x25519/src/dhkemX25519.ts

Lines changed: 248 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,253 @@
1-
import { Dhkem, KemId } from "@hpke/common";
1+
import type { DhkemPrimitives, KdfInterface } from "@hpke/common";
22

3+
import {
4+
base64UrlToBytes,
5+
DeriveKeyPairError,
6+
DeserializeError,
7+
Dhkem,
8+
EMPTY,
9+
KEM_USAGES,
10+
KemId,
11+
LABEL_DKP_PRK,
12+
LABEL_SK,
13+
NotSupportedError,
14+
SerializeError,
15+
XCryptoKey,
16+
} from "@hpke/common";
17+
18+
import { x25519 } from "./primitives/x25519.ts";
319
import { HkdfSha256 } from "./hkdfSha256.ts";
4-
import { X25519 } from "./x25519.ts";
20+
21+
const ALG_NAME = "X25519";
22+
23+
export class X25519 implements DhkemPrimitives {
24+
private _hkdf: KdfInterface;
25+
private _nPk: number;
26+
private _nSk: number;
27+
28+
constructor(hkdf: KdfInterface) {
29+
this._hkdf = hkdf;
30+
this._nPk = 32;
31+
this._nSk = 32;
32+
}
33+
34+
public async serializePublicKey(key: CryptoKey): Promise<ArrayBuffer> {
35+
try {
36+
return await this._serializePublicKey(key as XCryptoKey);
37+
} catch (e: unknown) {
38+
throw new SerializeError(e);
39+
}
40+
}
41+
42+
public async deserializePublicKey(key: ArrayBuffer): Promise<CryptoKey> {
43+
try {
44+
return await this._importRawKey(key, true);
45+
} catch (e: unknown) {
46+
throw new DeserializeError(e);
47+
}
48+
}
49+
50+
public async serializePrivateKey(key: CryptoKey): Promise<ArrayBuffer> {
51+
try {
52+
return await this._serializePrivateKey(key as XCryptoKey);
53+
} catch (e: unknown) {
54+
throw new SerializeError(e);
55+
}
56+
}
57+
58+
public async deserializePrivateKey(key: ArrayBuffer): Promise<CryptoKey> {
59+
try {
60+
return await this._importRawKey(key, false);
61+
} catch (e: unknown) {
62+
throw new DeserializeError(e);
63+
}
64+
}
65+
66+
public async importKey(
67+
format: "raw" | "jwk",
68+
key: ArrayBuffer | JsonWebKey,
69+
isPublic: boolean,
70+
): Promise<CryptoKey> {
71+
try {
72+
if (format === "raw") {
73+
return await this._importRawKey(key as ArrayBuffer, isPublic);
74+
}
75+
// jwk
76+
if (key instanceof ArrayBuffer) {
77+
throw new Error("Invalid jwk key format");
78+
}
79+
return await this._importJWK(key as JsonWebKey, isPublic);
80+
} catch (e: unknown) {
81+
throw new DeserializeError(e);
82+
}
83+
}
84+
85+
public async generateKeyPair(): Promise<CryptoKeyPair> {
86+
try {
87+
const rawSk = await x25519.utils.randomSecretKey();
88+
const sk = new XCryptoKey(ALG_NAME, rawSk, "private", KEM_USAGES);
89+
const pk = await this.derivePublicKey(sk);
90+
return { publicKey: pk, privateKey: sk };
91+
} catch (e: unknown) {
92+
throw new NotSupportedError(e);
93+
}
94+
}
95+
96+
public async deriveKeyPair(ikm: ArrayBuffer): Promise<CryptoKeyPair> {
97+
try {
98+
const dkpPrk = await this._hkdf.labeledExtract(
99+
EMPTY.buffer as ArrayBuffer,
100+
LABEL_DKP_PRK,
101+
new Uint8Array(ikm),
102+
);
103+
const rawSk = await this._hkdf.labeledExpand(
104+
dkpPrk,
105+
LABEL_SK,
106+
EMPTY,
107+
this._nSk,
108+
);
109+
const sk = new XCryptoKey(
110+
ALG_NAME,
111+
new Uint8Array(rawSk),
112+
"private",
113+
KEM_USAGES,
114+
);
115+
return {
116+
privateKey: sk,
117+
publicKey: await this.derivePublicKey(sk),
118+
};
119+
} catch (e: unknown) {
120+
throw new DeriveKeyPairError(e);
121+
}
122+
}
123+
124+
public async derivePublicKey(key: CryptoKey): Promise<CryptoKey> {
125+
try {
126+
return await this._derivePublicKey(key as XCryptoKey);
127+
} catch (e: unknown) {
128+
throw new DeserializeError(e);
129+
}
130+
}
131+
132+
public async dh(sk: CryptoKey, pk: CryptoKey): Promise<ArrayBuffer> {
133+
try {
134+
return await this._dh(sk as XCryptoKey, pk as XCryptoKey);
135+
} catch (e: unknown) {
136+
throw new SerializeError(e);
137+
}
138+
}
139+
140+
public async derive(sk: Uint8Array, pk: Uint8Array): Promise<Uint8Array> {
141+
try {
142+
return await this._derive(sk, pk);
143+
} catch (e: unknown) {
144+
throw new SerializeError(e);
145+
}
146+
}
147+
148+
private _serializePublicKey(k: XCryptoKey): Promise<ArrayBuffer> {
149+
return new Promise((resolve) => {
150+
resolve(k.key.buffer as ArrayBuffer);
151+
});
152+
}
153+
154+
private _serializePrivateKey(k: XCryptoKey): Promise<ArrayBuffer> {
155+
return new Promise((resolve) => {
156+
resolve(k.key.buffer as ArrayBuffer);
157+
});
158+
}
159+
160+
private _importRawKey(
161+
key: ArrayBuffer,
162+
isPublic: boolean,
163+
): Promise<CryptoKey> {
164+
return new Promise((resolve, reject) => {
165+
if (isPublic && key.byteLength !== this._nPk) {
166+
reject(new Error("Invalid length of the key"));
167+
}
168+
if (!isPublic && key.byteLength !== this._nSk) {
169+
reject(new Error("Invalid length of the key"));
170+
}
171+
resolve(
172+
new XCryptoKey(
173+
ALG_NAME,
174+
new Uint8Array(key),
175+
isPublic ? "public" : "private",
176+
isPublic ? [] : KEM_USAGES,
177+
),
178+
);
179+
});
180+
}
181+
182+
private _importJWK(key: JsonWebKey, isPublic: boolean): Promise<CryptoKey> {
183+
return new Promise((resolve, reject) => {
184+
if (typeof key.kty === "undefined" || key.kty !== "OKP") {
185+
reject(new Error(`Invalid kty: ${key.kty}`));
186+
}
187+
if (typeof key.crv === "undefined" || key.crv !== "X25519") {
188+
reject(new Error(`Invalid crv: ${key.crv}`));
189+
}
190+
if (isPublic) {
191+
if (typeof key.d !== "undefined") {
192+
reject(new Error("Invalid key: `d` should not be set"));
193+
}
194+
if (typeof key.x === "undefined") {
195+
reject(new Error("Invalid key: `x` not found"));
196+
}
197+
resolve(
198+
new XCryptoKey(
199+
ALG_NAME,
200+
base64UrlToBytes(key.x as string),
201+
"public",
202+
),
203+
);
204+
} else {
205+
if (typeof key.d !== "string") {
206+
reject(new Error("Invalid key: `d` not found"));
207+
}
208+
resolve(
209+
new XCryptoKey(
210+
ALG_NAME,
211+
base64UrlToBytes(key.d as string),
212+
"private",
213+
KEM_USAGES,
214+
),
215+
);
216+
}
217+
});
218+
}
219+
220+
private _derivePublicKey(k: XCryptoKey): Promise<CryptoKey> {
221+
return new Promise((resolve, reject) => {
222+
try {
223+
const pk = x25519.getPublicKey(k.key);
224+
resolve(new XCryptoKey(ALG_NAME, pk, "public"));
225+
} catch (e: unknown) {
226+
reject(e);
227+
}
228+
});
229+
}
230+
231+
private _dh(sk: XCryptoKey, pk: XCryptoKey): Promise<ArrayBuffer> {
232+
return new Promise((resolve, reject) => {
233+
try {
234+
resolve(x25519.getSharedSecret(sk.key, pk.key).buffer as ArrayBuffer);
235+
} catch (e: unknown) {
236+
reject(e);
237+
}
238+
});
239+
}
240+
241+
private _derive(sk: Uint8Array, pk: Uint8Array): Promise<Uint8Array> {
242+
return new Promise((resolve, reject) => {
243+
try {
244+
resolve(x25519.getSharedSecret(sk, pk));
245+
} catch (e: unknown) {
246+
reject(e);
247+
}
248+
});
249+
}
250+
}
5251

6252
/**
7253
* The DHKEM(X25519, HKDF-SHA256) for HPKE KEM implementing {@link KemInterface}.
File renamed without changes.

0 commit comments

Comments
 (0)