jose 0.3.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,21 +1,254 @@
1
1
  module JOSE
2
2
 
3
3
  class SignedBinary < ::String
4
+ # Expands a compacted signed binary or list of signed binaries into a map.
5
+ # @see JOSE::JWS.expand
4
6
  def expand
5
7
  return JOSE::JWS.expand(self)
6
8
  end
9
+
10
+ # Returns the decoded payload portion of a signed binary or map without verifying the signature.
11
+ # @see JOSE::JWS.peek_payload
12
+ def peek_payload
13
+ return JOSE::JWS.peek_payload(self)
14
+ end
15
+
16
+ # Returns the decoded protected portion of a signed binary or map without verifying the signature.
17
+ # @see JOSE::JWS.peek_protected
18
+ def peek_protected
19
+ return JOSE::JWS.peek_protected(self)
20
+ end
21
+
22
+ # Returns the decoded signature portion of a signed binary or map without verifying the signature.
23
+ # @see JOSE::JWS.peek_signature
24
+ def peek_signature
25
+ return JOSE::JWS.peek_signature(self)
26
+ end
7
27
  end
8
28
 
29
+ # Immutable signed Map structure based on {JOSE::Map JOSE::Map}.
9
30
  class SignedMap < JOSE::Map
31
+ # Compacts an expanded signed map or signed list into a binary.
32
+ # @see JOSE::JWS.compact
10
33
  def compact
11
34
  return JOSE::JWS.compact(self)
12
35
  end
13
36
  end
14
37
 
