opentoken 1.1.0 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +1 -1
- data/lib/opentoken.rb +14 -61
- data/lib/opentoken/cipher.rb +69 -0
- data/lib/opentoken/password_key_generator.rb +4 -4
- data/lib/opentoken/version.rb +1 -1
- data/test/test_opentoken.rb +3 -1
- metadata +4 -3
data/README.md
CHANGED
@@ -15,7 +15,7 @@ attributes = OpenToken.decode 'opentoken-hashed-string'
|
|
15
15
|
|
16
16
|
# encrypt opentoken from hash of attributes
|
17
17
|
attributes = { 'subject' => 'foo', 'bar' => 'bak' }
|
18
|
-
token = OpenToken.encode attributes, OpenToken::
|
18
|
+
token = OpenToken.encode attributes, OpenToken::Cipher::AES_128_CBC
|
19
19
|
```
|
20
20
|
|
21
21
|
## Contributing
|
data/lib/opentoken.rb
CHANGED
@@ -8,36 +8,11 @@ require 'time'
|
|
8
8
|
require File.join(File.dirname(__FILE__), 'opentoken', 'token')
|
9
9
|
require File.join(File.dirname(__FILE__), 'opentoken', 'key_value_serializer')
|
10
10
|
require File.join(File.dirname(__FILE__), 'opentoken', 'password_key_generator')
|
11
|
+
require File.join(File.dirname(__FILE__), 'opentoken', 'cipher')
|
11
12
|
|
12
13
|
module OpenToken
|
13
14
|
class TokenInvalidError < StandardError; end
|
14
15
|
|
15
|
-
CIPHER_NULL = 0
|
16
|
-
CIPHER_AES_256_CBC = 1
|
17
|
-
CIPHER_AES_128_CBC = 2
|
18
|
-
CIPHER_3DES_168_CBC = 3
|
19
|
-
|
20
|
-
CIPHERS = {
|
21
|
-
CIPHER_NULL => {
|
22
|
-
:iv_length => 0
|
23
|
-
},
|
24
|
-
CIPHER_AES_256_CBC => {
|
25
|
-
:algorithm => 'aes-256-cbc',
|
26
|
-
:iv_length => 32,
|
27
|
-
:key_length => 256
|
28
|
-
},
|
29
|
-
CIPHER_AES_128_CBC => {
|
30
|
-
:algorithm => 'aes-128-cbc',
|
31
|
-
:iv_length => 16,
|
32
|
-
:key_length => 128
|
33
|
-
},
|
34
|
-
CIPHER_3DES_168_CBC => {
|
35
|
-
:algorithm => 'des-cbc',
|
36
|
-
:iv_length => 8,
|
37
|
-
:key_length => 168
|
38
|
-
}
|
39
|
-
}
|
40
|
-
|
41
16
|
class << self
|
42
17
|
attr_accessor :debug
|
43
18
|
def debug?
|
@@ -48,39 +23,29 @@ module OpenToken
|
|
48
23
|
attr_accessor :token_lifetime
|
49
24
|
attr_accessor :renew_until_lifetime
|
50
25
|
|
51
|
-
def encode(attributes,
|
26
|
+
def encode(attributes, cipher)
|
52
27
|
attributes['not-before'] = Time.now.utc.iso8601.to_s
|
53
28
|
attributes['not-on-or-after'] = Time.at(Time.now.to_i + token_lifetime).utc.iso8601.to_s
|
54
29
|
attributes['renew-until'] = Time.at(Time.now.to_i + renew_until_lifetime).utc.iso8601.to_s
|
55
30
|
|
56
|
-
cipher = CIPHERS[cipher_suite]
|
57
|
-
verify !cipher.nil?, "Unknown cipher suite: #{cipher_suite}"
|
58
|
-
key = OpenToken::PasswordKeyGenerator.generate(password, cipher)
|
59
|
-
c = OpenSSL::Cipher::Cipher::new(cipher[:algorithm])
|
60
|
-
c.encrypt
|
61
|
-
c.key = key
|
62
|
-
c.iv = iv = c.random_iv
|
63
31
|
serialized = OpenToken::KeyValueSerializer.serialize(attributes)
|
64
32
|
compressed = zip_payload serialized
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
end
|
71
|
-
compressed += padlen.chr * padlen
|
72
|
-
encrypted = c.update(compressed)
|
33
|
+
|
34
|
+
key = cipher.generate_key
|
35
|
+
iv = cipher.generate_iv
|
36
|
+
encrypted = cipher.encrypt_payload compressed, key, iv
|
37
|
+
|
73
38
|
mac = []
|
74
39
|
mac << "0x01".hex.chr # OTK version
|
75
|
-
mac <<
|
40
|
+
mac << cipher.suite.chr
|
76
41
|
mac << iv
|
77
42
|
mac << serialized
|
78
43
|
hash = OpenSSL::HMAC.digest(OpenToken::PasswordKeyGenerator::SHA1_DIGEST, key, mac.join)
|
79
44
|
|
80
45
|
token_string = ""
|
81
|
-
token_string = "OTK" + 1.chr +
|
46
|
+
token_string = "OTK" + 1.chr + cipher.suite.chr
|
82
47
|
token_string += hash
|
83
|
-
token_string +=
|
48
|
+
token_string += cipher.iv_length.chr
|
84
49
|
token_string += iv
|
85
50
|
token_string += 0.chr # key info length
|
86
51
|
token_string += ((encrypted.length >> 8) &0xFF ).chr
|
@@ -101,8 +66,7 @@ module OpenToken
|
|
101
66
|
|
102
67
|
#cipher suite identifier
|
103
68
|
cipher_suite = char_value_of data[4]
|
104
|
-
cipher =
|
105
|
-
verify !cipher.nil?, "Unknown cipher suite: #{cipher_suite}"
|
69
|
+
cipher = OpenToken::Cipher.for_suite cipher_suite
|
106
70
|
|
107
71
|
#SHA-1 HMAC
|
108
72
|
payload_hmac = data[5..24]
|
@@ -113,7 +77,7 @@ module OpenToken
|
|
113
77
|
iv_end = char_value_of [26, 26 + iv_length - 1].max
|
114
78
|
iv = data[26..iv_end]
|
115
79
|
inspect_binary_string "IV [26..#{iv_end}]", iv
|
116
|
-
verify iv_length == cipher
|
80
|
+
verify iv_length == cipher.iv_length, "Cipher expects iv length of #{cipher.iv_length} and was: #{iv_length}"
|
117
81
|
|
118
82
|
#key (not currently used)
|
119
83
|
key_length = char_value_of data[iv_end + 1]
|
@@ -127,10 +91,10 @@ module OpenToken
|
|
127
91
|
verify encrypted_payload.length == payload_length, "Payload length is #{encrypted_payload.length} and was expected to be #{payload_length}"
|
128
92
|
inspect_binary_string "ENCRYPTED PAYLOAD [#{payload_offset}..#{data.length - 1}]", encrypted_payload
|
129
93
|
|
130
|
-
key =
|
94
|
+
key = cipher.generate_key
|
131
95
|
inspect_binary_string 'KEY', key
|
132
96
|
|
133
|
-
compressed_payload = decrypt_payload
|
97
|
+
compressed_payload = cipher.decrypt_payload encrypted_payload, key, iv
|
134
98
|
inspect_binary_string 'COMPRESSED PAYLOAD', compressed_payload
|
135
99
|
|
136
100
|
unparsed_payload = unzip_payload compressed_payload
|
@@ -185,17 +149,6 @@ module OpenToken
|
|
185
149
|
def verify(assertion, message = 'Invalid Token')
|
186
150
|
raise OpenToken::TokenInvalidError.new(message) unless assertion
|
187
151
|
end
|
188
|
-
#see http://snippets.dzone.com/posts/show/4975
|
189
|
-
#see http://jdwyah.blogspot.com/2009/12/decrypting-ruby-aes-encryption.html
|
190
|
-
#see http://snippets.dzone.com/posts/show/576
|
191
|
-
def decrypt_payload(encrypted_payload, cipher, key, iv)
|
192
|
-
return encrypted_payload unless cipher[:algorithm]
|
193
|
-
crypt = OpenSSL::Cipher::Cipher.new(cipher[:algorithm])
|
194
|
-
crypt.decrypt
|
195
|
-
crypt.key = key
|
196
|
-
crypt.iv = iv
|
197
|
-
crypt.update(encrypted_payload) + crypt.final
|
198
|
-
end
|
199
152
|
#decompress the payload
|
200
153
|
#see http://stackoverflow.com/questions/1361892/how-to-decompress-gzip-data-in-ruby
|
201
154
|
def unzip_payload(compressed_payload)
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
|
3
|
+
module OpenToken
|
4
|
+
class Cipher
|
5
|
+
class InvalidCipherError < StandardError; end
|
6
|
+
|
7
|
+
attr_reader :algorithm
|
8
|
+
attr_reader :iv_length
|
9
|
+
attr_reader :key_length
|
10
|
+
attr_reader :suite
|
11
|
+
|
12
|
+
def initialize(attrs = {})
|
13
|
+
@suite = attrs[:suite]
|
14
|
+
@iv_length = attrs[:iv_length]
|
15
|
+
@key_length = attrs[:key_length]
|
16
|
+
@algorithm = attrs[:algorithm]
|
17
|
+
end
|
18
|
+
def self.for_suite(cipher_suite)
|
19
|
+
cipher = REGISTERED_CIPHERS.detect {|c| c.suite == cipher_suite }
|
20
|
+
raise InvalidCipherError.new("Unknown cipher suite: #{cipher_suite}") unless cipher
|
21
|
+
cipher
|
22
|
+
end
|
23
|
+
|
24
|
+
def generate_key
|
25
|
+
OpenToken::PasswordKeyGenerator.generate OpenToken.password, self
|
26
|
+
end
|
27
|
+
def generate_iv
|
28
|
+
OpenSSL::Random.random_bytes(iv_length)
|
29
|
+
end
|
30
|
+
|
31
|
+
#see http://snippets.dzone.com/posts/show/4975
|
32
|
+
#see http://jdwyah.blogspot.com/2009/12/decrypting-ruby-aes-encryption.html
|
33
|
+
#see http://snippets.dzone.com/posts/show/576
|
34
|
+
def decrypt_payload(encrypted_payload, key, iv)
|
35
|
+
return encrypted_payload unless algorithm
|
36
|
+
c = crypt :decrypt, key, iv
|
37
|
+
c.update(encrypted_payload) + c.final
|
38
|
+
end
|
39
|
+
def encrypt_payload(payload, key, iv)
|
40
|
+
c = crypt :encrypt, key, iv
|
41
|
+
padding = if payload.length % iv_length == 0
|
42
|
+
iv_length
|
43
|
+
else
|
44
|
+
iv_length - (payload.length % iv_length)
|
45
|
+
end
|
46
|
+
c.update(payload + (padding.chr * padding))
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
def crypt(operation, key, iv)
|
51
|
+
crypt = OpenSSL::Cipher::Cipher.new(algorithm)
|
52
|
+
crypt.send operation
|
53
|
+
crypt.key = key
|
54
|
+
crypt.iv = iv
|
55
|
+
crypt
|
56
|
+
end
|
57
|
+
|
58
|
+
NULL = Cipher.new(:suite => 0, :iv_length => 0)
|
59
|
+
AES_256_CBC = Cipher.new(:suite => 1, :iv_length => 32, :key_length => 256, :algorithm => 'aes-256-cbc')
|
60
|
+
AES_128_CBC = Cipher.new(:suite => 2, :iv_length => 16, :key_length => 128, :algorithm => 'aes-128-cbc')
|
61
|
+
DES3_168_CBC = Cipher.new(:suite => 3, :iv_length => 8, :key_length => 168, :algorithm => 'des-cbc')
|
62
|
+
|
63
|
+
REGISTERED_CIPHERS = []
|
64
|
+
REGISTERED_CIPHERS << NULL
|
65
|
+
REGISTERED_CIPHERS << AES_256_CBC
|
66
|
+
REGISTERED_CIPHERS << AES_128_CBC
|
67
|
+
REGISTERED_CIPHERS << DES3_168_CBC
|
68
|
+
end
|
69
|
+
end
|
@@ -3,9 +3,9 @@ module OpenToken
|
|
3
3
|
SHA1_DIGEST = OpenSSL::Digest::Digest.new('sha1')
|
4
4
|
|
5
5
|
class << self
|
6
|
-
def generate(password,
|
6
|
+
def generate(password, cipher)
|
7
7
|
salt = 0.chr * 8
|
8
|
-
generate_impl(password,
|
8
|
+
generate_impl(password, cipher, salt, 1000)
|
9
9
|
end
|
10
10
|
|
11
11
|
private
|
@@ -35,9 +35,9 @@ module OpenToken
|
|
35
35
|
end
|
36
36
|
|
37
37
|
def generate_impl(password, cipher, salt, iterations)
|
38
|
-
return unless cipher
|
38
|
+
return unless cipher.algorithm
|
39
39
|
|
40
|
-
key_size = cipher
|
40
|
+
key_size = cipher.key_length / 8
|
41
41
|
numblocks = key_size / 20
|
42
42
|
numblocks += 1 if (key_size % 20) > 0
|
43
43
|
|
data/lib/opentoken/version.rb
CHANGED
data/test/test_opentoken.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
require 'helper'
|
2
2
|
|
3
3
|
class TestOpentoken < Test::Unit::TestCase
|
4
|
+
# OpenToken.debug = true
|
5
|
+
|
4
6
|
#"renew-until"=>"2010-03-05T07:19:15Z"
|
5
7
|
#"not-before"=>"2010-03-04T19:19:15Z"
|
6
8
|
#"not-on-or-after"=>"2010-03-04T19:24:15Z"
|
@@ -75,7 +77,7 @@ class TestOpentoken < Test::Unit::TestCase
|
|
75
77
|
context "with aes-128-cbc and subject attribute" do
|
76
78
|
setup do
|
77
79
|
@attributesIn = { "subject" => "john", "email" => "john@example.com"}
|
78
|
-
@token = OpenToken.encode @attributesIn, OpenToken::
|
80
|
+
@token = OpenToken.encode @attributesIn, OpenToken::Cipher::AES_128_CBC
|
79
81
|
end
|
80
82
|
should "be decodable" do
|
81
83
|
@attributesOut = OpenToken.decode @token
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: opentoken
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 31
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 1
|
8
|
-
-
|
8
|
+
- 2
|
9
9
|
- 0
|
10
|
-
version: 1.
|
10
|
+
version: 1.2.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Ryan Sonnek
|
@@ -95,6 +95,7 @@ files:
|
|
95
95
|
- README.md
|
96
96
|
- Rakefile
|
97
97
|
- lib/opentoken.rb
|
98
|
+
- lib/opentoken/cipher.rb
|
98
99
|
- lib/opentoken/key_value_serializer.rb
|
99
100
|
- lib/opentoken/password_key_generator.rb
|
100
101
|
- lib/opentoken/token.rb
|