File Coverage

blib/lib/App/GroupSecret/Crypt.pm
Criterion Covered Total %
statement 24 165 14.5
branch 0 56 0.0
condition n/a
subroutine 8 17 47.0
pod 7 7 100.0
total 39 245 15.9


line stmt bran cond sub pod time code
1             package App::GroupSecret::Crypt;
2             # ABSTRACT: Collection of crypto-related subroutines
3              
4 1     1   5 use warnings;
  1         2  
  1         26  
5 1     1   4 use strict;
  1         1  
  1         28  
6              
7             our $VERSION = '0.304'; # VERSION
8              
9 1     1   4 use Exporter qw(import);
  1         1  
  1         31  
10 1     1   592 use File::Temp;
  1         18094  
  1         55  
11 1     1   346 use IPC::Open2;
  1         2696  
  1         42  
12 1     1   6 use IPC::Open3;
  1         1  
  1         33  
13 1     1   4 use Symbol qw(gensym);
  1         2  
  1         49  
14 1     1   404 use namespace::clean -except => [qw(import)];
  1         12646  
  1         6  
15              
16             our @EXPORT_OK = qw(
17             generate_secure_random_bytes
18             read_openssh_public_key
19             read_openssh_key_fingerprint
20             decrypt_rsa
21             encrypt_rsa
22             decrypt_aes_256_cbc
23             encrypt_aes_256_cbc
24             );
25              
26             our $OPENSSL = 'openssl';
27             our $SSH_KEYGEN = 'ssh-keygen';
28              
29 0     0     sub _croak { require Carp; Carp::croak(@_) }
  0            
