sandal 0.0.0 → 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/lib/sandal.rb CHANGED
@@ -16,10 +16,10 @@ module Sandal
16
16
  if header_fields && header_fields['enc']
17
17
  throw ArgumentError.new('The header cannot contain an "enc" parameter.')
18
18
  end
19
- sig ||= Sandal::Sig::None.new
19
+ sig ||= Sandal::Sig::None.instance
20
20
 
21
21
  header = {}
22
- header['alg'] = sig.name if sig.name != 'none'
22
+ header['alg'] = sig.name if sig.name != Sandal::Sig::None.instance.name
23
23
  header = header_fields.merge(header) if header_fields
24
24
 
25
25
  encoded_header = Sandal::Util.base64_encode(JSON.generate(header))
@@ -66,7 +66,7 @@ module Sandal
66
66
  end
67
67
 
68
68
  # Decrypts a token.
69
- def self.decrypt_token(encrypted_token, &key_finder)
69
+ def self.decrypt_token(encrypted_token, &enc_finder)
70
70
  parts = encrypted_token.split('.')
71
71
  throw ArgumentError.new('Invalid token format.') unless parts.length == 5
72
72
  begin
@@ -79,50 +79,9 @@ module Sandal
79
79
  throw ArgumentError.new('Invalid token encoding.')
80
80
  end
81
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)]
82
+ enc = enc_finder.call(header)
83
+ throw SecurityError.new('No decryptor was found.') unless enc
84
+ enc.decrypt(encrypted_key, iv, ciphertext, parts.take(4).join('.'), integrity_value)
126
85
  end
127
86
 
128
87
  end
@@ -149,12 +108,12 @@ if __FILE__ == $0
149
108
  puts jws_token
150
109
 
151
110
  jwe_key = OpenSSL::PKey::RSA.new(2048)
152
- enc = Sandal::Enc::AES128CBC.new(jwe_key.public_key)
111
+ enc = Sandal::Enc::AES128GCM.new(jwe_key.public_key)
153
112
  jwe_token = Sandal.encrypt_token(jws_token, enc, { 'cty' => 'JWT' })
154
113
 
155
114
  puts jwe_token
156
115
 
157
- jws_token_2 = Sandal.decrypt_token(jwe_token) { |header| jwe_key }
116
+ jws_token_2 = Sandal.decrypt_token(jwe_token) { |header| Sandal::Enc::AES128CBC.new(jwe_key) }
158
117
  roundtrip_claims = Sandal.decode_token(jws_token_2) { |header| Sandal::Sig::RS256.new(jws_key.public_key) }
159
118
 
160
119
  puts roundtrip_claims
data/lib/sandal/enc.rb CHANGED
@@ -14,11 +14,12 @@ module Sandal
14
14
  end
15
15
 
16
16
  # Decrypts a token.
17
- def decrypt(data)
17
+ def decrypt(encrypted_key, iv, ciphertext, secured_input, integrity_value)
18
18
  throw NotImplementedError.new("#{@name}.decrypt is not implemented.")
19
19
  end
20
20
 
21
21
  end
22
22
  end
23
23
 
24
- require 'sandal/enc/aescbc'
24
+ require 'sandal/enc/aescbc'
25
+ require 'sandal/enc/aesgcm'
@@ -44,6 +44,19 @@ module Sandal
44
44
  [secured_input, encoded_integrity_value].join('.')
45
45
  end
46
46
 
47
+ def decrypt(encrypted_key, iv, ciphertext, secured_input, integrity_value)
48
+ content_master_key = @key.private_decrypt(encrypted_key)
49
+
50
+ content_integrity_key = derive_content_key('Integrity', content_master_key, @sha_size)
51
+ computed_integrity_value = OpenSSL::HMAC.digest(@digest, content_integrity_key, secured_input)
52
+ throw ArgumentError.new('Invalid signature.') unless integrity_value == computed_integrity_value
53
+
54
+ cipher = OpenSSL::Cipher.new(@cipher_name).decrypt
55
+ cipher.key = derive_content_key('Encryption', content_master_key, @aes_size)
56
+ cipher.iv = iv
57
+ cipher.update(ciphertext) + cipher.final
58
+ end
59
+
47
60
  private
