opentoken 1.0.0 → 1.1.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/.gitignore ADDED
@@ -0,0 +1,22 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+ Gemfile.lock
21
+
22
+ ## PROJECT::SPECIFIC
data/CONTRIBUTORS.txt ADDED
@@ -0,0 +1,7 @@
1
+ Ryan Sonnek - Original Author
2
+
3
+ Dan Alvizu - contributed OpenToken encode functionality
4
+
5
+ Complete list of contributors:
6
+ https://github.com/socialcast/opentoken/contributors
7
+
data/Gemfile CHANGED
@@ -1,12 +1,4 @@
1
1
  source "http://rubygems.org"
2
2
 
3
- gem "activesupport", "~> 3.0.3"
4
- gem "i18n", ">= 0"
5
-
6
- group :development do
7
- gem "shoulda", ">= 0"
8
- gem "timecop", '>=0.3.4'
9
- gem "bundler", "~> 1.0.0"
10
- gem "jeweler", "~> 1.5.2"
11
- gem "rcov", ">= 0"
12
- end
3
+ # Specify your gem's dependencies in opentoken.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License
2
+
3
+ Copyright (c) 2011 Socialcast, Inc
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
22
+
data/README.md ADDED
@@ -0,0 +1,33 @@
1
+ # opentoken
2
+
3
+ Parse encrypted opentoken properties
4
+
5
+ see http://www.pingidentity.com/opentoken
6
+
7
+ ## Usage
8
+
9
+ ```ruby
10
+ # configure decryption with shared key
11
+ OpenToken.password = 'shared_secret_to_decrypt'
12
+
13
+ # decrypt opentoken into hash of attributes
14
+ attributes = OpenToken.decode 'opentoken-hashed-string'
15
+
16
+ # encrypt opentoken from hash of attributes
17
+ attributes = { 'subject' => 'foo', 'bar' => 'bak' }
18
+ token = OpenToken.encode attributes, OpenToken::CIPHER_AES_128_CBC
19
+ ```
20
+
21
+ ## Contributing
22
+
23
+ * Fork the project
24
+ * Fix the issue
25
+ * Add tests
26
+ * Send me a pull request. Bonus points for topic branches.
27
+
28
+ see CONTRIBUTORS.txt for complete list of contributors.
29
+
30
+ ## Copyright
31
+
32
+ Copyright (c) 2011 Socialcast Inc.
33
+ See LICENSE.txt for details.
data/Rakefile CHANGED
@@ -1,30 +1,5 @@
1
- require 'rubygems'
2
1
  require 'bundler'
3
- begin
4
- Bundler.setup(:default, :development)
5
- rescue Bundler::BundlerError => e
6
- $stderr.puts e.message
7
- $stderr.puts "Run `bundle install` to install missing gems"
8
- exit e.status_code
9
- end
10
- require 'rake'
11
-
12
- require 'jeweler'
13
- Jeweler::Tasks.new do |gem|
14
- # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
15
- gem.name = "opentoken"
16
- gem.homepage = "http://github.com/wireframe/opentoken"
17
- gem.license = "MIT"
18
- gem.summary = %Q{ruby implementation of the opentoken specification}
19
- gem.description = %Q{parse opentoken properties passed for Single Signon requests}
20
- gem.email = "ryan@codecrate.com"
21
- gem.authors = ["Ryan Sonnek"]
22
- # Include your dependencies below. Runtime dependencies are required when using your gem,
23
- # and development dependencies are only needed for development (ie running rake tasks, tests, etc)
24
- # gem.add_runtime_dependency 'jabber4r', '> 0.1'
25
- # gem.add_development_dependency 'rspec', '> 1.2.3'
26
- end
27
- Jeweler::RubygemsDotOrgTasks.new
2
+ Bundler::GemHelper.install_tasks
28
3
 
29
4
  require 'rake/testtask'
30
5
  Rake::TestTask.new(:test) do |test|
@@ -33,21 +8,4 @@ Rake::TestTask.new(:test) do |test|
33
8
  test.verbose = true
34
9
  end
35
10
 
36
- require 'rcov/rcovtask'
37
- Rcov::RcovTask.new do |test|
38
- test.libs << 'test'
39
- test.pattern = 'test/**/test_*.rb'
40
- test.verbose = true
41
- end
42
-
43
11
  task :default => :test
44
-
45
- require 'rake/rdoctask'
46
- Rake::RDocTask.new do |rdoc|
47
- version = File.exist?('VERSION') ? File.read('VERSION') : ""
48
-
49
- rdoc.rdoc_dir = 'rdoc'
50
- rdoc.title = "opentoken #{version}"
51
- rdoc.rdoc_files.include('README*')
52
- rdoc.rdoc_files.include('lib/**/*.rb')
53
- end
data/lib/opentoken.rb CHANGED
@@ -4,6 +4,7 @@ require 'digest/sha1'
4
4
  require 'zlib'
5
5
  require 'stringio'
