File Coverage

blib/lib/Sodium/FFI.pm
Criterion Covered Total %
statement 51 59 86.4
branch 5 20 25.0
condition 5 12 41.6
subroutine 15 16 93.7
pod 4 4 100.0
total 80 111 72.0


line stmt bran cond sub pod time code
1             package Sodium::FFI;
2 9     9   947128 use strict;
  9         114  
  9         259  
3 9     9   68 use warnings;
  9         18  
  9         358  
4              
5             our $VERSION = '0.009';
6              
7 9     9   53 use Carp qw(croak);
  9         16  
  9         437  
8 9     9   77 use Exporter qw(import);
  9         26  
  9         377  
9              
10 9     9   4071 use Alien::Sodium;
  9         447310  
  9         81  
11 9     9   224145 use FFI::Platypus;
  9         64578  
  9         332  
12 9     9   74 use Path::Tiny qw(path);
  9         22  
  9         627  
13 9     9   4256 use Sub::Util qw(set_subname);
  9         3039  
  9         1331  
14              
15             # these are the methods we can easily attach
16             our @EXPORT_OK = qw(
17             randombytes_random randombytes_uniform
18             sodium_version_string sodium_library_version_minor sodium_base64_encoded_len
19             crypto_aead_aes256gcm_keygen crypto_aead_chacha20poly1305_keygen
20             crypto_aead_chacha20poly1305_ietf_keygen crypto_auth_keygen
21             );
22              
23             # add the various C Constants
24             push @EXPORT_OK, qw(
25             crypto_auth_BYTES crypto_auth_KEYBYTES
26             SODIUM_VERSION_STRING SIZE_MAX randombytes_SEEDBYTES SODIUM_LIBRARY_MINIMAL
27             SODIUM_LIBRARY_VERSION_MAJOR SODIUM_LIBRARY_VERSION_MINOR
28             sodium_base64_VARIANT_ORIGINAL sodium_base64_VARIANT_ORIGINAL_NO_PADDING
29             sodium_base64_VARIANT_URLSAFE sodium_base64_VARIANT_URLSAFE_NO_PADDING
30             crypto_aead_aes256gcm_KEYBYTES crypto_aead_aes256gcm_NPUBBYTES crypto_aead_aes256gcm_ABYTES
31             HAVE_AEAD_DETACHED HAVE_AESGCM
32             crypto_aead_chacha20poly1305_KEYBYTES crypto_aead_chacha20poly1305_NPUBBYTES
33             crypto_aead_chacha20poly1305_ABYTES
34             crypto_aead_chacha20poly1305_IETF_KEYBYTES crypto_aead_chacha20poly1305_IETF_NPUBBYTES
35             crypto_aead_chacha20poly1305_IETF_ABYTES
36             crypto_sign_SEEDBYTES crypto_sign_BYTES crypto_sign_SECRETKEYBYTES crypto_sign_PUBLICKEYBYTES
37             crypto_box_SEALBYTES crypto_box_PUBLICKEYBYTES crypto_box_SECRETKEYBYTES
38             crypto_box_MACBYTES crypto_box_NONCEBYTES crypto_box_SEEDBYTES crypto_box_BEFORENMBYTES
39             );
40              
41             our $ffi;
42             BEGIN {
43 9     9   104 $ffi = FFI::Platypus->new(api => 1, lib => Alien::Sodium->dynamic_libs);
44 9         147229 $ffi->bundle();
45             }
46             # All of these functions don't need to be gated by version.
47             $ffi->attach('randombytes_random' => [] => 'uint32');
48             $ffi->attach('randombytes_uniform' => ['uint32'] => 'uint32');
49             $ffi->attach('sodium_version_string' => [] => 'string');
50             $ffi->attach('sodium_library_version_major' => [] => 'int');
51             $ffi->attach('sodium_library_version_minor' => [] => 'int');
52             $ffi->attach('sodium_base64_encoded_len' => ['size_t', 'int'] => 'size_t');
53              
54             sub crypto_aead_aes256gcm_keygen {
55 0     0 1 0 my $len = Sodium::FFI::crypto_aead_aes256gcm_KEYBYTES;
56 0         0 return Sodium::FFI::randombytes_buf($len);
57             }
58              
59             sub crypto_aead_chacha20poly1305_ietf_keygen {
60 2     2 1 2365 my $len = Sodium::FFI::crypto_aead_chacha20poly1305_IETF_KEYBYTES;
61 2         9 return Sodium::FFI::randombytes_buf($len);
62             }
63              
64             sub crypto_aead_chacha20poly1305_keygen {
65 2     2 1 2273 my $len = Sodium::FFI::crypto_aead_chacha20poly1305_KEYBYTES;
66 2         8 return Sodium::FFI::randombytes_buf($len);
67             }
68              
69             sub crypto_auth_keygen {
70 1     1 1 926 my $len = Sodium::FFI::crypto_auth_KEYBYTES;
71 1         4 return Sodium::FFI::randombytes_buf($len);
72             }
73              
74             our %function = (
75             # int
76             # crypto_auth(unsigned char *out, const unsigned char *in,
77             # unsigned long long inlen, const unsigned char *k);
78             'crypto_auth' => [
79             ['string', 'string', 'size_t', 'string'] => 'int',
80             sub {
81             my ($xsub, $msg, $key) = @_;
82             my $msg_len = length($msg);
83             my $key_len = length($key);
84              
85             unless($key_len == Sodium::FFI::crypto_auth_KEYBYTES) {
86             croak("Secret key length should be crypto_auth_KEYBYTES bytes");
87             }
88              
89             my $mac = "\0" x Sodium::FFI::crypto_auth_BYTES;
90             my $real_len = 0;
91             my $ret = $xsub->($mac, $msg, $msg_len, $key);
92             croak("Internal error") unless $ret == 0;
93             return $mac;
94             }
95             ],
96              
97             # int
98             # crypto_auth_verify(const unsigned char *h, const unsigned char *in,
99             # unsigned long long inlen, const unsigned char *k);
100             'crypto_auth_verify' => [
101             ['string', 'string', 'size_t', 'string'] => 'int',
102             sub {
103             my ($xsub, $mac, $msg, $key) = @_;
104             my $mac_len = length($mac);
105             my $msg_len = length($msg);
106             my $key_len = length($key);
107             my $SIZE_MAX = Sodium::FFI::SIZE_MAX;
108              
109             unless ($key_len == Sodium::FFI::crypto_auth_KEYBYTES) {
110             croak("Secret key length should be crypto_auth_KEYBYTES bytes");
111             }
112             unless ($mac_len == Sodium::FFI::crypto_auth_BYTES) {
113             croak("authentication tag length should be crypto_auth_BYTES bytes");
114             }
115              
116             my $ret = $xsub->($mac, $msg, $msg_len, $key);
117             return 1 if $ret == 0;
118             return 0;
119             }
120             ],
121              
122             # int
123             # crypto_aead_chacha20poly1305_ietf_decrypt(unsigned char *m,
124             # unsigned long long *mlen_p,
125             # unsigned char *nsec,
126             # const unsigned char *c,
127             # unsigned long long clen,
128             # const unsigned char *ad,
129             # unsigned long long adlen,
130             # const unsigned char *npub,
131             # const unsigned char *k);
132             'crypto_aead_chacha20poly1305_ietf_decrypt' => [
133             ['string', 'size_t*', 'string', 'string', 'size_t', 'string', 'size_t', 'string', 'string'] => 'int',
134             sub {
135             my ($xsub, $ciphertext, $ad, $nonce, $key) = @_;
136             my $ciphertext_len = length($ciphertext);
137             my $ad_len = length($ad);
138             my $nonce_len = length($nonce);
139             my $key_len = length($key);
140             my $SIZE_MAX = Sodium::FFI::SIZE_MAX;
141              
142             unless ($nonce_len == Sodium::FFI::crypto_aead_chacha20poly1305_IETF_NPUBBYTES) {
143             croak("Nonce length should be crypto_aead_chacha20poly1305_IETF_NPUBBYTES bytes");
144             }
145             unless($key_len == Sodium::FFI::crypto_aead_chacha20poly1305_IETF_KEYBYTES) {
146             croak("Secret key length should be crypto_aead_chacha20poly1305_IETF_KEYBYTES bytes");
147             }
148             if ($ciphertext_len < Sodium::FFI::crypto_aead_chacha20poly1305_IETF_ABYTES) {
149             croak("cipher text length not right");
150             }
151             my $msg_len = $ciphertext_len;
152             if ($msg_len > $SIZE_MAX) {
153             croak("Message length greater than max size");
154             }
155             my $msg = "\0" x $msg_len;
156             my $real_len = 0;
157             my $ret = $xsub->($msg, \$real_len, undef, $ciphertext, $ciphertext_len, $ad, $ad_len, $nonce, $key);
158             croak("Internal error") unless $ret == 0;
159             if ($real_len <= 0 || $real_len >= $SIZE_MAX || $real_len > $msg_len) {
160             croak("Invalid resultant length");
161             }
162             if ($real_len >= $SIZE_MAX || $real_len > $msg_len) {
163             croak("arithmetic overflow");
164             }
165             return substr($msg, 0, $real_len);
166             }
167             ],
168              
169             # int
170             # crypto_aead_chacha20poly1305_ietf_encrypt(unsigned char *c,
171             # unsigned long long *clen_p,
172             # const unsigned char *m,
173             # unsigned long long mlen,
174             # const unsigned char *ad,
175             # unsigned long long adlen,
176             # const unsigned char *nsec,
177             # const unsigned char *npub,
178             # const unsigned char *k);
179             'crypto_aead_chacha20poly1305_ietf_encrypt' => [
180             ['string', 'size_t*', 'string', 'size_t', 'string', 'size_t', 'string', 'string', 'string'] => 'int',
181             sub {
182             my ($xsub, $msg, $ad, $nonce, $key) = @_;
183             my $msg_len = length($msg);
184             my $ad_len = length($ad);
185             my $nonce_len = length($nonce);
186             my $key_len = length($key);
187             my $SIZE_MAX = Sodium::FFI::SIZE_MAX;
188              
189             unless ($nonce_len == Sodium::FFI::crypto_aead_chacha20poly1305_IETF_NPUBBYTES) {
190             croak("Nonce length should be crypto_aead_chacha20poly1305_IETF_NPUBBYTES bytes");
191             }
192             unless($key_len == Sodium::FFI::crypto_aead_chacha20poly1305_IETF_KEYBYTES) {
193             croak("Secret key length should be crypto_aead_chacha20poly1305_IETF_KEYBYTES bytes");
194             }
195             if ($SIZE_MAX - $msg_len <= Sodium::FFI::crypto_aead_chacha20poly1305_IETF_ABYTES) {
196             croak("arithmetic overflow");
197             }
198              
199             my $ciphertext_len = $msg_len + Sodium::FFI::crypto_aead_chacha20poly1305_IETF_ABYTES;
200             my $ciphertext = "\0" x $ciphertext_len;
201             my $real_len = 0;
202             my $ret = $xsub->($ciphertext, \$real_len, $msg, $msg_len, $ad, $ad_len, undef, $nonce, $key);
203             croak("Internal error") unless $ret == 0;
204             if ($real_len <= 0 || $real_len > $SIZE_MAX || $real_len > $ciphertext_len) {
205             croak("Invalid resultant length");
206             }
207             return substr($ciphertext, 0, $real_len);
208             }
209             ],
210              
211             # int
212             # crypto_aead_chacha20poly1305_decrypt(unsigned char *m,
213             # unsigned long long *mlen_p,
214             # unsigned char *nsec,
215             # const unsigned char *c,
216             # unsigned long long clen,
217             # const unsigned char *ad,
218             # unsigned long long adlen,
219             # const unsigned char *npub,
220             # const unsigned char *k);
221             'crypto_aead_chacha20poly1305_decrypt' => [
222             ['string', 'size_t*', 'string', 'string', 'size_t', 'string', 'size_t', 'string', 'string'] => 'int',
223             sub {
224             my ($xsub, $ciphertext, $ad, $nonce, $key) = @_;
225             my $ciphertext_len = length($ciphertext);
226             my $ad_len = length($ad);
227             my $nonce_len = length($nonce);
228             my $key_len = length($key);
229             my $SIZE_MAX = Sodium::FFI::SIZE_MAX;
230              
231             unless ($nonce_len == Sodium::FFI::crypto_aead_chacha20poly1305_NPUBBYTES) {
232             croak("Nonce length should be crypto_aead_chacha20poly1305_NPUBBYTES bytes");
233             }
234             unless($key_len == Sodium::FFI::crypto_aead_chacha20poly1305_KEYBYTES) {
235             croak("Secret key length should be crypto_aead_chacha20poly1305_KEYBYTES bytes");
236             }
237             if ($ciphertext_len < Sodium::FFI::crypto_aead_chacha20poly1305_ABYTES) {
238             croak("cipher text length not right");
239             }
240             my $msg_len = $ciphertext_len;
241             if ($msg_len > $SIZE_MAX) {
242             croak("Message length greater than max size");
243             }
244             my $msg = "\0" x $msg_len;
245             my $real_len = 0;
246             my $ret = $xsub->($msg, \$real_len, undef, $ciphertext, $ciphertext_len, $ad, $ad_len, $nonce, $key);
247             croak("Internal error") unless $ret == 0;
248             if ($real_len <= 0 || $real_len >= $SIZE_MAX || $real_len > $msg_len) {
249             croak("Invalid resultant length");
250             }
251             if ($real_len >= $SIZE_MAX || $real_len > $msg_len) {
252             croak("arithmetic overflow");
253             }
254             return substr($msg, 0, $real_len);
255             }
256             ],
257              
258             # int
259             # crypto_aead_chacha20poly1305_encrypt(unsigned char *c,
260             # unsigned long long *clen_p,
261             # const unsigned char *m,
262             # unsigned long long mlen,
263             # const unsigned char *ad,
264             # unsigned long long adlen,
265             # const unsigned char *nsec,
266             # const unsigned char *npub,
267             # const unsigned char *k)
268             'crypto_aead_chacha20poly1305_encrypt' => [
269             ['string', 'size_t*', 'string', 'size_t', 'string', 'size_t', 'string', 'string', 'string'] => 'int',
270             sub {
271             my ($xsub, $msg, $ad, $nonce, $key) = @_;
272             my $msg_len = length($msg);
273             my $ad_len = length($ad);
274             my $nonce_len = length($nonce);
275             my $key_len = length($key);
276             my $SIZE_MAX = Sodium::FFI::SIZE_MAX;
277              
278             unless ($nonce_len == Sodium::FFI::crypto_aead_chacha20poly1305_NPUBBYTES) {
279             croak("Nonce length should be crypto_aead_chacha20poly1305_NPUBBYTES bytes");
280             }
281             unless($key_len == Sodium::FFI::crypto_aead_chacha20poly1305_KEYBYTES) {
282             croak("Secret key length should be crypto_aead_chacha20poly1305_KEYBYTES bytes");
283             }
284             if ($SIZE_MAX - $msg_len <= Sodium::FFI::crypto_aead_chacha20poly1305_ABYTES) {
285             croak("arithmetic overflow");
286             }
287              
288             my $ciphertext_len = $msg_len + Sodium::FFI::crypto_aead_chacha20poly1305_ABYTES;
289             my $ciphertext = "\0" x $ciphertext_len;
290             my $real_len = 0;
291             my $ret = $xsub->($ciphertext, \$real_len, $msg, $msg_len, $ad, $ad_len, undef, $nonce, $key);
292             croak("Internal error") unless $ret == 0;
293             if ($real_len <= 0 || $real_len > $SIZE_MAX || $real_len > $ciphertext_len) {
294             croak("Invalid resultant length");
295             }
296             return substr($ciphertext, 0, $real_len);
297             }
298             ],
299              
300             # int
301             # crypto_aead_aes256gcm_encrypt(unsigned char *c,
302             # unsigned long long *clen_p,
303             # const unsigned char *m,
304             # unsigned long long mlen,
305             # const unsigned char *ad,
306             # unsigned long long adlen,
307             # const unsigned char *nsec,
308             # const unsigned char *npub,
309             # const unsigned char *k);
310             'crypto_aead_aes256gcm_encrypt' => [
311             ['string', 'size_t*', 'string', 'size_t', 'string', 'size_t', 'string', 'string', 'string'] => 'int',
312             sub {
313             my ($xsub, $msg, $ad, $nonce, $key) = @_;
314             croak("AESGCM not available.") unless Sodium::FFI::crypto_aead_aes256gcm_is_available();
315             my $msg_len = length($msg);
316             my $ad_len = length($ad);
317             my $nonce_len = length($nonce);
318             my $key_len = length($key);
319             my $SIZE_MAX = Sodium::FFI::SIZE_MAX;
320              
321             unless ($nonce_len == Sodium::FFI::crypto_aead_aes256gcm_NPUBBYTES) {
322             croak("Nonce length should be crypto_aead_aes256gcm_NPUBBYTES bytes");
323             }
324             unless($key_len == Sodium::FFI::crypto_aead_aes256gcm_KEYBYTES) {
325             croak("Secret key length should be crypto_aead_aes256gcm_KEYBYTES bytes");
326             }
327             if ($SIZE_MAX - $msg_len <= Sodium::FFI::crypto_aead_aes256gcm_ABYTES) {
328             croak("arithmetic overflow");
329             }
330             if ($msg_len > (16 * ((1 << 32) - 2)) - Sodium::FFI::crypto_aead_aes256gcm_ABYTES) {
331             croak("message too long for a single key");
332             }
333             my $ciphertext_len = $msg_len + Sodium::FFI::crypto_aead_aes256gcm_ABYTES;
334             my $ciphertext = "\0" x $ciphertext_len;
335             my $real_len = 0;
336             my $ret = $xsub->($ciphertext, \$real_len, $msg, $msg_len, $ad, $ad_len, undef, $nonce, $key);
337             croak("Internal error") unless $ret == 0;
338             if ($real_len <= 0 || $real_len > $SIZE_MAX || $real_len > $ciphertext_len) {
339             croak("Invalid resultant length");
340             }
341             return substr($ciphertext, 0, $real_len);
342             }
343             ],
344              
345             # int
346             # crypto_aead_aes256gcm_decrypt(unsigned char *m,
347             # unsigned long long *mlen_p,
348             # unsigned char *nsec,
349             # const unsigned char *c,
350             # unsigned long long clen,
351             # const unsigned char *ad,
352             # unsigned long long adlen,
353             # const unsigned char *npub,
354             # const unsigned char *k);
355             'crypto_aead_aes256gcm_decrypt' => [
356             ['string', 'size_t*', 'string', 'string', 'size_t', 'string', 'size_t', 'string', 'string'] => 'int',
357             sub {
358             my ($xsub, $ciphertext, $ad, $nonce, $key) = @_;
359             croak("AESGCM not available.") unless Sodium::FFI::crypto_aead_aes256gcm_is_available();
360             my $ciphertext_len = length($ciphertext);
361             my $ad_len = length($ad);
362             my $nonce_len = length($nonce);
363             my $key_len = length($key);
364             my $SIZE_MAX = Sodium::FFI::SIZE_MAX;
365              
366             unless ($nonce_len == Sodium::FFI::crypto_aead_aes256gcm_NPUBBYTES) {
367             croak("Nonce length should be crypto_aead_aes256gcm_NPUBBYTES bytes");
368             }
369             unless($key_len == Sodium::FFI::crypto_aead_aes256gcm_KEYBYTES) {
370             croak("Secret key length should be crypto_aead_aes256gcm_KEYBYTES bytes");
371             }
372             if ($ciphertext_len < Sodium::FFI::crypto_aead_aes256gcm_ABYTES) {
373             croak("cipher text length not right");
374             }
375             if ($ciphertext_len - Sodium::FFI::crypto_aead_aes256gcm_ABYTES > 16 * ((1 << 32) - 2)) {
376             croak("cipher text too long for a single key");
377             }
378             my $msg_len = $ciphertext_len;
379             if ($msg_len > $SIZE_MAX) {
380             croak("Message length greater than max size");
381             }
382             my $msg = "\0" x $msg_len;
383             my $real_len = 0;
384             my $ret = $xsub->($msg, \$real_len, undef, $ciphertext, $ciphertext_len, $ad, $ad_len, $nonce, $key);
385             croak("Internal error") unless $ret == 0;
386             if ($real_len <= 0 || $real_len >= $SIZE_MAX || $real_len > $msg_len) {
387             croak("Invalid resultant length");
388             }
389             return substr($msg, 0, $real_len);
390             }
391             ],
392              
393             # int
394             # crypto_aead_aes256gcm_is_available()
395             'crypto_aead_aes256gcm_is_available' => [
396             [] => 'int',
397             sub {
398             my ($xsub) = @_;
399             if (Sodium::FFI::HAVE_AESGCM) {
400             return $xsub->();
401             }
402             return 0;
403             }
404             ],
405              
406             # int
407             # crypto_box_easy(unsigned char *c, const unsigned char *m,
408             # unsigned long long mlen, const unsigned char *n,
409             # const unsigned char *pk, const unsigned char *sk);
410             'crypto_box_easy' => [
411             ['string', 'string', 'size_t', 'string', 'string', 'string'] => 'int',
412             sub {
413             my ($xsub, $msg, $nonce, $pk, $sk) = @_;
414             my $msg_len = length($msg);
415             my $nonce_len = length($nonce);
416             my $pk_len = length($pk);
417             my $sk_len = length($sk);
418             my $SIZE_MAX = Sodium::FFI::SIZE_MAX;
419             if ($nonce_len != Sodium::FFI::crypto_box_NONCEBYTES) {
420             croak("The nonce must be crypto_box_NONCEBYTES in length");
421             }
422             if ($pk_len != Sodium::FFI::crypto_box_PUBLICKEYBYTES) {
423             croak("The public key must be crypto_box_PUBLICKEYBYTES in length");
424             }
425             if ($sk_len != Sodium::FFI::crypto_box_SECRETKEYBYTES) {
426             croak("The secret key must be crypto_box_SECRETKEYBYTES in length");
427             }
428             if ($SIZE_MAX - $msg_len <= Sodium::FFI::crypto_box_MACBYTES) {
429             croak("Arithmetic overflow");
430             }
431             my $cipher_len = Sodium::FFI::crypto_box_MACBYTES + $msg_len;
432             my $cipher_text = "\0" x $cipher_len;
433             my $ret = $xsub->($cipher_text, $msg, $msg_len, $nonce, $pk, $sk);
434             if ($ret != 0) {
435             croak("Some internal error happened");
436             }
437             return $cipher_text;
438             }
439             ],
440              
441             # int
442             # crypto_box_keypair(unsigned char *pk, unsigned char *sk);
443             'crypto_box_keypair' => [
444             ['string', 'string'] => 'int',
445             sub {
446             my ($xsub) = @_;
447             my $pubkey = "\0" x Sodium::FFI::crypto_box_PUBLICKEYBYTES ;
448             my $seckey = "\0" x Sodium::FFI::crypto_box_SECRETKEYBYTES;
449             my $ret = $xsub->($pubkey, $seckey);
450             if ($ret != 0) {
451             croak("Some internal error happened");
452             }
453             return ($pubkey, $seckey);
454             }
455             ],
456              
457             # int
458             # crypto_box_open_easy(unsigned char *m, const unsigned char *c,
459             # unsigned long long clen, const unsigned char *n,
460             # const unsigned char *pk, const unsigned char *sk);
461             'crypto_box_open_easy' => [
462             ['string', 'string', 'size_t', 'string', 'string', 'string'] => 'int',
463             sub {
464             my ($xsub, $cipher_text, $nonce, $pk, $sk) = @_;
465             my $cipher_len = length($cipher_text);
466             my $nonce_len = length($nonce);
467             my $pk_len = length($pk);
468             my $sk_len = length($sk);
469             my $SIZE_MAX = Sodium::FFI::SIZE_MAX;
470             if ($nonce_len != Sodium::FFI::crypto_box_NONCEBYTES) {
471             croak("The nonce must be crypto_box_NONCEBYTES in length");
472             }
473             if ($pk_len != Sodium::FFI::crypto_box_PUBLICKEYBYTES) {
474             croak("The public key must be crypto_box_PUBLICKEYBYTES in length");
475             }
476             if ($sk_len != Sodium::FFI::crypto_box_SECRETKEYBYTES) {
477             croak("The secret key must be crypto_box_SECRETKEYBYTES in length");
478             }
479             if ($cipher_len <= Sodium::FFI::crypto_box_MACBYTES) {
480             croak("The cipher text should be larger than crypto_box_MACBYTES bytes");
481             }
482              
483             my $msg_len = $cipher_len - Sodium::FFI::crypto_box_MACBYTES;
484             my $msg = "\0" x $msg_len;
485             my $ret = $xsub->($msg, $cipher_text, $cipher_len, $nonce, $pk, $sk);
486             if ($ret != 0) {
487             croak("Some internal error happened");
488             }
489             return $msg;
490             }
491             ],
492              
493             # int
494             # crypto_box_seal(unsigned char *c, const unsigned char *m,
495             # unsigned long long mlen, const unsigned char *pk)
496             'crypto_box_seal' => [
497             ['string', 'string', 'size_t', 'string'] => 'int',
498             sub {
499             my ($xsub, $message, $pk) = @_;
500              
501             if (length($pk) != Sodium::FFI::crypto_box_PUBLICKEYBYTES) {
502             croak(
503             "The public key must be crypto_box_PUBLICKEYBYTES in length"
504             );
505             }
506              
507             my $message_len = length($message);
508              
509             my $cipher_len = Sodium::FFI::crypto_box_SEALBYTES + $message_len;
510             my $cipher_text = "\0" x $cipher_len;
511              
512             my $ret = $xsub->($cipher_text, $message, $message_len, $pk);
513             if ($ret != 0) {
514             croak("Some internal error happened");
515             }
516             return $cipher_text;
517             }
518             ],
519              
520             # crypto_box_seal_open(unsigned char *m, const unsigned char *c,
521             # unsigned long long clen,
522             # const unsigned char *pk, const unsigned char *sk)
523             'crypto_box_seal_open' => [
524             ['string', 'string', 'size_t', 'string', 'string'] => 'int',
525             sub {
526             my ($xsub, $cipher_text, $pk, $sk) = @_;
527              
528             if (length($pk) != Sodium::FFI::crypto_box_PUBLICKEYBYTES) {
529             croak(
530             "The public key must be crypto_box_PUBLICKEYBYTES in length"
531             );
532             }
533             if (length($sk) != Sodium::FFI::crypto_box_SECRETKEYBYTES) {
534             croak(
535             "The secret key must be crypto_box_SECRETKEYBYTES in length"
536             );
537             }
538              
539             if (length($cipher_text) < crypto_box_SEALBYTES) {
540             return -1;
541             }
542              
543             my $msg = "\0" x (length($cipher_text) - crypto_box_SEALBYTES);
544             my $ret
545             = $xsub->($msg, $cipher_text, length($cipher_text), $pk, $sk);
546             if ($ret != 0) {
547             croak("Some internal error happened");
548             }
549             return $msg;
550             }
551             ],
552              
553             # int
554             # crypto_box_seed_keypair(unsigned char *pk, unsigned char *sk, const unsigned char *seed);
555             'crypto_box_seed_keypair' => [
556             ['string', 'string', 'string'] => 'int',
557             sub {
558             my ($xsub, $seed) = @_;
559             my $seed_len = length($seed);
560             unless ($seed_len == Sodium::FFI::crypto_box_SEEDBYTES) {
561             croak("Seed length must be crypto_box_SEEDBYTES in length");
562             }
563             my $pubkey = "\0" x Sodium::FFI::crypto_box_PUBLICKEYBYTES;
564             my $seckey = "\0" x Sodium::FFI::crypto_box_SECRETKEYBYTES;
565             my $ret = $xsub->($pubkey, $seckey, $seed);
566             if ($ret != 0) {
567             croak("Some internal error happened");
568             }
569             return ($pubkey, $seckey);
570             }
571             ],
572              
573             # int
574             # crypto_scalarmult_base(unsigned char *q, const unsigned char *n);
575             'crypto_scalarmult_base' => [
576             ['string', 'string'] => 'int',
577             sub {
578             my ($xsub, $secret_key) = @_;
579             my $sk_len = length($secret_key);
580             unless ($sk_len == Sodium::FFI::crypto_box_SECRETKEYBYTES) {
581             croak("Secret Key length must be crypto_box_SECRETKEYBYTES in length");
582             }
583             my $pubkey = "\0" x Sodium::FFI::crypto_box_PUBLICKEYBYTES;
584             my $ret = $xsub->($pubkey, $secret_key);
585             if ($ret != 0) {
586             croak("Some internal error happened");
587             }
588             return $pubkey;
589             }
590             ],
591              
592             # int
593             # crypto_sign_keypair(unsigned char *pk, unsigned char *sk);
594             'crypto_sign_keypair' => [
595             ['string', 'string'] => 'int',
596             sub {
597             my ($xsub) = @_;
598             my $pubkey = "\0" x Sodium::FFI::crypto_sign_PUBLICKEYBYTES;
599             my $seckey = "\0" x Sodium::FFI::crypto_sign_SECRETKEYBYTES;
600             my $ret = $xsub->($pubkey, $seckey);
601             if ($ret != 0) {
602             croak("Some internal error happened");
603             }
604             return ($pubkey, $seckey);
605             }
606             ],
607              
608             # int
609             # crypto_sign_seed_keypair(unsigned char *pk, unsigned char *sk, const unsigned char *seed);
610             'crypto_sign_seed_keypair' => [
611             ['string', 'string', 'string'] => 'int',
612             sub {
613             my ($xsub, $seed) = @_;
614             my $seed_len = length($seed);
615             unless ($seed_len == Sodium::FFI::crypto_sign_SEEDBYTES) {
616             croak("Seed length must be crypto_sign_SEEDBYTES in length");
617             }
618             my $pubkey = "\0" x Sodium::FFI::crypto_sign_PUBLICKEYBYTES;
619             my $seckey = "\0" x Sodium::FFI::crypto_sign_SECRETKEYBYTES;
620             my $ret = $xsub->($pubkey, $seckey, $seed);
621             if ($ret != 0) {
622             croak("Some internal error happened");
623             }
624             return ($pubkey, $seckey);
625             }
626             ],
627              
628             # int
629             # crypto_sign(unsigned char *sm, unsigned long long *smlen_p,
630             # const unsigned char *m, unsigned long long mlen,
631             # const unsigned char *sk);
632             'crypto_sign' => [
633             ['string', 'size_t*', 'string', 'size_t', 'string'] => 'int',
634             sub {
635             my ($xsub, $msg, $key) = @_;
636             my $SIZE_MAX = Sodium::FFI::SIZE_MAX;
637             my $msg_len = length($msg);
638             my $key_len = length($key);
639             unless ($key_len == Sodium::FFI::crypto_sign_SECRETKEYBYTES) {
640             croak("Secret Key length must be crypto_sign_SECRETKEYBYTES in length");
641             }
642             if ($SIZE_MAX - $msg_len <= Sodium::FFI::crypto_sign_BYTES) {
643             croak("Arithmetic overflow");
644             }
645             my $real_len = 0;
646             my $signed_len = $msg_len + Sodium::FFI::crypto_sign_BYTES;
647             my $signed = "\0" x $signed_len;
648             my $ret = $xsub->($signed, \$real_len, $msg, $msg_len, $key);
649             if ($ret != 0) {
650             croak("Some internal error happened");
651             }
652             if ($real_len >= $SIZE_MAX || $real_len > $signed_len) {
653             croak("Arithmetic overflow");
654             }
655             return substr($signed, 0, $real_len);
656             }
657             ],
658              
659             # int
660             # crypto_sign_detached(unsigned char *sig, unsigned long long *siglen_p,
661             # const unsigned char *m, unsigned long long mlen,
662             # const unsigned char *sk);
663             'crypto_sign_detached' => [
664             ['string', 'size_t*', 'string', 'size_t', 'string'] => 'int',
665             sub {
666             my ($xsub, $msg, $key) = @_;
667             my $SIZE_MAX = Sodium::FFI::SIZE_MAX;
668             my $msg_len = length($msg);
669             my $key_len = length($key);
670             unless ($key_len == Sodium::FFI::crypto_sign_SECRETKEYBYTES) {
671             croak("Secret Key length must be crypto_sign_SECRETKEYBYTES in length");
672             }
673             my $signature = "\0" x Sodium::FFI::crypto_sign_BYTES;
674             my $real_len = 0;
675             my $ret = $xsub->($signature, \$real_len, $msg, $msg_len, $key);
676             if ($ret != 0) {
677             croak("Signature creation failed.");
678             }
679             if ($real_len <= 0 || $real_len > Sodium::FFI::crypto_sign_BYTES) {
680             croak("Signature size isn't correct.");
681             }
682             return substr($signature, 0, $real_len);
683             }
684             ],
685              
686             # int
687             # crypto_sign_open(unsigned char *m, unsigned long long *mlen_p,
688             # const unsigned char *sm, unsigned long long smlen,
689             # const unsigned char *pk);
690             'crypto_sign_open' => [
691             ['string', 'size_t*', 'string', 'size_t', 'string'] => 'int',
692             sub {
693             my ($xsub, $msg, $key) = @_;
694             my $SIZE_MAX = Sodium::FFI::SIZE_MAX;
695             my $msg_len = length($msg);
696             my $key_len = length($key);
697             unless ($key_len == Sodium::FFI::crypto_sign_PUBLICKEYBYTES) {
698             croak("Public Key length must be crypto_sign_PUBLICKEYBYTES in length");
699             }
700             if ($SIZE_MAX - $msg_len <= Sodium::FFI::crypto_sign_BYTES) {
701             croak("Arithmetic overflow");
702             }
703             my $real_len = 0;
704             my $open = "\0" x $msg_len;
705             my $ret = $xsub->($open, \$real_len, $msg, $msg_len, $key);
706             if ($ret != 0) {
707             croak("Some internal error happened");
708             }
709             if ($real_len >= $SIZE_MAX || $real_len > $msg_len) {
710             croak("Arithmetic overflow");
711             }
712             return substr($open, 0, $real_len);
713             }
714             ],
715              
716             # int
717             # crypto_sign_verify_detached(const unsigned char *sig,
718             # const unsigned char *m,
719             # unsigned long long mlen,
720             # const unsigned char *pk);
721             'crypto_sign_verify_detached' => [
722             ['string', 'string', 'size_t', 'string'] => 'int',
723             sub {
724             my ($xsub, $sig, $msg, $key) = @_;
725             my $SIZE_MAX = Sodium::FFI::SIZE_MAX;
726             my $sig_len = length($sig);
727             my $msg_len = length($msg);
728             my $key_len = length($key);
729             unless ($sig_len == Sodium::FFI::crypto_sign_BYTES) {
730             croak("Signature length must be crypto_sign_BYTES in length");
731             }
732             unless ($key_len == Sodium::FFI::crypto_sign_PUBLICKEYBYTES) {
733             croak("Public Key length must be crypto_sign_PUBLICKEYBYTES in length");
734             }
735             my $ret = $xsub->($sig, $msg, $msg_len, $key);
736             return 1 if ($ret == 0);
737             return 0;
738             }
739             ],
740              
741             # void
742             # randombytes_buf(void * const buf, const size_t size)
743             'randombytes_buf' => [
744             ['string', 'size_t'] => 'void',
745             sub {
746             my ($xsub, $len) = @_;
747             $len //= 0;
748             return '' unless $len > 0;
749             my $buffer = "\0" x ($len + 1);
750             $xsub->($buffer, $len);
751             return substr($buffer, 0, $len);
752             }
753             ],
754              
755             # void
756             # sodium_add(unsigned char *a, const unsigned char *b, const size_t len)
757             'sodium_add' => [
758             ['string', 'string', 'size_t'] => 'void',
759             sub {
760             my ($xsub, $bin_string1, $bin_string2, $len) = @_;
761             return unless $bin_string1 && $bin_string2;
762             $len //= length($bin_string1);
763             my $copy = substr($bin_string1, 0);
764             $xsub->($copy, $bin_string2, $len);
765             return $copy;
766             }
767             ],
768              
769             # int
770             # sodium_base642bin(
771             # unsigned char * const bin, const size_t bin_maxlen,
772             # const char * const b64, const size_t b64_len,
773             # const char * const ignore, size_t * const bin_len,
774             # const char ** const b64_end, const int variant);
775             'sodium_base642bin' => [
776             ['string', 'size_t', 'string', 'size_t', 'string', 'size_t*', 'string*', 'int'] => 'int',
777             sub {
778             my ($xsub, $b64, $variant) = @_;
779             my $b64_len = length($b64);
780             $variant //= Sodium::FFI::sodium_base64_VARIANT_ORIGINAL;
781              
782             my $bin_max_len = $b64_len / 4 * 3 + 2;
783             my $bin = "\0" x $bin_max_len;
784             my $bin_real_len = 0;
785              
786             my $ignore = undef;
787             my $end = undef;
788             $xsub->($bin, $bin_max_len, $b64, $b64_len, $ignore, \$bin_real_len, \$end, $variant);
789             # $bin =~ s/\0//g;
790             return substr($bin, 0, $bin_real_len);
791             }
792             ],
793              
794             # char *
795             # sodium_bin2base64(char * const b64, const size_t b64_maxlen,
796             # const unsigned char * const bin, const size_t bin_len,
797             # const int variant);
798             'sodium_bin2base64' => [
799             ['string', 'size_t', 'string', 'size_t', 'int'] => 'string',
800             sub {
801             my ($xsub, $bin, $variant) = @_;
802             my $bin_len = length($bin);
803             $variant //= Sodium::FFI::sodium_base64_VARIANT_ORIGINAL;
804              
805             my $b64_len = Sodium::FFI::sodium_base64_encoded_len($bin_len, $variant);
806             my $b64 = "\0" x $b64_len;
807              
808             $xsub->($b64, $b64_len, $bin, $bin_len, $variant);
809             $b64 =~ s/\0//g;
810             return $b64;
811             }
812             ],
813              
814             # char *
815             # sodium_bin2hex(char *const hex, const size_t hex_maxlen,
816             # const unsigned char *const bin, const size_t bin_len)
817             'sodium_bin2hex' => [
818             ['string', 'size_t', 'string', 'size_t'] => 'string',
819             sub {
820             my ($xsub, $bin_string) = @_;
821             return unless $bin_string;
822             my $bin_len = length($bin_string);
823             my $hex_max = $bin_len * 2;
824              
825             my $buffer = "\0" x ($hex_max + 1);
826             $xsub->($buffer, $hex_max + 1, $bin_string, $bin_len);
827             return substr($buffer, 0, $hex_max);
828             }
829             ],
830              
831             # int
832             # sodium_hex2bin(
833             # unsigned char *const bin, const size_t bin_maxlen,
834             # const char *const hex, const size_t hex_len,
835             # const char *const ignore, size_t *const bin_len, const char **const hex_end)
836             'sodium_hex2bin' => [
837             ['string', 'size_t', 'string', 'size_t', 'string', 'size_t *', 'string *'] => 'int',
838             sub {
839             my ($xsub, $hex_string, %params) = @_;
840             $hex_string //= '';
841             my $hex_len = length($hex_string);
842              
843             # these two are mostly always void/undef
844             my $ignore = $params{ignore};
845             my $hex_end = $params{hex_end};
846              
847             my $bin_max_len = $params{max_len} // 0;
848             if ($bin_max_len <= 0) {
849             $bin_max_len = $hex_len;
850             $bin_max_len = int($hex_len / 2) unless $ignore;
851             }
852             my $buffer = "\0" x ($hex_len + 1);
853             my $bin_len = 0;
854              
855             my $ret = $xsub->($buffer, $hex_len, $hex_string, $hex_len, $ignore, \$bin_len, \$hex_end);
856             unless ($ret == 0) {
857             croak("sodium_hex2bin failed with: $ret");
858             }
859              
860             return substr($buffer, 0, $bin_max_len) if $bin_max_len < $bin_len;
861             return substr($buffer, 0, $bin_len);
862             }
863             ],
864              
865             # void
866             # sodium_increment(unsigned char *n, const size_t nlen)
867             'sodium_increment' => [
868             ['string', 'size_t'] => 'void',
869             sub {
870             my ($xsub, $bin_string, $len) = @_;
871             return unless $bin_string;
872             $len //= length($bin_string);
873             my $copy = substr($bin_string, 0);
874             $xsub->($copy, $len);
875             return $copy;
876             }
877             ],
878              
879             # int
880             # sodium_is_zero(const unsigned char *n, const size_t nlen)
881             'sodium_is_zero' => [
882             ['string', 'size_t'] => 'int',
883             sub {
884             my ($xsub, $bin_string, $len) = @_;
885             $len //= length($bin_string);
886             return $xsub->($bin_string, $len);
887             }
888             ],
889              
890             # int
891             # sodium_memcmp(const void * const b1_, const void * const b2_, size_t len);
892             'sodium_memcmp' => [
893             ['string', 'string', 'size_t'] => 'int',
894             sub {
895             my ($xsub, $string_x, $string_y, $len) = @_;
896             return unless $string_x;
897             $len //= length($string_x);
898             return $xsub->($string_x, $string_y, $len);
899             }
900             ],
901              
902             );
903              
904             our %maybe_function = (
905             # void
906             # randombytes_buf_deterministic(void * const buf, const size_t size,
907             # const unsigned char seed[randombytes_SEEDBYTES]);
908             'randombytes_buf_deterministic' => {
909             added => [1,0,12],
910             ffi => [
911             ['string', 'size_t', 'string'] => 'void',
912             sub {
913             my ($xsub, $len, $seed) = @_;
914             $len //= 0;
915             return '' unless $len > 0;
916             my $buffer = "\0" x ($len + 1);
917             $xsub->($buffer, $len, $seed);
918             return substr($buffer, 0, $len);
919             }
920             ],
921             fallback => sub { croak("randombytes_buf_deterministic not implemented until libsodium v1.0.12"); },
922             },
923              
924              
925             # int
926             # sodium_compare(const unsigned char *b1_,
927             # const unsigned char *b2_, size_t len)
928             'sodium_compare' => {
929             added => [1,0,4],
930             ffi => [
931             ['string', 'string', 'size_t'] => 'int',
932             sub {
933             my ($xsub, $bin_string1, $bin_string2, $len) = @_;
934             return unless $bin_string1 && $bin_string2;
935             $len //= length($bin_string1);
936             my $int = $xsub->($bin_string1, $bin_string2, $len);
937             return $int;
938             }
939             ],
940             fallback => sub { croak("sodium_compare not implemented until libsodium v1.0.4"); },
941             },
942              
943             # int
944             # sodium_library_minimal(void)
945             'sodium_library_minimal' => {
946             added => [1,0,12],
947             ffi => [[], 'int'],
948             fallback => sub { croak("sodium_library_minimal not implemented until libsodium v1.0.12"); },
949             },
950              
951             # int
952             # sodium_pad(size_t *padded_buflen_p, unsigned char *buf,
953             # size_t unpadded_buflen, size_t blocksize, size_t max_buflen)
954             'sodium_pad' => {
955             added => [1,0,14],
956             ffi => [
957             ['size_t', 'string', 'size_t', 'size_t', 'size_t'] => 'int',
958             sub {
959             my ($xsub, $unpadded, $block_size) = @_;
960             my $SIZE_MAX = Sodium::FFI::SIZE_MAX;
961             my $unpadded_len = length($unpadded);
962             $block_size //= 16;
963             $block_size = 16 if $block_size < 0;
964              
965             my $xpadlen = $block_size - 1;
966             if (($block_size & ($block_size - 1)) == 0) {
967             $xpadlen -= $unpadded_len & ($block_size - 1);
968             } else {
969             $xpadlen -= $unpadded_len % $block_size;
970             }
971             if ($SIZE_MAX - $unpadded_len <= $xpadlen) {
972             croak("Input is too large.");
973             }
974              
975             my $xpadded_len = $unpadded_len + $xpadlen;
976             my $padded = "\0" x ($xpadded_len + 1);
977             if ($unpadded_len > 0) {
978             my $st = 1;
979             my $i = 0;
980             my $k = $unpadded_len;
981             for my $j (0..$xpadded_len) {
982             substr($padded, $j, 1) = substr($unpadded, $i, 1);
983             $k -= $st;
984             $st = (~((((($k >> 48) | ($k >> 32) | ($k >> 16) | $k) & 0xffff) - 1) >> 16)) & 1;
985             $i += $st;
986             }
987             }
988             my $int = $xsub->(undef, $padded, $unpadded_len, $block_size, $xpadded_len + 1);
989             return $padded;
990             }
991             ],
992             fallback => sub { croak("sodium_pad not implemented until libsodium v1.0.14"); },
993             },
994              
995             # void
996             # sodium_sub(unsigned char *a, const unsigned char *b, const size_t len);
997             'sodium_sub' => {
998             added => [1,0,17],
999             ffi => [
1000             ['string', 'string', 'size_t'] => 'void',
1001             sub {
1002             my ($xsub, $bin_string1, $bin_string2, $len) = @_;
1003             return unless $bin_string1 && $bin_string2;
1004             $len //= length($bin_string1);
1005             my $copy = substr($bin_string1, 0);
1006             $xsub->($copy, $bin_string2, $len);
1007             return $copy;
1008             }
1009             ],
1010             fallback => sub { croak("sodium_sub not implemented until libsodium v1.0.17"); },
1011             },
1012              
1013             # int
1014             # sodium_unpad(size_t *unpadded_buflen_p, const unsigned char *buf,
1015             # size_t padded_buflen, size_t blocksize)
1016             'sodium_unpad' => {
1017             added => [1,0,14],
1018             ffi => [
1019             ['size_t*', 'string', 'size_t', 'size_t'] => 'int',
1020             sub {
1021             my ($xsub, $padded, $block_size) = @_;
1022             $block_size //= 16;
1023             $block_size = 16 if $block_size < 0;
1024              
1025             my $SIZE_MAX = Sodium::FFI::SIZE_MAX;
1026             my $padded_len = length($padded);
1027             if ($padded_len < $block_size) {
1028             croak("Invalid padding.");
1029             }
1030             my $unpadded_len = 0;
1031             my $int = $xsub->(\$unpadded_len, $padded, $padded_len, $block_size);
1032             return substr($padded, 0, $unpadded_len);
1033             }
1034             ],
1035             fallback => sub { croak("sodium_unpad not implemented until libsodium v1.0.14"); },
1036             },
1037             );
1038              
1039             foreach my $func (keys %function) {
1040             $ffi->attach($func, @{$function{$func}});
1041             push(@EXPORT_OK, $func) unless ref($func);
1042             }
1043              
1044             foreach my $func (keys %maybe_function) {
1045             my $href = $maybe_function{$func};
1046             if (_version_or_better(@{$href->{added}})) {
1047             $ffi->attach($func, @{$href->{ffi}});
1048             }
1049             else {
1050             # monkey patch in the subref
1051 9     9   153526 no strict 'refs';
  9         28  
  9         316  
1052 9     9   68 no warnings 'redefine';
  9         34  
  9         3471  
1053             my $pkg = __PACKAGE__;
1054             *{"${pkg}::$func"} = set_subname("${pkg}::$func", $href->{fallback});
1055             }
1056             push @EXPORT_OK, $func;
1057             }
1058              
1059             sub _version_or_better {
1060 59     59   2641 my ($maj, $min, $pat) = @_;
1061 59   50     174 $maj //= 0;
1062 59   50     126 $min //= 0;
1063 59   50     104 $pat //= 0;
1064 59         119 foreach my $partial ($maj, $min, $pat) {
1065 177 50       514 if ($partial =~ /[^0-9]/) {
1066 0         0 croak("_version_or_better requires 1 - 3 integers representing major, minor and patch numbers");
1067             }
1068             }
1069             # if no number was passed in, then the current version is higher
1070 59 0 33     146 return 1 unless ($maj || $min || $pat);
      33        
1071              
1072 59         414 my $version_string = Sodium::FFI::sodium_version_string();
1073 59 50       139 croak("No version string") unless $version_string;
1074 59         196 my ($smaj, $smin, $spatch) = split(/\./, $version_string);
1075 59 50       170 return 0 if $smaj < $maj; # full version behind of requested
1076 59 50       142 return 1 if $smaj > $maj; # full version ahead of requested
1077             # now we should be matching major versions
1078 59 50       224 return 1 unless $min; # if we were only given major, move on
1079 0 0         return 0 if $smin < $min; # same major, lower minor
1080 0 0         return 1 if $smaj > $min; # same major, higher minor
1081             # now we should be matching major and minor, check patch
1082 0 0         return 1 unless $pat; # move on if we were given maj, min only
1083 0 0         return 0 if $spatch < $pat;
1084 0           return 1;
1085             }
1086              
1087             1;
1088              
1089             __END__