opentoken 1.1.0 → 1.2.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.
- 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
|