breard-r/acmed

Unable to renew the certificate: Unsupported EC key

jpastuszek opened this issue · 8 comments

I just started with default configuration where I have added the certificate with (default) rsa2048 algorithm but I got this error.
It looks like the account file is using EC type that is not supported?

$ target/release/acmed -f --log-stderr --log-level trace -c test.toml
[2019-11-14T17:01:10Z INFO  acmed::config] Loading configuration file: test.toml
[2019-11-14T17:01:10Z TRACE acmed::certificate] crt-1: Testing file path: /tmp/acc/eHh4.priv-key.pem
[2019-11-14T17:01:10Z TRACE acmed::certificate] crt-1: Writing file "/tmp/acc/eHh4.pub-key.pem"
[2019-11-14T17:01:10Z TRACE acmed::certificate] crt-1: Writing file "/tmp/acc/eHh4.priv-key.pem"
[2019-11-14T17:01:10Z INFO  acmed::certificate] crt-1: Account xxx created.
[2019-11-14T17:01:10Z TRACE acmed::certificate] crt-1: Testing file path: /tmp/certs/hello.xxx.net_rsa2048.pk.pem
[2019-11-14T17:01:10Z DEBUG acmed::certificate] crt-1: certificate does not exist: requesting one
[2019-11-14T17:01:10Z DEBUG acmed::certificate] crt-1: GET: https://acme-staging-v02.api.letsencrypt.org/directory
[2019-11-14T17:01:11Z DEBUG acmed::certificate] crt-1: HEAD: https://acme-staging-v02.api.letsencrypt.org/acme/new-nonce
[2019-11-14T17:01:12Z TRACE acmed::certificate] crt-1: New nonce: 0001V2hQXxOcbr6ly8PN0IpUdfSiV8F6_OFScmN1cOp0b3E
[2019-11-14T17:01:12Z TRACE acmed::certificate] crt-1: Reading file "/tmp/acc/eHh4.priv-key.pem"
[2019-11-14T17:01:12Z WARN  acmed::certificate] crt-1: Unable to renew the certificate: Unsupported EC key

I have tried this on two systems, one with OpenSSL 1.0.2k-fips 26 Jan 2017 and other that has LibreSSL 2.9.2 with same result.

$ target/release/acmed --version
ACMEd 0.6.1 x86_64-unknown-linux-gnu

Compiled with:
  LibreSSL 2.9.2
  http_req 0.5.3

My configuration:

[global]
accounts_directory = "/tmp/acc"
certificates_directory = "/tmp/certs"

[[rate-limit]]
name = "LE min"
number = 20
period = "1s"

[[endpoint]]
name = "letsencrypt v2 staging"
url = "https://acme-staging-v02.api.letsencrypt.org/directory"
rate_limits = ["LE min"]
tos_agreed = true

[[account]]
name = "xxx"
email = "it@xxx.com"

[[certificate]]
account = "xxx"
endpoint = "letsencrypt v2 staging"
domains = [{ challenge = "dns-01", dns = "hello.xxx.net" }]
hooks = []

Looks like I am getting None from $key.ec_key()?.group().curve_name().

This is very strange. It seems that OpenSSL/LibreSSL is able to create the account's EC key pair but then doesn't recognize it, which makes no sense. I just pushed a patch that displays a more precise error message. Can you build the master branch and then re-run it with the trace log-level please?

So I get None from .curve_name() so I added extra debugging and got this:

warn!("id: {:?}, ec_key: {:?}, group: {:?}", $key.id(), $key.ec_key(), $key.ec_key()?.group().curve_name());

Will result in: id: Id(408), ec_key: Ok(EcKey), group: None

If I return KeyType::EcdsaP256 in the None => branch I get this to work OK.

So I got it all working with LibreSSL on my PC but on my server with OpenSSL 1.0.2k I am getting this error:

[2019-11-15T17:04:48Z WARN  acmed::certificate] crt-1: Unable to renew the certificate: error:10074065:elliptic curve routines:EC_POINT_get_affine_coordinates_GFp:incompatible objects:ec_lib.c:892:

