-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathCRLWriter.pm
369 lines (314 loc) · 11.4 KB
/
CRLWriter.pm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
#
# @(#)$Id$
#
# ###########################################################################
#
#
# Syntax:
# CRLWriter->new( [name [,index]] );
# CRLWriter->setTA( trustanchor );
# CRLWriter->setIndex( index );
#
package CRLWriter;
use strict;
use File::Basename;
use File::Temp qw/ tempfile /;
require OSSL and import OSSL unless defined &OSSL::new;
require base64 and import base64 unless defined &base64::b64encode;
use vars qw/ $log $cnf /;
sub new {
my $obref = {}; bless $obref;
my $self = shift;
$self = $obref;
my $name = shift;
my $index = shift;
$self->setTA($name) if defined $name;
$self->setIndex($name) if defined $index;
return $self;
}
sub getName($) {
my $self = shift;
return 0 unless defined $self;
return $self->{"ta"}->getAnchorName;
}
sub setTA($$) {
my $self = shift;
my ($ta) = shift;
return 0 unless defined $ta and defined $self;
$ta->{"anchorname"} or
$::log->err("CRLWriter::setTA called without uninitialised trust anchor")
and return 0;
$self->{"ta"} = $ta;
return 1;
}
sub setIndex($$) {
my $self = shift;
my ($index) = shift;
return 0 unless defined $self;
$self->{"ta"} or
$::log->err("CRLWriter::setIndex called without a loaded TA") and
return 0;
my $ta = $self->{"ta"};
$ta->{"crlurls"} or
$::log->err("CRLWriter::setIndex called with uninitialised TA") and
return 0;
! defined $index and delete $self->{"index"} and return 1;
$index < 0 and
$::log->err("CRLWriter::setIndex called with invalid index $index") and
return 0;
$index > $#{$ta->{"crlurls"}} and
$::log->err("CRLWriter::setIndex index $index too large") and
return 0;
$self->{"index"} = $index;
return 1;
}
sub updatefile($$%) {
my $file = shift;
my $content = shift;
my %flags = @_;
$content or return undef;
$file or
$::log->err("Cannot write content to undefined path") and return undef;
my ( $basename, $path, $suffix ) = fileparse($file);
# get content and do a comparison. If data identical, touch only
# to update mtime (other tools like NGC Nagios use this mtime semantics)
#
my $olddata;
my $mytime;
-f $file and do {
$mytime = (stat(_))[9];
{
open OLDFILE,'<',$file or
$::log->err("Cannot make backup of $file: $!") and return undef;
binmode OLDFILE; local $/;
$olddata = <OLDFILE>; close OLDFILE;
}
};
if ( $flags{"BACKUP"} and $olddata ) {
if ( -w $path ) {
-e "$file~" and ( unlink "$file~" or
$::log->warn("Cannot remove old backup $file~: $!") and return undef);
if (open BCKFILE,'>',"$file~" ) {
print BCKFILE $olddata;
close BCKFILE;
utime $mytime,$mytime, "$file~";
} else {
$::log->warn("Cannot reate backup $file~: $!");
}
} else {
$::log->warn("Cannot make backup, $path not writable");
}
}
defined $olddata and $olddata eq $content and do {
$::log->verb(4,"$file unchanged - touch only");
utime time,time,$file and return 1;
$::log->warn("Touch of $file failed, CRL unmodified");
return 0;
};
# write new CRL to file ($file in $path) - attempting to do
# an atomic action to prevent a reace condition with clients
# but do not insist if the $path is not writable for new files
my $tmpcrlmode=((stat $file)[2] || 0644) & 07777;
$::log->verb(5,"TMP file for $file mode $tmpcrlmode");
my $tmpcrl = File::Temp->new(DIR => $path, SUFFIX => '.tmp',
PERMS => $tmpcrlmode, UNLINK => 1);
if ( defined $tmpcrl ) { # we could create a tempfile next to current
print $tmpcrl $content or
$::log->err("Write to $tmpcrl: $!") and return undef;
# atomic move, but no need to restore from backup on failure
# and the unlink on destroy is implicit
chmod $tmpcrlmode,$tmpcrl or
$::log->err("chmod on $tmpcrl (to $tmpcrlmode): $!") and
return undef;
rename($tmpcrl, $file) or
$::log->err("rename $tmpcrl to $file: $!") and return undef;
# file was successfully renamed, so nothing left to unlink
$tmpcrl->unlink_on_destroy( 0 );
} elsif ( open FH,'>',$file ) {
# no adjecent write possible, fall back to rewrite
print FH $content or
$::log->err("Write to $file: $!") and return undef;
close FH or
$::log->err("Close on write of $file: $!") and return undef;
} else { # something went wrong in opening the file for write,
# so try and restore backup if that was selected
$::log->err("Open for write of $file: $!");
$flags{"BACKUP"} and ! -s "$file" and -s "$file~" and do {
#file has been clobbed, but backup OK
unlink "$file" and link "$file~","$file" and unlink "$file~" or
$::log->err("Restore of backup $file failed: $!");
};
return undef;
}
return 1;
}
sub writePEM($$$$) {
my $self = shift;
my $idx = shift;
my $data = shift;
my $ta = shift;
defined $idx and $data and $ta or
$::log->err("CRLWriter::writePEM: missing index or data") and return 0;
my $output = $::cnf->{_}->{"output"};
$output = $::cnf->{_}->{"output_pem"} if defined $::cnf->{_}->{"output_pem"};
$output and -d $output or
$::log->err("PEM target directory $output invalid") and return 0;
my $filename = "$output/".$ta->{"nametemplate_pem"};
$filename =~ s/\@R\@/$idx/g;
my %flags = ();
$::cnf->{_}->{"backups"} and $flags{"BACKUP"} = 1;
if ($data !~ /\n$/sm) {
$::log->verb(5,"Appending newline to short PEM file",$filename);
$data="$data\n";
}
$::log->verb(3,"Writing PEM file",$filename);
&updatefile($filename,$data,%flags) or return 0;
return 1;
}
sub writeDER($$$$) {
my $self = shift;
my $idx = shift;
my $data = shift;
my $ta = shift;
defined $idx and $data and $ta or
$::log->err("CRLWriter::writeDER: missing index or data") and return 0;
my $output = $::cnf->{_}->{"output"};
$output = $::cnf->{_}->{"output_der"} if defined $::cnf->{_}->{"output_der"};
$output and -d $output or
$::log->err("DER target directory $output invalid") and return 0;
my $filename = "$output/".$ta->{"nametemplate_der"};
$filename =~ s/\@R\@/$idx/g;
my %flags = ();
$::cnf->{_}->{"backups"} and $flags{"BACKUP"} = 1;
my $openssl=OSSL->new();
my ($der,$errors) = $openssl->Exec3($data,qw/crl -inform PEM -outform DER/);
$errors or not $der and
$::log->err("Data count not be converted to DER: $errors") and return 0;
$::log->verb(3,"Writing DER file",$filename);
&updatefile($filename,$der,%flags) or return 0;
return 1;
}
sub writeOpenSSL($$$$) {
my $self = shift;
my $idx = shift;
my $data = shift;
my $ta = shift;
defined $idx and $data and $ta or
$::log->err("CRLWriter::writeOpenSSL: missing index, data or ta") and
return 0;
my $output = $::cnf->{_}->{"output"};
$output = $::cnf->{_}->{"output_openssl"} if
defined $::cnf->{_}->{"output_openssl"};
$output and -d $output or
$::log->err("OpenSSL target directory $output invalid") and return 0;
my $openssl=OSSL->new();
# guess the hash name or names from OpenSSL
# if mode is dual (and OpenSSL1 installed) write two files
my $opensslversion = $openssl->getVersion() or return 0;
my ($cmddata,$errors);
my @hashes = ();
if ( $opensslversion ge "1" and $::cnf->{_}->{"opensslmode"} eq "dual" ) {
$::log->verb(5,"OpenSSL version 1 dual-mode enabled");
# this mode needs the ta cafile to get both hashes, since these
# can only be extracted by the x509 subcommand from a CA ...
($cmddata,$errors) = $openssl->Exec3(undef,
qw/x509 -noout -subject_hash -subject_hash_old -in/,
$ta->{"cafile"}[0]);
$cmddata or
$::log->err("OpenSSL cannot extract hashes from",$ta->{"cafile"}[0]) and
return 0;
@hashes = split(/[\s\n]+/,$cmddata);
} else {
$::log->verb(5,"OpenSSL version 1 single-mode or pre-1.0 style");
($cmddata,$errors) = $openssl->Exec3($data,qw/crl -noout -hash/);
$cmddata or
$::log->err("OpenSSL cannot extract hashes from CRL for",
$ta->{"alias"}.'/'.$idx
) and
return 0;
@hashes = split(/[\s\n]+/,$cmddata);
}
my %flags = ();
$::cnf->{_}->{"backups"} and $flags{"BACKUP"} = 1;
foreach my $hash ( @hashes ) {
my $filename = "$output/$hash.r$idx";
$::log->verb(3,"Writing OpenSSL file",$filename);
&updatefile($filename,$data,%flags) or return 0;
}
return 1;
}
sub writeNSS($$$$) {
my $self = shift;
my $idx = shift;
my $data = shift;
my $ta = shift;
defined $idx and $data and $ta or
$::log->err("CRLWriter::writeNSS: missing index, data or ta") and return 0;
my $output = $::cnf->{_}->{"output"};
$output = $::cnf->{_}->{"output_nss"} if defined $::cnf->{_}->{"output_nss"};
$output and -d $output or
$::log->err("NSS target directory $output invalid") and return 0;
my $dbprefix="";
$dbprefix = $::cnf->{_}->{"nssdbprefix"}
if defined $::cnf->{_}->{"nssdbprefix"};
my $filename = "$output/$dbprefix";
# the crlutil tool requires the DER formatted cert in a file
my $tmpdir = $::cnf->{_}->{exec3tmpdir} || $ENV{"TMPDIR"} || '/tmp';
my ($derfh,$dername) = tempfile("fetchcrl3der.XXXXXX",
DIR=>$tmpdir, UNLINK=>1);
(my $b64data = $data) =~ s/-[^\n]+//gm;
$b64data =~ s/\s+//gm;
print $derfh base64::b64decode($b64data); # der is decoded PEM :-)
my $cmd = "crlutil -I -d \"$output\" -P \"$dbprefix\" ";
$::cnf->{_}->{nonssverify} and $cmd .= "-B ";
$cmd .= "-n ".$ta->{"alias"}.'.'.$idx." ";
$cmd .= "-i \"$dername\"";
my $result = `$cmd 2>&1`;
unlink $dername;
if ( $? != 0 ) {
$::log->err("Cannot update NSSDB filename: $result");
} else {
$::log->verb(3,"WriteNSS: ".$ta->{"alias"}.'.'.$idx." added to $filename");
}
return 1;
}
sub writeall($) {
my $self = shift;
return 0 unless defined $self;
$self->{"ta"} or
$::log->err("CRLWriter::setIndex called without a loaded TA") and
return 0;
my $ta = $self->{"ta"};
$ta->{"crlurls"} or
$::log->err("CRLWriter::setIndex called with uninitialised TA") and
return 0;
$::log->verb(2,"Writing CRLs for",$ta->{"anchorname"});
my $completesuccess = 1;
for ( my $idx = 0 ; $idx <= $#{$ta->{"crl"}} ; $idx++ ) {
$ta->{"crl"}[$idx]{"pemdata"} or
$::log->verb(3,"Ignored CRL $idx skipped") and
next; # ignore empty crls, leave these in place
my $writeAttempt = 0;
my $writeSuccess = 0;
( grep /^pem$/, @{$::cnf->{_}->{formats_}} ) and ++$writeAttempt and
$writeSuccess += $self->writePEM($idx,$ta->{"crl"}[$idx]{"pemdata"},$ta);
( grep /^der$/, @{$::cnf->{_}->{formats_}} ) and ++$writeAttempt and
$writeSuccess += $self->writeDER($idx,$ta->{"crl"}[$idx]{"pemdata"},$ta);
( grep /^openssl$/, @{$::cnf->{_}->{formats_}} ) and ++$writeAttempt and
$writeSuccess += $self->writeOpenSSL($idx,
$ta->{"crl"}[$idx]{"pemdata"},$ta);
( grep /^nss$/, @{$::cnf->{_}->{formats_}} ) and ++$writeAttempt and
$writeSuccess += $self->writeNSS($idx,$ta->{"crl"}[$idx]{"pemdata"},$ta);
if ( $writeSuccess == $writeAttempt ) {
$::log->verb(4,"LastWrite time (mtime) set to current time");
$ta->{"crl"}[$idx]{"state"}{"mtime"} = time;
} else {
$::log->warn("Partial updating ($writeSuccess of $writeAttempt) for",
$ta->{"anchorname"},
"CRL $idx: mtime not updated");
}
$completesuccess &&= ($writeSuccess == $writeAttempt);
}
return $completesuccess;
}
1;