sandal 0.3.0 → 0.4.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0f0ff3d982eff5453f5a1db3592b96e6f8a75884
4
- data.tar.gz: b2ea7fad0f11051f2470f1a90bec6803ee10bb09
3
+ metadata.gz: 03610644805fbd043ba5d03d61a1d5926b45d37f
4
+ data.tar.gz: 9622863e1097e93f74740cd112ae2dd4d98a8fd7
5
5
  SHA512:
6
- metadata.gz: b10f36c1a03b93e428533b0df5eb3f4ba9ab18cfdd911ec3e1733f431b3b89c9babcd7cc2c4915787c6fbe9904427b4702ac5062db1f12439644e08be46c8fc0
7
- data.tar.gz: 27de343a94a824e353b1118003c752596b408ab7b2a8381c15af1e079ec407b797518ae7e1b885845fe0a191f658ad0718fa8538265b80db038e19140bb3b807
6
+ metadata.gz: ff2c734b2f07a064bad1701e26d976db390d9ebe2e666c41f066d80948486f7b16dca0ff189ba6772826dae589da2390195695fe3fa187e03b11cfc398325094
7
+ data.tar.gz: 77ea15a46b9170678cc34382f690e283ee0b1ec448b7e1caa29edfdfe053e5b41ee9615278687dd711c90bfd4828b0922714a6acd1ecce5e98ee27dd518c41a8
@@ -1,3 +1,20 @@
1
+ ## 0.4.0 (30 April 2013)
2
+
3
+ Features:
4
+
5
+ - The decode_token method now recursively decodes/decrypts nested tokens rather than requiring multiple calls.
6
+
7
+ Breaking changes:
8
+
9
+ - The decode_token method is now used for both signed and encrypted tokens; the decrypt_token method has been removed.
10
+
11
+ Bug fixes:
12
+
13
+ - The 'none' algorithm value is now always set in plaintext tokens.
14
+ - The zip parameter is now used when encrypting/decrypting JWE tokens.
15
+ - The Concat KDF function now works correctly when inputs have different string encodings by treating them all as binary.
16
+ - Errors related to jwt_base64_encode not being found when running under rack (although they worked fine under rspec) are resolved.
17
+
1
18
  ## 0.3.0 (20 April 2013)
2
19
 
3
20
  Features:
