File Coverage

blib/lib/Crypt/URandom.pm
Criterion Covered Total %
statement 108 108 100.0
branch 30 30 100.0
condition 9 9 100.0
subroutine 21 21 100.0
pod 2 2 100.0
total 170 170 100.0


line stmt bran cond sub pod time code
1             package Crypt::URandom;
2              
3 14     14   3775513 use warnings;
  14         36  
  14         1005  
4 14     14   168 use strict;
  14         28  
  14         5050  
5 14     14   106 use Carp();
  14         28  
  14         349  
6 14     14   4777 use English qw( -no_match_vars );
  14         24116  
  14         122  
7 14     14   6445 use Exporter();
  14         32  
  14         2083  
8             *import = \&Exporter::import;
9             our @EXPORT_OK = qw(
10             urandom
11             urandom_ub
12             getrandom
13             );
14              
15             our %EXPORT_TAGS = ( 'all' => \@EXPORT_OK, );
16              
17             our @CARP_NOT = ('Crypt::URandom');
18              
19             BEGIN {
20 14     14   59 our $VERSION = '0.55';
21             eval {
22 14         113 require XSLoader;
23              
24 14         11321 XSLoader::load( __PACKAGE__, $VERSION );
25 14 100       36 } or do {
26             };
27             }
28              
29             ## no critic (ProhibitConstantPragma)
30             # using constant for the speed benefit of constant-folding of values
31              
32 14     14   130 use constant CRYPT_SILENT => 64; # hex 40
  14         136  
  14         1539  
33 14     14   115 use constant PROV_RSA_FULL => 1;
  14         67  
  14         1063  
34 14     14   98 use constant VERIFY_CONTEXT => 4_026_531_840; # hex 'F0000000'
  14         28  
  14         830  
35 14     14   90 use constant W2K_MAJOR_VERSION => 5;
  14         25  
  14         730  
36 14     14   72 use constant W2K_MINOR_VERSION => 0;
  14         21  
  14         883  
37 14     14   86 use constant OS_FREEBSD => $OSNAME eq 'freebsd';
  14         21  
  14         930  
38 14     14   94 use constant OS_WIN32 => $OSNAME eq 'MSWin32';
  14         30  
  14         1428  
39 14         65 use constant PATH => do {
40 8         45 my $path = '/dev/urandom';
41 8         25 if ( OS_FREEBSD() ) {
42             $path = '/dev/random'; # FreeBSD's /dev/random is non-blocking
43             }
44 8         829 $path;
45 14     14   83 };
  14         32  
46 14         80 use constant GETRANDOM_AVAILABLE => do {
47 8         17 my $result = 0;
48             eval {
49 8         37 my $correct_length = 2;
50 8         217 $result = getrandom($correct_length);
51 8 100       20 } or do {
52 1         3 $result = undef;
53             };
54 8         513 $result;
55 14     14   85 };
  14         50  
56 14     14   112 use constant SYSTEM_CALL_FAILED => -1;
  14         23  
  14         24360  
