sodium 0.6.2 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
data.tar.gz.sig CHANGED
Binary file
@@ -5,12 +5,9 @@ bundler_args: --without tools
5
5
  env:
6
6
  matrix:
7
7
  - >
8
- LIBSODIUM_MIRROR="https://github.com/jedisct1/libsodium/tarball/%s"
9
- LIBSODIUM_VERSION="master"
10
- # - >
11
- # LIBSODIUM_MIRROR="http://download.dnscrypt.org/libsodium/releases/libsodium-%s.tar.gz"
12
- # LIBSODIUM_VERSION=0.4.1
13
- # LIBSODIUM_DIGEST=65756c7832950401cc0e6ee0e99b165974244e749f40f33d465f56447bae8ce3
8
+ LIBSODIUM_MIRROR="http://download.dnscrypt.org/libsodium/releases/libsodium-%s.tar.gz"
9
+ LIBSODIUM_VERSION=0.4.2
10
+ LIBSODIUM_DIGEST=1a7901cdd127471724e854a8eb478247dc0ca67be549345c75fc6f2d4e05ed39
14
11
 
15
12
  rvm:
16
13
  - 1.8.7
@@ -25,6 +22,16 @@ rvm:
25
22
  - rbx-19mode
26
23
 
27
24
  matrix:
25
+ include:
26
+ - rvm: 2.0.0
27
+ env: >
28
+ LIBSODIUM_MIRROR="https://github.com/jedisct1/libsodium/tarball/%s"
29
+ LIBSODIUM_VERSION="master"
30
+
28
31
  allow_failures:
29
32
  - rvm: ruby-head
30
33
  - rvm: jruby-head