OpenSSL EC_POINT_get_affine_coordinates_GFp can fail if point in not in compact encoding: https://github.com/openssl/openssl/blob/54a0d4ceb28d53f5b00a27fc5ca8ff8f0ddf9036/crypto/ec/ec_lib.c#L838

static ossl_inline int ec_point_is_compat(const EC_POINT *point,
                                          const EC_GROUP *group)
{
    return group->meth == point->meth
           && (group->curve_name == 0
               || point->curve_name == 0
               || group->curve_name == point->curve_name);
}

So if curve_name is not known it will fail like that... so my workaround is causing this and the root cause is that loaded key is not recognised as know curve for some reason.

OK, the problem is that you are saving the PKey object and not the EcKey to pem file.
The difference is that PKey does not know which group the EC was from, only all the parameter it needs to know about the final curve:

$ openssl ec -in /tmp/acc/eHh4.priv-key.pem -text
read EC key
Private-Key: (256 bit)
priv:
    00:cc:9d:95:81:68:4b:e1:40:71:a6:b4:cd:27:8c:
    68:4c:38:b1:98:4a:11:8e:5d:c0:b5:b7:9f:f2:75:
    2d:98:e7
pub:
    04:2f:bd:12:f1:c4:96:32:dd:15:f7:00:06:66:56:
    3f:52:cf:b6:ad:34:99:23:52:13:1c:59:a9:5e:53:
    b1:49:af:35:59:1a:68:01:b9:8e:23:b9:31:1c:d7:
    9c:96:3d:ab:c9:e7:dd:47:20:ab:36:86:64:a5:f9:
    74:2f:df:aa:a9
Field Type: prime-field
Prime:
    00:ff:ff:ff:ff:00:00:00:01:00:00:00:00:00:00:
    00:00:00:00:00:00:ff:ff:ff:ff:ff:ff:ff:ff:ff:
    ff:ff:ff
A:
    00:ff:ff:ff:ff:00:00:00:01:00:00:00:00:00:00:
    00:00:00:00:00:00:ff:ff:ff:ff:ff:ff:ff:ff:ff:
    ff:ff:fc
B:
    5a:c6:35:d8:aa:3a:93:e7:b3:eb:bd:55:76:98:86:
    bc:65:1d:06:b0:cc:53:b0:f6:3b:ce:3c:3e:27:d2:
    60:4b
Generator (uncompressed):
    04:6b:17:d1:f2:e1:2c:42:47:f8:bc:e6:e5:63:a4:
    40:f2:77:03:7d:81:2d:eb:33:a0:f4:a1:39:45:d8:
    98:c2:96:4f:e3:42:e2:fe:1a:7f:9b:8e:e7:eb:4a:
    7c:0f:9e:16:2b:ce:33:57:6b:31:5e:ce:cb:b6:40:
    68:37:bf:51:f5
Order:
    00:ff:ff:ff:ff:00:00:00:00:ff:ff:ff:ff:ff:ff:
    ff:ff:bc:e6:fa:ad:a7:17:9e:84:f3:b9:ca:c2:fc:
    63:25:51
Cofactor:  1 (0x1)
Seed:
    c4:9d:36:08:86:e7:04:93:6a:66:78:e1:13:9d:26:
    b7:81:9f:7e:90
