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