# b) # pick $challenge from $authorization->{challenges} according to desired type
# c) my $key_auth = $acme->key_authorization($challenge->{token});
# d) # setup challenge validation according to specification
-# e) $acme->request_challenge_validation($challenge->{url}, $key_auth);
+# e) $acme->request_challenge_validation($challenge->{url});
# f) # poll $acme->get_authorization($auth_url) until status is 'valid'
# 5) # generate CSR in PEM format
# 6) $acme->finalize_order($order, $csr);
}
sub fromjs($) {
- return from_json($_[0]);
+ my ($data) = @_;
+ ($data) = ($data =~ /^(.*)$/s); # untaint from_json croaks on error anyways.
+ return from_json($data);
}
sub fatal($$;$$) {
# 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 { __get_result($r, 200, 1); };
+ 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;
+ }
+ die "Server reply does not look like a PEM encoded certificate\n";
+ };
$self->fatal("POST of '$order->{certificate}' failed - $@", $r) if $@;
return $return;
}
# call after validation has been setup
# returns (potentially updated) challenge object
sub request_challenge_validation {
- my ($self, $url, $key_authorization) = @_;
-
- my $req = { keyAuthorization => $key_authorization };
+ my ($self, $url) = @_;
- my $r = $self->do(POST => $url, $req);
+ my $r = $self->do(POST => $url, {});
my $return = eval { __get_result($r, 200); };
$self->fatal("POST to '$url' failed - $@", $r) if $@;
return $return;
}
-# return all availible subplugins from the plugins
-sub get_subplugins {
-
- my $tmp = [];
- my $plugins = PVE::ACME::Challenge->lookup_types();
-
- foreach my $plugin_name (@$plugins) {
- my $plugin = PVE::ACME::Challenge->lookup($plugin_name);
- push @$tmp, $plugin->get_subplugins();
- }
-
- my $subplugins = [];
- foreach my $array (@$tmp) {
- foreach my $subplugin ( @$array) {
- push @$subplugins, $subplugin;
- }
- }
-
- return $subplugins;
-}
-
# actually 'do' a $method request on $url
# $data: input for JWS, optional
# $use_jwk: use JWK instead of KID in JWD (see sub jws)