-
Notifications
You must be signed in to change notification settings - Fork 84
/
util.rb
287 lines (261 loc) · 9.34 KB
/
util.rb
1
2
3
4
5
6
7
8
9
10
11
12
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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
# encoding: binary
# frozen_string_literal: true
module RbNaCl
# Various utility functions
module Util
extend Sodium
sodium_function :c_verify16, :crypto_verify_16, %i[pointer pointer]
sodium_function :c_verify32, :crypto_verify_32, %i[pointer pointer]
sodium_function :c_verify64, :crypto_verify_64, %i[pointer pointer]
module_function
# Returns a string of n zeros
#
# Lots of the functions require us to create strings to pass into functions of a specified size.
#
# @param [Integer] n the size of the string to make
#
# @return [String] A nice collection of zeros
def zeros(n = 32)
zeros = "\0" * n
# make sure they're 8-bit zeros, not 7-bit zeros. Otherwise we might get
# encoding errors later
zeros.respond_to?(:force_encoding) ? zeros.force_encoding("ASCII-8BIT") : zeros
end
# Prepends a message with zeros
#
# Many functions require a string with some zeros prepended.
#
# @param [Integer] n The number of zeros to prepend
# @param [String] message The string to be prepended
#
# @return [String] a bunch of zeros
def prepend_zeros(n, message)
zeros(n) + message
end
# Remove zeros from the start of a message
#
# Many functions require a string with some zeros prepended, then need them removing after.
# Note: this modifies the passed in string
#
# @param [Integer] n The number of zeros to remove
# @param [String] message The string to be slice
#
# @return [String] less a bunch of zeros
def remove_zeros(n, message)
message.slice!(n, message.bytesize - n)
end
# Pad a string out to n characters with zeros
#
# @param [Integer] n The length of the resulting string
# @param [String] message the message to be padded
#
# @raise [RbNaCl::LengthError] If the string is too long
#
# @return [String] A string, n bytes long
def zero_pad(n, message)
len = message.bytesize
if len == n
message
elsif len > n
raise LengthError, "String too long for zero-padding to #{n} bytes"
else
message + zeros(n - len)
end
end
# Check the length of the passed in string
#
# In several places through the codebase we have to be VERY strict with
# what length of string we accept. This method supports that.
#
# @raise [RbNaCl::LengthError] If the string is not the right length
#
# @param string [String] The string to compare
# @param length [Integer] The desired length
# @param description [String] Description of the string (used in the error)
def check_length(string, length, description)
if string.nil?
# code below is runs only in test cases
# nil can't be converted to str with #to_str method
raise LengthError,
"#{description} was nil (Expected #{length.to_int})",
caller
end
if string.bytesize != length.to_int
raise LengthError,
"#{description} was #{string.bytesize} bytes (Expected #{length.to_int})",
caller
end
true
end
# Check a passed in string, converting the argument if necessary
#
# In several places through the codebase we have to be VERY strict with
# the strings we accept. This method supports that.
#
# @raise [ArgumentError] If we cannot convert to a string with #to_str
# @raise [RbNaCl::LengthError] If the string is not the right length
#
# @param string [#to_str] The input string
# @param length [Integer] The only acceptable length of the string
# @param description [String] Description of the string (used in the error)
def check_string(string, length, description)
check_string_validation(string)
string = string.to_s
check_length(string, length, description)
string
end
# Check a passed in string, convertion if necessary
#
# This method will check the key, and raise error
# if argument is not a string, and if it's empty string.
#
# RFC 2104 HMAC
# The key for HMAC can be of any length (keys longer than B bytes are
# first hashed using H). However, less than L bytes is strongly
# discouraged as it would decrease the security strength of the
# function. Keys longer than L bytes are acceptable but the extra
# length would not significantly increase the function strength. (A
# longer key may be advisable if the randomness of the key is
# considered weak.)
#
# see https://tools.ietf.org/html/rfc2104#section-3
#
#
# @raise [ArgumentError] If we cannot convert to a string with #to_str
# @raise [RbNaCl::LengthError] If the string is empty
#
# @param string [#to_str] The input string
def check_hmac_key(string, _description)
check_string_validation(string)
string = string.to_str
if string.bytesize.zero?
raise LengthError,
"#{Description} was #{string.bytesize} bytes (Expected more than 0)",
caller
end
string
end
# Check a passed string is it valid
#
# Raise an error if passed argument is invalid
#
# @raise [TypeError] If string cannot convert to a string with #to_str
# @raise [EncodingError] If string have wrong encoding
#
# @param string [#to_str] The input string
def check_string_validation(string)
raise TypeError, "can't convert #{string.class} into String with #to_str" unless string.respond_to? :to_str
string = string.to_str
raise EncodingError, "strings must use BINARY encoding (got #{string.encoding})" if string.encoding != Encoding::BINARY
end
# Compare two 64 byte strings in constant time
#
# This should help to avoid timing attacks for string comparisons in your
# application. Note that many of the functions (such as HmacSha512#verify)
# use this method under the hood already.
#
# @param [String] one String #1
# @param [String] two String #2
#
# @return [Boolean] Well, are they equal?
def verify64(one, two)
return false unless two.bytesize == 64 && one.bytesize == 64
c_verify64(one, two)
end
# Compare two 64 byte strings in constant time
#
# This should help to avoid timing attacks for string comparisons in your
# application. Note that many of the functions (such as HmacSha512#verify)
# use this method under the hood already.
#
# @param [String] one String #1
# @param [String] two String #2
#
# @raise [ArgumentError] If the strings are not equal in length
#
# @return [Boolean] Well, are they equal?
def verify64!(one, two)
check_length(one, 64, "First message")
check_length(two, 64, "Second message")
c_verify64(one, two)
end
# Compare two 32 byte strings in constant time
#
# This should help to avoid timing attacks for string comparisons in your
# application. Note that many of the functions (such as HmacSha256#verify)
# use this method under the hood already.
#
# @param [String] one String #1
# @param [String] two String #2
#
# @return [Boolean] Well, are they equal?
def verify32(one, two)
return false unless two.bytesize == 32 && one.bytesize == 32
c_verify32(one, two)
end
# Compare two 32 byte strings in constant time
#
# This should help to avoid timing attacks for string comparisons in your
# application. Note that many of the functions (such as HmacSha256#verify)
# use this method under the hood already.
#
# @param [String] one String #1
# @param [String] two String #2
#
# @raise [ArgumentError] If the strings are not equal in length
#
# @return [Boolean] Well, are they equal?
def verify32!(one, two)
check_length(one, 32, "First message")
check_length(two, 32, "Second message")
c_verify32(one, two)
end
# Compare two 16 byte strings in constant time
#
# This should help to avoid timing attacks for string comparisons in your
# application. Note that many of the functions (such as OneTime#verify)
# use this method under the hood already.
#
# @param [String] one String #1
# @param [String] two String #2
#
# @return [Boolean] Well, are they equal?
def verify16(one, two)
return false unless two.bytesize == 16 && one.bytesize == 16
c_verify16(one, two)
end
# Compare two 16 byte strings in constant time
#
# This should help to avoid timing attacks for string comparisons in your
# application. Note that many of the functions (such as OneTime#verify)
# use this method under the hood already.
#
# @param [String] one String #1
# @param [String] two String #2
#
# @raise [ArgumentError] If the strings are not equal in length
#
# @return [Boolean] Well, are they equal?
def verify16!(one, two)
check_length(one, 16, "First message")
check_length(two, 16, "Second message")
c_verify16(one, two)
end
# Hex encodes a message
#
# @param [String] bytes The bytes to encode
#
# @return [String] Tasty, tasty hexadecimal
def bin2hex(bytes)
bytes.to_s.unpack1("H*")
end
# Hex decodes a message
#
# @param [String] hex hex to decode.
#
# @return [String] crisp and clean bytes
def hex2bin(hex)
[hex.to_s].pack("H*")
end
end
end