diff --git a/src/PVE/ACME.pm b/src/PVE/ACME.pm index 265482d..57578d7 100644 --- a/src/PVE/ACME.pm +++ b/src/PVE/ACME.pm @@ -442,17 +442,50 @@ sub deactivate_authorization { # Get certificate # GET-as-POST to order's certificate URL +# if $root is specified, attempts to find a matching (alternate) chain # Expects a '200 OK' reply # returns certificate chain in PEM format sub get_certificate { - my ($self, $order) = @_; + my ($self, $order, $root) = @_; $self->fatal("no certificate URL available (yet?)", $order) if !$order->{certificate}; + my $check_root = sub { + my ($chain) = @_; + + my @certs = PVE::Certificate::split_pem($chain); + my $root_pem = $certs[-1]; + + my ($file, $fh) = PVE::Tools::tempfile_contents($root_pem); + my $info = PVE::Certificate::get_certificate_info($file); + + return defined($info->{issuer}) && $info->{issuer} =~ m/\Q$root\E/i; + }; + my $r = $self->do(POST => $order->{certificate}, ''); my $return = eval { + # default chain my $res = __get_result($r, 200, 1); + if ($root && !$check_root->($res)) { + # alternate chains if requested and default didn't match + $res = undef; + my @links = $r->header('link'); + for my $link (@links) { + if ($link =~ /^<(.*)>;rel="alternate"$/) { + my $url = $1; + my $chain = eval { __get_result($self->do(POST => $url, ''), 200, 1); }; + die "failed to retrieve alternate chain from '$url' - $@\n" if $@; + if ($check_root->($chain)) { + $res = $chain; + last; + } + } + } + die "no matching alternate chain for '$root' returned by server\n" + if !defined($res); + } + if ($res =~ /^(-----BEGIN CERTIFICATE-----)(.+)(-----END CERTIFICATE-----)$/s) { # untaint return $1 . $2 . $3; }