sandal 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/lib/sandal/enc/aescbc.rb +78 -0
- data/lib/sandal/enc.rb +24 -0
- data/lib/sandal/sig/hs.rb +55 -0
- data/lib/sandal/sig/rs.rb +59 -0
- data/lib/sandal/sig.rb +43 -0
- data/lib/sandal/util.rb +36 -0
- data/lib/sandal/version.rb +4 -0
- data/lib/sandal.rb +162 -0
- metadata +53 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
NjVlMDRhMzgyZTY4OTkyMDZjZmUwNDY2ZGY3YjY4NTdiZWY0NDFjZA==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
MjE1N2ZmZmQxMjI1YmEzYzQ4YzJiMGY5MjliOWQ0NDU0NmYzZWM2Mw==
|
7
|
+
!binary "U0hBNTEy":
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
YjU2ZWEwYWU5NDc2MzliNmQwNjExMDdmYTU2ZTc4OGRlZWQ3ZWE4ODAxZmE1
|
10
|
+
MzJkMWJlMzQzZjQ2ZDg4MGY1YTlmZThkZDE5MmUzZTBmMTQ2NjQ1OWFkZTJm
|
11
|
+
MDkzYTY3NGQwNDFmNjlkMGQ4ODliOWFlMjg4MTA3MDAyNWNhMGY=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
NzRiZjIwYjg2OGUwOWZlZmY3MTAxY2U3ZWFmMjI5NjZiM2U1ODFlMjBlNDA0
|
14
|
+
Y2M4NzI2M2IyMGU2ODUyYWE1MTEzYTczMTc5YjM2Yzg1N2IxNWNlYjFlYzk1
|
15
|
+
MmFlMjkxZmU2ODRkNTUxMGZkODNlNWY1ZDQyMDQ1MGUwMmVkNzI=
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
require 'sandal/util'
|
3
|
+
|
4
|
+
module Sandal
|
5
|
+
module Enc
|
6
|
+
|
7
|
+
# Base implementation of the AES/CBC family of encryption algorithms.
|
8
|
+
class AESCBC
|
9
|
+
include Sandal::Enc
|
10
|
+
|
11
|
+
def initialize(aes_size, key)
|
12
|
+
throw ArgumentError.new('A key is required.') unless key
|
13
|
+
@aes_size = aes_size
|
14
|
+
@sha_size = aes_size * 2 # TODO: Any smarter way to do this?
|
15
|
+
@name = "A#{aes_size}CBC+HS#{@sha_size}"
|
16
|
+
@alg_name = "RSA1_5" # TODO: From key?
|
17
|
+
@cipher_name = "AES-#{aes_size}-CBC"
|
18
|
+
@key = key
|
19
|
+
@digest = OpenSSL::Digest.new("SHA#{@sha_size}")
|
20
|
+
end
|
21
|
+
|
22
|
+
def encrypt(header, payload)
|
23
|
+
cipher = OpenSSL::Cipher.new(@cipher_name).encrypt
|
24
|
+
content_master_key = cipher.random_key # TODO: Check with the spec if this is long enough
|
25
|
+
iv = cipher.random_iv
|
26
|
+
|
27
|
+
# TODO: Need to think about how this works with pre-shared symmetric keys - I'd originally thought
|
28
|
+
# this wouldn't be a common use case, but in cases where the recipient is also the issuer (e.g.
|
29
|
+
# an OAuth refresh token) then it would make a lot of sense.
|
30
|
+
encrypted_key = @key.public_encrypt(content_master_key)
|
31
|
+
encoded_encrypted_key = Sandal::Util.base64_encode(encrypted_key)
|
32
|
+
encoded_iv = Sandal::Util.base64_encode(iv)
|
33
|
+
|
34
|
+
cipher.key = derive_content_key('Encryption', content_master_key, @aes_size)
|
35
|
+
ciphertext = cipher.update(payload) + cipher.final
|
36
|
+
encoded_ciphertext = Sandal::Util.base64_encode(ciphertext)
|
37
|
+
|
38
|
+
encoded_header = Sandal::Util.base64_encode(JSON.generate(header))
|
39
|
+
secured_input = [encoded_header, encoded_encrypted_key, encoded_iv, encoded_ciphertext].join('.')
|
40
|
+
content_integrity_key = derive_content_key('Integrity', content_master_key, @sha_size)
|
41
|
+
integrity_value = OpenSSL::HMAC.digest(@digest, content_integrity_key, secured_input)
|
42
|
+
encoded_integrity_value = Sandal::Util.base64_encode(integrity_value)
|
43
|
+
|
44
|
+
[secured_input, encoded_integrity_value].join('.')
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
# Derives content keys using the Concat KDF.
|
50
|
+
def derive_content_key(label, content_master_key, size)
|
51
|
+
round_number = [1].pack('N')
|
52
|
+
output_size = [size].pack('N')
|
53
|
+
enc_bytes = @name.encode('utf-8').bytes.to_a.pack('C*')
|
54
|
+
epu = epv = [0].pack('N')
|
55
|
+
label_bytes = label.encode('us-ascii').bytes.to_a.pack('C*')
|
56
|
+
hash_input = round_number + content_master_key + output_size + enc_bytes + epu + epv + label_bytes
|
57
|
+
hash = @digest.digest(hash_input)
|
58
|
+
hash[0..((size / 8) - 1)]
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
# The AES-128-CBC encryption algorithm.
|
64
|
+
class AES128CBC < Sandal::Enc::AESCBC
|
65
|
+
def initialize(key)
|
66
|
+
super(128, key)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# The AES-256-CBC encryption algorithm.
|
71
|
+
class AES256CBC < Sandal::Enc::AESCBC
|
72
|
+
def initialize(key)
|
73
|
+
super(256, key)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
end
|
data/lib/sandal/enc.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
module Sandal
|
2
|
+
# Common encryption traits.
|
3
|
+
module Enc
|
4
|
+
|
5
|
+
# The JWA name of the encryption.
|
6
|
+
attr_reader :name
|
7
|
+
|
8
|
+
# The JWA name of the algorithm.
|
9
|
+
attr_reader :alg_name
|
10
|
+
|
11
|
+
# Encrypts a header and payload, and returns an encrypted token.
|
12
|
+
def encrypt(header, payload)
|
13
|
+
throw NotImplementedError.new("#{@name}.encrypt is not implemented.")
|
14
|
+
end
|
15
|
+
|
16
|
+
# Decrypts a token.
|
17
|
+
def decrypt(data)
|
18
|
+
throw NotImplementedError.new("#{@name}.decrypt is not implemented.")
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
require 'sandal/enc/aescbc'
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
|
3
|
+
module Sandal
|
4
|
+
module Sig
|
5
|
+
|
6
|
+
# Base implementation of the HMAC-SHA family of signature algorithms.
|
7
|
+
class HS
|
8
|
+
include Sandal::Sig
|
9
|
+
|
10
|
+
# Creates a new instance with the size of the SHA algorithm and a string key.
|
11
|
+
def initialize(sha_size, key)
|
12
|
+
throw ArgumentError.new('A key is required.') unless key
|
13
|
+
@name = "HS#{sha_size}"
|
14
|
+
@digest = OpenSSL::Digest.new("SHA#{sha_size}")
|
15
|
+
@key = key
|
16
|
+
end
|
17
|
+
|
18
|
+
# Signs a payload and returns the signature.
|
19
|
+
def sign(payload)
|
20
|
+
OpenSSL::HMAC.digest(@digest, @key, payload)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Verifies a payload signature and returns whether the signature matches.
|
24
|
+
def verify(signature, payload)
|
25
|
+
Sandal::Util.secure_equals(sign(payload), signature)
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
# The HMAC-SHA256 signing algorithm.
|
31
|
+
class HS256 < Sandal::Sig::HS
|
32
|
+
# Creates a new instance with a string key.
|
33
|
+
def initialize(key)
|
34
|
+
super(256, key)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# The HMAC-SHA384 signing algorithm.
|
39
|
+
class HS384 < Sandal::Sig::HS
|
40
|
+
# Creates a new instance with a string key.
|
41
|
+
def initialize(key)
|
42
|
+
super(384, key)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# The HMAC-SHA512 signing algorithm.
|
47
|
+
class HS512 < Sandal::Sig::HS
|
48
|
+
# Creates a new instance with a string key.
|
49
|
+
def initialize(key)
|
50
|
+
super(512, key)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
|
3
|
+
module Sandal
|
4
|
+
module Sig
|
5
|
+
|
6
|
+
# Base implementation of the RSA-SHA family of signature algorithms.
|
7
|
+
class RS
|
8
|
+
include Sandal::Sig
|
9
|
+
|
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.
|
14
|
+
def initialize(sha_size, key)
|
15
|
+
throw ArgumentError.new('A key is required.') unless key
|
16
|
+
@name = "RS#{sha_size}"
|
17
|
+
@digest = OpenSSL::Digest.new("SHA#{sha_size}")
|
18
|
+
@key = key
|
19
|
+
end
|
20
|
+
|
21
|
+
# Signs a payload and returns the signature.
|
22
|
+
def sign(payload)
|
23
|
+
throw ArgumentError.new('A private key is required to sign the payload.') unless @key.private?
|
24
|
+
@key.sign(@digest, payload)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Verifies a payload signature and returns whether the signature matches.
|
28
|
+
def verify(signature, payload)
|
29
|
+
@key.verify(@digest, signature, payload)
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
# The RSA-SHA256 signing algorithm.
|
35
|
+
class RS256 < Sandal::Sig::RS
|
36
|
+
# Creates a new instance with an OpenSSL RSA PKey.
|
37
|
+
def initialize(key)
|
38
|
+
super(256, key)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# The RSA-SHA384 signing algorithm.
|
43
|
+
class RS384 < Sandal::Sig::RS
|
44
|
+
# Creates a new instance with an OpenSSL RSA PKey.
|
45
|
+
def initialize(key)
|
46
|
+
super(384, key)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# The RSA-SHA512 signing algorithm.
|
51
|
+
class RS512 < Sandal::Sig::RS
|
52
|
+
# Creates a new instance with an OpenSSL RSA PKey.
|
53
|
+
def initialize(key)
|
54
|
+
super(512, key)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
data/lib/sandal/sig.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
module Sandal
|
2
|
+
# Common signature traits.
|
3
|
+
module Sig
|
4
|
+
|
5
|
+
# The JWA name of the algorithm.
|
6
|
+
attr_reader :name
|
7
|
+
|
8
|
+
# Signs a payload and returns the signature.
|
9
|
+
def sign(payload)
|
10
|
+
throw NotImplementedError.new("#{@name}.sign is not implemented.")
|
11
|
+
end
|
12
|
+
|
13
|
+
# Verifies a payload signature and returns whether the signature matches.
|
14
|
+
def verify(signature, payload)
|
15
|
+
throw NotImplementedError.new("#{@name}.verify is not implemented.")
|
16
|
+
end
|
17
|
+
|
18
|
+
# The 'none' JWA signature method.
|
19
|
+
class None
|
20
|
+
include Sandal::Sig
|
21
|
+
|
22
|
+
# Creates a new instance.
|
23
|
+
def initialize
|
24
|
+
@name = 'none'
|
25
|
+
end
|
26
|
+
|
27
|
+
# Returns an empty signature.
|
28
|
+
def sign(payload)
|
29
|
+
''
|
30
|
+
end
|
31
|
+
|
32
|
+
# Verifies that the signature is empty.
|
33
|
+
def verify(signature, payload)
|
34
|
+
signature.nil? || signature.length == 0
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
require 'sandal/sig/hs'
|
43
|
+
require 'sandal/sig/rs'
|
data/lib/sandal/util.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'base64'
|
2
|
+
|
3
|
+
module Sandal
|
4
|
+
# Implements some JWT utility functions. Shouldn't be needed by most people but may
|
5
|
+
# be useful if you're developing an extension to the library.
|
6
|
+
module Util
|
7
|
+
|
8
|
+
# A string equality function which doesn't short-circuit the equality check to help
|
9
|
+
# protect against timing attacks.
|
10
|
+
#--
|
11
|
+
# See http://rdist.root.org/2009/05/28/timing-attack-in-google-keyczar-library/
|
12
|
+
def self.secure_equals(a, b)
|
13
|
+
if a.nil? && b.nil?
|
14
|
+
true
|
15
|
+
elsif a.nil? || b.nil? || a.bytesize != b.bytesize
|
16
|
+
false
|
17
|
+
else
|
18
|
+
result = a.bytes.zip(b.bytes).reduce(0) { |memo, (b1, b2)| memo |= (b1 ^ b2) }
|
19
|
+
result == 0
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Base64 encodes a string, in compliance with the JWT specification.
|
24
|
+
def self.base64_encode(s)
|
25
|
+
Base64.urlsafe_encode64(s).gsub(%r{=+$}, '')
|
26
|
+
end
|
27
|
+
|
28
|
+
# Base64 decodes a string, in compliance with the JWT specification.
|
29
|
+
def self.base64_decode(s)
|
30
|
+
padding_length = (4 - (s.length % 4)) % 4
|
31
|
+
padding = '=' * padding_length
|
32
|
+
Base64.urlsafe_decode64(s + padding)
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
data/lib/sandal.rb
ADDED
@@ -0,0 +1,162 @@
|
|
1
|
+
$:.unshift('.')
|
2
|
+
|
3
|
+
require 'base64'
|
4
|
+
require 'json'
|
5
|
+
require 'openssl'
|
6
|
+
|
7
|
+
require 'sandal/version'
|
8
|
+
require 'sandal/sig'
|
9
|
+
require 'sandal/enc'
|
10
|
+
|
11
|
+
# A library for creating and reading JSON Web Tokens (JWT).
|
12
|
+
module Sandal
|
13
|
+
|
14
|
+
# Creates a signed token.
|
15
|
+
def self.encode_token(payload, sig, header_fields = nil)
|
16
|
+
if header_fields && header_fields['enc']
|
17
|
+
throw ArgumentError.new('The header cannot contain an "enc" parameter.')
|
18
|
+
end
|
19
|
+
sig ||= Sandal::Sig::None.new
|
20
|
+
|
21
|
+
header = {}
|
22
|
+
header['alg'] = sig.name if sig.name != 'none'
|
23
|
+
header = header_fields.merge(header) if header_fields
|
24
|
+
|
25
|
+
encoded_header = Sandal::Util.base64_encode(JSON.generate(header))
|
26
|
+
encoded_payload = Sandal::Util.base64_encode(payload)
|
27
|
+
secured_input = [encoded_header, encoded_payload].join('.')
|
28
|
+
|
29
|
+
signature = sig.sign(secured_input)
|
30
|
+
encoded_signature = Sandal::Util.base64_encode(signature)
|
31
|
+
[secured_input, encoded_signature].join('.')
|
32
|
+
end
|
33
|
+
|
34
|
+
# Creates an encrypted token.
|
35
|
+
def self.encrypt_token(payload, enc, header_fields = nil)
|
36
|
+
header = {}
|
37
|
+
header['enc'] = enc.name
|
38
|
+
header['alg'] = enc.alg_name
|
39
|
+
header = header_fields.merge(header) if header_fields
|
40
|
+
|
41
|
+
enc.encrypt(header, payload)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Decodes a token, verifying the signature if present.
|
45
|
+
def self.decode_token(token, &sig_finder)
|
46
|
+
parts = token.split('.')
|
47
|
+
throw ArgumentError.new('Invalid token format.') unless [2, 3].include?(parts.length)
|
48
|
+
begin
|
49
|
+
header = JSON.parse(Sandal::Util.base64_decode(parts[0]))
|
50
|
+
payload = Sandal::Util.base64_decode(parts[1])
|
51
|
+
signature = if parts.length > 2 then Sandal::Util.base64_decode(parts[2]) else '' end
|
52
|
+
rescue
|
53
|
+
throw ArgumentError.new('Invalid token encoding.')
|
54
|
+
end
|
55
|
+
|
56
|
+
algorithm = header['alg']
|
57
|
+
if algorithm && algorithm != 'none'
|
58
|
+
throw SecurityError.new('The signature is missing.') unless signature.length > 0
|
59
|
+
sig = sig_finder.call(header)
|
60
|
+
throw SecurityError.new('No signature verifier was found.') unless sig
|
61
|
+
secured_input = parts.take(2).join('.')
|
62
|
+
throw ArgumentError.new('Invalid signature.') unless sig.verify(signature, secured_input)
|
63
|
+
end
|
64
|
+
|
65
|
+
payload
|
66
|
+
end
|
67
|
+
|
68
|
+
# Decrypts a token.
|
69
|
+
def self.decrypt_token(encrypted_token, &key_finder)
|
70
|
+
parts = encrypted_token.split('.')
|
71
|
+
throw ArgumentError.new('Invalid token format.') unless parts.length == 5
|
72
|
+
begin
|
73
|
+
header = JSON.parse(Sandal::Util.base64_decode(parts[0]))
|
74
|
+
encrypted_key = Sandal::Util.base64_decode(parts[1])
|
75
|
+
iv = Sandal::Util.base64_decode(parts[2])
|
76
|
+
ciphertext = Sandal::Util.base64_decode(parts[3])
|
77
|
+
integrity_value = Sandal::Util.base64_decode(parts[4])
|
78
|
+
rescue
|
79
|
+
throw ArgumentError.new('Invalid token encoding.')
|
80
|
+
end
|
81
|
+
|
82
|
+
algorithm = header['alg']
|
83
|
+
encryption = header['enc']
|
84
|
+
case encryption
|
85
|
+
when 'A128CBC+HS256', 'A256CBC+HS512'
|
86
|
+
aes_length = Integer(encryption[1..3])
|
87
|
+
sha_length = Integer(encryption[-3..-1])
|
88
|
+
|
89
|
+
digest = OpenSSL::Digest.new("SHA#{sha_length}")
|
90
|
+
|
91
|
+
private_key = key_finder.call(header)
|
92
|
+
throw SecurityError.new('No key was found to decrypt the content master key.') unless private_key
|
93
|
+
content_master_key = private_key.private_decrypt(encrypted_key)
|
94
|
+
|
95
|
+
content_encryption_key = derive_content_key('Encryption', content_master_key, encryption, digest, aes_length)
|
96
|
+
content_integrity_key = derive_content_key('Integrity', content_master_key, encryption, digest, sha_length)
|
97
|
+
|
98
|
+
secured_input = parts.take(4).join('.')
|
99
|
+
computed_integrity_value = OpenSSL::HMAC.digest(digest, content_integrity_key, secured_input)
|
100
|
+
throw ArgumentError.new('Invalid signature.') unless integrity_value == computed_integrity_value
|
101
|
+
|
102
|
+
cipher = OpenSSL::Cipher.new("AES-#{aes_length}-CBC")
|
103
|
+
cipher.decrypt
|
104
|
+
cipher.key = content_encryption_key
|
105
|
+
cipher.iv = iv
|
106
|
+
cipher.update(ciphertext) + cipher.final
|
107
|
+
when 'A128GCM', 'A256GCM'
|
108
|
+
throw NotImplementedError.new("The GCM family of encryption algorithms are not implemented yet.")
|
109
|
+
else
|
110
|
+
throw NotImplementedError.new("The #{encryption} encryption algorithm is not supported.")
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
private
|
115
|
+
|
116
|
+
# Derives content keys using the Concat KDF.
|
117
|
+
def self.derive_content_key(label, content_master_key, encryption, digest, size)
|
118
|
+
round_number = [1].pack('N')
|
119
|
+
output_size = [size].pack('N')
|
120
|
+
enc_bytes = encryption.encode('utf-8').bytes.to_a.pack('C*')
|
121
|
+
epu = epv = [0].pack('N')
|
122
|
+
label_bytes = label.encode('us-ascii').bytes.to_a.pack('C*')
|
123
|
+
hash_input = round_number + content_master_key + output_size + enc_bytes + epu + epv + label_bytes
|
124
|
+
hash = digest.digest(hash_input)
|
125
|
+
hash[0..((size / 8) - 1)]
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
129
|
+
|
130
|
+
if __FILE__ == $0
|
131
|
+
|
132
|
+
# create payload
|
133
|
+
issued_at = Time.now
|
134
|
+
claims = JSON.generate({
|
135
|
+
iss: 'example.org',
|
136
|
+
aud: 'example.com',
|
137
|
+
sub: 'user@example.org',
|
138
|
+
iat: issued_at.to_i,
|
139
|
+
exp: (issued_at + 3600).to_i
|
140
|
+
})
|
141
|
+
|
142
|
+
puts claims.to_s
|
143
|
+
|
144
|
+
# sign and encrypt
|
145
|
+
jws_key = OpenSSL::PKey::RSA.new(2048)
|
146
|
+
sig = Sandal::Sig::RS256.new(jws_key)
|
147
|
+
jws_token = Sandal.encode_token(claims.to_s, sig)
|
148
|
+
|
149
|
+
puts jws_token
|
150
|
+
|
151
|
+
jwe_key = OpenSSL::PKey::RSA.new(2048)
|
152
|
+
enc = Sandal::Enc::AES128CBC.new(jwe_key.public_key)
|
153
|
+
jwe_token = Sandal.encrypt_token(jws_token, enc, { 'cty' => 'JWT' })
|
154
|
+
|
155
|
+
puts jwe_token
|
156
|
+
|
157
|
+
jws_token_2 = Sandal.decrypt_token(jwe_token) { |header| jwe_key }
|
158
|
+
roundtrip_claims = Sandal.decode_token(jws_token_2) { |header| Sandal::Sig::RS256.new(jws_key.public_key) }
|
159
|
+
|
160
|
+
puts roundtrip_claims
|
161
|
+
|
162
|
+
end
|
metadata
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sandal
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Greg Beech
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-03-22 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: A ruby library for creating and reading JSON Web Tokens (JWT), supporting
|
14
|
+
JSON Web Signatures (JWS) and JSON Web Encryption (JWE).
|
15
|
+
email:
|
16
|
+
- greg@gregbeech.com
|
17
|
+
executables: []
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- lib/sandal/enc/aescbc.rb
|
22
|
+
- lib/sandal/enc.rb
|
23
|
+
- lib/sandal/sig/hs.rb
|
24
|
+
- lib/sandal/sig/rs.rb
|
25
|
+
- lib/sandal/sig.rb
|
26
|
+
- lib/sandal/util.rb
|
27
|
+
- lib/sandal/version.rb
|
28
|
+
- lib/sandal.rb
|
29
|
+
homepage: http://rubygems.org/gems/sandal
|
30
|
+
licenses:
|
31
|
+
- MIT
|
32
|
+
metadata: {}
|
33
|
+
post_install_message:
|
34
|
+
rdoc_options: []
|
35
|
+
require_paths:
|
36
|
+
- lib
|
37
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ! '>='
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0'
|
42
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ! '>='
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
47
|
+
requirements: []
|
48
|
+
rubyforge_project:
|
49
|
+
rubygems_version: 2.0.3
|
50
|
+
signing_key:
|
51
|
+
specification_version: 4
|
52
|
+
summary: A JSON Web Token (JWT) library.
|
53
|
+
test_files: []
|