cli
cli copied to clipboard
`step ca renew` fails when existing certificate doesn't have `clientAuth` extended attribute
Subject of the issue
Issuing an immediately attempting to renew the certificate causes the following error
frebib@:~$ STEPPATH=/tmp/step step ca certificate $HOSTNAME /tmp/step/$HOSTNAME.crt /tmp/step/$HOSTNAME.key --token $TOKEN
✔ CA: https://<snip>/1.0/sign
✔ Certificate: /tmp/step/<snip>.crt
✔ Private Key: /tmp/step/<snip>.key
frebib@:~$ STEPPATH=/tmp/step step ca renew /tmp/step/$HOSTNAME.crt /tmp/step/$HOSTNAME.key
error renewing certificate: client.Renew; client POST https://<snip>/renew failed: Post "https://<snip>/renew": remote error: tls: bad certificate
step-ca logs:
2021/10/17 11:52:29 /usr/local/go/src/net/http/server.go:3157: http: TLS handshake error from <snip>: tls: failed to verify client certificate: x509: certificate specifies an incompatible key usage
I'm using a "pretty much default" JWK provisioner, but with this template, which I notably removed clientAuth
from:
{
"subject": {{ toJson .Subject }},
"sans": {{ toJson .SANs }},
{{- if typeIs "*rsa.PublicKey" .Insecure.CR.PublicKey }}
"keyUsage": ["keyEncipherment", "digitalSignature"],
{{- else }}
"keyUsage": ["digitalSignature"],
{{- end }}
"extKeyUsage": ["serverAuth"]
}
I expect that this is "that's just how it works" and my lack of understanding of some of the nuances of x509. It would probably make sense to spit out a client warning/error, though.
Your environment
- OS - Debian
- Version - Whatever the latest is at time of writing (0.7.5?)
Steps to reproduce
See above
Expected behaviour
Either a warning, or the certificate renews without problem
Actual behaviour
Non-descript error on the client side, slightly more information in the server logs. Certificate is not renewed
Our renewal mechanism uses client authentication / mTLS. I'm not sure I would say this is "working as intended", but what's going on here totally makes sense and the TLS stack is behaving as expected by enforcing the extended key use constraints.
The obvious / easy workaround here would be to add clientAuth
back. But, presumably, you've removed it for a reason (I am curious what that reason is, but understand if you're unable to share).
We do have a project on our backlog to add an alternative mechanism for renewal using JWTs (still authenticated using your cert / signed using the client's private key / using the x5c
header). I believe this only requires the digitalSignature
key use, and wouldn't require clientAuth
(which I believe is specific to mTLS). So this may be resolved by https://github.com/smallstep/certificates/issues/177. I don't currently have an ETA on this functionality. It's a surprisingly big project. I suppose we should consider how one might renew a certificate that doesn't have the digitalSignature
key use, either.
Regarding error messages... noted, but I am afraid this error is probably triggered pretty deep in the golang TLS stack during the handshake, so that may be difficult to do. At a minimum, we can add this to the list of reasons to switch to x5c
/JWT.
Edit:
I just realized I didn't really connect the dots between the "renew after expiry" ticket I linked above and x5c
/JWT-based renewal. Those are related because JWT-based authentication allows us to implement more sophisticated authentication policies and depend less on clunky workarounds to RFC5280 certificate path validation used by [m]TLS. This is relevant to renew-after-expiry, where we'd like to allow granular use of certificates after they've expired. It's also relevant here, where we may want to ignore [extended] key use for this particular endpoint.
Yes, this is working as intended.
But our X5C provisioner might not, right now this requires clientAuth
and digitalSignature
, probably digitalSignature
should be the only one required, as you're signing the token with the certificate key, I can't see any real reason to require the clientAuth
. @mmalone