38
+ # JWS stands for JSON Web Signature which is defined in [RFC 7515](https://tools.ietf.org/html/rfc7515).
39
+ #
40
+ # ## Unsecured Signing Vulnerability
41
+ #
42
+ # The [`"none"`](https://tools.ietf.org/html/rfc7515#appendix-A.5) signing
43
+ # algorithm is disabled by default to prevent accidental verification of empty
44
+ # signatures (read about the vulnerability [here](https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/)).
45
+ #
46
+ # You may also enable the `"none"` algorithm by setting the `JOSE_UNSECURED_SIGNING`
47
+ # environment variable or by using {JOSE.unsecured_signing= JOSE.unsecured_signing=}.
48
+ #
49
+ # ## Strict Verification Recommended
50
+ #
51
+ # {JOSE::JWS.verify_strict JOSE::JWS.verify_strict} is recommended over {JOSE::JWS.verify JOSE::JWS.verify} so that
52
+ # signing algorithms may be whitelisted during verification of signed input.
53
+ #
54
+ # ## Algorithms
55
+ #
56
+ # The following algorithms are currently supported by {JOSE::JWS JOSE::JWS} (some may need the {JOSE.crypto_fallback= JOSE.crypto_fallback=} option to be enabled):
57
+ #
58
+ # * `"Ed25519"`
59
+ # * `"Ed25519ph"`
60
+ # * `"Ed448"`
61
+ # * `"Ed448ph"`
62
+ # * `"ES256"`
63
+ # * `"ES384"`
64
+ # * `"ES512"`
65
+ # * `"HS256"`
66
+ # * `"HS384"`
67
+ # * `"HS512"`
68
+ # * `"PS256"`
69
+ # * `"PS384"`
70
+ # * `"PS512"`
71
+ # * `"RS256"`
72
+ # * `"RS384"`
73
+ # * `"RS512"`
74
+ # * `"none"` (disabled by default, enable with {JOSE.unsecured_signing= JOSE.unsecured_signing=})
75
+ #
76
+ # ## Examples
77
+ #
78
+ # All of the example keys generated below can be found here: [https://gist.github.com/potatosalad/925a8b74d85835e285b9](https://gist.github.com/potatosalad/925a8b74d85835e285b9)
79
+ #
80
+ # ### <a name="EdDSA-25519-group">Ed25519 and Ed25519ph</a>
81
+ #
82
+ # !!!ruby
83
+ # # let's generate the 2 keys we'll use below
84
+ # jwk_ed25519 = JOSE::JWK.generate_key([:okp, :Ed25519])
85
+ # jwk_ed25519ph = JOSE::JWK.generate_key([:okp, :Ed25519ph])
86
+ #
87
+ # # Ed25519
88
+ # signed_ed25519 = JOSE::JWS.sign(jwk_ed25519, "{}", { "alg" => "Ed25519" }).compact
89
+ # # => "eyJhbGciOiJFZDI1NTE5In0.e30.xyg2LTblm75KbLFJtROZRhEgAFJdlqH9bhx8a9LO1yvLxNLhO9fLqnFuU3ojOdbObr8bsubPkPqUfZlPkGHXCQ"
90
+ # JOSE::JWS.verify(jwk_ed25519, signed_ed25519).first
91
+ # # => true
92
+ #
93
+ # # Ed25519ph
94
+ # signed_ed25519ph = JOSE::JWS.sign(jwk_ed25519ph, "{}", { "alg" => "Ed25519ph" }).compact
95
+ # # => "eyJhbGciOiJFZDI1NTE5cGgifQ.e30.R3je4TTxQvoBOupIKkel_b8eW-G8KaWmXuC14NMGSCcHCTalURtMmVqX2KbcIpFBeI-OKP3BLHNIpt1keKveDg"
96
+ # JOSE::JWS.verify(jwk_ed25519ph, signed_ed25519ph).first
97
+ # # => true
98
+ #
99
+ # ### <a name="EdDSA-448-group">Ed448 and Ed448ph</a>
100
+ #
101
+ # !!!ruby
102
+ # # let's generate the 2 keys we'll use below
103
+ # jwk_ed448 = JOSE::JWK.generate_key([:okp, :Ed448])
104
+ # jwk_ed448ph = JOSE::JWK.generate_key([:okp, :Ed448ph])
105
+ #
106
+ # # Ed448
107
+ # signed_ed448 = JOSE::JWS.sign(jwk_ed448, "{}", { "alg" => "Ed448" }).compact
108
+ # # => "eyJhbGciOiJFZDQ0OCJ9.e30.UlqTx962FvZP1G5pZOrScRXlAB0DJI5dtZkknNTm1E70AapkONi8vzpvKd355czflQdc7uyOzTeAz0-eLvffCKgWm_zebLly7L3DLBliynQk14qgJgz0si-60mBFYOIxRghk95kk5hCsFpxpVE45jRIA"
109
+ # JOSE::JWS.verify(jwk_ed448, signed_ed448).first
110
+ # # => true
111
+ #
112
+ # # Ed448ph
113
+ # signed_ed448ph = JOSE::JWS.sign(jwk_ed448ph, "{}", { "alg" => "Ed448ph" }).compact
114
+ # # => "eyJhbGciOiJFZDQ0OHBoIn0.e30._7wxQF8Am-Fg3E-KgREXBv3Gr2vqLM6ja_7hs6kA5EakCrJVQ2QiAHrr4NriLABmiPbVd7F7IiaAApyR3Ud4ak3lGcHVxSyksjJjvBUbKnSB_xkT6v_QMmx27hV08JlxskUkfvjAG0-yKGC8BXoT9R0A"
115
+ # JOSE::JWS.verify(jwk_ed448ph, signed_ed448ph).first
116
+ # # => true
117
+ #
118
+ # ### <a name="ECDSA-group">ES256, ES384, and ES512</a>
119
+ #
120
+ # !!!ruby
121
+ # # let's generate the 3 keys we'll use below
122
+ # jwk_es256 = JOSE::JWK.generate_key([:ec, "P-256"])
123
+ # jwk_es384 = JOSE::JWK.generate_key([:ec, "P-384"])
124
+ # jwk_es512 = JOSE::JWK.generate_key([:ec, "P-521"])
125
+ #
126
+ # # ES256
127
+ # signed_es256 = JOSE::JWS.sign(jwk_es256, "{}", { "alg" => "ES256" }).compact
128
+ # # => "eyJhbGciOiJFUzI1NiJ9.e30.nb7cEQQuIi2NgcP5A468FHGG8UZg8gWZjloISyVIwNh3X6FiTTFZsvc0mL3RnulWoNJzKF6xwhae3botI1LbRg"
129
+ # JOSE::JWS.verify(jwk_es256, signed_es256).first
130
+ # # => true
131
+ #
132
+ # # ES384
133
+ # signed_es384 = JOSE::JWS.sign(jwk_es384, "{}", { "alg" => "ES384" }).compact
134
+ # # => "eyJhbGciOiJFUzM4NCJ9.e30.-2kZkNe66y2SprhgvvtMa0qBrSb2imPhMYkbi_a7vx-vpEHuVKsxCpUyNVLe5_CXaHWhHyc2rNi4uEfU73c8XQB3e03rg_JOj0H5XGIGS5G9f4RmNMSCiYGwqshLSDFI"
135
+ # JOSE::JWS.verify(jwk_es384, signed_es384).first
136
+ # # => true
137
+ #
138
+ # # ES512
139
+ # signed_es512 = JOSE::JWS.sign(jwk_es512, "{}", { "alg" => "ES512" }).compact
140
+ # # => "eyJhbGciOiJFUzUxMiJ9.e30.AOIw4KTq5YDu6QNrAYKtFP8R5IljAbhqXuPK1dUARPqlfc5F3mM0kmSh5KOVNHDmdCdapBv0F3b6Hl6glFDPlxpiASuSWtvvs9K8_CRfSkEzvToj8wf3WLGOarQHDwYXtlZoki1zMPGeWABwafTZNQaItNSpqYd_P9GtN0XM3AALdua0"
141
+ # JOSE::JWS.verify(jwk_es512, signed_es512).first
142
+ # # => true
143
+ #
144
+ # ### <a name="HMACSHA2-group">HS256, HS384, and HS512</a>
145
+ #
146
+ # !!!ruby
147
+ # # let's generate the 3 keys we'll use below
148
+ # jwk_hs256 = JOSE::JWK.generate_key([:oct, 16])
149
+ # jwk_hs384 = JOSE::JWK.generate_key([:oct, 24])
150
+ # jwk_hs512 = JOSE::JWK.generate_key([:oct, 32])
151
+ #
152
+ # # HS256
153
+ # signed_hs256 = JOSE::JWS.sign(jwk_hs256, "{}", { "alg" => "HS256" }).compact
154
+ # # => "eyJhbGciOiJIUzI1NiJ9.e30.r2JwwMFHECoDZlrETLT-sgFT4qN3w0MLee9MrgkDwXs"
155
+ # JOSE::JWS.verify(jwk_hs256, signed_hs256).first
156
+ # # => true
157
+ #
158
+ # # HS384
159
+ # signed_hs384 = JOSE::JWS.sign(jwk_hs384, "{}", { "alg" => "HS384" }).compact
160
+ # # => "eyJhbGciOiJIUzM4NCJ9.e30.brqQFXXM0XtMWDdKf0foEQcvK18swcoDkxBqCPeed_IO317_tisr60H2mz79SlNR"
161
+ # JOSE::JWS.verify(jwk_hs384, signed_hs384).first
162
+ # # => true
163
+ #
164
+ # # HS512
165
+ # signed_hs512 = JOSE::JWS.sign(jwk_hs512, "{}", { "alg" => "HS512" }).compact
166
+ # # => "eyJhbGciOiJIUzUxMiJ9.e30.ge1JYomO8Fyl6sgxLbc4g3AMPbaMHLmeTl0jrUYAJZSloN9j4VyhjucX8d-RWIlMjzdG0xyklw53k1-kaTlRVQ"
167
+ # JOSE::JWS.verify(jwk_hs512, signed_hs512).first
168
+ # # => true
169
+ #
170
+ # ### <a name="RSASSAPSS-group">PS256, PS384, and PS512</a>
171
+ #
172
+ # !!!ruby
173
+ # # let's generate the 3 keys we'll use below (cutkey must be installed as a dependency)
174
+ # jwk_ps256 = JOSE::JWK.generate_key([:rsa, 2048])
175
+ # jwk_ps384 = JOSE::JWK.generate_key([:rsa, 4096])
176
+ # jwk_ps512 = JOSE::JWK.generate_key([:rsa, 8192]) # this may take a few seconds
177
+ #
178
+ # # PS256
179
+ # signed_ps256 = JOSE::JWS.sign(jwk_ps256, "{}", { "alg" => "PS256" }).compact
180
+ # # => "eyJhbGciOiJQUzI1NiJ9.e30.RY5A3rG2TjmdlARE57eSSSFE6plkuQPKLKsyqz3WrqKRWZgSrvROACRTzoGyrx1sNvQEZJLZ-xVhrFvP-80Q14XzQbPfYLubvn-2wcMNCmih3OVQNVtFdFjA5U2NG-sF-SWAUmm9V_DvMShFGG0qHxLX7LqT83lAIgEulgsytb0xgOjtJObBru5jLjN_uEnc7fCfnxi3my1GAtnrs9NiKvMfuIVlttvOORDFBTO2aFiCv1F-S6Xgj16rc0FGImG0x3amQcmFAD9g41KY0_KsCXgUfoiVpC6CqO6saRC4UDykks91B7Nuoxjsm3nKWa_4vKh9QJy-V8Sf0gHxK58j8Q"
181
+ # JOSE::JWS.verify(jwk_ps256, signed_ps256).first
182
+ # # => true
183
+ #
184
+ # # PS384
185
+ # signed_ps384 = JOSE::JWS.sign(jwk_ps384, "{}", { "alg" => "PS384" }).compact
186
+ # # => "eyJhbGciOiJQUzM4NCJ9.e30.xmYVenIhi75hDMy3bnL6WVpVlTzYmO1ejOZeq9AkSjkp_STrdIp6uUEs9H_y7CLD9LrGYYHDNDl9WmoH6cn95WZT9KJgAVNFFYd8owY6JUHGKU1jUbLkptAgvdphVpWZ1C5fVCRt4vmp8K9f6jy3er9jCBNjl9gSBdmToFwYdXI26ZKSBjfoVm2tFFQIOThye4YQWCWHbzSho6J7d5ATje72L30zDvWXavJ-XNvof5Tkju4WQQB-ukFoqTw4yV8RVwCa-DX61I1hNrq-Zr75_iWmHak3GqNkg5ACBEjDtvtyxJizqy9KINKSlbB9jGztiWoEiXZ6wJ5sSJ6ZrSFJuQVEmns_dLqzpSHEFkWfczEV_gj9Eu_EXwMp9YQlQ3GktfXaz-mzH_jUaLmudEUskQGCiR92gK9KR6_ROQPJfD54Tkqdh6snwg6y17k8GdlTc5qMM3V84q3R6zllmhrRhV1Dlduc0MEqKcsQSX_IX21-sfiVMIcUsW73dIPXVZI2jsNlEHKqwMjWdSfjYUf3YApxSGERU3u4lRS3F0yRrZur8KWS3ToilApjg0cNg9jKas8g8C8ZPgGFYM6StVxUnXRmsJILDnsZMIPjbUDAPHhB0DwLwOB7OqGUBcItX-zwur1OVnHR7aIh1DbfWfyTIml8VIhYfGfazgXfgQVcGEM"
187
+ # JOSE::JWS.verify(jwk_ps384, signed_ps384).first
188
+ # # => true
189
+ #
190
+ # # PS512
191
+ # signed_ps512 = JOSE::JWS.sign(jwk_ps512, "{}", { "alg" => "PS512" }).compact
192
+ # # => "eyJhbGciOiJQUzUxMiJ9.e30.fJe52-PF3I7UrpQamLCnmVAGkBhP0HVeJi48qZqaFc1-_tQEiYTfxuwQBDlt01GQWpjTZRb097bZF6RcrKWwRHyAo3otOZdR32emWfOHddWLL3qotj_fTaDR2-OhLixwce6mFjnHqppHH1zjCmgbKPG8S2cAadNd5w10VR-IS6LdnFRhNZOahuuB7dzCEJaSjkGfm3_9xdj3I0ZRl4fauR_LO9NQIyvMMeCFevowz1sVGG1G-I2njPrEXvxhAMp7y2mao5Yik8UUORXRjcn2Wai3umy8Yh4nHYU5qqruHjLjDwudCPNDjxjg294z1uAUpt7S0v7VbrkgUvgutTFAT-bcHywFODiycajQuqIpFp1TCUAq3Xe2yk4DTRduvPIKcPkJQnFrVkClJAU9A4D4602xpdK-z2uCgWsBVHVokf5-9ba5EqVb8BJx2xYZUIA5CdrIiTBfoe_cI5Jh92uprcWC_llio2ZJvGdQpPgwCgca7-RQ94LAmIA4u3mAndrZj_z48T2GjHbaKzl18FOPQH0XEvK_W5oypUe5NOGlz9mMGZigbFqBY2lM-7oVVYc4ZA3VFy8Dv1nWhU6DGb2NnDnQUyChllyBREuZbwrkOTQEvqqdV-6lM6VwXNu1gqc3YHly9W6u5CmsnxtvlIxsUVg679HiqdtdWxLSaIJObd9Xji56-eEkWMEA08SNy9p-F9AgHOxzoZqgrAQDEwqyEwqoAW681xLc5Vck580AQDxO9Ha4IqLIPirpO5EODQjOd8-S_SlAP5o_wz1Oh38MC5T5V13PqPuZ70dbggB4bUgVaHYC4FE4XHCqP7W3xethaPc68cY9-g9f1RUvthmnEYXSRpvyaMY3iX0txZazWIS_Jg7pNTCEaWr9JCLTZd1MiLbFowPvKYGM-z-39K31OUbq5PIScy0I9OOz9joecm8KsCesA2ysPph1E7cL7Etiw5tGhCFzcdQwm8Gm6SDwj8vCEcZUkXeZJfhlS1cJtZk1sNu3KZNndevtZjRWaXi2m4WNKVxVE-nuaF7V3GWfDemh9RXxyFK8OC8aYLIqcc2pAKJM47ANVty2ll1xaCIB3q3CKdnk5fmsnzKkQI9SjKy70p9TWT-NNoYU682KG_mZo-ByEs5CvJ8w7qysmX8Xpb2I6oSJf7S3qjbqkqtXQcV5MuQ232vk7-g42CcQGL82xvRc09TuvwnmykpKHmjUaJ4U9k9zTN3g2iTdpkvl6vbnND9uG1SBaieVeFYWCT-6VdhovEiD9bvIdA7D_R7NZO8YHBt_lfBQRle_jDyLzHSlkP6kt9dYRhrc2SNMzF_4i3iEUAihbaQYvbNsGwWrHqyGofnva20pRXwc4GxOlw"
193
+ # JOSE::JWS.verify(jwk_ps512, signed_ps512).first
194
+ # # => true
195
+ #
196
+ # ### <a name="RSASSAPKCS1_5-group">RS256, RS384, and RS512</a>
197
+ #
198
+ # !!!ruby
199
+ # # let's generate the 3 keys we'll use below
200
+ # jwk_rs256 = JOSE::JWK.generate_key([:rsa, 1024])
201
+ # jwk_rs384 = JOSE::JWK.generate_key([:rsa, 2048])
202
+ # jwk_rs512 = JOSE::JWK.generate_key([:rsa, 4096])
203
+ #
204
+ # # RS256
205
+ # signed_rs256 = JOSE::JWS.sign(jwk_rs256, "{}", { "alg" => "RS256" }).compact
206
+ # # => "eyJhbGciOiJSUzI1NiJ9.e30.C0J8v5R-sEe9-g_s0SMgPorCh8VDdaZ9gLpWNm1Tn1Cv2xRph1Xn9Rzm10ZCEs84sj7kxA4v28fVShQ_P1AHN83yQ2mvstkKwsuwXxr-cludx_NLQL5CKKQtTR0ITD_pxUowjfAkBYuJv0677jUj-8lGKs1P5e2dbwW9IqFe4uE"
207
+ # JOSE::JWS.verify(jwk_rs256, signed_rs256).first
208
+ # # => true
209
+ #
210
+ # # RS384
211
+ # signed_rs384 = JOSE::JWS.sign(jwk_rs384, "{}", { "alg" => "RS384" }).compact
212
+ # # => "eyJhbGciOiJSUzM4NCJ9.e30.fvPxeNhO0oitOsdqFmrBgpGE7Gn_NdJ1J8F5ArKon54pdHB2v30hua9wbG4V2Hr-hNAyflaBJtoGAwIpKVkfHn-IW7d06hKw_Hv0ecG-VvZr60cK2IJnHS149Htz_652egThZh1GIKRZN1IrRVlraLMozFcWP0Ojc-L-g5XjcTFafesmV0GFGfFubAiQWEiWIgNV3822L-wPe7ZGeFe5yYsZ70WMHQQ1tSuNsm5QUOUVInOThAhJ30FRTCNFgv46l4TEF9aaI9443cKAbwzd_EavD0FpvgpwEhGyNTVx0sxiCZIYUE_jN53aSaHXB82d0xwIr2-GXlr3Y-dLwERIMw"
213
+ # JOSE::JWS.verify(jwk_rs384, signed_rs384).first
214
+ # # => true
215
+ #
216
+ # # RS512
217
+ # signed_rs512 = JOSE::JWS.sign(jwk_rs512, "{}", { "alg" => "RS512" }).compact
218
+ # # => "eyJhbGciOiJSUzUxMiJ9.e30.le2_kCnmj6Y02bl16Hh5EPqmLsFkB3YZpiEfvmA6xfdg9I3QJ5uSgOejs_HpuIbItuMFUdcqtkfW45_6YKlI7plB49iWiNnWY0PLxsvbiZaSmT4R4dOUWx9KlO_Ui5SE94XkigUoFanDTHTr9bh4NpvoIaNdi_xLdC7FYA-AqZspegRcgY-QZQv4kbD3NQJtxsEiAXk8-C8CX3lF6haRlh7s4pyAmgj7SJeElsPjhPNVZ7EduhTLZfVwiLrRmzLKQ6dJ_PrZDig1lgl9jf2NjzcsFpt6lvfrMsDdIQEGyJoh53-zXiD_ltyAZGS3pX-_tHRxoAZ1SyAPkkC4cCra6wc-03sBQPoUa26xyyhrgf4h7E2l-JqhKPXT7pJv6AbRPgKUH4prEH636gpoWQrRc-JxbDIJHR0ShdL8ssf5e-rKpcVVAZKnRI64NbSKXTg-JtDxhU9QG8JVEkHqOxSeo-VSXOoExdmm8lCfqylrw7qmDxjEwOq7TGjhINyjVaK1Op_64BWVuCzgooea6G2ZvCTIEl0-k8wY8s9VC7hxSrsgCAnpWeKpIcbLQoDIoyasG-6Qb5OuSLR367eg9NAQ8WMTbrrQkm-KLNCYvMFaxmlWzBFST2JDmIr0VH9BzXRAdfG81SymuyFA7_FdpiVYwAwEGR4Q5HYEpequ38tHu3Y"
219
+ # JOSE::JWS.verify(jwk_rs512, signed_rs512).first
220
+ # # => true
15
221
  class JWS < Struct.new(:alg, :b64, :fields)
