1 | const base64url = require('../help/base64url')
|
2 | const isDisjoint = require('../help/is_disjoint')
|
3 | const isObject = require('../help/is_object')
|
4 | const deepClone = require('../help/deep_clone')
|
5 | const { JWSInvalid } = require('../errors')
|
6 | const { sign } = require('../jwa')
|
7 | const getKey = require('../help/get_key')
|
8 |
|
9 | const serializers = require('./serializers')
|
10 |
|
11 | const PROCESS_RECIPIENT = Symbol('PROCESS_RECIPIENT')
|
12 |
|
13 | class 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 |
|
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 |
|
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 |
|
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 |
|
130 | module.exports = Sign
|