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.
- data/lib/octokey.rb +1 -1
- data/lib/octokey/buffer.rb +8 -37
- data/lib/octokey/challenge.rb +25 -13
- data/lib/octokey/key.rb +36 -0
- metadata +128 -93
data/lib/octokey.rb
CHANGED
@@ -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.
|
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
|
data/lib/octokey/buffer.rb
CHANGED
@@ -77,33 +77,22 @@ class Octokey
|
|
77
77
|
|
78
78
|
# Add a timestamp to this buffer
|
79
79
|
#
|
80
|
-
#
|
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
|
89
|
-
|
90
|
-
|
91
|
-
|
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
|
-
#
|
99
|
-
#
|
100
|
-
# @return [Time]
|
92
|
+
# @return [Fixnum] milliseconds since the epoch
|
101
93
|
# @raise [Octokey::InvalidBuffer]
|
102
|
-
def
|
103
|
-
|
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
|
data/lib/octokey/challenge.rb
CHANGED
@@ -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 *
|
35
|
-
# The minimum age of a valid challenge
|
36
|
-
MIN_AGE = -
|
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, :
|
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.
|
59
|
-
buffer.scan_all(:
|
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.
|
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 <
|
118
|
-
errors << "Challenge too new" unless current_time >
|
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
|
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} @
|
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
|
-
|
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
|
data/lib/octokey/key.rb
ADDED
@@ -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.
|
5
|
+
version: 0.1.pre.5
|
6
6
|
platform: ruby
|
7
|
-
authors:
|
8
|
-
|
9
|
-
|
7
|
+
authors:
|
8
|
+
- Conrad Irwin
|
9
|
+
- Martin Kleppmann
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
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
|
-
|
74
|
-
|
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
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
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
|
-
|
95
|
-
|
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
|
-
|
101
|
-
|
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
|
-
|
107
|
-
|
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:
|