UNPKG

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