16
222
 
17
223
  # Decode API
18
224
 
225
+ # Converts a binary or map into a {JOSE::JWS JOSE::JWS}.
226
+ #
227
+ # !!!ruby
228
+ # JOSE::JWS.from({ "alg" => "HS256" })
229
+ # # => #<struct JOSE::JWS
230
+ # # alg=#<struct JOSE::JWS::ALG_HMAC hmac=OpenSSL::Digest::SHA256>,
231
+ # # b64=nil,
232
+ # # fields=JOSE::Map[]>
233
+ # JOSE::JWS.from("{\"alg\":\"HS256\"}")
234
+ # # => #<struct JOSE::JWS
235
+ # # alg=#<struct JOSE::JWS::ALG_HMAC hmac=OpenSSL::Digest::SHA256>,
236
+ # # b64=nil,
237
+ # # fields=JOSE::Map[]>
238
+ #
239
+ # Support for custom algorithms may be added by specifying `:alg` under `modules`:
240
+ #
241
+ # !!!ruby
242
+ # JOSE::JWS.from({ "alg" => "custom" }, { alg: MyCustomAlgorithm })
243
+ # # => #<struct JOSE::JWS
244
+ # # alg=#<MyCustomAlgorithm:0x007f8c5419ff68>,
245
+ # # b64=nil,
246
+ # # fields=JOSE::Map[]>
247
+ #
248
+ # *Note:* `MyCustomAlgorithm` must implement the methods mentioned in other alg modules.
249
+ # @param [JOSE::Map, Hash, String, JOSE::JWS, Array<JOSE::Map, Hash, String, JOSE::JWS>] object
250
+ # @param [Hash] modules
251
+ # @return [JOSE::JWS, Array<JOSE::JWS>]
19
252
  def self.from(object, modules = {})
