Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Certificate chain not found with jarsigner and Azure Key Vault #222

Closed
JohnLBergqvist opened this issue May 15, 2024 · 26 comments
Closed

Certificate chain not found with jarsigner and Azure Key Vault #222

JohnLBergqvist opened this issue May 15, 2024 · 26 comments
Labels

Comments

@JohnLBergqvist
Copy link

JohnLBergqvist commented May 15, 2024

I'm using the following jarsigner command with 6.1-SNAPSHOT to work around #221.

jarsigner -J-cp "-Jjsign-6.1-SNAPSHOT.jar -J--add-modules -Jjava.sql -providerClass net.jsign.jca.JsignJcaProvider -providerArg [vault-name] -keystore NONE -storetype AZUREKEYVAULT -storepass [access-token] application.jar [cert]
I've confirmed my access token is valid.

And I get jarsigner: Certificate chain not found for: [cert]. [cert] must reference a valid KeyStore key entry containing a private key and corresponding public key certificate chain.

I have a valid certificate named [cert] inside my keystore, so what am I doing wrong?

@ebourg
Copy link
Owner

ebourg commented May 15, 2024

Does it work if you set the -certchain parameter ?

@JohnLBergqvist
Copy link
Author

JohnLBergqvist commented May 15, 2024

Does it work if you set the -certchain parameter ?

I've not tried this. What should I set it to? I was under the impression I could sign solely using the certificate from the key vault (which itself has been signed against my 3rd party certificate from an external provider)

@ebourg
Copy link
Owner

ebourg commented May 15, 2024

I've not tried this. What should I set it to?

Set it to a file containing your signing certificate.

I was under the impression I could sign solely using the certificate from the key vault

Yes that's how it should work, but there is an issue somewhere, it doesn't help that jarsigner hides the details (the exception thrown by JsignJcaProvider is swallowed). Providing the certificate on the command line will allow to check if the key can be used.

You could also try signing a random .exe file with Jsign and see if a more explicit error message appears. Try this:

jsign --storetype AZUREKEYVAULT -storepass [access-token] --keystore [vault-name] --alias [cert] application.exe

@JohnLBergqvist
Copy link
Author

JohnLBergqvist commented May 16, 2024

You could also try signing a random .exe file with Jsign and see if a more explicit error message appears. Try this:

jsign --storetype AZUREKEYVAULT -storepass [access-token] --keystore [vault-name] --alias [cert] application.exe

Well I tried this first, and it signs the exe perfectly, no warnings/errors.

I then downloaded the certificate in .cer format from Azure, which i've confirmed contains the full root/intermediate/cert chain, and passed that to the `-certchain parameter like you suggested:

jarsigner -J-cp -Jjsign-6.1-SNAPSHOT.jar -J--add-modules -Jjava.sql -verbose -providerClass net.jsign.jca.JsignJcaProvider -providerArg [vault-name] -keystore NONE -storetype AZUREKEYVAULT -storepass $API_ACCESS_TOKEN -cert chain downloaded_cert.cer application.jar [cert-name] and I get:
jarsigner: Cannot restore certchain from file specified

Adding the --certfile option to the jsign command for my exe above gives me:

jsign: Failed to load the certificate from my-cert.cer
java.security.cert.CertificateException: No certificate data found
        at java.base/sun.security.provider.X509Factory.parseX509orPKCS7Cert(X509Factory.java:456)
        at java.base/sun.security.provider.X509Factory.engineGenerateCertificates(X509Factory.java:356)
        at java.base/java.security.cert.CertificateFactory.generateCertificates(CertificateFactory.java:480)
        at net.jsign.CertificateUtils.loadCertificateChain(CertificateUtils.java:59)
        at net.jsign.SignerHelper.build(SignerHelper.java:349)
        at net.jsign.SignerHelper.sign(SignerHelper.java:435)
        at net.jsign.SignerHelper.execute(SignerHelper.java:278)
        at net.jsign.JsignCLI.execute(JsignCLI.java:166)
        at net.jsign.JsignCLI.main(JsignCLI.java:45)

