]> Dogcows Code - chaz/groupsecret/blob - lib/App/GroupSecret/Crypt.pm
add support for ssh-keygen without -E flag
[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 = '9999.999'; # 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 =func generate_secure_random_bytes
33
34 $bytes = generate_secure_random_bytes($num_bytes);
35
36 Get a certain number of secure random bytes.
37
38 =cut
39
40 sub generate_secure_random_bytes {
41 my $size = shift or _usage(q{generate_secure_random_bytes($num_bytes)});
42
43 my @cmd = ($OPENSSL, 'rand', $size);
44
45 my $out;
46 my $pid = open2($out, undef, @cmd);
47
48 waitpid($pid, 0);
49 my $status = $?;
50
51 my $exit_code = $status >> 8;
52 _croak 'Failed to generate secure random bytes' if $exit_code != 0;
53
54 return do { local $/; <$out> };
55 }
56
57 =func read_openssh_public_key
58
59 $pem_public_key = read_openssh_public_key($public_key_filepath);
60
61 Read a RFC4716 (SSH2) public key from a file, converting it to PKCS8 (PEM).
62
63 =cut
64
65 sub read_openssh_public_key {
66 my $filepath = shift or _usage(q{read_openssh_public_key($filepath)});
67
68 my @cmd = ($SSH_KEYGEN, qw{-e -m PKCS8 -f}, $filepath);
69
70 my $out;
71 my $pid = open2($out, undef, @cmd);
72
73 waitpid($pid, 0);
74 my $status = $?;
75
76 my $exit_code = $status >> 8;
77 _croak 'Failed to read OpenSSH public key' if $exit_code != 0;
78
79 return do { local $/; <$out> };
80 }
81
82 =func read_openssh_key_fingerprint
83
84 $fingerprint = read_openssh_key_fingerprint($filepath);
85
86 Get the fingerprint of an OpenSSH private or public key.
87
88 =cut
89
90 sub read_openssh_key_fingerprint {
91 my $filepath = shift or _usage(q{read_openssh_key_fingerprint($filepath)});
92
93 # try with the -E flag first
94 my @cmd = ($SSH_KEYGEN, qw{-l -E md5 -f}, $filepath);
95
96 my $out;
97 my $err = gensym;
98 my $pid = open3(undef, $out, $err, @cmd);
99
100 waitpid($pid, 0);
101 my $status = $?;
102
103 my $exit_code = $status >> 8;
104 if ($exit_code != 0) {
105 my $error_str = do { local $/; <$err> };
106 _croak 'Failed to read SSH2 key fingerprint' if $error_str !~ /unknown option -- E/s;
107
108 @cmd = ($SSH_KEYGEN, qw{-l -f}, $filepath);
109
110 undef $out;
111 $pid = open2($out, undef, @cmd);
112
113 waitpid($pid, 0);
114 $status = $?;
115
116 $exit_code = $status >> 8;
117 _croak 'Failed to read SSH2 key fingerprint' if $exit_code != 0;
118 }
119
120 my $line = do { local $/; <$out> };
121 chomp $line;
122
123 my ($bits, $fingerprint, $comment, $type) = $line =~ m!^(\d+) (?:MD5:)?([^ ]+) (.*) \(([^\)]+)\)$!;
124
125 $fingerprint =~ s/://g;
126
127 return {
128 bits => $bits,
129 fingerprint => $fingerprint,
130 comment => $comment,
131 type => lc($type),
132 };
133 }
134
135 =func decrypt_rsa
136
137 $plaintext = decrypt_rsa($ciphertext_filepath, $private_key_filepath);
138 $plaintext = decrypt_rsa(\$ciphertext, $private_key_filepath);
139 decrypt_rsa($ciphertext_filepath, $private_key_filepath, $plaintext_filepath);
140 decrypt_rsa(\$ciphertext, $private_key_filepath, $plaintext_filepath);
141
142 Do RSA decryption. Turn ciphertext into plaintext.
143
144 =cut
145
146 sub decrypt_rsa {
147 my $filepath = shift or _usage(q{decrypt_rsa($filepath, $keypath)});
148 my $privkey = shift or _usage(q{decrypt_rsa($filepath, $keypath)});
149 my $outfile = shift;
150
151 my $temp;
152 if (ref $filepath eq 'SCALAR') {
153 $temp = File::Temp->new(UNLINK => 1);
154 print $temp $$filepath;
155 close $temp;
156 $filepath = $temp->filename;
157 }
158
159 my @cmd = ($OPENSSL, qw{rsautl -decrypt -oaep -in}, $filepath, '-inkey', $privkey);
160 push @cmd, ('-out', $outfile) if $outfile;
161
162 my $out;
163 my $pid = open2($out, undef, @cmd);
164
165 waitpid($pid, 0);
166 my $status = $?;
167
168 my $exit_code = $status >> 8;
169 _croak 'Failed to decrypt ciphertext' if $exit_code != 0;
170
171 return do { local $/; <$out> };
172 }
173
174 =func encrypt_rsa
175
176 $ciphertext = decrypt_rsa($plaintext_filepath, $public_key_filepath);
177 $ciphertext = decrypt_rsa(\$plaintext, $public_key_filepath);
178 decrypt_rsa($plaintext_filepath, $public_key_filepath, $ciphertext_filepath);
179 decrypt_rsa(\$plaintext, $public_key_filepath, $ciphertext_filepath);
180
181 Do RSA encryption. Turn plaintext into ciphertext.
182
183 =cut
184
185 sub encrypt_rsa {
186 my $filepath = shift or _usage(q{encrypt_rsa($filepath, $keypath)});
187 my $pubkey = shift or _usage(q{encrypt_rsa($filepath, $keypath)});
188 my $outfile = shift;
189
190 my $temp1;
191 if (ref $filepath eq 'SCALAR') {
192 $temp1 = File::Temp->new(UNLINK => 1);
193 print $temp1 $$filepath;
194 close $temp1;
195 $filepath = $temp1->filename;
196 }
197
198 my $key = read_openssh_public_key($pubkey);
199
200 my $temp2 = File::Temp->new(UNLINK => 1);
201 print $temp2 $key;
202 close $temp2;
203 my $keypath = $temp2->filename;
204
205 my @cmd = ($OPENSSL, qw{rsautl -encrypt -oaep -pubin -inkey}, $keypath, '-in', $filepath);
206 push @cmd, ('-out', $outfile) if $outfile;
207
208 my $out;
209 my $pid = open2($out, undef, @cmd);
210
211 waitpid($pid, 0);
212 my $status = $?;
213
214 my $exit_code = $status >> 8;
215 _croak 'Failed to encrypt plaintext' if $exit_code != 0;
216
217 return do { local $/; <$out> };
218 }
219
220 =func decrypt_aes_256_cbc
221
222 $plaintext = decrypt_aes_256_cbc($ciphertext_filepath, $secret);
223 $plaintext = decrypt_aes_256_cbc(\$ciphertext, $secret);
224 decrypt_aes_256_cbc($ciphertext_filepath, $secret, $plaintext_filepath);
225 decrypt_aes_256_cbc(\$ciphertext, $secret, $plaintext_filepath);
226
227 Do symmetric decryption. Turn ciphertext into plaintext.
228
229 =cut
230
231 sub decrypt_aes_256_cbc {
232 my $filepath = shift or _usage(q{decrypt_aes_256_cbc($ciphertext, $secret)});
233 my $secret = shift or _usage(q{decrypt_aes_256_cbc($ciphertext, $secret)});
234 my $outfile = shift;
235
236 my $temp;
237 if (ref $filepath eq 'SCALAR') {
238 $temp = File::Temp->new(UNLINK => 1);
239 print $temp $$filepath;
240 close $temp;
241 $filepath = $temp->filename;
242 }
243
244 my @cmd = ($OPENSSL, qw{aes-256-cbc -d -pass stdin -md sha256 -in}, $filepath);
245 push @cmd, ('-out', $outfile) if $outfile;
246
247 my ($in, $out);
248 my $pid = open2($out, $in, @cmd);
249
250 print $in $secret;
251 close($in);
252
253 waitpid($pid, 0);
254 my $status = $?;
255
256 my $exit_code = $status >> 8;
257 _croak 'Failed to decrypt ciphertext' if $exit_code != 0;
258
259 return do { local $/; <$out> };
260 }
261
262 =func encrypt_aes_256_cbc
263
264 $ciphertext = encrypt_aes_256_cbc($plaintext_filepath, $secret);
265 $ciphertext = encrypt_aes_256_cbc(\$plaintext, $secret);
266 encrypt_aes_256_cbc($plaintext_filepath, $secret, $ciphertext_filepath);
267 encrypt_aes_256_cbc(\$plaintext, $secret, $ciphertext_filepath);
268
269 Do symmetric encryption. Turn plaintext into ciphertext.
270
271 =cut
272
273 sub encrypt_aes_256_cbc {
274 my $filepath = shift or _usage(q{encrypt_aes_256_cbc($plaintext, $secret)});
275 my $secret = shift or _usage(q{encrypt_aes_256_cbc($plaintext, $secret)});
276 my $outfile = shift;
277
278 my $temp;
279 if (ref $filepath eq 'SCALAR') {
280 $temp = File::Temp->new(UNLINK => 1);
281 print $temp $$filepath;
282 close $temp;
283 $filepath = $temp->filename;
284 }
285
286 my @cmd = ($OPENSSL, qw{aes-256-cbc -pass stdin -md sha256 -in}, $filepath);
287 push @cmd, ('-out', $outfile) if $outfile;
288
289 my ($in, $out);
290 my $pid = open2($out, $in, @cmd);
291
292 print $in $secret;
293 close($in);
294
295 waitpid($pid, 0);
296 my $status = $?;
297
298 my $exit_code = $status >> 8;
299 _croak 'Failed to encrypt plaintext' if $exit_code != 0;
300
301 return do { local $/; <$out> };
302 }
303
304 1;
This page took 0.045597 seconds and 4 git commands to generate.