20
253
  case object
21
254
  when JOSE::Map, Hash
@@ -24,61 +257,121 @@ module JOSE
24
257
  return from_binary(object, modules)
25
258
  when JOSE::JWS
26
259
  return object
260
+ when Array
261
+ return object.map { |obj| from(obj, modules) }
27
262
  else
28
- raise ArgumentError, "'object' must be a Hash, String, or JOSE::JWS"
263
+ raise ArgumentError, "'object' must be a Hash, String, JOSE::JWS, or Array"
29
264
  end
30
265
  end
31
266
 
267
+ # Converts a binary into a {JOSE::JWS JOSE::JWS}.
268
+ # @param [String, Array<String>] object
269
+ # @param [Hash] modules
270
+ # @return [JOSE::JWS, Array<JOSE::JWS>]
32
271
  def self.from_binary(object, modules = {})
33
272
  case object
34
273
  when String
35
274
  return from_map(JOSE.decode(object), modules)
275
+ when Array
276
+ return object.map { |obj| from_binary(obj, modules) }
36
277
  else
37
- raise ArgumentError, "'object' must be a String"
278
+ raise ArgumentError, "'object' must be a String or Array"
38
279
  end
39
280
  end
40
281
 
282
+ # Reads file and calls {.from_binary} to convert into a {JOSE::JWS JOSE::JWS}.
283
+ # @param [String] file
284
+ # @param [Hash] modules
285
+ # @return [JOSE::JWS]
41
286
  def self.from_file(file, modules = {})
