octokey 0.1.pre.4 → 0.1.pre.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -13,7 +13,7 @@ require File.expand_path('octokey/auth_request', File.dirname(__FILE__))
13
13
  # heavily by, and borrowing algorithms from, OpenSSH.
14
14
  class Octokey
15
15
  # The current version of Octokey as a string.
16
- VERSION = '0.1.pre.4'
16
+ VERSION = '0.1.pre.5'
17
17
 
18
18
  # Raised when you try and access details of an invalid octokey request.
19
19
  # If you always check .can_log_in? or .can_sign_up? first, you should not
@@ -77,33 +77,22 @@ class Octokey
77
77
 
78
78
  # Add a timestamp to this buffer
79
79
  #
80
- # Times are stored to millisecond precision, and are limited to
81
- # 2 **48 to give plenty of margin for implementations using doubles
82
- # as the backing for their date time, which nicely gives us a range
83
- # ending just after the year 10000.
84
- #
85
- # @param [Time] time
80
+ # @param [Fixnum] x milliseconds since the epoch
86
81
  # @return [Octokey::Buffer] self
87
82
  # @raise [Octokey::InvalidBuffer] if the time is too far into the future
88
- def add_time(time)
89
- seconds, millis = [time.to_i, (time.usec / 1000.0).round]
90
- raw = seconds * 1000 + millis
91
- raise Octokey::InvalidBuffer, "Invalid time" if raw >= 2 ** 48
92
- add_uint64(raw)
83
+ def add_timestamp(x)
84
+ raise InvalidBuffer, "Invalid timestamp: #{x}" if x < 0 || x >= 2 ** 64
85
+ add_uint32(x >> 32 & 0xffff_ffff)
86
+ add_uint32(x & 0xffff_ffff)
93
87
  self
94
88
  end
95
89
 
96
90
  # Destructively read a timestamp from this buffer
97
91
  #
98
- # Times are stored to millisecond precision
99
- #
100
- # @return [Time]
92
+ # @return [Fixnum] milliseconds since the epoch
101
93
  # @raise [Octokey::InvalidBuffer]
102
- def scan_time
103
- raw = scan_uint64
104
- raise Octokey::InvalidBuffer, "Invalid time" if raw >= 2 ** 48
105
- seconds, millis = [raw / 1000, raw % 1000]
106
- Time.at(seconds).utc + (millis / 1000.0)
94
+ def scan_timestamp
95
+ (scan_uint32 << 32) + scan_uint32
107
96
  end
108
97
 
109
98
  # Add an IPv4 or IPv6 address to this buffer
@@ -304,24 +293,6 @@ class Octokey
304
293
  scan(4).unpack("N").first
305
294
  end
306
295
 
307
- # Add an unsigned 64-bit number to this buffer
308
- # @param [Fixnum] x
309
- # @return [Octokey::Buffer] self
310
- # @raise [Octokey::InvalidBuffer] if x is not a uint64
311
- def add_uint64(x)
312
- raise InvalidBuffer, "Invalid uint64: #{x}" if x < 0 || x >= 2 ** 64
313
- add_uint32(x >> 32 & 0xffff_ffff)
314
- add_uint32(x & 0xffff_ffff)
315
- self
316
- end
317
-
318
- # Destructively read an unsigned 64-bit number from this buffer
319
- # @return [Fixnum]
320
- # @raise [Octokey::InvalidBuffer]
321
- def scan_uint64
322
- (scan_uint32 << 32) + scan_uint32
323
- end
324
-
325
296
  # Check whether a string is valid utf-8
326
297
  # @param [String] string
327
298
  # @return [String] string
@@ -30,13 +30,13 @@ class Octokey
30
30
  RANDOM_SIZE = 32
31
31
  # Hash algorithm to use in the HMAC
32
32
  HMAC_ALGORITHM = "sha1"
33
- # The maximum age of a valid challenge
34
- MAX_AGE = 5 * 60
35
- # The minimum age of a valid challenge
36
- MIN_AGE = -30
33
+ # The maximum age of a valid challenge (milliseconds)
34
+ MAX_AGE = 5 * 60_000
35
+ # The minimum age of a valid challenge (milliseconds)
36
+ MIN_AGE = -30_000
37
37
 
38
38
  private
39
- attr_accessor :version, :time, :client_ip, :random, :digest, :invalid_buffer
39
+ attr_accessor :version, :timestamp, :client_ip, :random, :digest, :invalid_buffer
40
40
 
41
41
  public
42
42
 
@@ -55,8 +55,8 @@ class Octokey
55
55
  begin
56
56
  self.version = buffer.scan_uint8
57
57
  if version == CHALLENGE_VERSION
58
- self.time, self.client_ip, self.random, self.digest =
59
- buffer.scan_all(:time, :ip, :varbytes, :varbytes)
58
+ self.timestamp, self.client_ip, self.random, self.digest =
59
+ buffer.scan_all(:timestamp, :ip, :varbytes, :varbytes)
60
60
  end
61
61
  rescue InvalidBuffer => e
62
62
  self.invalid_buffer = e.message