data/README.md CHANGED
@@ -83,7 +83,7 @@ jwe_token = Sandal.encrypt_token(jws_token, encrypter, {
83
83
  Decrypting example:
84
84
 
85
85
  ```ruby
86
- jws_token = Sandal.decrypt_token(jwe_token) do |header|
86
+ jws_token = Sandal.decode_token(jwe_token) do |header|
87
87
  if header['kid'] == 'your rsa key'
88
88
  alg = Sandal::Enc::Alg::RSA_OAEP.new(File.Read('/path/to/rsa_private_key.pem'))
89
89
  Sandal::Enc::A128GCM.new(alg)
@@ -1,5 +1,6 @@
1
1
  require 'multi_json'
2
2
  require 'openssl'
3
+ require 'zlib'
3
4
  require 'sandal/version'
4
5
  require 'sandal/claims'
5
6
  require 'sandal/enc'
@@ -48,7 +49,7 @@ module Sandal
48
49
  ignore_signature: false,
49
50
  max_clock_skew: 0,
50
51
  valid_iss: [],
51
- valid_aud: [],
52
+ valid_aud: []
52
53
  }
53
54
 
54
55
  # Overrides the default options.
@@ -60,6 +61,30 @@ module Sandal
60
61
  DEFAULT_OPTIONS.merge!(defaults)
61
62
  end
62
63
 
64
+ # Checks whether a token is encrypted.
65
+ #
66
+ # @param token [String or Array] The token, or token parts.
67
+ # @return [Boolean] true if the token is encrypted; otherwise false.
68
+ def self.is_encrypted?(token)
69
+ if token.is_a?(String)
70
+ token.count('.') == 4
71
+ else
72
+ token.count == 5
73
+ end
74
+ end
75
+
76
+ # Checks whether a token is signed.
77
+ #
78
+ # @param token [String or Array] The token, or token parts.
79
+ # @return [Boolean] true if the token is signed; otherwise false.
80
+ def self.is_signed?(token)
81
+ if token.is_a?(String)
82
+ !token.end_with?('.') && token.count('.') == 2
83
+ else
84
+ token.count == 3 && !token[2].nil? && !token[2].empty?
85
+ end
86
+ end
87
+
63
88
  # Creates a signed JSON Web Token.
64
89
  #
65
90
  # @param payload [String or Hash] The payload of the token. Hashes will be
@@ -73,7 +98,7 @@ module Sandal
73
98
  signer ||= Sandal::Sig::NONE
74
99
 
75
100
  header = {}
76
- header['alg'] = signer.name if signer.name != Sandal::Sig::NONE.name
101
+ header['alg'] = signer.name
77
102
  header = header_fields.merge(header) if header_fields
78
103
  header = MultiJson.dump(header)
79
104
 
@@ -84,41 +109,6 @@ module Sandal
84
109
  [sec_input, jwt_base64_encode(signature)].join('.')
85
110
  end
86
111
 
87
- # Decodes and validates a signed JSON Web Token.
88
- #
89
- # The block is called with the token header as the first parameter, and should
90
- # return the appropriate signature method to validate the signature. It can
91
- # optionally have a second options parameter which can be used to override the
92
- # {DEFAULT_OPTIONS} on a per-token basis.
93
- #
94
- # @param token [String] The encoded JSON Web Token.
95
- # @yieldparam header [Hash] The JWT header values.
96
- # @yieldparam options [Hash] (Optional) A hash that can be used to override
97
- # the default options.
98
- # @yieldreturn [#valid?] The signature validator.
99
- # @return [Hash or String] The payload of the token as a Hash if it was JSON,
100
- # otherwise as a String.
101
- # @raise [Sandal::ClaimError] One or more claims in the token is invalid.
102
- # @raise [Sandal::TokenError] The token format is invalid, or validation of
103
- # the token failed.
104
- def self.decode_token(token)
105
- parts = token.split('.')
106
- header, payload, signature = decode_jws_token_parts(parts)
107
-
108
- options = DEFAULT_OPTIONS.clone
109
- validator = yield header, options if block_given?
110
- validator ||= Sandal::Sig::NONE
111
-
112
- unless options[:ignore_signature]
113
- secured_input = parts.take(2).join('.')
114
- unless validator.valid?(signature, secured_input)
115
- raise TokenError, 'Invalid signature.'
116
- end
117
- end
118
-
119
- parse_and_validate(payload, header['cty'], options)
120
- end
121
-
122
112
  # Creates an encrypted JSON Web Token.
123
113
  #
124
114
  # @param payload [String] The payload of the token.
@@ -132,53 +122,85 @@ module Sandal
132
122
  header['alg'] = encrypter.alg.name
133
123
  header = header_fields.merge(header) if header_fields
134
124
 
125
+ if header.has_key?('zip')
126
+ unless header['zip'] == 'DEF'
127
+ raise ArgumentError, 'Invalid zip algorithm.'
128
+ end
129
+ payload = Zlib::Deflate.deflate(payload, Zlib::BEST_COMPRESSION)
130
+ end
131
+
135
132
  encrypter.encrypt(header, payload)
136
133
  end
137
134
 
138
- # Decrypts and validates an encrypted JSON Web Token.
135
+ # Decodes and validates a signed and/or encrypted JSON Web Token, recursing
136
+ # into any nested tokens, and returns the payload.
139
137
  #
140
138
  # The block is called with the token header as the first parameter, and should
141
- # return the appropriate encryption method to decrypt the token. It can
142
- # optionally have a second options parameter which can be used to override the
143
- # {DEFAULT_OPTIONS} on a per-token basis.
139
+ # return the appropriate signature or decryption method to either validate the
140
+ # signature or decrypt the token as applicable. When the tokens are nested,
141
+ # this block will be called once per token. It can optionally have a second
142
+ # options parameter which can be used to override the {DEFAULT_OPTIONS} on a
143
+ # per-token basis; options are not persisted between yields.
144
144
  #
145
- # @param token [String] The encrypted JSON Web Token.
145
+ # @param token [String] The encoded JSON Web Token.
146
+ # @param depth [Integer] The maximum depth of token nesting to decode to.
146
147
  # @yieldparam header [Hash] The JWT header values.
147
- # @yieldparam options [Hash] (Optional) A hash that can be used to override
148
+ # @yieldparam options [Hash] (Optional) A hash that can be used to override
148
149
  # the default options.
149
- # @yieldreturn [#decrypt] The token decrypter.
150
- # @return [Hash or String] The payload of the token as a Hash if it was JSON,
150
+ # @yieldreturn [#valid? or #decrypt] The signature validator if the token is
151
+ # signed, or the token decrypter if the token is encrypted.
152
+ # @return [Hash or String] The payload of the token as a Hash if it was JSON,
151
153
  # otherwise as a String.
152
154
  # @raise [Sandal::ClaimError] One or more claims in the token is invalid.
153
- # @raise [Sandal::TokenError] The token format is invalid, or decryption or
154
- # validation of the token failed.
155
- def self.decrypt_token(token)
155
+ # @raise [Sandal::TokenError] The token format is invalid, or validation of
156
+ # the token failed.
157
+ def self.decode_token(token, depth = 16)
156
158
  parts = token.split('.')
157
- decoded_parts = decode_jwe_token_parts(parts)
159
+ decoded_parts = decode_token_parts(parts)
158
160
  header = decoded_parts[0]
159
161
 
160
162
  options = DEFAULT_OPTIONS.clone
161
- decrypter = yield header, options if block_given?
163
+ decoder = yield header, options if block_given?
164
+
165
+ if is_encrypted?(parts)
166
+ payload = decoder.decrypt(parts, decoded_parts)
167
+ if header.has_key?('zip')
168
+ unless header['zip'] == 'DEF'
169
+ raise Sandal::TokenError, 'Invalid zip algorithm.'
170
+ end
171
+ payload = Zlib::Inflate.inflate(payload)
172
+ end
173
+ else
174
+ payload = decoded_parts[1]
175
+ unless options[:ignore_signature]
176
+ validate_signature(parts, decoded_parts[2], decoder)
177
+ end
178
+ end
162
179
 
163
- payload = decrypter.decrypt(parts, decoded_parts)
164
- parse_and_validate(payload, header['cty'], options)
180
+ if header['cty'] == 'JWT'
181
+ if depth > 0
182
+ if block_given?
183
+ decode_token(payload, depth - 1, &Proc.new)
184
+ else
185
+ decode_token(payload, depth - 1)
186
+ end
187
+ else
188
+ payload
189
+ end
190
+ else
191
+ parse_and_validate(payload, options)
192
+ end
165
193
  end
166
194
 
167
- private
195
+ private
168
196
 
169
- # Decodes the parts of a JWS token.
170
- def self.decode_jws_token_parts(parts)
171
- parts = decode_token_parts(parts)
172
- parts << '' if parts.length == 2
173
- raise TokenError, 'Invalid token format.' unless parts.length == 3
174
- parts
175
- end
176
-
177
- # Decodes the parts of a JWE token.
178
- def self.decode_jwe_token_parts(parts)
179
- parts = decode_token_parts(parts)
180
- raise TokenError, 'Invalid token format.' unless parts.length == 5
181
- parts
197
+ # Decodes and validates a signed JSON Web Token.
198
+ def self.validate_signature(parts, signature, validator)
199
+ validator ||= Sandal::Sig::NONE
200
+ secured_input = parts.take(2).join('.')
201
+ unless validator.valid?(signature, secured_input)
202
+ raise TokenError, 'Invalid signature.'
203
+ end
182
204
  end
183
205
 
184
206
  # Decodes the parts of a token.
@@ -191,9 +213,7 @@ private
191
213
  end
192
214
 
193
215
  # Parses the content of a token and validates the claims if is JSON claims.
194
- def self.parse_and_validate(payload, content_type, options)
195
- return payload if content_type == 'JWT'
196
-
216
+ def self.parse_and_validate(payload, options)
197
217
  claims = MultiJson.load(payload) rescue nil
198
218
  if claims
199
219
  claims.extend(Sandal::Claims).validate_claims(options)
@@ -1,6 +1,57 @@
1
+ require 'openssl'
2
+
1
3
  module Sandal
2
4
  # Contains encryption (JWE) functionality.
3
5
  module Enc
6
+
7
+ # The Concat Key Derivation Function.
8
+ #
9
+ # @param digest [OpenSSL::Digest or String] The digest for the algorithm.
10
+ # @param key [String] The key or shared secret.
11
+ # @param keydatalen [Integer] The desired output size in bits.
12
+ # @param algorithm_id [String] The name of the algorithm.
13
+ # @param party_u_info [String or 0] The partyUInfo.
14
+ # @param party_v_info [String or 0] The partyVInfo.
15
+ # @param supp_pub_info [String] Supplementary public info.
16
+ # @param supp_priv_info [String] Supplementary private info.
17
+ # @return [String] The derived keying material.
18
+ def self.concat_kdf(digest, key, keydatalen, algorithm_id,
19
+ party_u_info, party_v_info,
20
+ supp_pub_info = nil, supp_priv_info = nil)
21
+ digest = OpenSSL::Digest.new(digest) if digest.is_a?(String)
22
+ rounds = (keydatalen / (digest.digest_length * 8.0)).ceil
23
+
24
+ round_input = concat_kdf_round_input(key, keydatalen, algorithm_id,
25
+ party_u_info, party_v_info,
26
+ supp_pub_info, supp_priv_info)
27
+
28
+ (1..rounds).reduce('') do |output, round|
29
+ hash = digest.digest([round].pack('N') + round_input)
30
+ if round == rounds
31
+ round_bits = keydatalen % (digest.digest_length * 8)
32
+ hash = hash[0...(round_bits / 8)] unless round_bits == 0
33
+ end
34
+ output << hash
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ # The round input for the Concat KDF function (excluding round number).
41
+ def self.concat_kdf_round_input(key, keydatalen, algorithm_id,
42
+ party_u_info, party_v_info,
43
+ supp_pub_info, supp_priv_info)
44
+ input = ''.force_encoding('binary')
45
+ input << key.force_encoding('binary')
46
+ input << [keydatalen].pack('N')
47
+ input << algorithm_id.force_encoding('binary')
48
+ input << (party_u_info == 0 ? [0].pack('N') : party_u_info.force_encoding('binary'))
49
+ input << (party_v_info == 0 ? [0].pack('N') : party_v_info.force_encoding('binary'))
50
+ input << supp_pub_info.force_encoding('binary') if supp_pub_info
51
+ input << supp_priv_info.force_encoding('binary') if supp_priv_info
52
+ input
53
+ end
54
+
4
55
  end
5
56
  end
6
57
 
@@ -7,7 +7,7 @@ module Sandal
7
7
  # Base implementation of the AES/CBC+HMAC-SHA family of encryption
8
8
  # algorithms.
9
9
  class ACBC_HS
10
- extend Sandal::Util
10
+ include Sandal::Util
11
11
 
12
12
  # The JWA name of the encryption.
13
13
  attr_reader :name
@@ -76,25 +76,12 @@ module Sandal
76
76
 
77
77
  # Derives the content encryption key from the content master key.
78
78
  def derive_encryption_key(cmk)
79
- derive_content_key('Encryption', cmk, @aes_size)
79
+ Sandal::Enc.concat_kdf(@digest, cmk, @aes_size, @name, 0, 0, 'Encryption')
80
80
  end
81
81
 
82
82
  # Derives the content integrity key from the content master key.
83
83
  def derive_integrity_key(cmk)
84
- derive_content_key('Integrity', cmk, @sha_size)
85
- end
86
-
87
- # Derives content keys using the Concat KDF.
88
- def derive_content_key(label, cmk, size)
89
- hash_input = [1].pack('N')
90
- hash_input << cmk
91
- hash_input << [size].pack('N')
92
- hash_input << @name.encode('utf-8')
93
- hash_input << [0].pack('N')
94
- hash_input << [0].pack('N')
95
- hash_input << label.encode('us-ascii')
96
- hash = @digest.digest(hash_input)
97
- hash[0..((size / 8) - 1)]
84
+ Sandal::Enc.concat_kdf(@digest, cmk, @sha_size, @name, 0, 0, 'Integrity')
98
85
  end
99
86
 
100
87
  end
@@ -6,7 +6,7 @@ module Sandal
6
6
 
7
7
  # Base implementation of the AES/GCM family of encryption algorithms.
8
8
  class AGCM
9
- extend Sandal::Util
9
+ include Sandal::Util
10
10
 
11
11
  # The JWA name of the encryption.
12
12
  attr_reader :name
@@ -5,7 +5,7 @@ module Sandal
5
5
 
6
6
  # Base implementation of the HMAC-SHA family of signature algorithms.
7
7
  class HS
8
- extend Sandal::Util
8
+ include Sandal::Util
9
9
 
10
10
  # @return [String] The JWA name of the algorithm.
11
11
  attr_reader :name
@@ -22,7 +22,7 @@ module Sandal
22
22
  def jwt_strings_equal?(a, b)
23
23
  return true if a.object_id == b.object_id
24
24
  return false if a.nil? || b.nil? || a.length != b.length
25
- a.codepoints.zip(b.codepoints).reduce(0) { |r, (a, b)| r |= a ^ b } == 0
25
+ a.codepoints.zip(b.codepoints).reduce(0) { |r, (x, y)| r |= x ^ y } == 0
26
26
  end
27
27
 
28
28
  # Base64 encodes a string, in compliance with the JWT specification.
@@ -1,4 +1,4 @@
1
1
  module Sandal
2
2
  # The semantic version of the library.
3
- VERSION = '0.3.0'
3
+ VERSION = '0.4.0'
4
4
  end
@@ -19,7 +19,7 @@ describe Sandal::Enc::A128CBC_HS256 do
19
19
 
20
20
  it 'can decrypt the example token' do
21
21
  token = 'eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMTI4Q0JDK0hTMjU2In0.ZmnlqWgjXyqwjr7cXHys8F79anIUI6J2UWdAyRQEcGBU-KPHsePM910_RoTDGu1IW40Dn0dvcdVEjpJcPPNIbzWcMxDi131Ejeg-b8ViW5YX5oRdYdiR4gMSDDB3mbkInMNUFT-PK5CuZRnHB2rUK5fhPuF6XFqLLZCG5Q_rJm6Evex-XLcNQAJNa1-6CIU12Wj3mPExxw9vbnsQDU7B4BfmhdyiflLA7Ae5ZGoVRl3A__yLPXxRjHFhpOeDp_adx8NyejF5cz9yDKULugNsDMdlHeJQOMGVLYaSZt3KP6aWNSqFA1PHDg-10ceuTEtq_vPE4-Gtev4N4K4Eudlj4Q.AxY8DCtDaGlsbGljb3RoZQ.Rxsjg6PIExcmGSF7LnSEkDqWIKfAw1wZz2XpabV5PwQsolKwEauWYZNE9Q1hZJEZ.8LXqMd0JLGsxMaB5uoNaMpg7uUW_p40RlaZHCwMIyzk'
22
- payload = Sandal.decrypt_token(token) do |header|
22
+ payload = Sandal.decode_token(token) do |header|
23
23
  alg = Sandal::Enc::Alg::RSA1_5.new(@rsa)
24
24
  Sandal::Enc::A128CBC_HS256.new(alg)
25
25
  end
@@ -28,7 +28,7 @@ describe Sandal::Enc::A128CBC_HS256 do
28
28
 
29
29
  it 'raises a token error when the integrity value is changed' do
30
30
  token = 'eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMTI4Q0JDK0hTMjU2In0.ZmnlqWgjXyqwjr7cXHys8F79anIUI6J2UWdAyRQEcGBU-KPHsePM910_RoTDGu1IW40Dn0dvcdVEjpJcPPNIbzWcMxDi131Ejeg-b8ViW5YX5oRdYdiR4gMSDDB3mbkInMNUFT-PK5CuZRnHB2rUK5fhPuF6XFqLLZCG5Q_rJm6Evex-XLcNQAJNa1-6CIU12Wj3mPExxw9vbnsQDU7B4BfmhdyiflLA7Ae5ZGoVRl3A__yLPXxRjHFhpOeDp_adx8NyejF5cz9yDKULugNsDMdlHeJQOMGVLYaSZt3KP6aWNSqFA1PHDg-10ceuTEtq_vPE4-Gtev4N4K4Eudlj4Q.AxY8DCtDaGlsbGljb3RoZQ.Rxsjg6PIExcmGSF7LnSEkDqWIKfAw1wZz2XpabV5PwQsolKwEauWYZNE9Q1hZJEZ.7V5ZDko0v_mf2PAc4JMiUg'
31
- expect { Sandal.decrypt_token(token) do |header|
31
+ expect { Sandal.decode_token(token) do |header|
32
32
  alg = Sandal::Enc::Alg::RSA1_5.new(@rsa)
33
33
  Sandal::Enc::A128CBC_HS256.new(alg)
34
34
  end }.to raise_error Sandal::TokenError, 'Invalid integrity value.'
@@ -38,7 +38,7 @@ describe Sandal::Enc::A128CBC_HS256 do
38
38
 
39
39
  it 'raises a token error when the RSA keys JWE section A.2 are changed' do
40
40
  token = 'eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMTI4Q0JDK0hTMjU2In0.ZmnlqWgjXyqwjr7cXHys8F79anIUI6J2UWdAyRQEcGBU-KPHsePM910_RoTDGu1IW40Dn0dvcdVEjpJcPPNIbzWcMxDi131Ejeg-b8ViW5YX5oRdYdiR4gMSDDB3mbkInMNUFT-PK5CuZRnHB2rUK5fhPuF6XFqLLZCG5Q_rJm6Evex-XLcNQAJNa1-6CIU12Wj3mPExxw9vbnsQDU7B4BfmhdyiflLA7Ae5ZGoVRl3A__yLPXxRjHFhpOeDp_adx8NyejF5cz9yDKULugNsDMdlHeJQOMGVLYaSZt3KP6aWNSqFA1PHDg-10ceuTEtq_vPE4-Gtev4N4K4Eudlj4Q.AxY8DCtDaGlsbGljb3RoZQ.Rxsjg6PIExcmGSF7LnSEkDqWIKfAw1wZz2XpabV5PwQsolKwEauWYZNE9Q1hZJEZ.8LXqMd0JLGsxMaB5uoNaMpg7uUW_p40RlaZHCwMIyzk'
41
- expect { Sandal.decrypt_token(token) do |header|
41
+ expect { Sandal.decode_token(token) do |header|
42
42
  rsa = OpenSSL::PKey::RSA.new(2048)
43
43
  alg = Sandal::Enc::Alg::RSA1_5.new(rsa)
44
44
  Sandal::Enc::A128CBC_HS256.new(alg)
@@ -21,7 +21,7 @@ describe Sandal::Enc::A256GCM do
21
21
 
22
22
  it 'can decrypt the example token' do
23
23
  token = 'eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ.M2XxpbORKezKSzzQL_95-GjiudRBTqn_omS8z9xgoRb7L0Jw5UsEbxmtyHn2T71mrZLkjg4Mp8gbhYoltPkEOHvAopz25-vZ8C2e1cOaAo5WPcbSIuFcB4DjBOM3t0UAO6JHkWLuAEYoe58lcxIQneyKdaYSLbV9cKqoUoFQpvKWYRHZbfszIyfsa18rmgTjzrtLDTPnc09DSJE24aQ8w3i8RXEDthW9T1J6LsTH_vwHdwUgkI-tC2PNeGrnM-dNSfzF3Y7-lwcGy0FsdXkPXytvDV7y4pZeeUiQ-0VdibIN2AjjfW60nfrPuOjepMFG6BBBbR37pHcyzext9epOAQ.48V1_ALb6US04U3b._e21tGGhac_peEFkLXr2dMPUZiUkrw.7V5ZDko0v_mf2PAc4JMiUg'
24
- payload = Sandal.decrypt_token(token) do |header|
24
+ payload = Sandal.decode_token(token) do |header|
25
25
  alg = Sandal::Enc::Alg::RSA_OAEP.new(@rsa)
26
26
  Sandal::Enc::A256GCM.new(alg)
27
27
  end
@@ -30,7 +30,7 @@ describe Sandal::Enc::A256GCM do
30
30
 
31
31
  it 'raises a token error when the integrity value is changed' do
32
32
  token = 'eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ.M2XxpbORKezKSzzQL_95-GjiudRBTqn_omS8z9xgoRb7L0Jw5UsEbxmtyHn2T71mrZLkjg4Mp8gbhYoltPkEOHvAopz25-vZ8C2e1cOaAo5WPcbSIuFcB4DjBOM3t0UAO6JHkWLuAEYoe58lcxIQneyKdaYSLbV9cKqoUoFQpvKWYRHZbfszIyfsa18rmgTjzrtLDTPnc09DSJE24aQ8w3i8RXEDthW9T1J6LsTH_vwHdwUgkI-tC2PNeGrnM-dNSfzF3Y7-lwcGy0FsdXkPXytvDV7y4pZeeUiQ-0VdibIN2AjjfW60nfrPuOjepMFG6BBBbR37pHcyzext9epOAQ.48V1_ALb6US04U3b._e21tGGhac_peEFkLXr2dMPUZiUkrw.8LXqMd0JLGsxMaB5uoNaMpg7uUW_p40RlaZHCwMIyzk'
33
- expect { Sandal.decrypt_token(token) do |header|
33
+ expect { Sandal.decode_token(token) do |header|
34
34
  alg = Sandal::Enc::Alg::RSA_OAEP.new(@rsa)
35
35
  Sandal::Enc::A256GCM.new(alg)
36
36
  end }.to raise_error Sandal::TokenError, 'Invalid token.'
@@ -40,7 +40,7 @@ describe Sandal::Enc::A256GCM do
40
40
 
41
41
  it 'raises a token error when the RSA keys JWE section A.1 are changed' do
42
42
  token = 'eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ.M2XxpbORKezKSzzQL_95-GjiudRBTqn_omS8z9xgoRb7L0Jw5UsEbxmtyHn2T71mrZLkjg4Mp8gbhYoltPkEOHvAopz25-vZ8C2e1cOaAo5WPcbSIuFcB4DjBOM3t0UAO6JHkWLuAEYoe58lcxIQneyKdaYSLbV9cKqoUoFQpvKWYRHZbfszIyfsa18rmgTjzrtLDTPnc09DSJE24aQ8w3i8RXEDthW9T1J6LsTH_vwHdwUgkI-tC2PNeGrnM-dNSfzF3Y7-lwcGy0FsdXkPXytvDV7y4pZeeUiQ-0VdibIN2AjjfW60nfrPuOjepMFG6BBBbR37pHcyzext9epOAQ.48V1_ALb6US04U3b._e21tGGhac_peEFkLXr2dMPUZiUkrw.8LXqMd0JLGsxMaB5uoNaMpg7uUW_p40RlaZHCwMIyzk'
43
- expect { Sandal.decrypt_token(token) do |header|
43
+ expect { Sandal.decode_token(token) do |header|
44
44
  rsa = OpenSSL::PKey::RSA.new(2048)
45
45
  alg = Sandal::Enc::Alg::RSA_OAEP.new(rsa)
46
46
  Sandal::Enc::A256GCM.new(alg)
@@ -0,0 +1,40 @@
1
+ require 'helper'
2
+ require 'openssl'
3
+
4
+ include Sandal::Util
5
+
6
+ describe Sandal::Enc::Alg::RSA1_5 do
7
+
8
+ context '#name' do
9
+
10
+ it 'is "RSA1_5"' do
11
+ alg = Sandal::Enc::Alg::RSA1_5.new(OpenSSL::PKey::RSA.new(2048))
12
+ alg.name.should == 'RSA1_5'
13
+ end
14
+
15
+ end
16
+
17
+ context '#decrypt_cmk' do
18
+
19
+ it 'can decrypt the encypted content master key from JWE section A.2', :jruby_incompatible do
20
+ key = OpenSSL::PKey::RSA.new(2048)
21
+ key.n = make_bn([177, 119, 33, 13, 164, 30, 108, 121, 207, 136, 107, 242, 12, 224, 19, 226, 198, 134, 17, 71, 173, 75, 42, 61, 48, 162, 206, 161, 97, 108, 185, 234, 226, 219, 118, 206, 118, 5, 169, 224, 60, 181, 90, 85, 51, 123, 6, 224, 4, 122, 29, 230, 151, 12, 244, 127, 121, 25, 4, 85, 220, 144, 215, 110, 130, 17, 68, 228, 129, 138, 7, 130, 231, 40, 212, 214, 17, 179, 28, 124, 151, 178, 207, 20, 14, 154, 222, 113, 176, 24, 198, 73, 211, 113, 9, 33, 178, 80, 13, 25, 21, 25, 153, 212, 206, 67, 154, 147, 70, 194, 192, 183, 160, 83, 98, 236, 175, 85, 23, 97, 75, 199, 177, 73, 145, 50, 253, 206, 32, 179, 254, 236, 190, 82, 73, 67, 129, 253, 252, 220, 108, 136, 138, 11, 192, 1, 36, 239, 228, 55, 81, 113, 17, 25, 140, 63, 239, 146, 3, 172, 96, 60, 227, 233, 64, 255, 224, 173, 225, 228, 229, 92, 112, 72, 99, 97, 26, 87, 187, 123, 46, 50, 90, 202, 117, 73, 10, 153, 47, 224, 178, 163, 77, 48, 46, 154, 33, 148, 34, 228, 33, 172, 216, 89, 46, 225, 127, 68, 146, 234, 30, 147, 54, 146, 5, 133, 45, 78, 254, 85, 55, 75, 213, 86, 194, 218, 215, 163, 189, 194, 54, 6, 83, 36, 18, 153, 53, 7, 48, 89, 35, 66, 144, 7, 65, 154, 13, 97, 75, 55, 230, 132, 3, 13, 239, 71])
22
+ key.e = make_bn([1, 0, 1])
23
+ key.d = make_bn([84, 80, 150, 58, 165, 235, 242, 123, 217, 55, 38, 154, 36, 181, 221, 156, 211, 215, 100, 164, 90, 88, 40, 228, 83, 148, 54, 122, 4, 16, 165, 48, 76, 194, 26, 107, 51, 53, 179, 165, 31, 18, 198, 173, 78, 61, 56, 97, 252, 158, 140, 80, 63, 25, 223, 156, 36, 203, 214, 252, 120, 67, 180, 167, 3, 82, 243, 25, 97, 214, 83, 133, 69, 16, 104, 54, 160, 200, 41, 83, 164, 187, 70, 153, 111, 234, 242, 158, 175, 28, 198, 48, 211, 45, 148, 58, 23, 62, 227, 74, 52, 117, 42, 90, 41, 249, 130, 154, 80, 119, 61, 26, 193, 40, 125, 10, 152, 174, 227, 225, 205, 32, 62, 66, 6, 163, 100, 99, 219, 19, 253, 25, 105, 80, 201, 29, 252, 157, 237, 69, 1, 80, 171, 167, 20, 196, 156, 109, 249, 88, 0, 3, 152, 38, 165, 72, 87, 6, 152, 71, 156, 214, 16, 71, 30, 82, 51, 103, 76, 218, 63, 9, 84, 163, 249, 91, 215, 44, 238, 85, 101, 240, 148, 1, 82, 224, 91, 135, 105, 127, 84, 171, 181, 152, 210, 183, 126, 24, 46, 196, 90, 173, 38, 245, 219, 186, 222, 27, 240, 212, 194, 15, 66, 135, 226, 178, 190, 52, 245, 74, 65, 224, 81, 100, 85, 25, 204, 165, 203, 187, 175, 84, 100, 82, 15, 11, 23, 202, 151, 107, 54, 41, 207, 3, 136, 229, 134, 131, 93, 139, 50, 182, 204, 93, 130, 89])
24
+ cmk = [4, 211, 31, 197, 84, 157, 252, 254, 11, 100, 157, 250, 63, 170, 106, 206, 107, 124, 212, 45, 111, 107, 9, 219, 200, 177, 0, 240, 143, 156, 44, 207].pack('C*')
25
+ encrypted_cmk = jwt_base64_decode('ZmnlqWgjXyqwjr7cXHys8F79anIUI6J2UWdAyRQEcGBU-KPHsePM910_RoTDGu1IW40Dn0dvcdVEjpJcPPNIbzWcMxDi131Ejeg-b8ViW5YX5oRdYdiR4gMSDDB3mbkInMNUFT-PK5CuZRnHB2rUK5fhPuF6XFqLLZCG5Q_rJm6Evex-XLcNQAJNa1-6CIU12Wj3mPExxw9vbnsQDU7B4BfmhdyiflLA7Ae5ZGoVRl3A__yLPXxRjHFhpOeDp_adx8NyejF5cz9yDKULugNsDMdlHeJQOMGVLYaSZt3KP6aWNSqFA1PHDg-10ceuTEtq_vPE4-Gtev4N4K4Eudlj4Q')
26
+ alg = Sandal::Enc::Alg::RSA1_5.new(key)
27
+ alg.decrypt_cmk(encrypted_cmk).should == cmk
28
+ end
29
+
30
+ it 'raises a TokenError when the wrong key is used for decryption' do
31
+ key = OpenSSL::PKey::RSA.new(2048)
32
+ cmk = [4, 211, 31, 197, 84, 157, 252, 254, 11, 100, 157, 250, 63, 170, 106, 206, 107, 124, 212, 45, 111, 107, 9, 219, 200, 177, 0, 240, 143, 156, 44, 207].pack('C*')
33
+ encrypted_cmk = jwt_base64_decode('ZmnlqWgjXyqwjr7cXHys8F79anIUI6J2UWdAyRQEcGBU-KPHsePM910_RoTDGu1IW40Dn0dvcdVEjpJcPPNIbzWcMxDi131Ejeg-b8ViW5YX5oRdYdiR4gMSDDB3mbkInMNUFT-PK5CuZRnHB2rUK5fhPuF6XFqLLZCG5Q_rJm6Evex-XLcNQAJNa1-6CIU12Wj3mPExxw9vbnsQDU7B4BfmhdyiflLA7Ae5ZGoVRl3A__yLPXxRjHFhpOeDp_adx8NyejF5cz9yDKULugNsDMdlHeJQOMGVLYaSZt3KP6aWNSqFA1PHDg-10ceuTEtq_vPE4-Gtev4N4K4Eudlj4Q')
34
+ alg = Sandal::Enc::Alg::RSA1_5.new(key)
35
+ expect { alg.decrypt_cmk(encrypted_cmk) }.to raise_error Sandal::TokenError, 'Cannot decrypt content master key.'
36
+ end
37
+
38
+ end
39
+
40
+ end
@@ -8,7 +8,7 @@ shared_examples 'algorithm compatibility' do |enc_class|
8
8
  content_master_key = SecureRandom.random_bytes(32)
9
9
  encrypter = enc_class.new(Sandal::Enc::Alg::Direct.new(content_master_key))
10
10
  token = Sandal.encrypt_token(payload, encrypter)
11
- output = Sandal.decrypt_token(token) { encrypter }
11
+ output = Sandal.decode_token(token) { encrypter }
12
12
  output.should == payload
13
13
  end
14
14
 
@@ -17,7 +17,7 @@ shared_examples 'algorithm compatibility' do |enc_class|
17
17
  rsa = OpenSSL::PKey::RSA.new(2048)
18
18
  encrypter = enc_class.new(Sandal::Enc::Alg::RSA1_5.new(rsa.public_key))
19
19
  token = Sandal.encrypt_token(payload, encrypter)
20
- output = Sandal.decrypt_token(token) do
20
+ output = Sandal.decode_token(token) do
21
21
  enc_class.new(Sandal::Enc::Alg::RSA1_5.new(rsa))
22
22
  end
23
23
  output.should == payload
@@ -28,7 +28,7 @@ shared_examples 'algorithm compatibility' do |enc_class|
28
28
  rsa = OpenSSL::PKey::RSA.new(2048)
29
29
  encrypter = enc_class.new(Sandal::Enc::Alg::RSA_OAEP.new(rsa.public_key))
30
30
  token = Sandal.encrypt_token(payload, encrypter)
31
- output = Sandal.decrypt_token(token) do
31
+ output = Sandal.decode_token(token) do
32
32
  enc_class.new(Sandal::Enc::Alg::RSA_OAEP.new(rsa))
33
33
  end
34
34
  output.should == payload
@@ -1,8 +1,38 @@
1
1
  require 'helper'
2
2
  require 'openssl'
3
+ require 'multi_json'
3
4
 
4
5
  describe Sandal do
5
6
 
7
+ context '#encrypt_token' do
8
+
9
+ it 'supports zip using the DEFLATE algorithm' do
10
+ payload = 'some payload to be zipped'
11
+ private_key = OpenSSL::PKey::RSA.new(2048)
12
+ encrypter = Sandal::Enc::A128CBC_HS256.new(Sandal::Enc::Alg::RSA1_5.new(private_key.public_key))
13
+ token = Sandal.encrypt_token(payload, encrypter, { 'zip' => 'DEF' })
14
+ decoded_payload = Sandal.decode_token(token) do |header|
15
+ Sandal::Enc::A128CBC_HS256.new(Sandal::Enc::Alg::RSA1_5.new(private_key))
16
+ end
17
+ decoded_payload.should == payload
18
+ end
19
+
20
+ it 'raises an ArgumentError if the zip parameter is present and nil' do
21
+ encrypter = Sandal::Enc::A128CBC_HS256.new(Sandal::Enc::Alg::RSA1_5.new(OpenSSL::PKey::RSA.new(2048)))
22
+ expect {
23
+ Sandal.encrypt_token('any payload', encrypter, { 'zip' => nil })
24
+ }.to raise_error ArgumentError, 'Invalid zip algorithm.'
25
+ end
26
+
27
+ it 'raises an ArgumentError if the zip parameter is present and not "DEF"' do
28
+ encrypter = Sandal::Enc::A128CBC_HS256.new(Sandal::Enc::Alg::RSA1_5.new(OpenSSL::PKey::RSA.new(2048)))
29
+ expect {
30
+ Sandal.encrypt_token('any payload', encrypter, { 'zip' => 'INVALID' })
31
+ }.to raise_error ArgumentError, 'Invalid zip algorithm.'
32
+ end
33
+
34
+ end
35
+
6
36
  it 'raises a token error when the token format is invalid' do
7
37
  expect { Sandal.decode_token('not a valid token') }.to raise_error Sandal::TokenError
8
38
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sandal
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Greg Beech
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-04-20 00:00:00.000000000 Z
11
+ date: 2013-04-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: multi_json
@@ -164,6 +164,7 @@ files:
164
164
  - spec/sandal/enc/a256cbc_hs512_spec.rb
165
165
  - spec/sandal/enc/a256gcm_spec.rb
166
166
  - spec/sandal/enc/alg/direct_spec.rb
167
+ - spec/sandal/enc/alg/rsa1_5_spec.rb
167
168
  - spec/sandal/enc/shared_examples.rb
168
169
  - spec/sandal/sig/es_spec.rb
169
170
  - spec/sandal/sig/hs_spec.rb
@@ -203,6 +204,7 @@ test_files:
203
204
  - spec/sandal/enc/a256cbc_hs512_spec.rb
204
205
  - spec/sandal/enc/a256gcm_spec.rb
205
206
  - spec/sandal/enc/alg/direct_spec.rb
207
+ - spec/sandal/enc/alg/rsa1_5_spec.rb
206
208
  - spec/sandal/enc/shared_examples.rb
207
209
  - spec/sandal/sig/es_spec.rb
208
210
  - spec/sandal/sig/hs_spec.rb