[PATCH manager] cli: acme: Allow to get certificates from CAs requiring EAB

Ember 'n0emis' Keske mailinglists at noemis.me
Tue Apr 12 21:13:23 CEST 2022


From: Ember 'n0emis' Keske <git at n0emis.eu>

to allow to fetch certificates from CA's like sectigo or zerossl.
The CLI checks, if the CA requires EAB credentials and promts
the user to enter them on demand.

Signed-off-by: Ember 'n0emis' Keske <git at n0emis.eu>
---
 PVE/API2/ACMEAccount.pm | 74 ++++++++++++++++++++++++++++++++++++++++-
 PVE/CLI/pvenode.pm      | 12 +++++++
 2 files changed, 85 insertions(+), 1 deletion(-)

diff --git a/PVE/API2/ACMEAccount.pm b/PVE/API2/ACMEAccount.pm
index b790843a..ca285da5 100644
--- a/PVE/API2/ACMEAccount.pm
+++ b/PVE/API2/ACMEAccount.pm
@@ -2,6 +2,7 @@ package PVE::API2::ACMEAccount;
 
 use strict;
 use warnings;
+use Digest::SHA;
 
 use PVE::ACME;
 use PVE::CertHelpers;
@@ -35,6 +36,31 @@ my $account_contact_from_param = sub {
     my @addresses = PVE::Tools::split_list(extract_param($_[0], 'contact'));
     return [ map { "mailto:$_" } @addresses ];
 };
+my $generate_eab = sub {
+    my ($acme, $param) = @_;
+    my $payload = PVE::ACME::encode(PVE::ACME::tojs($acme->jwk()));
+
+    my $kid = extract_param($param, 'eab_kid');
+    my $key = extract_param($param, 'eab_hmac_key');
+
+    my $url = $acme->_method('newAccount');
+
+    my $protected = {
+	alg => 'HS256',
+	kid => $kid,
+	url => $url,
+    };
+
+    $protected = PVE::ACME::encode(PVE::ACME::tojs($protected, canonical=>1));
+    my $signdata = "$protected.$payload";
+    my $signature = PVE::ACME::encode(Digest::SHA::hmac_sha256($signdata, MIME::Base64::decode_base64url($key)));
+
+    return {
+	protected => $protected,
+	payload => $payload,
+	signature => $signature,
+    };
+};
 my $acme_account_dir = PVE::CertHelpers::acme_account_dir();
 
 __PACKAGE__->register_method ({
@@ -115,6 +141,16 @@ __PACKAGE__->register_method ({
 		default => $acme_default_directory_url,
 		optional => 1,
 	    }),
+	    eab_kid => {
+		type => 'string',
+		description => 'Key Identifier for External Account Binding.',
+		optional => 1,
+	    },
+	    eab_hmac_key => {
+		type => 'string',
+		description => 'HMAC key for External Account Binding.',
+		optional => 1,
+	    },
 	},
     },
     returns => {
@@ -145,7 +181,12 @@ __PACKAGE__->register_method ({
 		print "Generating ACME account key..\n";
 		$acme->init(4096);
 		print "Registering ACME account..\n";
-		eval { $acme->new_account($param->{tos_url}, contact => $contact); };
+		if ($param->{eab_kid}) {
+		    my $eab = $generate_eab->($acme, $param);
+		    eval { $acme->new_account($param->{tos_url}, contact => $contact, externalAccountBinding => $eab); };
+		} else {
+		    eval { $acme->new_account($param->{tos_url}, contact => $contact); };
+		}
 		if (my $err = $@) {
 		    unlink $account_file;
 		    die "Registration failed: $err\n";
@@ -339,6 +380,37 @@ __PACKAGE__->register_method ({
 	return $meta ? $meta->{termsOfService} : undef;
     }});
 
+__PACKAGE__->register_method ({
+    name => 'get_eab',
+    path => 'eab',
+    method => 'GET',
+    description => "Retrieve where an external Account is required from CA.",
+    permissions => { user => 'all' },
+    parameters => {
+	additionalProperties => 0,
+	properties => {
+	    directory => get_standard_option('pve-acme-directory-url', {
+		default => $acme_default_directory_url,
+		optional => 1,
+	    }),
+	},
+    },
+    returns => {
+	type => 'boolean',
+	default => 0,
+	description => 'returns 1 if an external Account is required',
+    },
+    code => sub {
+	my ($param) = @_;
+
+	my $directory = extract_param($param, 'directory') // $acme_default_directory_url;
+
+	my $acme = PVE::ACME->new(undef, $directory);
+	my $meta = $acme->get_meta();
+
+	return $meta ? $meta->{externalAccountRequired} : 0;
+    }});
+
 __PACKAGE__->register_method ({
     name => 'get_directories',
     path => 'directories',
diff --git a/PVE/CLI/pvenode.pm b/PVE/CLI/pvenode.pm
index acef6c3b..658f5ee8 100644
--- a/PVE/CLI/pvenode.pm
+++ b/PVE/CLI/pvenode.pm
@@ -129,6 +129,18 @@ __PACKAGE__->register_method({
 	} else {
 	    print "No Terms of Service found, proceeding.\n";
 	}
+	print "\nAttempting to check wether an external Account is required for '$param->{directory}'..\n";
+	my $eab = PVE::API2::ACMEAccount->get_eab({ directory => $param->{directory} });
+	if ($eab) {
+	    print "External Account Binding information is required.\n";
+	    my $term = Term::ReadLine->new('pvenode');
+	    my $kid = $term->readline('Enter the Key Identifier for External Account Binding (KID): ');
+	    my $hmac_key = $term->readline('Enter the HMAC key for External Account Binding: ');
+	    $param->{eab_kid} = $kid;
+	    $param->{eab_hmac_key} = $hmac_key;
+	} else {
+	    print "No external Account required, proceeding.\n";
+	}
 	print "\nAttempting to register account with '$param->{directory}'..\n";
 
 	$upid_exit->(PVE::API2::ACMEAccount->register_account($param));
-- 
2.35.1





More information about the pve-devel mailing list