sodium 0.6.2 → 0.7.0

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