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
|
#!/usr/bin/perl
package SPIN::LE;
use strict;
use warnings;
use autodie qw(:all);
use Date::Parse;
use LWP::UserAgent;
{
our @restart_services_cmd = qw(sudo service httpd graceful);
our @openssl_bin = qw(/usr/bin/openssl);
our @acme_tiny_bin = qw(/usr/bin/python acme-tiny/acme_tiny.py);
our $account_key_path = 'data/account.key';
our $challenges_dir = 'data/challenges/';
our $days_left_limit = 14;
our $csr_dir = 'data';
require 'config.pm' if -e 'config.pm';
sub get_intermediate_cert_url {
my $crt_fn = shift;
open my $openssl, '-|', @openssl_bin, 'x509', '-in', $crt_fn, '-text', '-noout';
while(my $line = <$openssl>) {
return $1 if $line =~ m|^\s*CA Issuers - URI:(.*?)\s*$|;
}
die("Cert URL not found");
}
sub get_intermediate_cert {
my $crt_fn = shift;
my $pem_fn = shift;
my $der_fn = "$pem_fn.tmp";
my $url = get_intermediate_cert_url($crt_fn);
my $ua = LWP::UserAgent->new;
my $response = $ua->mirror($url, $der_fn);
die("Failed to download $url\n") if !($response->is_success);
system(@openssl_bin, 'x509', '-inform', 'der', '-in', $der_fn, '-out', $pem_fn);
unlink($der_fn);
}
sub get_cert {
my ($csr_fn, $crt_fn, $int_crt_fn) = @_;
open my $acme_tiny, '-|', @acme_tiny_bin,
'--account-key', $account_key_path, '--csr', $csr_fn,
'--acme-dir', $challenges_dir;
my $cert_start = 0;
my $cert_end = 0;
my $lines = '';
while (my $line = <$acme_tiny>) {
print $line;
$cert_start = 1 if $line =~ /^-----BEGIN CERTIFICATE-----$/;
$cert_end = 1 if $line =~ /^-----END CERTIFICATE-----$/;
$lines .= $line;
}
if ($cert_start && $cert_end) {
my $tmp_fn = "$crt_fn.tmp";
my $int_tmp_fn = "$int_crt_fn.tmp";
open my $crt, '>', $tmp_fn;
print $crt $lines or die("Write failed: $!\n");
close $crt;
get_intermediate_cert($tmp_fn, $int_tmp_fn);
rename $tmp_fn,$crt_fn;
rename $int_tmp_fn,$int_crt_fn;
} else {
die("Output doesn't look like a certificate\n");
}
}
sub renew {
my %fmap = @_;
my $certs_updated = 0;
for my $csr_fn (keys %fmap) {
my $crt_fn = $fmap{$csr_fn}->[0];
my $int_crt_fn = $fmap{$csr_fn}->[1];
if (!-e $crt_fn) {
print "Getting new certificate $crt_fn\n";
get_cert($csr_fn, $crt_fn, $int_crt_fn);
next;
}
open my $openssl, '-|', @openssl_bin, 'x509', '-enddate', '-noout', '-in', $crt_fn;
my $ret = <$openssl>;
if ($ret =~ /^notAfter=(.*)$/) {
my $date_str = $1;
my $time = str2time($date_str);
if (defined $time) {
if ($time - time < $days_left_limit*24*60*60) {
my $days = (($time - time)/60/60/24)|0;
print "Certificate $crt_fn ";
if ($days >= 1) { print "will expire in $days days" }
elsif ($days == 0) { print "will expire today" }
elsif ($days == -1) { print "expired yesterday" }
else { printf "expired %d days ago", -$days };
print "\nRenewing...\n";
get_cert($csr_fn, $crt_fn, $int_crt_fn);
$certs_updated++;
}
} else {
warn "Failed parsing expiration date for $crt_fn: openssl returned $ret\n";
}
} else {
warn "Failed getting expiration date for $crt_fn: openssl returned $ret\n";
}
}
return $certs_updated;
}
sub run {
opendir(my $dh, $csr_dir);
my %fmap =
map { my $base = $_; $base =~ s/\.csr$//; $_ => [ "$base.crt", "$base-int.crt" ] }
map { "$csr_dir/$_" }
grep { /\.csr$/ }
readdir $dh;
system(@restart_services_cmd) if renew(%fmap);
}
}
__PACKAGE__->run() unless caller();
|