42
287
  return from_binary(File.binread(file), modules)
43
288
  end
44
289
 
290
+ # Converts a map into a {JOSE::JWS JOSE::JWS}.
291
+ # @param [JOSE::Map, Hash, Array<JOSE::Map, Hash>] object
292
+ # @param [Hash] modules
293
+ # @return [JOSE::JWS, Array<JOSE::JWS>]
45
294
  def self.from_map(object, modules = {})
46
295
  case object
47
296
  when JOSE::Map, Hash
48
297
  return from_fields(JOSE::JWS.new(nil, nil, JOSE::Map.new(object)), modules)
298
+ when Array
299
+ return object.map { |obj| from_map(obj, modules) }
49
300
  else
50
- raise ArgumentError, "'object' must be a Hash"
301
+ raise ArgumentError, "'object' must be a Hash or Array"
51
302
  end
52
303
  end
53
304
 
54
305
  # Encode API
55
306
 
307
+ # Converts a {JOSE::JWS JOSE::JWS} into a binary.
308
+ # @param [JOSE::Map, Hash, String, JOSE::JWS, Array<JOSE::Map, Hash, String, JOSE::JWS>] jws
309
+ # @return [String, Array<String>]
56
310
  def self.to_binary(jws)
57
- return from(jws).to_binary
311
+ if jws.is_a?(Array)
312
+ return from(jws).map { |obj| obj.to_binary }
313
+ else
314
+ return from(jws).to_binary
315
+ end
58
316
  end
59
317
 
318
+ # Converts a {JOSE::JWS JOSE::JWS} into a binary.
319
+ # @return [String]
60
320
  def to_binary
61
321
  return JOSE.encode(to_map)
62
322
  end
63
323
 
324
+ # Calls {.to_binary} on a {JOSE::JWS JOSE::JWS} and then writes the binary to `file`.
325
+ # @param [JOSE::Map, Hash, String, JOSE::JWS] jws
326
+ # @param [String] file
327
+ # @return [Fixnum] bytes written
64
328
  def self.to_file(jws, file)
65
329
  return from(jws).to_file(file)
66
330
  end
67
331
 
332
+ # Calls {#to_binary} on a {JOSE::JWS JOSE::JWS} and then writes the binary to `file`.
333
+ # @param [String] file
334
+ # @return [Fixnum] bytes written
68
335
  def to_file(file)
69
336
  return File.binwrite(file, to_binary)
70
337
  end
71
338
 
339
+ # Converts a {JOSE::JWS JOSE::JWS} into a map.
340
+ # @param [JOSE::Map, Hash, String, JOSE::JWS, Array<JOSE::Map, Hash, String, JOSE::JWS>] jws
341
+ # @return [JOSE::Map, Array<JOSE::Map>]
72
342
  def self.to_map(jws)
73
- return from(jws).to_map
343
+ if jws.is_a?(Array)
344
+ return from(jws).map { |obj| obj.to_map }
345
+ else
346
+ return from(jws).to_map
347
+ end
74
348
  end
75
349
 
350
+ # Converts a {JOSE::JWS JOSE::JWS} into a map.
351
+ # @return [JOSE::Map]
76
352
  def to_map
77
- return alg.to_map(fields)
353
+ map = alg.to_map(fields)
354
+ if b64 == false or b64 == true
355
+ map = map.put('b64', b64)
356
+ end
357
+ return map
78
358
  end
79
359
 
80
360
  # API
81
361
 