30 0     0     sub _usage { _croak("Usage: @_\n") }
31              
32              
33             sub generate_secure_random_bytes {
34 0 0   0 1   my $size = shift or _usage(q{generate_secure_random_bytes($num_bytes)});
35              
36 0           my @cmd = ($OPENSSL, 'rand', $size);
37              
38 0           my $out;
39 0           my $pid = open2($out, undef, @cmd);
40              
41 0           waitpid($pid, 0);
42 0           my $status = $?;
43              
44 0           my $exit_code = $status >> 8;
45 0 0         _croak 'Failed to generate secure random bytes' if $exit_code != 0;
46              
47 0           return do { local $/; <$out> };
  0            
  0            
48             }
49              
50              
51             sub read_openssh_public_key {
52 0 0   0 1   my $filepath = shift or _usage(q{read_openssh_public_key($filepath)});
53              
54 0           my @cmd = ($SSH_KEYGEN, qw{-e -m PKCS8 -f}, $filepath);
55              
56 0           my $out;
57 0           my $pid = open2($out, undef, @cmd);
58              
59 0           waitpid($pid, 0);
60 0           my $status = $?;
61              
62 0           my $exit_code = $status >> 8;
63 0 0         _croak 'Failed to read OpenSSH public key' if $exit_code != 0;
64              
65 0           return do { local $/; <$out> };
  0            
  0            
66             }
67              
68              
69             sub read_openssh_key_fingerprint {
70 0 0   0 1   my $filepath = shift or _usage(q{read_openssh_key_fingerprint($filepath)});
71              
72             # try with the -E flag first
73 0           my @cmd = ($SSH_KEYGEN, qw{-l -E md5 -f}, $filepath);
74              
75 0           my $out;
76 0           my $err = gensym;
77 0           my $pid = open3(undef, $out, $err, @cmd);
78              
79 0           waitpid($pid, 0);
80 0           my $status = $?;
81              
82 0           my $exit_code = $status >> 8;
83 0 0         if ($exit_code != 0) {
84 0           my $error_str = do { local $/; <$err> };
  0            
  0            
85 0 0         _croak 'Failed to read SSH2 key fingerprint' if $error_str !~ /unknown option -- E/s;
86              
87 0           @cmd = ($SSH_KEYGEN, qw{-l -f}, $filepath);
88              
89 0           undef $out;
90 0           $pid = open2($out, undef, @cmd);
91              
92 0           waitpid($pid, 0);
93 0           $status = $?;
94              
95 0           $exit_code = $status >> 8;
96 0 0         _croak 'Failed to read SSH2 key fingerprint' if $exit_code != 0;
97             }
98              
99 0           my $line = do { local $/; <$out> };
  0            
  0            
100 0           chomp $line;
101              
102 0           my ($bits, $fingerprint, $comment, $type) = $line =~ m!^(\d+) (?:MD5:)?([^ ]+) (.*) \(([^\)]+)\)$!;
103              
104 0           $fingerprint =~ s/://g;
105              
106             return {
107 0           bits => $bits,
108             fingerprint => $fingerprint,
109             comment => $comment,
110             type => lc($type),
111             };
112             }
113              
114              
115             sub decrypt_rsa {
116 0 0   0 1   my $filepath = shift or _usage(q{decrypt_rsa($filepath, $keypath)});
117 0 0         my $privkey = shift or _usage(q{decrypt_rsa($filepath, $keypath)});
118 0           my $outfile = shift;
119              
120 0           my $temp;
121 0 0         if (ref $filepath eq 'SCALAR') {
122 0           $temp = File::Temp->new(UNLINK => 1);
123 0           print $temp $$filepath;
124 0           close $temp;
125 0           $filepath = $temp->filename;
126             }
127              
128 0           my @cmd = ($OPENSSL, qw{rsautl -decrypt -oaep -in}, $filepath, '-inkey', $privkey);
129 0 0         push @cmd, ('-out', $outfile) if $outfile;
130              
131 0           my $out;
132 0           my $pid = open2($out, undef, @cmd);
133              
134 0           waitpid($pid, 0);
135 0           my $status = $?;
136              
137 0           my $exit_code = $status >> 8;
138 0 0         _croak 'Failed to decrypt ciphertext' if $exit_code != 0;
139              
140 0           return do { local $/; <$out> };
  0            
  0            
141             }
142              
143              
144             sub encrypt_rsa {
145 0 0   0 1   my $filepath = shift or _usage(q{encrypt_rsa($filepath, $keypath)});
146 0 0         my $pubkey = shift or _usage(q{encrypt_rsa($filepath, $keypath)});
147 0           my $outfile = shift;
148              
149 0           my $temp1;
150 0 0         if (ref $filepath eq 'SCALAR') {
151 0           $temp1 = File::Temp->new(UNLINK => 1);
152 0           print $temp1 $$filepath;
153 0           close $temp1;
154 0           $filepath = $temp1->filename;
155             }
156              
157 0           my $key = read_openssh_public_key($pubkey);
158              
159 0           my $temp2 = File::Temp->new(UNLINK => 1);
160 0           print $temp2 $key;
161 0           close $temp2;
162 0           my $keypath = $temp2->filename;
163              
164 0           my @cmd = ($OPENSSL, qw{rsautl -encrypt -oaep -pubin -inkey}, $keypath, '-in', $filepath);
165 0 0         push @cmd, ('-out', $outfile) if $outfile;
166              
167 0           my $out;
168 0           my $pid = open2($out, undef, @cmd);
169              
170 0           waitpid($pid, 0);
171 0           my $status = $?;
172              
173 0           my $exit_code = $status >> 8;
174 0 0         _croak 'Failed to encrypt plaintext' if $exit_code != 0;
175              
176 0           return do { local $/; <$out> };
  0            
  0            
177             }
178              
179              
180             sub decrypt_aes_256_cbc {
181 0 0   0 1   my $filepath = shift or _usage(q{decrypt_aes_256_cbc($ciphertext, $secret)});
182 0 0         my $secret = shift or _usage(q{decrypt_aes_256_cbc($ciphertext, $secret)});
183 0           my $outfile = shift;
184              
185 0           my $temp;
186 0 0         if (ref $filepath eq 'SCALAR') {
187 0           $temp = File::Temp->new(UNLINK => 1);
188 0           print $temp $$filepath;
189 0           close $temp;
190 0           $filepath = $temp->filename;
191             }
192              
193 0           my @cmd = ($OPENSSL, qw{aes-256-cbc -d -pass stdin -md sha256 -in}, $filepath);
194 0 0         push @cmd, ('-out', $outfile) if $outfile;
195              
196 0           my ($in, $out);
197 0           my $pid = open2($out, $in, @cmd);
198              
199 0           print $in $secret;
200 0           close($in);
201              
202 0           waitpid($pid, 0);
203 0           my $status = $?;
204              
205 0           my $exit_code = $status >> 8;
206 0 0         _croak 'Failed to decrypt ciphertext' if $exit_code != 0;
207              
208 0           return do { local $/; <$out> };
  0            
  0            
209             }
210              
211              
212             sub encrypt_aes_256_cbc {
213 0 0   0 1   my $filepath = shift or _usage(q{encrypt_aes_256_cbc($plaintext, $secret)});
214 0 0         my $secret = shift or _usage(q{encrypt_aes_256_cbc($plaintext, $secret)});
215 0           my $outfile = shift;
216              
217 0           my $temp;
218 0 0         if (ref $filepath eq 'SCALAR') {
219 0           $temp = File::Temp->new(UNLINK => 1);
220 0           print $temp $$filepath;
221 0           close $temp;
222 0           $filepath = $temp->filename;
223             }
224              
225 0           my @cmd = ($OPENSSL, qw{aes-256-cbc -pass stdin -md sha256 -in}, $filepath);
226 0 0         push @cmd, ('-out', $outfile) if $outfile;
227              
228 0           my ($in, $out);
229 0           my $pid = open2($out, $in, @cmd);
230              
231 0           print $in $secret;
232 0           close($in);
233              
234 0           waitpid($pid, 0);
235 0           my $status = $?;
236              
237 0           my $exit_code = $status >> 8;
238 0 0         _croak 'Failed to encrypt plaintext' if $exit_code != 0;
239              
240 0           return do { local $/; <$out> };
  0            
  0            
241             }
242              
243             1;
244              
245             __END__