UNPKG

4.21 kBJavaScriptView Raw
1const base64url = require('../help/base64url')
2const isDisjoint = require('../help/is_disjoint')
3const isObject = require('../help/is_object')
4const deepClone = require('../help/deep_clone')
5const { JWSInvalid } = require('../errors')
6const { sign } = require('../jwa')
7const getKey = require('../help/get_key')
8
9const serializers = require('./serializers')
10
11const PROCESS_RECIPIENT = Symbol('PROCESS_RECIPIENT')
12
13class Sign {
14 constructor (payload) {
15 if (typeof payload === 'string') {
16 payload = base64url.encode(payload)
17 } else if (Buffer.isBuffer(payload)) {
18 payload = base64url.encodeBuffer(payload)
19 this._binary = true
20 } else if (isObject(payload)) {
21 payload = base64url.JSON.encode(payload)
22 } else {
23 throw new TypeError('payload argument must be a Buffer, string or an object')
24 }
25
26 this._payload = payload
27 this._recipients = []
28 }
29
30 /*
31 * @public
32 */
33 recipient (key, protectedHeader, unprotectedHeader) {
34 key = getKey(key)
35
36 if (protectedHeader !== undefined && !isObject(protectedHeader)) {
37 throw new TypeError('protectedHeader argument must be a plain object when provided')
38 }
39
40 if (unprotectedHeader !== undefined && !isObject(unprotectedHeader)) {
41 throw new TypeError('unprotectedHeader argument must be a plain object when provided')
42 }
43
44 if (!isDisjoint(protectedHeader, unprotectedHeader)) {
45 throw new JWSInvalid('JWS Protected and JWS Unprotected Header Parameter names must be disjoint')
46 }
47
48 this._recipients.push({
49 key,
50 protectedHeader: protectedHeader ? deepClone(protectedHeader) : undefined,
51 unprotectedHeader: unprotectedHeader ? deepClone(unprotectedHeader) : undefined
52 })
53
54 return this
55 }
56
57 /*
58 * @private
59 */
60 [PROCESS_RECIPIENT] (recipient) {
61 const { key, protectedHeader, unprotectedHeader } = recipient
62
63 if (key.use === 'enc') {
64 throw new TypeError('a key with "use":"enc" is not usable for signing')
65 }
66
67 const joseHeader = {
68 protected: protectedHeader || {},
69 unprotected: unprotectedHeader || {}
70 }
71
72 let alg = joseHeader.protected.alg || joseHeader.unprotected.alg
73
74 if (!alg) {
75 alg = key.alg || [...key.algorithms('sign')][0]
76 if (recipient.protectedHeader) {
77 joseHeader.protected.alg = recipient.protectedHeader.alg = alg
78 } else {
79 joseHeader.protected = recipient.protectedHeader = { alg }
80 }
81 }
82
83 if (!alg) {
84 throw new JWSInvalid('could not resolve a usable "alg" for a recipient')
85 }
86
87 recipient.header = unprotectedHeader
88 recipient.protected = Object.keys(joseHeader.protected).length ? base64url.JSON.encode(joseHeader.protected) : ''
89
90 let toBeSigned
91 if (joseHeader.protected.crit && joseHeader.protected.crit.includes('b64')) {
92 if (this._b64 !== undefined && this._b64 !== joseHeader.protected.b64) {
93 throw new JWSInvalid('the "b64" Header Parameter value MUST be the same for all recipients')
94 } else {
95 this._b64 = joseHeader.protected.b64
96 }
97
98 if (!joseHeader.protected.b64) {
99 if (this._binary) {
100 this._payload = base64url.decodeToBuffer(this._payload)
101 } else {
102 this._payload = base64url.decode(this._payload)
103 }
104 }
105
106 toBeSigned = Buffer.concat([
107 Buffer.from(recipient.protected || ''),
108 Buffer.from('.'),
109 Buffer.isBuffer(this._payload) ? this._payload : Buffer.from(this._payload)
110 ])
111 } else {
112 toBeSigned = `${recipient.protected || ''}.${this._payload}`
113 }
114
115 recipient.signature = base64url.encodeBuffer(sign(alg, key, toBeSigned))
116 }
117
118 /*
119 * @public
120 */
121 sign (serialization) {
122 const serializer = serializers[serialization]
123 if (!serializer) {
124 throw new TypeError('serialization must be one of "compact", "flattened", "general"')
125 }
126
127 if (!this._recipients.length) {
128 throw new JWSInvalid('missing recipients')
129 }
130
131 serializer.validate(this, this._recipients)
132
133 for (const recipient of this._recipients) {
134 this[PROCESS_RECIPIENT](recipient)
135 }
136
137 return serializer(this._payload, this._recipients)
138 }
139}
140
141module.exports = Sign