@@ -79,7 +79,7 @@ class Octokey
79
79
  current_time = opts[:current_time] || Time.now
80
80
 
81
81
  self.version = CHALLENGE_VERSION
82
- self.time = current_time
82
+ self.timestamp = current_time.to_i * 1000 + current_time.usec / 1000
83
83
  self.client_ip = expected_ip
84
84
  self.random = SecureRandom.random_bytes(RANDOM_SIZE)
85
85
  self.digest = expected_digest
@@ -109,16 +109,17 @@ class Octokey
109
109
  def errors(opts)
110
110
  expected_ip = IPAddr(opts[:client_ip])
111
111
  current_time = opts[:current_time] || Time.now
112
+ current_time = current_time.to_i * 1000 + current_time.usec / 1000
112
113
 
113
114
  return [invalid_buffer] unless invalid_buffer.nil?
114
115
  return ["Challenge version mismatch"] unless version == CHALLENGE_VERSION
115
116
 
116
117
  errors = []
117
- errors << "Challenge too old" unless current_time < time + MAX_AGE
118
- errors << "Challenge too new" unless current_time > time + MIN_AGE
118
+ errors << "Challenge too old" unless current_time < timestamp + MAX_AGE
119
+ errors << "Challenge too new" unless current_time > timestamp + MIN_AGE
119
120
  errors << "Challenge IP mismatch" unless client_ip == expected_ip
120
121
  errors << "Challenge random mismatch" unless random.size == RANDOM_SIZE
121
- errors << "Challenge HMAC mismatch" unless digest == expected_digest
122
+ errors << "Challenge HMAC mismatch" unless time_safe_equals(digest, expected_digest)
122
123
 
123
124
  errors
124
125
  end
@@ -142,7 +143,7 @@ class Octokey
142
143
  #
143
144
  # @return [String]
144
145
  def inspect
145
- "#<Octokey::Challenge @version=#{version.inspect} @time=#{time.inspect}" +
146
+ "#<Octokey::Challenge @version=#{version.inspect} @timestamp=#{timestamp.inspect}" +
146
147
  "@client_ip=#{client_ip.inspect}>"
147
148
  end
148
149
 
@@ -161,7 +162,7 @@ class Octokey
161
162
  def unsigned_buffer
162
163
  Octokey::Buffer.new.
163
164
  add_uint8(version).
164
- add_time(time).
165
+ add_timestamp(timestamp).
165
166
  add_ip(client_ip).
166
167
  add_varbytes(random)
167
168
  end
@@ -174,5 +175,16 @@ class Octokey
174
175
  def IPAddr(x)
175
176
  x && IPAddr.new(x.to_s) or raise ArgumentError, "no client IP given"
176
177
  end
178
+
179
+ # Compare two strings in a manner hard to timing attack
180
+ #
181
+ # Useful for comparing digests so that a motivated attacker can't guess the correct
182
+ # checksum by measuring how long it takes for comparing checksums to fail.
183
+ #
184
+ # @param [String] a
185
+ # @param [String] b
186
+ def time_safe_equals(a, b)
187
+ a.hash == b.hash && a == b
188
+ end
177
189
  end
178
190
  end
@@ -0,0 +1,36 @@
1
+ class Octokey
2
+ class KeyCoder
3
+
4
+ def self.from_buffer(buffer)
5
+
6
+ end
7
+
8
+ def self.from_formatted(formatted)
9
+ type, buffer, comment = formatted.split(" ", 3)
10
+ if formatted =~ /\A(ssh-rsa)\s+([^\s]+)(\s.*)?/
11
+ key, errors = decode_public_key(Octokey::Buffer.new($2), $1)
12
+ raise "Invalid public key: #{errors.join(". ")}." unless errors.empty?
13
+
14
+ key
15
+ else
16
+ raise "Invalid public key: Got #{formatted.inspect}, expected \"ssh-rsa AAAAf...\""
17
+ end
18
+ end
19
+
20
+ def initialize(public_key)
21
+ raise "not an RSA key: #{public_key}" unless OpenSSL::PKey::RSA === public_key
22
+ end
23
+
24
+ def to_buffer
25
+ buffer = Octokey::Buffer.new
26
+ buffer.add_string "ssh-rsa"
27
+ buffer.add_mpint public_key.e
28
+ buffer.add_mpint public_key.n
29
+ buffer
30
+ end
31
+
32
+ def to_formatted
33
+ "ssh-rsa #{to_buffer.to_s}"
34
+ end
35
+ end
36
+ end
metadata CHANGED
@@ -1,116 +1,151 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: octokey
3
- version: !ruby/object:Gem::Version
3
+ version: !ruby/object:Gem::Version
4
4
  prerelease: 4
5
- version: 0.1.pre.4
5
+ version: 0.1.pre.5
6
6
  platform: ruby