57              
58             ## use critic
59              
60             my $_initialised;
61             my $_context;
62             my $_cryptgenrandom;
63             my $_rtlgenrand;
64             my $_urandom_handle;
65              
66             sub _init {
67 25 100 100 25   403 if ( !( ( defined $_initialised ) && ( $_initialised == $PROCESS_ID ) ) ) {
68 16         18 if ( OS_WIN32() ) {
69             require Win32;
70             require Win32::API;
71             require Win32::API::Type;
72             my ( $major, $minor ) = ( Win32::GetOSVersion() )[ 1, 2 ];
73             my $ntorlower = ( $major < W2K_MAJOR_VERSION() ) ? 1 : 0;
74             my $w2k =
75             ( $major == W2K_MAJOR_VERSION()
76             and $minor == W2K_MINOR_VERSION() )
77             ? 1
78             : 0;
79              
80             if ($ntorlower) {
81             Carp::croak(
82             'No secure alternative for random number generation for Win32 versions older than W2K'
83             );
84             }
85             elsif ($w2k) {
86              
87             my $crypt_acquire_context_a =
88             Win32::API->new( 'advapi32', 'CryptAcquireContextA', 'PPPNN',
89             'I' );
90             if ( !defined $crypt_acquire_context_a ) {
91             Carp::croak(
92             "Could not import CryptAcquireContext: $EXTENDED_OS_ERROR"
93             );
94             }
95              
96             my $context = chr(0) x Win32::API::Type->sizeof('PULONG');
97             my $result =
98             $crypt_acquire_context_a->Call( $context, 0, 0,
99             PROV_RSA_FULL(), CRYPT_SILENT() | VERIFY_CONTEXT() );
100             my $pack_type = Win32::API::Type::packing('PULONG');
101             $context = unpack $pack_type, $context;
102             if ( !$result ) {
103             Carp::croak(
104             "CryptAcquireContext failed: $EXTENDED_OS_ERROR");
105             }
106              
107             my $crypt_gen_random =
108             Win32::API->new( 'advapi32', 'CryptGenRandom', 'NNP', 'I' );
109             if ( !defined $crypt_gen_random ) {
110             Carp::croak(
111             "Could not import CryptGenRandom: $EXTENDED_OS_ERROR");
112             }
113             $_context = $context;
114             $_cryptgenrandom = $crypt_gen_random;
115             }
116             else {
117             my $rtlgenrand =
118             Win32::API->new( 'advapi32', <<'_RTLGENRANDOM_PROTO_' );
119             INT SystemFunction036(
120             PVOID RandomBuffer,
121             ULONG RandomBufferLength
122             )
123             _RTLGENRANDOM_PROTO_
124             if ( !defined $rtlgenrand ) {
125             Carp::croak(
126             "Could not import SystemFunction036: $EXTENDED_OS_ERROR"
127             );
128             }
129             $_rtlgenrand = $rtlgenrand;
130             }
131             }
132             else {
133 16         176 require FileHandle;
134 16 100       146 $_urandom_handle = FileHandle->new( PATH(), Fcntl::O_RDONLY() )
135             or Carp::croak(
136             q[Failed to open ] . PATH() . qq[ for reading:$OS_ERROR] );
137 14         1954 binmode $_urandom_handle;
138             }
139 14         96 $_initialised = $PROCESS_ID;
140             }
141 23         35 return;
142             }
143              
144             sub urandom_ub {
145 15     15 1 4897 my ($length) = @_;
146 15         43 return _urandom( 'sysread', $length );
147             }
148              
149             sub urandom {
150 31     31 1 874318 my ($length) = @_;
151 31         236 return _urandom( 'read', $length );
152             }
153              
154             sub _urandom {
155 46     46   217 my ( $type, $length ) = @_;
156              
157 46         108 my $length_ok;
158 46 100       215 if ( defined $length ) {
159 45 100       496 if ( $length =~ /^\d+$/xms ) {
160 44         79 $length_ok = 1;
161             }
162             }
163 46 100       111 if ( !$length_ok ) {
164 2         406 Carp::croak(
165             'The length argument must be supplied and must be an integer');
166             }
167 44         61 if ( !GETRANDOM_AVAILABLE() ) {
168             _init();
169             }
170 44         77 if ( OS_WIN32() ) {
171             my $urandom = chr(0) x $length;
172             if ($_cryptgenrandom) {
173              
174             my $result = $_cryptgenrandom->Call( $_context, $length, $urandom );
175             if ( !$result ) {
176             Carp::croak("CryptGenRandom failed: $EXTENDED_OS_ERROR");
177             }
178             }
179             elsif ($_rtlgenrand) {
180              
181             my $result = $_rtlgenrand->Call( $urandom, $length );
182             if ( !$result ) {
183             Carp::croak("RtlGenRand failed: $EXTENDED_OS_ERROR");
184             }
185             }
186             return $urandom;
187             }
188             elsif ( GETRANDOM_AVAILABLE() ) {
189 42         10071 return getrandom($length);
190             }
191             else {
192             return _read_urandom_fs( $type, $length );
193             }
194 23         49 return;
195             }
196              
197             sub _read_urandom_fs {
198 23     23   57 my ( $type, $length ) = @_;
199 23         32 my $original_length = $length;
200 23         43 my $urandom;
201             BUFFER_FILLED: {
202 23         39 my $result;
  28         28  
203 28 100       61 if ( defined $urandom ) {
204 4         33 $length = $original_length - ( length $urandom );
205 4         17 $result = $_urandom_handle->$type( my $buffer, $length );
206 4 100       52 if ( defined $buffer ) {
207 3         6 $urandom .= $buffer;
208             }
209             }
210             else {
211 24         169 $result = $_urandom_handle->$type( my $buffer, $length );
212 24 100       5896 if ( defined $buffer ) {
213 14         1013 $urandom .= $buffer;
214             }
215             }
216 28 100 100     217 if ( ( defined $urandom )
    100 100        
    100          
    100          
    100          
217             && ( length $urandom == $original_length ) )
218             {
219             }
220             elsif ( !defined $result ) {
221 2         6 my $error = $EXTENDED_OS_ERROR;
222 2         14 $_urandom_handle = undef;
223 2         1 $_initialised = undef;
224 2         191 Carp::croak( q[Failed to read from ] . PATH() . qq[:$error] );
225             }
226             elsif ( $result == 0 ) {
227 1         7 $_urandom_handle = undef;
228 1         1 $_initialised = undef;
229 1         86 Carp::croak( PATH() . q[ has returned EOF] );
230             }
231             elsif (( $result == SYSTEM_CALL_FAILED() )
232             && ( $OS_ERROR == POSIX::EINTR() ) )
233             {
234 2         6 redo BUFFER_FILLED;
235             }
236             elsif ( $result != SYSTEM_CALL_FAILED() ) {
237 3         8 redo BUFFER_FILLED;
238             }
239             else {
240 6         16 my $error = $EXTENDED_OS_ERROR;
241 6         56 $_urandom_handle = undef;
242 6         8 $_initialised = undef;
243 6         715 Carp::croak( q[Failed to read from ] . PATH() . qq[:$error] );
244             }
245             }
246 14         60 return $urandom;
247             }
248              
249             1; # Magic true value required at end of module
250             __END__