sandal 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ N2E4NjhhOTY5Y2JkNGU1MmU1YjE2MmIzYTY4NzUwMTg5ZTc3NTk0OA==
5
+ data.tar.gz: !binary |-
6
+ YjA1YTA1MzExMDg2YmMwNDllNWQyYTgxZjNmMmQ0MjJhNWNiNTM0Mw==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ YTBlODVkNGFiNWQwNGQzZWE2YjFkY2JmNTg4MDliMDlkN2YyYjA0NzBmYWYw
10
+ OTk5MjMzMjViYjNiODBjOGU0YmQ5MjlmMzUxODI2MzU4NzU5ZTI3ODQ1ZTk5
11
+ YmI1OThiMDFkZDNjNzRlMmViZTUxYjU5MWU0M2MwMGE4NDcxMDg=
12
+ data.tar.gz: !binary |-
13
+ ZWFhNjNlNzc4NDgwNmI5OTAwMmNhNTFlNDE1NjhjY2ViM2E2N2FlMjQ5NjM3
14
+ MDkxZDk5MmFiMzY2NGM4YTlkMjY0MzI5NDI1YTRlYWIzOWY1ZTA2MzE2NzMw
15
+ NTIwMjA1ZjQxMTQ1ZWU3Y2I2YjA3YmI5NDA3OTgxODZhMGRkMjA=
data/.gitignore CHANGED
@@ -2,19 +2,18 @@
2
2
  *.rbc
3
3
  .bundle
4
4
  .config
5
- .DS_Store
6
5
  coverage
6
+ doc
7
+ .DS_Store
7
8
  Gemfile.lock
8
9
  InstalledFiles
9
10
  lib/bundler/man
10
11
  pkg
12
+ .rbx
11
13
  rdoc
12
14
  spec/reports
13
15
  test/tmp
14
16
  test/version_tmp
15
17
  tmp
16
-
17
- # YARD artifacts
18
18
  .yardoc
19
19
  _yardoc
20
- doc/
data/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ ## 0.1.1 (01 April 2013)
2
+
3
+ Features:
4
+
5
+ - Changed from json to multi_json gem for improved compatibility.
6
+ - New Claims module can add claims validation functionality to Hash-like objects.
7
+ - New ClaimError type for claim validation errors.
8
+
9
+ Bug fixes:
10
+
11
+ - Base64 decoding now ensures there is no padding before decoding.
12
+
1
13
  ## 0.1.0 (30 March 2013)
2
14
 
3
15
  The first version worth using.
data/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  **NOTE: This library is pretty new and still has a lot of things that aren't finished or could be improved. Expect bugs and interface changes. Pull requests or feedback very much appreciated.**
2
2
 
3
- # Sandal [![Build Status](https://travis-ci.org/gregbeech/sandal.png?branch=master)](https://travis-ci.org/gregbeech/sandal) [![Coverage Status](https://coveralls.io/repos/gregbeech/sandal/badge.png?branch=master)](https://coveralls.io/r/gregbeech/sandal)
3
+ # Sandal [![Build Status](https://travis-ci.org/gregbeech/sandal.png?branch=master)](https://travis-ci.org/gregbeech/sandal) [![Coverage Status](https://coveralls.io/repos/gregbeech/sandal/badge.png?branch=master)](https://coveralls.io/r/gregbeech/sandal) [![Code Climate](https://codeclimate.com/github/gregbeech/sandal.png)](https://codeclimate.com/github/gregbeech/sandal)
4
4
 