362
+ # Compacts an expanded signed map or signed list into a binary.
363
+ #
364
+ # !!!ruby
365
+ # JOSE::JWS.compact({
366
+ # "payload" => "e30",
367
+ # "protected" => "eyJhbGciOiJIUzI1NiJ9",
368
+ # "signature" => "5paAJxaOXSqRUIXrP_vJXUZu2SCBH-ojgP4D6Xr6GPU"
369
+ # })
370
+ # # => "eyJhbGciOiJIUzI1NiJ9.e30.5paAJxaOXSqRUIXrP_vJXUZu2SCBH-ojgP4D6Xr6GPU"
371
+ #
372
+ # @see JOSE::JWS.expand
373
+ # @param [JOSE::SignedMap, JOSE::Map, Hash] map
374
+ # @return [JOSE::SignedBinary]
82
375
  def self.compact(map)
83
376
  if map.is_a?(Hash) or map.is_a?(JOSE::Map)
84
377
  return JOSE::SignedBinary.new([
@@ -93,6 +386,18 @@ module JOSE
93
386
  end
94
387
  end
95
388
 
389
+ # Expands a compacted signed binary or list of signed binaries into a map.
390
+ #
391
+ # !!!ruby
392
+ # JOSE::JWS.expand("eyJhbGciOiJIUzI1NiJ9.e30.5paAJxaOXSqRUIXrP_vJXUZu2SCBH-ojgP4D6Xr6GPU")
393
+ # # => JOSE::SignedMap[
394
+ # # "protected" => "eyJhbGciOiJIUzI1NiJ9",
395
+ # # "payload" => "e30",
396
+ # # "signature" => "5paAJxaOXSqRUIXrP_vJXUZu2SCBH-ojgP4D6Xr6GPU"]
397
+ #
398
+ # @see JOSE::JWS.compact
399
+ # @param [JOSE::SignedBinary, String] binary
400
+ # @return [JOSE::SignedMap]
96
401
  def self.expand(binary)
97
402
  if binary.is_a?(String)
98
403
  if binary.count('.') == 2 and (parts = binary.split('.', 3)).length == 3
@@ -110,18 +415,46 @@ module JOSE
110
415
  end
111
416
  end
112
417
 
113
- def self.generate_key(object, modules = {})
114
- return from(object, modules).generate_key
418
+ # Generates a new {JOSE::JWK JOSE::JWK} based on the algorithms of the specified {JOSE::JWS JOSE::JWS}.
419
+ #
420
+ # !!!ruby
421
+ # JOSE::JWS.generate_key({"alg" => "HS256"})
422
+ # # => #<struct JOSE::JWK
423
+ # # keys=nil,
424
+ # # kty=
425
+ # # #<struct JOSE::JWK::KTY_oct
426
+ # # oct="\x96G\x1DO\xE4 \xDA\x04o\xFA\xD4\x81\xE2\xADV\xCDH0bdBDq\r+<z\xF8\xB3,\x8C\x18">,
427
+ # # fields=JOSE::Map["alg" => "HS256", "use" => "sig"]>
428
+ # @param [JOSE::Map, Hash, String, JOSE::JWS, Array<JOSE::Map, Hash, String, JOSE::JWS>] jws
429
+ # @param [Hash] modules
430
+ # @return [JOSE::JWK, Array<JOSE::JWK>]
431
+ def self.generate_key(jws, modules = {})
432
+ if jws.is_a?(Array)
433
+ return from(jws, modules).map { |obj| obj.generate_key }
434
+ else
435
+ return from(jws, modules).generate_key
436
+ end
115
437
  end
116
438
 
439
+ # Generates a new {JOSE::JWK JOSE::JWK} based on the algorithms of the specified {JOSE::JWS JOSE::JWS}.
440
+ #
441
+ # @see JOSE::JWS.generate_key
442
+ # @return [JOSE::JWK]
117
443
  def generate_key
118
444
  return alg.generate_key(fields)
119
445
  end
120
446
 
447
+ # Merges map on right into map on left.
448
+ # @param [JOSE::Map, Hash, String, JOSE::JWS] left
449
+ # @param [JOSE::Map, Hash, String, JOSE::JWS] right
450
+ # @return [JOSE::JWS]
121
451
  def self.merge(left, right)
122
452
  return from(left).merge(right)
123
453
  end
124
454
 
455
+ # Merges object into current map.
456
+ # @param [JOSE::Map, Hash, String, JOSE::JWS] object
457
+ # @return [JOSE::JWS]
125
458
  def merge(object)
126
459
  object = case object
127
460
  when JOSE::Map, Hash
@@ -136,6 +469,14 @@ module JOSE
136
469
  return JOSE::JWS.from_map(self.to_map.merge(object))
137
470
  end
138
471
 
472
+ # Returns the decoded payload portion of a signed binary or map without verifying the signature.
473
+ #
474
+ # !!!ruby
475
+ # JOSE::JWS.peek_payload("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.dMAojPMVbFvvkouYUSI9AxIRBxgqretQMCvNF7KmTHU")
476
+ # # => "{}"
477
+ #
478
+ # @param [JOSE::SignedBinary, String] signed
479
+ # @return [String]
139
480
  def self.peek_payload(signed)
140
481
  if signed.is_a?(String)
141
482
  signed = expand(signed)
@@ -143,6 +484,14 @@ module JOSE
143
484
  return JOSE.urlsafe_decode64(signed['payload'])
144
485
  end
145
486
 
487
+ # Returns the decoded protected portion of a signed binary or map without verifying the signature.
488
+ #
489
+ # !!!ruby
490
+ # JOSE::JWS.peek_protected("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.dMAojPMVbFvvkouYUSI9AxIRBxgqretQMCvNF7KmTHU")
491
+ # # => JOSE::Map["alg" => "HS256", "typ" => "JWT"]
492
+ #
493
+ # @param [JOSE::SignedBinary, String] signed
494
+ # @return [JOSE::Map]
146
495
  def self.peek_protected(signed)
147
496
  if signed.is_a?(String)
148
497
  signed = expand(signed)
@@ -150,27 +499,112 @@ module JOSE
150
499
  return JOSE::Map.new(JOSE.decode(JOSE.urlsafe_decode64(signed['protected'])))
151
500
  end
152
501
 
153
- def self.sign(key, plain_text, jws, header = nil)
154
- return from(jws).sign(key, plain_text, header)
502
+ # Returns the decoded signature portion of a signed binary or map without verifying the signature.
503
+ #
504
+ # !!!ruby
505
+ # JOSE::JWS.peek_signature("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.dMAojPMVbFvvkouYUSI9AxIRBxgqretQMCvNF7KmTHU")
506
+ # # => "t\xC0(\x8C\xF3\x15l[\xEF\x92\x8B\x98Q\"=\x03\x12\x11\a\x18*\xAD\xEBP0+\xCD\x17\xB2\xA6Lu"
507
+ #
508
+ # @param [JOSE::SignedBinary, String] signed
509
+ # @return [String]
510
+ def self.peek_signature(signed)
511
+ if signed.is_a?(String)
512
+ signed = expand(signed)
513
+ end
514
+ return JOSE.urlsafe_decode64(signed['signature'])
515
+ end
516
+
517
+ # Signs the `plain_text` using the `jwk` and algorithm specified by the `jws`.
518
+ #
519
+ # !!!ruby
520
+ # jwk = JOSE::JWK.from({"k" => "qUg4Yw", "kty" => "oct"})
521
+ # # => #<struct JOSE::JWK keys=nil, kty=#<struct JOSE::JWK::KTY_oct oct="\xA9H8c">, fields=JOSE::Map[]>
522
+ # JOSE::JWS.sign(jwk, "{}", { "alg" => "HS256" })
523
+ # # => JOSE::SignedMap[
524
+ # # "signature" => "5paAJxaOXSqRUIXrP_vJXUZu2SCBH-ojgP4D6Xr6GPU",
525
+ # # "protected" => "eyJhbGciOiJIUzI1NiJ9",
526
+ # # "payload" => "e30"]
527
+ #
528
+ # If the `jwk` has a `"kid"` assigned, it will be added to the `"header"` on the signed map:
529
+ #
530
+ # !!!ruby
531
+ # jwk = JOSE::JWK.from({"k" => "qUg4Yw", "kid" => "eyHC48MN26DvoBpkaudvOVXuI5Sy8fKMxQMYiRWmjFw", "kty" => "oct"})
532
+ # # => #<struct JOSE::JWK
533
+ # # keys=nil,
534
+ # # kty=#<struct JOSE::JWK::KTY_oct oct="\xA9H8c">,
535
+ # # fields=JOSE::Map["kid" => "eyHC48MN26DvoBpkaudvOVXuI5Sy8fKMxQMYiRWmjFw"]>
536
+ # JOSE::JWS.sign(jwk, "test", { "alg" => "HS256" })
537
+ # # => JOSE::SignedMap[
538
+ # # "signature" => "ZEBxtZ4SAW5hYyT7CKxH8dqynTAg-Y24QjkudQMaA_M",
539
+ # # "header" => {"kid"=>"eyHC48MN26DvoBpkaudvOVXuI5Sy8fKMxQMYiRWmjFw"},
540
+ # # "protected" => "eyJhbGciOiJIUzI1NiJ9",
541
+ # # "payload" => "dGVzdA"]
542
+ #
543
+ # *Note:* Signed maps with a `"header"` or other fields will have data loss when used with {JOSE::JWS.compact JOSE::JWS.compact}.
544
+ # @param [JOSE::JWK] jwk
545
+ # @param [String] plain_text
546
+ # @param [JOSE::Map, Hash, String, JOSE::JWS] jws
547
+ # @param [JOSE::Map, Hash] header
548
+ # @return [JOSE::SignedMap]
549
+ def self.sign(jwk, plain_text, jws, header = nil)
550
+ return from(jws).sign(jwk, plain_text, header)
155
551
  end
156
552
 
157
- def sign(key, plain_text, header = nil)
553
+ # Signs the `plain_text` using the `jwk` and algorithm specified by the `jws`.
554
+ # @see JOSE::JWS.sign
555
+ # @param [JOSE::JWK] jwk
556
+ # @param [String] plain_text
557
+ # @param [JOSE::Map, Hash] header
558
+ # @return [JOSE::SignedMap]
559
+ def sign(jwk, plain_text, header = nil)
158
560
  protected_binary = JOSE.urlsafe_encode64(to_binary)
159
561
  payload = JOSE.urlsafe_encode64(plain_text)
160
562
  signing_input = signing_input(plain_text, protected_binary)
161
- signature = JOSE.urlsafe_encode64(alg.sign(key, signing_input))
162
- return signature_to_map(payload, protected_binary, header, key, signature)
563
+ signature = JOSE.urlsafe_encode64(alg.sign(jwk, signing_input))
564
+ return signature_to_map(payload, protected_binary, header, jwk, signature)
565
+ end
566
+
567
+ # Combines `payload` and `protected_binary` based on the `"b64"` setting on the `jws` for the signing input used by {JOSE::JWS.sign JOSE::JWS.sign}.
568
+ #
569
+ # If `"b64"` is set to `false` on the `jws`, the raw `payload` will be used:
570
+ #
571
+ # !!!ruby
572
+ # JOSE::JWS.signing_input("{}", { "alg" => "HS256" })
573
+ # # => "eyJhbGciOiJIUzI1NiJ9.e30"
574
+ # JOSE::JWS.signing_input("{}", { "alg" => "HS256", "b64" => false })
575
+ # # => "eyJhbGciOiJIUzI1NiIsImI2NCI6ZmFsc2V9.{}"
576
+ #
577
+ # @see https://tools.ietf.org/html/draft-ietf-jose-jws-signing-input-options-04 JWS Unencoded Payload Option
578
+ # @param [String] payload
579
+ # @param [JOSE::Map, Hash, String, JOSE::JWS] jws
580
+ # @param [String] protected_binary
581
+ # @return [String]
582
+ def self.signing_input(payload, jws, protected_binary = nil)
583
+ return from(jws).signing_input(payload, protected_binary)
163
584
  end
164
585
 
165
- # See https://tools.ietf.org/html/draft-ietf-jose-jws-signing-input-options-04
166
- def signing_input(payload, protected_binary = JOSE.urlsafe_encode64(to_binary))
586
+ # Combines `payload` and `protected_binary` based on the `"b64"` setting on the `jws` for the signing input used by {JOSE::JWS.sign JOSE::JWS.sign}.
587
+ # @see JOSE::JWS.signing_input
588
+ def signing_input(payload, protected_binary = nil)
167
589
  if b64 == true or b64.nil?
168
590
  payload = JOSE.urlsafe_encode64(payload)
169
591
  end
592
+ protected_binary ||= JOSE.urlsafe_encode64(to_binary)
170
593
  return [protected_binary, '.', payload].join
171
594
  end
172
595
 
173
- def self.verify(key, signed)
596
+ # Verifies the `signed` using the `jwk`.
597
+ #
598
+ # !!!ruby
599
+ # jwk = JOSE::JWK.from({"k" => "qUg4Yw", "kty" => "oct"})
600
+ # # => #<struct JOSE::JWK keys=nil, kty=#<struct JOSE::JWK::KTY_oct oct="\xA9H8c">, fields=JOSE::Map[]>
601
+ # JOSE::JWS.verify(jwk, "eyJhbGciOiJIUzI1NiJ9.e30.5paAJxaOXSqRUIXrP_vJXUZu2SCBH-ojgP4D6Xr6GPU")
602
+ # # => => [true, "{}", #<struct JOSE::JWS alg=#<struct JOSE::JWS::ALG_HMAC hmac=OpenSSL::Digest::SHA256>, b64=nil, fields=JOSE::Map[]>]
603
+ #
604
+ # @param [JOSE::JWK] jwk
605
+ # @param [JOSE::SignedBinary, JOSE::SignedMap, Hash, String] signed
606
+ # @return [[Boolean, String, JOSE::JWS]]
607
+ def self.verify(jwk, signed)
174
608
  if signed.is_a?(String)
175
609
  signed = JOSE::JWS.expand(signed)
176
610
  end
@@ -181,18 +615,49 @@ module JOSE
181
615
  jws = from_binary(JOSE.urlsafe_decode64(signed['protected']))
182
616
  signature = JOSE.urlsafe_decode64(signed['signature'])
183
617
  plain_text = JOSE.urlsafe_decode64(signed['payload'])
184
- return jws.verify(key, plain_text, signature, signed['protected'])
618
+ return jws.verify(jwk, plain_text, signature, signed['protected'])
185
619
  else
186
620
  raise ArgumentError, "'signed' is not a valid signed String, Hash, or JOSE::Map"
187
621
  end
188
622
  end
189
623
 
190
- def verify(key, plain_text, signature, protected_binary = JOSE.urlsafe_encode64(to_binary))
624
+ # Verifies the `signature` using the `jwk`, `plain_text`, and `protected_binary`.
625
+ # @see JOSE::JWS.verify
626
+ # @see JOSE::JWS.verify_strict
627
+ # @param [JOSE::JWK] jwk
628
+ # @param [String] plain_text
629
+ # @param [String] signature
630
+ # @param [String] protected_binary
631
+ # @return [[Boolean, String, JOSE::JWS]]
632
+ def verify(jwk, plain_text, signature, protected_binary = nil)
633
+ protected_binary ||= JOSE.urlsafe_encode64(to_binary)
191
634
  signing_input = signing_input(plain_text, protected_binary)
192
- return alg.verify(key, signing_input, signature), plain_text, self
635
+ return alg.verify(jwk, signing_input, signature), plain_text, self
193
636
  end
194
637
 
195
- def self.verify_strict(key, allow, signed)
638
+ # Same as {JOSE::JWS.verify JOSE::JWS.verify}, but uses `allow` as a whitelist for `"alg"` which are allowed to verify against.
639
+ #
640
+ # If the detected algorithm is not present in `allow`, then `false` is returned.
641
+ #
642
+ # !!!ruby
643
+ # jwk = JOSE::JWK.from({"k" => "qUg4Yw", "kty" => "oct"})
644
+ # # => #<struct JOSE::JWK keys=nil, kty=#<struct JOSE::JWK::KTY_oct oct="\xA9H8c">, fields=JOSE::Map[]>
645
+ # signed_hs256 = JOSE::JWS.sign(jwk, "{}", { "alg" => "HS256" }).compact
646
+ # # => "eyJhbGciOiJIUzI1NiJ9.e30.5paAJxaOXSqRUIXrP_vJXUZu2SCBH-ojgP4D6Xr6GPU"
647
+ # signed_hs512 = JOSE::JWS.sign(jwk, "{}", { "alg" => "HS512" }).compact
648
+ # # => "eyJhbGciOiJIUzUxMiJ9.e30.DN_JCks5rzQiDJJ15E6uJFskAMw-KcasGINKK_4S8xKo7W6tZ-a00ZL8UWOWgE7oHpcFrYnvSpNRldAMp19iyw"
649
+ # JOSE::JWS.verify_strict(jwk, ["HS256"], signed_hs256).first
650
+ # # => true
651
+ # JOSE::JWS.verify_strict(jwk, ["HS256"], signed_hs512).first
652
+ # # => false
653
+ # JOSE::JWS.verify_strict(jwk, ["HS256", "HS512"], signed_hs512).first
654
+ # # => true
655
+ #
656
+ # @param [JOSE::JWK] jwk
657
+ # @param [Array<String>] allow
658
+ # @param [JOSE::SignedBinary, JOSE::SignedMap, Hash, String] signed
659
+ # @return [[Boolean, String, (JOSE::JWS, JOSE::Map)]]
660
+ def self.verify_strict(jwk, allow, signed)
196
661
  if signed.is_a?(String)
197
662
  signed = JOSE::JWS.expand(signed)
198
663
  end
@@ -205,7 +670,7 @@ module JOSE
205
670
  if allow.member?(protected_map['alg'])
206
671
  jws = from_map(protected_map)
207
672
  signature = JOSE.urlsafe_decode64(signed['signature'])
208
- return jws.verify(key, plain_text, signature, signed['protected'])
673
+ return jws.verify(jwk, plain_text, signature, signed['protected'])
209
674
  else
210
675
  return false, plain_text, protected_map
211
676
  end