opentoken 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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