summaryrefslogtreecommitdiff
path: root/renew.pl
blob: 2cdb8e8e88d0c8eec6b652d973e8d0c76956fc0a (plain)
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();