Problems with my new perl ACME2 client

I have problems with my new ACME2 client. Switched key from my test key to production and now it don’t generate certificates anymore. Seems there is some issues with my private key? Since it works with the test key but not real key…

root@sebastian-desktop:/etc/nsd# ./certbot2
Creating challenge for sebbe.eu
Creating challenge for sebbe.eu
Writing challenges to zone file
Signing DNSSEC data…
Submitting challenges for validation…
Getting validation results…
Passed authorization for sebbe.eu
Passed authorization for sebbe.eu
Generating certificate…
Unable to generate certificate --> Possible failed some validation for sebbe.eu or exceeded rate limits
root@sebastian-desktop:/etc/nsd#

#!/usr/bin/perl

use Net::ACME2;
use Net::ACME2::LetsEncrypt;
use Digest::SHA;
use MIME::Base64;

#In the real client, key is unencrypted. To avoid publishing the key here, I have encrypted it with 
#OpenPGP key ID 0xE3E238BB9AE1D0226B400CAD3CBF8C99F1FAF31D.

$private_key = <<'PEMPRIVATEKEY';
-----BEGIN PGP MESSAGE-----
Version: OpenPGP v2.0.8
Comment: Encrypted with PubKeyID 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
=THDj
-----END PGP MESSAGE-----
PEMPRIVATEKEY


$no_request = <<'LECSRB';
-----BEGIN CERTIFICATE REQUEST-----
MIIDPjCCAiYCAQAwga4xCzAJBgNVBAYTAlNFMQ0wCwYDVQQIDARub25lMQ0wCwYD
VQQHDARub25lMQ4wDAYDVQQRDAU0MzY0NTEdMBsGA1UECQwUSHVsdEFzZW4gMTUg
TEdIIDEwMDIxDTALBgNVBAoMBG5vbmUxDTALBgNVBAsMBG5vbmUxETAPBgNVBAMM
CHNlYmJlLmV1MSEwHwYJKoZIhvcNAQkBFhJzZWJhc3RpYW5Ac2ViYmUuZXUwggEi
MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDC6rA7dp88RBatmwbj71cJmIgn
eMqDlhI+mV2DXhEUxK71Z22ZUK0MZmrVI2kszYgn0R05RhPykM3BBgH1cvdeLEg4
sAI5ZWufoGaVrwIoc9pnY4LSLSad+6C5sdr08bclNZAcfQaGsi69HfYFgNCgJAdo
2TVqLEcOIS81ftyqfn5P7Fr489NG7psHjGMJyp+aKf/Qc3CYlodYubEzrEGAFmDu
uNzGKLBlTUl8Ku+Ot0gnV+EobjTnwFzNdGSbGxRscojlfjEuKzCsI4JnpzXRCkM0
H4+Ujfd4F28CSqMcT0IE+bhfWhdebD0L9ibWJ8ZrDmFcLnm3w0rKGEECPXyrAgMB
AAGgSjBIBgkqhkiG9w0BCQ4xOzA5MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgXgMB8G
A1UdEQQYMBaCCiouc2ViYmUuZXWCCHNlYmJlLmV1MA0GCSqGSIb3DQEBCwUAA4IB
AQBilaxvefhHssi1QawFdz77XAxhlfaqqk9yc2Qk2XZmSOi6OWvceV2djV2WwSMX
neSN/uXaQVAtVfbXDIP3o0jSz+pzR4ILmZsMA2tmgdYO7ni4DL2Y44WefucbEvQF
IbyJ6iwvHLXSrBs6bC5F6TX63WCeT+hozuJfENRnpglltvczAtcvai8xX40D8VoA
YFu1Cww57bRf4BgDRQWuyh1bU6IR+PGlUfCYonw9OVsm+5a8mCUb+k/UrRFZMZoo
j4P8SdZJYNf/8ujBzElICBmkHXQdnfrdCtTFyihVDq5us7il26SpP+/pULcXWFij
3hi3wwk5cE6ogk1FPwUvbScM
-----END CERTIFICATE REQUEST-----
LECSRB

$cert_request = $no_request;