5
- A Ruby library for creating and reading [JSON Web Tokens (JWT) draft-06](http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-06), supporting [JSON Web Signatures (JWS) draft-08](http://tools.ietf.org/html/draft-ietf-jose-json-web-signature-08) and [JSON Web Encryption (JWE) draft-08](http://tools.ietf.org/html/draft-ietf-jose-json-web-encryption-08). See the [CHANGELOG](CHANGELOG.md) for version history.
5
+ A Ruby library for creating and reading [JSON Web Tokens (JWT) drfat-06](http://tools.ietf.org/html/draft-ietf-oauth-json-web-token-06), supporting [JSON Web Signatures (JWS) draft-08](http://tools.ietf.org/html/draft-ietf-jose-json-web-signature-08) and [JSON Web Encryption (JWE) draft-08](http://tools.ietf.org/html/draft-ietf-jose-json-web-encryption-08). See the [CHANGELOG](CHANGELOG.md) for version history.
6
6
 
7
7
  ## Installation
8
8
 
@@ -0,0 +1,82 @@
1
+ module Sandal
2
+ # A module that can be mixed into Hash-like objects to provide claims-related functionality.
3
+ module Claims
4
+
5
+ # Validates the set of claims.
6
+ #
7
+ # @param options [Hash] The validation options (see {Sandal::DEFAULT_OPTIONS} for details).
8
+ # @return [Hash] A reference to self.
9
+ # @raise [Sandal::ClaimError] One or more claims is invalid.
10
+ def validate_claims(options)
11
+ validate_exp(options[:max_clock_skew]) if options[:validate_exp]
12
+ validate_nbf(options[:max_clock_skew]) if options[:validate_nbf]
13
+ validate_iss(options[:valid_iss])
14
+ validate_aud(options[:valid_aud])
15
+ self
16
+ end
17
+
18
+ # Validates the expires claim.
19
+ #
20
+ # @param max_clock_skew [Numeric] The maximum clock skew, in seconds.
21
+ # @return [void].
22
+ # @raise [Sandal::ClaimError] The 'exp' claim is invalid, or the token has expired.
23
+ def validate_exp(max_clock_skew)
24
+ exp = time_claim('exp')
25
+ if exp && exp <= (Time.now - max_clock_skew)
26
+ raise Sandal::ClaimError, 'The token has expired.'
27
+ end
28
+ nil
29
+ end
30
+
31
+ # Validates the not-before claim.
32
+ #
33
+ # @param max_clock_skew [Numeric] The maximum clock skew, in seconds.
34
+ # @return [void].
35
+ # @raise [Sandal::ClaimError] The 'nbf' claim is invalid, or the token is not valid yet.
36
+ def validate_nbf(max_clock_skew)
37
+ nbf = time_claim('nbf')
38
+ if nbf && nbf > (Time.now + max_clock_skew)
39
+ raise Sandal::ClaimError, 'The token is not valid yet.'
40
+ end
41
+ end
42
+
43
+ # Validates the issuer claim.
44
+ #
45
+ # @param valid_iss [Array] The valid issuers.
46
+ # @return [void].
47
+ # @raise [Sandal::ClaimError] The 'iss' claim value is not a valid issuer.
48
+ def validate_iss(valid_iss)
49
+ if valid_iss && valid_iss.length > 0
50
+ raise Sandal::ClaimError, 'The issuer is invalid.' unless valid_iss.include?(self['iss'])
51
+ end
52
+ end
53
+
54
+ # Validates the audience claim.
55
+ #
56
+ # @param valid_aud [Array] The valid audiences.
57
+ # @return [void].
58
+ # @raise [Sandal::ClaimError] The 'aud' claim value does not contain a valid audience.
59
+ def validate_aud(valid_aud)
60
+ if valid_aud && valid_aud.length > 0
61
+ aud = self['aud']
62
+ aud = [aud] unless aud.is_a?(Array)
63
+ raise Sandal::ClaimError, 'The audence is invalid.' unless (aud & valid_aud).length > 0
64
+ end
65
+ end
66
+
67
+ private
68
+
69
+ # Gets the value of a claim as a Time.
70
+ def time_claim(name)
71
+ claim = self[name]
72
+ if claim
73
+ begin
74
+ Time.at(claim)
75
+ rescue
76
+ raise ClaimError, "The '#{name}' claim is invalid."
77
+ end
78
+ end
79
+ end
80
+
81
+ end
82
+ end
@@ -35,7 +35,7 @@ module Sandal
35
35
  ciphertext = cipher.update(payload) + cipher.final
36
36
  encoded_ciphertext = Sandal::Util.base64_encode(ciphertext)
37
37
 
38
- encoded_header = Sandal::Util.base64_encode(JSON.generate(header))
38
+ encoded_header = Sandal::Util.base64_encode(MultiJson.dump(header))
39
39
  secured_input = [encoded_header, encoded_encrypted_key, encoded_iv, encoded_ciphertext].join('.')
40
40
  content_integrity_key = derive_content_key('Integrity', content_master_key, @sha_size)
41
41
  integrity_value = OpenSSL::HMAC.digest(@digest, content_integrity_key, secured_input)
data/lib/sandal/sig/es.rb CHANGED
@@ -5,11 +5,16 @@ module Sandal
5
5
 
6
6
  # Base implementation of the ECDSA-SHA family of signature algorithms.
7
7
  class ES
8
- include Sandal::Sig
9
8
 
10
- # Creates a new instance with the size of the SHA algorithm and an OpenSSL ES PKey.
9
+ # @return [String] The JWA name of the algorithm.
10
+ attr_reader :name
11
+
12
+ # Creates a new instance; it's probably easier to use one of the subclass constructors.
13
+ #
14
+ # @param sha_size [Integer] The size of the SHA algorithm.
15
+ # @param prime_size [Integer] The size of the ECDSA primes.
16
+ # @param key [OpenSSL::PKey::EC] The key to use for signing (private) or validation (public).
11
17
  def initialize(sha_size, prime_size, key)
12
- raise ArgumentError, 'A key is required.' unless key
13
18
  @name = "ES#{sha_size}"
14
19
  @digest = OpenSSL::Digest.new("sha#{sha_size}")
15
20
  @prime_size = prime_size
@@ -17,6 +22,9 @@ module Sandal
17
22
  end
18
23
 
19
24
  # Signs a payload and returns the signature.
25
+ #
26
+ # @param payload [String] The payload of the token to sign.
27
+ # @return [String] The signature.
20
28
  def sign(payload)
21
29
  hash = @digest.digest(payload)
22
30
  asn1_sig = @key.dsa_sign_asn1(hash)
@@ -24,64 +32,107 @@ module Sandal
24
32
  self.class.encode_jws_signature(r, s, @prime_size)
25
33
  end
26
34
 
27
- # Verifies a payload signature and returns whether the signature matches.
28
- def verify(signature, payload)
35
+ # Validates a payload signature and returns whether the signature matches.
36
+ #
37
+ # @param signature [String] The signature to verify.
38
+ # @param payload [String] The payload of the token.
39
+ # @return [Boolean] true if the signature is correct; otherwise false.
40
+ def valid?(signature, payload)
29
41
  hash = @digest.digest(payload)
30
42
  r, s = self.class.decode_jws_signature(signature)
31
43
  asn1_sig = self.class.encode_asn1_signature(r, s)
32
- result = @key.dsa_verify_asn1(hash, asn1_sig)
44
+ @key.dsa_verify_asn1(hash, asn1_sig)
33
45
  end
34
46
 
35
- # Decodes an ASN1 signature into a pair of BNs.
47
+ # Decodes an ASN.1 signature into a pair of BNs.
48
+ #
49
+ # @param signature [String] The ASN.1 signature.
50
+ # @return [OpenSSL::BN, OpenSSL::BN] A pair of BNs.
36
51
  def self.decode_asn1_signature(signature)
37
52
  asn_seq = OpenSSL::ASN1.decode(signature)
38
53
  return asn_seq.value[0].value, asn_seq.value[1].value
39
54
  end
40
55
 
41
- # Encodes a pair of BNs into an ASN1 signature.
56
+ # Encodes a pair of BNs into an ASN.1 signature.
57
+ #
58
+ # @param r [OpenSSL::BN] The 'r' value.
59
+ # @param s [OpenSSL::BN] The 's' value.
60
+ # @return [String] The ASN.1 signature.
42
61
  def self.encode_asn1_signature(r, s)
43
62
  items = [OpenSSL::ASN1::Integer.new(r), OpenSSL::ASN1::Integer.new(s)]
44
63
  OpenSSL::ASN1::Sequence.new(items).to_der
45
64
  end
46
65
 
47
66
  # Decodes a JWS signature into a pair of BNs.
67
+ #
68
+ # @param signature [String] The ASN.1 signature.
69
+ # @return [OpenSSL::BN, OpenSSL::BN] A pair of BNs.
48
70
  def self.decode_jws_signature(signature)
49
71
  n_length = signature.length / 2
50
- s_to_n = lambda { |s| OpenSSL::BN.new(s.unpack('H*')[0], 16) }
72
+ s_to_n = -> s { OpenSSL::BN.new(s.unpack('H*')[0], 16) }
51
73
  r = s_to_n.call(signature[0..(n_length - 1)])
52
74
  s = s_to_n.call(signature[n_length..-1])
53
75
  return r, s
54
76
  end
55
77
 
56
78
  # Encodes a pair of BNs into a JWS signature.
79
+ #
80
+ # @param r [OpenSSL::BN] The 'r' value.
81
+ # @param s [OpenSSL::BN] The 's' value.
82
+ # @param prime_size [Integer] The size of the ECDSA primes.
83
+ # @return [String] The ASN.1 signature.
57
84
  def self.encode_jws_signature(r, s, prime_size)
58
85
  byte_count = (prime_size / 8.0).ceil
59
- n_to_s = lambda { |n| [n.to_s(16)].pack('H*').rjust(byte_count, "\0") }
86
+ n_to_s = -> n { [n.to_s(16)].pack('H*').rjust(byte_count, "\0") }
60
87
  n_to_s.call(r) + n_to_s.call(s)
61
88
  end
62
89
 
90
+ protected
91
+
92
+ # Ensures that a key has a specified curve name.
93
+ #
94
+ # @param key [OpenSSL::PKey::EC] The key.
95
+ # @param curve_name [String] The curve name.
96
+ # @return [void].
97
+ # @raise [ArgumentError] The key has a different curve name.
98
+ def ensure_curve(key, curve_name)
99
+ raise ArgumentError, "The key must be in the #{curve_name} group." unless key.group.curve_name == curve_name
100
+ end
101
+
63
102
  end
64
103
 
65
104
  # The ECDSA-SHA256 signing algorithm.
66
105
  class ES256 < Sandal::Sig::ES
67
- # Creates a new instance with an OpenSSL ES PKey.
106
+ # Creates a new instance.
107
+ #
108
+ # @param key [OpenSSL::PKey::EC] The key to use for signing (private) or validation (public).
109
+ # @raise [ArgumentError] The key is not in the "prime256v1" group.
68
110
  def initialize(key)
111
+ ensure_curve(key, 'prime256v1')
69
112
  super(256, 256, key)
70
113
  end
71
114
  end
72
115
 
73
116
  # The ECDSA-SHA384 signing algorithm.
74
117
  class ES384 < Sandal::Sig::ES
75
- # Creates a new instance with an OpenSSL ES PKey.
118
+ # Creates a new instance.
119
+ #
120
+ # @param key [OpenSSL::PKey::EC] The key to use for signing (private) or validation (public).
121
+ # @raise [ArgumentError] The key is not in the "secp384r1" group.
76
122
  def initialize(key)
123
+ ensure_curve(key, 'secp384r1')
77
124
  super(384, 384, key)
78
125
  end
79
126
  end
80
127
 
81
128
  # The ECDSA-SHA512 signing algorithm.
82
129
  class ES512 < Sandal::Sig::ES
83
- # Creates a new instance with an OpenSSL ES PKey.
130
+ # Creates a new instance.
131
+ #
132
+ # @param key [OpenSSL::PKey::EC] The key to use for signing (private) or validation (public).
133
+ # @raise [ArgumentError] The key is not in the "secp521r1" group.
84
134
  def initialize(key)
135
+ ensure_curve(key, 'secp521r1')
85
136
  super(512, 521, key)
86
137
  end
87
138
  end
data/lib/sandal/sig/hs.rb CHANGED
@@ -5,23 +5,34 @@ module Sandal
5
5
 
6
6
  # Base implementation of the HMAC-SHA family of signature algorithms.
7
7
  class HS
8
- include Sandal::Sig
9
8
 
10
- # Creates a new instance with the size of the SHA algorithm and a string key.
9
+ # @return [String] The JWA name of the algorithm.
10
+ attr_reader :name
11
+
12
+ # Creates a new instance; it's probably easier to use one of the subclass constructors.
13
+ #
14
+ # @param sha_size [Integer] The size of the SHA algorithm.
15
+ # @param key [String] The key to use for signing or validation.
11
16
  def initialize(sha_size, key)
12
- raise ArgumentError, 'A key is required.' unless key
13
17
  @name = "HS#{sha_size}"
14
18
  @digest = OpenSSL::Digest.new("sha#{sha_size}")
15
19
  @key = key
16
20
  end
17
21
 
18
22
  # Signs a payload and returns the signature.
23
+ #
24
+ # @param payload [String] The payload of the token to sign.
25
+ # @return [String] The signature.
19
26
  def sign(payload)
20
27
  OpenSSL::HMAC.digest(@digest, @key, payload)
21
28
  end
22
29
 
23
- # Verifies a payload signature and returns whether the signature matches.
24
- def verify(signature, payload)
30
+ # Validates a payload signature and returns whether the signature matches.
31
+ #
32
+ # @param signature [String] The signature to verify.
33
+ # @param payload [String] The payload of the token.
34
+ # @return [Boolean] true if the signature is correct; otherwise false.
35
+ def valid?(signature, payload)
25
36
  Sandal::Util.secure_equals(sign(payload), signature)
26
37
  end
27
38
 
@@ -29,7 +40,9 @@ module Sandal
29
40
 
30
41
  # The HMAC-SHA256 signing algorithm.
31
42
  class HS256 < Sandal::Sig::HS
32
- # Creates a new instance with a string key.
43
+ # Creates a new instance.
44
+ #
45
+ # @param key [String] The key to use for signing or validation.
33
46
  def initialize(key)
34
47
  super(256, key)
35
48
  end
@@ -37,7 +50,9 @@ module Sandal
37
50
 
38
51
  # The HMAC-SHA384 signing algorithm.
39
52
  class HS384 < Sandal::Sig::HS
40
- # Creates a new instance with a string key.
53
+ # Creates a new instance.
54
+ #
55
+ # @param key [String] The key to use for signing or validation.
41
56
  def initialize(key)
42
57
  super(384, key)
43
58
  end
@@ -45,7 +60,9 @@ module Sandal
45
60
 
46
61
  # The HMAC-SHA512 signing algorithm.
47
62
  class HS512 < Sandal::Sig::HS
48
- # Creates a new instance with a string key.
63
+ # Creates a new instance.
64
+ #
65
+ # @param key [String] The key to use for signing or validation.
49
66
  def initialize(key)
50
67
  super(512, key)
51
68
  end
data/lib/sandal/sig/rs.rb CHANGED
@@ -5,27 +5,35 @@ module Sandal
5
5
 
6
6
  # Base implementation of the RSA-SHA family of signature algorithms.
7
7
  class RS
8
- include Sandal::Sig
9
8
 
10
- # Creates a new instance with the size of the SHA algorithm and an OpenSSL RSA PKey. To sign
11
- # a value this must contain a private key; to verify a signature a public key is sufficient.
12
- # Note that the size of the RSA key must be at least 2048 bits to be compliant with the
13
- # JWA specification.
9
+ # @return [String] The JWA name of the algorithm.
10
+ attr_reader :name
11
+
12
+ # Creates a new instance; it's probably easier to use one of the subclass constructors.
13
+ #
14
+ # @param sha_size [Integer] The size of the SHA algorithm.
15
+ # @param key [OpenSSL::PKey::RSA] The key to use for signing (private) or validation (public). This must
16
+ # be at least 2048 bits to be compliant with the JWA specification.
14
17
  def initialize(sha_size, key)
15
- raise ArgumentError, 'A key is required.' unless key
16
18
  @name = "RS#{sha_size}"
17
19
  @digest = OpenSSL::Digest.new("sha#{sha_size}")
18
20
  @key = key
19
21
  end
20
22
 
21
23
  # Signs a payload and returns the signature.
24
+ #
25
+ # @param payload [String] The payload of the token to sign.
26
+ # @return [String] The signature.
22
27
  def sign(payload)
23
- raise ArgumentError, 'A private key is required to sign the payload.' unless @key.private?
24
28
  @key.sign(@digest, payload)
25
29
  end
26
30
 
27
- # Verifies a payload signature and returns whether the signature matches.
28
- def verify(signature, payload)
31
+ # Validates a payload signature and returns whether the signature matches.
32
+ #
33
+ # @param signature [String] The signature to verify.
34
+ # @param payload [String] The payload of the token.
35
+ # @return [Boolean] true if the signature is correct; otherwise false.
36
+ def valid?(signature, payload)
29
37
  @key.verify(@digest, signature, payload)
30
38
  end
31
39
 
@@ -33,7 +41,10 @@ module Sandal
33
41
 
34
42
  # The RSA-SHA256 signing algorithm.
35
43
  class RS256 < Sandal::Sig::RS
36
- # Creates a new instance with an OpenSSL RSA PKey.
44
+ # Creates a new instance.
45
+ #
46
+ # @param key [OpenSSL::PKey::RSA] The key to use for signing (private) or validation (public). This must
47
+ # be at least 2048 bits to be compliant with the JWA specification.
37
48
  def initialize(key)
38
49
  super(256, key)
39
50
  end
@@ -41,7 +52,10 @@ module Sandal
41
52
 
42
53
  # The RSA-SHA384 signing algorithm.
43
54
  class RS384 < Sandal::Sig::RS
44
- # Creates a new instance with an OpenSSL RSA PKey.
55
+ # Creates a new instance.
56
+ #
57
+ # @param key [OpenSSL::PKey::RSA] The key to use for signing (private) or validation (public). This must
58
+ # be at least 2048 bits to be compliant with the JWA specification.
45
59
  def initialize(key)
46
60
  super(384, key)
47
61
  end
@@ -49,7 +63,10 @@ module Sandal
49
63
 
50
64
  # The RSA-SHA512 signing algorithm.
51
65
  class RS512 < Sandal::Sig::RS
52
- # Creates a new instance with an OpenSSL RSA PKey.
66
+ # Creates a new instance.
67
+ #
68
+ # @param key [OpenSSL::PKey::RSA] The key to use for signing (private) or validation (public). This must
69
+ # be at least 2048 bits to be compliant with the JWA specification.
53
70
  def initialize(key)
54
71
  super(512, key)
55
72
  end
data/lib/sandal/sig.rb CHANGED
@@ -1,34 +1,16 @@
1
1
  require 'singleton'
2
2
 
3
3
  module Sandal
4
- # Common signature traits.
4
+ # Contains signature (JWS) functionality.
5
5
  module Sig
6
6
 
7
- # @return [String] The JWA name of the algorithm.
8
- attr_reader :name
9
-
10
- # Signs a payload and returns the signature.
11
- #
12
- # @param payload [String] The payload of the token to sign.
13
- # @return [String] The signature.
14
- def sign(payload)
15
- raise NotImplementedError, "#{@name}.sign is not implemented."
16
- end
17
-
18
- # Verifies a payload signature and returns whether the signature matches.
19
- #
20
- # @param signature [String] The signature to verify.
21
- # @param payload [String] The payload of the token.
22
- # @return [Boolean] true if the signature is correct; otherwise false.
23
- def verify(signature, payload)
24
- raise NotImplementedError, "#{@name}.verify is not implemented."
25
- end
26
-
27
7
  # The 'none' JWA signature method.
28
8
  class None
29
- include Sandal::Sig
30
9
  include Singleton
31
10
 
11
+ # @return [String] The JWA name of the algorithm.
12
+ attr_reader :name
13
+
32
14
  # Creates a new instance.
33
15
  def initialize
34
16
  @name = 'none'
@@ -42,13 +24,13 @@ module Sandal
42
24
  ''
43
25
  end
44
26
 
45
- # Verifies that a signature is nil or empty.
27
+ # Validates that a signature is nil or empty.
46
28
  #
47
29
  # @param signature [String] The signature to verify.
48
30
  # @param payload [String] This parameter is ignored.
49
- # @return [Boolean] `true` if the signature is nil or empty; otherwise `false`.
50
- def verify(signature, payload)
51
- signature.nil? || signature.length == 0
31
+ # @return [Boolean] true if the signature is nil or empty; otherwise false.
32
+ def valid?(signature, payload)
33
+ signature.nil? || signature.empty?
52
34
  end
53
35
 
54
36
  end
data/lib/sandal/util.rb CHANGED
@@ -21,12 +21,20 @@ module Sandal
21
21
  end
22
22
 
23
23
  # Base64 encodes a string, in compliance with the JWT specification.
24
+ #
25
+ # @param s [String] The string to encode.
26
+ # @return [String] The encoded base64 string.
24
27
  def self.base64_encode(s)
25
28
  Base64.urlsafe_encode64(s).gsub(%r{=+$}, '')
26
29
  end
27
30
 
28
31
  # Base64 decodes a string, in compliance with the JWT specification.
32
+ #
33
+ # @param s [String] The base64 string to decode.
34
+ # @return [String] The decoded string.
35
+ # @raise [Sandal::TokenError] The base64 string contains padding.
29
36
  def self.base64_decode(s)
37
+ raise Sandal::TokenError, 'Base64 strings cannot contain padding.' if s.end_with?('=')
30
38
  padding_length = (4 - (s.length % 4)) % 4
31
39
  padding = '=' * padding_length
32
40
  Base64.urlsafe_decode64(s + padding)
@@ -1,4 +1,4 @@
1
1
  module Sandal
2
2
  # The semantic version of the library.
3
- VERSION = '0.1.0'
3
+ VERSION = '0.1.1'
4
4
  end
data/lib/sandal.rb CHANGED
@@ -1,12 +1,11 @@
1
1
  $:.unshift('.')
2
2
 
3
3
  require 'base64'
4
- require 'json'
4
+ require 'multi_json'
5
5
  require 'openssl'
6
-
6
+ require 'sandal/claims'
7
+ require 'sandal/util'
7
8
  require 'sandal/version'
8
- require 'sandal/sig'
9
- require 'sandal/enc'
10
9
 
11
10
  # A library for creating and reading JSON Web Tokens (JWT).
12
11
  module Sandal
@@ -14,6 +13,9 @@ module Sandal
14
13
  # The error that is raised when a token is invalid.
15
14
  class TokenError < StandardError; end
16
15
 
16
+ # The error that is raised when a claim within a token is invalid.
17
+ class ClaimError < TokenError; end
18
+
17
19
  # The default options for token handling.
18
20
  #
19
21
  # max_clock_skew:: The maximum clock skew, in seconds, when validating times.
@@ -43,23 +45,20 @@ module Sandal
43
45
 
44
46
  # Creates a signed JSON Web Token.
45
47
  #
46
- # @param payload [String/Hash] The payload of the token. If a Hash then it will be encoded as JSON.
47
- # @param signer [Sandal::Sig] The token signer, which may be nil for an unsigned token.
48
+ # @param payload [String/Hash] The payload of the token. Hashes will be encoded as JSON.
49
+ # @param signer [#name,#sign] The token signer, which may be nil for an unsigned token.
48
50
  # @param header_fields [Hash] Header fields for the token (note: do not include 'alg').
49
51
  # @return [String] A signed JSON Web Token.
50
52
  def self.encode_token(payload, signer, header_fields = nil)
51
- if header_fields && header_fields['enc']
52
- raise ArgumentError, 'The header cannot contain an "enc" parameter.'
53
- end
54
53
  signer ||= Sandal::Sig::None.instance
55
54
 
56
55
  header = {}
57
56
  header['alg'] = signer.name if signer.name != Sandal::Sig::None.instance.name
58
57
  header = header_fields.merge(header) if header_fields
59
58
 
60
- payload = JSON.generate(payload) if payload.kind_of?(Hash)
59
+ payload = MultiJson.dump(payload) unless payload.is_a?(String)
61
60
 
62
- encoded_header = Sandal::Util.base64_encode(JSON.generate(header))
61
+ encoded_header = Sandal::Util.base64_encode(MultiJson.dump(header))
63
62
  encoded_payload = Sandal::Util.base64_encode(payload)
64
63
  secured_input = [encoded_header, encoded_payload].join('.')
65
64
 
@@ -86,47 +85,29 @@ module Sandal
86
85
  # Decodes and validates a JSON Web Token.
87
86
  #
88
87
  # The block is called with the token header as the first parameter, and should return the appropriate
89
- # {Sandal::Sig} to verify the signature. It can optionally have a second options parameter which can
88
+ # {Sandal::Sig} to validate the signature. It can optionally have a second options parameter which can
90
89
  # be used to override the {DEFAULT_OPTIONS} on a per-token basis.
91
90
  #
92
91
  # @param token [String] The encoded JSON Web Token.
93
92
  # @yieldparam header [Hash] The JWT header values.
94
93
  # @yieldparam options [Hash] (Optional) A hash that can be used to override the default options.
95
- # @yieldreturn [Sandal::Sig] The signature verifier.
94
+ # @yieldreturn [#valid?] The signature validator.
96
95
  # @return [Hash/String] The payload of the token as a Hash if it was JSON, otherwise as a String.
97
- # @raise [ArgumentError] The token parameter is nil or empty, or the block has the wrong arity.
98
- # @raise [TokenError] The token format is invalid, or validation of the token failed.
99
- def self.decode_token(token, &block)
100
- raise ArgumentError, 'A token is required.' unless token && token.length > 0
96
+ # @raise [Sandal::TokenError] The token format is invalid, or validation of the token failed.
97
+ def self.decode_token(token)
101
98
  parts = token.split('.')
102
- raise TokenError, 'Invalid token format.' unless [2, 3].include?(parts.length)
103
- begin
104
- header = JSON.parse(Sandal::Util.base64_decode(parts[0]))
105
- payload = Sandal::Util.base64_decode(parts[1])
106
- signature = if parts.length > 2 then Sandal::Util.base64_decode(parts[2]) else '' end
107
- rescue
108
- raise TokenError, 'Invalid token encoding.'
109
- end
99
+ header, payload, signature = decode_jws_parts(parts)
110
100
 
111
101
  options = DEFAULT_OPTIONS.clone
112
- if block
113
- case block.arity
114
- when 1 then verifier = block.call(header)
115
- when 2 then verifier = block.call(header, options)
116
- else raise ArgumentError, 'Incorrect number of block parameters.'
117
- end
118
- end
119
- verifier ||= Sandal::Sig::None.instance
102
+ validator = yield header, options if block_given?
103
+ validator ||= Sandal::Sig::None.instance
120
104
 
121
105
  if options[:validate_signature]
122
106
  secured_input = parts.take(2).join('.')
123
- raise TokenError, 'Invalid signature.' unless verifier.verify(signature, secured_input)
107
+ raise TokenError, 'Invalid signature.' unless validator.valid?(signature, secured_input)
124
108
  end
125
109
 
126
- claims = JSON.parse(payload) rescue nil
127
- validate_claims(claims, options) if claims
128
-
129
- claims || payload
110
+ parse_and_validate(payload, header['cty'], options)
130
111
  end
131
112
 
132
113
  # Decrypts an encrypted JSON Web Token.
@@ -136,7 +117,7 @@ module Sandal
136
117
  parts = encrypted_token.split('.')
137
118
  raise ArgumentError, 'Invalid token format.' unless parts.length == 5
138
119
  begin
139
- header = JSON.parse(Sandal::Util.base64_decode(parts[0]))
120
+ header = MultiJson.load(Sandal::Util.base64_decode(parts[0]))
140
121
  encrypted_key = Sandal::Util.base64_decode(parts[1])
141
122
  iv = Sandal::Util.base64_decode(parts[2])
142
123
  ciphertext = Sandal::Util.base64_decode(parts[3])
@@ -152,54 +133,30 @@ module Sandal
152
133
 
153
134
  private
154
135
 
155
- # Validates token claims according to the options
156
- def self.validate_claims(claims, options)
157
- validate_expires(claims, options)
158
- validate_not_before(claims, options)
159
- validate_issuer(claims, options)
160
- validate_audience(claims, options)
161
- end
162
-
163
- # Validates the 'exp' claim.
164
- def self.validate_expires(claims, options)
165
- if options[:validate_exp] && claims['exp']
166
- begin
167
- exp = Time.at(claims['exp'])
168
- rescue
169
- raise TokenError, 'The "exp" claim is invalid.'
170
- end
171
- raise TokenError, 'The token has expired.' unless exp > (Time.now - options[:max_clock_skew])
172
- end
173
- end
174
-
175
- # Validates the 'nbf' claim
176
- def self.validate_not_before(claims, options)
177
- if options[:validate_nbf] && claims['nbf']
178
- begin
179
- nbf = Time.at(claims['nbf'])
180
- rescue
181
- raise TokenError, 'The "nbf" claim is invalid.'
182
- end
183
- raise TokenError, 'The token is not valid yet.' unless nbf < (Time.now + options[:max_clock_skew])
136
+ # Decodes the parts of a JWS token.
137
+ def self.decode_jws_parts(parts)
138
+ raise TokenError, 'Invalid token format.' unless [2, 3].include?(parts.length)
139
+ begin
140
+ header = MultiJson.load(Sandal::Util.base64_decode(parts[0]))
141
+ payload = Sandal::Util.base64_decode(parts[1])
142
+ signature = if parts.length > 2 then Sandal::Util.base64_decode(parts[2]) else '' end
143
+ rescue
144
+ raise TokenError, 'Invalid token encoding.'
184
145
  end
146
+ return header, payload, signature
185
147
  end
186
148
 
187
- # Validates the 'iss' claim.
188
- def self.validate_issuer(claims, options)
189
- valid_iss = options[:valid_iss]
190
- if valid_iss && valid_iss.length > 0
191
- raise TokenError, 'The issuer is invalid.' unless valid_iss.include?(claims['iss'])
149
+ # Parses the content of a token and validates the claims if is a JSON claim set.
150
+ def self.parse_and_validate(payload, content_type, options)
151
+ claims = MultiJson.load(payload) rescue nil unless content_type == 'JWT'
152
+ if claims
153
+ claims.extend(Sandal::Claims).validate_claims(options)
154
+ else
155
+ payload
192
156
  end
193
157
  end
194
158
 
195
- # Validates the 'aud' claim.
196
- def self.validate_audience(claims, options)
197
- valid_aud = options[:valid_aud]
198
- if valid_aud && valid_aud.length > 0
199
- aud = claims['aud']
200
- aud = [aud] unless aud.kind_of?(Array)
201
- raise TokenError, 'The audence is invalid.' unless (aud & valid_aud).length > 0
202
- end
203
- end
159
+ end
204
160
 
205
- end
161
+ require 'sandal/enc'
162
+ require 'sandal/sig'
data/sandal.gemspec CHANGED
@@ -17,16 +17,16 @@ Gem::Specification.new do |s|
17
17
  s.require_paths = ['lib']
18
18
  s.extra_rdoc_files = ['README.md', 'LICENSE.md']
19
19
 
20
- s.add_runtime_dependency 'json', '~> 1.7'
20
+ s.add_runtime_dependency 'multi_json', '~> 1.7'
21
21
  s.add_runtime_dependency 'jruby-openssl', '~> 0.7', '>= 0.7.3' if RUBY_PLATFORM == 'java'
22
22
 
23
- s.add_development_dependency 'bundler', '~> 1.3'
24
- s.add_development_dependency 'rake', '~> 10.0'
25
- s.add_development_dependency 'rspec', '~> 2.13'
26
- s.add_development_dependency 'coveralls', '~> 0.6'
27
- s.add_development_dependency 'yard', '~> 0.8'
28
- s.add_development_dependency 'redcarpet', '~> 2.2' unless RUBY_PLATFORM == 'java' # for yard
29
- s.add_development_dependency 'kramdown', '~> 1.0' if RUBY_PLATFORM == 'java' # for yard
23
+ s.add_development_dependency 'bundler', '>= 1.3'
24
+ s.add_development_dependency 'rake', '>= 10.0'
25
+ s.add_development_dependency 'rspec', '>= 2.13'
26
+ s.add_development_dependency 'coveralls', '>= 0.6'
27
+ s.add_development_dependency 'yard', '>= 0.8'
28
+ s.add_development_dependency 'redcarpet', '>= 2.2' unless RUBY_PLATFORM == 'java' # for yard
29
+ s.add_development_dependency 'kramdown', '>= 1.0' if RUBY_PLATFORM == 'java' # for yard
30
30
 
31
31
  s.requirements << 'openssl 1.0.1c for EC signature methods'
32
32
  end
@@ -55,8 +55,8 @@ describe Sandal::Sig::ES256 do
55
55
  signature = signer.sign(data)
56
56
  public_key = OpenSSL::PKey::EC.new(group)
57
57
  public_key.public_key = private_key.public_key
58
- verifier = Sandal::Sig::ES256.new(public_key)
59
- verifier.verify(signature, data).should == true
58
+ validator = Sandal::Sig::ES256.new(public_key)
59
+ validator.valid?(signature, data).should == true
60
60
  end
61
61
 
62
62
  it 'can verify the signature in JWS section A3.1' do
@@ -69,8 +69,8 @@ describe Sandal::Sig::ES256 do
69
69
  group = OpenSSL::PKey::EC::Group.new('prime256v1')
70
70
  public_key = OpenSSL::PKey::EC.new(group)
71
71
  public_key.public_key = make_point(group, x, y)
72
- verifier = Sandal::Sig::ES256.new(public_key)
73
- verifier.verify(signature, data).should == true
72
+ validator = Sandal::Sig::ES256.new(public_key)
73
+ validator.valid?(signature, data).should == true
74
74
  end
75
75
 
76
76
  it 'fails to verify the signature in JWS section A3.1 when the data is changed' do
@@ -83,8 +83,14 @@ describe Sandal::Sig::ES256 do
83
83
  group = OpenSSL::PKey::EC::Group.new('prime256v1')
84
84
  public_key = OpenSSL::PKey::EC.new(group)
85
85
  public_key.public_key = make_point(group, x, y)
86
- verifier = Sandal::Sig::ES256.new(public_key)
87
- verifier.verify(signature, data).should == false
86
+ validator = Sandal::Sig::ES256.new(public_key)
87
+ validator.valid?(signature, data).should == false
88
+ end
89
+
90
+ it 'raises an argument error if the key has the wrong curve' do
91
+ group = OpenSSL::PKey::EC::Group.new('secp384r1')
92
+ private_key = OpenSSL::PKey::EC.new(group).generate_key
93
+ expect { Sandal::Sig::ES256.new(private_key) }.to raise_error ArgumentError
88
94
  end
89
95
 
90
96
  end
@@ -99,8 +105,14 @@ describe Sandal::Sig::ES384 do
99
105
  signature = signer.sign(data)
100
106
  public_key = OpenSSL::PKey::EC.new(group)
101
107
  public_key.public_key = private_key.public_key
102
- verifier = Sandal::Sig::ES384.new(public_key)
103
- verifier.verify(signature, data).should == true
108
+ validator = Sandal::Sig::ES384.new(public_key)
109
+ validator.valid?(signature, data).should == true
110
+ end
111
+
112
+ it 'raises an argument error if the key has the wrong curve' do
113
+ group = OpenSSL::PKey::EC::Group.new('secp521r1')
114
+ private_key = OpenSSL::PKey::EC.new(group).generate_key
115
+ expect { Sandal::Sig::ES384.new(private_key) }.to raise_error ArgumentError
104
116
  end
105
117
 
106
118
  end
@@ -115,8 +127,8 @@ describe Sandal::Sig::ES512 do
115
127
  signature = signer.sign(data)
116
128
  public_key = OpenSSL::PKey::EC.new(group)
117
129
  public_key.public_key = private_key.public_key
118
- verifier = Sandal::Sig::ES512.new(public_key)
119
- verifier.verify(signature, data).should == true
130
+ validator = Sandal::Sig::ES512.new(public_key)
131
+ validator.valid?(signature, data).should == true
120
132
  end
121
133
 
122
134
  it 'can verify the signature in JWS section A4.1' do
@@ -129,8 +141,8 @@ describe Sandal::Sig::ES512 do
129
141
  group = OpenSSL::PKey::EC::Group.new('secp521r1')
130
142
  public_key = OpenSSL::PKey::EC.new(group)
131
143
  public_key.public_key = make_point(group, x, y)
132
- verifier = Sandal::Sig::ES512.new(public_key)
133
- verifier.verify(signature, data).should == true
144
+ validator = Sandal::Sig::ES512.new(public_key)
145
+ validator.valid?(signature, data).should == true
134
146
  end
135
147
 
136
148
  it 'fails to verify the signature in JWS section A4.1 when the data is changed' do
@@ -143,8 +155,14 @@ describe Sandal::Sig::ES512 do
143
155
  group = OpenSSL::PKey::EC::Group.new('secp521r1')
144
156
  public_key = OpenSSL::PKey::EC.new(group)
145
157
  public_key.public_key = make_point(group, x, y)
146
- verifier = Sandal::Sig::ES512.new(public_key)
147
- verifier.verify(signature, data).should == false
158
+ validator = Sandal::Sig::ES512.new(public_key)
159
+ validator.valid?(signature, data).should == false
160
+ end
161
+
162
+ it 'raises an argument error if the key has the wrong curve' do
163
+ group = OpenSSL::PKey::EC::Group.new('prime256v1')
164
+ private_key = OpenSSL::PKey::EC.new(group).generate_key
165
+ expect { Sandal::Sig::ES512.new(private_key) }.to raise_error ArgumentError
148
166
  end
149
167
 
150
168
  end
@@ -7,7 +7,7 @@ describe Sandal::Sig::HS256 do
7
7
  key = 'A secret key'
8
8
  signer = Sandal::Sig::HS256.new(key)
9
9
  signature = signer.sign(data)
10
- signer.verify(signature, data).should == true
10
+ signer.valid?(signature, data).should == true
11
11
  end
12
12
  end
13
13
 
@@ -17,7 +17,7 @@ describe Sandal::Sig::HS384 do
17
17
  key = 'Another secret key'
18
18
  signer = Sandal::Sig::HS384.new(key)
19
19
  signature = signer.sign(data)
20
- signer.verify(signature, data).should == true
20
+ signer.valid?(signature, data).should == true
21
21
  end
22
22
  end
23
23
 
@@ -27,6 +27,6 @@ describe Sandal::Sig::HS512 do
27
27
  key = 'Yet another secret key'
28
28
  signer = Sandal::Sig::HS512.new(key)
29
29
  signature = signer.sign(data)
30
- signer.verify(signature, data).should == true
30
+ signer.valid?(signature, data).should == true
31
31
  end
32
32
  end
@@ -7,8 +7,8 @@ describe Sandal::Sig::RS256 do
7
7
  private_key = OpenSSL::PKey::RSA.generate(2048)
8
8
  signer = Sandal::Sig::RS256.new(private_key)
9
9
  signature = signer.sign(data)
10
- verifier = Sandal::Sig::RS256.new(private_key.public_key)
11
- verifier.verify(signature, data).should == true
10
+ validator = Sandal::Sig::RS256.new(private_key.public_key)
11
+ validator.valid?(signature, data).should == true
12
12
  end
13
13
  end
14
14
 
@@ -18,8 +18,8 @@ describe Sandal::Sig::RS384 do
18
18
  private_key = OpenSSL::PKey::RSA.generate(2048)
19
19
  signer = Sandal::Sig::RS384.new(private_key)
20
20
  signature = signer.sign(data)
21
- verifier = Sandal::Sig::RS384.new(private_key.public_key)
22
- verifier.verify(signature, data).should == true
21
+ validator = Sandal::Sig::RS384.new(private_key.public_key)
22
+ validator.valid?(signature, data).should == true
23
23
  end
24
24
  end
25
25
 
@@ -29,7 +29,7 @@ describe Sandal::Sig::RS512 do
29
29
  private_key = OpenSSL::PKey::RSA.generate(2048)
30
30
  signer = Sandal::Sig::RS512.new(private_key)
31
31
  signature = signer.sign(data)
32
- verifier = Sandal::Sig::RS512.new(private_key.public_key)
33
- verifier.verify(signature, data).should == true
32
+ validator = Sandal::Sig::RS512.new(private_key.public_key)
33
+ validator.valid?(signature, data).should == true
34
34
  end
35
35
  end
@@ -11,6 +11,10 @@ describe Sandal::Util do
11
11
  val.should == src
12
12
  end
13
13
 
14
+ it 'raises a token error if base64 strings contain padding' do
15
+ expect { Sandal::Util.base64_decode('eyJpc3MiOiJq=') }.to raise_error Sandal::TokenError
16
+ end
17
+
14
18
  it 'compares nil strings as equal' do
15
19
  Sandal::Util.secure_equals(nil, nil).should == true
16
20
  end
data/spec/sandal_spec.rb CHANGED
@@ -27,22 +27,22 @@ describe Sandal do
27
27
 
28
28
  it 'decodes non-JSON payloads to a String' do
29
29
  token = Sandal.encode_token('not valid json', nil)
30
- Sandal.decode_token(token).class.should == String
30
+ Sandal.decode_token(token).class.should.kind_of? String
31
31
  end
32
32
 
33
33
  it 'decodes JSON payloads to a Hash' do
34
34
  token = Sandal.encode_token({ 'valid' => 'json' }, nil)
35
- Sandal.decode_token(token).class.should == Hash
35
+ Sandal.decode_token(token).class.should.kind_of? Hash
36
36
  end
37
37
 
38
- it 'raises a token error when the expiry date is far in the past' do
38
+ it 'raises a claim error when the expiry date is far in the past' do
39
39
  token = Sandal.encode_token({ 'exp' => (Time.now - 600).to_i }, nil)
40
- expect { Sandal.decode_token(token) }.to raise_error Sandal::TokenError
40
+ expect { Sandal.decode_token(token) }.to raise_error Sandal::ClaimError
41
41
  end
42
42
 
43
- it 'raises a token error when the expiry date is invalid' do
43
+ it 'raises a claim error when the expiry date is invalid' do
44
44
  token = Sandal.encode_token({ 'exp' => 'invalid value' }, nil)
45
- expect { Sandal.decode_token(token) }.to raise_error Sandal::TokenError
45
+ expect { Sandal.decode_token(token) }.to raise_error Sandal::ClaimError
46
46
  end
47
47
 
48
48
  it 'does not raise an error when the expiry date is far in the past but validation is disabled' do
@@ -60,14 +60,14 @@ describe Sandal do
60
60
  Sandal.decode_token(token)
61
61
  end
62
62
 
63
- it 'raises a token error when the not-before date is far in the future' do
63
+ it 'raises a claim error when the not-before date is far in the future' do
64
64
  token = Sandal.encode_token({ 'nbf' => (Time.now + 600).to_i }, nil)
65
- expect { Sandal.decode_token(token) }.to raise_error Sandal::TokenError
65
+ expect { Sandal.decode_token(token) }.to raise_error Sandal::ClaimError
66
66
  end
67
67
 
68
- it 'raises a token error when the not-before date is invalid' do
68
+ it 'raises a claim error when the not-before date is invalid' do
69
69
  token = Sandal.encode_token({ 'nbf' => 'invalid value' }, nil)
70
- expect { Sandal.decode_token(token) }.to raise_error Sandal::TokenError
70
+ expect { Sandal.decode_token(token) }.to raise_error Sandal::ClaimError
71
71
  end
72
72
 
73
73
  it 'does not raise an error when the not-before date is far in the future but validation is disabled' do
@@ -85,12 +85,12 @@ describe Sandal do
85
85
  Sandal.decode_token(token)
86
86
  end
87
87
 
88
- it 'raises a token error when the issuer is not valid' do
88
+ it 'raises a claim error when the issuer is not valid' do
89
89
  token = Sandal.encode_token({ 'iss' => 'example.org' }, nil)
90
90
  expect { Sandal.decode_token(token) do |header, options|
91
91
  options[:valid_iss] = ['example.net']
92
92
  nil
93
- end }.to raise_error Sandal::TokenError
93
+ end }.to raise_error Sandal::ClaimError
94
94
  end
95
95
 
96
96
  it 'does not raise an error when the issuer is valid' do
@@ -101,20 +101,20 @@ describe Sandal do
101
101
  end
102
102
  end
103
103
 
104
- it 'raises a token error when the audience string is not valid' do
104
+ it 'raises a claim error when the audience string is not valid' do
105
105
  token = Sandal.encode_token({ 'aud' => 'example.com' }, nil)
106
106
  expect { Sandal.decode_token(token) do |header, options|
107
107
  options[:valid_aud] = ['example.net']
108
108
  nil
109
- end }.to raise_error Sandal::TokenError
109
+ end }.to raise_error Sandal::ClaimError
110
110
  end
