Why is certificate failing to generate?

I made my own ACME client in perl, using the Net::ACME module.

But now its failing:

The ACME function “https://acme-v01.api.letsencrypt.org/acme/new-cert” indicated an error: “Error creating new cert :: certificate public key must be different than account key (The request message was malformed)” (400, “Bad Request”, urn:acme:error:malformed). at /usr/local/share/perl/5.22.1/Net/ACME/HTTP.pm line 139.
Net::ACME::HTTP::_request(Net::ACME::HTTP=HASH(0x2096a18), “post”, “https://acme-v01.api.letsencrypt.org/acme/new-cert”, HASH(0x35e7700)) called at /usr/local/share/perl/5.22.1/Net/ACME/HTTP.pm line 158
Net::ACME::HTTP::_request_and_set_last_nonce(Net::ACME::HTTP=HASH(0x2096a18), “post”, “https://acme-v01.api.letsencrypt.org/acme/new-cert”, HASH(0x35e7700)) called at /usr/local/share/perl/5.22.1/Net/ACME/HTTP.pm line 79
Net::ACME::HTTP::post(Net::ACME::HTTP=HASH(0x2096a18), “https://acme-v01.api.letsencrypt.org/acme/new-cert”, HASH(0x35f7b08)) called at /usr/local/share/perl/5.22.1/Net/ACME.pm line 521
Net::ACME::_post_url(Net::ACME::LetsEncrypt=HASH(0x2aed320), “https://acme-v01.api.letsencrypt.org/acme/new-cert”, HASH(0x35f7b08)) called at /usr/local/share/perl/5.22.1/Net/ACME.pm line 510
Net::ACME::_post(Net::ACME::LetsEncrypt=HASH(0x2aed320), “new-cert”, HASH(0x35f7b08)) called at /usr/local/share/perl/5.22.1/Net/ACME.pm line 431
Net::ACME::get_certificate(Net::ACME::LetsEncrypt=HASH(0x2aed320), “-----BEGIN CERTIFICATE REQUEST-----\x{a}MIIFiDCCA3ACAQAwgbAxCzAJB”…) called at ./certbot.pl line 150

It looks like its failing because im using the same keypair for registration and CSR. But why? What is the problem?

You have to use different private keys for the certificate and account. There are negative security implications with this sort of key re-use.

What are the “negative security implications”?

As long as the private key is kept secret, it should not be a problem with key reuse for like a million different purposes?

It’s a key reuse thing really. Same reason you generally don’t want to use the same across multiple servers. Let’s Encrypt also encourages changing private keys with each renewal. I don’t know if it’s part of the ACME spec or not, off the top of my head, but key generation is trivial enough.

Even if it’s not a problem now, it could be in the future. With cryptography, you can never be too careful.

I know that key regeneration is “trivial”. Its just that theres a heck of different private keys to track. My NSD folder already contains 6 private keys, 2 DNSSEC keys, one Letsencrypt client key, one certificate private key (which are then synlinked from the apache2 and postfix directory), and then 2 private keys for the nsd service.

Im aware of the security implications of signing something with the same key used for decryption (with signature blinding, it can cause inadvertient decryption of data) but since its a hash being signed and not the data directly, this is not a danger.

So key reuse is actually safe. I Think LE should change so key reuse is permitted.

I’d be interested to hear from someone with more familiarity on the ACME spec with regards to the protocol implications of this change. I’ll look over the draft RFC and known divergences when I have a chance. Depending on who actually made this decision, this may be something you’d need to take up with the IETF instead.

To be honest, I mostly agree with you that this seems like behavior that should at least be allowed, even if frowned upon, much like reuse of private keys for renewed certificates is allowed, but not the norm for most clients.

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";

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.