File Coverage

blib/lib/Module/Crypt.pm
Criterion Covered Total %
statement 153 160 95.6
branch 21 36 58.3
condition 6 10 60.0
subroutine 21 22 95.4
pod 1 4 25.0
total 202 232 87.0


line stmt bran cond sub pod time code
1             # ===========================================================================
2             # Module::Crypt
3             #
4             # Encrypt your Perl code and compile it into XS
5             #
6             # Author: Alessandro Ranellucci
7             # Copyright (c).
8             #
9             # This is EXPERIMENTAL code. Use it AT YOUR OWN RISK.
10             # See below for documentation.
11             #
12              
13             package Module::Crypt;
14              
15 4     4   49777 use strict;
  4         9  
  4         150  
16 4     4   25 use warnings;
  4         6  
  4         195  
17             our $VERSION = 0.06;
18              
19 4     4   21 use Carp qw[croak];
  4         14  
  4         219  
20 4     4   4528 use ExtUtils::CBuilder ();
  4         504075  
  4         108  
21 4     4   4881 use ExtUtils::ParseXS ();
  4         128838  
  4         131  
22 4     4   3791 use ExtUtils::Mkbootstrap;
  4         2616  
  4         580  
23 4     4   3477 use File::Copy 'move';
  4         10205  
  4         290  
24 4     4   28 use File::Find ();
  4         10  
  4         60  
25 4     4   21 use File::Path ();
  4         9  
  4         57  
26 4     4   48 use File::Spec ();
  4         9  
  4         69  
27 4     4   20 use File::Temp 'mktemp';
  4         8  
  4         170  
28 4     4   1211 use IO::File;
  4         10  
  4         661  
29 4     4   13883 use Crypt::RC4;
  4         2469  
  4         7950  