34
+ - rvm: 2.0.0
35
+ env: >
36
+ LIBSODIUM_MIRROR="https://github.com/jedisct1/libsodium/tarball/%s"
37
+ LIBSODIUM_VERSION="master"
@@ -1,4 +1,18 @@
1
- ### 0.6.1 (2013-07-09)
1
+ ### 0.7.0 (2013-07-18)
2
+
3
+ - Additions
4
+ * Sodium::Buffer#to_ptr added to replace #to_str
5
+ * Sodium::Buffer#to_s added to replace #to_str
6
+
7
+ - Removals
8
+ * Sodium::Buffer#to_str removed
9
+
10
+ - Bug Fixes
11
+ * Potential data loss bug fixed. Sodium::Buffer can no longer be
12
+ garbage collected (thus clearing its bytes) while a pointer to its
13
+ bytes (from #to_ptr) is being held.
14
+
15
+ ### 0.6.2 (2013-07-09)
2
16
 
3
17
  - Additions
4
18
  * now actually distributed with a license! (MIT)
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.6.2
1
+ 0.7.0
@@ -13,10 +13,10 @@ class Sodium::Auth
13
13
 
14
14
  Sodium::Buffer.empty self.implementation[:BYTES] do |authenticator|
15
15
  self.implementation.nacl(
16
- authenticator.to_str,
17
- message.to_str,
18
- message.to_str.bytesize,
19
- key.to_str
16
+ authenticator.to_ptr,
17
+ message .to_ptr,
18
+ message .bytesize,
19
+ key .to_ptr
20
20
  ) or raise Sodium::CryptoError, 'failed to generate an authenticator'
21
21
  end
22
22
  end
@@ -27,10 +27,10 @@ class Sodium::Auth
27
27
  authenticator = self._authenticator(authenticator)
28
28
 
29
29
  self.implementation.nacl_verify(
30
- authenticator.to_str,
31
- message.to_str,
32
- message.to_str.bytesize,
33
- key.to_str
30
+ authenticator.to_ptr,
31
+ message .to_ptr,
32
+ message .bytesize,
33
+ key .to_ptr
34
34
  )
35
35
  end
36
36
 
@@ -8,8 +8,8 @@ class Sodium::Box
8
8
  secret_key = Sodium::Buffer.empty self.implementation[:SECRETKEYBYTES]
9
9
 
10
10
  self.implementation.nacl_keypair(
11
- public_key.to_str,
12
- secret_key.to_str
11
+ public_key.to_ptr,
12
+ secret_key.to_ptr
13
13
  ) or raise Sodium::CryptoError, 'failed to generate a keypair'
14
14
 
15
15
  return secret_key, public_key
@@ -22,11 +22,11 @@ class Sodium::Box
22
22
 
23
23
  Sodium::Buffer.empty(message.bytesize) do |ciphertext|
24
24
  self.implementation.nacl_afternm(
25
- ciphertext.to_str,
26
- message.to_str,
27
- message.to_str.bytesize,
28
- nonce.to_str,
29
- shared_key.to_str
25
+ ciphertext.to_ptr,
26
+ message .to_ptr,
27
+ message .bytesize,
28
+ nonce .to_ptr,
29
+ shared_key.to_ptr
30
30
  ) or raise Sodium::CryptoError, 'failed to close the box'
31
31
  end.ldrop self.implementation[:BOXZEROBYTES]
32
32
  end
@@ -38,11 +38,11 @@ class Sodium::Box
38
38
 
39
39
  Sodium::Buffer.empty(ciphertext.bytesize) do |message|
40
40
  self.implementation.nacl_open_afternm(
41
- message.to_str,
42
- ciphertext.to_str,
43
- ciphertext.to_str.bytesize,
44
- nonce.to_str,
45
- shared_key.to_str
41
+ message .to_ptr,
42
+ ciphertext.to_ptr,
43
+ ciphertext.bytesize,
44
+ nonce .to_ptr,
45
+ shared_key.to_ptr
46
46
  ) or raise Sodium::CryptoError, 'failed to open the box'
47
47
  end.ldrop self.implementation[:ZEROBYTES]
48
48
  end
@@ -63,12 +63,12 @@ class Sodium::Box
63
63
 
64
64
  Sodium::Buffer.empty(message.bytesize) do |ciphertext|
65
65
  self.implementation.nacl(
66
- ciphertext.to_str,
67
- message.to_str,
68
- message.to_str.bytesize,
69
- nonce.to_str,
70
- @public_key.to_str,
71
- @secret_key.to_str
66
+ ciphertext .to_ptr,
67
+ message .to_ptr,
68
+ message .bytesize,
69
+ nonce .to_ptr,
70
+ @public_key.to_ptr,
71
+ @secret_key.to_ptr
72
72
  ) or raise Sodium::CryptoError, 'failed to close the box'
73
73
  end.ldrop self.implementation[:BOXZEROBYTES]
74
74
  end
@@ -79,12 +79,12 @@ class Sodium::Box
79
79
 
80
80
  Sodium::Buffer.empty(ciphertext.bytesize) do |message|
81
81
  self.implementation.nacl_open(
82
- message.to_str,
83
- ciphertext.to_str,
84
- ciphertext.to_str.bytesize,
85
- nonce.to_str,
86
- @public_key.to_str,
87
- @secret_key.to_str
82
+ message .to_ptr,
83
+ ciphertext .to_ptr,
84
+ ciphertext .bytesize,
85
+ nonce .to_ptr,
86
+ @public_key.to_ptr,
87
+ @secret_key.to_ptr
88
88
  ) or raise Sodium::CryptoError, 'failed to open the box'
89
89
  end.ldrop self.implementation[:ZEROBYTES]
90
90
  end
@@ -92,9 +92,9 @@ class Sodium::Box
92
92
  def beforenm
93
93
  Sodium::Buffer.empty self.implementation[:BEFORENMBYTES] do |shared_key|
94
94
  self.implementation.nacl_beforenm(
95
- shared_key.to_str,
96
- @public_key.to_str,
97
- @secret_key.to_str
95
+ shared_key .to_ptr,
96
+ @public_key.to_ptr,
97
+ @secret_key.to_ptr
98
98
  ) or raise Sodium::CryptoError, 'failed to create a shared key'
99
99
  end
100
100
  end
@@ -48,21 +48,37 @@ class Sodium::Buffer
48
48
  end
49
49
 
50
50
  def initialize(bytes)
51
- # initialize with a forced hard copy of the bytes (Ruby is smart
52
- # about using copy-on-write for strings 24 bytes or longer, so we
53
- # have to perform a no-op that forces Ruby to copy the bytes)
54
- @bytes = bytes.dup.tap {|s|
55
- s.force_encoding('BINARY') if
56
- s.respond_to?(:force_encoding)
57
- }.tr('', '').freeze
58
-
59
- self.class._mlock! @bytes
60
- self.class._mwipe! bytes
61
-
62
- ObjectSpace.define_finalizer self,
63
- self.class._finalizer(@bytes)
64
-
65
- self.freeze
51
+ # allocate memory for the incoming bytes and copy them in to our
52
+ # newly-owned memory
53
+ pointer = Sodium::FFI::LibC.calloc(1, bytes.bytesize)
54
+ pointer.write_string(bytes)
55
+
56
+ # use the ZeroingDelegator finalizer to wipe the memory from our
57
+ # pointer, and attach our own finalizer to free() the memory
58
+ ZeroingDelegator._finalize! self, pointer, bytes.bytesize,
59
+ &self.class._finalizer(pointer, bytes.bytesize)
60
+
61
+ # zero out the bytes passed to us, since we can't control their
62
+ # lifecycle
63
+ ZeroingDelegator._mwipe!(bytes, bytes.bytesize)
64
+
65
+ # WARNING: The following section is critical. Edit with caution!
66
+ #
67
+ # We create a new pointer to the bytes allocated earlier and set a
68
+ # hidden instance variable pointing at ourself. We do the latter
69
+ # so that there is a cyclic dependency between the buffer and the
70
+ # pointer; if either is still live in the current scope, it is
71
+ # enough to prevent the other from being collected. We create the
72
+ # new pointer since the previous pointer is pointed to by our
73
+ # finalizer; if we didn't, its existence in the finalizer proc
74
+ # would keep us from being garbage collected because of its
75
+ # pointer to us!
76
+ @bytesize = bytes.bytesize
77
+ @bytes = FFI::Pointer.new(pointer.address)
78
+ @bytes.instance_variable_set(:@_sodium_buffer, self)
79
+
80
+ @bytes.freeze
81
+ self .freeze
66
82
  end
67
83
 
68
84
  def ==(bytes)
@@ -72,8 +88,8 @@ class Sodium::Buffer
72
88
  self.bytesize == bytes.bytesize
73
89
 
74
90
  Sodium::FFI::Crypto.sodium_memcmp(
75
- self.to_str,
76
- bytes.to_str,
91
+ self .to_ptr,
92
+ bytes.to_ptr,
77
93
  bytes.bytesize
78
94
  ) == 0
79
95
  end
@@ -93,10 +109,10 @@ class Sodium::Buffer
93
109
 
94
110
  Sodium::Buffer.empty(self.bytesize) do |buffer|
95
111
  Sodium::FFI::Memory.sodium_memxor(
96
- buffer.to_str,
97
- self.to_str,
98
- bytes.to_str,
99
- bytes.bytesize
112
+ buffer.to_ptr,
113
+ self .to_ptr,
114
+ bytes .to_ptr,
115
+ bytes .bytesize
100
116
  )
101
117
  end
102
118
  end
@@ -112,8 +128,8 @@ class Sodium::Buffer
112
128
  bytes = Sodium::Buffer.new(bytes)
113
129
 
114
130
  Sodium::FFI::Memory.sodium_memput(
115
- self.to_str,
116
- bytes.to_str,
131
+ self .to_ptr,
132
+ bytes.to_ptr,
117
133
  offset,
118
134
  size
119
135
  )
@@ -121,67 +137,16 @@ class Sodium::Buffer
121
137
  true
122
138
  end
123
139
 
124
- def [](*args)
125
- return self.class.new(
126
- @bytes.byteslice(*args).to_s
127
- ) if (
128
- # Ruby 1.8 doesn't have byteslice
129
- @bytes.respond_to?(:byteslice) or
130
-
131
- # JRuby reuses memory regions when calling byteslice, which
132
- # results in them getting cleared when the new buffer initializes
133
- defined?(RUBY_ENGINE) and RUBY_ENGINE == 'java'
140
+ def [](offset, size)
141
+ self.class.new(
142
+ @bytes.get_bytes(offset, size)
134
143
  )
135
-
136
- raise ArgumentError, 'wrong number of arguments (0 for 1..2)' if
137
- args.length < 1 or args.length > 2
138
-
139
- start, finish = case
140
- when args[1]
141
- # matches: byteslice(start, size)
142
- start = args[0].to_i
143
- size = args[1].to_i
144
-
145
- # if size is less than 1, finish needs to be small enough that
146
- # `finish - start + 1 <= 0` even after finish is wrapped
147
- # around to account for negative indices
148
- finish = size > 0 ?
149
- start + size - 1 :
150
- - self.bytesize.succ
151
-
152
- [ start, finish ]
153
- when args[0].kind_of?(Range)
154
- # matches: byteslice(start .. finish)
155
- # matches: byteslice(start ... finish)
156
- range = args[0]
157
- start = range.begin.to_i
158
- finish = range.exclude_end? ? range.end.to_i - 1 : range.end.to_i
159
-
160
- [ start, finish ]
161
- else
162
- # matches: byteslice(start)
163
- [ args[0].to_i, args[0].to_i ]
164
- end
165
-
166
- # ensure negative values are wrapped around explicitly
167
- start += self.bytesize if start < 0
168
- finish += self.bytesize if finish < 0
169
- size = finish - start + 1
170
-
171
- # this approach ensures the bytes are copied into new memory, so
172
- # instantiating a new buffer doesn't clear the bytes in the
173
- # existing buffer
174
- bytes = (start >= 0 and size >= 0) ?
175
- @bytes.unpack("@#{start}a#{size}").first :
176
- ''
177
-
178
- self.class.new(bytes)
179
144
  end
180
145
 
181
146
  alias byteslice []
182
147
 
183
148
  def bytesize
184
- @bytes.bytesize
149
+ @bytesize
185
150
  end
186
151
 
187
152
  def rdrop(size)
@@ -198,22 +163,113 @@ class Sodium::Buffer
198
163
  "#<%s:0x%x>" % [ self.class.name, self.__id__ * 2 ]
199
164
  end
200
165
 
166
+ def to_s
167
+ # Pretend to return a string, but really return a Delegator that
168
+ # wipes the string's memory when it gets garbage collected.
169
+ #
170
+ # Since any calls to the methods of the String inside the
171
+ # delegator by necessity have the delegator's `method_missing` in
172
+ # their backtrace, there can never be a situation where there is a
173
+ # live pointer to the string itself but not one to the delegator.
174
+ ZeroingDelegator.new(
175
+ @bytes.read_bytes(@bytesize)
176
+ )
177
+ end
178
+
179
+ def to_ptr
180
+ @bytes
181
+ end
182
+
183
+ private
184
+
185
+ def self._finalizer(pointer, size)
186
+ proc { self._free!(pointer) }
187
+ end
188
+
189
+ def self._free!(pointer)
190
+ Sodium::FFI::LibC.free(pointer)
191
+ end
192
+ end
193
+
194
+ class Sodium::Buffer::ZeroingDelegator
195
+ self.instance_methods.map(&:to_sym).each do |method|
196
+ undef_method method unless [
197
+ :__id__,
198
+ :__send__,
199
+ :object_id,
200
+ :equal?,
201
+ ].include?(method)
202
+ end
203
+
204
+ def initialize(string, &finalizer)
205
+ self.__setobj__(string)
206
+
207
+ # specify class name explicitly, since we're letting the `class`
208
+ # method delegate to the wrapped object
209
+ Sodium::Buffer::ZeroingDelegator._mlock! string, string.bytesize
210
+ Sodium::Buffer::ZeroingDelegator._finalize! self, string, string.bytesize,
211
+ &finalizer
212
+
213
+ self.__getobj__.freeze
214
+ self .freeze
215
+ end
216
+
217
+ def to_s
218
+ self
219
+ end
220
+
201
221
  def to_str
202
- @bytes.to_str
222
+ # Okay, fine, you win. I'll give you access to the raw string
223
+ # we're wrapping. But now I can't wipe the data when you're done
224
+ # with it.
225
+ self.__getobj__.
226
+ tr('', ''). # trick to force the string to be copied
227
+ freeze
228
+ end
229
+
230
+ alias dup to_s
231
+ alias clone to_s
232
+
233
+ protected
234
+
235
+ def method_missing(*args, &block)
236
+ self.__getobj__.__send__(*args, &block)
237
+ ensure
238
+ $@.delete_if do |trace|
239
+ # delete lines from the backtrace that originate from the
240
+ # __send__ line above
241
+ trace =~ %r{ \A #{Regexp.quote(__FILE__)}:#{__LINE__ - 5} : }x
242
+ end if $@
243
+ end
244
+
245
+ def __getobj__
246
+ @_obj
247
+ end
248
+
249
+ def __setobj__(string)
250
+ @_obj = string
203
251
  end
204
252
 
205
253
  private
206
254
 
207
- def self._finalizer(buffer)
208
- proc { self._mwipe!(buffer) }
255
+ def self._finalizer(pointer, size, &finalizer)
256
+ proc {
257
+ self._mwipe! pointer, size
258
+ finalizer.call pointer, size if finalizer
259
+ }
260
+ end
261
+
262
+ def self._finalize!(delegator, pointer, size, &finalizer)
263
+ ObjectSpace.define_finalizer delegator,
264
+ self._finalizer(pointer, size, &finalizer)
209
265
  end
210
266
 
211
- def self._mwipe!(buffer)
212
- Sodium::FFI::Crypto.sodium_memzero(buffer, buffer.bytesize)
267
+ def self._mwipe!(pointer, size)
268
+ Sodium::FFI::Crypto.sodium_memzero(pointer, size)
213
269
  end
214
270
 
215
- def self._mlock!(buffer)
216
- Sodium::FFI::LibC.mlock(buffer, buffer.bytesize) or
271
+ def self._mlock!(pointer, size)
272
+ Sodium::FFI::LibC.mlock(pointer, size) or
217
273
  raise Sodium::MemoryError, 'could not mlock(2) secure buffer into memory'
218
274
  end
219
275
  end