From 2805d2d45c0e14ebb18c0b31d0783523746af907 Mon Sep 17 00:00:00 2001 From: Orie Steele Date: Sat, 2 Mar 2024 16:43:16 -0600 Subject: [PATCH] chore tests --- src/cose/encrypt/hpke/wrap.ts | 2 +- src/cose/encrypt/wrap.ts | 4 +- src/cose/key/convertJsonWebKeyToCoseKey.ts | 4 + src/cose/key/generate.ts | 4 + .../draft-jose-cose-hpke-cookbook.test.ts | 208 +++++++++++++++++- .../draft-jose-cose-hpke-cookbook/examples.md | 117 ++++++++++ test/jose-hpke/src/IntegratedEncryption.ts | 79 +++++-- test/jose-hpke/src/KeyEncryption.ts | 123 ++++++----- test/jose-hpke/src/keys.ts | 24 +- .../tests/IntegratedEncryption.test.ts | 8 +- test/jose-hpke/tests/KeyEncryption.test.ts | 18 +- test/jose-hpke/tests/cross.no.aad.test.ts | 2 +- test/jose-hpke/tests/cross.test.ts | 4 +- test/jose-hpke/tests/mixed.test.ts | 4 +- 14 files changed, 491 insertions(+), 110 deletions(-) create mode 100644 test/draft-jose-cose-hpke-cookbook/examples.md diff --git a/src/cose/encrypt/hpke/wrap.ts b/src/cose/encrypt/hpke/wrap.ts index 181a3af..240f618 100644 --- a/src/cose/encrypt/hpke/wrap.ts +++ b/src/cose/encrypt/hpke/wrap.ts @@ -22,7 +22,7 @@ export const encryptWrap = async (req: RequestWrapEncryption) => { const unprotectedHeader = req.unprotectedHeader; const encodedProtectedHeader = encode(req.protectedHeader) const cek = await aes.generateKey(alg); - const iv = await aes.getIv(alg); + const iv = toArrayBuffer(await aes.getIv(alg)); unprotectedHeader.set(5, iv); // set IV const senderRecipients = [] for (const recipient of req.recipients.keys) { diff --git a/src/cose/encrypt/wrap.ts b/src/cose/encrypt/wrap.ts index 1a1a260..3c3d02a 100644 --- a/src/cose/encrypt/wrap.ts +++ b/src/cose/encrypt/wrap.ts @@ -25,6 +25,7 @@ export const decrypt = async (req: RequestWrapDecryption) => { const receiverPrivateKeyJwk = req.recipients.keys.find((k) => { return k.kid === kid }) + if (receiverPrivateKeyJwk.alg === 'HPKE-Base-P256-SHA256-AES128GCM') { return hpke.decrypt.wrap(req) } @@ -33,6 +34,7 @@ export const decrypt = async (req: RequestWrapDecryption) => { } const decodedRecipientProtectedHeader = await decodeFirst(recipientProtectedHeader) const recipientAlgorithm = decodedRecipientProtectedHeader.get(Protected.Alg) + const epk = recipientUnprotectedHeader.get(Unprotected.Epk) // ensure the epk has the algorithm that is set in the protected header epk.set(Epk.Alg, recipientAlgorithm) @@ -89,7 +91,7 @@ export const encrypt = async (req: RequestWrapEncryption) => { const kek = await ecdh.deriveKey(protectedHeader, recipientProtectedHeader, recipientPublicKeyJwk, senderPrivateKeyJwk) const cek = await aes.generateKey(alg); - const iv = await aes.getIv(alg); + const iv = toArrayBuffer(await aes.getIv(alg)); unprotectedHeader.set(Unprotected.Iv, iv) let kwAlg = KeyWrap.A128KW if (keyAgreementWithKeyWrappingAlgorithm === KeyAgreementWithKeyWrap["ECDH-ES+A128KW"]) { diff --git a/src/cose/key/convertJsonWebKeyToCoseKey.ts b/src/cose/key/convertJsonWebKeyToCoseKey.ts index 0f959f1..124a00e 100644 --- a/src/cose/key/convertJsonWebKeyToCoseKey.ts +++ b/src/cose/key/convertJsonWebKeyToCoseKey.ts @@ -81,6 +81,10 @@ export const convertJsonWebKeyToCoseKey = async (jwk: PublicKeyJwk | SecretKe break } case 'alg': { + if (value === 'HPKE-Base-P256-SHA256-AES128GCM') { + coseKey.set(label, 35) + break + } if (foundCommonParam) { const foundAlgorithm = algorithms.find((param) => { return param.Name === value diff --git a/src/cose/key/generate.ts b/src/cose/key/generate.ts index b1a0356..0c7d24c 100644 --- a/src/cose/key/generate.ts +++ b/src/cose/key/generate.ts @@ -18,6 +18,7 @@ import { convertJsonWebKeyToCoseKey } from './convertJsonWebKeyToCoseKey' import { thumbprint } from "./thumbprint" import { formatJwk } from './formatJwk' +import { cbor } from "../.." export const generate = async (alg: CoseSignatureAlgorithms | CoseDirectEncryptionAlgorithms, contentType: PrivateKeyContentType = 'application/jwk+json'): Promise => { @@ -48,6 +49,9 @@ export const generate = async (alg: CoseSignatureAlgorithms | CoseDirectEncry const secretKeyCoseKey = await convertJsonWebKeyToCoseKey(secretKeyJwk) const coseKeyThumbprint = await thumbprint.calculateCoseKeyThumbprint(secretKeyCoseKey) secretKeyCoseKey.set(2, coseKeyThumbprint) + if (alg === 'HPKE-Base-P256-SHA256-AES128GCM') { + return new Uint8Array(cbor.encode(secretKeyCoseKey)) as T + } return secretKeyCoseKey as T } throw new Error('Unsupported content type.') diff --git a/test/draft-jose-cose-hpke-cookbook/draft-jose-cose-hpke-cookbook.test.ts b/test/draft-jose-cose-hpke-cookbook/draft-jose-cose-hpke-cookbook.test.ts index 8f6ce1c..4480794 100644 --- a/test/draft-jose-cose-hpke-cookbook/draft-jose-cose-hpke-cookbook.test.ts +++ b/test/draft-jose-cose-hpke-cookbook/draft-jose-cose-hpke-cookbook.test.ts @@ -3,7 +3,211 @@ import * as jose from '../jose-hpke' import * as cose from '../../src' +import fs from 'fs' +import { publicFromPrivate } from '../../src/cose/key' + +let examples = `` + +let privateKeyJwk: any +let publicKeyJwk: any + +let privateKeyCose: any +let publicKeyCose: any + it('generate private keys', async () => { - const k1 = await jose.key.generate('HPKE-Base-P256-SHA256-AES128GCM') - const k2 = await cose.key.generate('HPKE-Base-P256-SHA256-AES128GCM', 'application/cose-key') + privateKeyJwk = await jose.key.generate('HPKE-Base-P256-SHA256-AES128GCM') + const k1 = Buffer.from(JSON.stringify(privateKeyJwk, null, 2)) + publicKeyJwk = publicFromPrivate(privateKeyJwk) + const k1c = await cose.key.convertJsonWebKeyToCoseKey>(JSON.parse(k1.toString())) + k1c.set(2, await cose.key.thumbprint.calculateCoseKeyThumbprintUri(k1c)) + privateKeyCose = k1c + publicKeyCose = publicFromPrivate(privateKeyCose) + const k2 = await cose.cbor.encode(k1c) + examples += ` + +## Private Key + +### application/jwk+json + +~~~ +${k1.toString('hex')} +~~~ +{: #private-key-jwk-hex align="left" title="JSON Web Key Bytes"} + +~~~json +${k1.toString()} +~~~ +{: #private-key-jwk align="left" title="JSON Web Key"} + +### application/cose-key + +~~~ +${Buffer.from(k2).toString('hex')} +~~~ +{: #private-key-cose-key align="left" title="COSE Key Bytes"} + +~~~ cbor-diag +${(await cose.cbor.diagnose(k2)).trim()} +~~~ +{: #private-key-cose-key align="left" title="COSE Key Diagnostic"} + + `.trim() + '\n\n' +}) + +it('direct encryption', async () => { + const messsageText = `⌛ My lungs taste the air of Time Blown past falling sands ⌛` + const aadText = `✨ It’s a dangerous business, Frodo, going out your door. ✨` + const plaintext = new TextEncoder().encode(messsageText) + const aad = new TextEncoder().encode(aadText) + + const jwe = await jose.IntegratedEncryption.encrypt(plaintext, publicKeyJwk, aad, { serialization: 'GeneralJson' }) + const decryptedJwe = await jose.IntegratedEncryption.decrypt(jwe, privateKeyJwk, { serialization: 'GeneralJson' }) + expect(new TextDecoder().decode(decryptedJwe.plaintext)).toBe(messsageText) + expect(new TextDecoder().decode(decryptedJwe.additionalAuthenticatedData)).toBe(aadText) + + const ct = await cose.encrypt.direct({ + protectedHeader: cose.ProtectedHeader([ + [cose.Protected.Alg, cose.Direct['HPKE-Base-P256-SHA256-AES128GCM']], + ]), + aad, + plaintext, + recipients: { + keys: [{ ...publicKeyJwk, kid: publicKeyCose.get(2) }] + } + }) + const pt = await cose.decrypt.direct({ + ciphertext: ct, + aad, + recipients: { + keys: [{ ...privateKeyJwk, kid: publicKeyCose.get(2) }] + } + }) + expect(new TextDecoder().decode(pt)).toBe(messsageText) + examples += ` + +## Direct Encryption + +~~~ +${messsageText} +~~~ +{: #direct-encryption-message align="left" title="Direct Encryption Message"} + +~~~ +${aadText} +~~~ +{: #direct-encryption-addition-authenticated-data align="left" title="Direct Encryption AAD"} + +### application/jose+json + +~~~ +${Buffer.from(JSON.stringify(jwe, null, 2)).toString('hex')} +~~~ +{: #direct-ciphertext-jose-bytes align="left" title="Direct JOSE Bytes"} + +~~~json +${JSON.stringify(jwe, null, 2)} +~~~ +{: #direct-ciphertext-json align="left" title="Direct JOSE JSON"} + +### application/cose + +~~~ +${ct.toString('hex')} +~~~ +{: #direct-ciphertext-cose-bytes align="left" title="Direct COSE Bytes"} + +~~~ cbor-diag +${(await cose.cbor.diagnose(ct)).trim()} +~~~ +{: #direct-ciphertext-cose-diag align="left" title="Direct COSE Diagnostic"} + + `.trim() + '\n\n' }) + + +it('key encryption', async () => { + const messsageText = `⌛ My lungs taste the air of Time Blown past falling sands ⌛` + const aadText = `✨ It’s a dangerous business, Frodo, going out your door. ✨` + const plaintext = new TextEncoder().encode(messsageText) + const aad = new TextEncoder().encode(aadText) + const jwe = await jose.KeyEncryption.encrypt({ + protectedHeader: { enc: 'A128GCM' }, + additionalAuthenticatedData: aad, + plaintext, + recipients: { + keys: [publicKeyJwk] + } + }) + const decryptedJwe = await jose.KeyEncryption.decrypt({ + jwe, + recipients: { + keys: [privateKeyJwk] + } + }) + expect(new TextDecoder().decode(decryptedJwe.plaintext)).toBe(messsageText) + expect(new TextDecoder().decode(decryptedJwe.additionalAuthenticatedData)).toBe(aadText) + + const ct = await cose.encrypt.wrap({ + protectedHeader: cose.ProtectedHeader([ + [cose.Protected.Alg, cose.Aead.A128GCM], + ]), + aad, + plaintext, + recipients: { + keys: [{ ...publicKeyJwk, kid: publicKeyCose.get(2) }] + } + }) + + const pt = await cose.decrypt.wrap({ + ciphertext: ct, + aad, + recipients: { + keys: [{ ...privateKeyJwk, kid: publicKeyCose.get(2) }] + } + }) + expect(new TextDecoder().decode(pt)).toBe(messsageText) + examples += ` + +## Key Encryption + +~~~ +${messsageText} +~~~ +{: #key-encryption-message align="left" title="Key Encryption Message"} + +~~~ +${aadText} +~~~ +{: #key-encryption-addition-authenticated-data align="left" title="Key Encryption AAD"} + +### application/jose+json + +~~~ +${Buffer.from(JSON.stringify(jwe, null, 2)).toString('hex')} +~~~ +{: #wrap-ciphertext-jose-bytes align="left" title="Key Encryption JOSE Bytes"} + +~~~json +${JSON.stringify(jwe, null, 2)} +~~~ +{: #wrap-ciphertext-jose-json align="left" title="Key Encryption JOSE JSON"} + +### application/cose + +~~~ +${ct.toString('hex')} +~~~ +{: #wrap-ciphertext-cose-bytes align="left" title="Key Encryption COSE Bytes"} + +~~~ cbor-diag +${(await cose.cbor.diagnose(ct)).trim()} +~~~ +{: #wrap-ciphertext-cose-diag align="left" title="Key Encryption COSE Diagnostic"} + + `.trim() + '\n\n' +}) + + +afterAll(() => { + // fs.writeFileSync('./test/draft-jose-cose-hpke-cookbook/examples.md', examples) +}) \ No newline at end of file diff --git a/test/draft-jose-cose-hpke-cookbook/examples.md b/test/draft-jose-cose-hpke-cookbook/examples.md new file mode 100644 index 0000000..382241c --- /dev/null +++ b/test/draft-jose-cose-hpke-cookbook/examples.md @@ -0,0 +1,117 @@ +## Private Key + +### application/jwk+json + +~~~ +7b0a2020226b6964223a202275726e3a696574663a706172616d733a6f617574683a6a776b2d7468756d627072696e743a7368612d3235363a5a74467579756f396c47777a6b5a543072564d6345464e716e50425736546439326c50597a6a794b675659222c0a202022616c67223a202248504b452d426173652d503235362d5348413235362d41455331323847434d222c0a2020226b7479223a20224543222c0a202022637276223a2022502d323536222c0a20202278223a202255645241515142396d656e62734f4a574f52376c4e7573445379576931477a7034326e46684a4344574f6f222c0a20202279223a2022666250554b3739374d576e44517a433479746e6c342d784277785a6b7653696c5535664b5479584e734c49222c0a20202264223a202266637a5043694531353479574e5751737379587278425541787532577970566335357a3042393667593034220a7d +~~~ +{: #private-key-jwk-hex align="left" title="JSON Web Key Bytes"} + +~~~json +{ + "kid": "urn:ietf:params:oauth:jwk-thumbprint:sha-256:ZtFuyuo9lGwzkZT0rVMcEFNqnPBW6Td92lPYzjyKgVY", + "alg": "HPKE-Base-P256-SHA256-AES128GCM", + "kty": "EC", + "crv": "P-256", + "x": "UdRAQQB9menbsOJWOR7lNusDSyWi1Gzp42nFhJCDWOo", + "y": "fbPUK797MWnDQzC4ytnl4-xBwxZkvSilU5fKTyXNsLI", + "d": "fczPCiE154yWNWQssyXrxBUAxu2WypVc55z0B96gY04" +} +~~~ +{: #private-key-jwk align="left" title="JSON Web Key"} + +### application/cose-key + +~~~ +a702784d75726e3a696574663a706172616d733a6f617574683a636b743a7368612d3235363a76567a736e55563857535069616e714f68425a665038774b303357364d77697a4a496332486664473334490318230102200121582051d44041007d99e9dbb0e256391ee536eb034b25a2d46ce9e369c584908358ea2258207db3d42bbf7b3169c34330b8cad9e5e3ec41c31664bd28a55397ca4f25cdb0b22358207dcccf0a2135e78c9635642cb325ebc41500c6ed96ca955ce79cf407dea0634e +~~~ +{: #private-key-cose-key align="left" title="COSE Key Bytes"} + +~~~ cbor-diag +{2: "urn:ietf:params:oauth:ckt:sha-256:vVzsnUV8WSPianqOhBZfP8wK03W6MwizJIc2HfdG34I", 3: 35, 1: 2, -1: 1, -2: h'51d44041007d99e9dbb0e256391ee536eb034b25a2d46ce9e369c584908358ea', -3: h'7db3d42bbf7b3169c34330b8cad9e5e3ec41c31664bd28a55397ca4f25cdb0b2', -4: h'7dcccf0a2135e78c9635642cb325ebc41500c6ed96ca955ce79cf407dea0634e'} +~~~ +{: #private-key-cose-key align="left" title="COSE Key Diagnostic"} + +## Direct Encryption + +~~~ +⌛ My lungs taste the air of Time Blown past falling sands ⌛ +~~~ +{: #direct-encryption-message align="left" title="Direct Encryption Message"} + +~~~ +✨ It’s a dangerous business, Frodo, going out your door. ✨ +~~~ +{: #direct-encryption-addition-authenticated-data align="left" title="Direct Encryption AAD"} + +### application/jose+json + +~~~ +7b0a20202270726f746563746564223a202265794a68624763694f694a6b615849694c434a6c626d4d694f694a49554574464c554a6863325574554449314e693154534545794e545974515556544d54493452304e4e496977695a584272496a7037496d743065534936496b564c496977695a5773694f694a4354586479564652725556424d58315674526e5a6c65586c48565552785647307962324a6b4c5639775231423055336479596e526b4f4639785a315242636c5a495257647062563933566c70665532553563455a74526a6c5256486c7261486f336544677a4e6d6c42625855314e53314e5a306b69665830222c0a202022616164223a20223470796f49456c30346f435a6379426849475268626d646c636d39316379426964584e70626d567a63797767526e4a765a473873494764766157356e4947393164434235623356794947527662334975494f4b637141222c0a20202263697068657274657874223a20224a484d73652d4c694e4674566f5a675f62486865326561555149736d7562722d59446137595a686b446430615863614e3642425461592d54775830567659716c53774b5453436a727352687a6e7a784c467579527253546f70486664522d473052547050635342656541220a7d +~~~ +{: #direct-ciphertext-jose-bytes align="left" title="Direct JOSE Bytes"} + +~~~json +{ + "protected": "eyJhbGciOiJkaXIiLCJlbmMiOiJIUEtFLUJhc2UtUDI1Ni1TSEEyNTYtQUVTMTI4R0NNIiwiZXBrIjp7Imt0eSI6IkVLIiwiZWsiOiJCTXdyVFRrUVBMX1VtRnZleXlHVURxVG0yb2JkLV9wR1B0U3dyYnRkOF9xZ1RBclZIRWdpbV93VlpfU2U5cEZtRjlRVHlraHo3eDgzNmlBbXU1NS1NZ0kifX0", + "aad": "4pyoIEl04oCZcyBhIGRhbmdlcm91cyBidXNpbmVzcywgRnJvZG8sIGdvaW5nIG91dCB5b3VyIGRvb3IuIOKcqA", + "ciphertext": "JHMse-LiNFtVoZg_bHhe2eaUQIsmubr-YDa7YZhkDd0aXcaN6BBTaY-TwX0VvYqlSwKTSCjrsRhznzxLFuyRrSTopHfdR-G0RTpPcSBeeA" +} +~~~ +{: #direct-ciphertext-json align="left" title="Direct JOSE JSON"} + +### application/cose + +~~~ +d08344a1011823a204784d75726e3a696574663a706172616d733a6f617574683a636b743a7368612d3235363a76567a736e55563857535069616e714f68425a665038774b303357364d77697a4a4963324866644733344923584104d39c578ee72f030d145fc92e2088797d39fe94aa03f581ec0d9a63cea0dd2f2500e36c3c30ce4563279334a47cc1bdf476d5d0149ef63150a806971564702dd8584fe14133dadd5c6f92c53aa71eee1c786f92c1199079d75ecde135f15ace97c91dd7b80eafa0129c09f28eb7be3b2b6e1a39d6d17e260b7dad0d7456cd2b0c44374f18173820b80abda93b52ee146f8b +~~~ +{: #direct-ciphertext-cose-bytes align="left" title="Direct COSE Bytes"} + +~~~ cbor-diag +16([h'a1011823', {4: "urn:ietf:params:oauth:ckt:sha-256:vVzsnUV8WSPianqOhBZfP8wK03W6MwizJIc2HfdG34I", -4: h'04d39c578ee72f030d145fc92e2088797d39fe94aa03f581ec0d9a63cea0dd2f2500e36c3c30ce4563279334a47cc1bdf476d5d0149ef63150a806971564702dd8'}, h'e14133dadd5c6f92c53aa71eee1c786f92c1199079d75ecde135f15ace97c91dd7b80eafa0129c09f28eb7be3b2b6e1a39d6d17e260b7dad0d7456cd2b0c44374f18173820b80abda93b52ee146f8b']) +~~~ +{: #direct-ciphertext-cose-diag align="left" title="Direct COSE Diagnostic"} + +## Key Encryption + +~~~ +⌛ My lungs taste the air of Time Blown past falling sands ⌛ +~~~ +{: #key-encryption-message align="left" title="Key Encryption Message"} + +~~~ +✨ It’s a dangerous business, Frodo, going out your door. ✨ +~~~ +{: #key-encryption-addition-authenticated-data align="left" title="Key Encryption AAD"} + +### application/jose+json + +~~~ +7b0a20202270726f746563746564223a202265794a6c626d4d694f694a424d54493452304e4e496977695a584272496a7037496d743065534936496b564c496977695a5773694f694a43526d3947535870535a33567a646a4e714e335a6c5a7a426f55335268533049304c53316d53566b346155524f566b52305a58424e4e304574576b5270536b786c55465a35623055775556426c5830497755324978526e4a75636d7058616d524b576a5272656b6474513251744e465253587a5169665830222c0a202022656e637279707465645f6b6579223a2022393745355031505f356f714972436d4a4370637751536a4d785a733079495a55583757675a5977514b5a6b222c0a2020226976223a2022354f56524d5030684c52503348643147222c0a20202263697068657274657874223a20225041567a51644d587046476873763938494d687352724d6d3754785f6c666f42442d4d7253757538734972497254784f414e6755634830737a6c52615633525764363937514b7249616b6e727645476452687030222c0a202022746167223a20223552584b5963624e4f51756939397049755462625f77222c0a202022616164223a20223470796f49456c30346f435a6379426849475268626d646c636d39316379426964584e70626d567a63797767526e4a765a473873494764766157356e4947393164434235623356794947527662334975494f4b637141220a7d +~~~ +{: #wrap-ciphertext-jose-bytes align="left" title="Key Encryption JOSE Bytes"} + +~~~json +{ + "protected": "eyJlbmMiOiJBMTI4R0NNIiwiZXBrIjp7Imt0eSI6IkVLIiwiZWsiOiJCRm9GSXpSZ3VzdjNqN3ZlZzBoU3RhS0I0LS1mSVk4aUROVkR0ZXBNN0EtWkRpSkxlUFZ5b0UwUVBlX0IwU2IxRnJucmpXamRKWjRrekdtQ2QtNFRSXzQifX0", + "encrypted_key": "97E5P1P_5oqIrCmJCpcwQSjMxZs0yIZUX7WgZYwQKZk", + "iv": "5OVRMP0hLRP3Hd1G", + "ciphertext": "PAVzQdMXpFGhsv98IMhsRrMm7Tx_lfoBD-MrSuu8sIrIrTxOANgUcH0szlRaV3RWd697QKrIaknrvEGdRhp0", + "tag": "5RXKYcbNOQui99pIuTbb_w", + "aad": "4pyoIEl04oCZcyBhIGRhbmdlcm91cyBidXNpbmVzcywgRnJvZG8sIGdvaW5nIG91dCB5b3VyIGRvb3IuIOKcqA" +} +~~~ +{: #wrap-ciphertext-jose-json align="left" title="Key Encryption JOSE JSON"} + +### application/cose + +~~~ +d8608443a10101a105503f2ffb31b5937ad71f9c05703444135b584fcac6d2b9d3de828ee3f53315078efe646245a68e8c6ae77dac3518d168c131daee205eacee566db92637c5340899d1ac1035f5e379939f0681e08016f1cb3745e6cf1c4a9247788348d5e6448e8079818344a1011823a204784d75726e3a696574663a706172616d733a6f617574683a636b743a7368612d3235363a76567a736e55563857535069616e714f68425a665038774b303357364d77697a4a49633248666447333449235841047f1321836185571a5d49140b6b823bc40fbbc06365c6e0c0681dd2232ef66aa01b87fbe60829452dfa8c32e867ec1d553e2ded27b5320c9fac4972f7563d51665820927e9cf95a50e2e87244bc2ef923475df2463f8237a7a49c5e8469a58a4486d2 +~~~ +{: #wrap-ciphertext-cose-bytes align="left" title="Key Encryption COSE Bytes"} + +~~~ cbor-diag +96([h'a10101', {5: h'3f2ffb31b5937ad71f9c05703444135b'}, h'cac6d2b9d3de828ee3f53315078efe646245a68e8c6ae77dac3518d168c131daee205eacee566db92637c5340899d1ac1035f5e379939f0681e08016f1cb3745e6cf1c4a9247788348d5e6448e8079', [[h'a1011823', {4: "urn:ietf:params:oauth:ckt:sha-256:vVzsnUV8WSPianqOhBZfP8wK03W6MwizJIc2HfdG34I", -4: h'047f1321836185571a5d49140b6b823bc40fbbc06365c6e0c0681dd2232ef66aa01b87fbe60829452dfa8c32e867ec1d553e2ded27b5320c9fac4972f7563d5166'}, h'927e9cf95a50e2e87244bc2ef923475df2463f8237a7a49c5e8469a58a4486d2']]]) +~~~ +{: #wrap-ciphertext-cose-diag align="left" title="Key Encryption COSE Diagnostic"} + diff --git a/test/jose-hpke/src/IntegratedEncryption.ts b/test/jose-hpke/src/IntegratedEncryption.ts index c283179..1a93127 100644 --- a/test/jose-hpke/src/IntegratedEncryption.ts +++ b/test/jose-hpke/src/IntegratedEncryption.ts @@ -2,7 +2,17 @@ import { base64url } from "jose"; import { publicKeyFromJwk, suites, isKeyAlgorithmSupported, privateKeyFromJwk, JOSE_HPKE_ALG } from "./keys"; -export const encrypt = async (plaintext: Uint8Array, publicKeyJwk: any, options = { serialization: 'CompactSerialization'}): Promise => { + + +const createJWEAAD = (protectedHeader: string, aad?: Uint8Array) => { + let textAad = protectedHeader + if (aad && aad.length > 0) { + textAad += '.' + base64url.encode(aad) + } + return textAad +} + +export const encrypt = async (plaintext: Uint8Array, publicKeyJwk: any, aad = new Uint8Array(), options = { serialization: 'CompactSerialization' }): Promise => { if (!isKeyAlgorithmSupported(publicKeyJwk)) { throw new Error('Public key algorithm is not supported') } @@ -20,60 +30,83 @@ export const encrypt = async (plaintext: Uint8Array, publicKeyJwk: any, options "ek": encapsulatedKey } })) - const hpkeSealAad = new TextEncoder().encode(protectedHeader) + + const jweAad = createJWEAAD(protectedHeader, aad) + + const hpkeSealAad = new TextEncoder().encode(jweAad) const ciphertext = base64url.encode(new Uint8Array(await sender.seal(plaintext, hpkeSealAad))); + const [encodedProtectedHeader, encodedAAD] = jweAad.split('.') + // https://datatracker.ietf.org/doc/html/rfc7516#section-3.1 - const compact = `${protectedHeader}...${ciphertext}.` - if (options.serialization === 'CompactSerialization'){ + const compact = `${encodedProtectedHeader}...${ciphertext}.` + if (options.serialization === 'CompactSerialization') { + if (aad && aad.length > 2) { + throw new Error('CompactSerialization does not support JWE AAD') + } return compact } - if (options.serialization === 'GeneralJson'){ + if (options.serialization === 'GeneralJson') { const [protectedHeader, encryptedKey, initializationVector, ciphertext, tag] = compact.split('.') return JSON.parse(JSON.stringify({ protected: protectedHeader, - encrypted_key: encryptedKey.length === 0 ? undefined: encryptedKey, - iv: initializationVector.length === 0 ? undefined: initializationVector, - tag: tag.length === 0 ? undefined: tag, - ciphertext: ciphertext.length === 0 ? undefined: ciphertext + encrypted_key: encryptedKey.length === 0 ? undefined : encryptedKey, + iv: initializationVector.length === 0 ? undefined : initializationVector, + tag: tag.length === 0 ? undefined : tag, + aad: encodedAAD, + ciphertext: ciphertext.length === 0 ? undefined : ciphertext })) } throw new Error('Unsupported Serialization.') } -export const decrypt = async (jwe: string | any, privateKeyJwk: any, options = { serialization: 'CompactSerialization'}): Promise => { - if (typeof jwe === 'object' && options.serialization !== 'GeneralJson'){ +export const decrypt = async (jwe: string | any, privateKeyJwk: any, options = { serialization: 'CompactSerialization' }): Promise => { + if (typeof jwe === 'object' && options.serialization !== 'GeneralJson') { throw new Error('expected object for general json serialization decrypt.') } - let compact = '' - if (options.serialization === 'CompactSerialization'){ - if (typeof jwe !== 'string'){ + + let protectedHeader + let ciphertext + let aad; + let _blankEncKey + let _blankIv + let _blankTag + if (options.serialization === 'CompactSerialization') { + if (typeof jwe !== 'string') { throw new Error('expected string for compact serialization decrypt.') } - compact = jwe + + ([protectedHeader, _blankEncKey, _blankIv, ciphertext, _blankTag] = jwe.split('.')); } - if (options.serialization === 'GeneralJson'){ - if (typeof jwe !== 'object'){ + if (options.serialization === 'GeneralJson') { + if (typeof jwe !== 'object') { throw new Error('expected object for general json serialization decrypt.') } - compact = `${jwe.protected}...${jwe.ciphertext}.` + + ({ protected: protectedHeader, aad, ciphertext } = jwe) } if (!isKeyAlgorithmSupported(privateKeyJwk)) { throw new Error('Public key algorithm is not supported') } const suite = suites[privateKeyJwk.alg as JOSE_HPKE_ALG] - const [protectedHeader, _blankEncKey, _blankIv, ciphertext, _blankTag] = compact.split('.'); + const decodedProtectedHeader = JSON.parse(new TextDecoder().decode(base64url.decode(protectedHeader))) - if (decodedProtectedHeader.alg !== 'dir'){ + if (decodedProtectedHeader.alg !== 'dir') { throw new Error('Expected alg:dir for integrated encryption.') } - if (decodedProtectedHeader.enc !== privateKeyJwk.alg){ + if (decodedProtectedHeader.enc !== privateKeyJwk.alg) { throw new Error('Private key does not support this algorithm: ' + decodedProtectedHeader.enc) } const recipient = await suite.createRecipientContext({ recipientKey: await privateKeyFromJwk(privateKeyJwk), enc: base64url.decode(decodedProtectedHeader.epk.ek) }) - const hpkeOpenAad = new TextEncoder().encode(protectedHeader) + const additionalAuthenticatedData = aad ? base64url.decode(aad) : new Uint8Array() + const jweAad = createJWEAAD(protectedHeader, additionalAuthenticatedData) + const hpkeOpenAad = new TextEncoder().encode(jweAad) const plaintext = await recipient.open(base64url.decode(ciphertext), hpkeOpenAad) - return new Uint8Array(plaintext) + const result = { plaintext: new Uint8Array(plaintext), protectedHeader: decodedProtectedHeader } as any + if (aad && aad.length > 0) { + result.additionalAuthenticatedData = new Uint8Array(additionalAuthenticatedData) + } + return result } \ No newline at end of file diff --git a/test/jose-hpke/src/KeyEncryption.ts b/test/jose-hpke/src/KeyEncryption.ts index 414cfa7..de8561e 100644 --- a/test/jose-hpke/src/KeyEncryption.ts +++ b/test/jose-hpke/src/KeyEncryption.ts @@ -14,25 +14,25 @@ export type RequestGeneralEncrypt = { recipients: JWKS } -const sortJsonSerialization = (jwe: any)=> { +const sortJsonSerialization = (jwe: any) => { // https://datatracker.ietf.org/doc/html/rfc7516#section-3.2 - const { protected: protectedHeader, unprotected, header, encrypted_key, ciphertext, iv, aad, tag, recipients} = jwe + const { protected: protectedHeader, unprotected, header, encrypted_key, ciphertext, iv, aad, tag, recipients } = jwe return JSON.parse(JSON.stringify({ - protected: protectedHeader, + protected: protectedHeader, unprotected, header, encrypted_key, iv, - ciphertext, - tag, + ciphertext, + tag, aad, - recipients, + recipients, })) } const prepareAad = (protectedHeader: any, aad?: Uint8Array) => { let textAad = base64url.encode(JSON.stringify(protectedHeader)) - if (aad){ + if (aad) { textAad += '.' + base64url.encode(aad) } return textAad @@ -41,7 +41,7 @@ const prepareAad = (protectedHeader: any, aad?: Uint8Array) => { export const encrypt = async ( req: RequestGeneralEncrypt, - options = {serialization: 'GeneralJson'} + options = { serialization: 'GeneralJson' } ): Promise => { let jwe = {} as any; @@ -53,8 +53,8 @@ export const encrypt = async ( let jweAad = prepareAad(req.protectedHeader, req.additionalAuthenticatedData) - // generate a content encryption key for a content encryption algorithm - const contentEncryptionKey = crypto.randomBytes(16); // for A128GCM + // generate a content encryption key for a content encryption algorithm + const contentEncryptionKey = crypto.randomBytes(16); // for A128GCM for (const recipient of req.recipients.keys) { if (isKeyAlgorithmSupported(recipient)) { @@ -68,36 +68,36 @@ export const encrypt = async ( // prepare the add for the seal operation for the recipient // ensure the recipient must process the protected header // and understand the chosen "encyption algorithm" - - if (req.recipients.keys.length === 1){ - let newHeader = {...req.protectedHeader, epk: {kty: 'EK', ek: encapsulatedKey}} + + if (req.recipients.keys.length === 1) { + let newHeader = { ...req.protectedHeader, epk: { kty: 'EK', ek: encapsulatedKey } } jwe.protected = base64url.encode(JSON.stringify(newHeader)) jweAad = prepareAad(newHeader, req.additionalAuthenticatedData) } - + const hpkeSealAad = new TextEncoder().encode(jweAad) // encrypt the content encryption key to the recipient, // while binding the content encryption algorithm to the protected header const encrypted_key = base64url.encode(new Uint8Array(await sender.seal(contentEncryptionKey, hpkeSealAad))); jwe.encrypted_key = encrypted_key - if (req.recipients.keys.length !== 1){ + if (req.recipients.keys.length !== 1) { unprotectedHeader.recipients.push( { encrypted_key: encrypted_key, header: { kid: recipient.kid, alg: recipient.alg, - epk: {kty: 'EK', ek: encapsulatedKey} as any, + epk: { kty: 'EK', ek: encapsulatedKey } as any, } as any } ) } - + } else if (recipient.alg === 'ECDH-ES+A128KW') { // throw new Error('Mixed mode not supported') const ek = await jose.generateKeyPair(recipient.alg, { crv: recipient.crv, extractable: true }) const epk = await jose.exportJWK(ek.publicKey) - const sharedSecret = await mixed.deriveKey( recipient, await jose.exportJWK(ek.privateKey)) + const sharedSecret = await mixed.deriveKey(recipient, await jose.exportJWK(ek.privateKey)) const encrypted_key = mixed.wrap('A128KW', sharedSecret, contentEncryptionKey) unprotectedHeader.recipients.push({ encrypted_key: base64url.encode(encrypted_key), @@ -112,7 +112,7 @@ export const encrypt = async ( } } - + // generate an initialization vector for use with the content encryption key const initializationVector = crypto.getRandomValues(new Uint8Array(12)); // possibly wrong const iv = base64url.encode(initializationVector) @@ -136,9 +136,9 @@ export const encrypt = async ( jwe.tag = tag; // for each recipient public key, encrypt the content encryption key to the recipient public key // and add the result to the unprotected header recipients property - + jwe.recipients = unprotectedHeader.recipients - if (jwe.recipients.length === 0){ + if (jwe.recipients.length === 0) { jwe.recipients = undefined } @@ -146,12 +146,12 @@ export const encrypt = async ( jwe.aad = base64url.encode(req.additionalAuthenticatedData) } - const general = sortJsonSerialization(jwe); - if (options.serialization === 'GeneralJson'){ + const general = sortJsonSerialization(jwe); + if (options.serialization === 'GeneralJson') { return general } - if (options.serialization === 'Compact'){ - if (general.recipients !== undefined){ + if (options.serialization === 'Compact') { + if (general.recipients !== undefined) { throw new Error('Compact serialization does not support multiple recipients') } const compact = `${general.protected}.${general.encrypted_key}.${general.iv}.${general.ciphertext}.${general.tag}` @@ -163,48 +163,53 @@ export const encrypt = async ( export type RequestGeneralDecrypt = { jwe: string | any, // need types - privateKeys: JWKS + recipients: { + keys: any[] + } } -const produceDecryptionResult = async (protectedHeader: string, ciphertext: string, tag: string, iv: string, cek: Uint8Array, aad ?: string) => { +const produceDecryptionResult = async (protectedHeader: string, ciphertext: string, tag: string, iv: string, cek: Uint8Array, aad?: string) => { const ct = base64url.decode(ciphertext) const initializationVector = base64url.decode(iv); const parsedProtectedHeader = JSON.parse(new TextDecoder().decode(base64url.decode(protectedHeader))) let jweAad = protectedHeader - if (aad){ + if (aad) { jweAad += '.' + aad } + + const plaintext = await mixed.gcmDecrypt( - parsedProtectedHeader.enc, - cek, - ct, - initializationVector, - base64url.decode(tag), - new TextEncoder().encode(jweAad), + parsedProtectedHeader.enc, + cek, + ct, + initializationVector, + base64url.decode(tag), + new TextEncoder().encode(jweAad), ) const decryption = { plaintext: new Uint8Array(plaintext) } as any; decryption.protectedHeader = parsedProtectedHeader; - if (aad){ - decryption.aad = base64url.decode(aad); + if (aad) { + const additionalAuthenticatedData = new Uint8Array(base64url.decode(aad)) + decryption.additionalAuthenticatedData = additionalAuthenticatedData; } return decryption } -export const decrypt = async (req: RequestGeneralDecrypt, options = {serialization: 'GeneralJson'}): Promise => { +export const decrypt = async (req: RequestGeneralDecrypt, options = { serialization: 'GeneralJson' }): Promise => { let { protected: protectedHeader, recipients, iv, ciphertext, aad, tag } = {} as any let encrypted_key; - if (options.serialization === 'GeneralJson'){ - if (typeof req.jwe !== 'object'){ - throw new Error('GeneralJson decrypt requires jwe as object') + if (options.serialization === 'GeneralJson') { + if (typeof req.jwe !== 'object') { + throw new Error('GeneralJson decrypt requires jwe as object') } ({ protected: protectedHeader, encrypted_key, recipients, iv, ciphertext, aad, tag } = req.jwe); } - if (recipients === undefined && options.serialization !== 'Compact' && typeof req.jwe !== 'string'){ - if (req.privateKeys.keys.length !== 1){ + if (recipients === undefined && options.serialization !== 'Compact' && typeof req.jwe !== 'string') { + if (req.recipients.keys.length !== 1) { throw new Error('Expected single private key for single recipient general json') } const parsedProtectedHeader = JSON.parse(new TextDecoder().decode(base64url.decode(protectedHeader))) @@ -212,17 +217,17 @@ export const decrypt = async (req: RequestGeneralDecrypt, options = {serializati { encrypted_key, header: { - kid: req.privateKeys.keys[0].kid, - alg: req.privateKeys.keys[0].alg, + kid: req.recipients.keys[0].kid, + alg: req.recipients.keys[0].alg, epk: parsedProtectedHeader.epk } } ] } - if (options.serialization === 'Compact'){ - if (typeof req.jwe === 'object'){ - throw new Error('Compact decrypt requires jwe as string') + if (options.serialization === 'Compact') { + if (typeof req.jwe === 'object') { + throw new Error('Compact decrypt requires jwe as string') } ([protectedHeader, encrypted_key, iv, ciphertext, tag] = req.jwe.split('.')) const parsedProtectedHeader = JSON.parse(new TextDecoder().decode(base64url.decode(protectedHeader))) @@ -230,25 +235,25 @@ export const decrypt = async (req: RequestGeneralDecrypt, options = {serializati { encrypted_key, header: { - kid: req.privateKeys.keys[0].kid, - alg: req.privateKeys.keys[0].alg, + kid: req.recipients.keys[0].kid, + alg: req.recipients.keys[0].alg, epk: parsedProtectedHeader.epk } } ] } - + // find a recipient for which we have a private key - let matchingRecipient = undefined - let matchingPrivateKey = undefined - for (const privateKey of req.privateKeys.keys) { + let matchingRecipient = undefined as any + let matchingPrivateKey = undefined as any + for (const privateKey of req.recipients.keys) { const recipient = recipients.find((r: HPKERecipient) => { return r.header.kid === privateKey.kid }) if (recipient) { // we have a private key for this recipient matchingRecipient = recipient; - matchingPrivateKey = privateKey; + matchingPrivateKey = privateKey as any; break } } @@ -265,17 +270,21 @@ export const decrypt = async (req: RequestGeneralDecrypt, options = {serializati // selected the encapsulated_key for the recipient const { encrypted_key, header } = matchingRecipient; - const { epk: {ek: encapsulated_key} } = header + const { epk: { ek: encapsulated_key } } = header + const backupAlg = matchingPrivateKey.alg + delete matchingPrivateKey.alg // create the HPKE recipient const recipient = await suite.createRecipientContext({ recipientKey: await privateKeyFromJwk(matchingPrivateKey), enc: base64url.decode(encapsulated_key) }) + matchingPrivateKey.alg = backupAlg + // compute the additional data from the protected header let jweAad = protectedHeader - if (aad){ + if (aad) { jweAad += '.' + aad } const hpkeOpenAad = new TextEncoder().encode(jweAad) @@ -289,7 +298,7 @@ export const decrypt = async (req: RequestGeneralDecrypt, options = {serializati return produceDecryptionResult(protectedHeader, ciphertext, tag, iv, contentEncryptionKey, aad); } else if (matchingPrivateKey.alg === 'ECDH-ES+A128KW') { // compute the shared secret from the recipient - const sharedSecret = await mixed.deriveKey( matchingRecipient.header.epk, matchingPrivateKey) + const sharedSecret = await mixed.deriveKey(matchingRecipient.header.epk, matchingPrivateKey) const encryptedKey = jose.base64url.decode(matchingRecipient.encrypted_key) // unrwap the content encryption key const contentEncryptionKey = mixed.unwrap('A128KW', sharedSecret, encryptedKey) diff --git a/test/jose-hpke/src/keys.ts b/test/jose-hpke/src/keys.ts index ad33610..e5c92bd 100644 --- a/test/jose-hpke/src/keys.ts +++ b/test/jose-hpke/src/keys.ts @@ -8,7 +8,7 @@ import { AeadId, CipherSuite, KdfId, KemId } from "hpke-js"; export type JOSE_HPKE_ALG = `HPKE-Base-P256-SHA256-AES128GCM` | `HPKE-Base-P384-SHA256-AES128GCM` export type JWK = { - kid?:string + kid?: string alg?: string kty: string crv: string @@ -43,7 +43,7 @@ export const suites = { } export const isKeyAlgorithmSupported = (recipient: JWK) => { - const supported_alg = Object.keys(suites) as string [] + const supported_alg = Object.keys(suites) as string[] return supported_alg.includes(`${recipient.alg}`) } @@ -54,7 +54,7 @@ export const formatJWK = (jwk: any) => { })) } -export const publicFromPrivate = (privateKeyJwk: any) => { +export const publicFromPrivate = (privateKeyJwk: any) => { const { kid, alg, kty, crv, x, y, ...rest } = privateKeyJwk return { kid, alg, kty, crv, x, y @@ -75,7 +75,9 @@ export const publicKeyFromJwk = async (publicKeyJwk: any) => { return publicKey; } -export const privateKeyFromJwk = async (privateKeyJwk: any)=>{ +export const privateKeyFromJwk = async (privateKeyJwk: any) => { + + const privateKey = await crypto.subtle.importKey( 'jwk', privateKeyJwk, @@ -89,14 +91,15 @@ export const privateKeyFromJwk = async (privateKeyJwk: any)=>{ return privateKey } -export const generate = async (alg: JOSE_HPKE_ALG) => { - if (!suites[alg]){ +export const generate = async (alg: JOSE_HPKE_ALG, contentType?: string) => { + + if (!suites[alg]) { throw new Error('Algorithm not supported') } let kp; - if (alg.includes('P256')){ + if (alg.includes('P256')) { kp = await generateKeyPair('ECDH-ES+A256KW', { crv: 'P-256', extractable: true }) - } else if (alg.includes('P384')){ + } else if (alg.includes('P384')) { kp = await generateKeyPair('ECDH-ES+A256KW', { crv: 'P-384', extractable: true }) } else { throw new Error('Could not generate private key for ' + alg) @@ -104,5 +107,10 @@ export const generate = async (alg: JOSE_HPKE_ALG) => { const privateKeyJwk = await exportJWK(kp.privateKey); privateKeyJwk.kid = await calculateJwkThumbprintUri(privateKeyJwk) privateKeyJwk.alg = alg; + if (contentType === 'application/jwk+json') { + return new TextEncoder().encode(JSON.stringify(privateKeyJwk)) + } return formatJWK(privateKeyJwk) + + } \ No newline at end of file diff --git a/test/jose-hpke/tests/IntegratedEncryption.test.ts b/test/jose-hpke/tests/IntegratedEncryption.test.ts index ae8163c..259c13e 100644 --- a/test/jose-hpke/tests/IntegratedEncryption.test.ts +++ b/test/jose-hpke/tests/IntegratedEncryption.test.ts @@ -8,24 +8,24 @@ describe('encrypt / decrypt ', () => { const publicKeyJwk = await hpke.key.publicFromPrivate(privateKeyJwk) const message = `It’s a 💀 dangerous business 💀, Frodo, going out your door.` const plaintext = new TextEncoder().encode(message); - const jwe = await hpke.IntegratedEncryption.encrypt(plaintext, publicKeyJwk) + const jwe = await hpke.IntegratedEncryption.encrypt(plaintext, publicKeyJwk, new Uint8Array()) expect(jwe.split('.').length).toBe(5) // compact jwe is default const recovered = await hpke.IntegratedEncryption.decrypt(jwe, privateKeyJwk) - expect(new TextDecoder().decode(recovered)).toBe(message); + expect(new TextDecoder().decode(recovered.plaintext)).toBe(message); }) it('JSON', async () => { const privateKeyJwk = await hpke.key.generate('HPKE-Base-P256-SHA256-AES128GCM') const publicKeyJwk = await hpke.key.publicFromPrivate(privateKeyJwk) const message = `It’s a 💀 dangerous business 💀, Frodo, going out your door.` const plaintext = new TextEncoder().encode(message); - const jwe = await hpke.IntegratedEncryption.encrypt(plaintext, publicKeyJwk, { serialization: 'GeneralJson' }) + const jwe = await hpke.IntegratedEncryption.encrypt(plaintext, publicKeyJwk, new Uint8Array(), { serialization: 'GeneralJson' }) expect(jwe.protected).toBeDefined() expect(jwe.ciphertext).toBeDefined() expect(jwe.iv).toBeUndefined() expect(jwe.tag).toBeUndefined() expect(jwe.encrypted_key).toBeUndefined() const recovered = await hpke.IntegratedEncryption.decrypt(jwe, privateKeyJwk, { serialization: 'GeneralJson' }) - expect(new TextDecoder().decode(recovered)).toBe(message); + expect(new TextDecoder().decode(recovered.plaintext)).toBe(message); }) }) diff --git a/test/jose-hpke/tests/KeyEncryption.test.ts b/test/jose-hpke/tests/KeyEncryption.test.ts index a0313f9..b8f68ec 100644 --- a/test/jose-hpke/tests/KeyEncryption.test.ts +++ b/test/jose-hpke/tests/KeyEncryption.test.ts @@ -31,11 +31,11 @@ describe('KeyEncryption', () => { }, { serialization: 'GeneralJson' }); const privateKey = resolvePrivateKey(publicKey1.kid) const recipientPrivateKeys = { "keys": [privateKey] } - const decryption = await hpke.KeyEncryption.decrypt({ jwe, privateKeys: recipientPrivateKeys }, { serialization: 'GeneralJson' }) + const decryption = await hpke.KeyEncryption.decrypt({ jwe, recipients: recipientPrivateKeys }, { serialization: 'GeneralJson' }) expect(decryption.protectedHeader.epk.kty).toBe('EK') expect(decryption.protectedHeader.enc).toBe('A128GCM') expect(new TextDecoder().decode(decryption.plaintext)).toBe(`It’s a 💀 dangerous business 💀, Frodo, going out your door.`); - expect(new TextDecoder().decode(decryption.aad)).toBe('💀 aad'); + expect(new TextDecoder().decode(decryption.additionalAuthenticatedData)).toBe('💀 aad'); }) it('Single Recipient JSON (without aad)', async () => { @@ -63,11 +63,11 @@ describe('KeyEncryption', () => { }, { serialization: 'GeneralJson' }); const privateKey = resolvePrivateKey(publicKey1.kid) const recipientPrivateKeys = { "keys": [privateKey] } - const decryption = await hpke.KeyEncryption.decrypt({ jwe, privateKeys: recipientPrivateKeys }, { serialization: 'GeneralJson' }) + const decryption = await hpke.KeyEncryption.decrypt({ jwe, recipients: recipientPrivateKeys }, { serialization: 'GeneralJson' }) expect(decryption.protectedHeader.epk.kty).toBe('EK') expect(decryption.protectedHeader.enc).toBe('A128GCM') expect(new TextDecoder().decode(decryption.plaintext)).toBe(`It’s a 💀 dangerous business 💀, Frodo, going out your door.`); - expect(decryption.aad).toBeUndefined() + expect(decryption.additionalAuthenticatedData).toBeUndefined() }) // expect alg to be in protected header @@ -119,10 +119,10 @@ describe('KeyEncryption', () => { const privateKey = resolvePrivateKey(recipient.kid) // simulate having only one of the recipient private keys const recipientPrivateKeys = { "keys": [privateKey] } - const decryption = await hpke.KeyEncryption.decrypt({ jwe: ciphertext, privateKeys: recipientPrivateKeys }) + const decryption = await hpke.KeyEncryption.decrypt({ jwe: ciphertext, recipients: recipientPrivateKeys }) expect(new TextDecoder().decode(decryption.plaintext)).toBe(`It’s a 💀 dangerous business 💀, Frodo, going out your door.`); - expect(decryption.aad).toBeDefined() - expect(new TextDecoder().decode(decryption.aad)).toBe('💀 aad'); + expect(decryption.additionalAuthenticatedData).toBeDefined() + expect(new TextDecoder().decode(decryption.additionalAuthenticatedData)).toBe('💀 aad'); } @@ -159,9 +159,9 @@ describe('KeyEncryption', () => { const privateKey = resolvePrivateKey(publicKey1.kid) // simulate having only one of the recipient private keys const recipientPrivateKeys = { "keys": [privateKey] } - const decryption = await hpke.KeyEncryption.decrypt({ jwe, privateKeys: recipientPrivateKeys }, { serialization: 'Compact' }) + const decryption = await hpke.KeyEncryption.decrypt({ jwe, recipients: recipientPrivateKeys }, { serialization: 'Compact' }) expect(new TextDecoder().decode(decryption.plaintext)).toBe(`It’s a 💀 dangerous business 💀, Frodo, going out your door.`); - expect(decryption.aad).toBeUndefined() + expect(decryption.additionalAuthenticatedData).toBeUndefined() diff --git a/test/jose-hpke/tests/cross.no.aad.test.ts b/test/jose-hpke/tests/cross.no.aad.test.ts index 3a7f596..357bf2c 100644 --- a/test/jose-hpke/tests/cross.no.aad.test.ts +++ b/test/jose-hpke/tests/cross.no.aad.test.ts @@ -31,7 +31,7 @@ it('encrypt (theirs) / decrypt (ours)', async () => { // simulate having only one of the recipient private keys const recipientPrivateKeys = { "keys": [privateKeyJwk] } - const decryption = await hpke.KeyEncryption.decrypt({ jwe, privateKeys: recipientPrivateKeys }) + const decryption = await hpke.KeyEncryption.decrypt({ jwe, recipients: recipientPrivateKeys }) expect(new TextDecoder().decode(decryption.plaintext)).toBe(`✨ It’s a dangerous business, Frodo, going out your door. ✨`); expect(new TextDecoder().decode(decryption.aad)).toBe(''); }) diff --git a/test/jose-hpke/tests/cross.test.ts b/test/jose-hpke/tests/cross.test.ts index 4e965dd..8c5a535 100644 --- a/test/jose-hpke/tests/cross.test.ts +++ b/test/jose-hpke/tests/cross.test.ts @@ -32,9 +32,9 @@ it('encrypt (theirs) / decrypt (ours)', async () => { // simulate having only one of the recipient private keys const recipientPrivateKeys = { "keys": [privateKeyJwk] } - const decryption = await hpke.KeyEncryption.decrypt({ jwe, privateKeys: recipientPrivateKeys }) + const decryption = await hpke.KeyEncryption.decrypt({ jwe, recipients: recipientPrivateKeys }) expect(new TextDecoder().decode(decryption.plaintext)).toBe(`✨ It’s a dangerous business, Frodo, going out your door. ✨`); - expect(new TextDecoder().decode(decryption.aad)).toBe('💀 aad'); + expect(new TextDecoder().decode(decryption.additionalAuthenticatedData)).toBe('💀 aad'); }) it('encrypt (ours) / decrypt (theirs)', async () => { diff --git a/test/jose-hpke/tests/mixed.test.ts b/test/jose-hpke/tests/mixed.test.ts index 206a0a6..913d19f 100644 --- a/test/jose-hpke/tests/mixed.test.ts +++ b/test/jose-hpke/tests/mixed.test.ts @@ -41,9 +41,9 @@ it('encrypt / decrypt', async () => { const privateKey = resolvePrivateKey(recipient.kid) // simulate having only one of the recipient private keys const recipientPrivateKeys = { "keys": [privateKey] } - const decryption = await hpke.KeyEncryption.decrypt({ jwe, privateKeys: recipientPrivateKeys }) + const decryption = await hpke.KeyEncryption.decrypt({ jwe, recipients: recipientPrivateKeys }) expect(new TextDecoder().decode(decryption.plaintext)).toBe(`It’s a 💀 dangerous business 💀, Frodo, going out your door.`); - expect(new TextDecoder().decode(decryption.aad)).toBe('💀 aad'); + expect(new TextDecoder().decode(decryption.additionalAuthenticatedData)).toBe('💀 aad'); } }) \ No newline at end of file