forked from Proxmox/proxmox-perl-rs
b6274a49a5
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
173 lines
5.9 KiB
Perl
173 lines
5.9 KiB
Perl
#!/usr/bin/env perl
|
|
|
|
use v5.28.0;
|
|
use Data::Dumper;
|
|
|
|
use lib '.';
|
|
use PMG::RS::Acme;
|
|
use PMG::RS::CSR;
|
|
|
|
# "Config:" The Acme server URL:
|
|
my $DIR = 'https://acme-staging-v02.api.letsencrypt.org/directory';
|
|
|
|
# Useage:
|
|
#
|
|
# * Create a new account:
|
|
# | ~/ $ ./test.pl ./account.json new 'somebody@example.invalid"
|
|
#
|
|
# The `./account.json` will be created using an EC P-256 key.
|
|
# Optionally an RSA key size can be passed as additional parameter to generate
|
|
# an account with an RSA key instead.
|
|
#
|
|
# From here on out the `./account.json` file must already exist:
|
|
#
|
|
# * Place a new order:
|
|
# | ~/ $ ./test.pl ./account.json new-order my.domain.com
|
|
# | $VAR1 = {
|
|
# | ... order data ...
|
|
# | 'authorizations' => [
|
|
# | 'https://acme.example/auths/1244',
|
|
# | ... possibly more ...
|
|
# | ]
|
|
# | }
|
|
# | Order URL: https://acme.example/order/1793
|
|
#
|
|
# Note: This ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
|
|
# URL will be used later for finalization and certifiate download.
|
|
# The `$VAR1` dump contains the order JSON data.
|
|
# The 'authorizations' URLs are going to be used next.
|
|
#
|
|
# * Get authorization info
|
|
# | ~/ $ ./test.pl ./account.json get-auth 'https://acme.example/auths/1244'
|
|
# | $VAR1 = {
|
|
# | ... auth data ...
|
|
# | 'challenges' => [
|
|
# | {
|
|
# | 'type' => 'dns-01',
|
|
# | 'url' => 'https://acme.example/challenge/8188/dns1'
|
|
# | }
|
|
# | ... likely more ...
|
|
# | ]
|
|
# | }
|
|
# | Key Authorization = SuperVeryMegaLongValue
|
|
# | dns-01 TXT value = ShorterValue
|
|
#
|
|
# Now perform the things you need to for the challenge, eg. setup the DNS
|
|
# entry using the provided TXT value.
|
|
# Then use the correct challenge's URL with req-auth
|
|
#
|
|
# * Request challenge validation
|
|
# | ~/ $ ./test.pl ./account.json \
|
|
# | req-challenge 'https://acme.example/challenge/8188/dns1
|
|
#
|
|
# * Repeat the above 2 steps for all authorizations.
|
|
# * Wait for the order to be valid via `get-order`
|
|
# | ~/ $ ./test.pl ./account.json get-order 'https://acme.example/order/1793'
|
|
# | $VAR1 = {
|
|
# | 'status' => 'valid',
|
|
# | 'finalize' => 'some URL',
|
|
# | ... order data ...
|
|
# | }
|
|
# | Order URL: https://acme.example/order/1793
|
|
#
|
|
# * Finalize the order via the *Order URL* and a private key to sign the
|
|
# request with (eg. generated via `openssl genrsa` or `openssl ecparam`).
|
|
# | ~/ $ ./test.pl ./account.json \
|
|
# | finalize my.domain.com ./my-private-key.pem \
|
|
# | 'https://acme.example/order/1793'
|
|
#
|
|
# * Wait for a 'certificate' property to pop up in the order
|
|
# (check via 'get-order')
|
|
#
|
|
# * Grab the certificate with the Order URL and a destination file name:
|
|
# | ~/ $ ./test.pl ./account.json get-cert \
|
|
# | 'https://acme.example/order/1793' \
|
|
# | ./my-cert.pem
|
|
|
|
|
|
my $account = shift // die "missing account file\n";
|
|
my $cmd = shift // die "missing account file\n";
|
|
|
|
sub load : prototype($) {
|
|
my ($file) = @_;
|
|
open(my $fh, '<', $file) or die "open($file): $!\n";
|
|
my $data = do {
|
|
local $/ = undef;
|
|
<$fh>
|
|
};
|
|
close($fh);
|
|
return $data;
|
|
}
|
|
|
|
sub store : prototype($$) {
|
|
my ($file, $data) = @_;
|
|
open(my $fh, '>', $file) or die "open($file): $!\n";
|
|
syswrite($fh, $data) == length($data)
|
|
or die "failed to write data to $file: $!\n";
|
|
close($fh);
|
|
}
|
|
|
|
if ($cmd eq 'new') {
|
|
my $mail = shift // die "missing mail address\n";
|
|
my $rsa_bits = shift;
|
|
if (defined($rsa_bits)) {
|
|
$rsa_bits = int($rsa_bits);
|
|
}
|
|
my $acme = PMG::RS::Acme->new($DIR);
|
|
$acme->new_account($account, 1, ["mailto:$mail"], undef);
|
|
} elsif ($cmd eq 'get-meta') {
|
|
#my $acme = PMG::RS::Acme->new($DIR);
|
|
my $acme = PMG::RS::Acme->new('https%3A%2F%2Facme-v02.api.letsencrypt.org%2Fdirectory');
|
|
my $data = $acme->get_meta();
|
|
say Dumper($data);
|
|
} elsif ($cmd eq 'new-order') {
|
|
my $domain = shift // die "missing domain\n";
|
|
my $acme = PMG::RS::Acme->load($account);
|
|
my ($url, $order) = $acme->new_order([$domain]);
|
|
say Dumper($order);
|
|
say "Order URL: $url\n";
|
|
} elsif ($cmd eq 'get-auth') {
|
|
my $url = shift // die "missing url\n";
|
|
my $acme = PMG::RS::Acme->load($account);
|
|
my $auth = $acme->get_authorization($url);
|
|
say Dumper($auth);
|
|
for my $challenge ($auth->{challenges}->@*) {
|
|
next if $challenge->{type} ne 'dns-01';
|
|
say "Key Authorization = ".$acme->key_authorization($challenge->{token});
|
|
say "dns-01 TXT value = ".$acme->dns_01_txt_value($challenge->{token});
|
|
}
|
|
} elsif ($cmd eq 'req-challenge') {
|
|
my $url = shift // die "missing url\n";
|
|
my $acme = PMG::RS::Acme->load($account);
|
|
my $challenge = $acme->request_challenge_validation($url);
|
|
say Dumper($challenge);
|
|
} elsif ($cmd eq 'finalize') {
|
|
my $domain = shift // die 'missing domain\n';
|
|
my $pkfile = shift // die "missing private key file\n";
|
|
my $order_url = shift // die "missing order URL\n";
|
|
my ($csr_der, $pkey_pem) = PMG::RS::CSR::generate_csr([$domain], {});
|
|
store($pkfile, $pkey_pem);
|
|
my $acme = PMG::RS::Acme->load($account);
|
|
my $order = $acme->get_order($order_url);
|
|
say Dumper($order);
|
|
die "order not ready\n" if $order->{status} ne 'ready';
|
|
$acme->finalize_order($order->{finalize}, $csr_der);
|
|
} elsif ($cmd eq 'get-order') {
|
|
my $order_url = shift // die "missing order URL\n";
|
|
my $acme = PMG::RS::Acme->load($account);
|
|
my $order = $acme->get_order($order_url);
|
|
say Dumper($order);
|
|
} elsif ($cmd eq 'get-cert') {
|
|
my $order_url = shift // die "missing order URL\n";
|
|
my $file_name = shift // die "missing destination file name\n";
|
|
my $acme = PMG::RS::Acme->load($account);
|
|
my $order = $acme->get_order($order_url);
|
|
my $cert_url = $order->{certificate};
|
|
die "certificate not ready\n" if !$cert_url;
|
|
say Dumper($order);
|
|
my $cert = $acme->get_certificate($cert_url);
|
|
store($file_name, $cert);
|
|
} else {
|
|
die "unknown command '$cmd'\n";
|
|
}
|