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

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.
@@ -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: