file_get_contents
);
+use PVE::ACME::DNSChallenge;
+
Crypt::OpenSSL::RSA->import_random_seed();
my $LETSENCRYPT_STAGING = 'https://acme-staging-v02.api.letsencrypt.org/directory';
# 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($$;$$) {
return bless $self, $class;
}
+sub set_proxy($$) {
+ my ($self, $proxy) = @_;
+
+ $self->{ua}->proxy('https', $proxy);
+}
+
# RS256: PKCS#1 padding, no OAEP, SHA256
my $configure_key = sub {
my ($key) = @_;
# 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 ($self, $url) = @_;
- my $req = { keyAuthorization => $key_authorization };
-
- 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;