this repo has no description
1/**
2 * DPoP proof generation for e2e tests
3 */
4
5import { base64UrlEncode, computeJwkThumbprint } from '../../src/pds.js';
6
7/**
8 * Generate an ES256 key pair for DPoP
9 * @returns {Promise<{privateKey: CryptoKey, publicKey: CryptoKey, jwk: object}>}
10 */
11export async function generateKeyPair() {
12 const keyPair = await crypto.subtle.generateKey(
13 { name: 'ECDSA', namedCurve: 'P-256' },
14 true,
15 ['sign', 'verify'],
16 );
17
18 const jwk = await crypto.subtle.exportKey('jwk', keyPair.publicKey);
19 const publicJwk = { kty: jwk.kty, crv: jwk.crv, x: jwk.x, y: jwk.y };
20
21 return {
22 privateKey: keyPair.privateKey,
23 publicKey: keyPair.publicKey,
24 jwk: publicJwk,
25 };
26}
27
28/**
29 * Create a DPoP proof JWT
30 * @param {object} params
31 * @param {CryptoKey} params.privateKey
32 * @param {object} params.jwk
33 * @param {string} params.method
34 * @param {string} params.url
35 * @param {string} [params.accessToken]
36 * @returns {Promise<string>}
37 */
38export async function createDpopProof({
39 privateKey,
40 jwk,
41 method,
42 url,
43 accessToken,
44}) {
45 const header = { typ: 'dpop+jwt', alg: 'ES256', jwk };
46 const payload = {
47 jti: base64UrlEncode(crypto.getRandomValues(new Uint8Array(16))),
48 htm: method,
49 htu: url,
50 iat: Math.floor(Date.now() / 1000),
51 };
52
53 if (accessToken) {
54 const hash = await crypto.subtle.digest(
55 'SHA-256',
56 new TextEncoder().encode(accessToken),
57 );
58 payload.ath = base64UrlEncode(new Uint8Array(hash));
59 }
60
61 const headerB64 = base64UrlEncode(
62 new TextEncoder().encode(JSON.stringify(header)),
63 );
64 const payloadB64 = base64UrlEncode(
65 new TextEncoder().encode(JSON.stringify(payload)),
66 );
67 const signingInput = `${headerB64}.${payloadB64}`;
68
69 const signature = await crypto.subtle.sign(
70 { name: 'ECDSA', hash: 'SHA-256' },
71 privateKey,
72 new TextEncoder().encode(signingInput),
73 );
74
75 return `${signingInput}.${base64UrlEncode(new Uint8Array(signature))}`;
76}
77
78/**
79 * DPoP client helper
80 */
81export class DpopClient {
82 #privateKey;
83 #jwk;
84 #jkt = null;
85
86 constructor(privateKey, jwk) {
87 this.#privateKey = privateKey;
88 this.#jwk = jwk;
89 }
90
91 static async create() {
92 const { privateKey, jwk } = await generateKeyPair();
93 return new DpopClient(privateKey, jwk);
94 }
95
96 async getJkt() {
97 if (!this.#jkt) this.#jkt = await computeJwkThumbprint(this.#jwk);
98 return this.#jkt;
99 }
100
101 getJwk() {
102 return this.#jwk;
103 }
104
105 async createProof(method, url, accessToken) {
106 return createDpopProof({
107 privateKey: this.#privateKey,
108 jwk: this.#jwk,
109 method,
110 url,
111 accessToken,
112 });
113 }
114}