/** * Copyright 2012 Emmanuel Bourg * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package net.jsign; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.security.KeyStore; import java.security.Security; import java.util.HashSet; import java.util.List; import junit.framework.TestCase; import net.jsign.pe.PEFile; import net.jsign.timestamp.AuthenticodeTimestamper; import net.jsign.timestamp.TimestampingException; import net.jsign.timestamp.TimestampingMode; import org.apache.commons.io.FileUtils; import org.bouncycastle.asn1.nist.NISTObjectIdentifiers; import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; import org.bouncycastle.cms.CMSSignedData; import org.bouncycastle.cms.SignerInformation; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.tsp.TSPAlgorithms; public class PESignerTest extends TestCase { private static String PRIVATE_KEY_PASSWORD = "password"; private static String ALIAS = "test"; private KeyStore getKeyStore() throws Exception { KeyStore keystore = KeyStore.getInstance("JKS"); keystore.load(new FileInputStream("target/test-classes/keystore.jks"), "password".toCharArray()); return keystore; } public void testSign() throws Exception { File sourceFile = new File("target/test-classes/wineyes.exe"); File targetFile = new File("target/test-classes/wineyes-signed.exe"); FileUtils.copyFile(sourceFile, targetFile); PEFile peFile = new PEFile(targetFile); PESigner signer = new PESigner(getKeyStore(), ALIAS, PRIVATE_KEY_PASSWORD) .withTimestamping(false) .withProgramName("WinEyes") .withProgramURL("http://www.steelblue.com/WinEyes"); signer.sign(peFile); peFile = new PEFile(targetFile); List<CMSSignedData> signatures = peFile.getSignatures(); assertNotNull(signatures); assertEquals(1, signatures.size()); CMSSignedData signature = signatures.get(0); assertNotNull(signature); peFile.printInfo(System.out); } public void testTimestampAuthenticode() throws Exception { testTimestamp(TimestampingMode.AUTHENTICODE, DigestAlgorithm.SHA1); } public void testTimestampRFC3161() throws Exception { testTimestamp(TimestampingMode.RFC3161, DigestAlgorithm.SHA256); } public void testTimestamp(TimestampingMode mode, DigestAlgorithm alg) throws Exception { File sourceFile = new File("target/test-classes/wineyes.exe"); File targetFile = new File("target/test-classes/wineyes-timestamped-" + mode.name().toLowerCase() + ".exe"); FileUtils.copyFile(sourceFile, targetFile); PEFile peFile = new PEFile(targetFile); PESigner signer = new PESigner(getKeyStore(), ALIAS, PRIVATE_KEY_PASSWORD); signer.withDigestAlgorithm(alg); signer.withTimestamping(true); signer.withTimestampingMode(mode); signer.sign(peFile); peFile = new PEFile(targetFile); List<CMSSignedData> signatures = peFile.getSignatures(); assertNotNull("list of signatures null", signatures); assertEquals("number of signatures", 1, signatures.size()); assertNotNull("null signature", signatures.get(0)); SignatureAssert.assertTimestamped("Invalid timestamp", signatures.get(0)); peFile.printInfo(System.out); } /** * Tests that a custom Timestamper implementation can be provided. */ public void testWithTimestamper() throws Exception { File sourceFile = new File("target/test-classes/wineyes.exe"); File targetFile = new File("target/test-classes/wineyes-timestamped-custom.exe"); FileUtils.copyFile(sourceFile, targetFile); PEFile peFile = new PEFile(targetFile); final HashSet<Boolean> called = new HashSet<>(); PESigner signer = new PESigner(getKeyStore(), ALIAS, PRIVATE_KEY_PASSWORD); signer.withDigestAlgorithm(DigestAlgorithm.SHA1); signer.withTimestamping(true); signer.withTimestamper(new AuthenticodeTimestamper() { @Override protected CMSSignedData timestamp(DigestAlgorithm algo, byte[] encryptedDigest) throws IOException, TimestampingException { called.add(true); return super.timestamp(algo, encryptedDigest); } }); signer.sign(peFile); peFile = new PEFile(targetFile); List<CMSSignedData> signatures = peFile.getSignatures(); assertNotNull(signatures); assertEquals(1, signatures.size()); CMSSignedData signature = signatures.get(0); assertNotNull(signature); assertTrue("expecting our Timestamper to be used", called.contains(true)); SignatureAssert.assertTimestamped("Invalid timestamp", signature); } public void testSignTwice() throws Exception { File sourceFile = new File("target/test-classes/wineyes.exe"); File targetFile = new File("target/test-classes/wineyes-signed-twice.exe"); FileUtils.copyFile(sourceFile, targetFile); PEFile peFile = new PEFile(targetFile); PESigner signer = new PESigner(getKeyStore(), ALIAS, PRIVATE_KEY_PASSWORD) .withDigestAlgorithm(DigestAlgorithm.SHA1) .withTimestamping(true) .withProgramName("WinEyes") .withProgramURL("http://www.steelblue.com/WinEyes"); signer.sign(peFile); peFile = new PEFile(targetFile); List<CMSSignedData> signatures = peFile.getSignatures(); assertNotNull(signatures); assertEquals("number of signatures", 1, signatures.size()); assertNotNull(signatures.get(0)); SignatureAssert.assertTimestamped("Invalid timestamp", signatures.get(0)); // second signature signer.withDigestAlgorithm(DigestAlgorithm.SHA256); signer.withTimestamping(false); signer.sign(peFile); peFile = new PEFile(targetFile); signatures = peFile.getSignatures(); assertNotNull(signatures); assertEquals("number of signatures", 2, signatures.size()); assertNotNull(signatures.get(0)); SignatureAssert.assertTimestamped("Timestamp corrupted after adding the second signature", signatures.get(0)); } public void testSignThreeTimes() throws Exception { File sourceFile = new File("target/test-classes/wineyes.exe"); File targetFile = new File("target/test-classes/wineyes-signed-three-times.exe"); FileUtils.copyFile(sourceFile, targetFile); PEFile peFile = new PEFile(targetFile); PESigner signer = new PESigner(getKeyStore(), ALIAS, PRIVATE_KEY_PASSWORD) .withDigestAlgorithm(DigestAlgorithm.SHA1) .withTimestamping(true) .withProgramName("WinEyes") .withProgramURL("http://www.steelblue.com/WinEyes"); signer.sign(peFile); peFile = new PEFile(targetFile); List<CMSSignedData> signatures = peFile.getSignatures(); assertNotNull(signatures); assertEquals("number of signatures", 1, signatures.size()); assertNotNull(signatures.get(0)); SignatureAssert.assertTimestamped("Invalid timestamp", signatures.get(0)); // second signature signer.withDigestAlgorithm(DigestAlgorithm.SHA256); signer.withTimestamping(false); signer.sign(peFile); peFile = new PEFile(targetFile); signatures = peFile.getSignatures(); assertNotNull(signatures); assertEquals("number of signatures", 2, signatures.size()); assertNotNull(signatures.get(0)); SignatureAssert.assertTimestamped("Timestamp corrupted after adding the second signature", signatures.get(0)); // third signature signer.withDigestAlgorithm(DigestAlgorithm.SHA512); signer.withTimestamping(false); signer.sign(peFile); peFile = new PEFile(targetFile); signatures = peFile.getSignatures(); assertNotNull(signatures); assertEquals("number of signatures", 3, signatures.size()); assertNotNull(signatures.get(0)); SignatureAssert.assertTimestamped("Timestamp corrupted after adding the third signature", signatures.get(0)); } public void testReplaceSignature() throws Exception { File sourceFile = new File("target/test-classes/wineyes.exe"); File targetFile = new File("target/test-classes/wineyes-re-signed.exe"); FileUtils.copyFile(sourceFile, targetFile); PEFile peFile = new PEFile(targetFile); PESigner signer = new PESigner(getKeyStore(), ALIAS, PRIVATE_KEY_PASSWORD) .withDigestAlgorithm(DigestAlgorithm.SHA1) .withProgramName("WinEyes") .withProgramURL("http://www.steelblue.com/WinEyes"); signer.sign(peFile); peFile = new PEFile(targetFile); List<CMSSignedData> signatures = peFile.getSignatures(); assertNotNull(signatures); assertEquals("number of signatures", 1, signatures.size()); assertNotNull(signatures.get(0)); // second signature signer.withDigestAlgorithm(DigestAlgorithm.SHA256); signer.withTimestamping(false); signer.withSignaturesReplaced(true); signer.sign(peFile); peFile = new PEFile(targetFile); signatures = peFile.getSignatures(); assertNotNull(signatures); assertEquals("number of signatures", 1, signatures.size()); assertNotNull(signatures.get(0)); assertEquals("Digest algorithm", DigestAlgorithm.SHA256.oid, signatures.get(0).getDigestAlgorithmIDs().iterator().next().getAlgorithm()); } public void testInvalidAuthenticodeTimestampingAutority() throws Exception { testInvalidTimestampingAutority(TimestampingMode.AUTHENTICODE); } public void testInvalidRFC3161TimestampingAutority() throws Exception { testInvalidTimestampingAutority(TimestampingMode.RFC3161); } public void testInvalidTimestampingAutority(TimestampingMode mode) throws Exception { File sourceFile = new File("target/test-classes/wineyes.exe"); File targetFile = new File("target/test-classes/wineyes-timestamped-unavailable-" + mode.name().toLowerCase() + ".exe"); FileUtils.copyFile(sourceFile, targetFile); PEFile peFile = new PEFile(targetFile); PESigner signer = new PESigner(getKeyStore(), ALIAS, PRIVATE_KEY_PASSWORD); signer.withDigestAlgorithm(DigestAlgorithm.SHA1); signer.withTimestamping(true); signer.withTimestampingMode(mode); signer.withTimestampingAutority("http://www.google.com/" + mode.name().toLowerCase()); signer.withTimestampingRetries(1); try { signer.sign(peFile); fail("IOException not thrown"); } catch (TimestampingException e) { assertTrue("Missing suppressed IOException", e.getSuppressed() != null && e.getSuppressed().length > 0 && e.getSuppressed()[0].getClass().equals(IOException.class)); // expected } peFile = new PEFile(targetFile); List<CMSSignedData> signatures = peFile.getSignatures(); assertNotNull(signatures); assertTrue(signatures.isEmpty()); } public void testBrokenAuthenticodeTimestampingAutority() throws Exception { testBrokenTimestampingAutority(TimestampingMode.AUTHENTICODE); } public void testBrokenRFC3161TimestampingAutority() throws Exception { testBrokenTimestampingAutority(TimestampingMode.RFC3161); } public void testBrokenTimestampingAutority(TimestampingMode mode) throws Exception { File sourceFile = new File("target/test-classes/wineyes.exe"); File targetFile = new File("target/test-classes/wineyes-timestamped-broken-" + mode.name().toLowerCase() + ".exe"); FileUtils.copyFile(sourceFile, targetFile); PEFile peFile = new PEFile(targetFile); PESigner signer = new PESigner(getKeyStore(), ALIAS, PRIVATE_KEY_PASSWORD); signer.withDigestAlgorithm(DigestAlgorithm.SHA1); signer.withTimestamping(true); signer.withTimestampingMode(mode); signer.withTimestampingAutority("http://github.com"); signer.withTimestampingRetries(1); try { signer.sign(peFile); fail("TimestampingException not thrown"); } catch (TimestampingException e) { // expected } peFile = new PEFile(targetFile); List<CMSSignedData> signatures = peFile.getSignatures(); assertNotNull(signatures); assertTrue(signatures.isEmpty()); } public void testInvalidTimestampingURL() throws Exception { PEFile peFile = new PEFile(new File("target/test-classes/wineyes.exe")); PESigner signer = new PESigner(getKeyStore(), ALIAS, PRIVATE_KEY_PASSWORD); signer.withDigestAlgorithm(DigestAlgorithm.SHA1); signer.withTimestamping(true); signer.withTimestampingMode(TimestampingMode.RFC3161); signer.withTimestampingAutority("example://example.com"); signer.withTimestampingRetries(1); try { signer.sign(peFile); fail("IllegalArgumentException not thrown"); } catch (IllegalArgumentException e) { // expected } } public void testAuthenticodeTimestampingFailover() throws Exception { testTimestampingFailover(TimestampingMode.AUTHENTICODE, "http://timestamp.comodoca.com/authenticode"); } public void testRFS3161TimestampingFailover() throws Exception { testTimestampingFailover(TimestampingMode.RFC3161, "http://timestamp.comodoca.com/rfc3161"); } public void testTimestampingFailover(TimestampingMode mode, String validURL) throws Exception { File sourceFile = new File("target/test-classes/wineyes.exe"); File targetFile = new File("target/test-classes/wineyes-timestamped-failover-" + mode.name().toLowerCase() + ".exe"); FileUtils.copyFile(sourceFile, targetFile); PEFile peFile = new PEFile(targetFile); PESigner signer = new PESigner(getKeyStore(), ALIAS, PRIVATE_KEY_PASSWORD); signer.withDigestAlgorithm(DigestAlgorithm.SHA1); signer.withTimestamping(true); signer.withTimestampingMode(mode); signer.withTimestampingRetryWait(1); signer.withTimestampingAutority("http://www.google.com/" + mode.name().toLowerCase(), "http://github.com", validURL); signer.sign(peFile); peFile = new PEFile(targetFile); List<CMSSignedData> signatures = peFile.getSignatures(); assertNotNull(signatures); assertEquals("number of signatures", 1, signatures.size()); assertNotNull(signatures.get(0)); SignatureAssert.assertTimestamped("Invalid timestamp", signatures.get(0)); } /** * Tests that it is possible to specify a signature algorithm. * * @throws Exception */ public void testWithSignatureAlgorithmSHA1withRSA() throws Exception { File sourceFile = new File("target/test-classes/wineyes.exe"); File targetFile = new File("target/test-classes/wineyes-signed.exe"); FileUtils.copyFile(sourceFile, targetFile); PEFile peFile = null; try { peFile = new PEFile(targetFile); PESigner signer = new PESigner(getKeyStore(), ALIAS, PRIVATE_KEY_PASSWORD) .withTimestamping(false) .withDigestAlgorithm(DigestAlgorithm.SHA256) .withSignatureAlgorithm("SHA1withRSA"); signer.sign(peFile); peFile = new PEFile(targetFile); List<CMSSignedData> signatures = peFile.getSignatures(); assertNotNull(signatures); assertEquals(1, signatures.size()); CMSSignedData signedData = signatures.get(0); assertNotNull(signedData); // Check the signature algorithm SignerInformation si = signedData.getSignerInfos().getSigners().iterator().next(); assertEquals("Digest algorithm", TSPAlgorithms.SHA1, si.getDigestAlgorithmID().getAlgorithm()); assertEquals("Encryption algorithm", PKCSObjectIdentifiers.rsaEncryption.getId(), si.getEncryptionAlgOID()); } finally { if (peFile != null) { peFile.close(); } } } /** * Tests that it is possible to specify a signature algorithm who's name is * not simply a concatenation of a digest algorithm and the key algorithm. * * This test also sets the signature provider as a provider supporting * the RSASSA-PSS algorithms might not be installed. * * @throws Exception */ public void testWithSignatureAlgorithmSHA256withRSAandMGF1() throws Exception { Security.addProvider(new BouncyCastleProvider()); File sourceFile = new File("target/test-classes/wineyes.exe"); File targetFile = new File("target/test-classes/wineyes-signed.exe"); FileUtils.copyFile(sourceFile, targetFile); PEFile peFile = null; try { peFile = new PEFile(targetFile); PESigner signer = new PESigner(getKeyStore(), ALIAS, PRIVATE_KEY_PASSWORD) .withTimestamping(false) .withDigestAlgorithm(DigestAlgorithm.SHA1) .withSignatureAlgorithm("SHA256withRSAandMGF1", "BC"); signer.sign(peFile); peFile = new PEFile(targetFile); List<CMSSignedData> signatures = peFile.getSignatures(); assertNotNull(signatures); assertEquals(1, signatures.size()); CMSSignedData signedData = signatures.get(0); assertNotNull(signedData); // Check the signature algorithm SignerInformation si = signedData.getSignerInfos().getSigners().iterator().next(); assertEquals("Digest algorithm", NISTObjectIdentifiers.id_sha256, si.getDigestAlgorithmID().getAlgorithm()); assertEquals("Encryption algorithm", PKCSObjectIdentifiers.id_RSASSA_PSS.getId(), si.getEncryptionAlgOID()); } finally { if (peFile != null) { peFile.close(); } } } }