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   949837 use strict;
  9         110  
  9         280  
3 9     9   50 use warnings;
  9         15  
  9         408  
4              
5             our $VERSION = '0.008';
6              
7 9     9   51 use Carp qw(croak);
  9         17  
  9         473  
8 9     9   53 use Exporter qw(import);
  9         16  
  9         349  
9              
10 9     9   3983 use Alien::Sodium;
  9         454553  
  9         89  
11 9     9   226469 use FFI::Platypus;
  9         66571  
  9         379  
12 9     9   71 use Path::Tiny qw(path);
  9         22  
  9         637  
13 9     9   4438 use Sub::Util qw(set_subname);
  9         3017  
  9         1302  
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   127 $ffi = FFI::Platypus->new(api => 1, lib => Alien::Sodium->dynamic_libs);
44 9         150349 $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 3356 my $len = Sodium::FFI::crypto_aead_chacha20poly1305_IETF_KEYBYTES;
61 2         8 return Sodium::FFI::randombytes_buf($len);
62             }
63              
64             sub crypto_aead_chacha20poly1305_keygen {
65 2     2 1 2371 my $len = Sodium::FFI::crypto_aead_chacha20poly1305_KEYBYTES;
66 2         9 return Sodium::FFI::randombytes_buf($len);
67             }
68              
69             sub crypto_auth_keygen {
70 1     1 1 1015 my $len = Sodium::FFI::crypto_auth_KEYBYTES;
71 1         6 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_seed_keypair(unsigned char *pk, unsigned char *sk, const unsigned char *seed);
495             'crypto_box_seed_keypair' => [
496             ['string', 'string', 'string'] => 'int',
497             sub {
498             my ($xsub, $seed) = @_;
499             my $seed_len = length($seed);
500             unless ($seed_len == Sodium::FFI::crypto_box_SEEDBYTES) {
501             croak("Seed length must be crypto_box_SEEDBYTES in length");
502             }
503             my $pubkey = "\0" x Sodium::FFI::crypto_box_PUBLICKEYBYTES;
504             my $seckey = "\0" x Sodium::FFI::crypto_box_SECRETKEYBYTES;
505             my $ret = $xsub->($pubkey, $seckey, $seed);
506             if ($ret != 0) {
507             croak("Some internal error happened");
508             }
509             return ($pubkey, $seckey);
510             }
511             ],
512              
513             # int
514             # crypto_scalarmult_base(unsigned char *q, const unsigned char *n);
515             'crypto_scalarmult_base' => [
516             ['string', 'string'] => 'int',
517             sub {
518             my ($xsub, $secret_key) = @_;
519             my $sk_len = length($secret_key);
520             unless ($sk_len == Sodium::FFI::crypto_box_SECRETKEYBYTES) {
521             croak("Secret Key length must be crypto_box_SECRETKEYBYTES in length");
522             }
523             my $pubkey = "\0" x Sodium::FFI::crypto_box_PUBLICKEYBYTES;
524             my $ret = $xsub->($pubkey, $secret_key);
525             if ($ret != 0) {
526             croak("Some internal error happened");
527             }
528             return $pubkey;
529             }
530             ],
531              
532             # int
533             # crypto_sign_keypair(unsigned char *pk, unsigned char *sk);
534             'crypto_sign_keypair' => [
535             ['string', 'string'] => 'int',
536             sub {
537             my ($xsub) = @_;
538             my $pubkey = "\0" x Sodium::FFI::crypto_sign_PUBLICKEYBYTES;
539             my $seckey = "\0" x Sodium::FFI::crypto_sign_SECRETKEYBYTES;
540             my $ret = $xsub->($pubkey, $seckey);
541             if ($ret != 0) {
542             croak("Some internal error happened");
543             }
544             return ($pubkey, $seckey);
545             }
546             ],
547              
548             # int
549             # crypto_sign_seed_keypair(unsigned char *pk, unsigned char *sk, const unsigned char *seed);
550             'crypto_sign_seed_keypair' => [
551             ['string', 'string', 'string'] => 'int',
552             sub {
553             my ($xsub, $seed) = @_;
554             my $seed_len = length($seed);
555             unless ($seed_len == Sodium::FFI::crypto_sign_SEEDBYTES) {
556             croak("Seed length must be crypto_sign_SEEDBYTES in length");
557             }
558             my $pubkey = "\0" x Sodium::FFI::crypto_sign_PUBLICKEYBYTES;
559             my $seckey = "\0" x Sodium::FFI::crypto_sign_SECRETKEYBYTES;
560             my $ret = $xsub->($pubkey, $seckey, $seed);
561             if ($ret != 0) {
562             croak("Some internal error happened");
563             }
564             return ($pubkey, $seckey);
565             }
566             ],
567              
568             # int
569             # crypto_sign(unsigned char *sm, unsigned long long *smlen_p,
570             # const unsigned char *m, unsigned long long mlen,
571             # const unsigned char *sk);
572             'crypto_sign' => [
573             ['string', 'size_t*', 'string', 'size_t', 'string'] => 'int',
574             sub {
575             my ($xsub, $msg, $key) = @_;
576             my $SIZE_MAX = Sodium::FFI::SIZE_MAX;
577             my $msg_len = length($msg);
578             my $key_len = length($key);
579             unless ($key_len == Sodium::FFI::crypto_sign_SECRETKEYBYTES) {
580             croak("Secret Key length must be crypto_sign_SECRETKEYBYTES in length");
581             }
582             if ($SIZE_MAX - $msg_len <= Sodium::FFI::crypto_sign_BYTES) {
583             croak("Arithmetic overflow");
584             }
585             my $real_len = 0;
586             my $signed_len = $msg_len + Sodium::FFI::crypto_sign_BYTES;
587             my $signed = "\0" x $signed_len;
588             my $ret = $xsub->($signed, \$real_len, $msg, $msg_len, $key);
589             if ($ret != 0) {
590             croak("Some internal error happened");
591             }
592             if ($real_len >= $SIZE_MAX || $real_len > $signed_len) {
593             croak("Arithmetic overflow");
594             }
595             return substr($signed, 0, $real_len);
596             }
597             ],
598              
599             # int
600             # crypto_sign_detached(unsigned char *sig, unsigned long long *siglen_p,
601             # const unsigned char *m, unsigned long long mlen,
602             # const unsigned char *sk);
603             'crypto_sign_detached' => [
604             ['string', 'size_t*', 'string', 'size_t', 'string'] => 'int',
605             sub {
606             my ($xsub, $msg, $key) = @_;
607             my $SIZE_MAX = Sodium::FFI::SIZE_MAX;
608             my $msg_len = length($msg);
609             my $key_len = length($key);
610             unless ($key_len == Sodium::FFI::crypto_sign_SECRETKEYBYTES) {
611             croak("Secret Key length must be crypto_sign_SECRETKEYBYTES in length");
612             }
613             my $signature = "\0" x Sodium::FFI::crypto_sign_BYTES;
614             my $real_len = 0;
615             my $ret = $xsub->($signature, \$real_len, $msg, $msg_len, $key);
616             if ($ret != 0) {
617             croak("Signature creation failed.");
618             }
619             if ($real_len <= 0 || $real_len > Sodium::FFI::crypto_sign_BYTES) {
620             croak("Signature size isn't correct.");
621             }
622             return substr($signature, 0, $real_len);
623             }
624             ],
625              
626             # int
627             # crypto_sign_open(unsigned char *m, unsigned long long *mlen_p,
628             # const unsigned char *sm, unsigned long long smlen,
629             # const unsigned char *pk);
630             'crypto_sign_open' => [
631             ['string', 'size_t*', 'string', 'size_t', 'string'] => 'int',
632             sub {
633             my ($xsub, $msg, $key) = @_;
634             my $SIZE_MAX = Sodium::FFI::SIZE_MAX;
635             my $msg_len = length($msg);
636             my $key_len = length($key);
637             unless ($key_len == Sodium::FFI::crypto_sign_PUBLICKEYBYTES) {
638             croak("Public Key length must be crypto_sign_PUBLICKEYBYTES in length");
639             }
640             if ($SIZE_MAX - $msg_len <= Sodium::FFI::crypto_sign_BYTES) {
641             croak("Arithmetic overflow");
642             }
643             my $real_len = 0;
644             my $open = "\0" x $msg_len;
645             my $ret = $xsub->($open, \$real_len, $msg, $msg_len, $key);
646             if ($ret != 0) {
647             croak("Some internal error happened");
648             }
649             if ($real_len >= $SIZE_MAX || $real_len > $msg_len) {
650             croak("Arithmetic overflow");
651             }
652             return substr($open, 0, $real_len);
653             }
654             ],
655              
656             # int
657             # crypto_sign_verify_detached(const unsigned char *sig,
658             # const unsigned char *m,
659             # unsigned long long mlen,
660             # const unsigned char *pk);
661             'crypto_sign_verify_detached' => [
662             ['string', 'string', 'size_t', 'string'] => 'int',
663             sub {
664             my ($xsub, $sig, $msg, $key) = @_;
665             my $SIZE_MAX = Sodium::FFI::SIZE_MAX;
666             my $sig_len = length($sig);
667             my $msg_len = length($msg);
668             my $key_len = length($key);
669             unless ($sig_len == Sodium::FFI::crypto_sign_BYTES) {
670             croak("Signature length must be crypto_sign_BYTES in length");
671             }
672             unless ($key_len == Sodium::FFI::crypto_sign_PUBLICKEYBYTES) {
673             croak("Public Key length must be crypto_sign_PUBLICKEYBYTES in length");
674             }
675             my $ret = $xsub->($sig, $msg, $msg_len, $key);
676             return 1 if ($ret == 0);
677             return 0;
678             }
679             ],
680              
681             # void
682             # randombytes_buf(void * const buf, const size_t size)
683             'randombytes_buf' => [
684             ['string', 'size_t'] => 'void',
685             sub {
686             my ($xsub, $len) = @_;
687             $len //= 0;
688             return '' unless $len > 0;
689             my $buffer = "\0" x ($len + 1);
690             $xsub->($buffer, $len);
691             return substr($buffer, 0, $len);
692             }
693             ],
694              
695             # void
696             # sodium_add(unsigned char *a, const unsigned char *b, const size_t len)
697             'sodium_add' => [
698             ['string', 'string', 'size_t'] => 'void',
699             sub {
700             my ($xsub, $bin_string1, $bin_string2, $len) = @_;
701             return unless $bin_string1 && $bin_string2;
702             $len //= length($bin_string1);
703             my $copy = substr($bin_string1, 0);
704             $xsub->($copy, $bin_string2, $len);
705             return $copy;
706             }
707             ],
708              
709             # int
710             # sodium_base642bin(
711             # unsigned char * const bin, const size_t bin_maxlen,
712             # const char * const b64, const size_t b64_len,
713             # const char * const ignore, size_t * const bin_len,
714             # const char ** const b64_end, const int variant);
715             'sodium_base642bin' => [
716             ['string', 'size_t', 'string', 'size_t', 'string', 'size_t*', 'string*', 'int'] => 'int',
717             sub {
718             my ($xsub, $b64, $variant) = @_;
719             my $b64_len = length($b64);
720             $variant //= Sodium::FFI::sodium_base64_VARIANT_ORIGINAL;
721              
722             my $bin_max_len = $b64_len / 4 * 3 + 2;
723             my $bin = "\0" x $bin_max_len;
724             my $bin_real_len = 0;
725              
726             my $ignore = undef;
727             my $end = undef;
728             $xsub->($bin, $bin_max_len, $b64, $b64_len, $ignore, \$bin_real_len, \$end, $variant);
729             # $bin =~ s/\0//g;
730             return substr($bin, 0, $bin_real_len);
731             }
732             ],
733              
734             # char *
735             # sodium_bin2base64(char * const b64, const size_t b64_maxlen,
736             # const unsigned char * const bin, const size_t bin_len,
737             # const int variant);
738             'sodium_bin2base64' => [
739             ['string', 'size_t', 'string', 'size_t', 'int'] => 'string',
740             sub {
741             my ($xsub, $bin, $variant) = @_;
742             my $bin_len = length($bin);
743             $variant //= Sodium::FFI::sodium_base64_VARIANT_ORIGINAL;
744              
745             my $b64_len = Sodium::FFI::sodium_base64_encoded_len($bin_len, $variant);
746             my $b64 = "\0" x $b64_len;
747              
748             $xsub->($b64, $b64_len, $bin, $bin_len, $variant);
749             $b64 =~ s/\0//g;
750             return $b64;
751             }
752             ],
753              
754             # char *
755             # sodium_bin2hex(char *const hex, const size_t hex_maxlen,
756             # const unsigned char *const bin, const size_t bin_len)
757             'sodium_bin2hex' => [
758             ['string', 'size_t', 'string', 'size_t'] => 'string',
759             sub {
760             my ($xsub, $bin_string) = @_;
761             return unless $bin_string;
762             my $bin_len = length($bin_string);
763             my $hex_max = $bin_len * 2;
764              
765             my $buffer = "\0" x ($hex_max + 1);
766             $xsub->($buffer, $hex_max + 1, $bin_string, $bin_len);
767             return substr($buffer, 0, $hex_max);
768             }
769             ],
770              
771             # int
772             # sodium_hex2bin(
773             # unsigned char *const bin, const size_t bin_maxlen,
774             # const char *const hex, const size_t hex_len,
775             # const char *const ignore, size_t *const bin_len, const char **const hex_end)
776             'sodium_hex2bin' => [
777             ['string', 'size_t', 'string', 'size_t', 'string', 'size_t *', 'string *'] => 'int',
778             sub {
779             my ($xsub, $hex_string, %params) = @_;
780             $hex_string //= '';
781             my $hex_len = length($hex_string);
782              
783             # these two are mostly always void/undef
784             my $ignore = $params{ignore};
785             my $hex_end = $params{hex_end};
786              
787             my $bin_max_len = $params{max_len} // 0;
788             if ($bin_max_len <= 0) {
789             $bin_max_len = $hex_len;
790             $bin_max_len = int($hex_len / 2) unless $ignore;
791             }
792             my $buffer = "\0" x ($hex_len + 1);
793             my $bin_len = 0;
794              
795             my $ret = $xsub->($buffer, $hex_len, $hex_string, $hex_len, $ignore, \$bin_len, \$hex_end);
796             unless ($ret == 0) {
797             croak("sodium_hex2bin failed with: $ret");
798             }
799              
800             return substr($buffer, 0, $bin_max_len) if $bin_max_len < $bin_len;
801             return substr($buffer, 0, $bin_len);
802             }
803             ],
804              
805             # void
806             # sodium_increment(unsigned char *n, const size_t nlen)
807             'sodium_increment' => [
808             ['string', 'size_t'] => 'void',
809             sub {
810             my ($xsub, $bin_string, $len) = @_;
811             return unless $bin_string;
812             $len //= length($bin_string);
813             my $copy = substr($bin_string, 0);
814             $xsub->($copy, $len);
815             return $copy;
816             }
817             ],
818              
819             # int
820             # sodium_is_zero(const unsigned char *n, const size_t nlen)
821             'sodium_is_zero' => [
822             ['string', 'size_t'] => 'int',
823             sub {
824             my ($xsub, $bin_string, $len) = @_;
825             $len //= length($bin_string);
826             return $xsub->($bin_string, $len);
827             }
828             ],
829              
830             # int
831             # sodium_memcmp(const void * const b1_, const void * const b2_, size_t len);
832             'sodium_memcmp' => [
833             ['string', 'string', 'size_t'] => 'int',
834             sub {
835             my ($xsub, $string_x, $string_y, $len) = @_;
836             return unless $string_x;
837             $len //= length($string_x);
838             return $xsub->($string_x, $string_y, $len);
839             }
840             ],
841              
842             );
843              
844             our %maybe_function = (
845             # void
846             # randombytes_buf_deterministic(void * const buf, const size_t size,
847             # const unsigned char seed[randombytes_SEEDBYTES]);
848             'randombytes_buf_deterministic' => {
849             added => [1,0,12],
850             ffi => [
851             ['string', 'size_t', 'string'] => 'void',
852             sub {
853             my ($xsub, $len, $seed) = @_;
854             $len //= 0;
855             return '' unless $len > 0;
856             my $buffer = "\0" x ($len + 1);
857             $xsub->($buffer, $len, $seed);
858             return substr($buffer, 0, $len);
859             }
860             ],
861             fallback => sub { croak("randombytes_buf_deterministic not implemented until libsodium v1.0.12"); },
862             },
863              
864              
865             # int
866             # sodium_compare(const unsigned char *b1_,
867             # const unsigned char *b2_, size_t len)
868             'sodium_compare' => {
869             added => [1,0,4],
870             ffi => [
871             ['string', 'string', 'size_t'] => 'int',
872             sub {
873             my ($xsub, $bin_string1, $bin_string2, $len) = @_;
874             return unless $bin_string1 && $bin_string2;
875             $len //= length($bin_string1);
876             my $int = $xsub->($bin_string1, $bin_string2, $len);
877             return $int;
878             }
879             ],
880             fallback => sub { croak("sodium_compare not implemented until libsodium v1.0.4"); },
881             },
882              
883             # int
884             # sodium_library_minimal(void)
885             'sodium_library_minimal' => {
886             added => [1,0,12],
887             ffi => [[], 'int'],
888             fallback => sub { croak("sodium_library_minimal not implemented until libsodium v1.0.12"); },
889             },
890              
891             # int
892             # sodium_pad(size_t *padded_buflen_p, unsigned char *buf,
893             # size_t unpadded_buflen, size_t blocksize, size_t max_buflen)
894             'sodium_pad' => {
895             added => [1,0,14],
896             ffi => [
897             ['size_t', 'string', 'size_t', 'size_t', 'size_t'] => 'int',
898             sub {
899             my ($xsub, $unpadded, $block_size) = @_;
900             my $SIZE_MAX = Sodium::FFI::SIZE_MAX;
901             my $unpadded_len = length($unpadded);
902             $block_size //= 16;
903             $block_size = 16 if $block_size < 0;
904              
905             my $xpadlen = $block_size - 1;
906             if (($block_size & ($block_size - 1)) == 0) {
907             $xpadlen -= $unpadded_len & ($block_size - 1);
908             } else {
909             $xpadlen -= $unpadded_len % $block_size;
910             }
911             if ($SIZE_MAX - $unpadded_len <= $xpadlen) {
912             croak("Input is too large.");
913             }
914              
915             my $xpadded_len = $unpadded_len + $xpadlen;
916             my $padded = "\0" x ($xpadded_len + 1);
917             if ($unpadded_len > 0) {
918             my $st = 1;
919             my $i = 0;
920             my $k = $unpadded_len;
921             for my $j (0..$xpadded_len) {
922             substr($padded, $j, 1) = substr($unpadded, $i, 1);
923             $k -= $st;
924             $st = (~((((($k >> 48) | ($k >> 32) | ($k >> 16) | $k) & 0xffff) - 1) >> 16)) & 1;
925             $i += $st;
926             }
927             }
928             my $int = $xsub->(undef, $padded, $unpadded_len, $block_size, $xpadded_len + 1);
929             return $padded;
930             }
931             ],
932             fallback => sub { croak("sodium_pad not implemented until libsodium v1.0.14"); },
933             },
934              
935             # void
936             # sodium_sub(unsigned char *a, const unsigned char *b, const size_t len);
937             'sodium_sub' => {
938             added => [1,0,17],
939             ffi => [
940             ['string', 'string', 'size_t'] => 'void',
941             sub {
942             my ($xsub, $bin_string1, $bin_string2, $len) = @_;
943             return unless $bin_string1 && $bin_string2;
944             $len //= length($bin_string1);
945             my $copy = substr($bin_string1, 0);
946             $xsub->($copy, $bin_string2, $len);
947             return $copy;
948             }
949             ],
950             fallback => sub { croak("sodium_sub not implemented until libsodium v1.0.17"); },
951             },
952              
953             # int
954             # sodium_unpad(size_t *unpadded_buflen_p, const unsigned char *buf,
955             # size_t padded_buflen, size_t blocksize)
956             'sodium_unpad' => {
957             added => [1,0,14],
958             ffi => [
959             ['size_t*', 'string', 'size_t', 'size_t'] => 'int',
960             sub {
961             my ($xsub, $padded, $block_size) = @_;
962             $block_size //= 16;
963             $block_size = 16 if $block_size < 0;
964              
965             my $SIZE_MAX = Sodium::FFI::SIZE_MAX;
966             my $padded_len = length($padded);
967             if ($padded_len < $block_size) {
968             croak("Invalid padding.");
969             }
970             my $unpadded_len = 0;
971             my $int = $xsub->(\$unpadded_len, $padded, $padded_len, $block_size);
972             return substr($padded, 0, $unpadded_len);
973             }
974             ],
975             fallback => sub { croak("sodium_unpad not implemented until libsodium v1.0.14"); },
976             },
977             );
978              
979             foreach my $func (keys %function) {
980             $ffi->attach($func, @{$function{$func}});
981             push(@EXPORT_OK, $func) unless ref($func);
982             }
983              
984             foreach my $func (keys %maybe_function) {
985             my $href = $maybe_function{$func};
986             if (_version_or_better(@{$href->{added}})) {
987             $ffi->attach($func, @{$href->{ffi}});
988             }
989             else {
990             # monkey patch in the subref
991 9     9   154196 no strict 'refs';
  9         28  
  9         437  
992 9     9   63 no warnings 'redefine';
  9         23  
  9         3366  
993             my $pkg = __PACKAGE__;
994             *{"${pkg}::$func"} = set_subname("${pkg}::$func", $href->{fallback});
995             }
996             push @EXPORT_OK, $func;
997             }
998              
999             sub _version_or_better {
1000 59     59   2664 my ($maj, $min, $pat) = @_;
1001 59   50     163 $maj //= 0;
1002 59   50     113 $min //= 0;
1003 59   50     149 $pat //= 0;
1004 59         103 foreach my $partial ($maj, $min, $pat) {
1005 177 50       415 if ($partial =~ /[^0-9]/) {
1006 0         0 croak("_version_or_better requires 1 - 3 integers representing major, minor and patch numbers");
1007             }
1008             }
1009             # if no number was passed in, then the current version is higher
1010 59 0 33     141 return 1 unless ($maj || $min || $pat);
      33        
1011              
1012 59         425 my $version_string = Sodium::FFI::sodium_version_string();
1013 59 50       148 croak("No version string") unless $version_string;
1014 59         190 my ($smaj, $smin, $spatch) = split(/\./, $version_string);
1015 59 50       156 return 0 if $smaj < $maj; # full version behind of requested
1016 59 50       158 return 1 if $smaj > $maj; # full version ahead of requested
1017             # now we should be matching major versions
1018 59 50       244 return 1 unless $min; # if we were only given major, move on
1019 0 0         return 0 if $smin < $min; # same major, lower minor
1020 0 0         return 1 if $smaj > $min; # same major, higher minor
1021             # now we should be matching major and minor, check patch
1022 0 0         return 1 unless $pat; # move on if we were given maj, min only
1023 0 0         return 0 if $spatch < $pat;
1024 0           return 1;
1025             }
1026              
1027             1;
1028              
1029             __END__