jose 0.3.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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