I also have the option to download the cert from Azure as a PFX/PEM file (although if this one contains the private key, surely this defeats the point of keeping the private keys only on an HSM in the first place)? I get the same error as above from jarsign when using the PFX file with -certchain, and with jsign on the .exe I get this:

jsign: Failed to load the certificate from my-cert.pfx
java.security.cert.CertificateParsingException: signed fields invalid
        at java.base/sun.security.x509.X509CertImpl.parse(X509CertImpl.java:1774)
        at java.base/sun.security.x509.X509CertImpl.<init>(X509CertImpl.java:178)
        at java.base/sun.security.x509.X509CertImpl.newX509CertImpl(X509CertImpl.java:304)
        at java.base/sun.security.provider.X509Factory.parseX509orPKCS7Cert(X509Factory.java:471)
        at java.base/sun.security.provider.X509Factory.engineGenerateCertificates(X509Factory.java:356)
        at java.base/java.security.cert.CertificateFactory.generateCertificates(CertificateFactory.java:480)
        at net.jsign.CertificateUtils.loadCertificateChain(CertificateUtils.java:59)
        at net.jsign.SignerHelper.build(SignerHelper.java:349)
        at net.jsign.SignerHelper.sign(SignerHelper.java:435)
        at net.jsign.SignerHelper.execute(SignerHelper.java:278)
        at net.jsign.JsignCLI.execute(JsignCLI.java:166)
        at net.jsign.JsignCLI.main(JsignCLI.java:45)

@ebourg
Copy link
Owner

ebourg commented May 16, 2024

I tried this first, and it signs the exe perfectly, no warnings/errors.

Good, at least it rules out wrong keystore/storepass/alias parameters.

I also have the option to download the cert from Azure as a PFX/PEM file (although if this one contains the private key, surely this defeats the point of keeping the private keys only on an HSM in the first place)?

Don't worry the certificate contains only the public key, the private key is locked in the HSM.

jsign: Failed to load the certificate from my-cert.cer
java.security.cert.CertificateException: No certificate data found

jsign: Failed to load the certificate from my-cert.pfx
java.security.cert.CertificateParsingException: signed fields invalid

Could you send these certificate files to ebourg@apache.org please? I'd like to investigate why they fail to load.

@ebourg ebourg changed the title Certificate chain not found for: [cert]. [cert] must reference a valid KeyStore key entry containing a private key and corresponding public key certificate chain Certificate chain not found with jarsigner and Azure Key Vault May 16, 2024
@JohnLBergqvist
Copy link
Author

JohnLBergqvist commented May 16, 2024

I tried this first, and it signs the exe perfectly, no warnings/errors.

Good, at least it rules out wrong keystore/storepass/alias parameters.

I also have the option to download the cert from Azure as a PFX/PEM file (although if this one contains the private key, surely this defeats the point of keeping the private keys only on an HSM in the first place)?

Don't worry the certificate contains only the public key, the private key is locked in the HSM.

jsign: Failed to load the certificate from my-cert.cer
java.security.cert.CertificateException: No certificate data found
jsign: Failed to load the certificate from my-cert.pfx
java.security.cert.CertificateParsingException: signed fields invalid

Could you send these certificate files to ebourg@apache.org please? I'd like to investigate why they fail to load.

Unfortunately i'm unable to send you the certificates directly. What I can tell you is that if I open the .cer file in the windows certificate viewer, i see the full chain:
image
However if I open the same file in the Java keystore explorer's certificate viewer, I don't see the root & intermediate certificates in the chain:
image

I used the following settings on GlobalSign's website to create my certificate in the Key vault: https://support.globalsign.com/digital-certificates/digital-certificate-installation/Code-Signing-certificate-setup-in-Azure-Key-vault

