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