]> Dogcows Code - chaz/groupsecret/blob - lib/App/GroupSecret/Crypt.pm
Version 0.303
[chaz/groupsecret] / lib / App / GroupSecret / Crypt.pm
1 package App::GroupSecret::Crypt;
2 # ABSTRACT: Collection of crypto-related subroutines
3
4 use warnings;
5 use strict;
6
7 our $VERSION = '0.303'; # VERSION
8
9 use Exporter qw(import);
10 use File::Temp;
11 use IPC::Open2;
12 use IPC::Open3;
13 use Symbol qw(gensym);
14 use namespace::clean -except => [qw(import)];
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 sub _croak { require Carp; Carp::croak(@_) }
30 sub _usage { _croak("Usage: @_\n") }
31
32
33 sub generate_secure_random_bytes {
34 my $size = shift or _usage(q{generate_secure_random_bytes($num_bytes)});
35
36 my @cmd = ($OPENSSL, 'rand', $size);
37
38 my $out;
39 my $pid = open2($out, undef, @cmd);
40
41 waitpid($pid, 0);
42 my $status = $?;
43
44 my $exit_code = $status >> 8;
45 _croak 'Failed to generate secure random bytes' if $exit_code != 0;
46
47 return do { local $/; <$out> };
48 }
49
50
51 sub read_openssh_public_key {
52 my $filepath = shift or _usage(q{read_openssh_public_key($filepath)});
53
54 my @cmd = ($SSH_KEYGEN, qw{-e -m PKCS8 -f}, $filepath);
55
56 my $out;
57 my $pid = open2($out, undef, @cmd);
58
59 waitpid($pid, 0);
60 my $status = $?;
61
62 my $exit_code = $status >> 8;
63 _croak 'Failed to read OpenSSH public key' if $exit_code != 0;
64
65 return do { local $/; <$out> };
66 }
67
68
69 sub read_openssh_key_fingerprint {
70 my $filepath = shift or _usage(q{read_openssh_key_fingerprint($filepath)});
71
72 # try with the -E flag first
73 my @cmd = ($SSH_KEYGEN, qw{-l -E md5 -f}, $filepath);
74
75 my $out;
76 my $err = gensym;
77 my $pid = open3(undef, $out, $err, @cmd);
78
79 waitpid($pid, 0);
80 my $status = $?;
81
82 my $exit_code = $status >> 8;
83 if ($exit_code != 0) {
84 my $error_str = do { local $/; <$err> };
85 _croak 'Failed to read SSH2 key fingerprint' if $error_str !~ /unknown option -- E/s;
86
87 @cmd = ($SSH_KEYGEN, qw{-l -f}, $filepath);
88
89 undef $out;
90 $pid = open2($out, undef, @cmd);
91
92 waitpid($pid, 0);
93 $status = $?;
94
95 $exit_code = $status >> 8;
96 _croak 'Failed to read SSH2 key fingerprint' if $exit_code != 0;
97 }
98
99 my $line = do { local $/; <$out> };
100 chomp $line;
101
102 my ($bits, $fingerprint, $comment, $type) = $line =~ m!^(\d+) (?:MD5:)?([^ ]+) (.*) \(([^\)]+)\)$!;
103
104 $fingerprint =~ s/://g;
105
106 return {
107 bits => $bits,
108 fingerprint => $fingerprint,
109 comment => $comment,
110 type => lc($type),
111 };
112 }
113
114
115 sub decrypt_rsa {
116 my $filepath = shift or _usage(q{decrypt_rsa($filepath, $keypath)});
117 my $privkey = shift or _usage(q{decrypt_rsa($filepath, $keypath)});
118 my $outfile = shift;
119
120 my $temp;
121 if (ref $filepath eq 'SCALAR') {
122 $temp = File::Temp->new(UNLINK => 1);
123 print $temp $$filepath;
124 close $temp;
125 $filepath = $temp->filename;
126 }
127
128 my @cmd = ($OPENSSL, qw{rsautl -decrypt -oaep -in}, $filepath, '-inkey', $privkey);
129 push @cmd, ('-out', $outfile) if $outfile;
130
131 my $out;
132 my $pid = open2($out, undef, @cmd);
133
134 waitpid($pid, 0);
135 my $status = $?;
136
137 my $exit_code = $status >> 8;
138 _croak 'Failed to decrypt ciphertext' if $exit_code != 0;
139
140 return do { local $/; <$out> };
141 }
142
143
144 sub encrypt_rsa {
145 my $filepath = shift or _usage(q{encrypt_rsa($filepath, $keypath)});
146 my $pubkey = shift or _usage(q{encrypt_rsa($filepath, $keypath)});
147 my $outfile = shift;
148
149 my $temp1;
150 if (ref $filepath eq 'SCALAR') {
151 $temp1 = File::Temp->new(UNLINK => 1);
152 print $temp1 $$filepath;
153 close $temp1;
154 $filepath = $temp1->filename;
155 }
156
157 my $key = read_openssh_public_key($pubkey);
158
159 my $temp2 = File::Temp->new(UNLINK => 1);
160 print $temp2 $key;
161 close $temp2;
162 my $keypath = $temp2->filename;
163
164 my @cmd = ($OPENSSL, qw{rsautl -encrypt -oaep -pubin -inkey}, $keypath, '-in', $filepath);
165 push @cmd, ('-out', $outfile) if $outfile;
166
167 my $out;
168 my $pid = open2($out, undef, @cmd);
169
170 waitpid($pid, 0);
171 my $status = $?;
172
173 my $exit_code = $status >> 8;
174 _croak 'Failed to encrypt plaintext' if $exit_code != 0;
175
176 return do { local $/; <$out> };
177 }
178
179
180 sub decrypt_aes_256_cbc {
181 my $filepath = shift or _usage(q{decrypt_aes_256_cbc($ciphertext, $secret)});
182 my $secret = shift or _usage(q{decrypt_aes_256_cbc($ciphertext, $secret)});
183 my $outfile = shift;
184
185 my $temp;
186 if (ref $filepath eq 'SCALAR') {
187 $temp = File::Temp->new(UNLINK => 1);
188 print $temp $$filepath;
189 close $temp;
190 $filepath = $temp->filename;
191 }
192
193 my @cmd = ($OPENSSL, qw{aes-256-cbc -d -pass stdin -md sha256 -in}, $filepath);
194 push @cmd, ('-out', $outfile) if $outfile;
195
196 my ($in, $out);
197 my $pid = open2($out, $in, @cmd);
198
199 print $in $secret;
200 close($in);
201
202 waitpid($pid, 0);
203 my $status = $?;
204
205 my $exit_code = $status >> 8;
206 _croak 'Failed to decrypt ciphertext' if $exit_code != 0;
207
208 return do { local $/; <$out> };
209 }
210
211
212 sub encrypt_aes_256_cbc {
213 my $filepath = shift or _usage(q{encrypt_aes_256_cbc($plaintext, $secret)});
214 my $secret = shift or _usage(q{encrypt_aes_256_cbc($plaintext, $secret)});
215 my $outfile = shift;
216
217 my $temp;
218 if (ref $filepath eq 'SCALAR') {
219 $temp = File::Temp->new(UNLINK => 1);
220 print $temp $$filepath;
221 close $temp;
222 $filepath = $temp->filename;
223 }
224
225 my @cmd = ($OPENSSL, qw{aes-256-cbc -pass stdin -md sha256 -in}, $filepath);
226 push @cmd, ('-out', $outfile) if $outfile;
227
228 my ($in, $out);
229 my $pid = open2($out, $in, @cmd);
230
231 print $in $secret;
232 close($in);
233
234 waitpid($pid, 0);
235 my $status = $?;
236
237 my $exit_code = $status >> 8;
238 _croak 'Failed to encrypt plaintext' if $exit_code != 0;
239
240 return do { local $/; <$out> };
241 }
242
243 1;
244
245 __END__
246
247 =pod
248
249 =encoding UTF-8
250
251 =head1 NAME
252
253 App::GroupSecret::Crypt - Collection of crypto-related subroutines
254
255 =head1 VERSION
256
257 version 0.303
258
259 =head1 FUNCTIONS
260
261 =head2 generate_secure_random_bytes
262
263 $bytes = generate_secure_random_bytes($num_bytes);
264
265 Get a certain number of secure random bytes.
266
267 =head2 read_openssh_public_key
268
269 $pem_public_key = read_openssh_public_key($public_key_filepath);
270
271 Read a RFC4716 (SSH2) public key from a file, converting it to PKCS8 (PEM).
272
273 =head2 read_openssh_key_fingerprint
274
275 $fingerprint = read_openssh_key_fingerprint($filepath);
276
277 Get the fingerprint of an OpenSSH private or public key.
278
279 =head2 decrypt_rsa
280
281 $plaintext = decrypt_rsa($ciphertext_filepath, $private_key_filepath);
282 $plaintext = decrypt_rsa(\$ciphertext, $private_key_filepath);
283 decrypt_rsa($ciphertext_filepath, $private_key_filepath, $plaintext_filepath);
284 decrypt_rsa(\$ciphertext, $private_key_filepath, $plaintext_filepath);
285
286 Do RSA decryption. Turn ciphertext into plaintext.
287
288 =head2 encrypt_rsa
289
290 $ciphertext = decrypt_rsa($plaintext_filepath, $public_key_filepath);
291 $ciphertext = decrypt_rsa(\$plaintext, $public_key_filepath);
292 decrypt_rsa($plaintext_filepath, $public_key_filepath, $ciphertext_filepath);
293 decrypt_rsa(\$plaintext, $public_key_filepath, $ciphertext_filepath);
294
295 Do RSA encryption. Turn plaintext into ciphertext.
296
297 =head2 decrypt_aes_256_cbc
298
299 $plaintext = decrypt_aes_256_cbc($ciphertext_filepath, $secret);
300 $plaintext = decrypt_aes_256_cbc(\$ciphertext, $secret);
301 decrypt_aes_256_cbc($ciphertext_filepath, $secret, $plaintext_filepath);
302 decrypt_aes_256_cbc(\$ciphertext, $secret, $plaintext_filepath);
303
304 Do symmetric decryption. Turn ciphertext into plaintext.
305
306 =head2 encrypt_aes_256_cbc
307
308 $ciphertext = encrypt_aes_256_cbc($plaintext_filepath, $secret);
309 $ciphertext = encrypt_aes_256_cbc(\$plaintext, $secret);
310 encrypt_aes_256_cbc($plaintext_filepath, $secret, $ciphertext_filepath);
311 encrypt_aes_256_cbc(\$plaintext, $secret, $ciphertext_filepath);
312
313 Do symmetric encryption. Turn plaintext into ciphertext.
314
315 =head1 BUGS
316
317 Please report any bugs or feature requests on the bugtracker website
318 L<https://github.com/chazmcgarvey/groupsecret/issues>
319
320 When submitting a bug or request, please include a test-file or a
321 patch to an existing test-file that illustrates the bug or desired
322 feature.
323
324 =head1 AUTHOR
325
326 Charles McGarvey <chazmcgarvey@brokenzipper.com>
327
328 =head1 COPYRIGHT AND LICENSE
329
330 This software is Copyright (c) 2017 by Charles McGarvey.
331
332 This is free software, licensed under:
333
334 The MIT (X11) License
335
336 =cut
This page took 0.054645 seconds and 4 git commands to generate.