File Coverage

blib/lib/GnuPG/Crypticle.pm
Criterion Covered Total %
statement 24 178 13.4
branch 0 108 0.0
condition 0 25 0.0
subroutine 8 15 53.3
pod 5 5 100.0
total 37 331 11.1


line stmt bran cond sub pod time code
1             package GnuPG::Crypticle;
2             $GnuPG::Crypticle::VERSION = '0.023';
3             # ABSTRACT: (DEPRECATED) use GnuPG::Interface instead!
4             # KEYWORDS: deprecated
5 1     1   13417 use namespace::autoclean;
  1         12521  
  1         2  
6 1     1   528 use Moose;
  1         283457  
  1         7  
7 1     1   5102 use Fcntl qw//;
  1         5  
  1         16  
8 1     1   614 use File::Copy qw//;
  1         1627  
  1         20  
9 1     1   404 use File::stat;
  1         4563  
  1         3  
10 1     1   47 use File::Path qw/make_path/;
  1         1  
  1         40  
11 1     1   3 use File::Spec qw//;
  1         1  
  1         11  
12 1     1   462 use IO::Handle;
  1         4351  
  1         1349  
13             has 'gpg_bin' => (
14             is => 'ro',
15             isa => 'Str',
16             default => '/usr/bin/gpg',
17             documentation => 'path to gpg binary',
18             );
19              
20             has 'gpg_home' => (
21             is => 'ro',
22             isa => 'Str',
23             required => 1,
24             lazy => 1,
25             default => sub { return "$ENV{HOME}/.gnupg"; },
26             documentation => 'Home directory for GnuPG files (pubring, secring, trustdb)',
27             );
28              
29             has 'gpg_pass_file' => (
30             is => 'ro',
31             isa => 'Str|FileHandle',
32             required => 0,
33             predicate => 'has_gpg_pass_file',
34             documentation => 'passphrase file for decrypting secret keys',
35             );
36              
37             has 'gpg_temp_home' => (
38             is => 'ro',
39             isa => 'Str',
40             required => 0,
41             predicate => 'has_gpg_temp_home',
42             documentation => 'path to temp home',
43             );
44              
45             has '_passphrase_fh' => (
46             is => 'ro',
47             isa => 'FileHandle',
48             lazy => 1,
49             builder => '_open_passphrase_file',
50             documentation => 'filehandle to passphrase file',
51             );
52             has '_null_fh' => (
53             is => 'ro',
54             isa => 'FileHandle',
55             lazy => 1,
56             builder => '_open_dev_null',
57             documentation => 'filehandle to /dev/null',
58             );
59              
60             sub BUILD {
61 0     0 1   my $self = shift;
62 0 0         if ($self->has_gpg_temp_home) {
63 0           my $homedir = $self->gpg_home;
64 0           my $gpgdir = $self->gpg_temp_home;
65 0           my $cumask = umask(0077);
66 0           my $mkpatherr;
67 0 0 0       unless (
      0        
      0        
      0        
68             (-d $gpgdir and -w $gpgdir) or
69             File::Path::make_path($gpgdir, {error=>\$mkpatherr}) or
70             (-d $gpgdir and -w $gpgdir)
71             ) {
72 0           umask($cumask);
73 0 0         if ($mkpatherr) {
74             # ugly but necessary, perldoc File::Path for info
75 0           my $k = (keys %{$mkpatherr->[0]})[0];
  0            
76 0           $mkpatherr = $mkpatherr->[0]->{$k};
77             }
78             else {
79 0           $mkpatherr = "$!";
80             }
81 0           umask($cumask);
82 0           die "Unable to create gpg_temp_home '$gpgdir': $mkpatherr";
83             }
84 0           for my $f (qw/secring.gpg trustdb.gpg pubring.gpg/) {
85 0           my $file = File::Spec->catfile($homedir, $f);
86 0 0         unless (File::Copy::cp($file, $gpgdir)) {
87 0           umask($cumask);
88 0           die "Failed to copy '$file' to '$gpgdir': $!";
89             }
90             }
91 0           File::Copy::cp(File::Spec->catfile($homedir, 'gpg.conf'), $gpgdir);
92 0           umask($cumask);
93             }
94             }
95              
96             sub decrypt {
97 0     0 1   my ($self, %opts) = @_;
98 0   0       $opts{gpg_args} ||= [];
99 0           push(@{$opts{gpg_args}}, '-d');
  0            
100 0           return $self->call_gpg(%opts);
101             }
102              
103             sub encrypt {
104 0     0 1   my ($self, %opts) = @_;
105 0   0       $opts{gpg_args} ||= [];
106 0           my $rcpt = delete $opts{rcpt};
107 0           push(@{$opts{gpg_args}}, '-r', $rcpt, '-e');
  0            
108 0           return $self->call_gpg(%opts);
109             }
110              
111             sub detect_encryption {
112 0     0 1   my ($self, %opts) = @_;
113 0           my $fh;
114 0 0 0       if (ref($opts{file}) and defined(fileno($opts{file}))) {
    0          
115 0           $fh = $opts{file};
116             }
117             elsif (!open($fh, '<:raw', $opts{file})) {
118 0           die "Failed detecting encryption: $!";
119             }
120 0           my $stat = stat($fh);
121             # 100 is arbitrary, but if less than 100 bytes could it be an encrypted file?
122             # don't go below what is read in for magic (64)
123 0 0         if ($stat->size > 100) {
124             # read in 64 bytes, long enough for the magic test
125 0           my ($magic,$buffer,$bytes) = ('','',0);
126 0           while ($bytes < 64) {
127 0           $bytes += read($fh, $buffer, 64);
128 0 0         if (!defined($bytes)) {
    0          
129 0           die "Read error: $!";
130             }
131             elsif (!$bytes) {
132             # old mcdonald had a farm, e i...
133 0           $! = 5;
134 0           die "Reached EOF on before 64 bytes, though stat said size over 100";
135             }
136             else {
137 0           $magic .= $buffer;
138             }
139             }
140 0 0 0       if (
      0        
141             $magic =~ /^\xa6\x00/ or
142             $magic =~ /^\x85[\x01\x02\x04]/ or
143             $magic =~ /^-----BEGIN\x20PGP\x20(SIGNED\x20)?MESSAGE-/
144             ) {
145 0           return 1;
146             }
147             }
148 0           return 0;
149             }
150              
151             sub call_gpg {
152 0     0 1   my ($self, %opts) = @_;
153 0           my $dest = delete $opts{dst};
154 0           my $error = delete $opts{err};
155 0           my $source = delete $opts{src};
156 0           my ($close_dest, $close_error, $close_source);
157             # using std filehandles for i/o lets us completely ignore dealing with
158             # close-on-exec
159 0           my ($stdout,$stderr,$stdin);
160 0 0         if ($dest) {
161 0 0         if (defined(fileno($dest))) {
162 0 0         unless(binmode($dest)) {
163 0           die "Failed to flush dest handle to raw: $!";
164             }
165             }
166             else {
167 0           my $file = $dest;
168 0           $dest = undef;
169 0 0         unless (open($dest, '>:raw', $file)) {
170 0           die "Failed to open dest file '$file': $!";
171             }
172 0           $close_dest = 1;
173             }
174 0 0         if (defined(fileno(STDOUT))) {
175 0 0         unless (open($stdout, ">&", \*STDOUT)) {
176 0           die "Failed to dup stdout: $!";
177             }
178             }
179 0 0         unless (open(STDOUT, ">&", $dest)) {
180 0           die "Failed to dup over STDOUT: $!";
181             }
182             }
183 0 0         if ($error) {
184 0 0         if (defined(fileno($error))) {
185 0 0         unless (binmode($error)) {
186 0           die "failed to flush error handle to raw: $!";
187             }
188             }
189             else {
190 0           my $file = $error;
191 0           $error = undef;
192 0 0         unless (open($error, '>>:raw', $file)) {
193 0           die "Failed to open error file '$file': $!";
194             }
195 0           $close_error = 1;
196             }
197 0 0         if (defined(fileno(STDERR))) {
198 0 0         unless (open($stderr, ">&", \*STDERR)) {
199 0           die "Failed to dup stderr: $!";
200             }
201             }
202 0 0         unless (open(STDERR, ">&", $error)) {
203 0           die "Failed to dup over STDERR: $!";
204             }
205             }
206 0 0         if ($source) {
207 0 0         if (defined(fileno($source))) {
208 0 0         unless (binmode($source)) {
209 0           die "Failed to flush source handle to raw: $!";
210             }
211             }
212             else {
213 0           my $file = $source;
214 0           $source = undef;
215 0 0         unless (open($source, '<:raw', $file)) {
216 0           die "Failed to open source file '$file': $!";
217             }
218 0           $close_source = 1;
219             }
220 0 0         if (defined(fileno(STDIN))) {
221 0 0         unless (open($stdin, "<&", \*STDIN)) {
222 0           die "Failed to dup stdin: $!";
223             }
224             }
225 0 0         unless (open(STDIN, "<&", $source)) {
226 0           die "Failed to dup over STDIN: $!";
227             }
228             }
229 0 0         if ($self->has_gpg_pass_file) {
230 0           unshift(@{$opts{gpg_args}}, '--passphrase-fd', fileno($self->_passphrase_fh));
  0            
231             }
232             else {
233 0           unshift(@{$opts{gpg_args}}, '--passphrase-fd', fileno($self->_null_fh));
  0            
234             }
235 0           my $homedir;
236 0 0         if ($self->has_gpg_temp_home) {
237 0           unshift(@{$opts{gpg_args}}, '--homedir', $self->gpg_temp_home);
  0            
238             }
239             else {
240 0           unshift(@{$opts{gpg_args}}, '--homedir', $self->gpg_home);
  0            
241             }
242 0           my $gpg_fail;
243 0 0         unless (system($self->gpg_bin, '--batch', '--no-tty', @{$opts{gpg_args}}) == 0) {
  0            
244 0 0         if ($! == 0) {
245 0           $gpg_fail = "Failed to execute gpg: $?";
246             }
247             else {
248 0           $gpg_fail = "gpg call failed: $?";
249             }
250             }
251 0 0         if ($self->has_gpg_pass_file) {
252 0           seek($self->_passphrase_fh, 0, 0);
253             }
254 0 0         if ($stdin) {
255 0 0         unless (open(STDIN, "<&", $stdin)) {
256 0           die "Failed to restore STDIN";
257             }
258             }
259 0 0         close($source) if $close_source;
260 0 0         if ($stderr) {
261 0 0         unless (open(STDERR, ">&", $stderr)) {
262 0           die "Failed to restore STDERR";
263             }
264             }
265 0 0         close($error) if $close_error;
266 0 0         if ($stdout) {
267 0 0         unless (open(STDOUT, ">&", $stdout)) {
268 0           die "Failed to restore STDOUT";
269             }
270             }
271 0 0         close($dest) if $close_dest;
272 0 0         die $gpg_fail if $gpg_fail;
273 0           return 1;
274             }
275              
276             sub _open_passphrase_file {
277 0     0     my $self = shift;
278 0 0         if (my $file = $self->gpg_pass_file) {
279 0 0         if (ref($file)) {
280 0           my $flags;
281 0 0         unless ($flags = fcntl($file, Fcntl::F_GETFD, 0)) {
282 0           die "fcntl F_GETFD failed: $!";
283             }
284 0 0         unless (fcntl($file, Fcntl::F_SETFD, $flags & ~Fcntl::FD_CLOEXEC)) {
285 0           die "fcntl F_SETFD failed: $!";
286             }
287 0           return $file;
288             }
289             else {
290 0           my $fh;
291 0 0         unless (open($fh, '<', $file)) {
292 0           die "Failed to open passphrase file: $!";
293             }
294 0           my $flags;
295 0 0         unless ($flags = fcntl($fh, Fcntl::F_GETFD, 0)) {
296 0           die "fcntl F_GETFD failed: $!";
297             }
298 0 0         unless (fcntl($fh, Fcntl::F_SETFD, $flags & ~Fcntl::FD_CLOEXEC)) {
299 0           die "fcntl F_SETFD failed: $!";
300             }
301 0           return $fh;
302             }
303             }
304             }
305              
306             sub _open_dev_null {
307 0     0     my $fh;
308 0 0         unless (open($fh, '<', File::Spec->devnull)) {
309 0           die "Failed to open /dev/null: $!";
310             }
311 0           return $fh;
312             }
313              
314             __PACKAGE__->meta->make_immutable();
315              
316             1;
317              
318             __END__
319              
320             =pod
321              
322             =encoding UTF-8
323              
324             =head1 NAME
325              
326             GnuPG::Crypticle - (DEPRECATED) use GnuPG::Interface instead!
327              
328             =head1 VERSION
329              
330             version 0.023
331              
332             =head1 SYNOPSIS
333              
334             Stop reading here, and go use L<GnuPG::Interface> instead.
335              
336             use GnuPG::Crypticle;
337              
338             my $crypticle = GnuPG::Crypticle->new(gpg_home => /home/me/.gnupg);
339             $crypticle->encrypt(src => '/tmp/sourcefile.txt', dst => '/tmp/destfile.gpg', rcpt => 'ABCD0123');
340             ...
341              
342             =head1 DEPRECATION
343              
344             This module should be considered deprecated and unmaintained. It was a stop-gap
345             -- albeit not a very good one -- when the author had no better option to use
346             gpg2 (L<GnuPG> only works with gpg1). L<GnuPG::Interface> is a much better
347             option. Please use that module instead!
348              
349             =head1 ATTRIBUTES
350              
351             =head2 gpg_bin
352              
353             full path to gpg binary
354              
355             =head2 gpg_home
356              
357             location of the .gnupg directory gpg should use
358              
359             =head2 gpg_pass_file
360              
361             plaintext file containing the passphrase used with any secret keys
362              
363             =head2 gpg_temp_home
364              
365             path to use as temporary home
366              
367             =head1 METHODS
368              
369             Parameters are passed to all methods as a key/value list (hash) e.g.,
370              
371             subroutine(key1 => val1, key2 => val2);
372              
373             =head2 BUILD
374              
375             During object initialization, copies of master gpg keyrings are made in a
376             temporary directory to prevent locking and corruption problems. A restart of
377             the application is necessary if there are key ring changes. Dies on failure.
378              
379             =head2 decrypt
380              
381             Encrypts from a source to destination file. Croaks on decryption failure,
382             including signature failure.
383              
384             parameters:
385              
386             =over 2
387              
388             =item src
389              
390             file name or handle to be decrypted
391              
392             =item dst
393              
394             file name or handle to which decrypted output is sent
395              
396             =back
397              
398             returns:
399              
400             =over 2
401              
402             valid signature if present, or true
403              
404             =back
405              
406             =head2 encrypt
407              
408             Dies on failure
409              
410             parameters:
411              
412             =over 2
413              
414             =item src
415              
416             file name or handle to be encrypted
417              
418             =item dst
419              
420             file name or handle to which encrypted output is sent
421              
422             =item gpg_args
423              
424             arguments passed directly to gpg execution
425              
426             =back
427              
428             returns:
429              
430             =over 2
431              
432             valid signature if present, or true
433              
434             =back
435              
436             =head2 detect_encryption
437              
438             Dies on failure. Detects pgp or gpg decryption the same as mime magic does.
439              
440             This is nowhere near complete or reliable. For best results, just try to
441             decrypt.
442              
443             parameters:
444              
445             =over 2
446              
447             =item file
448              
449             file name or handle from which to detect encryption
450              
451             =back
452              
453             =head2 call_gpg
454              
455             (private) calls gpg command with necessary options
456              
457             =head2 _open_passphrase_file
458              
459             (private) Opens the passphrase file.
460              
461             =head2 _open_dev_null
462              
463             (private) returns a filehandle to /dev/null
464              
465             =head1 SEE ALSO
466              
467             This should be read "see instead." L<GnuPG::Interface>
468              
469             =head1 AUTHOR
470              
471             Brad Barden <b at 13os.net>
472              
473             =head1 COPYRIGHT AND LICENSE
474              
475             This software is Copyright (c) 2016 by Brad Barden.
476              
477             This is free software, licensed under:
478              
479             The ISC License
480              
481             =cut