111
111
 
112
- it 'raises a token error when the audience array is not valid' do
112
+ it 'raises a claim error when the audience array is not valid' do
113
113
  token = Sandal.encode_token({ 'aud' => ['example.org', 'example.com'] }, nil)
114
114
  expect { Sandal.decode_token(token) do |header, options|
115
115
  options[:valid_aud] = ['example.net']
116
116
  nil
117
- end }.to raise_error Sandal::TokenError
117
+ end }.to raise_error Sandal::ClaimError
118
118
  end
119
119
 
120
120
  it 'does not raise an error when the audience string is valid' do
metadata CHANGED
@@ -1,20 +1,18 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sandal
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
5
- prerelease:
4
+ version: 0.1.1
6
5
  platform: ruby
7
6
  authors:
8
7
  - Greg Beech
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2013-03-30 00:00:00.000000000 Z
11
+ date: 2013-04-01 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
- name: json
14
+ name: multi_json
16
15
  requirement: !ruby/object:Gem::Requirement
17
- none: false
18
16
  requirements:
19
17
  - - ~>
20
18
  - !ruby/object:Gem::Version
@@ -22,7 +20,6 @@ dependencies:
22
20
  type: :runtime
23
21
  prerelease: false
24
22
  version_requirements: !ruby/object:Gem::Requirement