writing EC key
-----BEGIN EC PRIVATE KEY-----
MIIBaAIBAQQgzJ2VgWhL4UBxprTNJ4xoTDixmEoRjl3Atbef8nUtmOeggfowgfcC
AQEwLAYHKoZIzj0BAQIhAP////8AAAABAAAAAAAAAAAAAAAA////////////////
MFsEIP////8AAAABAAAAAAAAAAAAAAAA///////////////8BCBaxjXYqjqT57Pr
vVV2mIa8ZR0GsMxTsPY7zjw+J9JgSwMVAMSdNgiG5wSTamZ44ROdJreBn36QBEEE
axfR8uEsQkf4vOblY6RA8ncDfYEt6zOg9KE5RdiYwpZP40Li/hp/m47n60p8D54W
K84zV2sxXs7LtkBoN79R9QIhAP////8AAAAA//////////+85vqtpxeehPO5ysL8
YyVRAgEBoUQDQgAEL70S8cSWMt0V9wAGZlY/Us+2rTSZI1ITHFmpXlOxSa81WRpo
AbmOI7kxHNeclj2ryefdRyCrNoZkpfl0L9+qqQ==
-----END EC PRIVATE KEY-----

If I generate the key with OpenSSL I get it in the format that has the curve group and point:

openssl ecparam -name prime256v1 -genkey -noout -out my.key.pem
$ openssl ec -in my.key.pem -text
read EC key
Private-Key: (256 bit)
priv:
    00:f1:9e:3d:87:45:6c:90:4b:3a:31:02:d5:dd:2e:
    71:3f:5c:ad:4d:c9:20:f6:14:80:e9:e2:51:8b:da:
    3e:64:9f
pub:
    04:10:30:e4:3b:f4:38:16:a3:52:76:e5:a7:22:a2:
    a5:63:6f:ea:fe:d5:cd:08:6f:f9:9a:06:a3:73:e0:
    aa:9d:f3:59:23:cb:f9:85:ca:3c:92:cc:30:31:17:
    8f:66:0a:e8:92:44:66:06:64:21:b9:19:4c:8c:7c:
    53:54:2d:9d:9d
ASN1 OID: prime256v1
NIST CURVE: P-256
writing EC key
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIPGePYdFbJBLOjEC1d0ucT9crU3JIPYUgOniUYvaPmSfoAoGCCqGSM49
AwEHoUQDQgAEEDDkO/Q4FqNSduWnIqKlY2/q/tXNCG/5mgajc+CqnfNZI8v5hco8
kswwMRePZgrokkRmBmQhuRlMjHxTVC2dnQ==
-----END EC PRIVATE KEY-----

Generated key like this will load fine and acmed will be able to function correctly.

On the side note apparently LibreSSL does not need curve_name to get affine_coordinates while OpenSSL does.

Also with certificate with algorithm = "ecdsa_p256" set even if I generate the account key with OpenSSL I will get this error:

[2019-11-18T14:47:47Z TRACE acmed::certificate] crt-1: Writing file "/etc/acmed/certs/hello2.whatclinic.net_ecdsa-p256.pk.pem"
...
[2019-11-18T14:47:47Z DEBUG acmed::certificate] crt-1: POST: https://acme-staging-v02.api.letsencrypt.org/acme/finalize/11595370/62009620
[2019-11-18T14:47:48Z TRACE acmed::certificate] crt-1: response body: {
      "type": "urn:ietf:params:acme:error:malformed",
      "detail": "Error parsing certificate request: x509: failed to parse ECDSA parameters as named curve",
      "status": 400
    }

The key for certificate also look like this:

read EC key
Private-Key: (256 bit)
priv:
    00:f8:47:da:d5:26:f7:d1:fa:63:33:05:2a:11:9e:
    86:ff:ba:f2:e1:57:56:e6:62:5b:17:dd:cc:aa:d5:
    ff:73:e7
pub:
    04:40:e3:bf:45:0a:4b:de:57:fe:6c:74:d2:d4:46:
    b0:fc:84:c0:45:4c:4e:8e:de:77:11:94:c4:e5:8d:
    2c:80:6a:e6:11:1c:d3:5d:88:1c:ed:07:43:d4:2f:
    de:3e:eb:57:6c:ed:42:b2:b3:e2:07:c5:66:e9:f3:
    74:26:9a:f9:54
Field Type: prime-field
Prime:
    00:ff:ff:ff:ff:00:00:00:01:00:00:00:00:00:00:
    00:00:00:00:00:00:ff:ff:ff:ff:ff:ff:ff:ff:ff:
    ff:ff:ff
A:
    00:ff:ff:ff:ff:00:00:00:01:00:00:00:00:00:00:
    00:00:00:00:00:00:ff:ff:ff:ff:ff:ff:ff:ff:ff:
    ff:ff:fc
B:
    5a:c6:35:d8:aa:3a:93:e7:b3:eb:bd:55:76:98:86:
    bc:65:1d:06:b0:cc:53:b0:f6:3b:ce:3c:3e:27:d2:
    60:4b
Generator (uncompressed):
    04:6b:17:d1:f2:e1:2c:42:47:f8:bc:e6:e5:63:a4:
    40:f2:77:03:7d:81:2d:eb:33:a0:f4:a1:39:45:d8:
    98:c2:96:4f:e3:42:e2:fe:1a:7f:9b:8e:e7:eb:4a:
    7c:0f:9e:16:2b:ce:33:57:6b:31:5e:ce:cb:b6:40:
    68:37:bf:51:f5
Order:
    00:ff:ff:ff:ff:00:00:00:00:ff:ff:ff:ff:ff:ff:
    ff:ff:bc:e6:fa:ad:a7:17:9e:84:f3:b9:ca:c2:fc:
    63:25:51
Cofactor:  1 (0x1)
Seed:
    c4:9d:36:08:86:e7:04:93:6a:66:78:e1:13:9d:26:
    b7:81:9f:7e:90
writing EC key
-----BEGIN EC PRIVATE KEY-----
MIIBaAIBAQQg+Efa1Sb30fpjMwUqEZ6G/7ry4VdW5mJbF93MqtX/c+eggfowgfcC
AQEwLAYHKoZIzj0BAQIhAP////8AAAABAAAAAAAAAAAAAAAA////////////////
MFsEIP////8AAAABAAAAAAAAAAAAAAAA///////////////8BCBaxjXYqjqT57Pr
vVV2mIa8ZR0GsMxTsPY7zjw+J9JgSwMVAMSdNgiG5wSTamZ44ROdJreBn36QBEEE
axfR8uEsQkf4vOblY6RA8ncDfYEt6zOg9KE5RdiYwpZP40Li/hp/m47n60p8D54W
K84zV2sxXs7LtkBoN79R9QIhAP////8AAAAA//////////+85vqtpxeehPO5ysL8
YyVRAgEBoUQDQgAEQOO/RQpL3lf+bHTS1Eaw/ITARUxOjt53EZTE5Y0sgGrmERzT
XYgc7QdD1C/ePutXbO1CsrPiB8Vm6fN0Jpr5VA==
-----END EC PRIVATE KEY-----

So this looks like same issue but when generating key for the certificate.

Your last message gave me the key to the solution. Between OpenSSL 1.0 and 1.1 there has been a change in the PEM format generated by the PEM_write_bio_PKCS8PrivateKey function (1.0.2 manual, 1.1.0 manual, look for PEM_write_bio_PKCS8PrivateKey()). As you can see, which is confirmed by your example, 1.0 uses a legacy format (called "traditional") which includes all of the curve's parameters whereas 1.1 only stores the curve's identifier.

Obviously, 1.1 can transparently handles both formats, but 1.0 only accepts the legacy one. You compiled ACMEd against 1.0, this is why it generates every key in the legacy format and cannot handle the newer key you manually generated with a higher version.

Also, this legacy format is obviously the reason why no NID is returned when calling .curve_name() (the curve itself is stored instead of its identifier), hence your first issue. Because there is nothing I can do about it, I think the solution is to explicitly require OpenSSL 1.1 (and maybe an equivalent for LibreSSL).

The difference is in defaults of EcGroup between versions as described on:
https://docs.rs/openssl/0.10.25/openssl/ec/struct.EcGroupRef.html#method.set_asn1_flag

So to get this working it should be sufficient to add this line:

group.set_asn1_flag(Asn1Flag::NAMED_CURVE);

Let me try it out and if this works I will send a PR.