Content-Type: PKCS#12
EKUS: 1.3.6.1.5.5.7.3.3
X.509 Key Usage Flags: Digital Signature & Key Encipherment
Reuse Key on Renewal: No
Exportable Private Key: No
Key Type: RSA-HSM
Key Size: 4096
Enable Certificate Transparency: Yes

@JohnLBergqvist
Copy link
Author

I've made some progress. From my certificate provider, i've noticed they've given me the full chain in a p7b file. I can sign with jarsigner & jsign (with it talking to azure as required):

TSA location: http://ts.ssl.com
 updating: META-INF/MANIFEST.MF
   adding: META-INF/[MY-CERT].SF
   adding: META-INF/[MY-CERT].RSA
   adding: com/
   ...
  signing: com/...
jar signed.

I don't get the previous warning I was getting with the prior PEM about: The signer's certificate chain is invalid. Reason: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target and Keystore explorer appears to have no problems with the p7b file, it's showing the full chain correctly.

However when I then do jarsigner -verify -verbose -certs [signed-jar], I get: jarsigner: java.lang.SecurityException: cannot verify signature block file META-INF/[MY-CERT]
Which is so weird. This is only with this p7b file. I don't understand how jarsigner can sign it, say it's been signed correctly, then the next second crash when it comes to verify it :/

@ebourg
Copy link
Owner

ebourg commented May 16, 2024

jsign: Failed to load the certificate from my-cert.pfx
java.security.cert.CertificateParsingException: signed fields invalid

I've figured out this error, a pfx files is a PKCS#12 keystore, but Jsign expects either a PEM file or a PKCS#7 file (p7b).

I don't understand how jarsigner can sign it, say it's been signed correctly, then the next second crash when it comes to verify it :/

I guess you also have to set the -certchain parameter for verifying

@JohnLBergqvist
Copy link
Author

JohnLBergqvist commented May 16, 2024

jsign: Failed to load the certificate from my-cert.pfx
java.security.cert.CertificateParsingException: signed fields invalid

I've figured out this error, a pfx files is a PKCS#12 keystore, but Jsign expects either a PEM file or a PKCS#7 file (p7b).

That's fine, I've got the PKCS#7 file now, but like I said, jarsigner sucessfully signs with it, but cannot then verify the jar once it's been signed :/

I don't understand how jarsigner can sign it, say it's been signed correctly, then the next second crash when it comes to verify it :/

I guess you also have to set the -certchain parameter for verifying

I've tried that but that makes no difference, I still get the error:

jarsigner -J-cp -Jjsign-6.1-SNAPSHOT.jar -J--add-modules -Jjava.sql -verbose -providerClass net.jsign.jca.JsignJcaProvider -providerArg [vault-name -keystore NONE -storetype AZUREKEYVAULT -storepass [access-token] -certchain fullchain.p7b myjar.jar -tsa http://ts.ssl.com [cert-name]
requesting a signature timestamp
TSA location: http://ts.ssl.com
 updating: META-INF/MANIFEST.MF
   adding: META-INF/[CERT-NAME].SF
   adding: META-INF/[CERT-NAME].RSA
   adding: com/...
   signing: com/...

jar signed.

jarsigner -verify -verbose -certs -certchain fullchain.p7b myjar.jar
jarsigner: java.lang.SecurityException: cannot verify signature block file META-INF/[CERT-NAME]

I'm using

openjdk version "17.0.9" 2023-10-17 LTS
OpenJDK Runtime Environment Corretto-17.0.9.8.1 (build 17.0.9+8-LTS)
OpenJDK 64-Bit Server VM Corretto-17.0.9.8.1 (build 17.0.9+8-LTS, mixed mode, sharing)

If I open fullchain.p7b inside keystore explorer, it all reads fine, and I can see the full chain. If I open the signed jar up in keystore explorer, it crashes as above, but I at least get more of a stack trace:
image

I've just noticed that the root certificate i'm using has a signature algorithm of SHA-384 with RSA, unlike the olther 2 in the chain which use SHA-256 with RSA - could that be it?

@ebourg
Copy link
Owner

ebourg commented May 16, 2024

I've just noticed that the root certificate i'm using has a signature algorithm of SHA-384 with RSA, unlike the olther 2 in the chain which use SHA-256 with RSA - could that be it?

I don't think so, that's well supported by currents JDKs.

You may get some useful details by verifying with the -J-Djava.security.debug=all option.

@JohnLBergqvist
Copy link
Author

I've just noticed that the root certificate i'm using has a signature algorithm of SHA-384 with RSA, unlike the olther 2 in the chain which use SHA-256 with RSA - could that be it?

I don't think so, that's well supported by currents JDKs.

You may get some useful details by verifying with the -J-Djava.security.debug=all option.

Thanks. This is the tail end of that log - does this shed any more info on it to you?

keystore: JKS keystore detected: C:\Users\John\.sdkman\candidates\java\17.0.9-amzn\lib\security\cacerts
Provider: KeyStore.JKS type from: SUN
keystore: JavaKeyStore load: private key count: 0. trusted key count: 146
keystore: Loaded a keystore in JKS format
Provider: KeyStore.PKCS12 type from: SUN
jar: beginEntry META-INF/MANIFEST.MF
jar: beginEntry META-INF/COVER.SF
jar: processEntry: processing block
jar: beginEntry META-INF/COVER.RSA
jar: processEntry: processing block
ProviderList: ThreadLocal providers: [SUN, SunRsaSign, SunEC, SunJCE]
certpath: Constraints: DSA keySize < 1024
certpath: Constraints set to keySize: keySize < 1024
certpath: Constraints: MD2
certpath: Constraints: MD5
certpath: Constraints: RSA keySize < 1024
certpath: Constraints set to keySize: keySize < 1024
certpath: Constraints: SHA1 denyAfter 2019-01-01
certpath: Constraints set to denyAfter
certpath: DenyAfterConstraint read in as: year 2019, month = 1, day = 1
certpath: DenyAfterConstraint date set to: 2019-01-01
ProviderList: Disabling ThreadLocal providers
ProviderList: ThreadLocal providers: [SUN, SunRsaSign, SunEC, SunJCE]
jar: Unsupported signer attribute: 1.2.840.113549.1.9.16.2.47
Provider: MessageDigest.SHA-256 algorithm from: SUN
Provider: MessageDigest.SHA-256 algorithm from: SUN
Provider: MessageDigest.SHA-256 algorithm from: SUN
Provider: Signature.SHA256withECDSA verification algorithm from: SunEC
Provider: MessageDigest.SHA-256 algorithm from: SUN
jar:
jar: Detected signature timestamp (#7325152077045029379) generated on Thu May 16 11:26:54 BST 2024
jar:
Provider: MessageDigest.SHA-256 algorithm from: SUN
Provider: MessageDigest.SHA-256 algorithm from: SUN
Provider: Signature.SHA256withRSA verification algorithm from: SunRsaSign
ProviderList: Disabling ThreadLocal providers
jarsigner: java.lang.SecurityException: cannot verify signature block file META-INF/COVER

@JohnLBergqvist
Copy link
Author

@ebourg I've got permission to email you the public key files now, so i'll send those across 🙂

@TheNormalnij
Copy link

TheNormalnij commented May 16, 2024

I have this issue with the same certification chain,
Jar file cannot be verified unless "GlobalSign GCC R45 CodeSigning CA 2020" is added into local key store.

@ebourg
Copy link
Owner

ebourg commented May 16, 2024

That's indeed a root certificate issue. "GlobalSign GCC R45 CodeSigning CA 2020" is an intermediate certificate, the root is "GlobalSign Code Signing Root R45" and it isn't included in the OpenJDK truststore (at least not in OpenJDK 17.0.11). GlobalSign provide a cross signed variant of this certificate which is signed by "GlobalSign Root CA - R3", and this one is included in the JDK.

So I think adding this certificate to the chain will solve the issue.

@JohnLBergqvist
Copy link
Author

JohnLBergqvist commented May 16, 2024

That's indeed a root certificate issue. "GlobalSign GCC R45 CodeSigning CA 2020" is an intermediate certificate, the root is "GlobalSign Code Signing Root R45" and it isn't included in the OpenJDK truststore (at least not in OpenJDK 17.0.11). GlobalSign provide a cross signed variant of this certificate which is signed by "GlobalSign Root CA - R3", and this one is included in the JDK.

So I think adding this certificate to the chain will solve the issue.

@ebourg do you know how I would do that? I'm not sure on the exact openssl command. Or do I have to go back to globalsign and get them to issue me with a new certificate signed by the correct root?

@JohnLBergqvist
Copy link
Author

JohnLBergqvist commented May 16, 2024

@ebourg Actually, i've created my own chain now, however from what I can see the cross-signed version is the one in my original p7b file from them.

@ebourg
Copy link
Owner

ebourg commented May 16, 2024

Ok, let's recap:

  • cert.pem contains only your signing certificate, and not the intermediate certificates. It shouldn't be used with jarsigner.
  • cert.p7b contains the whole chain, with "GlobalSign Root CA - R3" as root. That's the right file to use with jarsigner.
  • azure-cert.cer contains only your signing certificate, and not the intermediate certificates. That probably explains why jarsigner didn't work without the certchain parameter providing the full chain. Can you replace the Azure certificate with the whole chain from cert.p7b?
  • Jsign manages to sign an executable with your Azure key and certificate. But is the signature valid? (look into the Digital Signatures tab of the file properties on Windows)

If you didn't regenerate the jar file between your attempts, I suggest doing so and trying again with -certchain cert.p7b.

@JohnLBergqvist
Copy link
Author

JohnLBergqvist commented May 16, 2024

So i'm using the original P7B file from globalsign, i've verified that this file contains the Cross-signed variant of the Root CodeSigning Certificate from them (same as the one you linked above).

I've signed the exe file with this, and windows is saying it's valid. There are 4 certificate layers on the signed exe according to Windows:
image

The P7B file contains the bottom 3 certificates. The top-most one (GlobalSign) i've confirmed is also in the JDK's default key store - the rest aren't (screenshot showing the certificate in my JDK's keystore):
image

If I sign my jar with that p7b file, i still get the same jarsigner: java.lang.SecurityException: cannot verify signature block file META-INF/COVER file when attempting to verify it after the signing has finished.

@ebourg
Copy link
Owner

ebourg commented May 16, 2024

Extract the META-INF/COVER.RSA file from the signed jar, rename it to .p7b and open it. Does it contain the same chain as cert.p7b?

@JohnLBergqvist
Copy link
Author

Extract the META-INF/COVER.RSA file from the signed jar, rename it to .p7b and open it. Does it contain the same chain as cert.p7b?

Yes, the chain is identical. What was COVER.RSA is 4kb bigger than the original file though - but all 3 certificates seem to match when I view them in both Windows & Keystore explorer

@ebourg
Copy link
Owner

ebourg commented May 16, 2024

Yes COVER.RSA is larger because it contains the signature in addition to the certificate store.

Looking at the JDK code, the exception is thrown here:
https://github.com/openjdk/jdk/blob/fa3e94d30f11bdccbe290041ae19490ce4940bb1/src/java.base/share/classes/sun/security/util/SignatureFileVerifier.java#L308

The PKCS7.verify() method returns null when checking the content of COVER.RSA:
https://github.com/openjdk/jdk/blob/fa3e94d30f11bdccbe290041ae19490ce4940bb1/src/java.base/share/classes/sun/security/pkcs/PKCS7.java#L545

You can try to call this method directly in a test class and debug step by step to understand why it returns null.

@TheNormalnij
Copy link

Debugger can be attached to jarsigner too.
jarsigner -verify jmLogicKit-rt.jar -J-agentlib:jdwp=transport=dt_socket,address=9000,server=y,suspend=y

@TheNormalnij
Copy link

It returns false in RSASignature.verify for me.

Btw, signersInfo in PKCS7.verify() contains only GlobalSign R3 certificate

I was truing different certificate chains. When i try full chain GlobalSign R3 -> GlobalSign Code Signing Root R45 -> GlobalSign GCC R45 CodeSigning CA 2020 -> my cert, jarsigner shows warning "The signer certificate's KeyUsage extension doesn't allow code signing.

>>> Signer
    X.509, CN=GlobalSign, O=GlobalSign, OU=GlobalSign Root CA - R3
    Signature algorithm: SHA256withRSA, 2048-bit key
    [trusted certificate]
    [KeyUsage extension does not support code signing]
    X.509, CN=GlobalSign Code Signing Root R45, O=GlobalSign nv-sa, C=BE
    Signature algorithm: SHA384withRSA, 4096-bit key
    [certificate is valid from 7/28/20, 3:00 AM to 3/18/29, 3:00 AM]
    X.509, CN=GlobalSign GCC R45 CodeSigning CA 2020, O=GlobalSign nv-sa, C=BE
    Signature algorithm: SHA256withRSA, 4096-bit key
    [certificate is valid from 7/28/20, 3:00 AM to 7/28/30, 3:00 AM]
    X.509, EMAILADDRESS=<deleted>, CN=<deleted> O=<deleted>, L=<deleted>, ST=<deleted>, C=<deleted>
    Signature algorithm: SHA256withRSA, 4096-bit key
    [certificate is valid from <deleted> to <deleted>]

@JohnLBergqvist
Copy link
Author

JohnLBergqvist commented May 17, 2024

@ebourg @TheNormalnij Yes, I find it odd that both the non-cross-signing Code Signing R3 certificates, and the Cross-signed version apparently don't allow code signing :/

Anyway, I created a chain like so:

  1. GlobalSign Code Signing Root R45 (R3 cross)
  2. GlobalSign GCC R45 CodeSigning CA 2020
  3. My own certificate

But in reverse order - with my certificate at the start of thefile, and the root at the bottom of the file, and jarsigner -verify no longer crashes! It seems before it was looking at the first certificate in the chain, which was the root and apparently unsuitable for code signing, and then just giving up and not looking any further.

>>> Signer
    X.509, CN=<deleted>, O=<deleted>, L=<deleted>, ST=<deleted>, C=<deleted>
    Signature algorithm: SHA256withRSA, 4096-bit key
    [certificate is valid from <deleted> to <deleted>]
    X.509, CN=GlobalSign GCC R45 CodeSigning CA 2020, O=GlobalSign nv-sa, C=BE
    Signature algorithm: SHA256withRSA, 4096-bit key
    [certificate is valid from 7/28/20, 12:00 AM to 7/28/30, 12:00 AM]
    X.509, CN=GlobalSign Code Signing Root R45, O=GlobalSign nv-sa, C=BE
    Signature algorithm: SHA384withRSA, 4096-bit key
    [certificate is valid from 7/28/20, 12:00 AM to 3/18/29, 12:00 AM]

Is reversing the order like this OK? I mean jarsigner & the keystore explorer certainly aren't complaining any more. If I open the jar in keystore explorer, it shows the chain in the correct order, with root at the top.

@ebourg
Copy link
Owner

ebourg commented May 17, 2024

I don't know if jarsigner expects a specific order, it doesn't seem to be documented. But if it works I guess that's fine.

@JohnLBergqvist
Copy link
Author

Happy to close this now, and sorry for the wild goose chase!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants