diff --git a/packages/jwt-service/src/agent/JwtService.ts b/packages/jwt-service/src/agent/JwtService.ts index c61c7083..68a41909 100644 --- a/packages/jwt-service/src/agent/JwtService.ts +++ b/packages/jwt-service/src/agent/JwtService.ts @@ -71,7 +71,7 @@ export class JwtService implements IAgentPlugin { try { debug(`JWE Encrypt: ${JSON.stringify(args, null, 2)}`) - const alg = jweAlg(args.alg) ?? jweAlg(protectedHeader.alg) ?? 'ECDH-ES' + let alg = jweAlg(args.alg) ?? jweAlg(protectedHeader.alg) ?? 'ECDH-ES' const enc = jweEnc(args.enc) ?? jweEnc(protectedHeader.enc) ?? 'A256GCM' const encJwks = recipientKey.jwks.length === 1 @@ -91,8 +91,8 @@ export class JwtService implements IAgentPlugin { const apu = apuVal ? u8a.fromString(apuVal, 'base64url') : undefined const apvVal = protectedHeader.apv ?? args.apv const apv = apvVal ? u8a.fromString(apvVal, 'base64url') : undefined - - const pubKey = await importJWK(jwkInfo.jwk) + + const pubKey = await importJWK(jwkInfo.jwk, !jwkInfo.jwk.alg ? alg : undefined) const encrypter = new CompactJwtEncrypter({ enc, alg, diff --git a/packages/jwt-service/src/functions/JWE.ts b/packages/jwt-service/src/functions/JWE.ts index 57ccf089..c880f9e9 100644 --- a/packages/jwt-service/src/functions/JWE.ts +++ b/packages/jwt-service/src/functions/JWE.ts @@ -26,6 +26,8 @@ export interface EncryptionResult { cek?: Uint8Array } +import {createHmac} from 'crypto'; + export const generateContentEncryptionKey = async ({ alg, randomSource = defaultRandomSource, @@ -192,13 +194,16 @@ export class CompactJwtEncrypter implements JweEncrypter { const protectedHeader = { ...jweProtectedHeader, alg: jweProtectedHeader.alg ?? this._alg, - enc: jweProtectedHeader.enc ?? this._enc, + enc: jweProtectedHeader.enc ?? this._enc } + if (!protectedHeader.alg || !protectedHeader.enc) { return Promise.reject(Error(`no 'alg' or 'enc' value set for the protected JWE header!`)) } + this.enc = protectedHeader.enc this.alg = protectedHeader.alg + if (payload.exp) { this.expirationTime = payload.exp } @@ -208,17 +213,20 @@ export class CompactJwtEncrypter implements JweEncrypter { if (payload.aud) { this.audience = payload.aud } + const encrypt = new jose.EncryptJWT(payload).setProtectedHeader({ ...protectedHeader, alg: this.alg, - enc: this.enc, + enc: this.enc }) + if (this._alg!.startsWith('ECDH')) { if (!this._keyManagementParams) { return Promise.reject(Error(`ECDH requires key management params`)) } encrypt.setKeyManagementParameters(this._keyManagementParams!) } + if (this.expirationTime !== undefined) { encrypt.setExpirationTime(this.expirationTime) } @@ -226,11 +234,30 @@ export class CompactJwtEncrypter implements JweEncrypter { if (this.issuer) { encrypt.setIssuer(this.issuer) } + if (this.audience) { encrypt.setAudience(this.audience) } - return await encrypt.encrypt(this.recipientKey) + + const jwt = await encrypt.encrypt(this.recipientKey) + + // Extract components from the compact JWT + // @ts-ignore + const [protectedPart, encryptedKey, ivB64, ciphertextB64, tagB64] = jwt.split('.') + + // Convert base64url components back to Uint8Arrays for HMAC verification + const iv = base64ToBytes(ivB64) + const ciphertext = base64ToBytes(ciphertextB64) + + // If this is a content encryption key, we can use it as the HMAC key + if (this.recipientKey instanceof Uint8Array) { + // Log the HMAC calculation + verifyHmac(iv, ciphertext, this.recipientKey) + } + + return jwt } + public static async decryptCompactJWT(jwt: string, key: KeyLike | Uint8Array, options?: JWTDecryptOptions) { return await jose.jwtDecrypt(jwt, key, options) @@ -357,3 +384,19 @@ export async function decryptJwe(jwe: JweJsonGeneral, decrypter: JweDecrypter): export function toWebCryptoCiphertext(ciphertext: string, tag: string): Uint8Array { return u8a.concat([base64ToBytes(ciphertext), base64ToBytes(tag)]) } + +function verifyHmac(iv: Uint8Array, ciphertext: Uint8Array, macKey: Uint8Array) { + const hmac = createHmac('sha256', macKey) + hmac.update(new Uint8Array([...iv, ...ciphertext])) + const computedHash = hmac.digest() + + console.log('Computed HMAC:', u8a.toString(computedHash, 'hex')) + + // Here, replace `expectedAuthTag` with the actual authentication tag from the JWE + const expectedAuthTag = new Uint8Array() // Placeholder, replace with actual value + if (u8a.equals(computedHash, expectedAuthTag)) { + console.log('HMAC verification succeeded!') + } else { + console.error('HMAC verification failed!') + } +}