x.509 S/MIME commit signing: issuer verification cannot be performed with both key usage and extended key usage values set
Summary
Update: Further investigation identified that the problem relates to having certificates with both keyUsage and extendedKeyUsage populated.
Quoting from a 14 year old thread reporting a similar issue:
By default a PKCS#7 structure is used for S/MIME mail and that extended key usage specificaly excludes that possibility: i.e. the CA didn't intend that purpose.
The extensions each place restrictions on how the key can be used it is an AND and not an OR operation. So key usage says you can only use the key for digital signatures AND EKU also says you can only use if for SSL client aut
This seems to reflect what's in the TLS RFCs: https://datatracker.ietf.org/doc/html/rfc5280#page-44
- More detail.
- Email Protection works around this.
- Additional troubleshooting steps have been documented, and revised.
Original content
hidden here
A customer raised a support ticket as the certificates their developers are issued with for signing commits, do to result in verified commits in GitLab.Their signatures do not contain the intermediate issuer certificate, and trusting the intermediate issuer does not resolve this issue.
Their tool chain does not support adding the intermediate, so this feature is broken for them, with no workaround.
Steps to reproduce
-
Generate a code signing certificate. See the GDK for more details.
However, the CA setup needs to differ in one respect: you are likely to need a separate intermediate CA certificate and key for signing with, in between the root and the signing certificate.
As is, it's possible to test by trying to trust the signing certificate directly which seems like it should be possible, though I couldn't get it to work even with
PKCS7_NOINTERN
- see commentIt seems that
p12
files have to have the key and certificate; this isn't realistic for certificate authorities as the private keys are locked up. Yourgpgsm
keychain can have just certificates added with:gpgsm --import /etc/ssl/sign_csrs_with_me.crt
-
Don't add any certificates as trusted in GitLab.
-
Sign a commit and push to GitLab
-
Run through the troubleshooting steps;
signature.verified_signature
will fail -
Proceed with the crypto steps in the troubleshooting.
-
The failure will arise at the
signature.__send__(:p7).verify([], signature.__send__(:cert_store), signature.__send__(:signed_text))
indicating thatp7.verify([], cert_store, signed_text)
here isn't working - as expected. -
Pull out the certificate store
cert_store = signature.__send__ :cert_store
-
Add the intermediate as trusted
cert_store.add_file("/etc/ssl/sign_csrs_with_me.crt")
-
Recheck against the amended certificate store;
false
is still returned.signature.__send__(:p7).verify([], cert_store, signature.__send__(:signed_text))
-
Add the root
cert_store.add_file("/etc/ssl/ca_root.crt")
-
Recheck against the amended certificate store;
true
is now returned (assuming your signature contains the chain .. if it doesn't, it won't be possible to get this to runtrue
)signature.__send__(:p7).verify([], cert_store, signature.__send__(:signed_text))
Example Project
What is the current bug behavior?
Customer ran a check using openssl.
This is expected to fail, since all trust stores have been invalidated:
$ /opt/gitlab/embedded/bin/openssl verify -no-CAfile -no-CApath -partial_chain codesigning.crt
O = Example Inc., CN = jondoe@example.com
error 20 at 0 depth lookup: unable to get local issuer certificate
error codesigning.crt: verification failed
This succeeded:
$ /opt/gitlab/embedded/bin/openssl verify -no-CAfile -no-CApath -partial_chain \
> -trusted /etc/gitlab/trusted-certs/sign_csrs_with_me.pem \
> codesigning.crt
codesigning.crt: OK
So, this verified that this was the issuer, and in principle trust could be established.
But it doesn't work for the OpenSSL::PKCS7
method verify
.
It's worth highlighting this from the openssl verify
man page:
-trusted file
A file of trusted certificates, which must be self-signed, unless the -partial_chain option is specified.
This also succeeds:
/opt/gitlab/embedded/bin/openssl verify codesigning.crt
This isn't using trusted
- it's using the default behaviour with the compiled in locations for CAfile
and CApath
. But, since it passes, we can see that "vanilla" openssl verify
doesn't worry of trusted certificates are self signed.
This was why we tested with -partial_chain
. It appears as though OpenSSL::PKCS7
effectively performs a verify with -trusted
, and mandates that trusted certificates are self signed.
OR .. something else is going on that looks just like this.
What is the expected correct behavior?
If a code signing certificate verifies using openssl verify
against the certificates GitLab trusts, the signature check for x509 should also succeed.
Relevant logs and/or screenshots
Output of checks
reproduced on v16.6.4-ee