diff --git a/src/__tests__/JWE.test.ts b/src/__tests__/JWE.test.ts index 57cc5179..03d0b964 100644 --- a/src/__tests__/JWE.test.ts +++ b/src/__tests__/JWE.test.ts @@ -15,7 +15,7 @@ import { } from '../encryption/xc20pEncryption.js' import { base64ToBytes, decodeBase64url, encodeBase64url } from '../util.js' import { createX25519ECDH, ECDH } from '../encryption/ECDH.js' -import { x25519DecrypterWithA256KW, x25519EncrypterWithA256KW } from '../encryption/aesEncryption.js' +import { xc20pAnonDecrypterX25519WithA256KW, xc20pAnonEncrypterX25519WithA256KW } from '../encryption/aesEncryption.js' import { xc20pDirDecrypter, xc20pDirEncrypter } from '../encryption/xc20pDir' const u8a = { toString, fromString } @@ -107,7 +107,7 @@ describe('JWE', () => { async ({ key, cleartext, jwe }) => { expect.assertions(1) const receiverSecret = base64ToBytes(key) - const decrypter = x25519DecrypterWithA256KW(receiverSecret) + const decrypter = xc20pAnonDecrypterX25519WithA256KW(receiverSecret) const cleartextU8a = await decryptJWE(jwe as any, decrypter) expect(u8a.toString(cleartextU8a)).toEqual(cleartext) } @@ -201,8 +201,8 @@ describe('JWE', () => { secretkey = randomBytes(32) pubkey = generateKeyPairFromSeed(secretkey).publicKey cleartext = u8a.fromString('hello world') - encrypter = x25519EncrypterWithA256KW(pubkey) - decrypter = x25519DecrypterWithA256KW(secretkey) + encrypter = xc20pAnonEncrypterX25519WithA256KW(pubkey) + decrypter = xc20pAnonDecrypterX25519WithA256KW(secretkey) }) it('Creates with only ciphertext', async () => { diff --git a/src/encryption/X25519-ECDH-ES.ts b/src/encryption/X25519-ECDH-ES.ts index 166e3a45..abb6e977 100644 --- a/src/encryption/X25519-ECDH-ES.ts +++ b/src/encryption/X25519-ECDH-ES.ts @@ -27,7 +27,7 @@ export async function computeX25519EcdhEsKek(recipient: Recipient, receiverSecre export function createX25519EcdhEsKek( ephemeralKeyPair: EphemeralKeyPair | undefined, - publicKey: Uint8Array, + recipientPublicKey: Uint8Array, apv: string | undefined, alg: string ) { @@ -37,7 +37,7 @@ export function createX25519EcdhEsKek( ? generateKeyPairFromSeed(ephemeralKeyPair.secretKey) : generateKeyPair() const epk = { kty: 'OKP', crv, x: bytesToBase64url(ephemeral.publicKey) } - const sharedSecret = sharedKey(ephemeral.secretKey, publicKey) + const sharedSecret = sharedKey(ephemeral.secretKey, recipientPublicKey) // Key Encryption Key const consumerInfo = base64ToBytes(apv ?? '') const kek = concatKDF(sharedSecret, keyLen, alg, undefined, consumerInfo) diff --git a/src/encryption/aesEncryption.ts b/src/encryption/aesEncryption.ts index 85668aea..96d8ab86 100644 --- a/src/encryption/aesEncryption.ts +++ b/src/encryption/aesEncryption.ts @@ -2,11 +2,12 @@ import { randomBytes } from '@stablelib/random' import crypto from 'isomorphic-webcrypto' import { Decrypter, Encrypter, EncryptionResult, EphemeralKeyPair, ProtectedHeader, Recipient } from './JWE.js' import { base64ToBytes, bytesToBase64url } from '../util.js' -import { genX25519EphemeralKeyPair } from './xc20pEncryption.js' +import { AuthEncryptParams, genX25519EphemeralKeyPair } from './xc20pEncryption.js' import { ECDH } from './ECDH.js' import { KeyUnwrapper, KeyWrapper } from './KW.js' import { xc20pDirDecrypter, xc20pDirEncrypter } from './xc20pDir.js' import { computeX25519EcdhEsKek, createX25519EcdhEsKek } from './X25519-ECDH-ES.js' +import { computeX25519Ecdh1PUv3Kek, createX25519Ecdh1PUv3Kek } from './X25519-ECDH-1PU.js' /** * Creates a wrapper using AES-KW @@ -64,12 +65,16 @@ export async function a256KeyUnwrapper(wrappingKey: Uint8Array): Promise { - const { epk, kek } = createX25519EcdhEsKek(ephemeralKeyPair, publicKey, apv, alg) + const { epk, kek } = createX25519EcdhEsKek(ephemeralKeyPair, recipientPublicKey, apv, alg) const wrapper = await a256KeyWrapper(kek) const res = await wrapper.wrap(cek) const recipient: Recipient = { @@ -110,7 +115,7 @@ export function x25519EncrypterWithA256KW(publicKey: Uint8Array, kid?: string, a return { alg, enc, encrypt, encryptCek, genEpk: genX25519EphemeralKeyPair } } -export function x25519DecrypterWithA256KW(receiverSecret: Uint8Array | ECDH): Decrypter { +export function xc20pAnonDecrypterX25519WithA256KW(receiverSecret: Uint8Array | ECDH): Decrypter { const alg = 'ECDH-ES+A256KW' const enc = 'XC20P' @@ -133,3 +138,92 @@ export function x25519DecrypterWithA256KW(receiverSecret: Uint8Array | ECDH): De return { alg, enc, decrypt } } + +export function xc20pAuthEncrypterEcdh1PuV3x25519WithA256KW( + recipientPublicKey: Uint8Array, + senderSecret: Uint8Array | ECDH, + options: Partial = {} +): Encrypter { + const alg = 'ECDH-ES+A256KW' + const enc = 'XC20P' + + async function encryptCek(cek: Uint8Array, ephemeralKeyPair?: EphemeralKeyPair): Promise { + const { epk, kek } = await createX25519Ecdh1PUv3Kek( + ephemeralKeyPair, + recipientPublicKey, + senderSecret, + options.apu, + options.apv, + alg + ) + const wrapper = await a256KeyWrapper(kek) + const res = await wrapper.wrap(cek) + const recipient: Recipient = { + encrypted_key: bytesToBase64url(res.ciphertext), + header: {}, + } + if (res.iv) recipient.header.iv = bytesToBase64url(res.iv) + if (res.tag) recipient.header.tag = bytesToBase64url(res.tag) + if (options.kid) recipient.header.kid = options.kid + if (options.apu) recipient.header.apu = options.apu + if (options.apv) recipient.header.apv = options.apv + if (!ephemeralKeyPair) { + recipient.header.alg = alg + recipient.header.epk = epk + } + + return recipient + } + + async function encrypt( + cleartext: Uint8Array, + protectedHeader: ProtectedHeader = {}, + aad?: Uint8Array, + ephemeralKeyPair?: EphemeralKeyPair + ): Promise { + // we won't want alg to be set to dir from xc20pDirEncrypter + Object.assign(protectedHeader, { alg: undefined }) + // Content Encryption Key + const cek = randomBytes(32) + const recipient: Recipient = await encryptCek(cek, ephemeralKeyPair) + if (ephemeralKeyPair) { + protectedHeader.alg = alg + protectedHeader.epk = ephemeralKeyPair.publicKeyJWK + } + return { + ...(await xc20pDirEncrypter(cek).encrypt(cleartext, protectedHeader, aad)), + recipient, + cek, + } + } + + return { alg, enc, encrypt, encryptCek, genEpk: genX25519EphemeralKeyPair } +} + +export function xc20pAuthDecrypterEcdh1PuV3x25519WithA256KW( + recipientSecret: Uint8Array | ECDH, + senderPublicKey: Uint8Array +): Decrypter { + const alg = 'ECDH-1PU+A256KW' + const enc = 'XC20P' + + async function decrypt( + sealed: Uint8Array, + iv: Uint8Array, + aad?: Uint8Array, + recipient?: Recipient + ): Promise { + recipient = recipient + recipient = recipient + const kek = await computeX25519Ecdh1PUv3Kek(recipient, recipientSecret, senderPublicKey, alg) + if (!kek) return null + // Content Encryption Key + const unwrapper = await a256KeyUnwrapper(kek) + const cek = await unwrapper.unwrap(base64ToBytes(recipient.encrypted_key)) + if (cek === null) return null + + return xc20pDirDecrypter(cek).decrypt(sealed, iv, aad) + } + + return { alg, enc, decrypt } +} diff --git a/src/encryption/xc20pEncryption.ts b/src/encryption/xc20pEncryption.ts index 98df41d0..7738eb2f 100644 --- a/src/encryption/xc20pEncryption.ts +++ b/src/encryption/xc20pEncryption.ts @@ -196,6 +196,7 @@ export function xc20pAuthEncrypterEcdh1PuV3x25519WithXc20PkwV2( options: Partial = {} ): Encrypter { const alg = 'ECDH-1PU+XC20PKW' + const enc = 'XC20P' async function encryptCek(cek: Uint8Array, ephemeralKeyPair?: EphemeralKeyPair): Promise { const { epk, kek } = await createX25519Ecdh1PUv3Kek( @@ -247,7 +248,7 @@ export function xc20pAuthEncrypterEcdh1PuV3x25519WithXc20PkwV2( } } - return { alg, enc: 'XC20P', encrypt, encryptCek, genEpk: genX25519EphemeralKeyPair } + return { alg, enc, encrypt, encryptCek, genEpk: genX25519EphemeralKeyPair } } export async function resolveX25519Encrypters(dids: string[], resolver: Resolvable): Promise { @@ -301,6 +302,7 @@ export async function resolveX25519Encrypters(dids: string[], resolver: Resolvab export function x25519Decrypter(receiverSecret: Uint8Array | ECDH): Decrypter { const alg = 'ECDH-ES+XC20PKW' + const enc = 'XC20P' async function decrypt( sealed: Uint8Array, @@ -321,7 +323,7 @@ export function x25519Decrypter(receiverSecret: Uint8Array | ECDH): Decrypter { return xc20pDirDecrypter(cek).decrypt(sealed, iv, aad) } - return { alg, enc: 'XC20P', decrypt } + return { alg, enc, decrypt } } /** @@ -334,6 +336,7 @@ export function xc20pAuthDecrypterEcdh1PuV3x25519WithXc20PkwV2( senderPublicKey: Uint8Array ): Decrypter { const alg = 'ECDH-1PU+XC20PKW' + const enc = 'XC20P' async function decrypt( sealed: Uint8Array, @@ -353,5 +356,5 @@ export function xc20pAuthDecrypterEcdh1PuV3x25519WithXc20PkwV2( return xc20pDirDecrypter(cek).decrypt(sealed, iv, aad) } - return { alg, enc: 'XC20P', decrypt } + return { alg, enc, decrypt } }