1 | import type { Decrypter, Encrypter, EncryptionResult, ProtectedHeader } from './types.js'
|
2 | import { bytesToBase64url, encodeBase64url, stringToBytes } from '../util.js'
|
3 | import { xchacha20poly1305 } from '@noble/ciphers/chacha'
|
4 | import { randomBytes } from '@noble/hashes/utils'
|
5 |
|
6 | export function xc20pEncrypter(key: Uint8Array): (cleartext: Uint8Array, aad?: Uint8Array) => EncryptionResult {
|
7 | return (cleartext: Uint8Array, aad?: Uint8Array) => {
|
8 | const iv = randomBytes(24)
|
9 | const cipher = xchacha20poly1305(key, iv, aad)
|
10 | const sealed = cipher.encrypt(cleartext)
|
11 | return {
|
12 | ciphertext: sealed.subarray(0, sealed.length - 16),
|
13 | tag: sealed.subarray(sealed.length - 16),
|
14 | iv,
|
15 | }
|
16 | }
|
17 | }
|
18 |
|
19 | export function xc20pDirEncrypter(key: Uint8Array): Encrypter {
|
20 | const xc20pEncrypt = xc20pEncrypter(key)
|
21 | const enc = 'XC20P'
|
22 | const alg = 'dir'
|
23 |
|
24 | async function encrypt(
|
25 | cleartext: Uint8Array,
|
26 | protectedHeader: ProtectedHeader = {},
|
27 | aad?: Uint8Array
|
28 | ): Promise<EncryptionResult> {
|
29 | const protHeader = encodeBase64url(JSON.stringify(Object.assign({ alg }, protectedHeader, { enc })))
|
30 | const encodedAad = stringToBytes(aad ? `${protHeader}.${bytesToBase64url(aad)}` : protHeader)
|
31 | return {
|
32 | ...xc20pEncrypt(cleartext, encodedAad),
|
33 | protectedHeader: protHeader,
|
34 | }
|
35 | }
|
36 |
|
37 | return { alg, enc, encrypt }
|
38 | }
|
39 |
|
40 | export function xc20pDirDecrypter(key: Uint8Array): Decrypter {
|
41 | async function decrypt(sealed: Uint8Array, iv: Uint8Array, aad?: Uint8Array): Promise<Uint8Array | null> {
|
42 | try {
|
43 | return xchacha20poly1305(key, iv, aad).decrypt(sealed)
|
44 | } catch (error) {
|
45 | return null
|
46 | }
|
47 | }
|
48 |
|
49 | return { alg: 'dir', enc: 'XC20P', decrypt }
|
50 | }
|