Request For CertBot To Support The Signing of Ed25519 Certificates

OpenSSL clearly already supports the generate of Ed25519 private keys and derived certificates. But the Certbot robot does not support the signing of such certificates by widely respected Certificate Authorities.

(Can/should ISRG submit a proposal to support Ed25519/Ed448 certificates to CA/B Forum? - #9 by schoen)

Unlike the ECDSA algorithms, Ed25519 cannot reveal the public key even if the attacker has access to a message and its corresponding digital signature:

Reference: EdDSA and Ed25519 - Practical Cryptography for Developers

Moreover, unlike ECDSA, Ed25519 will not reveal the private key even if the user accidentially reveals the nonce used to sign a message. This vulnerability exists for secp256k1, P-256, and P-512:

Reference: https://asecuritysite.com/encryption/ecd2

Finally, Ed25519 is simpler in design than ECDSA. May Certbot please start supporting the signing of Ed25519 certificates by trustworthy Certificate Authority

4 Likes

What's the stance of CA/B forum on such certificates?

3 Likes

Do you know of any CA already issuing Ed25519 certificates? It's not like that if certbot would support it, suddenly CAs will issue certs with Ed25519.

Also, I'm not sure if this Community is the right place to ask for this feature request for certbot. The "About" thread in this Feature Requests section does actually state it's the right place to ask for feature requests for the "Let's Encrypt client", but that was back in 2015! Since then the development of certbot has been taken over by the EFF. So maybe the certbot Github page would be the most likely candidate to request this feature request: Issues · certbot/certbot · GitHub Although I think chances are pretty slim that they'll implement it if CAs are not allowed to issue Ed25519 certs.

2 Likes

That's exactly it; see this thread: Support Ed25519 and Ed448

5 Likes

I don't think anyone can realistically expect LetsEncrypt/Certbot to support the CA/B forum adopting this standard out of the blue.

Perhaps someone would be interested in coding a Certbot/Pebble POC? I'm certainly not :wink: – but I do think there is probably someone passionate about this, and if successful that experiment could motivate the LetsEncrypt staff to advocate for it.

It's possible to do a POC in Certbot, as Python's Cryptography toolkit supports it: Search · ed25519 · GitHub

I haven't looked into support with go.

IMHO it would make sense for someone to make a POC to explore the feasibility of this, and use that to lobby LetsEncrypt/Certbot, who could then consider whether or not it should be advocated to the CA/B forum.

4 Likes

Pebble doesn't care about the contents of the private key, I checked. No code checking RSA/ECDSA/EdDSA what so ever :smiley:

For certbot on the other hand: different story. I tried a few hours yesterday, but it's not as simple as I thought. Cerbot uses multiple libraries for the RSA/ECDSA key processing (cryptography and OpenSSLs crypt) and for some reason it didn't play well together.. I was stuck on getting crypt to actually sign the CSR without a hash. Usually, a CSR is signed with the RSA/ECDSA key in combination with a hash function, but with EdDSA it seems you don't need the hash function? Tell crypt that! I couldn't...

4 Likes

looks like ed25519 defined to use SHA-512 as hash function, try send it as parameter anyway?

3 Likes

Assuming you have your own ACME enabled Certificate Authority you could perhaps use your own CSR with --csr in certonly mode. As far as I know there are no web browsers(?) which would be able to consume an Ed25519 certificate, if that's relevant to your use case.

3 Likes

That didn't work, but the following did work:

diff --git a/certbot/certbot/_internal/cli/__init__.py b/certbot/certbot/_internal/cli/__init__.py
index 4212c353b..38f9674a4 100644
--- a/certbot/certbot/_internal/cli/__init__.py
+++ b/certbot/certbot/_internal/cli/__init__.py
@@ -279,7 +279,7 @@ def prepare_and_parse_args(plugins, args, detect_defaults=False):
         "security", "--rsa-key-size", type=int, metavar="N",
         default=flag_default("rsa_key_size"), help=config_help("rsa_key_size"))
     helpful.add(
-        "security", "--key-type", choices=['rsa', 'ecdsa'], type=str,
+        "security", "--key-type", choices=['rsa', 'ecdsa', 'ed25519'], type=str,
         default=flag_default("key_type"), help=config_help("key_type"))
     helpful.add(
         "security", "--elliptic-curve", type=str, choices=[
diff --git a/certbot/certbot/crypto_util.py b/certbot/certbot/crypto_util.py
index e620d43e0..be903e866 100644
--- a/certbot/certbot/crypto_util.py
+++ b/certbot/certbot/crypto_util.py
@@ -19,6 +19,7 @@ from cryptography.hazmat.backends import default_backend
 from cryptography.hazmat.primitives.asymmetric import ec
 from cryptography.hazmat.primitives.asymmetric.ec import ECDSA
 from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePublicKey
+from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
 from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15
 from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey
 from cryptography.hazmat.primitives.serialization import Encoding
@@ -289,6 +290,15 @@ def make_key(bits=1024, key_type="rsa", elliptic_curve=None):
             encryption_algorithm=NoEncryption()
         )
         key = crypto.load_privatekey(crypto.FILETYPE_PEM, _key_pem)
+    elif key_type == 'ed25519':
+        _key = Ed25519PrivateKey.generate()
+        _key_pem = _key.private_bytes(
+            encoding=Encoding.PEM,
+            format=PrivateFormat.PKCS8,
+            encryption_algorithm=NoEncryption()
+        )
+        key = crypto.load_privatekey(crypto.FILETYPE_PEM, _key_pem)
+
     else:
         raise errors.Error("Invalid key_type specified: {}.  Use [rsa|ecdsa]".format(key_type))
     return crypto.dump_privatekey(crypto.FILETYPE_PEM, key)

But ALSO:

diff --git a/acme/acme/crypto_util.py b/acme/acme/crypto_util.py
index 749478bf5..52435970e 100644
--- a/acme/acme/crypto_util.py
+++ b/acme/acme/crypto_util.py
@@ -214,7 +214,7 @@ def make_csr(private_key_pem, domains, must_staple=False):
     csr.add_extensions(extensions)
     csr.set_pubkey(private_key)
     csr.set_version(2)
-    csr.sign(private_key, 'sha256')
+    csr.sign(private_key, 'NULL')
     return crypto.dump_certificate_request(
         crypto.FILETYPE_PEM, csr)
 

AND modify OpenSSLs crypt!:

diff -Naur a/lib64/python3.9/site-packages/OpenSSL/crypto.py b/lib64/python3.9/site-packages/OpenSSL/crypto.py
--- a/lib64/python3.9/site-packages/OpenSSL/crypto.py	2021-08-13 20:35:44.284389392 +0200
+++ b/lib64/python3.9/site-packages/OpenSSL/crypto.py	2021-08-13 20:36:24.206705683 +0200
@@ -1052,8 +1052,8 @@
             raise ValueError("Key is uninitialized")
 
         digest_obj = _lib.EVP_get_digestbyname(_byte_string(digest))
-        if digest_obj == _ffi.NULL:
-            raise ValueError("No such digest method")
+        #if digest_obj == _ffi.NULL:
+        #    raise ValueError("No such digest method")
 
         sign_result = _lib.X509_REQ_sign(self._req, pkey._pkey, digest_obj)
         _openssl_assert(sign_result > 0)

Without that latter modification, OpenSSLs crypt doesn't accept the NULL parameter..

This is of course just a proof of concept modification and shouldn't be used. :slight_smile:

Also note that the following actually works too, for Ed25519:

diff --git a/acme/acme/crypto_util.py b/acme/acme/crypto_util.py
index 749478bf5..56c16f7d9 100644
--- a/acme/acme/crypto_util.py
+++ b/acme/acme/crypto_util.py
@@ -214,7 +214,7 @@ def make_csr(private_key_pem, domains, must_staple=False):
     csr.add_extensions(extensions)
     csr.set_pubkey(private_key)
     csr.set_version(2)
-    csr.sign(private_key, 'sha256')
+    csr.sign(private_key, 'FOOBAR')
     return crypto.dump_certificate_request(
         crypto.FILETYPE_PEM, csr)
 

... as long as you also modify the OpenSSL crypt part to ignore any non-existing digest :stuck_out_tongue: But an existing digest does not work :slight_smile:

:rofl:

5 Likes

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

For anyone interested. I've incorporated part of above code and augmented it so it doesn't require a patched OpenSSL (Python) library!

You can find a certbot version supporting Ed25518 as wel as Ed448 here:

Unfortunately Go does not support Ed448 (see proposal: x/crypto: add implementation of Diffie-Hellman x448 · Issue #29390 · golang/go · GitHub for their arguments), so I believe SmallStep does not support Ed448 (but it does support EdDSA, probably Ed25519 only) and I could not test the above code fully with aid of Pebble (although it functions almost completely, it just doesn't get a certificate back from Pebble..)

7 Likes

Very cool! Apache and OpenSSL (and curl, at least when compiled against OpenSSL) support EdDSA:

osiris@erazer ~ $ openssl s_client -CAfile /tmp/root.pem -connect example.com:443
CONNECTED(00000003)
depth=2 CN = Pebble Root CA 7520ca
verify return:1
depth=1 CN = Pebble Intermediate CA 529b41
verify return:1
depth=0 CN = example.com
verify return:1
---
Certificate chain
 0 s:CN = example.com
   i:CN = Pebble Intermediate CA 529b41
 1 s:CN = Pebble Intermediate CA 529b41
   i:CN = Pebble Root CA 7520ca
---
Server certificate
-----BEGIN CERTIFICATE-----
MIICozCCAYugAwIBAgIIF7UoWBRUy3swDQYJKoZIhvcNAQELBQAwKDEmMCQGA1UE
AxMdUGViYmxlIEludGVybWVkaWF0ZSBDQSA1MjliNDEwHhcNMjExMjIzMTkzNDM5
WhcNMjYxMjIzMTkzNDM5WjAWMRQwEgYDVQQDEwtleGFtcGxlLmNvbTAqMAUGAytl
cAMhAGW8mID9dksBxrfYsD8kwdjCwwhsloD9FTtzsYPTOwzho4HcMIHZMA4GA1Ud
DwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0T
AQH/BAIwADAdBgNVHQ4EFgQUl0+XY9yfmLub5oUzwPBL60JyjxwwHwYDVR0jBBgw
FoAU9dWfJZOWzGYdk7UxoMYrt2+AYzIwMQYIKwYBBQUHAQEEJTAjMCEGCCsGAQUF
BzABhhVodHRwOi8vMTI3LjAuMC4xOjQwMDIwJwYDVR0RBCAwHoILZXhhbXBsZS5j
b22CD3d3dy5leGFtcGxlLmNvbTANBgkqhkiG9w0BAQsFAAOCAQEAaJL+H9WQNkwB
qjHQ9x1j8JtYNMtJL0LrKCpj2hJtZE02SfLrirKS9S1otS1YjvppPPPRDwAGGJla
8bQCksswJu3MYQjxm0A1oR2QhS5ByBtS9RrbV6tESfuwe66tQk9umNQyXSOj7rW+
LzPvj8JUMEO51vh9DC2mBNsQ426MdDh9xYlYWss9herExLDbUdgZY/QupUJjhy4N
WRDkqD1lhHnmdKL2nbf8djQOLvUgJi1FNw4/s3/SiVyczD4xIRv9J14Lze2yJufl
ITW3Nn/CPIRR9JP0xkxmXHBViDI9kM2LJRakAsfi/2PfAiP6yvLBDWXaNWaBy1+I
aHsJ+9BqUA==
-----END CERTIFICATE-----
subject=CN = example.com

issuer=CN = Pebble Intermediate CA 529b41

---
No client certificate CA names sent
Peer signature type: Ed25519
Server Temp Key: X25519, 253 bits
---
(...)

See above how (almost) the entire handshake is done using Curve25519! Ed25519 for the signature and X25519 for the ECDHE key exchange!

Unfortunately Pebble uses RSA intermediates and roots.. :cry: So a large part, if not most, of the data in the certificate are due to the RSA signature :stuck_out_tongue:

7 Likes

I can confirm that SmallStep returns an "algorithm unimplemented" error when trying to get a Ed448 CSR signed unfortunately.. I guess another reason NOT to use Go (developers making strange decisions..)

5 Likes