UNPKG

3.98 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, first) {
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 (first && !joseHeader.protected.b64) {
93 if (this._binary) {
94 this._payload = base64url.decodeToBuffer(this._payload)
95 } else {
96 this._payload = base64url.decode(this._payload)
97 }
98 }
99
100 toBeSigned = Buffer.concat([
101 Buffer.from(recipient.protected || ''),
102 Buffer.from('.'),
103 Buffer.isBuffer(this._payload) ? this._payload : Buffer.from(this._payload)
104 ])
105 } else {
106 toBeSigned = `${recipient.protected || ''}.${this._payload}`
107 }
108
109 recipient.signature = base64url.encodeBuffer(sign(alg, key, toBeSigned))
110 }
111
112 /*
113 * @public
114 */
115 sign (serialization) {
116 const serializer = serializers[serialization]
117 if (!serializer) {
118 throw new TypeError('serialization must be one of "compact", "flattened", "general"')
119 }
120
121 if (!this._recipients.length) {
122 throw new JWSInvalid('missing recipients')
123 }
124
125 serializer.validate(this, this._recipients)
126
127 this._recipients.forEach((recipient, i) => {
128 this[PROCESS_RECIPIENT](recipient, i === 0)
129 })
130
131 return serializer(this._payload, this._recipients)
132 }
133}
134
135module.exports = Sign