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