6
6
  require 'cgi'
7
+ require 'time'
7
8
  require File.join(File.dirname(__FILE__), 'opentoken', 'token')
8
9
  require File.join(File.dirname(__FILE__), 'opentoken', 'key_value_serializer')
9
10
  require File.join(File.dirname(__FILE__), 'opentoken', 'password_key_generator')
@@ -38,27 +39,68 @@ module OpenToken
38
39
  }
39
40
 
40
41
  class << self
41
- @@debug = nil
42
- def debug=(flag)
43
- @@debug = flag
44
- end
42
+ attr_accessor :debug
45
43
  def debug?
46
- @@debug
44
+ !!debug
47
45
  end
48
- @@password = nil
49
- def password=(password)
50
- @@password = password
46
+
47
+ attr_accessor :password
48
+ attr_accessor :token_lifetime
49
+ attr_accessor :renew_until_lifetime
50
+
51
+ def encode(attributes, cipher_suite)
52
+ attributes['not-before'] = Time.now.utc.iso8601.to_s
53
+ attributes['not-on-or-after'] = Time.at(Time.now.to_i + token_lifetime).utc.iso8601.to_s
54
+ attributes['renew-until'] = Time.at(Time.now.to_i + renew_until_lifetime).utc.iso8601.to_s
55
+
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
+ serialized = OpenToken::KeyValueSerializer.serialize(attributes)
64
+ compressed = zip_payload serialized
65
+ ivlen = cipher[:iv_length]
66
+ if ((compressed.length % ivlen) == 0)
67
+ padlen = ivlen
68
+ else
69
+ padlen = ivlen - (compressed.length % ivlen)
70
+ end
71
+ compressed += padlen.chr * padlen
72
+ encrypted = c.update(compressed)
73
+ mac = []
74
+ mac << "0x01".hex.chr # OTK version
75
+ mac << cipher_suite.chr
76
+ mac << iv
77
+ mac << serialized
78
+ hash = OpenSSL::HMAC.digest(OpenToken::PasswordKeyGenerator::SHA1_DIGEST, key, mac.join)
79
+
80
+ token_string = ""
81
+ token_string = "OTK" + 1.chr + cipher_suite.chr
82
+ token_string += hash
83
+ token_string += ivlen.chr
84
+ token_string += iv
85
+ token_string += 0.chr # key info length
86
+ token_string += ((encrypted.length >> 8) &0xFF ).chr
87
+ token_string += (encrypted.length & 0xFF).chr
88
+ token_string += encrypted
89
+ inspect_binary_string "Unencoded", token_string
90
+ encoded = urlsafe_encode64 token_string
91
+ inspect_binary_string "Encoded", encoded
92
+ encoded
51
93
  end
52
- def parse(opentoken = nil)
94
+ def decode(opentoken = nil)
53
95
  verify opentoken.present?, 'Unable to parse empty token'
54
- data = decode(opentoken)
96
+ data = urlsafe_decode64(opentoken)
55
97
  inspect_binary_string 'DATA', data
56
98
 
57
99
  verify_header data
58
100
  verify_version data
59
101
 
60
102
  #cipher suite identifier
61
- cipher_suite = data[4]
103
+ cipher_suite = char_value_of data[4]
62
104
  cipher = CIPHERS[cipher_suite]
63
105
  verify !cipher.nil?, "Unknown cipher suite: #{cipher_suite}"
64
106
 
@@ -67,16 +109,16 @@ module OpenToken
67
109
  inspect_binary_string "PAYLOAD HMAC [5..24]", payload_hmac
68
110
 
69
111
  #Initialization Vector (iv)
70
- iv_length = data[25]
71
- iv_end = [26, 26 + iv_length - 1].max
112
+ iv_length = char_value_of data[25]
113
+ iv_end = char_value_of [26, 26 + iv_length - 1].max
72
114
  iv = data[26..iv_end]
73
115
  inspect_binary_string "IV [26..#{iv_end}]", iv
74
116
  verify iv_length == cipher[:iv_length], "Cipher expects iv length of #{cipher[:iv_length]} and was: #{iv_length}"
75
117
 
76
118
  #key (not currently used)
77
- key_length = data[iv_end + 1]
119
+ key_length = char_value_of data[iv_end + 1]
78
120
  key_end = iv_end + 1
79
- verify key_length == 0, "Token key embedding is not currently supported"
121
+ verify key_length == 0, "Token key embedding is not currently supported. Key length is: #{key_length}"
80
122
 
81
123
  #payload
82
124
  payload_length = data[(key_end + 1)..(key_end + 2)].unpack('n').first
@@ -85,7 +127,7 @@ module OpenToken
85
127
  verify encrypted_payload.length == payload_length, "Payload length is #{encrypted_payload.length} and was expected to be #{payload_length}"
86
128
  inspect_binary_string "ENCRYPTED PAYLOAD [#{payload_offset}..#{data.length - 1}]", encrypted_payload
87
129
 
