Skip to content

Commit

Permalink
add "alg": "A256KW" support for JWEs
Browse files Browse the repository at this point in the history
  • Loading branch information
overheadhunter committed Mar 2, 2024
1 parent 42bbf89 commit 2d080ea
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 3 deletions.
40 changes: 37 additions & 3 deletions frontend/src/common/jwe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ export class ConcatKDF {
}

export type JWEHeader = {
readonly alg: 'ECDH-ES' | 'PBES2-HS512+A256KW',
readonly enc: 'A256GCM' | 'A128GCM',
readonly alg: 'ECDH-ES' | 'PBES2-HS512+A256KW' | 'A256KW',
readonly enc: 'A256GCM' | 'A128GCM', // A128GCM for testing only, as we use test vectors with 128 bit keys
readonly apu?: string,
readonly apv?: string,
readonly epk?: JsonWebKey,
Expand Down Expand Up @@ -97,7 +97,7 @@ export class JWEParser {
* @throws {UnwrapKeyError} if decryption failed (wrong password?)
*/
public async decryptPbes2(password: string): Promise<any> {
if (this.header.alg != 'PBES2-HS512+A256KW' || /* this.header.enc != 'A256GCM' || */ !this.header.p2s || !this.header.p2c) {
if (this.header.alg != 'PBES2-HS512+A256KW' || this.header.enc != 'A256GCM' || !this.header.p2s || !this.header.p2c) {
throw new Error('unsupported alg or enc');
}
const saltInput = base64url.parse(this.header.p2s, { loose: true });
Expand All @@ -110,6 +110,24 @@ export class JWEParser {
}
}

/**
* Decrypts the JWE, assuming alg == A256KW and enc == A256GCM.
* @param kek The key used to wrap the CEK
* @returns Decrypted payload
* @throws {UnwrapKeyError} if decryption failed (wrong kek?)
*/
public async decryptA256kw(kek: CryptoKey): Promise<any> {
if (this.header.alg != 'A256KW' || this.header.enc != 'A256GCM') {
throw new Error('unsupported alg or enc');
}
try {
const cek = crypto.subtle.unwrapKey('raw', this.encryptedKey, kek, 'AES-KW', { name: 'AES-GCM', length: 256 }, false, ['decrypt']);
return this.decrypt(await cek);
} catch (error) {
throw new UnwrapKeyError(error);
}
}

private async decrypt(cek: CryptoKey): Promise<any> {
const utf8enc = new TextEncoder();
const m = new Uint8Array(this.ciphertext.length + this.tag.length);
Expand Down Expand Up @@ -180,6 +198,22 @@ export class JWEBuilder {
return new JWEBuilder(header, encryptedKey, cek);
}

/**
* Prepares a new JWE using alg: A256KW and enc: A256GCM.
*
* @param kek The key used to wrap the CEK
* @returns A new JWEBuilder ready to encrypt the payload
*/
public static a256kw(kek: CryptoKey): JWEBuilder {
const header = (async () => <JWEHeader>{
alg: 'A256KW',
enc: 'A256GCM'
})();
const cek = crypto.subtle.generateKey({ name: 'AES-GCM', length: 256 }, true, ['encrypt']);
const encryptedKey = (async () => new Uint8Array(await crypto.subtle.wrapKey('raw', await cek, kek, 'AES-KW')))();
return new JWEBuilder(header, encryptedKey, cek);
}

/**
* Builds the JWE.
* @param payload Payload to be encrypted
Expand Down
23 changes: 23 additions & 0 deletions frontend/test/common/jwe.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,29 @@ describe('JWE', () => {
// TODO: add some more decrypt-only tests with JWE from 3rd party
});

describe('JWE using alg: A256KW', () => {
it('x = decrypt(encrypt(x, kek), kek)', async () => {
const kek = await crypto.subtle.generateKey({ name: 'AES-KW', length: 256 }, false, ['wrapKey', 'unwrapKey']);
const orig = { hello: 'world' };

const jwe = await JWEBuilder.a256kw(kek).encrypt(orig);

const decrypted = await JWEParser.parse(jwe).decryptA256kw(kek);
expect(decrypted).to.deep.eq(orig);
});

it('decrypt', async () => {
// JWE generated by https://dinochiesa.github.io/jwt/
const jwe = 'eyJhbGciOiJBMjU2S1ciLCJlbmMiOiJBMjU2R0NNIn0.JTSrGbw4XEKXYFTC7siTT7DIZUX2SogThcLKXgxe0FPK3Fi8ckjr9A.zQx0t4qoTVIc-h5f.cmqzZ-md3cvdTNH9FWbKOsw.DCdGhmdwjoYKIuNC5zgQJQ';
const rawKek = base64url.parse('y_uxz8iAtcOXlqMYpm2jASvDWokpCYMtwkthFSK6IF0', { loose: true });
const kek = await crypto.subtle.importKey('raw', rawKek, 'AES-KW', false, ['unwrapKey']);
const orig = { hello: 'world' };

const decrypted = await JWEParser.parse(jwe).decryptA256kw(kek);
expect(decrypted).to.deep.eq(orig);
});
});

describe('PBES2', () => {
/**
* Test vectors from https://www.rfc-editor.org/rfc/rfc7517#appendix-C.4
Expand Down

0 comments on commit 2d080ea

Please sign in to comment.