7
- authors:
8
- - Conrad Irwin
9
- - Martin Kleppmann
7
+ authors:
8
+ - Conrad Irwin
9
+ - Martin Kleppmann
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
-
14
- date: 2012-07-30 00:00:00 Z
15
- dependencies:
16
- - !ruby/object:Gem::Dependency
17
- name: rspec
18
- prerelease: false
19
- requirement: &id001 !ruby/object:Gem::Requirement
20
- none: false
21
- requirements:
22
- - - ">="
23
- - !ruby/object:Gem::Version
24
- version: "0"
25
- type: :development
26
- version_requirements: *id001
27
- - !ruby/object:Gem::Dependency
28
- name: active_support
29
- prerelease: false
30
- requirement: &id002 !ruby/object:Gem::Requirement
31
- none: false
32
- requirements:
33
- - - ">="
34
- - !ruby/object:Gem::Version
35
- version: "0"
36
- type: :development
37
- version_requirements: *id002
38
- - !ruby/object:Gem::Dependency
39
- name: i18n
40
- prerelease: false
41
- requirement: &id003 !ruby/object:Gem::Requirement
42
- none: false
43
- requirements:
44
- - - ">="
45
- - !ruby/object:Gem::Version
46
- version: "0"
47
- type: :development
48
- version_requirements: *id003
49
- - !ruby/object:Gem::Dependency
50
- name: simplecov
51
- prerelease: false
52
- requirement: &id004 !ruby/object:Gem::Requirement
53
- none: false
54
- requirements:
55
- - - ">="
56
- - !ruby/object:Gem::Version
57
- version: "0"
58
- type: :development
59
- version_requirements: *id004
60
- - !ruby/object:Gem::Dependency
61
- name: yard
62
- prerelease: false
63
- requirement: &id005 !ruby/object:Gem::Requirement
64
- none: false
65
- requirements:
66
- - - ">="
67
- - !ruby/object:Gem::Version
68
- version: "0"
69
- type: :development
70
- version_requirements: *id005
13
+ date: 2012-12-03 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ type: :development
17
+ requirement: !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ! '>='
21
+ - !ruby/object:Gem::Version
22
+ version: '0'
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ name: rake
31
+ - !ruby/object:Gem::Dependency
32
+ type: :development
33
+ requirement: !ruby/object:Gem::Requirement
34
+ none: false
35
+ requirements:
36
+ - - ! '>='
37
+ - !ruby/object:Gem::Version
38
+ version: '0'
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ name: rspec
47
+ - !ruby/object:Gem::Dependency
48
+ type: :development
49
+ requirement: !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ name: active_support
63
+ - !ruby/object:Gem::Dependency
64
+ type: :development
65
+ requirement: !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - ! '>='
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ name: i18n
79
+ - !ruby/object:Gem::Dependency
80
+ type: :development
81
+ requirement: !ruby/object:Gem::Requirement
82
+ none: false
83
+ requirements:
84
+ - - ! '>='
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ name: simplecov
95
+ - !ruby/object:Gem::Dependency
96
+ type: :development
97
+ requirement: !ruby/object:Gem::Requirement
98
+ none: false
99
+ requirements:
100
+ - - ! '>='
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ name: yard
71
111
  description: Allows you to use secure authentication mechanisms in place of passwords
72
- email:
73
- - conrad.irwin@gmail.com
74
- - martin@kleppmann.de
112
+ email:
113
+ - conrad.irwin@gmail.com
114
+ - martin@kleppmann.de
75
115
  executables: []
76
-
77
116
  extensions: []
78
-
79
117
  extra_rdoc_files: []
80
-
81
- files:
82
- - lib/octokey.rb
83
- - lib/octokey/auth_request.rb
84
- - lib/octokey/buffer.rb
85
- - lib/octokey/challenge.rb
86
- - lib/octokey/config.rb
87
- - lib/octokey/public_key.rb
118
+ files:
119
+ - lib/octokey.rb
120
+ - lib/octokey/public_key.rb
121
+ - lib/octokey/config.rb
122
+ - lib/octokey/challenge.rb
123
+ - lib/octokey/buffer.rb
124
+ - lib/octokey/key.rb
125
+ - lib/octokey/auth_request.rb
88
126
  homepage: https://github.com/octokey/octokey-gem
89
127
  licenses: []
90
-
91
128
  post_install_message:
92
129
  rdoc_options: []
93
-
94
- require_paths:
95
- - lib
96
- required_ruby_version: !ruby/object:Gem::Requirement
130
+ require_paths:
131
+ - lib
132
+ required_ruby_version: !ruby/object:Gem::Requirement
97
133
  none: false
98
- requirements:
99
- - - ">="
100
- - !ruby/object:Gem::Version
101
- version: "0"
102
- required_rubygems_version: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - ! '>='
136
+ - !ruby/object:Gem::Version
137
+ version: '0'
138
+ required_rubygems_version: !ruby/object:Gem::Requirement
103
139
  none: false
104
- requirements:
105
- - - ">"
106
- - !ruby/object:Gem::Version
107
- version: 1.3.1
140
+ requirements:
141
+ - - ! '>'
142
+ - !ruby/object:Gem::Version
143
+ version: 1.3.1
108
144
  requirements: []
109
-
110
145
  rubyforge_project:
111
146
  rubygems_version: 1.8.24
112
147
  signing_key:
113
148
  specification_version: 3
114
149
  summary: Public key authentication for the web!
115
150
  test_files: []
116
-
151
+ has_rdoc: