Creating and Importing an ECDSA certificate to AWS Certificate Manager

Hi All,

Relatively new to this so bear with me. Problem statement - we generally use AWS Certificate Manager to handle all certs for our Route53 registered domain however we have a requirement to add an ECDSA certificate to an AWS load balancer to support one of the following ciphers in the TLS 1.2 handshake: (ECDHE-ECDSA-AES256-GCM-SHA384, ECDHE-ECDSA-AES128-GCM-SHA256). AWS supports the import of ECDSA certificates and I've attempted to generate one below:

My domain is: staging-panic.aura.services

I ran this command:

sudo certbot certonly --dns-route53 --agree-tos --email grantm@aura.services --domains "staging-panic.aura.services" --key-type ecdsa --elliptic-curve secp384r1

It produced this output:

Saving debug log to /var/log/letsencrypt/letsencrypt.log
Requesting a certificate for staging-panic.aura.services

Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/staging-panic.aura.services/fullchain.pem
Key is saved at: /etc/letsencrypt/live/staging-panic.aura.services/privkey.pem
This certificate expires on 2023-01-30.
These files will be updated when the certificate renews.

The version of my client is (e.g. output of certbot --version or certbot-auto --version if you're using Certbot): 1.31.0

When attempting to import the certificate into ACM (or via IAM certificates), the certificate chain validation fails.

Unable to validate certificate chain. The certificate chain must start with the immediate signing certificate, followed by any intermediaries in order. The index within the chain of the invalid certificate is: 1

I've also attempted to verify the certificate using openssl by running the following:

openssl verify -CApath fullchain.pem cert.pem

Receiving output:

cert.pem: CN = staging-panic.aura.services
error 20 at 0 depth lookup:unable to get local issuer certificate

Any advice or general pointers would be greatly appreciated here!

The error message makes it sound like you may have tried importing the fullchain.pem file when ACM was looking for just chain.pem. The docs indicate that the leaf cert, private key, and chain are all uploaded separately.

So in certbot's case, you'd be uploading cert.pem, privkey.pem, and chain.pem while ignoring fullchain.pem.

4 Likes

And, as for openssl verify, here's the best way to do that

3 Likes

Thanks so much for the quick reply!

I have indeed tried importing fullchain! I have attempted to use just chain.pem for ACM, however it still fails the validation. Importing the certificate via iam with chain.pem however works.

aws iam upload-server-certificate

Will test it out and see if I can add the IAM cert to our load balancer.

1 Like

Well, it should work, you just need to make sure that you have the key, leaf cert, and rest of the chain in the right fields.

For what it's worth, here's the lightly-redacted Node.js Lambda I use to load a ECDSA cert into ACM for my le-e1-test site I host in CloudFront/S3, which loads the key and fullchain from where I have it stored in S3 (from a separate Lambda process that requests the cert and stores it there):

const AWS = require("aws-sdk");
const acm = new AWS.ACM();
const s3 = new AWS.S3();

async function getS3Body(s3ResultPromise) {
    const s3Result = await s3ResultPromise;
    return s3Result.Body.toString();
}

async function splitChain(certificateChainPromise) {
    return (await certificateChainPromise).split(/(?<=-----END CERTIFICATE-----)\n+(?=-----BEGIN CERTIFICATE-----)/);
}

exports.handler = async (event) => {
    const privKeyS3ResultPromise = s3.getObject({Bucket: "<my-bucket>", Key: "le-e1-test/certs/privkey.pem"}).promise();
    const fullChainS3ResultPromise = s3.getObject({Bucket: "<my-bucket>", Key: "le-e1-test/certs/fullchain.pem"}).promise();
    const privKeyPromise = getS3Body(privKeyS3ResultPromise);
    const fullChainPromise = getS3Body(fullChainS3ResultPromise);
    const fullChainArrayPromise = splitChain(fullChainPromise);

    const [privKey, fullChainArray] = await Promise.all([privKeyPromise, fullChainArrayPromise]);
    const cert = fullChainArray.shift();
    const chain = fullChainArray.join("\n");
    
    const importCertificatePromise = acm.importCertificate({
        CertificateArn: "<the ARN of the certificate I'm updating in ACM>",
        Certificate: cert,
        PrivateKey: privKey,
        CertificateChain: chain
    }).promise();
    
    return await importCertificatePromise;
};

The main thing is that it takes just the leaf cert (the first part) from the fullchain to pass to the import certificate API, and the remainder of the certs as the CertificateChain argument. But from certbot I think that you can already get the cert and chain as separate files which might make things easier (as opposed to my convoluted setup that only outputs the fullchain as one file).

Yeah, the arguments to openssl verify seem to confuse a lot of people. In order to verify, you'd need to have the CApath be the trust store you want to use, and have an -untrusted to indicate what other certificates were sent in the chain, and really even if you did it right then it still wouldn't really help you with what you're trying to do. :slight_smile:

4 Likes

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