did-jwt
Version:
Library for Signing and Verifying JWTs that use DIDs as issuers and JWEs that use DIDs as recipients
220 lines • 10.8 kB
JavaScript
import { base64ToBytes, extractPublicKeyBytes, isDefined, toSealed } from '../util.js';
import { xc20pDirDecrypter, xc20pDirEncrypter, xc20pEncrypter } from './xc20pDir.js';
import { computeX25519Ecdh1PUv3Kek, createX25519Ecdh1PUv3Kek } from './X25519-ECDH-1PU.js';
import { computeX25519EcdhEsKek, createX25519EcdhEsKek } from './X25519-ECDH-ES.js';
import { createFullEncrypter } from './createEncrypter.js';
/**
* @deprecated Use
* {@link xc20pAuthEncrypterEcdh1PuV3x25519WithXc20PkwV2 | xc20pAuthEncrypterEcdh1PuV3x25519WithXc20PkwV2() } instead
*/
export function createAuthEncrypter(recipientPublicKey, senderSecret, options = {}) {
return xc20pAuthEncrypterEcdh1PuV3x25519WithXc20PkwV2(recipientPublicKey, senderSecret, options);
}
/**
* @deprecated Use {@link xc20pAnonEncrypterEcdhESx25519WithXc20PkwV2 | xc20pAnonEncrypterEcdhESx25519WithXc20PkwV2() }
* instead
*/
export function createAnonEncrypter(publicKey, options = {}) {
return xc20pAnonEncrypterEcdhESx25519WithXc20PkwV2(publicKey, options);
}
/**
* @deprecated Use
* {@link xc20pAuthDecrypterEcdh1PuV3x25519WithXc20PkwV2 | xc20pAuthDecrypterEcdh1PuV3x25519WithXc20PkwV2() } instead
*/
export function createAuthDecrypter(recipientSecret, senderPublicKey) {
return xc20pAuthDecrypterEcdh1PuV3x25519WithXc20PkwV2(recipientSecret, senderPublicKey);
}
/**
* @deprecated Use {@link xc20pAnonDecrypterEcdhESx25519WithXc20PkwV2 | xc20pAnonDecrypterEcdhESx25519WithXc20PkwV2() }
* instead
*/
export function createAnonDecrypter(recipientSecret) {
return xc20pAnonDecrypterEcdhESx25519WithXc20PkwV2(recipientSecret);
}
function validateHeader(header) {
if (!(header && header.epk && header.iv && header.tag)) {
throw new Error('bad_jwe: malformed header');
}
return header;
}
const xc20pKeyWrapper = {
from: (wrappingKey) => {
const wrap = async (cek) => {
return xc20pEncrypter(wrappingKey)(cek);
};
return { wrap };
},
alg: 'XC20PKW',
};
/**
* @deprecated Use {@link xc20pAnonEncrypterEcdhESx25519WithXc20PkwV2 | xc20pAnonEncrypterEcdhESx25519WithXc20PkwV2() }
* instead
*/
export function x25519Encrypter(publicKey, kid, apv) {
return xc20pAnonEncrypterEcdhESx25519WithXc20PkwV2(publicKey, { kid, apv });
}
/**
* Recommended encrypter for anonymous encryption (i.e. no sender authentication).
* Uses {@link https://tools.ietf.org/html/draft-amringer-jose-chacha-02 | ECDH-ES+XC20PKW v2}.
*
* @param recipientPublicKey - the byte array representing the recipient public key
* @param options - {@link AnonEncryptParams} used to specify the recipient key ID (`kid`)
*
* @returns an {@link Encrypter} instance usable with {@link createJWE}
*
* NOTE: ECDH-ES+XC20PKW is a proposed draft in IETF and not a standard yet and
* is subject to change as new revisions or until the official CFRG specification is released.
*/
export function xc20pAnonEncrypterEcdhESx25519WithXc20PkwV2(recipientPublicKey, options = {}) {
return createFullEncrypter(recipientPublicKey, undefined, options, { createKek: createX25519EcdhEsKek, alg: 'ECDH-ES' }, xc20pKeyWrapper, { from: (cek) => xc20pDirEncrypter(cek), enc: 'XC20P' });
}
/**
* Recommended encrypter for authenticated encryption (i.e. sender authentication and requires
* sender private key to encrypt the data).
* Uses {@link https://tools.ietf.org/html/draft-madden-jose-ecdh-1pu-03 | ECDH-1PU v3 } and
* {@link https://tools.ietf.org/html/draft-amringer-jose-chacha-02 | XC20PKW v2 }.
*
* @param recipientPublicKey - the byte array representing the recipient public key
* @param senderSecret - either a Uint8Array representing the sender secret key or
* an ECDH function that wraps the key and can promise a shared secret given a public key
* @param options - {@link AuthEncryptParams} used to specify extra header parameters
*
* @returns an {@link Encrypter} instance usable with {@link createJWE}
*
* NOTE: ECDH-1PU and XC20PKW are proposed drafts in IETF and not a standard yet and
* are subject to change as new revisions or until the official CFRG specification are released.
*
* Implements ECDH-1PU+XC20PKW with XChaCha20Poly1305 based on the following specs:
* - {@link https://tools.ietf.org/html/draft-amringer-jose-chacha-02 | XC20PKW}
* - {@link https://tools.ietf.org/html/draft-madden-jose-ecdh-1pu-03 | ECDH-1PU}
*/
export function xc20pAuthEncrypterEcdh1PuV3x25519WithXc20PkwV2(recipientPublicKey, senderSecret, options = {}) {
return createFullEncrypter(recipientPublicKey, senderSecret, options, { createKek: createX25519Ecdh1PUv3Kek, alg: 'ECDH-1PU' }, xc20pKeyWrapper, { from: (cek) => xc20pDirEncrypter(cek), enc: 'XC20P' });
}
export async function resolveX25519Encrypters(dids, resolver) {
const encryptersForDID = async (did, resolved = []) => {
const { didResolutionMetadata, didDocument } = await resolver.resolve(did);
resolved.push(did);
if (didResolutionMetadata?.error || didDocument == null) {
throw new Error(`resolver_error: Could not resolve ${did}: ${didResolutionMetadata.error}, ${didResolutionMetadata.message}`);
}
let controllerEncrypters = [];
if (!didDocument.controller && !didDocument.keyAgreement) {
throw new Error(`no_suitable_keys: Could not find x25519 key for ${did}`);
}
if (didDocument.controller) {
let controllers = Array.isArray(didDocument.controller) ? didDocument.controller : [didDocument.controller];
controllers = controllers.filter((c) => !resolved.includes(c));
const encrypterPromises = controllers.map((did) => encryptersForDID(did, resolved).catch(() => {
return [];
}));
const encrypterArrays = await Promise.all(encrypterPromises);
controllerEncrypters = [].concat(...encrypterArrays);
}
const agreementKeys = didDocument.keyAgreement
?.map((key) => {
if (typeof key === 'string') {
return [...(didDocument.publicKey || []), ...(didDocument.verificationMethod || [])].find((pk) => pk.id === key);
}
return key;
})
?.filter((key) => typeof key !== 'undefined');
const pks = agreementKeys?.filter((key) => ['X25519KeyAgreementKey2019', 'X25519KeyAgreementKey2020', 'JsonWebKey2020', 'Multikey'].includes(key.type)) ?? [];
if (!pks.length && !controllerEncrypters.length)
throw new Error(`no_suitable_keys: Could not find X25519 key for ${did}`);
return pks
.map((pk) => {
const { keyBytes, keyType } = extractPublicKeyBytes(pk);
if (keyType === 'X25519') {
return x25519Encrypter(keyBytes, pk.id);
}
else {
return null;
}
})
.filter(isDefined)
.concat(...controllerEncrypters);
};
const encrypterPromises = dids.map((did) => encryptersForDID(did));
const encrypterArrays = await Promise.all(encrypterPromises);
return [].concat(...encrypterArrays);
}
/**
* @deprecated Use {@link xc20pAnonDecrypterEcdhESx25519WithXc20PkwV2 | xc20pAnonDecrypterEcdhESx25519WithXc20PkwV2() }
* instead
*/
export function x25519Decrypter(receiverSecret) {
return xc20pAnonDecrypterEcdhESx25519WithXc20PkwV2(receiverSecret);
}
/**
* Recommended decrypter for anonymous encryption (i.e. no sender authentication).
* Uses {@link https://tools.ietf.org/html/draft-amringer-jose-chacha-02 | ECDH-ES+XC20PKW v2 }.
*
* @param recipientSecret - either a Uint8Array representing the recipient secret key or
* an ECDH function that wraps the key and can promise a shared secret given a public key
*
* @returns a {@link Decrypter} instance usable with {@link decryptJWE}
*
* NOTE: ECDH-ES+XC20PKW is a proposed draft in IETF and not a standard yet and
* is subject to change as new revisions or until the official CFRG specification is released.
*
* @beta
*/
export function xc20pAnonDecrypterEcdhESx25519WithXc20PkwV2(recipientSecret) {
const alg = 'ECDH-ES+XC20PKW';
const enc = 'XC20P';
async function decrypt(sealed, iv, aad, recipient) {
recipient = recipient;
const header = validateHeader(recipient.header);
const kek = await computeX25519EcdhEsKek(recipient, recipientSecret, alg);
if (!kek)
return null;
// Content Encryption Key
const sealedCek = toSealed(recipient.encrypted_key, header.tag);
const cek = await xc20pDirDecrypter(kek).decrypt(sealedCek, base64ToBytes(header.iv));
if (cek === null)
return null;
return xc20pDirDecrypter(cek).decrypt(sealed, iv, aad);
}
return { alg, enc, decrypt };
}
/**
* Recommended decrypter for authenticated encryption (i.e. sender authentication and requires
* sender public key to decrypt the data).
* Uses {@link https://tools.ietf.org/html/draft-madden-jose-ecdh-1pu-03 | ECDH-1PU v3 } and
* {@link https://tools.ietf.org/html/draft-amringer-jose-chacha-02 | XC20PKW v2 }.
*
* @param recipientSecret - either a Uint8Array representing the recipient secret key or
* an ECDH function that wraps the key and can promise a shared secret given a public key
* @param senderPublicKey - the byte array representing the sender public key
*
* @returns a {@link Decrypter} instance usable with {@link decryptJWE}
*
* NOTE: ECDH-1PU and XC20PKW are proposed drafts in IETF and not a standard yet and
* are subject to change as new revisions or until the official CFRG specification are released.
*
* @beta
*
* Implements ECDH-1PU+XC20PKW with XChaCha20Poly1305 based on the following specs:
* - {@link https://tools.ietf.org/html/draft-amringer-jose-chacha-02 | XC20PKW}
* - {@link https://tools.ietf.org/html/draft-madden-jose-ecdh-1pu-03 | ECDH-1PU}
*/
export function xc20pAuthDecrypterEcdh1PuV3x25519WithXc20PkwV2(recipientSecret, senderPublicKey) {
const alg = 'ECDH-1PU+XC20PKW';
const enc = 'XC20P';
async function decrypt(sealed, iv, aad, recipient) {
recipient = recipient;
const header = validateHeader(recipient.header);
const kek = await computeX25519Ecdh1PUv3Kek(recipient, recipientSecret, senderPublicKey, alg);
if (!kek)
return null;
// Content Encryption Key
const sealedCek = toSealed(recipient.encrypted_key, header.tag);
const cek = await xc20pDirDecrypter(kek).decrypt(sealedCek, base64ToBytes(header.iv));
if (cek === null)
return null;
return xc20pDirDecrypter(cek).decrypt(sealed, iv, aad);
}
return { alg, enc, decrypt };
}
//# sourceMappingURL=xc20pEncryption.js.map