I have checked through the standard (draft-08 of ACME) and theres nothing that restricts the user from using the same keypair for the certificate and account.
The only thing that is restricted is having 2 accounts registred with the same keypair, because that would of course make one of the accounts inaccessible.
The only problem I can see, is certificate revocation, if the account and cert has the same private key. Should the authorization be considered to be done with an account key, or should authorization be considered to be done with an certificate key?
The simplest solution to this is to combine both views, so when it comes to revocation, an if an key belongs to both an certificate and account, this key is authorized to request revocation for any certificate that shares the same account and/or public key.
Here is how my ACME client looks like now. But the nice thing with using the same keypair was that I could generate the CSR automatically using the account key. Thats not possible with this limitation.
(Currently in my cron.monthly, so it will hard-renew each month. If it fails it gets a new chance next month without expiry)
#!/usr/bin/perl
use Net::ACME;
use Net::ACME::LetsEncrypt;
use Digest::SHA;
use MIME::Base64;
$private_key = <<'PEMPRIVATEKEY';
-----BEGIN PRIVATE KEY-----
**Wont disclose this**
-----END PRIVATE KEY-----
PEMPRIVATEKEY
$cert_request = <<'LECSR';
-----BEGIN CERTIFICATE REQUEST-----
MIIDtjCCAp4CAQAwgbAxCzAJBgNVBAYTAlNFMQ0wCwYDVQQIDARub25lMQ0wCwYD
VQQHDARub25lMQ4wDAYDVQQRDAU0MTY0ODEfMB0GA1UECQwWQW5kZXJzIFBlcnNv
bnNnYXRhbiAxOTENMAsGA1UECgwEbm9uZTENMAsGA1UECwwEbm9uZTERMA8GA1UE
AwwIc2ViYmUuZXUxITAfBgkqhkiG9w0BCQEWEnNlYmFzdGlhbkBzZWJiZS5ldTCC
ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMLqsDt2nzxEFq2bBuPvVwmY
iCd4yoOWEj6ZXYNeERTErvVnbZlQrQxmatUjaSzNiCfRHTlGE/KQzcEGAfVy914s
SDiwAjlla5+gZpWvAihz2mdjgtItJp37oLmx2vTxtyU1kBx9BoayLr0d9gWA0KAk
B2jZNWosRw4hLzV+3Kp+fk/sWvjz00bumweMYwnKn5op/9BzcJiWh1i5sTOsQYAW
YO643MYosGVNSXwq7463SCdX4ShuNOfAXM10ZJsbFGxyiOV+MS4rMKwjgmenNdEK
QzQfj5SN93gXbwJKoxxPQgT5uF9aF15sPQv2JtYnxmsOYVwuebfDSsoYQQI9fKsC
AwEAAaCBvzCBvAYJKoZIhvcNAQkOMYGuMIGrMAkGA1UdEwQCMAAwCwYDVR0PBAQD
AgXgMH4GA1UdEQR3MHWCDWRuczIuc2ViYmUuZXWCDWRuczEuc2ViYmUuZXWCDHd3
dy5zZWJiZS5ldYIQcHJpbnRlci5zZWJiZS5ldYIIc2ViYmUuZXWCDXNtdHAuc2Vi
YmUuZXWCDW1haWwuc2ViYmUuZXWCDWltYXAuc2ViYmUuZXUwEQYIKwYBBQUHARgE
BTADAgEFMA0GCSqGSIb3DQEBCwUAA4IBAQARvhUMKyOJyTcaE+v5+7JLWeyY5aWo
tc3CW/TL5wVbddTGht0jcpM9GY+Ht5Zrm0Hnsuvlb7/16BtpeONRQo+8zovV6ttu
NowBYoLfK7CwXS6XdRNJaCrI5F2WANG2WuA8FNDDCLob1r2eWOpcDc/h7Qq/Fh2B
+7d+Dqkz+W8qPTrq+gM+jyWGpXAUg+5aQDsHuNu1b48W8QzVniGk9HnbydYAaNvV
U3j+bXrGV0Xq5TrLSHF2JsMEXa2tjO8y/h3ZKx9C8FuiNioto9bkMLBwWWpNG2oz
Y0UdIWK11KuZ5jU46cXojjTGAFxGu0U0XWkHiXTfXR+dSbn16XDwbGM2
-----END CERTIFICATE REQUEST-----
LECSR
#$tos_url = Net::ACME::LetsEncrypt->get_terms_of_service();
$acme = Net::ACME::LetsEncrypt->new( key => $private_key );
#$reg = $acme->register('mailto:sebastian@sebbe.eu');
#$acme->accept_tos( $reg->uri(), $tos_url );
$key_jwk = Net::ACME::Crypt::parse_key($private_key)->get_struct_for_public_jwk();
@domains = ('sebbe.eu', 'www.sebbe.eu', 'dns1.sebbe.eu', 'dns2.sebbe.eu', 'printer.sebbe.eu', 'mail.sebbe.eu', 'smtp.sebbe.eu', 'imap.sebbe.eu');
foreach $domain (@domains) {
$authz_p = $acme->start_domain_authz($domain);
$pollcomplete{$domain} = $authz_p;
foreach $cmb_ar ( $authz_p->combinations() ) {
next if @$cmb_ar > 1;
next if $cmb_ar->[0]->type() ne 'dns-01';
$kauthz = $cmb_ar->[0]->make_key_authz( $key_jwk );
$sha = Digest::SHA::sha256($kauthz);
$b64 = MIME::Base64::encode_base64url($sha);
print "Creating challenge for $domain\n";
push(@writechallenges, $domain."!!".$b64);
push(@pendingcompletion, $cmb_ar->[0]);
}
}
print "Writing challenges to zone file\n";
open(ZONEFILEA, ">/etc/nsd/sebbe.eu.zone.signed");
print ZONEFILEA "";
close(ZONEFILEA);
open(ZONEFILEB, ">/etc/nsd/sebbe.eu.zone");
print ZONEFILEB "";
close(ZONEFILEB);
open(ZONETEMPLATE, "/etc/nsd/sebbe.eu.template");
@zonetemp = <ZONETEMPLATE>;
close(ZONETEMPLATE);
open(ZONEFILE, ">/etc/nsd/sebbe.eu.zone");
foreach $zoneline (@zonetemp) {
print ZONEFILE $zoneline;
}
foreach $challauth (@writechallenges) {
($domain, $b64) = split("!!", $challauth);
print ZONEFILE "_acme-challenge.".$domain.". 3600 IN TXT \"$b64\"\n";
}
close(ZONEFILE);
print "Signing DNSSEC data...\n";
$currenttime = time;
$dnssec_expiration = $currenttime + 7776060;
system("ldns-signzone -e ".$dnssec_expiration." /etc/nsd/sebbe.eu.zone /etc/nsd/Ksebbe.eu.+007+14838 /etc/nsd/Ksebbe.eu.+007+47438");
system("service nsd restart");
sleep 1;
print "Submitting challenges for validation...\n";
foreach $uchall (@pendingcompletion) {
$acme->do_challenge($uchall);
}
print "Getting validation results...\n";
foreach $dom (keys %pollcomplete) {
while (1) {
if ( $pollcomplete{$dom}->is_time_to_poll() ) {
$poll = $pollcomplete{$dom}->poll();
last if $poll->status() eq 'valid';
if ( $poll->status() eq 'invalid' ) {
die "Failed authorization for \"$dom\"!";
}
}
sleep 1;
}
}
print "Generating certificate...\n";
$cert = $acme->get_certificate($cert_request);
while ( !$cert->pem() ) {
sleep 1;
next if !$cert->is_time_to_poll();
$cert = $cert->poll() || $cert;
}
print "Writing certificate...\n";
open(CAFILE, "/etc/nsd/cacert.pem");
@cacert = <CAFILE>;
close(CAFILE);
open(CERTFILE, ">/etc/nsd/servercert.pem");
print CERTFILE $cert->pem();
print CERTFILE "\n";
foreach $caline (@cacert) {
print CERTFILE $caline;
}
close(CERTFILE);
print "Restarting services...\n";
system("service apache2 restart");
system("service postfix restart");
print "Successfully generated LE certificate!\n";