25
- none: false
26
23
  requirements:
27
24
  - - ~>
28
25
  - !ruby/object:Gem::Version
@@ -30,97 +27,85 @@ dependencies:
30
27
  - !ruby/object:Gem::Dependency
31
28
  name: bundler
32
29
  requirement: !ruby/object:Gem::Requirement
33
- none: false
34
30
  requirements:
35
- - - ~>
31
+ - - ! '>='
36
32
  - !ruby/object:Gem::Version
37
33
  version: '1.3'
38
34
  type: :development
39
35
  prerelease: false
40
36
  version_requirements: !ruby/object:Gem::Requirement
41
- none: false
42
37
  requirements:
43
- - - ~>
38
+ - - ! '>='
44
39
  - !ruby/object:Gem::Version
45
40
  version: '1.3'
46
41
  - !ruby/object:Gem::Dependency
47
42
  name: rake
48
43
  requirement: !ruby/object:Gem::Requirement
49
- none: false
50
44
  requirements:
51
- - - ~>
45
+ - - ! '>='
52
46
  - !ruby/object:Gem::Version
53
47
  version: '10.0'
54
48
  type: :development
55
49
  prerelease: false
56
50
  version_requirements: !ruby/object:Gem::Requirement
57
- none: false
58
51
  requirements:
59
- - - ~>
52
+ - - ! '>='
60
53
  - !ruby/object:Gem::Version
61
54
  version: '10.0'
62
55
  - !ruby/object:Gem::Dependency
63
56
  name: rspec
64
57
  requirement: !ruby/object:Gem::Requirement
65
- none: false
66
58
  requirements:
67
- - - ~>
59
+ - - ! '>='
68
60
  - !ruby/object:Gem::Version
69
61
  version: '2.13'
70
62
  type: :development
71
63
  prerelease: false
72
64
  version_requirements: !ruby/object:Gem::Requirement
73
- none: false
74
65
  requirements:
75
- - - ~>
66
+ - - ! '>='
76
67
  - !ruby/object:Gem::Version
77
68
  version: '2.13'
78
69
  - !ruby/object:Gem::Dependency
79
70
  name: coveralls
80
71
  requirement: !ruby/object:Gem::Requirement
81
- none: false
82
72
  requirements:
83
- - - ~>
73
+ - - ! '>='
84
74
  - !ruby/object:Gem::Version
85
75
  version: '0.6'
86
76
  type: :development
87
77
  prerelease: false
88
78
  version_requirements: !ruby/object:Gem::Requirement
89
- none: false
90
79
  requirements:
91
- - - ~>
80
+ - - ! '>='
92
81
  - !ruby/object:Gem::Version
