1 | import type {
|
2 | AuthEncryptParams,
|
3 | ContentEncrypter,
|
4 | ECDH,
|
5 | Encrypter,
|
6 | EncryptionResult,
|
7 | EphemeralKeyPair,
|
8 | KekCreator,
|
9 | KeyWrapper,
|
10 | ProtectedHeader,
|
11 | Recipient,
|
12 | } from './types.js'
|
13 | import { bytesToBase64url, genX25519EphemeralKeyPair } from '../util.js'
|
14 | import { randomBytes } from '@noble/hashes/utils'
|
15 |
|
16 | export function createFullEncrypter(
|
17 | recipientPublicKey: Uint8Array,
|
18 | senderSecret: Uint8Array | ECDH | undefined,
|
19 | options: Partial<AuthEncryptParams> = {},
|
20 | kekCreator: KekCreator,
|
21 | keyWrapper: KeyWrapper,
|
22 | contentEncrypter: ContentEncrypter
|
23 | ): Encrypter {
|
24 | async function encryptCek(cek: Uint8Array, ephemeralKeyPair?: EphemeralKeyPair): Promise<Recipient> {
|
25 | const { epk, kek } = await kekCreator.createKek(
|
26 | recipientPublicKey,
|
27 | senderSecret,
|
28 | `${kekCreator.alg}+${keyWrapper.alg}`,
|
29 | options.apu,
|
30 | options.apv,
|
31 | ephemeralKeyPair
|
32 | )
|
33 | const res = await keyWrapper.from(kek).wrap(cek)
|
34 | const recipient: Recipient = {
|
35 | encrypted_key: bytesToBase64url(res.ciphertext),
|
36 | header: {},
|
37 | }
|
38 | if (res.iv) recipient.header.iv = bytesToBase64url(res.iv)
|
39 | if (res.tag) recipient.header.tag = bytesToBase64url(res.tag)
|
40 | if (options.kid) recipient.header.kid = options.kid
|
41 | if (options.apu) recipient.header.apu = options.apu
|
42 | if (options.apv) recipient.header.apv = options.apv
|
43 | if (!ephemeralKeyPair) {
|
44 | recipient.header.alg = `${kekCreator.alg}+${keyWrapper.alg}`
|
45 | recipient.header.epk = epk
|
46 | }
|
47 |
|
48 | return recipient
|
49 | }
|
50 |
|
51 | async function encrypt(
|
52 | cleartext: Uint8Array,
|
53 | protectedHeader: ProtectedHeader = {},
|
54 | aad?: Uint8Array,
|
55 | ephemeralKeyPair?: EphemeralKeyPair
|
56 | ): Promise<EncryptionResult> {
|
57 |
|
58 | Object.assign(protectedHeader, { alg: undefined })
|
59 |
|
60 | const cek = randomBytes(32)
|
61 | const recipient: Recipient = await encryptCek(cek, ephemeralKeyPair)
|
62 |
|
63 | if (ephemeralKeyPair) {
|
64 | protectedHeader.alg = `${kekCreator.alg}+${keyWrapper.alg}`
|
65 | protectedHeader.epk = ephemeralKeyPair.publicKeyJWK
|
66 | }
|
67 | return {
|
68 | ...(await contentEncrypter.from(cek).encrypt(cleartext, protectedHeader, aad)),
|
69 | recipient,
|
70 | cek,
|
71 | }
|
72 | }
|
73 |
|
74 | return { alg: keyWrapper.alg, enc: contentEncrypter.enc, encrypt, encryptCek, genEpk: genX25519EphemeralKeyPair }
|
75 | }
|