This repository has been archived by the owner on Sep 17, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathjwa.coffee
360 lines (290 loc) · 13.1 KB
/
jwa.coffee
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
# Node
crypto = require "crypto"
# Lib
ju = require "./utils"
#
# The following is based on [JSON Web Algorithms (JWA) v02](https://www.ietf.org/id/draft-ietf-jose-json-web-algorithms-02.txt):
#
# The JSON Web Algorithms (JWA) specification enumerates cryptographic algorithms and identifiers to be used with the
# JSON Web Signature (JWS) [JWS] and JSON Web Encryption (JWE) [JWE] specifications. Enumerating the algorithms and
# identifiers for them in this specification, rather than in the JWS and JWE specifications, is intended to allow them
# to remain unchanged in the face of changes in the set of required, recommended, optional, and deprecated algorithms
# over time. This specification also describes the semantics and operations that are specific to these algorithms and
# algorithm families.
#
# +--------------------+----------------------------------------------+
# | alg Parameter | Digital Signature or MAC Algorithm |
# | Value | |
# +--------------------+----------------------------------------------+
# | HS256 | HMAC using SHA-256 hash algorithm |
# | HS384 | HMAC using SHA-384 hash algorithm |
# | HS512 | HMAC using SHA-512 hash algorithm |
# | RS256 | RSA using SHA-256 hash algorithm |
# | RS384 | RSA using SHA-384 hash algorithm |
# | RS512 | RSA using SHA-512 hash algorithm |
# | ES256 | ECDSA using P-256 curve and SHA-256 hash |
# | | algorithm |
# | ES384 | ECDSA using P-384 curve and SHA-384 hash |
# | | algorithm |
# | ES512 | ECDSA using P-521 curve and SHA-512 hash |
# | | algorithm |
# | none | No digital signature or MAC value included |
# +--------------------+----------------------------------------------+
#
# Of these algorithms, only HMAC SHA-256 and "none" MUST be implemented by conforming JWS implementations.
# It is RECOMMENDED that implementations also support the RSA SHA-256 and ECDSA P-256 SHA-256 algorithms.
# Support for other algorithms and key sizes is OPTIONAL.
#
jwa_table =
NONE :
TYPE : "SIGNATURE"
HASH_AL : {}
HMAC :
TYPE : "SIGNATURE"
HASH_AL :
HS256 : "SHA256"
HS384 : "SHA384"
HS512 : "SHA512"
RSA :
TYPE : "SIGNATURE"
HASH_AL :
RS256 : "RSA-SHA256"
RS384 : "RSA-SHA384"
RS512 : "RSA-SHA512"
class JwaAlgorithm
type : () ->
jwa_table?[@alg]?.TYPE
hash : (alg) ->
jwa_table?[@alg]?.HASH_AL[alg]
#
# To support use cases where the content is secured by a means other than a digital signature or MAC value, JWSs MAY also be created
# without them. These are called "Plaintext JWSs". Plaintext JWSs MUST use the "alg" value "none", and are formatted identically to
# other JWSs, but with an empty JWS Signature value.
#
class NoneSigner extends JwaAlgorithm
alg : "NONE"
update: (data) ->
@
digest: () -> ""
sign: () -> @digest()
NONE_SIGNER = new NoneSigner
newNoneSigner = ( ) -> NONE_SIGNER
class NoneVerifier extends JwaAlgorithm
alg : "NONE"
verify: (jwt_req) ->
jwt_header = jwt_req?.header
jwt_claim = jwt_req?.claim
jwt_enc_sig = jwt_req?.segments?[2]
return false unless jwt_header.alg == "none"
return false if jwt_enc_sig
true
newNoneVerifier = () -> new NoneVerifier
# Provides the HMAC implementation of the **HS256**, **HS384** and **HS512** algorithms.
# Cryptographic algorithms are provided by **Node's** [Crypto library](http://nodejs.org/api/crypto.html)
#
# As mentioned in the specification the HMAC (Hash-based Message Authentication Codes) enable the usage
# of a *known secret*, this can be used to demonstrate that the MAC matches the hashed content,
# in this case the JWS Secured Input, which therefore demonstrates that whoever generated the MAC was in
# possession of the secret.
#
# To review the specifics of the algorithms please review chapter
# "3.2. MAC with HMAC SHA-256, HMAC SHA-384, or HMAC SHA-512" of
# the [Specification](https://www.ietf.org/id/draft-ietf-jose-json-web-algorithms-02.txt).
#
class HMACSigner extends JwaAlgorithm
alg : "HMAC"
#
# Creates and returns an HMAC object, a cryptographic HMAC binded to the given algorithm and key.
# The supported algorithm is dependent on the available algorithms in *OpenSSL* - to get the list
# type `openssl list-message-digest-algorithms` in the terminal. If you provide an algorithm that is
# not supported an error will be thrown.
#
constructor: (alg = "HS256" , @key) ->
throw Error "A defined algorithm is required." unless alg
@osslAlg = @hash(alg.toUpperCase())
throw Error "Algorithm #{@alg} is not supported by HMAC." unless @osslAlg
try
@hmac = crypto.createHmac @osslAlg, @key
catch error
throw new Error "HMAC does not support algorithm #{@alg} => #{@osslAlg}! #{error}"
update: (data) ->
throw new Error "There is no reference to the hmac object!" unless @hmac
@hmac.update data
@
digest: (encoding = "base64") ->
throw new Error "There is no reference to the hmac object!" unless @hmac
ju.base64urlEscape( @hmac.digest(encoding) )
sign: (encoding) -> @digest(encoding)
# Factory to create *HMAC Algorithm* instances
newHMACSigner = (alg, key) ->
new HMACSigner(alg, key)
# Todo: Move to JWS
class HMACVerifier extends JwaAlgorithm
alg: "HMAC"
verify: (jwt_req, key) ->
throw new Error "jwt request not specified" unless jwt_req
throw new Error "key not specified" unless key
_typ = jwt_req?.header?.typ
_alg = jwt_req?.header?.alg
_claim = jwt_req?.claim
_enc_sig = jwt_req?.segments?[2]
# is the header a jwt header?
return false unless _typ == "JWT"
# is the algorithm supported/available for hmac ?
throw new Error "Hash #{_alg} is not supported!" unless @hash( _alg)
# do we have an encoded signature?
return false unless _enc_sig
# do we have the implementation of such hmac algorithm?
algImpl = jwa_provider(_alg)
return false unless algImpl
# set the signer
signer = algImpl key
# if so we proceed to sign the segments that belong to the header and the claim
signer.update "#{jwt_req.segments?[0]}.#{jwt_req.segments?[1]}"
# get signed value form the JWT
_actual_sign = signer.sign()
#compare
_actual_sign == _enc_sig
# Todo: Move to JWS
newHMACVerifier = () -> new HMACVerifier
#
# Implementation of digital signature with RSA SHA-256, RSA SHA-384, or RSA SHA-512
#
# To review the specifics of the algorithms please review chapter
# "3.3. Digital Signature with RSA SHA-256, RSA SHA-384, or RSA SHA-512" of
# the [Specification](https://www.ietf.org/id/draft-ietf-jose-json-web-algorithms-02.txt).
#
# Important elements to understand are.
# * RSASSA-PKCS1-v1_5 digital signature algorithm (commonly known as PKCS#1),
# using SHA-256, SHA-384, or SHA-512 as the hash function.
#
# The *"alg"* (algorithm) header parameter values used in the JWS Header to indicate that
# the *Encoded JWS Signature* contains a **base64url** encoded **RSA digital signature* using the
# respective hash function are:
# * "RS256"
# * "RS384"
# * "RS512"
#
# **A key of size 2048 bits or larger MUST be used with these algorithms.**
#
#
class RSSigner extends JwaAlgorithm
alg : "RSA"
_assertSigner : () ->
throw Error "Signer is not defined!" unless @signer
constructor: (alg = "RSA-SHA256", @key_PEM) ->
throw Error "A defined algorithm is required." unless alg
@osslAlg = @hash( alg.toUpperCase() )
new Error "Algorithm #{alg} is not supported by the specification." unless @osslAlg
try
@signer = crypto.createSign(@osslAlg)
catch error
throw new Error "Unable to create a signer with algorithm #{@osslAlg}!"
update: (data) ->
@_assertSigner()
@signer.update data
@
sign: (format = "base64") ->
@_assertSigner()
_signed = @signer.sign(@key_PEM, format)
_enc_sign = ju.base64urlEscape _signed
_enc_sign
newRSSigner = (alg, key_PEM) -> new RSSigner( alg, key_PEM )
# TODO move verifier to JWS
#
# Implementation of verification of a RSA SHA-256, RSA SHA-384, or RSA SHA-512 signature.
#
# To review the specifics of the algorithms please review chapter
# "3.3. Digital Signature with RSA SHA-256, RSA SHA-384, or RSA SHA-512" of
# the [Specification](https://www.ietf.org/id/draft-ietf-jose-json-web-algorithms-02.txt).
#
# The *Encoded JWS Signature* contains a **base64url** encoded **RSA digital signature*. The
# following hash functions are available.
#
# Per specification the validation should be implemented as follows:
#
# o. Take the Encoded JWS Signature and base64url decode it into a
# byte array. If decoding fails, the JWS MUST be rejected.
#
# 0. Submit the bytes of the UTF-8 representation of the JWS Secured
# Input (which is the same as the ASCII representation) and the
# public key corresponding to the private key used by the signer to
# the RSASSA-PKCS1-V1_5-VERIFY algorithm using the corresponding SHA hash function (e.g. SHA-256).
#
# 0. If the validation fails, the JWS MUST be rejected.
#
#
class RSVerifier extends JwaAlgorithm
alg : "RSA"
_createVerifier = (alg) ->
try
crypto.createVerify(alg)
catch error
throw new Error "Unable to create a verifier with algorithm #{alg}! #{error}"
verify: (jwt_req, public_key) ->
throw new Error "jwt request not specified" unless jwt_req
throw new Error "public_key not specified" unless public_key
_typ = jwt_req?.header?.typ
_alg = jwt_req?.header?.alg
_claim = jwt_req?.claim
_enc_sig = jwt_req?.segments?[2]
# is the header a jwt header?
return false unless _typ == "JWT"
# do we have an encoded signature?
return false unless _enc_sig
# is the algorithm supported/available for RSA ?
openSSL = @hash(_alg)
throw new Error "Hash #{_alg} is not supported for this JWA #{@type}" unless openSSL
# can we create a verifier for the implementation of such algorithm?
_verifier = _createVerifier openSSL
return false unless _verifier
# update the verifier with the that used to generate the key
_verifier.update "#{jwt_req.segments[0]}.#{jwt_req.segments[1]}"
# we un-encode the encoded signature
_sig = ju.base64urlUnescape _enc_sig
# finally we verify
_verifier.verify public_key, _sig, "base64"
newRSVerifier = () -> new RSVerifier
#
# TODO: Implement
# 3.4. Digital Signature with ECDSA P-256 SHA-256, ECDSA P-384 SHA-384,
# or ECDSA P-521 SHA-512
#
# Returns a function that holds an *Encryption Algorithm*, or `undefined` if the algorithm is not supported or not found.
#
# +--------------------+----------------------------------------------+
# | alg Parameter | Digital Signature or MAC Algorithm |
# | Value | |
# +--------------------+----------------------------------------------+
# | HS256 | HMAC using SHA-256 hash algorithm |
# | HS384 | HMAC using SHA-384 hash algorithm |
# | HS512 | HMAC using SHA-512 hash algorithm |
# | RS256 | RSA using SHA-256 hash algorithm |
# | RS384 | RSA using SHA-384 hash algorithm |
# | RS512 | RSA using SHA-512 hash algorithm |
# | ES256 | ECDSA using P-256 curve and SHA-256 hash |
# | | algorithm |
# | ES384 | ECDSA using P-384 curve and SHA-384 hash |
# | | algorithm |
# | ES512 | ECDSA using P-521 curve and SHA-512 hash |
# | | algorithm |
# | none | No digital signature or MAC value included |
# +--------------------+----------------------------------------------+
#
module.exports.provider = jwa_provider = (code) ->
switch code
when "none" then () => newNoneSigner()
when "HS256", "HS384", "HS512" then (key) => newHMACSigner code, key
when "RS256", "RS384", "RS512" then (key) => newRSSigner code, key
when "ES256", "ES384", "ES512" then undefined #throw new Error "ECDSA not yet implemented."
else undefined #throw new Error "There is no JWA Provider for #{code}!"
#
# Provides
#
module.exports.verifier = jwa_verifier = (code) ->
switch code
when "none" then newNoneVerifier()
when "HS256", "HS384", "HS512" then newHMACVerifier()
when "RS256", "RS384", "RS512" then newRSVerifier()
when "ES256", "ES384", "ES512" then undefined #throw new Error "ECDSA not yet implemented."
else undefined #throw new Error "There is no JWA Provider for #{code}!"