93
82
  version: '0.6'
94
83
  - !ruby/object:Gem::Dependency
95
84
  name: yard
96
85
  requirement: !ruby/object:Gem::Requirement
97
- none: false
98
86
  requirements:
99
- - - ~>
87
+ - - ! '>='
100
88
  - !ruby/object:Gem::Version
101
89
  version: '0.8'
102
90
  type: :development
103
91
  prerelease: false
104
92
  version_requirements: !ruby/object:Gem::Requirement
105
- none: false
106
93
  requirements:
107
- - - ~>
94
+ - - ! '>='
108
95
  - !ruby/object:Gem::Version
109
96
  version: '0.8'
110
97
  - !ruby/object:Gem::Dependency
111
98
  name: redcarpet
112
99
  requirement: !ruby/object:Gem::Requirement
113
- none: false
114
100
  requirements:
115
- - - ~>
101
+ - - ! '>='
116
102
  - !ruby/object:Gem::Version
117
103
  version: '2.2'
118
104
  type: :development
119
105
  prerelease: false
120
106
  version_requirements: !ruby/object:Gem::Requirement
121
- none: false
122
107
  requirements:
123
- - - ~>
108
+ - - ! '>='
124
109
  - !ruby/object:Gem::Version
125
110
  version: '2.2'