88
- key = OpenToken::PasswordKeyGenerator.generate(@@password, cipher)
130
+ key = OpenToken::PasswordKeyGenerator.generate(password, cipher)
89
131
  inspect_binary_string 'KEY', key
90
132
 
91
133
  compressed_payload = decrypt_payload(encrypted_payload, cipher, key, iv)
@@ -115,19 +157,31 @@ module OpenToken
115
157
  end
116
158
 
117
159
  private
160
+ def char_value_of(character)
161
+ if RUBY_VERSION < "1.9"
162
+ return character
163
+ else
164
+ return character.chr.ord
165
+ end
166
+ end
118
167
  def verify_header(data)
119
168
  header = data[0..2]
120
169
  verify header == 'OTK', "Invalid token header: #{header}"
121
170
  end
122
171
  def verify_version(data)
123
- version = data[3]
124
- verify version == 1, "Unsupported token version: #{version}"
172
+ version = char_value_of data[3]
173
+ verify version == 1, "Unsupported token version: '#{version}'"
125
174
  end
126
175
  #ruby 1.9 has Base64.urlsafe_decode64 which can be used instead of gsubbing '_' and '-'
127
- def decode(token)
176
+ def urlsafe_decode64(token)
128
177
  string = token.gsub('*', '=').gsub('_', '/').gsub('-', '+')
129
178
  data = Base64.decode64(string)
130
179
  end
180
+ def urlsafe_encode64(token)
181
+ string = Base64.encode64(token);
182
+ string = string.gsub('=', '*').gsub('/', '_').gsub('+', '-').gsub(10.chr, '').gsub(11.chr, '')
183
+ string
184
+ end
131
185
  def verify(assertion, message = 'Invalid Token')
132
186
  raise OpenToken::TokenInvalidError.new(message) unless assertion
133
187
  end
@@ -151,6 +205,10 @@ module OpenToken
151
205
  Zlib::Inflate.new(-Zlib::MAX_WBITS).inflate(compressed_payload[2, compressed_payload.size])
152
206
  end
153
207
  end
208
+ def zip_payload(uncompressed)
209
+ compressed = Zlib::Deflate.deflate(uncompressed, 9)
210
+ compressed
211
+ end
154
212
  def inspect_binary_string(header, string)
155
213
  return unless debug?
156
214
  puts "#{header}:"
@@ -162,3 +220,7 @@ module OpenToken
162
220
  end
163
221
  end
164
222
  end
223
+
224
+ # intialize defaults
225
+ OpenToken.token_lifetime = 300
226
+ OpenToken.renew_until_lifetime = 43200
@@ -8,110 +8,135 @@ module OpenToken
8
8
  IN_VALUE = 5
9
9
  IN_QUOTED_VALUE = 6
10
10
 
11
- def self.unescape_value(value)
12
- value.gsub("\\\"", "\"").gsub("\\\'", "'")
13
- end
14
-
15
- def self.deserialize(string)
16
- result = OpenToken::Token.new
17
- state = LINE_START
18
- open_quote_char = 0.chr
19
- currkey = ""
20
- token = ""
21
- nextval = ""
22
-
23
- string.split(//).each do |c|
24
- nextval = c
25
-
26
- case c
27
- when "\t"
28
- if state == IN_KEY
29
- # key ends
30
- currkey = token
31
- token = ""
32
- state = EMPTY_SPACE
33
- elsif state == IN_VALUE
34
- # non-quoted value ends
35
- result[currkey] = self.deserialize(token)
36
- token = ""
37
- state = LINE_END
38
- elsif state == IN_QUOTED_VALUE
39
- token += c
40
- end
41
- when " "
42
- if state == IN_KEY
43
- # key ends
44
- currkey = token
45
- token = ""
46
- state = EMPTY_SPACE
47
- elsif state == IN_VALUE
48
- # non-quoted value ends
49
- result[currkey] = self.deserialize(token)
50
- token = ""
51
- state = LINE_END
52
- elsif state == IN_QUOTED_VALUE
53
- token += c
54
- end
55
- when "\n"
56
- # newline
57
- if (state == IN_VALUE) || (state == VALUE_START)
58
- result[currkey] = self.unescape_value(token)
59
- token = ""
60
- state = LINE_START
61
- elsif state == LINE_END
62
- token = ""
63
- state = LINE_START
64
- elsif state == IN_QUOTED_VALUE
65
- token += c
11
+ class << self
12
+ def serialize(hashmap)
13
+ result = String.new
14
+ count = 0;
15
+ hashmap.each_pair do |key,value|
16
+ if (count != 0)
17
+ result = result + "\n"
66
18
  end
67
- when "="
68
- if state == IN_KEY
69
- currkey = token
70
- token = ""
71
- state = VALUE_START
72
- elsif (state == IN_QUOTED_VALUE) || (state == IN_VALUE)
73
- token += c
74
- end
75
- when "\""
76
- if state == IN_QUOTED_VALUE
77
- if (c == open_quote_char) && (token[token.size-1] != "\\"[0])
78
- result[currkey] = self.unescape_value(token)
19
+ count +=1
20
+ result += key + "="
21
+ result += escape_value(value)
22
+ end
23
+ result
24
+ end
25
+ def deserialize(string)
26
+ result = OpenToken::Token.new
27
+ state = LINE_START
28
+ open_quote_char = 0.chr
29
+ currkey = ""
30
+ token = ""
31
+ nextval = ""
32
+
33
+ string.split(//).each do |c|
34
+ nextval = c
35
+
36
+ case c
37
+ when "\t"
38
+ if state == IN_KEY
39
+ # key ends
40
+ currkey = token
41
+ token = ""
42
+ state = EMPTY_SPACE
43
+ elsif state == IN_VALUE
44
+ # non-quoted value ends
45
+ result[currkey] = self.deserialize(token)
79
46
  token = ""
80
47
  state = LINE_END
81
- else
48
+ elsif state == IN_QUOTED_VALUE
82
49
  token += c
83
50
  end
84
- elsif state == VALUE_START
85
- state = IN_QUOTED_VALUE
86
- open_quote_char = c
87
- end
88
- when "'"
89
- if state == IN_QUOTED_VALUE
90
- if (c == open_quote_char) && (token[token.size-1] != "\\"[0])
91
- result[currkey] = self.unescape_value(token)
51
+ when " "
52
+ if state == IN_KEY
53
+ # key ends
54
+ currkey = token
55
+ token = ""
56
+ state = EMPTY_SPACE
57
+ elsif state == IN_VALUE
58
+ # non-quoted value ends
59
+ result[currkey] = self.deserialize(token)
92
60
  token = ""
93
61
  state = LINE_END
94
- else
62
+ elsif state == IN_QUOTED_VALUE
63
+ token += c
64
+ end
65
+ when "\n"
66
+ # newline
67
+ if (state == IN_VALUE) || (state == VALUE_START)
68
+ result[currkey] = unescape_value(token)
69
+ token = ""
70
+ state = LINE_START
71
+ elsif state == LINE_END
72
+ token = ""
73
+ state = LINE_START
74
+ elsif state == IN_QUOTED_VALUE
75
+ token += c
76
+ end
77
+ when "="
78
+ if state == IN_KEY
79
+ currkey = token
80
+ token = ""
81
+ state = VALUE_START
82
+ elsif (state == IN_QUOTED_VALUE) || (state == IN_VALUE)
95
83
  token += c
96
84
  end
97
- else state == VALUE_START
98
- state = IN_QUOTED_VALUE
99
- open_quote_char = c
85
+ when "\""
86
+ if state == IN_QUOTED_VALUE
87
+ if (c == open_quote_char) && (token[token.size-1] != "\\"[0])
88
+ result[currkey] = unescape_value(token)
89
+ token = ""
90
+ state = LINE_END
91
+ else
92
+ token += c
93
+ end
94
+ elsif state == VALUE_START
95
+ state = IN_QUOTED_VALUE
96
+ open_quote_char = c
97
+ end
98
+ when "'"
99
+ if state == IN_QUOTED_VALUE
100
+ if (c == open_quote_char) && (token[token.size-1] != "\\"[0])
101
+ result[currkey] = unescape_value(token)
102
+ token = ""
103
+ state = LINE_END
104
+ else
105
+ token += c
106
+ end
107
+ else state == VALUE_START
108
+ state = IN_QUOTED_VALUE
109
+ open_quote_char = c
110
+ end
111
+ else
112
+ if state == LINE_START
113
+ state = IN_KEY
114
+ elsif state == VALUE_START
115
+ state = IN_VALUE
116
+ end
117
+ token += c
100
118
  end
101
- else
102
- if state == LINE_START
103
- state = IN_KEY
104
- elsif state == VALUE_START
105
- state = IN_VALUE
119
+
120
+ if (state == IN_QUOTED_VALUE) || (state == IN_VALUE)
121
+ result[currkey] = unescape_value(token)
106
122
  end
107
- token += c
108
123
  end
109
-
110
- if (state == IN_QUOTED_VALUE) || (state == IN_VALUE)
111
- result[currkey] = unescape_value(token)
124
+ result
125
+ end
126
+ private
127
+ def unescape_value(value)
128
+ value.gsub("\\\"", "\"").gsub("\\\'", "'")
129
+ end
130
+ def escape_value(value)
131
+ value.each_byte do |b|
132
+ c = b.chr
133
+ if c == "\n" or c == "\t" or c == " " or c == "'" or c == "\""
134
+ value = "'" + value.gsub("'", "\'").gsub("\"", "\\\"") + "'"
135
+ break
136
+ end
112
137
  end
138
+ value
113
139
  end
114
- result
115
140
  end
116
141
  end
117
- end
142
+ end
@@ -2,56 +2,64 @@ module OpenToken
2
2
  class PasswordKeyGenerator
3
3
  SHA1_DIGEST = OpenSSL::Digest::Digest.new('sha1')
4
4
 
5
- def self.generate(password, cipher_suite)
6
- salt = 0.chr * 8
7
- self.generate_impl(password, cipher_suite, salt, 1000)
8
- end
5
+ class << self
6
+ def generate(password, cipher_suite)
7
+ salt = 0.chr * 8
8
+ generate_impl(password, cipher_suite, salt, 1000)
9
+ end
9
10
 
10
- def self.generate_block(password, salt, count, index)
11
- mac = salt
12
- mac += [index].pack("N")
13
-
14
- result = OpenSSL::HMAC.digest(SHA1_DIGEST, password, mac)
15
- cur = result
16
-
17
- i_count = 1
18
- while i_count < count
19
- i_count +=1
11
+ private
12
+ def generate_block(password, salt, count, index)
13
+ mac = salt
14
+ mac += [index].pack("N")
20
15
 
21
- cur = OpenSSL::HMAC.digest(SHA1_DIGEST, password, cur)
16
+ result = OpenSSL::HMAC.digest(SHA1_DIGEST, password, mac)
17
+ cur = result
22
18
 
23
- 20.times do |i|
24
- result[i] = result[i] ^ cur[i]
19
+ i_count = 1
20
+ while i_count < count
21
+ i_count +=1
22
+
23
+ cur = OpenSSL::HMAC.digest(SHA1_DIGEST, password, cur)
24
+
25
+ 20.times do |i|
26
+ if RUBY_VERSION < "1.9"
27
+ result[i] = result[i] ^ cur[i]
28
+ else
29
+ result[i] = (result[i].chr.ord ^ cur[i].chr.ord).chr
30
+ end
31
+ end
25
32
  end
33
+
34
+ return result
26
35
  end
36
+
37
+ def generate_impl(password, cipher, salt, iterations)
38
+ return unless cipher[:algorithm]
27
39
 
28
- return result
40
+ key_size = cipher[:key_length] / 8
41
+ numblocks = key_size / 20
42
+ numblocks += 1 if (key_size % 20) > 0
43
+
44
+ # Generate the appropriate number of blocks and write their output to
45
+ # the key bytes; note that it's important to start from 1 (vs. 0) as the
46
+ # initial block number affects the hash. It's not clear that this fact
47
+ # is stated explicitly anywhere, but without this approach, the generated
48
+ # keys will not match up with test cases defined in RFC 3962.
49
+ key_buffer_index = 0
50
+ key = ""
51
+
52
+ numblocks.times do |i|
53
+ i+=1 # Previously zero based, needs to be 1 based
54
+ block = generate_block(password, salt, iterations, i)
55
+ len = [20, (key_size - key_buffer_index)].min
56
+ key += block[0, len]
57
+ key_buffer_index += len
58
+ end
59
+
60
+ return key
61
+ end
29
62
  end
30
-
31
- def self.generate_impl(password, cipher, salt, iterations)
32
- return unless cipher[:algorithm]
33
63
 
34
- key_size = cipher[:key_length] / 8
35
- numblocks = key_size / 20
36
- numblocks += 1 if (key_size % 20) > 0
37
-
38
- # Generate the appropriate number of blocks and write their output to
39
- # the key bytes; note that it's important to start from 1 (vs. 0) as the
40
- # initial block number affects the hash. It's not clear that this fact
41
- # is stated explicitly anywhere, but without this approach, the generated
42
- # keys will not match up with test cases defined in RFC 3962.
43
- key_buffer_index = 0
44
- key = ""
45
-
46
- numblocks.times do |i|
47
- i+=1 # Previously zero based, needs to be 1 based
48
- block = self.generate_block(password, salt, iterations, i)
49
- len = [20, (key_size - key_buffer_index)].min
50
- key += block[0, len]
51
- key_buffer_index += len
52
- end
53
-
54
- return key
55
64
  end
56
- end
57
- end
65
+ end
@@ -0,0 +1,3 @@
1
+ module OpenToken
2
+ VERSION = '1.1.0'
3
+ end
data/opentoken.gemspec CHANGED
@@ -1,74 +1,26 @@
1
- # Generated by jeweler
2
- # DO NOT EDIT THIS FILE DIRECTLY
3
- # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
1
  # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "opentoken/version"
5
4
 
6
5
  Gem::Specification.new do |s|
7
- s.name = %q{opentoken}
8
- s.version = "1.0.0"
9
-
10
- s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
- s.authors = ["Ryan Sonnek"]
12
- s.date = %q{2011-01-18}
6
+ s.name = "opentoken"
7
+ s.version = OpenToken::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Ryan Sonnek"]
10
+ s.email = ["ryan@socialcast.com"]
11
+ s.homepage = "http://github.com/socialcast/opentoken"
12
+ s.summary = %q{ruby implementation of the opentoken specification}
13
13
  s.description = %q{parse opentoken properties passed for Single Signon requests}
14
- s.email = %q{ryan@codecrate.com}
15
- s.extra_rdoc_files = [
16
- "LICENSE",
17
- "README.rdoc"
18
- ]
19
- s.files = [
20
- ".document",
21
- "Gemfile",
22
- "LICENSE",
23
- "README.rdoc",
24
- "Rakefile",
25
- "VERSION",
26
- "lib/opentoken.rb",
27
- "lib/opentoken/key_value_serializer.rb",
28
- "lib/opentoken/password_key_generator.rb",
29
- "lib/opentoken/token.rb",
30
- "opentoken.gemspec",
31
- "test/helper.rb",
32
- "test/test_opentoken.rb"
33
- ]
34
- s.homepage = %q{http://github.com/wireframe/opentoken}
35
- s.licenses = ["MIT"]
36
- s.require_paths = ["lib"]
37
- s.rubygems_version = %q{1.4.2}
38
- s.summary = %q{ruby implementation of the opentoken specification}
39
- s.test_files = [
40
- "test/helper.rb",
41
- "test/test_opentoken.rb"
42
- ]
43
14
 
44
- if s.respond_to? :specification_version then
45
- s.specification_version = 3
15
+ s.rubyforge_project = "opentoken"
46
16
 
47
- if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
48
- s.add_runtime_dependency(%q<activesupport>, ["~> 3.0.3"])
49
- s.add_runtime_dependency(%q<i18n>, [">= 0"])
50
- s.add_development_dependency(%q<shoulda>, [">= 0"])
51
- s.add_development_dependency(%q<timecop>, [">= 0.3.4"])
52
- s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
53
- s.add_development_dependency(%q<jeweler>, ["~> 1.5.2"])
54
- s.add_development_dependency(%q<rcov>, [">= 0"])
55
- else
56
- s.add_dependency(%q<activesupport>, ["~> 3.0.3"])
57
- s.add_dependency(%q<i18n>, [">= 0"])
58
- s.add_dependency(%q<shoulda>, [">= 0"])
59
- s.add_dependency(%q<timecop>, [">= 0.3.4"])
60
- s.add_dependency(%q<bundler>, ["~> 1.0.0"])
61
- s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
62
- s.add_dependency(%q<rcov>, [">= 0"])
63
- end
64
- else
65
- s.add_dependency(%q<activesupport>, ["~> 3.0.3"])
66
- s.add_dependency(%q<i18n>, [">= 0"])
67
- s.add_dependency(%q<shoulda>, [">= 0"])
68
- s.add_dependency(%q<timecop>, [">= 0.3.4"])
69
- s.add_dependency(%q<bundler>, ["~> 1.0.0"])
70
- s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
71
- s.add_dependency(%q<rcov>, [">= 0"])
72
- end
73
- end
17
+ s.add_runtime_dependency(%q<activesupport>, ["~> 3.0.3"])
18
+ s.add_runtime_dependency(%q<i18n>, [">= 0"])
19
+ s.add_development_dependency(%q<shoulda>, [">= 0"])
20
+ s.add_development_dependency(%q<timecop>, [">= 0.3.4"])
74
21
 
22
+ s.files = `git ls-files`.split("\n")
23
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
24
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
25
+ s.require_paths = ["lib"]
26
+ end
@@ -10,11 +10,11 @@ class TestOpentoken < Test::Unit::TestCase
10
10
  @password = 'Test123'
11
11
  OpenToken.password = @password
12
12
  end
13
- context "parsing token between expiration dates" do
13
+ context "decoding token between expiration dates" do
14
14
  setup do
15
15
  Timecop.travel(Time.iso8601('2010-03-04T19:20:10Z')) do
16
16
  assert_nothing_raised do
17
- @token = OpenToken.parse @opentoken
17
+ @token = OpenToken.decode @opentoken
18
18
  end
19
19
  end
20
20
  end
@@ -29,31 +29,31 @@ class TestOpentoken < Test::Unit::TestCase
29
29
  end
30
30
  end
31
31
 
32
- context "parsing token when current time is before expiration date" do
32
+ context "decoding token when current time is before expiration date" do
33
33
  should "raise TokenExpiredError" do
34
34
  Timecop.travel(Time.iso8601('2010-03-04T19:19:10Z')) do
35
35
  assert_raises OpenToken::TokenExpiredError do
36
- @token = OpenToken.parse @opentoken
36
+ @token = OpenToken.decode @opentoken
37
37
  end
38
38
  end
39
39
  end
40
40
  end
41
41
 
42
- context "parsing token when current time is equal to expiration date" do
42
+ context "decoding token when current time is equal to expiration date" do
43
43
  should "raise TokenExpiredError" do
44
44
  Timecop.travel(Time.iso8601('2010-03-04T19:24:15Z')) do
45
45
  assert_raises OpenToken::TokenExpiredError do
46
- @token = OpenToken.parse @opentoken
46
+ @token = OpenToken.decode @opentoken
47
47
  end
48
48
  end
49
49
  end
50
50
  end
51
51
 
52
- context "parsing token with attribute value containing apostrophe" do
52
+ context "decoding token with attribute value containing apostrophe" do
53
53
  setup do
54
54
  Timecop.travel(Time.iso8601('2011-01-13T11:08:01Z')) do
55
55
  @opentoken = "T1RLAQLIjiqgexqi1PQcEKCetvGoSYR2jhDFSIfE5ctlSBxEnq3S1ydjAADQUNRIKJx6_14aE3MQZnDABupGJrKNfoJHFS5VOnKexjMtboeOgst31Hf-D9CZBrpB7Jv0KBwnQ7DN3HizecPT76oX3UGtq_Vi5j5bKYCeObYm9W6h7NY-VzcZY5TTqIuulc2Jit381usAWZ2Sv1c_CWwhrH4hw-x7vUQMSjErvXK1qvsrFCpfNr7XlArx0HjI6kT5XEaHgQNdC0zrLw9cZ4rewoEisR3H5oM7B6gMaP82wTSFVBXvpn5r0KT-Iuc3JuG2en1zVh3GNf110oQCKQ**"
56
- @token = OpenToken.parse @opentoken
56
+ @token = OpenToken.decode @opentoken
57
57
  end
58
58
  end
59
59
  should 'preserve apostrophe in attribute payload' do
@@ -63,7 +63,23 @@ class TestOpentoken < Test::Unit::TestCase
63
63
 
64
64
  should 'raise invalid token error parsing nil token' do
65
65
  assert_raises OpenToken::TokenInvalidError do
66
- OpenToken.parse nil
66
+ OpenToken.decode nil
67
+ end
68
+ end
69
+ end
70
+
71
+ context "encoding token" do
72
+ setup do
73
+ OpenToken.password = "Password1"
74
+ end
75
+ context "with aes-128-cbc and subject attribute" do
76
+ setup do
77
+ @attributesIn = { "subject" => "john", "email" => "john@example.com"}
78
+ @token = OpenToken.encode @attributesIn, OpenToken::CIPHER_AES_128_CBC
79
+ end
80
+ should "be decodable" do
81
+ @attributesOut = OpenToken.decode @token
82
+ assert_equal @attributesIn, @attributesOut
67
83
  end
68
84
  end
69
85
  end
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: 23
4
+ hash: 19
5
5
  prerelease:
6
6
  segments:
7
7
  - 1
8
+ - 1
8
9
  - 0
9
- - 0
10
- version: 1.0.0
10
+ version: 1.1.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Ryan Sonnek
@@ -15,12 +15,12 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-01-18 00:00:00 -06:00
19
- default_executable:
18
+ date: 2011-10-13 00:00:00 Z
20
19
  dependencies:
21
20
  - !ruby/object:Gem::Dependency
22
- type: :runtime
23
- version_requirements: &id001 !ruby/object:Gem::Requirement
21
+ name: activesupport
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
24
  none: false
25
25
  requirements:
26
26
  - - ~>
@@ -31,12 +31,12 @@ dependencies:
31
31
  - 0
32
32
  - 3
33
33
  version: 3.0.3
34
- requirement: *id001
35
- prerelease: false
36
- name: activesupport
37
- - !ruby/object:Gem::Dependency
38
34
  type: :runtime
39
- version_requirements: &id002 !ruby/object:Gem::Requirement
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ name: i18n
38
+ prerelease: false
39
+ requirement: &id002 !ruby/object:Gem::Requirement
40
40
  none: false
41
41
  requirements:
42
42
  - - ">="
@@ -45,12 +45,12 @@ dependencies:
45
45
  segments:
46
46
  - 0
47
47
  version: "0"
48
- requirement: *id002
49
- prerelease: false
50
- name: i18n
48
+ type: :runtime
49
+ version_requirements: *id002
51
50
  - !ruby/object:Gem::Dependency
52
- type: :development
53
- version_requirements: &id003 !ruby/object:Gem::Requirement
51
+ name: shoulda
52
+ prerelease: false
53
+ requirement: &id003 !ruby/object:Gem::Requirement
54
54
  none: false
55
55
  requirements:
56
56
  - - ">="
@@ -59,12 +59,12 @@ dependencies:
59
59
  segments:
60
60
  - 0
61
61
  version: "0"
62
- requirement: *id003
63
- prerelease: false
64
- name: shoulda
65
- - !ruby/object:Gem::Dependency
66
62
  type: :development
67
- version_requirements: &id004 !ruby/object:Gem::Requirement
63
+ version_requirements: *id003
64
+ - !ruby/object:Gem::Dependency
65
+ name: timecop
66
+ prerelease: false
67
+ requirement: &id004 !ruby/object:Gem::Requirement
68
68
  none: false
69
69
  requirements:
70
70
  - - ">="
@@ -75,82 +75,36 @@ dependencies:
75
75
  - 3
76
76
  - 4
77
77
  version: 0.3.4
78
- requirement: *id004
79
- prerelease: false
80
- name: timecop
81
- - !ruby/object:Gem::Dependency
82
78
  type: :development
83
- version_requirements: &id005 !ruby/object:Gem::Requirement
84
- none: false
85
- requirements:
86
- - - ~>
87
- - !ruby/object:Gem::Version
88
- hash: 23
89
- segments:
90
- - 1
91
- - 0
92
- - 0
93
- version: 1.0.0
94
- requirement: *id005
95
- prerelease: false
96
- name: bundler
97
- - !ruby/object:Gem::Dependency
98
- type: :development
99
- version_requirements: &id006 !ruby/object:Gem::Requirement
100
- none: false
101
- requirements:
102
- - - ~>
103
- - !ruby/object:Gem::Version
104
- hash: 7
105
- segments:
106
- - 1
107
- - 5
108
- - 2
109
- version: 1.5.2
110
- requirement: *id006
111
- prerelease: false
112
- name: jeweler
113
- - !ruby/object:Gem::Dependency
114
- type: :development
115
- version_requirements: &id007 !ruby/object:Gem::Requirement
116
- none: false
117
- requirements:
118
- - - ">="
119
- - !ruby/object:Gem::Version
120
- hash: 3
121
- segments:
122
- - 0
123
- version: "0"
124
- requirement: *id007
125
- prerelease: false
126
- name: rcov
79
+ version_requirements: *id004
127
80
  description: parse opentoken properties passed for Single Signon requests
128
- email: ryan@codecrate.com
81
+ email:
82
+ - ryan@socialcast.com
129
83
  executables: []
130
84
 
131
85
  extensions: []
132
86
 
133
- extra_rdoc_files:
134
- - LICENSE
135
- - README.rdoc
87
+ extra_rdoc_files: []
88
+
136
89
  files:
137
90
  - .document
91
+ - .gitignore
92
+ - CONTRIBUTORS.txt
138
93
  - Gemfile
139
- - LICENSE
140
- - README.rdoc
94
+ - LICENSE.txt
95
+ - README.md
141
96
  - Rakefile
142
- - VERSION
143
97
  - lib/opentoken.rb
144
98
  - lib/opentoken/key_value_serializer.rb
145
99
  - lib/opentoken/password_key_generator.rb
146
100
  - lib/opentoken/token.rb
101
+ - lib/opentoken/version.rb
147
102
  - opentoken.gemspec
148
103
  - test/helper.rb
149
104
  - test/test_opentoken.rb
150
- has_rdoc: true
151
- homepage: http://github.com/wireframe/opentoken
152
- licenses:
153
- - MIT
105
+ homepage: http://github.com/socialcast/opentoken
106
+ licenses: []
107
+
154
108
  post_install_message:
155
109
  rdoc_options: []
156
110
 
@@ -176,8 +130,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
176
130
  version: "0"
177
131
  requirements: []
178
132
 
179
- rubyforge_project:
180
- rubygems_version: 1.4.2
133
+ rubyforge_project: opentoken
134
+ rubygems_version: 1.8.5
181
135
  signing_key:
182
136
  specification_version: 3
183
137
  summary: ruby implementation of the opentoken specification
data/LICENSE DELETED
@@ -1,20 +0,0 @@
1
- Copyright (c) 2009 Ryan Sonnek
2
-
3
- Permission is hereby granted, free of charge, to any person obtaining
4
- a copy of this software and associated documentation files (the
5
- "Software"), to deal in the Software without restriction, including
6
- without limitation the rights to use, copy, modify, merge, publish,
7
- distribute, sublicense, and/or sell copies of the Software, and to
8
- permit persons to whom the Software is furnished to do so, subject to
9
- the following conditions:
10
-
11
- The above copyright notice and this permission notice shall be
12
- included in all copies or substantial portions of the Software.
13
-
14
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc DELETED
@@ -1,25 +0,0 @@
1
- = opentoken
2
-
3
- Parse encrypted opentoken properties
4
-
5
- see http://www.pingidentity.com/opentoken
6
-
7
- == Usage
8
-
9
- #configure decryption with shared key
10
- OpenToken.password = 'shared_secret_to_decrypt'
11
-
12
- #decrypt opentoken into hash of attributes
13
- attributes = OpenToken.parse opentoken
14
-
15
- == Note on Patches/Pull Requests
16
-
17
- * Fork the project.
18
- * Make your feature addition or bug fix.
19
- * Add tests for it. This is important so I don't break it in a future version unintentionally.
20
- * Commit, do not mess with rakefile, version, or history. (bump version in a commit by itself I can ignore when I pull)
21
- * Send me a pull request. Bonus points for topic branches.
22
-
23
- == Copyright
24
-
25
- Copyright (c) 2010 Ryan Sonnek. See LICENSE for details.
data/VERSION DELETED
@@ -1 +0,0 @@
1
- 1.0.0