File Coverage

blib/lib/Metabrik/Crypto/X509.pm
Criterion Covered Total %
statement 12 153 7.8
branch 0 88 0.0
condition 0 30 0.0
subroutine 4 15 26.6
pod 1 10 10.0
total 17 296 5.7


line stmt bran cond sub pod time code
1             #
2             # $Id$
3             #
4             # crypto::x509 Brik
5             #
6             package Metabrik::Crypto::X509;
7 1     1   654 use strict;
  1         2  
  1         30  
8 1     1   5 use warnings;
  1         2  
  1         28  
9              
10 1     1   6 use base qw(Metabrik::Shell::Command);
  1         1  
  1         1945  
11              
12             sub brik_properties {
13             return {
14 0     0 1   revision => '$Revision$',
15             tags => [ qw(unstable openssl ssl pki certificate) ],
16             author => 'GomoR ',
17             license => 'http://opensource.org/licenses/BSD-3-Clause',
18             attributes => {
19             datadir => [ qw(directory) ],
20             ca_name => [ qw(name) ],
21             ca_lc_name => [ qw(name) ],
22             ca_key => [ qw(key_file) ],
23             ca_cert => [ qw(cert_file) ],
24             ca_directory => [ qw(directory) ],
25             ca_conf => [ qw(conf_file) ],
26             use_passphrase => [ qw(0|1) ],
27             key_size => [ qw(bits) ],
28             },
29             attributes_default => {
30             capture_stderr => 1,
31             use_passphrase => 0,
32             key_size => 2048,
33             },
34             commands => {
35             ca_init => [ qw(name|OPTIONAL directory|OPTIONAL) ],
36             set_ca_attributes => [ qw(ca_name|OPTIONAL) ],
37             ca_show => [ qw(ca_name|OPTIONAL) ],
38             ca_sign_csr => [ qw(csr_file|OPTIONAL ca_name|OPTIONAL) ],
39             csr_new => [ qw(base_file use_passphrase|OPTIONAL) ],
40             cert_hash => [ qw(cert_file) ],
41             cert_verify => [ qw(cert_file ca_name|OPTIONAL) ],
42             cert_show => [ qw(cert_file) ],
43             parse_certificate_string => [ qw(string) ],
44             },
45             require_modules => {
46             'Crypt::X509' => [ ],
47             'Metabrik::File::Text' => [ ],
48             'Metabrik::System::File' => [ ],
49             },
50             require_binaries => {
51             'openssl', => [ ],
52             },
53             };
54             }
55              
56             sub set_ca_attributes {
57 0     0 0   my $self = shift;
58 0           my ($ca_name) = @_;
59              
60 0   0       $ca_name ||= $self->ca_name;
61 0 0         $self->brik_help_run_undef_arg('set_ca_attributes', $ca_name) or return;
62              
63 0           my $ca_lc_name = lc($ca_name);
64 0   0       my $ca_directory = $self->ca_directory || $self->datadir.'/'.$ca_lc_name;
65              
66 0           my $ca_conf = $ca_directory.'/'.$ca_lc_name.'.conf';
67 0           my $ca_cert = $ca_directory.'/'.$ca_lc_name.'.pem';
68 0           my $ca_key = $ca_directory.'/'.$ca_lc_name.'.key';
69 0           my $email = 'dummy@example.com';
70 0           my $organization = 'Dummy Org';
71              
72 0           $self->ca_name($ca_name);
73 0           $self->ca_lc_name($ca_lc_name);
74 0           $self->ca_conf($ca_conf);
75 0           $self->ca_directory($ca_directory);
76 0           $self->ca_cert($ca_cert);
77 0           $self->ca_key($ca_key);
78              
79 0           return 1;
80             }
81              
82             sub ca_init {
83 0     0 0   my $self = shift;
84 0           my ($ca_name, $ca_directory) = @_;
85              
86 0   0       $ca_name ||= $self->ca_name;
87 0   0       $ca_directory ||= $self->ca_directory;
88 0 0         $self->brik_help_run_undef_arg('ca_init', $ca_name) or return;
89 0 0         $self->brik_help_run_undef_arg('ca_init', $ca_directory) or return;
90              
91 0 0         $self->set_ca_attributes($ca_name)
92             or return $self->log->error("ca_init: set_ca_attributes failed");
93              
94 0 0         if (-d $ca_directory) {
95 0           return $self->log->error("ca_init: ca with name [$ca_name] already exists");
96             }
97             else {
98 0 0         mkdir($ca_directory)
99             or return $self->log->error("ca_init: mkdir1 failed with error [$!]");
100 0 0         mkdir($ca_directory.'/certs')
101             or return $self->log->error("ca_init: mkdir2 failed with error [$!]");
102 0 0         mkdir($ca_directory.'/csrs')
103             or return $self->log->error("ca_init: mkdir3 failed with error [$!]");
104              
105 0 0         my $ft = Metabrik::File::Text->new_from_brik_init($self) or return;
106 0 0         $ft->write('', $ca_directory.'/index.txt') or return;
107 0 0         $ft->write('01', $ca_directory.'/serial') or return;
108             }
109              
110 0           $self->log->verbose("ca_init: using directory [$ca_directory]");
111              
112 0           my $ca_conf = $self->ca_conf;
113 0           my $ca_cert = $self->ca_cert;
114 0           my $ca_key = $self->ca_key;
115 0           my $ca_lc_name = $self->ca_lc_name;
116 0           my $key_size = $self->key_size;
117              
118 0           my $email = 'dummy@example.com';
119 0           my $organization = 'Dummy Org';
120              
121 0           my $content = [
122             "[ ca ]",
123             "default_ca = $ca_lc_name",
124             "",
125             "[ $ca_lc_name ]",
126             "dir = $ca_directory",
127             "certificate = $ca_cert",
128             "database = \$dir/index.txt",
129             "#certs = \$dir/cert-csr",
130             "new_certs_dir = \$dir/certs",
131             "private_key = $ca_key",
132             "serial = \$dir/serial",
133             "default_crl_days = 7",
134             "default_days = 3650",
135             "#default_md = md5",
136             "default_md = sha1",
137             "policy = ${ca_lc_name}_policy",
138             "x509_extensions = certificate_extensions",
139             "",
140             "[ ${ca_lc_name}_policy ]",
141             "commonName = supplied",
142             "stateOrProvinceName = supplied",
143             "countryName = supplied",
144             "organizationName = supplied",
145             "organizationalUnitName = optional",
146             "emailAddress = optional",
147             "",
148             "[ certificate_extensions ]",
149             "basicConstraints = CA:false",
150             "",
151             "[ req ]",
152             "default_bits = $key_size",
153             "default_keyfile = $ca_key",
154             "#default_md = md5",
155             "default_days = 1800",
156             "default_md = sha1",
157             "prompt = no",
158             "distinguished_name = root_ca_distinguished_name",
159             "x509_extensions = root_ca_extensions",
160             "",
161             "[ root_ca_distinguished_name ]",
162             "commonName = $ca_name",
163             "stateOrProvinceName = Paris",
164             "countryName = FR",
165             "emailAddress = $email",
166             "organizationName = $organization",
167             "",
168             "[ root_ca_extensions ]",
169             "basicConstraints = CA:true",
170             ];
171              
172 0 0         my $ft = Metabrik::File::Text->new_from_brik_init($self) or return;
173 0           $ft->overwrite(1);
174 0 0         $ft->write($content, $ca_conf)
175             or return $self->log->error("ca_init: write failed");
176              
177 0           $self->log->verbose("ca_init: using conf file [$ca_conf] and cert [$ca_cert]");
178              
179 0           my $cmd = "openssl req -x509 -newkey rsa:$key_size ".
180             "-days 1800 -out $ca_cert -outform PEM -config $ca_conf";
181              
182 0 0         $self->system($cmd) or return;
183              
184 0 0         my $hash = $self->cert_hash($ca_cert) or return;
185              
186 0 0         my $sf = Metabrik::System::File->new_from_brik_init($self) or return;
187 0 0         $sf->link($ca_cert, $ca_directory.'/'.$hash.'.0') or return;
188              
189 0           return $ca_cert;
190             }
191              
192             sub ca_show {
193 0     0 0   my $self = shift;
194 0           my ($ca_name) = @_;
195              
196 0   0       $ca_name ||= $self->ca_name;
197 0 0         $self->brik_help_run_undef_arg('ca_show', $ca_name) or return;
198              
199 0 0         $self->set_ca_attributes($ca_name) or return;
200              
201 0           my $ca_cert = $self->ca_cert;
202 0           my $cmd = "openssl x509 -in $ca_cert -text -noout";
203 0           return $self->capture($cmd);
204             }
205              
206             sub csr_new {
207 0     0 0   my $self = shift;
208 0           my ($base_file, $use_passphrase) = @_;
209              
210 0   0       $use_passphrase ||= $self->use_passphrase;
211 0 0         $self->brik_help_run_undef_arg('csr_new', $base_file) or return;
212              
213 0           my $ca_directory = $self->ca_directory;
214 0           my $csr_cert = $ca_directory.'/csrs/'.$base_file.'.csr';
215 0           my $csr_key = $ca_directory.'/csrs/'.$base_file.'.key';
216 0           my $key_size = $self->key_size;
217              
218 0 0         if (-f $csr_cert) {
219 0           return $self->log->error("csr_new: file [$csr_cert] already exists");
220             }
221              
222 0           my $cmd = "openssl req -newkey rsa:$key_size -keyout $csr_key -keyform PEM ".
223             "-out $csr_cert -outform PEM";
224              
225 0 0         if (! $use_passphrase) {
226 0           $cmd .= " -nodes";
227             }
228              
229 0           $self->system($cmd);
230 0 0         if ($?) {
231 0           return $self->log->error("csr_new: system failed");
232             }
233              
234 0           return [ $csr_cert, $csr_key ];
235             }
236              
237             sub ca_sign_csr {
238 0     0 0   my $self = shift;
239 0           my ($csr_cert, $ca_name) = @_;
240              
241 0   0       $ca_name ||= $self->ca_name;
242 0 0         $self->brik_help_run_undef_arg('ca_sign_csr', $csr_cert) or return;
243 0 0         $self->brik_help_run_file_not_found('ca_sign_csr', $csr_cert) or return;
244 0 0         $self->brik_help_run_undef_arg('ca_sign_csr', $ca_name) or return;
245              
246 0 0         $self->set_ca_attributes($ca_name) or return;
247              
248 0           my $ca_directory = $self->ca_directory;
249 0           my $ca_conf = $self->ca_conf;
250              
251 0 0         my $sf = Metabrik::System::File->new_from_brik_init($self) or return;
252 0 0         my $base_file = $sf->basefile($csr_cert) or return;
253 0           $base_file =~ s/.[^\.]+$//; # Remove extension
254              
255 0           my $signed_cert = $ca_directory.'/certs/'.$base_file.'.signed.pem';
256 0           my $cmd = "openssl ca -in $csr_cert -out $signed_cert -config $ca_conf";
257 0           $self->log->verbose("ca_sign_csr: cmd[$cmd]");
258 0           $self->system($cmd);
259 0 0         if ($?) {
260 0           return $self->log->error("ca_sign_csr: system failed");
261             }
262              
263 0           return $signed_cert;
264             }
265              
266             #
267             # Returns hash ID of a certificate.
268             #
269             sub cert_hash {
270 0     0 0   my $self = shift;
271 0           my ($cert_file) = @_;
272              
273 0 0         $self->brik_help_run_undef_arg('cert_hash', $cert_file) or return;
274 0 0         $self->brik_help_run_file_not_found('cert_hash', $cert_file) or return;
275              
276 0           my $cmd = "openssl x509 -noout -hash -in \"$cert_file\"";
277 0 0         my $lines = $self->capture($cmd) or return;
278              
279 0 0         if (@$lines == 0) {
280 0           return $self->log->error('cert_hash: unable to get hash');
281             }
282              
283 0           return $lines->[0];
284             }
285              
286             sub cert_verify {
287 0     0 0   my $self = shift;
288 0           my ($cert_file, $ca_name) = @_;
289              
290 0   0       $ca_name ||= $self->ca_name;
291 0 0         $self->brik_help_run_undef_arg('cert_verify', $cert_file) or return;
292 0 0         $self->brik_help_run_file_not_found('cert_verify', $cert_file) or return;
293 0 0         $self->brik_help_run_undef_arg('cert_verify', $ca_name) or return;
294              
295 0 0         $self->set_ca_attributes($ca_name) or return;
296              
297 0           my $ca_directory = $self->ca_directory;
298              
299 0           my $cmd = "openssl verify -CApath $ca_directory $cert_file";
300              
301 0           return $self->capture($cmd);
302             }
303              
304             sub cert_show {
305 0     0 0   my $self = shift;
306 0           my ($cert_file) = @_;
307              
308 0   0       $cert_file ||= $self->cert_file;
309 0 0         $self->brik_help_run_undef_arg('cert_show', $cert_file) or return;
310 0 0         $self->brik_help_run_file_not_found('cert_show', $cert_file) or return;
311              
312 0           my $cmd = "openssl x509 -in $cert_file -text -noout";
313 0           return $self->capture($cmd);
314             }
315              
316             sub parse_certificate_string {
317 0     0 0   my $self = shift;
318 0           my ($string) = @_;
319              
320 0 0         $self->brik_help_run_undef_arg('parse_certificate_string', $string)
321             or return;
322              
323 0 0         if (! length($string)) {
324 0           return $self->log->error("parse_certificate_string: empty string found");
325             }
326              
327             # Patch fonction to add a defined() check.
328             {
329 1     1   8 no warnings 'redefine';
  1         4  
  1         254  
  0            
330              
331             *Crypt::X509::pubkey_components = sub {
332 0     0     my $self = shift;
333 0           my $pubkeyalg = $self->PubKeyAlg();
334 0 0 0       if (defined($pubkeyalg) && $pubkeyalg eq 'RSA') {
335 0           my $parser = Crypt::X509::_init('RSAPubKeyInfo');
336             my $values = $parser->decode(
337 0           $self->{tbsCertificate}{subjectPublicKeyInfo}{subjectPublicKey}[0]
338             );
339 0           return $values;
340             }
341             else {
342 0           return undef;
343             }
344 0           };
345             };
346              
347 0           my $decoded = Crypt::X509->new(cert => $string);
348 0 0         if ($decoded->error) {
349 0           return $self->log->error("parse_certificate_string: failed: ".
350             $decoded->error);
351             }
352              
353 0           return $decoded;
354             }
355              
356             1;
357              
358             __END__