30              
31             require Exporter;
32             our @ISA = qw[Exporter];
33             our @EXPORT = qw[CryptModule];
34              
35             our @ToDelete;
36              
37             sub CryptModule {
38 4     4 1 393819 my %Params = @_;
39            
40             # get modules list
41 4         12 my @Files;
42 4 50       30 if ($Params{file}) {
43 4         17 push @Files, $Params{file};
44             }
45 4 50 33     57 if (ref $Params{files} eq 'ARRAY') {
    50          
46 0         0 push @Files, @{$Params{files}};
  0         0  
47             } elsif ($Params{files} && !ref $Params{files}) {
48 0         0 $Params{files} = File::Spec->rel2abs($Params{files});
49 0 0       0 if (-d $Params{files}) {
    0          
50             # scan directory
51             File::Find::find({wanted => sub {
52 0 0   0   0 push @Files, $File::Find::name if $File::Find::name =~ /\.pm$/;
53 0         0 }, no_chdir => 1}, $Params{files});
54             } elsif (-f $Params{files}) {
55 0         0 push @Files, $Params{file};
56             }
57             }
58 4         12 my (%Modules, $package, $version);
59 4         11 foreach my $file (@Files) {
60 4         83 $file = File::Spec->rel2abs($file);
61 4 50       133 croak("File $file does not exist") unless -e $file;
62 4         56 $package = '';
63 4         12 $version = '1.00';
64 4         174 open(MOD, "<$file");
65 4         127 while () {
66 32 100       93 if (/^\s*package\s+([a-zA-Z0-9]+(?:::[a-zA-Z0-9_]+)*)\s*/) {
67 4         27 $package = $1;
68             }
69 32 100       127 if (/^\s*(?:our\s+)?\$VERSION\s*=\s*['"]?([0-9a-z\.]+)['"]?\s*;/) {
70 4         17 $version = $1;
71             }
72             }
73 4         43 close MOD;
74 4 50       18 croak("Failed to parse package name in $file") unless $package;
75 4 50       19 croak("File $file conflicts with $Modules{$package}->{file} (package name: $package)")
76             if $Modules{$package};
77 4         30 $Modules{$package} = {file => $file, version => $version};
78             }
79            
80             # let's make sure install_base exists
81 4   50     18 $Params{install_base} ||= 'output';
82 4         49 $Params{install_base} = File::Spec->rel2abs($Params{install_base});
83 4         746 File::Path::mkpath($Params{install_base});
84            
85             # create temp directory
86 4         83 my $TempDir = mktemp( File::Spec->catdir($Params{install_base}, "/tmp.XXXXXXXXX") );
87 4         1925 File::Path::mkpath($TempDir);
88 4         13 push @ToDelete, $TempDir;
89            
90             # compile modules
91 4         88 my $cbuilder = ExtUtils::CBuilder->new;
92            
93 4         243139 foreach my $module (keys %Modules) {
94            
95 4         22 my @module_path = _module_path($module);
96 4         12 my $module_basename = pop @module_path;
97            
98             # let's create path
99 4         1276 File::Path::mkpath( File::Spec->catdir($TempDir, @module_path) );
100            
101             # let's write source files
102 4         89 my $newpath = File::Spec->catfile($TempDir, @module_path, "$module_basename");
103 4         50 _write_c($module, $Modules{$module}->{version},
104             $Modules{$module}->{file}, $newpath,
105             $Params{password}, $Params{allow_debug},
106             $Params{addl_code});
107            
108             # .xs -> .c
109 4         51 ExtUtils::ParseXS::process_file(
110             filename => "$newpath.xs",
111             prototypes => 0,
112             output => "$newpath.c",
113             );
114            
115             # .c -> .o
116 4         269308 my $obj_file = $cbuilder->object_file("$newpath.c");
117 4         255 $cbuilder->compile(
118             source => "$newpath.c",
119             object_file => $obj_file
120             );
121            
122             # .xs -> .bs
123 4         1787831 ExtUtils::Mkbootstrap::Mkbootstrap($newpath);
124 4         709 {my $fh = IO::File->new(">> $newpath.bs")}; # create
  4         162  
125            
126             # .o -> .(a|bundle)
127 4         1331 my $lib_file = $cbuilder->lib_file($obj_file);
128 4         1051 print "--> $lib_file\n";
129 4         139 $cbuilder->link(
130             module_name => $module,
131             objects => [$obj_file],
132             lib_file => $lib_file
133             );
134            
135             # move everything to install_base
136 4         222661 my $final_path = File::Spec->catdir($Params{install_base}, @module_path);
137 4         99 my $final_path_auto = File::Spec->catdir($Params{install_base}, "auto", @module_path, $module_basename);
138 4         1705 File::Path::mkpath($final_path);
139 4         1567 File::Path::mkpath($final_path_auto);
140 4 50       125 move("${newpath}.pm", "${final_path}/${module_basename}.pm") or die $!;
141 4         1184 foreach (qw[bs a bundle so]) {
142 16 100       2434 next unless -e "$newpath.$_";
143 8 50       66 move("${newpath}.$_", "${final_path_auto}/") or die $!;
144             }
145             }
146              
147 4         989 _cleanup();
148 4         169 return 1;
149             }
150              
151             sub _module_path {
152 4     4   12 my ($package) = @_;
153 4         35 return split(/::/, $package);
154             }
155              
156             sub END {
157 4     4   80500 _cleanup();
158             }
159              
160             sub _cleanup {
161 8     8   9777 File::Path::rmtree($_) foreach @ToDelete;
162             }
163              
164             sub _write_c {
165 4     4   24 my ($package, $version, $pm, $newpath, # UGH!
166             $password, $allow_debug, $addl_code) = @_;
167            
168             # get source script
169 4         217 open(SRC, "<$pm");
170 4         142 my @lines = ;
171 4         111 close SRC;
172            
173            
174             # encrypt things
175 4         323 open(XS, ">$newpath.xs");
176 4         37 print XS wr( join('', @lines), $password, $allow_debug, $addl_code );
177 4         26 print XS <<"EOF"
178              
179             #include "EXTERN.h"
180             #include "perl.h"
181             #include "XSUB.h"
182             #include
183             #include
184             #include
185              
186             /**
187             * 'Alleged RC4' Source Code picked up from the news.
188             * From: allen\@gateway.grumman.com (John L. Allen)
189             * Newsgroups: comp.lang.c
190             * Subject: Shrink this C code for fame and fun
191             * Date: 21 May 1996 10:49:37 -0400
192             */
193              
194             static unsigned char stte[256], indx, jndx, kndx;
195              
196             /*
197             * Reset arc4 stte.
198             */
199             void stte_0(void)
200             {
201             indx = jndx = kndx = 0;
202             do {
203             stte[indx] = indx;
204             } while (++indx);
205             }
206              
207             /*
208             * Set key. Can be used more than once.
209             */
210             void key(void * str, int len)
211             {
212             unsigned char tmp, * ptr = (unsigned char *)str;
213             while (len > 0) {
214             do {
215             tmp = stte[indx];
216             kndx += tmp;
217             kndx += ptr[(int)indx % len];
218             stte[indx] = stte[kndx];
219             stte[kndx] = tmp;
220             } while (++indx);
221             ptr += 256;
222             len -= 256;
223             }
224             }
225              
226             /*
227             * Crypt data.
228             */
229             void arc4(void * str, int len)
230             {
231             unsigned char tmp, * ptr = (unsigned char *)str;
232             while (len > 0) {
233             indx++;
234             tmp = stte[indx];
235             jndx += tmp;
236             stte[indx] = stte[jndx];
237             stte[jndx] = tmp;
238             tmp += stte[indx];
239             *ptr ^= stte[tmp];
240             ptr++;
241             len--;
242             }
243             }
244              
245             MODULE = $package PACKAGE = $package
246              
247             BOOT:
248             /* First try to detect if we're under debugger siege */
249             if ( !ALLOW_DEBUG ) {
250             int i;
251             for ( i = 0; i < dbg_eval_z; i++ ) {
252             dbg_eval[i] ^= PSWD_XOR;
253             };
254            
255             SV *dbg = eval_pv(dbg_eval, G_SCALAR);
256            
257             if ( dbg != NULL && (int) SvIV(dbg) > 0 ) {
258             /* Bomb 'em! */
259             SV *sv = (SV *)0xBAADF00D;
260             SvIVX(sv) = 0xDEADBEEF;
261             };
262             }
263            
264             /* Reveal the password */
265             {
266             int i;
267             for ( i = 0; i < pswd_z; i++ ) {
268             pswd[i] ^= PSWD_XOR;
269             };
270             }
271            
272             /* If we have additional check, unencrypt and run it now */
273             if ( addl_z ) {
274             stte_0();
275             key(pswd, pswd_z);
276             arc4(addl, addl_z);
277              
278             eval_pv(addl, G_SCALAR);
279              
280             SV *err = get_sv("@", 0);
281              
282             if ( SvPOK(err) && SvCUR(err) > 0 )
283             croak(SvPV_nolen(err), NULL);
284             };
285            
286             /* Now unencrypt main code and eval it */
287             stte_0();
288             key(pswd, pswd_z);
289             arc4(text, text_z);
290            
291             eval_pv(text, G_SCALAR);
292              
293             EOF
294             ;
295 4         270 close XS;
296            
297 4         371 open(PM, ">$newpath.pm");
298 4         71 print PM <<"EOF"
299             package $package;
300              
301             use strict;
302             use warnings;
303              
304             our \$VERSION = $version;
305              
306             use XSLoader;
307             XSLoader::load __PACKAGE__, \$VERSION;
308              
309             1;
310              
311             EOF
312             ;
313 4         128 close PM;
314             }
315              
316             my $offset = 0;
317              
318             sub wr {
319 4     4 0 12 my ($script, $pass, $allow_debug, $addl_code) = @_;
320              
321             # First make sure password is set
322 4   66     34 $pass ||= generate_noise(256);
323 4         11 my $pass_len = length $pass;
324              
325             # Now encrypt the data with un-XORed password
326 4         31 my $encrypted = RC4($pass, $script);
327 4   100     4315 my $addl_encr = RC4($pass, $addl_code || '');
328              
329             # Password is XORed before writing in file
330 4         2300 my $pass_xor = chr int rand 256;
331 4         25 $pass ^= $pass_xor x $pass_len;
332              
333             # Now determine padding size
334 4         7 my $script_len = length $encrypted;
335 4         9 my $addl_len = length $addl_encr;
336 4         14 my $total_padding_len
337             = (int(($pass_len + $script_len + $addl_len)/512) + 1) * 512
338             - $script_len - $pass_len - $addl_len - 3;
339 4         11 my $padding_start_len = int rand $total_padding_len;
340 4         9 my $padding_end_len = int rand $total_padding_len -
341             $padding_start_len;
342 4         8 my $padding_middle_len = $total_padding_len -
343             $padding_start_len - $padding_end_len;
344              
345 4         11 my ($padding_start, $padding_middle, $padding_end) = ('', '', '');
346 4         235 $padding_start .= chr int rand 256 for 0..$padding_start_len - 1;
347 4         66 $padding_middle .= chr int rand 256 for 0..$padding_middle_len - 1;
348 4         44 $padding_end .= chr int rand 256 for 0..$padding_end_len - 1;
349              
350 4         21 my $data = $padding_start . $pass . "\0" .
351             $padding_middle . $encrypted . "\0" .
352             $padding_end . $addl_encr . "\0";
353              
354 4         18 my $output = "static char data[] =" . print_bytes($data) .
355             ";\n\t/* End of data */\n";
356              
357             # Now definitions
358 4         41 $output .= sprintf "#define PSWD_XOR %d\n", ord $pass_xor;
359 4         16 $output .= sprintf "#define pswd_z %d\n", length $pass;
360 4         16 $output .= sprintf "#define pswd ((&data[%d]))\n",
361             $padding_start_len;
362              
363 4         12 $output .= sprintf "#define text_z %d\n", length $encrypted;
364 4         18 $output .= sprintf "#define text ((&data[%d]))\n",
365             $padding_start_len + length($pass) + 1 +
366             $padding_middle_len;
367              
368 4         14 $output .= sprintf "#define addl_z %d\n", length $addl_encr;
369 4         17 $output .= sprintf "#define addl ((&data[%d]))\n",
370             $padding_start_len + length($pass) + 1 +
371             $padding_middle_len + length($encrypted) + 1 +
372             $padding_end_len;
373              
374             # Debugger check command is eval'ed in situ
375             # I have not found better way to check for $^P yet
376 4         8 $output .= "#define dbg_eval_z 3\n";
377 4         19 $output .= sprintf "static char dbg_eval[] =" .
378             print_bytes('$^P' ^ ($pass_xor x 3)) . ";\n";
379              
380 4 50       33 $output .= sprintf "#define ALLOW_DEBUG %d\n", $allow_debug ? 1 : 0;
381              
382 4         83 return $output;
383             }
384              
385             sub print_bytes {
386 8     8 0 14 my ($bytes) = @_;
387              
388 8         14 my $output = "";
389              
390 8         20 for my $i ( 0 .. length($bytes) - 1 ) {
391 2060 100       3163 $output .= qq{\n\t"} if ($i & 0xf) == 0;
392              
393 2060         2913 $output .= sprintf '\%03o', ord substr $bytes, $i, 1;
394              
395 2060 100       3549 $output .= '"' if ($i & 0xf) == 0xf;
396             };
397              
398 8 100       61 $output .= '"' unless $output =~ /"$/;
399              
400 8         69 return $output;
401             }
402              
403             sub generate_noise {
404 3     3 0 7 my ($length) = @_;
405              
406 3         8 my $noise = "";
407              
408 3         15 for (0 .. $length - 1) {
409 768         778 my $char;
410              
411 768         719 do { $char = chr int rand 128 } until ( $char =~ /^[[:alnum:]]$/ );
  1577         4170  
412              
413 768         958 $noise .= $char;
414             };
415              
416 3         19 return $noise;
417             }
418              
419             1;
420              
421             __END__