1 | const isObject = require('../help/is_object')
|
2 | let validateCrit = require('../help/validate_crit')
|
3 |
|
4 | const { JWEInvalid } = require('../errors')
|
5 |
|
6 | validateCrit = validateCrit.bind(undefined, JWEInvalid)
|
7 |
|
8 | const compactSerializer = (final, [recipient]) => {
|
9 | return `${final.protected}.${recipient.encrypted_key}.${final.iv}.${final.ciphertext}.${final.tag}`
|
10 | }
|
11 | compactSerializer.validate = (protectedHeader, unprotectedHeader, aad, { 0: { header }, length }) => {
|
12 | if (length !== 1 || aad || unprotectedHeader || header) {
|
13 | throw new JWEInvalid('JWE Compact Serialization doesn\'t support multiple recipients, JWE unprotected headers or AAD')
|
14 | }
|
15 | validateCrit(protectedHeader, unprotectedHeader, protectedHeader ? protectedHeader.crit : undefined)
|
16 | }
|
17 |
|
18 | const flattenedSerializer = (final, [recipient]) => {
|
19 | const { header, encrypted_key: encryptedKey } = recipient
|
20 |
|
21 | return {
|
22 | ...(final.protected ? { protected: final.protected } : undefined),
|
23 | ...(final.unprotected ? { unprotected: final.unprotected } : undefined),
|
24 | ...(header ? { header } : undefined),
|
25 | ...(encryptedKey ? { encrypted_key: encryptedKey } : undefined),
|
26 | ...(final.aad ? { aad: final.aad } : undefined),
|
27 | iv: final.iv,
|
28 | ciphertext: final.ciphertext,
|
29 | tag: final.tag
|
30 | }
|
31 | }
|
32 | flattenedSerializer.validate = (protectedHeader, unprotectedHeader, aad, { 0: { header }, length }) => {
|
33 | if (length !== 1) {
|
34 | throw new JWEInvalid('Flattened JWE JSON Serialization doesn\'t support multiple recipients')
|
35 | }
|
36 | validateCrit(protectedHeader, { ...unprotectedHeader, ...header }, protectedHeader ? protectedHeader.crit : undefined)
|
37 | }
|
38 |
|
39 | const generalSerializer = (final, recipients) => {
|
40 | const result = {
|
41 | ...(final.protected ? { protected: final.protected } : undefined),
|
42 | ...(final.unprotected ? { unprotected: final.unprotected } : undefined),
|
43 | recipients: recipients.map(({ header, encrypted_key: encryptedKey, generatedHeader }) => {
|
44 | if (!header && !encryptedKey && !generatedHeader) {
|
45 | return false
|
46 | }
|
47 |
|
48 | return {
|
49 | ...(header || generatedHeader ? { header: { ...header, ...generatedHeader } } : undefined),
|
50 | ...(encryptedKey ? { encrypted_key: encryptedKey } : undefined)
|
51 | }
|
52 | }).filter(Boolean),
|
53 | ...(final.aad ? { aad: final.aad } : undefined),
|
54 | iv: final.iv,
|
55 | ciphertext: final.ciphertext,
|
56 | tag: final.tag
|
57 | }
|
58 |
|
59 | if (!result.recipients.length) {
|
60 | delete result.recipients
|
61 | }
|
62 |
|
63 | return result
|
64 | }
|
65 | generalSerializer.validate = (protectedHeader, unprotectedHeader, aad, recipients) => {
|
66 | recipients.forEach(({ header }) => {
|
67 | validateCrit(protectedHeader, { ...header, ...unprotectedHeader }, protectedHeader ? protectedHeader.crit : undefined)
|
68 | })
|
69 | }
|
70 |
|
71 | const isJSON = (input) => {
|
72 | return isObject(input) &&
|
73 | typeof input.ciphertext === 'string' &&
|
74 | typeof input.iv === 'string' &&
|
75 | typeof input.tag === 'string' &&
|
76 | (input.unprotected === undefined || isObject(input.unprotected)) &&
|
77 | (input.protected === undefined || typeof input.protected === 'string') &&
|
78 | (input.aad === undefined || typeof input.aad === 'string')
|
79 | }
|
80 |
|
81 | const isSingleRecipient = (input) => {
|
82 | return (input.encrypted_key === undefined || typeof input.encrypted_key === 'string') &&
|
83 | (input.header === undefined || isObject(input.header))
|
84 | }
|
85 |
|
86 | const isValidRecipient = (recipient) => {
|
87 | return isObject(recipient) && typeof recipient.encrypted_key === 'string' && (recipient.header === undefined || isObject(recipient.header))
|
88 | }
|
89 |
|
90 | const isMultiRecipient = (input) => {
|
91 | if (Array.isArray(input.recipients) && input.recipients.every(isValidRecipient)) {
|
92 | return true
|
93 | }
|
94 |
|
95 | return false
|
96 | }
|
97 |
|
98 | const detect = (input) => {
|
99 | if (typeof input === 'string' && input.split('.').length === 5) {
|
100 | return 'compact'
|
101 | }
|
102 |
|
103 | if (isJSON(input)) {
|
104 | if (isMultiRecipient(input)) {
|
105 | return 'general'
|
106 | }
|
107 |
|
108 | if (isSingleRecipient(input)) {
|
109 | return 'flattened'
|
110 | }
|
111 | }
|
112 |
|
113 | throw new JWEInvalid('JWE malformed or invalid serialization')
|
114 | }
|
115 |
|
116 | module.exports = {
|
117 | compact: compactSerializer,
|
118 | flattened: flattenedSerializer,
|
119 | general: generalSerializer,
|
120 | detect
|
121 | }
|