sandal 0.0.0 → 0.0.1

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.
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=