48
61
 
49
62
  # Derives content keys using the Concat KDF.
@@ -0,0 +1,40 @@
1
+ require 'openssl'
2
+ require 'sandal/util'
3
+
4
+ module Sandal
5
+ module Enc
6
+
7
+ # Base implementation of the AES/GCM family of encryption algorithms.
8
+ class AESGCM
9
+ include Sandal::Enc
10
+
11
+ def initialize(aes_size, key)
12
+ throw NotImplementedException.new('AES-CGM is not yet implemented.')
13
+ end
14
+
15
+ def encrypt(header, payload)
16
+ throw NotImplementedException.new('AES-CGM is not yet implemented.')
17
+ end
18
+
19
+ def decrypt(encrypted_key, iv, ciphertext, secured_input, integrity_value)
20
+ throw NotImplementedException.new('AES-CGM is not yet implemented.')
21
+ end
22
+
23
+ end
24
+
25
+ # The AES-128-GCM encryption algorithm.
26
+ class AES128GCM < Sandal::Enc::AESGCM
27
+ def initialize(key)
28
+ super(128, key)
29
+ end
30
+ end
31
+
32
+ # The AES-256-GCM encryption algorithm.
33
+ class AES256GCM < Sandal::Enc::AESGCM
34
+ def initialize(key)
35
+ super(256, key)
36
+ end
37
+ end
38
+
39
+ end
40
+ end
data/lib/sandal/sig.rb CHANGED
@@ -1,3 +1,5 @@
1
+ require 'singleton'
2
+
1
3
  module Sandal
2
4
  # Common signature traits.
3
5
  module Sig
@@ -18,6 +20,7 @@ module Sandal
18
20
  # The 'none' JWA signature method.
19
21
  class None
20
22
  include Sandal::Sig
23
+ include Singleton
21
24
 
22
25
  # Creates a new instance.
23
26
  def initialize
@@ -39,5 +42,6 @@ module Sandal
39
42
  end
40
43
  end
41
44
 
45
+ require 'sandal/sig/es'
42
46
  require 'sandal/sig/hs'
43
47
  require 'sandal/sig/rs'
@@ -0,0 +1,88 @@
1
+ require 'openssl'
2
+
3
+ module Sandal
4
+ module Sig
5
+
6
+ # Base implementation of the ECDSA-SHA family of signature algorithms.
7
+ class ES
8
+ include Sandal::Sig
9
+
10
+ # Creates a new instance with the size of the SHA algorithm and an OpenSSL ES PKey.
11
+ def initialize(sha_size, key)
12
+ throw ArgumentError.new('A key is required.') unless key
13
+ @name = "ES#{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
+ hash = @digest.digest(payload)
21
+ asn1_sig = @key.dsa_sign_asn1(hash)
22
+ r, s = self.class.decode_asn1_signature(asn1_sig)
23
+ self.class.encode_jws_signature(r, s)
24
+ end
25
+
26
+ # Verifies a payload signature and returns whether the signature matches.
27
+ def verify(signature, payload)
28
+ hash = @digest.digest(payload)
29
+ r, s = self.class.decode_jws_signature(signature)
30
+ asn1_sig = self.class.encode_asn1_signature(r, s)
31
+ @key.dsa_verify_asn1(hash, asn1_sig)
32
+ end
33
+
34
+ # Decodes an ASN1 signature into a pair of BNs.
35
+ def self.decode_asn1_signature(signature)
36
+ asn_seq = OpenSSL::ASN1.decode(signature)
37
+ return asn_seq.value[0].value, asn_seq.value[1].value
38
+ end
39
+
40
+ # Encodes a pair of BNs into an ASN1 signature.
41
+ def self.encode_asn1_signature(r, s)
42
+ items = [OpenSSL::ASN1::Integer.new(r), OpenSSL::ASN1::Integer.new(s)]
43
+ OpenSSL::ASN1::Sequence.new(items).to_der
44
+ end
45
+
46
+ # Decodes a JWS signature into a pair of BNs.
47
+ def self.decode_jws_signature(signature)
48
+ hex_string = Sandal::Util.base64_decode(signature)
49
+ coord_length = hex_string.length / 2
50
+ r = OpenSSL::BN.new(hex_string[0..(coord_length - 1)].unpack('H*')[0], 16)
51
+ s = OpenSSL::BN.new(hex_string[coord_length..-1].unpack('H*')[0], 16)
52
+ return r, s
53
+ end
54
+
55
+ # Encodes a pair of BNs into a JWS signature.
56
+ def self.encode_jws_signature(r, s)
57
+ hex_string = [r.to_s(16) + s.to_s(16)].pack('H*')
58
+ Sandal::Util.base64_encode(hex_string)
59
+ end
60
+
61
+ end
62
+
63
+ # The ECDSA-SHA256 signing algorithm.
64
+ class ES256 < Sandal::Sig::ES
65
+ # Creates a new instance with an OpenSSL ES PKey.
66
+ def initialize(key)
67
+ super(256, key)
68
+ end
69
+ end
70
+
71
+ # The ECDSA-SHA384 signing algorithm.
72
+ class ES384 < Sandal::Sig::ES
73
+ # Creates a new instance with an OpenSSL ES PKey.
74
+ def initialize(key)
75
+ super(384, key)
76
+ end
77
+ end
78
+
79
+ # The ECDSA-SHA512 signing algorithm.
80
+ class ES512 < Sandal::Sig::ES
81
+ # Creates a new instance with an OpenSSL ES PKey.
82
+ def initialize(key)
83
+ super(512, key)
84
+ end
85
+ end
86
+
87
+ end
88
+ end
@@ -1,4 +1,4 @@
1
1
  module Sandal