$acme = Net::ACME2::LetsEncrypt->new( key => $private_key, key_id => "https://acme-v02.api.letsencrypt.org/acme/acct/81135719" );
#$acme->create_account( termsOfServiceAgreed => 1 );
#print $acme->key_id."\n";

$order = $acme->create_order(
    identifiers => [
        { type => 'dns', value => '*.sebbe.eu' },
        { type => 'dns', value => 'sebbe.eu' },
    ],
);

foreach $dauth ($order->authorizations()) {
$fdauth = $acme->get_authorization( $dauth );
foreach $chtype ($fdauth->challenges()) {
if ($chtype->type() eq "dns-01") {
$chalstring = $acme->make_key_authorization($chtype);
$sha = Digest::SHA::sha256($chalstring);
$b64 = MIME::Base64::encode_base64url($sha);
print "Creating challenge for ".$fdauth->identifier()->{'value'}."\n";
push(@writechallenges, $fdauth->identifier()->{'value'}."!!".$b64);
push(@pendingcompletion, $chtype);
}
}
}

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->accept_challenge($uchall);
}

print "Getting validation results...\n";

foreach $qdauth ($order->authorizations()) {
$dauth = $acme->get_authorization( $qdauth );
while (1) {
        $valresult = $acme->poll_authorization($dauth);
        if ($valresult eq "valid") {
        print "Passed authorization for ".$dauth->identifier()->{'value'}."\n";
        last;
        }
        else
        {
        if ($valresult eq "invalid") {
        print "Failed authorization for ".$dauth->identifier()->{'value'}."\n";
        die();
        }
        }
        sleep 1;
}
}

print "Generating certificate...\n";
$acme->finalize_order($order,$cert_request) || die "Unable to generate certificate --> Possible failed some validation for sebbe.eu or exceeded rate limits\n";

while ($order->status() ne 'valid') {
    sleep 1;
    $acme->poll_order($order);
}
$pem = $acme->get_certificate_chain($order);

print "Writing certificate...\n";
open(CAFILE, "/etc/nsd/cacert.pem");
@cacert = <CAFILE>;
close(CAFILE);

open(OCSPFILE, ">/etc/nsd/ocspcert.pem");
open(CERTFILE, ">/etc/nsd/servercert.pem");
open(INSFILE, ">/etc/inspircd/cert.pem");
print CERTFILE $pem;
print OCSPFILE $pem;
print INSFILE $pem;
close(OCSPFILE);
print CERTFILE "\n";
foreach $caline (@cacert) {
print CERTFILE $caline;
print INSFILE $caline;
}
close(CERTFILE);
close(INSFILE);

print "Successfully generated LE certificate!\n";

Hi @sebastiannielsen

I don't know how that library $acme works.

But your error handling is wrong. The finalize_order should return a more detailed error message.

So you know if it is a validation or a rate limit problem.

oooh I see now…

Deleted the die part and got this instead:

Net::ACME2::x::ACME: “https://acme-v02.api.letsencrypt.org/acme/finalize/81135719/2722922570” indicated an ACME error: 400 Bad Request (400 urn:ietf:params:acme:error:malformed (The request message was malformed) (Error finalizing order :: certificate public key must be different than account key)).
==> Net::ACME2::x::Generic::new(‘Net::ACME2::x::ACME’, ‘“https://acme-v02.api.letsencrypt.org/acme/finalize/81135719/2722922570” indicated an ACME error: 400 Bad Request (400 urn:ietf:params:acme:error:malformed (The request message was malformed) (Error finalizing order :: certificate public key must be different than account key)).’, HASH(0x557993ffcb58)) (called in /usr/local/share/perl/5.26.1/Net/ACME2/X/ACME.pm at line 31)
==> Net::ACME2::x::ACME::new(‘Net::ACME2::x::ACME’, HASH(0x557993ffcb58)) (called in /usr/local/share/perl/5.26.0/X/Tiny.pm at line 169)
==> X::Tiny::create(‘Net::ACME2::X’, ‘ACME’, HASH(0x557993ffcb58)) (called in /usr/local/share/perl/5.26.1/Net/ACME2/HTTP.pm at line 159)
==> Net::ACME2::HTTP::_request(Net::ACME2::HTTP=HASH(0x5579933193a8), ‘POST’, ‘https://acme-v02.api.letsencrypt.org/acme/finalize/81135719/2722922570’, HASH(0x557993ffc9a8), HASH(0x557993fe7640)) (called in /usr/local/share/perl/5.26.1/Net/ACME2/HTTP.pm at line 181)
==> Net::ACME2::HTTP::_request_and_set_last_nonce(Net::ACME2::HTTP=HASH(0x5579933193a8), ‘POST’, ‘https://acme-v02.api.letsencrypt.org/acme/finalize/81135719/2722922570’, HASH(0x557993ffc9a8), HASH(0x557993fe7640)) (called in /usr/local/share/perl/5.26.1/Net/ACME2/HTTP.pm at line 108)
==> Net::ACME2::HTTP::_post(Net::ACME2::HTTP=HASH(0x5579933193a8), ‘create_key_id_jws’, ‘https://acme-v02.api.letsencrypt.org/acme/finalize/81135719/2722922570’, HASH(0x557993fe8cb0)) (called in /usr/local/share/perl/5.26.1/Net/ACME2/HTTP.pm at line 88)
==> Net::ACME2::HTTP::post_key_id(Net::ACME2::HTTP=HASH(0x5579933193a8), ‘https://acme-v02.api.letsencrypt.org/acme/finalize/81135719/2722922570’, HASH(0x557993fe8cb0)) (called in /usr/local/share/perl/5.26.1/Net/ACME2.pm at line 555)
==> Net::ACME2::_post_url(Net::ACME2::LetsEncrypt=HASH(0x557992bd4f00), ‘https://acme-v02.api.letsencrypt.org/acme/finalize/81135719/2722922570’, HASH(0x557993fe8cb0)) (called in /usr/local/share/perl/5.26.1/Net/ACME2.pm at line 423)
==> Net::ACME2::finalize_order(Net::ACME2::LetsEncrypt=HASH(0x557992bd4f00), Net::ACME2::Order=HASH(0x557992d63488), '-----BEGIN CERTIFICATE REQUEST-----
MIIDPjCCAiYCAQAwga4xCzAJBgNVBAYTAlNFMQ0wCwYDVQQIDARub25lMQ0wCwYD
VQQHDARub25lMQ4wDAYDVQQRDAU0MzY0NTEdMBsGA1UECQwUSHVsdEFzZW4gMTUg
TEdIIDEwMDIxDTALBgNVBAoMBG5vbmUxDTALBgNVBAsMBG5vbmUxETAPBgNVBAMM
CHNlYmJlLmV1MSEwHwYJKoZIhvcNAQkBFhJzZWJhc3RpYW5Ac2ViYmUuZXUwggEi
MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDC6rA7dp88RBatmwbj71cJmIgn
eMqDlhI+mV2DXhEUxK71Z22ZUK0MZmrVI2kszYgn0R05RhPykM3BBgH1cvdeLEg4
sAI5ZWufoGaVrwIoc9pnY4LSLSad+6C5sdr08bclNZAcfQaGsi69HfYFgNCgJAdo
2TVqLEcOIS81ftyqfn5P7Fr489NG7psHjGMJyp+aKf/Qc3CYlodYubEzrEGAFmDu
uNzGKLBlTUl8Ku+Ot0gnV+EobjTnwFzNdGSbGxRscojlfjEuKzCsI4JnpzXRCkM0
H4+Ujfd4F28CSqMcT0IE+bhfWhdebD0L9ibWJ8ZrDmFcLnm3w0rKGEECPXyrAgMB
AAGgSjBIBgkqhkiG9w0BCQ4xOzA5MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgXgMB8G
A1UdEQQYMBaCCiouc2ViYmUuZXWCCHNlYmJlLmV1MA0GCSqGSIb3DQEBCwUAA4IB
AQBilaxvefhHssi1QawFdz77XAxhlfaqqk9yc2Qk2XZmSOi6OWvceV2djV2WwSMX
neSN/uXaQVAtVfbXDIP3o0jSz+pzR4ILmZsMA2tmgdYO7ni4DL2Y44WefucbEvQF
IbyJ6iwvHLXSrBs6bC5F6TX63WCeT+hozuJfENRnpglltvczAtcvai8xX40D8VoA
YFu1Cww57bRf4BgDRQWuyh1bU6IR+PGlUfCYonw9OVsm+5a8mCUb+k/UrRFZMZoo
j4P8SdZJYNf/8ujBzElICBmkHXQdnfrdCtTFyihVDq5us7il26SpP+/pULcXWFij
3hi3wwk5cE6ogk1FPwUvbScM
-----END CERTIFICATE REQUEST-----
') (called in ./certbot2 at line 151)

OMG its that stupid limitation again… “certificate public key must be different than account key”
Tought that stupid limitation was deleted in ACME2.

Please: That's not a stupid limitation.

That's an absolutely required limitation.

Your code is a good sample.

1 Like

Its just so arbitary… like restricting which private keys that can be used. It wouldn’t hurt security to allow account and certificate private keys to be equal.
Could understand requiring a minimum keylength, but not requiring different keys.

Just because you don't understand the reasoning, doesn't mean it is without reason.

It's good cryptographic engineering practice to use each key for only one purpose. This mitigates a number of risks.

  • protocol confusion and similar oracle attacks, in which someone can trick you in the course of one protocol or application into signing data that has a different meaning in the context of another¹

  • target aggregation in which there is a heightened incentive to break a particular key because doing so will achieve multiple goals

  • target aggregation in terms of the ability to keep different keys on different devices where they're needed, so that a compromise of one device won't compromise all of the other applications (e.g. if one ACME account provisions keys for many different servers under the same domain, the ACME account key can be kept on the certificate provisioning server and the subject keys can be kept on the individual servers where they're actually used, so neither one can directly compromise the other's role!)

  • selective revocation of a particular credential without requiring the revocation of other credentials that use the same key

  • ability to choose different key types or key strengths for different applications that may have different requirements (e.g., perhaps choosing longer keys for purposes that require longer-term validity)

  • ergonomics about confirming that software developers and system administrators who are responsible for keys understand the nature and purpose of each one

  • detecting severe software bugs, such as PRNG initialization bugs, that cause the same private key to be generated repeatedly

You can read about some fairly similar considerations in

and a number of similar forum threads about PGP's use of multiple keys (signing key, encryption key, long-term identity key).

I think the intuition about keeping keys that represent different roles, capabilities, or functions separate is a powerful idea in the evolution of cryptography. It's one that's mitigated real attacks and also one that's allowed revocation of credentials to be targeted narrowly when keys are compromised or suspected to be compromised.

If you're not persuaded by these considerations, I can offer a completely different account of why this might be useful: the "brown M&M" theory.

So, when I would walk backstage, if I saw a brown M&M in that bowl … well, line-check the entire production. Guaranteed you’re going to arrive at a technical error. They didn’t read the contract. Guaranteed you’d run into a problem. Sometimes it would threaten to just destroy the whole show. Something like, literally, life-threatening.

Having specific and detailed rules may also serve as a good check that implementers are paying attention to the other rules, even when they might not understand the importance or purpose of those rules. Cryptography and PKI are so easy to get wrong and so it's worthwhile to try to make sure that people are being detail-oriented.

¹ I think this is the most fundamental cryptographic reason here. An interesting example is DROWN attack - Wikipedia and other literature on cryptographic cross-protocol attacks, but see also Related-key attack - Wikipedia for another kind of issue. A useful conclusion is that you probably don't want two different kinds of software/application/protocol implementation to have access to the same private key because an operation that can be done legitimately in one context may be usable as an oracle to attack another context.

9 Likes

What a fantastic, high effort post!

2 Likes

Additional: It's not only a "good cryptographic engineering practice", it's a general software developing rule.

The "Principle of least privilege":

Different things (account, certificate) -> different keys, different rights.

Same with buildings etc.:

One building -> different locks -> different keys.

3 Likes

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