octokey 0.1.pre.2 → 0.1.pre.3
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/octokey/auth_request.rb +210 -0
- data/lib/octokey/buffer.rb +239 -74
- data/lib/octokey/challenge.rb +178 -0
- data/lib/octokey/config.rb +101 -0
- data/lib/octokey/public_key.rb +157 -0
- data/lib/octokey.rb +109 -350
- metadata +79 -23
@@ -0,0 +1,210 @@
|
|
1
|
+
class Octokey
|
2
|
+
# An AuthRequest is sent by the client when it wants to log in or sign up.
|
3
|
+
#
|
4
|
+
# It includes an {Octokey::Challenge} so that we can verify its recency, and
|
5
|
+
# also the username the user wishes to log in as, the url that they wish to
|
6
|
+
# log in to, and the public key corresponding to their private key.
|
7
|
+
#
|
8
|
+
# You can create an Octokey::AuthRequest from any string, and later determine
|
9
|
+
# whether or not it was valid by calling {#valid?}
|
10
|
+
class AuthRequest
|
11
|
+
# The service name is used to check that the client knows which protocol it is speaking.
|
12
|
+
SERVICE_NAME = "octokey-auth"
|
13
|
+
# The auth method indicates that the client wants to use publickey authentication.
|
14
|
+
AUTH_METHOD = "publickey"
|
15
|
+
# The signing algorithm is copied straight from SSH.
|
16
|
+
SIGNING_ALGORITHM = "ssh-rsa"
|
17
|
+
|
18
|
+
attr_accessor :challenge_buffer, :request_url, :username, :service_name,
|
19
|
+
:auth_method, :signing_algorithm, :public_key, :signature_buffer,
|
20
|
+
:invalid_buffer
|
21
|
+
|
22
|
+
# Given a challenge and a private key, generate an auth request.
|
23
|
+
#
|
24
|
+
# @param [Hash] opts
|
25
|
+
# @option opts [String] :request_url
|
26
|
+
# @option opts [String] :username
|
27
|
+
# @option opts [String] :challenge The base64-encoded challenge
|
28
|
+
# @option opts [OpenSSL::PKey::RSA] :private_key
|
29
|
+
# @return [Octokey::AuthRequest]
|
30
|
+
def self.generate(opts)
|
31
|
+
private_key = opts[:private_key] or raise ArgumentError, "No private_key given"
|
32
|
+
challenge = opts[:challenge] or raise ArgumentError, "No challenge given"
|
33
|
+
|
34
|
+
new.instance_eval do
|
35
|
+
self.challenge_buffer = Octokey::Buffer.new(challenge)
|
36
|
+
self.request_url = opts[:request_url] or raise ArgumentError, "No request_url given"
|
37
|
+
self.username = opts[:username] or raise ArgumentError, "No username given"
|
38
|
+
self.service_name = SERVICE_NAME
|
39
|
+
self.auth_method = AUTH_METHOD
|
40
|
+
self.signing_algorithm = SIGNING_ALGORITHM
|
41
|
+
self.public_key = Octokey::PublicKey.from_key(private_key.public_key)
|
42
|
+
self.signature_buffer = signature_buffer_with(private_key)
|
43
|
+
|
44
|
+
self
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Parse an auth request sent from the client.
|
49
|
+
#
|
50
|
+
# @param[String] The base64-encoded auth request from the client.
|
51
|
+
# @return [Octokey::AuthRequest]
|
52
|
+
def self.from_string(string)
|
53
|
+
buffer = Octokey::Buffer.new(string)
|
54
|
+
new.instance_eval do
|
55
|
+
begin
|
56
|
+
self.challenge_buffer, self.request_url, self.username,
|
57
|
+
self.service_name, self.auth_method, self.signing_algorithm,
|
58
|
+
self.public_key, self.signature_buffer =
|
59
|
+
buffer.scan_all(
|
60
|
+
:buffer, :string, :string,
|
61
|
+
:string, :string, :string,
|
62
|
+
:public_key, :buffer)
|
63
|
+
rescue Octokey::InvalidBuffer => e
|
64
|
+
self.invalid_buffer = e.message
|
65
|
+
end
|
66
|
+
|
67
|
+
self
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Get any errors ignoring those caused by the challenge.
|
72
|
+
#
|
73
|
+
# @param [Hash] opts
|
74
|
+
# @return [Array<String>]
|
75
|
+
def errors_ignoring_challenge(opts)
|
76
|
+
return [invalid_buffer] if invalid_buffer
|
77
|
+
errors = []
|
78
|
+
|
79
|
+
errors += request_url_errors(opts)
|
80
|
+
errors << "Auth request username mismatch" unless username == opts[:username]
|
81
|
+
errors << "Auth request service name mismatch" unless service_name == SERVICE_NAME
|
82
|
+
errors << "Auth request auth method unsupported" unless auth_method == AUTH_METHOD
|
83
|
+
errors << "Auth request signing algorithm unsupported" unless signing_algorithm == SIGNING_ALGORITHM
|
84
|
+
|
85
|
+
if public_key.valid?
|
86
|
+
errors += signature_errors(public_key.public_key, signature_buffer.dup)
|
87
|
+
else
|
88
|
+
errors += public_key.errors
|
89
|
+
end
|
90
|
+
|
91
|
+
errors
|
92
|
+
end
|
93
|
+
|
94
|
+
# Get any errors caused by the challenge.
|
95
|
+
#
|
96
|
+
# @param [Hash] opts
|
97
|
+
# @return [Array<String>]
|
98
|
+
def challenge_errors(opts)
|
99
|
+
return [] if invalid_buffer
|
100
|
+
Octokey::Config.get_challenge(challenge_buffer.to_s, opts).errors(opts)
|
101
|
+
end
|
102
|
+
|
103
|
+
# Get all the error for this auth request.
|
104
|
+
#
|
105
|
+
# @param [Hash] opts
|
106
|
+
# @return [Array<String>]
|
107
|
+
def errors(opts)
|
108
|
+
errors_ignoring_challenge(opts) + challenge_errors(opts)
|
109
|
+
end
|
110
|
+
|
111
|
+
# If the challenge was valid, would this auth request be valid?
|
112
|
+
#
|
113
|
+
# This can be used to check whether the auth request should be retried.
|
114
|
+
#
|
115
|
+
# @param [Hash] opts
|
116
|
+
# @return [Boolean]
|
117
|
+
def valid_ignoring_challenge?(opts)
|
118
|
+
errors_ignoring_challenge(opts) == []
|
119
|
+
end
|
120
|
+
|
121
|
+
# Is this auth request valid?
|
122
|
+
#
|
123
|
+
# @param [Hash] opts
|
124
|
+
# @return [Boolean]
|
125
|
+
def valid?(opts)
|
126
|
+
errors(opts) == []
|
127
|
+
end
|
128
|
+
|
129
|
+
# Get the Base64-encoded version of this auth request.
|
130
|
+
#
|
131
|
+
# @return [String]
|
132
|
+
def to_s
|
133
|
+
unsigned_buffer.add_buffer(signature_buffer).to_s
|
134
|
+
end
|
135
|
+
|
136
|
+
# Get a string that identifies this auth request while debugging
|
137
|
+
#
|
138
|
+
# @return [String]
|
139
|
+
def inspect
|
140
|
+
"#<Octokey::AuthRequest #{to_s.inspect}>"
|
141
|
+
end
|
142
|
+
|
143
|
+
private
|
144
|
+
|
145
|
+
# What are the problems with the signature?
|
146
|
+
#
|
147
|
+
# @param [OpenSSL::PKey::RSA] key the public key
|
148
|
+
# @param [Octokey::Buffer] signature_buffer the signature buffer
|
149
|
+
# @return [Array<String>]
|
150
|
+
def signature_errors(key, signature_buffer)
|
151
|
+
algorithm_used, signature = signature_buffer.scan_all(:string, :varbytes)
|
152
|
+
|
153
|
+
errors = []
|
154
|
+
errors << "Signature type mismatch" unless algorithm_used == signing_algorithm
|
155
|
+
errors << "Signature mismatch" unless key.verify(OpenSSL::Digest::SHA1.new, signature, unsigned_buffer.raw)
|
156
|
+
errors
|
157
|
+
|
158
|
+
rescue Octokey::InvalidBuffer => e
|
159
|
+
["Signature #{e.message}"]
|
160
|
+
end
|
161
|
+
|
162
|
+
# What are the problems with the request url?
|
163
|
+
#
|
164
|
+
# @param [Hash] opts
|
165
|
+
# @return [Array<String>]
|
166
|
+
def request_url_errors(opts)
|
167
|
+
url = URI.parse(request_url)
|
168
|
+
|
169
|
+
valid_hostname = Octokey::Config.valid_hostnames.any? do |hostname|
|
170
|
+
if hostname[/\A\*\.(.*)\z/]
|
171
|
+
url.host.end_with?($1)
|
172
|
+
else
|
173
|
+
url.host == hostname
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
errors = []
|
178
|
+
errors << "Request url insecure" unless url.scheme == "https"
|
179
|
+
errors << "Request url mismatch" unless valid_hostname
|
180
|
+
errors
|
181
|
+
|
182
|
+
rescue URI::InvalidURIError
|
183
|
+
["Request url invalid"]
|
184
|
+
end
|
185
|
+
|
186
|
+
# Get the buffer containing everything other than the signature.
|
187
|
+
#
|
188
|
+
# @return [Octokey::Buffer]
|
189
|
+
def unsigned_buffer
|
190
|
+
Octokey::Buffer.new.
|
191
|
+
add_buffer(challenge_buffer).
|
192
|
+
add_string(request_url).
|
193
|
+
add_string(username).
|
194
|
+
add_string(service_name).
|
195
|
+
add_string(auth_method).
|
196
|
+
add_string(signing_algorithm).
|
197
|
+
add_public_key(public_key)
|
198
|
+
end
|
199
|
+
|
200
|
+
# Get the signature buffer using the given key.
|
201
|
+
#
|
202
|
+
# @param [OpenSSL::PKey::RSA] private_key
|
203
|
+
# @return [Octokey::Buffer]
|
204
|
+
def signature_buffer_with(private_key)
|
205
|
+
Octokey::Buffer.new.
|
206
|
+
add_string(SIGNING_ALGORITHM).
|
207
|
+
add_varbytes(private_key.sign(OpenSSL::Digest::SHA1.new, unsigned_buffer.raw))
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
data/lib/octokey/buffer.rb
CHANGED
@@ -1,12 +1,24 @@
|
|
1
1
|
require 'base64'
|
2
2
|
class Octokey
|
3
|
+
# Buffers are used throughout Octokey to provide a bijective serialization format.
|
4
|
+
# For any valid buffer, there's exactly one valid object, and vice-versa.
|
5
|
+
#
|
6
|
+
# Mostly we used Base64-encoded buffers to avoid problems with potentially 8-bit
|
7
|
+
# unsafe channels. You should take care not to perform any operations on the Base64
|
8
|
+
# encoded form as there are many accepted formats for Base64-encoding a given string.
|
9
|
+
#
|
10
|
+
# In the current implementation, reading out of a buffer is a destructive operation,
|
11
|
+
# you should first .dup any buffer that you want to read more than once.
|
3
12
|
class Buffer
|
4
|
-
attr_accessor :buffer, :
|
13
|
+
attr_accessor :buffer, :invalid_buffer
|
5
14
|
|
6
15
|
# to avoid DOS caused by duplicating enourmous buffers,
|
7
16
|
# we limit the maximum size of any string stored to 100k
|
8
17
|
MAX_STRING_SIZE = 100 * 1024
|
9
18
|
|
19
|
+
# Create a new buffer from raw bits.
|
20
|
+
#
|
21
|
+
# @param [String] raw
|
10
22
|
def self.from_raw(raw = "")
|
11
23
|
ret = new
|
12
24
|
ret.buffer = raw.dup
|
@@ -14,95 +26,107 @@ class Octokey
|
|
14
26
|
ret
|
15
27
|
end
|
16
28
|
|
29
|
+
# Create a new buffer from a Base64-encoded string.
|
30
|
+
# @param [String] string
|
17
31
|
def initialize(string = "")
|
18
32
|
self.buffer = Base64.decode64(string || "")
|
19
|
-
|
20
|
-
|
33
|
+
buffer.force_encoding('BINARY') if buffer.respond_to?(:force_encoding)
|
34
|
+
self.invalid_buffer = "Badly formatted Base64" unless to_s == string
|
21
35
|
end
|
22
36
|
|
37
|
+
# Get the underlying bits contained in this buffer.
|
38
|
+
# @return [String]
|
23
39
|
def raw
|
24
40
|
buffer
|
25
41
|
end
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
end
|
30
|
-
|
42
|
+
|
43
|
+
# Get the canonical Base64 representation of this buffer.
|
44
|
+
# @return [String]
|
31
45
|
def to_s
|
32
46
|
Base64.encode64(buffer).gsub("\n", "")
|
33
47
|
end
|
34
48
|
|
35
|
-
|
36
|
-
|
49
|
+
# Get a string that describes this buffer suitably for debugging.
|
50
|
+
# @return [String]
|
51
|
+
def inspect
|
52
|
+
"#<Octokey::Buffer @buffer=#{to_s.inspect}>"
|
37
53
|
end
|
38
54
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
end
|
44
|
-
self.buffer = buf
|
45
|
-
ret
|
55
|
+
# Is this buffer empty?
|
56
|
+
# @return [Boolean]
|
57
|
+
def empty?
|
58
|
+
buffer.empty?
|
46
59
|
end
|
47
60
|
|
61
|
+
# Add an unsigned 8-bit number to this buffer
|
62
|
+
# @param [Fixnum] x
|
63
|
+
# @return [Octokey::Buffer] self
|
64
|
+
# @raise [Octokey::InvalidBuffer] if x is not a uint8
|
48
65
|
def add_uint8(x)
|
49
66
|
raise InvalidBuffer, "Invalid uint8: #{x}" if x < 0 || x >= 2 ** 8
|
50
67
|
buffer << [x].pack("C")
|
68
|
+
self
|
51
69
|
end
|
52
70
|
|
71
|
+
# Destructively read an unsigned 8-bit number from this buffer
|
72
|
+
# @return [Fixnum]
|
73
|
+
# @raise [Octokey::InvalidBuffer]
|
53
74
|
def scan_uint8
|
54
75
|
scan(1).unpack("C").first
|
55
76
|
end
|
56
77
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
raise InvalidBuffer, "Invalid uint64: #{x}" if x < 0 || x >= 2 ** 64
|
68
|
-
add_uint32(x >> 32 & 0xffff_ffff)
|
69
|
-
add_uint32(x & 0xffff_ffff)
|
70
|
-
end
|
71
|
-
|
72
|
-
def scan_uint64
|
73
|
-
(scan_uint32 << 32) + scan_uint32
|
74
|
-
end
|
75
|
-
|
76
|
-
def add_uint128(x)
|
77
|
-
raise InvalidBuffer, "Invalid uint128: #{x}" if x < 0 || x >= 2 ** 128
|
78
|
-
add_uint64(x >> 64 & 0xffff_ffff_ffff_ffff)
|
79
|
-
add_uint64(x & 0xffff_ffff_ffff_ffff)
|
80
|
-
end
|
81
|
-
|
82
|
-
def scan_uint128
|
83
|
-
(scan_uint64 << 64) + scan_uint64
|
84
|
-
end
|
85
|
-
|
78
|
+
# Add a timestamp to this buffer
|
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
|
86
|
+
# @return [Octokey::Buffer] self
|
87
|
+
# @raise [Octokey::InvalidBuffer] if the time is too far into the future
|
86
88
|
def add_time(time)
|
87
|
-
|
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)
|
93
|
+
self
|
88
94
|
end
|
89
95
|
|
96
|
+
# Destructively read a timestamp from this buffer
|
97
|
+
#
|
98
|
+
# Times are stored to millisecond precision
|
99
|
+
#
|
100
|
+
# @return [Time]
|
101
|
+
# @raise [Octokey::InvalidBuffer]
|
90
102
|
def scan_time
|
91
|
-
|
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)
|
92
107
|
end
|
93
108
|
|
109
|
+
# Add an IPv4 or IPv6 address to this buffer
|
110
|
+
#
|
111
|
+
# @param [IPAddr] ipaddr
|
112
|
+
# @return [Octokey::Buffer] self
|
113
|
+
# @raise [Octokey::InvalidBuffer] not a valid IP address
|
94
114
|
def add_ip(ipaddr)
|
95
115
|
if ipaddr.ipv4?
|
96
116
|
add_uint8(4)
|
97
|
-
|
117
|
+
buffer << ipaddr.hton
|
98
118
|
elsif ipaddr.ipv6?
|
99
119
|
add_uint8(6)
|
100
|
-
|
120
|
+
buffer << ipaddr.hton
|
101
121
|
else
|
102
122
|
raise InvalidBuffer, "Unsupported IP address: #{ipaddr.to_s}"
|
103
123
|
end
|
124
|
+
self
|
104
125
|
end
|
105
126
|
|
127
|
+
# Destructively read an IPv4 or IPv6 address from this buffer.
|
128
|
+
# @return [IPAddr]
|
129
|
+
# @raise [Octokey::InvalidBuffer]
|
106
130
|
def scan_ip
|
107
131
|
type = scan_uint8
|
108
132
|
case type
|
@@ -111,69 +135,210 @@ class Octokey
|
|
111
135
|
when 6
|
112
136
|
IPAddr.new_ntoh scan(16)
|
113
137
|
else
|
114
|
-
raise InvalidBuffer, "
|
138
|
+
raise InvalidBuffer, "Unknown IP family: #{type.inspect}"
|
115
139
|
end
|
116
140
|
end
|
117
141
|
|
142
|
+
# Add a length-prefixed number of bytes to this buffer
|
143
|
+
# @param [String] bytes
|
144
|
+
# @return [Octokey::Buffer] self
|
145
|
+
# @raise [Octokey::InvalidBuffer] if there are too any bytes
|
118
146
|
def add_varbytes(bytes)
|
147
|
+
bytes.force_encoding('BINARY') if bytes.respond_to?(:force_encoding)
|
119
148
|
size = bytes.size
|
120
|
-
raise InvalidBuffer, "
|
149
|
+
raise InvalidBuffer, "Too much length: #{size}" if size > MAX_STRING_SIZE
|
121
150
|
add_uint32 size
|
122
|
-
|
151
|
+
buffer << bytes
|
152
|
+
self
|
123
153
|
end
|
124
154
|
|
155
|
+
# Destructively read a length-prefixed number of bytes from this buffer
|
156
|
+
# @return [String] bytes
|
157
|
+
# @raise [Octokey::InvalidBuffer]
|
125
158
|
def scan_varbytes
|
126
159
|
size = scan_uint32
|
127
|
-
raise InvalidBuffer, "
|
160
|
+
raise InvalidBuffer, "Too much length: #{size}" if size > MAX_STRING_SIZE
|
128
161
|
scan(size)
|
129
162
|
end
|
130
163
|
|
164
|
+
# Add a length-prefixed number of bytes of UTF-8 string to this buffer
|
165
|
+
# @param [String] string
|
166
|
+
# @return [Octokey::Buffer] self
|
167
|
+
# @raise [Octokey::InvalidBuffer] if the string is not utf-8
|
131
168
|
def add_string(string)
|
132
|
-
|
133
|
-
add_varbytes string.encode('BINARY')
|
134
|
-
else
|
135
|
-
add_varbytes string
|
136
|
-
end
|
169
|
+
add_varbytes(validate_utf8(string))
|
137
170
|
end
|
138
171
|
|
172
|
+
# Destructively read a length-prefixed number of bytes of UTF-8 string
|
173
|
+
# @return [String] with encoding == 'utf-8' on ruby-1.9
|
174
|
+
# @raise [Octokey::InvalidBuffer]
|
139
175
|
def scan_string
|
140
|
-
|
141
|
-
if string.respond_to?(:encode)
|
142
|
-
string.encode('UTF-8')
|
143
|
-
else
|
144
|
-
string
|
145
|
-
end
|
146
|
-
rescue EncodingError => e
|
147
|
-
raise InvalidBuffer, e
|
176
|
+
validate_utf8(scan_varbytes)
|
148
177
|
end
|
149
178
|
|
179
|
+
# Add the length-prefixed contents of another buffer to this one.
|
180
|
+
# @param [Octokey::Buffer] buffer
|
181
|
+
# @return [Octokey::Buffer] self
|
182
|
+
# @raise [Octokey::InvalidBuffer]
|
150
183
|
def add_buffer(buffer)
|
151
184
|
add_varbytes buffer.raw
|
185
|
+
self
|
152
186
|
end
|
153
187
|
|
188
|
+
# Destrictively read a length-prefixed buffer out of this one.
|
189
|
+
# @return [Octokey::Buffer]
|
190
|
+
# @raise [Octokey::InvalidBuffer]
|
154
191
|
def scan_buffer
|
155
192
|
Octokey::Buffer.from_raw scan_varbytes
|
156
193
|
end
|
157
194
|
|
195
|
+
# Add an unsigned multi-precision integer to this buffer
|
196
|
+
# @param [OpenSSL::BN,Fixnum] x
|
197
|
+
# @return [Octokey::Buffer] self
|
198
|
+
# @raise [Octokey::InvalidBuffer] if x is negative or enourmous
|
158
199
|
def add_mpint(x)
|
159
|
-
raise InvalidBuffer, "
|
200
|
+
raise InvalidBuffer, "Invalid mpint: #{mpint.inspect}" if x < 0
|
160
201
|
bytes = OpenSSL::BN.new(x.to_s, 10).to_s(2)
|
161
202
|
bytes = "\x00" + bytes if bytes.bytes.first >= 0x80
|
162
203
|
add_varbytes(bytes)
|
204
|
+
self
|
163
205
|
end
|
164
206
|
|
207
|
+
# Destructively read an unsigned multi-precision integer from this buffer
|
208
|
+
# @return [OpenSSL::BN]
|
209
|
+
# @raise [Octokey::InvalidBuffer]
|
165
210
|
def scan_mpint
|
166
|
-
|
211
|
+
raw = scan_varbytes
|
167
212
|
|
168
|
-
|
169
|
-
|
213
|
+
first, second = raw.bytes.first(2)
|
214
|
+
|
215
|
+
# ensure only positive numbers with no superflous leading 0s
|
216
|
+
if first >= 0x80 || first == 0x00 && second < 0x80
|
217
|
+
raise InvalidBuffer, "Badly formatted mpint"
|
170
218
|
end
|
171
219
|
|
172
|
-
OpenSSL::BN.new(
|
220
|
+
OpenSSL::BN.new(raw, 2)
|
173
221
|
end
|
174
222
|
|
175
|
-
|
176
|
-
|
223
|
+
# Destructively read a public key from this buffer
|
224
|
+
#
|
225
|
+
# NOTE: the returned public key may not be valid, you must call
|
226
|
+
# .valid? on it before trying to use it.
|
227
|
+
#
|
228
|
+
# @return [Octokey::PublicKey]
|
229
|
+
# @raise [Octokey::InvalidBuffer]
|
230
|
+
def scan_public_key
|
231
|
+
Octokey::PublicKey.from_buffer(scan_buffer)
|
232
|
+
end
|
233
|
+
|
234
|
+
# Add a public key to this buffer
|
235
|
+
# @param [Octokey::PublicKey] public_key
|
236
|
+
# @return [Octokey::Buffer] self
|
237
|
+
# @raise [Octokey::InvalidBuffer]
|
238
|
+
def add_public_key(public_key)
|
239
|
+
add_buffer public_key.to_buffer
|
240
|
+
end
|
241
|
+
|
242
|
+
# Destructively read the entire buffer.
|
243
|
+
#
|
244
|
+
# It's strongly recommended that you use this method to parse buffers, as it
|
245
|
+
# remembers to verify that the buffer doesn't contain any trailing bytes; and
|
246
|
+
# will return nothing if the buffer is invalid, so your code doesn't have to
|
247
|
+
# deal with half-parsed buffers.
|
248
|
+
#
|
249
|
+
# The tokens should correspond to the scan_X methods defined here. For example:
|
250
|
+
# type, e, n = buffer.scan_all(:string, :mpint, :mpint)
|
251
|
+
# is equivalent to:
|
252
|
+
# type, e, n, _ = [buffer.scan_string, buffer.scan_mpint, buffer.scan_mpint,
|
253
|
+
# buffer.scan_end]
|
254
|
+
#
|
255
|
+
# @param [Array<Symbol>] tokens
|
256
|
+
# @return [Array<Object>]
|
257
|
+
# @raise [Octokey::InvalidBuffer]
|
258
|
+
def scan_all(*tokens)
|
259
|
+
ret = tokens.map do |token|
|
260
|
+
raise "invalid token type: #{token.inspect}" unless respond_to?("scan_#{token}")
|
261
|
+
send("scan_#{token}")
|
262
|
+
end
|
263
|
+
|
264
|
+
scan_end
|
265
|
+
ret
|
266
|
+
end
|
267
|
+
|
268
|
+
# Verify that the buffer has been completely scanned.
|
269
|
+
# @raise [Octokey::InvalidBuffer] if there is still buffer to read.
|
270
|
+
def scan_end
|
271
|
+
raise InvalidBuffer, "Buffer too long" unless empty?
|
272
|
+
end
|
273
|
+
|
274
|
+
private
|
275
|
+
|
276
|
+
# Destructively read bytes from the front of this buffer.
|
277
|
+
# @param [Fixnum] n
|
278
|
+
# @return [String]
|
279
|
+
# @raise [Octokey::InvalidBuffer]
|
280
|
+
def scan(n)
|
281
|
+
raise InvalidBuffer, invalid_buffer if invalid_buffer
|
282
|
+
ret, buf = [buffer[0...n], buffer[n..-1]]
|
283
|
+
if ret.size < n || !buf
|
284
|
+
raise InvalidBuffer, "Buffer too short"
|
285
|
+
end
|
286
|
+
self.buffer = buf
|
287
|
+
ret
|
288
|
+
end
|
289
|
+
|
290
|
+
# Add an unsigned 32-bit number to this buffer
|
291
|
+
# @param [Fixnum] x
|
292
|
+
# @return [Octokey::Buffer] self
|
293
|
+
# @raise [Octokey::InvalidBuffer] if x is not a uint32
|
294
|
+
def add_uint32(x)
|
295
|
+
raise InvalidBuffer, "Invalid uint32: #{x}" if x < 0 || x >= 2 ** 32
|
296
|
+
buffer << [x].pack("N")
|
297
|
+
self
|
298
|
+
end
|
299
|
+
|
300
|
+
# Destructively read an unsigned 32-bit number from this buffer
|
301
|
+
# @return [Fixnum]
|
302
|
+
# @raise [Octokey::InvalidBuffer]
|
303
|
+
def scan_uint32
|
304
|
+
scan(4).unpack("N").first
|
305
|
+
end
|
306
|
+
|
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
|
+
# Check whether a string is valid utf-8
|
326
|
+
# @param [String] string
|
327
|
+
# @return [String] string
|
328
|
+
# @raise [Octokey::InvalidBuffer] invalid utf-8
|
329
|
+
def validate_utf8(string)
|
330
|
+
if string.respond_to?(:force_encoding)
|
331
|
+
string.force_encoding('UTF-8')
|
332
|
+
raise InvalidBuffer, "String not UTF-8" unless string.valid_encoding?
|
333
|
+
string
|
334
|
+
else
|
335
|
+
require 'iconv'
|
336
|
+
begin
|
337
|
+
Iconv.conv('utf-8', 'utf-8', string)
|
338
|
+
rescue Iconv::Failure
|
339
|
+
raise InvalidBuffer, "String not UTF-8"
|
340
|
+
end
|
341
|
+
end
|
177
342
|
end
|
178
343
|
end
|
179
344
|
end
|