2
2
  # The semantic version of the library.
3
- VERSION = '0.0.0'
3
+ VERSION = '0.0.1'
4
4
  end
metadata CHANGED
@@ -1,14 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sandal
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.0
4
+ version: 0.0.1
5
+ prerelease:
5
6
  platform: ruby
6
7
  authors:
7
8
  - Greg Beech
8
9
  autorequire:
9
10
  bindir: bin
10
11
  cert_chain: []
11
- date: 2013-03-22 00:00:00.000000000 Z
12
+ date: 2013-03-25 00:00:00.000000000 Z
12
13
  dependencies: []
13
14
  description: A ruby library for creating and reading JSON Web Tokens (JWT), supporting
14
15
  JSON Web Signatures (JWS) and JSON Web Encryption (JWE).
@@ -19,7 +20,9 @@ extensions: []
19
20
  extra_rdoc_files: []
20
21
  files:
21
22
  - lib/sandal/enc/aescbc.rb
23
+ - lib/sandal/enc/aesgcm.rb
22
24
  - lib/sandal/enc.rb
25
+ - lib/sandal/sig/es.rb
23
26
  - lib/sandal/sig/hs.rb
24
27
  - lib/sandal/sig/rs.rb
25
28
  - lib/sandal/sig.rb
@@ -29,25 +32,26 @@ files:
29
32
  homepage: http://rubygems.org/gems/sandal
30
33
  licenses:
31
34
  - MIT
32
- metadata: {}
33
35
  post_install_message:
34
36
  rdoc_options: []
35
37
  require_paths:
36
38
  - lib
37
39
  required_ruby_version: !ruby/object:Gem::Requirement
40
+ none: false
38
41
  requirements:
39
42
  - - ! '>='
40
43
  - !ruby/object:Gem::Version
41
44
  version: '0'
42
45
  required_rubygems_version: !ruby/object:Gem::Requirement
46
+ none: false
43
47
  requirements:
44
48
  - - ! '>='
45
49
  - !ruby/object:Gem::Version
46
50
  version: '0'
47
51
  requirements: []
48
52
  rubyforge_project:
49
- rubygems_version: 2.0.3
53
+ rubygems_version: 1.8.25
50
54
  signing_key:
51
- specification_version: 4
55
+ specification_version: 3
52
56
  summary: A JSON Web Token (JWT) library.
53
57
  test_files: []
checksums.yaml DELETED
@@ -1,15 +0,0 @@
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=