Skip to content

Commit

Permalink
Display a user friendly error message when the private key and the ce…
Browse files Browse the repository at this point in the history
…rtificate don't match (#106)
  • Loading branch information
ebourg committed Jun 1, 2023
1 parent 5631c83 commit b2b47a9
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 3 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ See https://ebourg.github.io/jsign for more information.
* SHA-1 signing with Azure Key Vault is now possible (contributed by Andrij Abyzov)
* Signing a MSI file already signed with an extended signature is no longer rejected
* An issue causing some MSI files to become corrupted once signed has been fixed
* A user friendly error message is now displayed when the private key and the certificate don't match
* Setting `-Djava.security.debug=sunpkcs11` with the `YUBIKEY` storetype no longer triggers an error
* The cloud keystore name is no longer treated as a relative file by the Ant task and the Maven plugin
* The paths are resolved relatively to the Ant/Maven/Gradle subproject or module directory instead of the root directory
Expand Down
27 changes: 24 additions & 3 deletions jsign-core/src/main/java/net/jsign/AuthenticodeSigner.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.PublicKey;
import java.security.Security;
import java.security.SignatureException;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
Expand Down Expand Up @@ -356,9 +358,7 @@ protected CMSSignedData createSignedData(Signable file) throws Exception {
CMSSignedData sigData = generator.generate(contentInfo.getContentType(), contentInfo.getContent());

// verify the signature
DigestCalculatorProvider digestCalculatorProvider = new AuthenticodeDigestCalculatorProvider();
SignerInformationVerifier verifier = new JcaSignerInfoVerifierBuilder(digestCalculatorProvider).build(chain[0].getPublicKey());
sigData.getSignerInfos().iterator().next().verify(verifier);
verify(sigData);

// timestamping
if (timestamping) {
Expand Down Expand Up @@ -457,6 +457,27 @@ private boolean isSelfSigned(X509Certificate certificate) {
return certificate.getSubjectDN().equals(certificate.getIssuerDN());
}

private void verify(CMSSignedData signedData) throws SignatureException, OperatorCreationException {
PublicKey publicKey = chain[0].getPublicKey();
DigestCalculatorProvider digestCalculatorProvider = new AuthenticodeDigestCalculatorProvider();
SignerInformationVerifier verifier = new JcaSignerInfoVerifierBuilder(digestCalculatorProvider).build(publicKey);

boolean result = false;
Throwable cause = null;
try {
result = signedData.verifySignatures(signerId -> verifier, false);
} catch (Exception e) {
cause = e;
while (cause.getCause() != null) {
cause = cause.getCause();
}
}

if (!result) {
throw new SignatureException("Signature verification failed, the private key doesn't match the certificate", cause);
}
}

/**
* Creates the authenticated attributes for the SignerInfo section of the signature.
*
Expand Down
66 changes: 66 additions & 0 deletions jsign-core/src/test/java/net/jsign/SignerHelperTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -287,4 +287,70 @@ public void testESigner() throws Exception {

SignatureAssert.assertSigned(new PEFile(targetFile), SHA256);
}

@Test
public void testSignWithMismatchedKeyAlgorithms() throws Exception {
File sourceFile = new File("target/test-classes/wineyes.exe");
File targetFile = new File("target/test-classes/wineyes-signed-mismatched-keys.exe");

FileUtils.copyFile(sourceFile, targetFile);

SignerHelper signer = new SignerHelper(new StdOutConsole(2), "parameter")
.keyfile("target/test-classes/keystores/privatekey-ec-p384.pkcs1.pem")
.keypass("password")
.certfile("target/test-classes/keystores/jsign-test-certificate-full-chain.pem");

try {
signer.sign(targetFile);
fail("No exception thrown");
} catch (SignerException e) {
assertEquals("message", "Signature verification failed, the private key doesn't match the certificate", e.getCause().getMessage());
}

SignatureAssert.assertSigned(Signable.of(targetFile));
}

@Test
public void testSignWithMismatchedKeyLengths() throws Exception {
File sourceFile = new File("target/test-classes/wineyes.exe");
File targetFile = new File("target/test-classes/wineyes-signed-mismatched-keys.exe");

FileUtils.copyFile(sourceFile, targetFile);

SignerHelper signer = new SignerHelper(new StdOutConsole(2), "parameter")
.keyfile("target/test-classes/keystores/privatekey.pkcs1.pem")
.keypass("password")
.certfile("target/test-classes/keystores/jsign-root-ca.pem");

try {
signer.sign(targetFile);
fail("No exception thrown");
} catch (SignerException e) {
assertEquals("message", "Signature verification failed, the private key doesn't match the certificate", e.getCause().getMessage());
}

SignatureAssert.assertSigned(Signable.of(targetFile));
}

@Test
public void testSignWithMismatchedRSAKeys() throws Exception {
File sourceFile = new File("target/test-classes/wineyes.exe");
File targetFile = new File("target/test-classes/wineyes-signed-mismatched-keys.exe");

FileUtils.copyFile(sourceFile, targetFile);

SignerHelper signer = new SignerHelper(new StdOutConsole(2), "parameter")
.keyfile("target/test-classes/keystores/privatekey.pkcs1.pem")
.keypass("password")
.certfile("target/test-classes/keystores/jsign-test-certificate-partial-chain.pem");

try {
signer.sign(targetFile);
fail("No exception thrown");
} catch (SignerException e) {
assertEquals("message", "Signature verification failed, the private key doesn't match the certificate", e.getCause().getMessage());
}

SignatureAssert.assertSigned(Signable.of(targetFile));
}
}

0 comments on commit b2b47a9

Please sign in to comment.