126
111
  description: A ruby library for creating and reading JSON Web Tokens (JWT), supporting
@@ -143,6 +128,7 @@ files:
143
128
  - README.md
144
129
  - Rakefile
145
130
  - lib/sandal.rb
131
+ - lib/sandal/claims.rb
146
132
  - lib/sandal/enc.rb
147
133
  - lib/sandal/enc/aescbc.rb
148
134
  - lib/sandal/enc/aesgcm.rb
@@ -162,34 +148,27 @@ files:
162
148
  homepage: http://rubygems.org/gems/sandal
163
149
  licenses:
164
150
  - MIT
151
+ metadata: {}
165
152
  post_install_message:
166
153
  rdoc_options: []
167
154
  require_paths:
168
155
  - lib
169
156
  required_ruby_version: !ruby/object:Gem::Requirement
170
- none: false
171
157
  requirements:
172
158
  - - ! '>='
173
159
  - !ruby/object:Gem::Version
174
160
  version: '0'
175
- segments:
176
- - 0
177
- hash: -3672992457221215645
178
161
  required_rubygems_version: !ruby/object:Gem::Requirement
179
- none: false
180
162
  requirements:
181
163
  - - ! '>='
182
164
  - !ruby/object:Gem::Version
183
165
  version: '0'
184
- segments:
185
- - 0
186
- hash: -3672992457221215645
187
166
  requirements:
188
167
  - openssl 1.0.1c for EC signature methods
189
168
  rubyforge_project:
190
- rubygems_version: 1.8.25
169
+ rubygems_version: 2.0.3
191
170
  signing_key:
192
- specification_version: 3
171
+ specification_version: 4
193
172
  summary: A JSON Web Token (JWT) library.
194
173
  test_files:
195
174
  - spec/helper.rb