did-jwt
Version:
Library for Signing and Verifying JWTs that use DIDs as issuers and JWEs that use DIDs as recipients
90 lines • 3.61 kB
JavaScript
import { base64ToBytes, bytesToBase64url, decodeBase64url, stringToBytes, toSealed } from '../util.js';
function validateJWE(jwe) {
if (!(jwe.protected && jwe.iv && jwe.ciphertext && jwe.tag)) {
throw new Error('bad_jwe: missing properties');
}
if (jwe.recipients) {
jwe.recipients.map((rec) => {
if (!(rec.header && rec.encrypted_key)) {
throw new Error('bad_jwe: malformed recipients');
}
});
}
}
function encodeJWE({ ciphertext, tag, iv, protectedHeader, recipient }, aad) {
const jwe = {
protected: protectedHeader,
iv: bytesToBase64url(iv ?? new Uint8Array(0)),
ciphertext: bytesToBase64url(ciphertext),
tag: bytesToBase64url(tag ?? new Uint8Array(0)),
};
if (aad)
jwe.aad = bytesToBase64url(aad);
if (recipient)
jwe.recipients = [recipient];
return jwe;
}
export async function createJWE(cleartext, encrypters, protectedHeader = {}, aad, useSingleEphemeralKey = false) {
if (encrypters[0].alg === 'dir') {
if (encrypters.length > 1)
throw new Error('not_supported: Can only do "dir" encryption to one key.');
const encryptionResult = await encrypters[0].encrypt(cleartext, protectedHeader, aad);
return encodeJWE(encryptionResult, aad);
}
else {
const tmpEnc = encrypters[0].enc;
if (!encrypters.reduce((acc, encrypter) => acc && encrypter.enc === tmpEnc, true)) {
throw new Error('invalid_argument: Incompatible encrypters passed');
}
let cek;
let jwe;
let epk;
if (useSingleEphemeralKey) {
epk = encrypters[0].genEpk?.();
const alg = encrypters[0].alg;
protectedHeader = { ...protectedHeader, alg, epk: epk?.publicKeyJWK };
}
for (const encrypter of encrypters) {
if (!cek) {
const encryptionResult = await encrypter.encrypt(cleartext, protectedHeader, aad, epk);
cek = encryptionResult.cek;
jwe = encodeJWE(encryptionResult, aad);
}
else {
const recipient = await encrypter.encryptCek?.(cek, epk);
if (recipient) {
jwe?.recipients?.push(recipient);
}
}
}
return jwe;
}
}
export async function decryptJWE(jwe, decrypter) {
validateJWE(jwe);
const protHeader = JSON.parse(decodeBase64url(jwe.protected));
if (protHeader.enc !== decrypter.enc)
throw new Error(`not_supported: Decrypter does not supported: '${protHeader.enc}'`);
const sealed = toSealed(jwe.ciphertext, jwe.tag);
const aad = stringToBytes(jwe.aad ? `${jwe.protected}.${jwe.aad}` : jwe.protected);
let cleartext = null;
if (protHeader.alg === 'dir' && decrypter.alg === 'dir') {
cleartext = await decrypter.decrypt(sealed, base64ToBytes(jwe.iv), aad);
}
else if (!jwe.recipients || jwe.recipients.length === 0) {
throw new Error('bad_jwe: missing recipients');
}
else {
for (let i = 0; !cleartext && i < jwe.recipients.length; i++) {
const recipient = jwe.recipients[i];
Object.assign(recipient.header, protHeader);
if (recipient.header.alg === decrypter.alg) {
cleartext = await decrypter.decrypt(sealed, base64ToBytes(jwe.iv), aad, recipient);
}
}
}
if (cleartext === null)
throw new Error('failure: Failed to decrypt');
return cleartext;
}
//# sourceMappingURL=JWE.js.map