/**
* 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.security.Permission;
import java.security.ProviderException;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import io.netty.handler.codec.http.HttpRequest;
import junit.framework.TestCase;
import org.apache.commons.cli.ParseException;
import org.apache.commons.io.FileUtils;
import org.bouncycastle.cms.CMSSignedData;
import org.littleshoot.proxy.HttpFilters;
import org.littleshoot.proxy.HttpFiltersSourceAdapter;
import org.littleshoot.proxy.HttpProxyServer;
import org.littleshoot.proxy.ProxyAuthenticator;
import org.littleshoot.proxy.impl.DefaultHttpProxyServer;
import net.jsign.pe.PEFile;
public class PESignerCLITest extends TestCase {
private PESignerCLI cli;
private File sourceFile = new File("target/test-classes/wineyes.exe");
private File targetFile = new File("target/test-classes/wineyes-signed-with-cli.exe");
private String keystore = "keystore.jks";
private String alias = "test";
private String keypass = "password";
private static final long SOURCE_FILE_CRC32 = 0xA6A363D8L;
protected void setUp() throws Exception {
cli = new PESignerCLI();
// remove the files signed previously
if (targetFile.exists()) {
assertTrue("Unable to remove the previously signed file", targetFile.delete());
}
assertEquals("Source file CRC32", SOURCE_FILE_CRC32, FileUtils.checksumCRC32(sourceFile));
Thread.sleep(100);
FileUtils.copyFile(sourceFile, targetFile);
}
public void testPrintHelp() throws Exception {
PESignerCLI.main("--help");
}
public void testMissingKeyStore() throws Exception {
try {
cli.execute("" + targetFile);
fail("No exception thrown");
} catch (SignerException e) {
// expected
}
}
public void testUnsupportedKeyStoreType() throws Exception {
try {
cli.execute("--keystore=keystore.jks", "--storetype=ABC", "" + targetFile);
fail("No exception thrown");
} catch (SignerException e) {
// expected
}
}
public void testKeyStoreNotFound() throws Exception {
try {
cli.execute("--keystore=keystore2.jks", "" + targetFile);
fail("No exception thrown");
} catch (SignerException e) {
// expected
}
}
public void testCorruptedKeyStore() throws Exception {
try {
cli.execute("--keystore=" + targetFile, "" + targetFile);
fail("No exception thrown");
} catch (SignerException e) {
// expected
}
}
public void testMissingAlias() throws Exception {
try {
cli.execute("--keystore=target/test-classes/keystore.jks", "" + targetFile);
fail("No exception thrown");
} catch (SignerException e) {
// expected
}
}
public void testAliasNotFound() throws Exception {
try {
cli.execute("--keystore=target/test-classes/keystore.jks", "--alias=unknown", "" + targetFile);
fail("No exception thrown");
} catch (SignerException e) {
// expected
}
}
public void testCertificateNotFound() throws Exception {
try {
cli.execute("--keystore=target/test-classes/keystore.jks", "--alias=foo", "" + targetFile);
fail("No exception thrown");
} catch (SignerException e) {
// expected
}
}
public void testMissingFile() throws Exception {
try {
cli.execute("--keystore=target/test-classes/keystore.jks", "--alias=test", "--keypass=password");
fail("No exception thrown");
} catch (SignerException e) {
// expected
}
}
public void testFileNotFound() throws Exception {
try {
cli.execute("--keystore=target/test-classes/keystore.jks", "--alias=test", "--keypass=password", "wineyes-foo.exe");
fail("No exception thrown");
} catch (SignerException e) {
// expected
}
}
public void testCorruptedFile() throws Exception {
try {
cli.execute("--keystore=target/test-classes/keystore.jks", "--alias=test", "--keypass=password", "target/test-classes/keystore.jks");
fail("No exception thrown");
} catch (SignerException e) {
// expected
}
}
public void testConflictingAttributes() throws Exception {
try {
cli.execute("--keystore=target/test-classes/keystore.jks", "--alias=test", "--keypass=password", "--keyfile=privatekey.pvk", "--certfile=certificate.spc", "" + targetFile);
fail("No exception thrown");
} catch (SignerException e) {
// expected
}
}
public void testMissingCertFile() throws Exception {
try {
cli.execute("--keyfile=target/test-classes/privatekey.pvk", "" + targetFile);
fail("No exception thrown");
} catch (SignerException e) {
// expected
}
}
public void testMissingKeyFile() throws Exception {
try {
cli.execute("--certfile=target/test-classes/certificate.spc", "" + targetFile);
fail("No exception thrown");
} catch (SignerException e) {
// expected
}
}
public void testCertFileNotFound() throws Exception {
try {
cli.execute("--certfile=target/test-classes/certificate2.spc", "--keyfile=target/test-classes/privatekey.pvk", "" + targetFile);
fail("No exception thrown");
} catch (SignerException e) {
// expected
}
}
public void testKeyFileNotFound() throws Exception {
try {
cli.execute("--certfile=target/test-classes/certificate.spc", "--keyfile=target/test-classes/privatekey2.pvk", "" + targetFile);
fail("No exception thrown");
} catch (SignerException e) {
// expected
}
}
public void testCorruptedCertFile() throws Exception {
try {
cli.execute("--certfile=target/test-classes/privatekey.pvk", "--keyfile=target/test-classes/privatekey.pvk", "" + targetFile);
fail("No exception thrown");
} catch (SignerException e) {
// expected
}
}
public void testCorruptedKeyFile() throws Exception {
try {
cli.execute("--certfile=target/test-classes/certificate.spc", "--keyfile=target/test-classes/certificate.spc", "" + targetFile);
fail("No exception thrown");
} catch (SignerException e) {
// expected
}
}
public void testUnsupportedDigestAlgorithm() throws Exception {
try {
cli.execute("--alg=SHA-123", "--keystore=target/test-classes/keystore.jks", "--alias=test", "--keypass=password", "" + targetFile);
fail("No exception thrown");
} catch (SignerException e) {
// expected
}
}
public void testSigning() throws Exception {
cli.execute("--name=WinEyes", "--url=http://www.steelblue.com/WinEyes", "--alg=SHA-1", "--keystore=target/test-classes/" + keystore, "--alias=" + alias, "--keypass=" + keypass, "" + targetFile);
assertTrue("The file " + targetFile + " wasn't changed", SOURCE_FILE_CRC32 != FileUtils.checksumCRC32(targetFile));
try (PEFile peFile = new PEFile(targetFile)) {
List<CMSSignedData> signatures = peFile.getSignatures();
assertNotNull(signatures);
assertEquals(1, signatures.size());
CMSSignedData signature = signatures.get(0);
assertNotNull(signature);
}
}
public void testSigningPKCS12() throws Exception {
cli.execute("--name=WinEyes", "--url=http://www.steelblue.com/WinEyes", "--alg=SHA-256", "--keystore=target/test-classes/keystore.p12", "--alias=test", "--storepass=password", "" + targetFile);
assertTrue("The file " + targetFile + " wasn't changed", SOURCE_FILE_CRC32 != FileUtils.checksumCRC32(targetFile));
try (PEFile peFile = new PEFile(targetFile)) {
List<CMSSignedData> signatures = peFile.getSignatures();
assertNotNull(signatures);
assertEquals(1, signatures.size());
CMSSignedData signature = signatures.get(0);
assertNotNull(signature);
}
}
public void testSigningPVKSPC() throws Exception {
cli.execute("--url=http://www.steelblue.com/WinEyes", "--certfile=target/test-classes/certificate.spc", "--keyfile=target/test-classes/privatekey-encrypted.pvk", "--storepass=password", "" + targetFile);
assertTrue("The file " + targetFile + " wasn't changed", SOURCE_FILE_CRC32 != FileUtils.checksumCRC32(targetFile));
try (PEFile peFile = new PEFile(targetFile)) {
List<CMSSignedData> signatures = peFile.getSignatures();
assertNotNull(signatures);
assertEquals(1, signatures.size());
CMSSignedData signature = signatures.get(0);
assertNotNull(signature);
}
}
public void testSigningPEM() throws Exception {
cli.execute("--certfile=target/test-classes/certificate.pem", "--keyfile=target/test-classes/privatekey.pkcs8.pem", "--keypass=password", "" + targetFile);
assertTrue("The file " + targetFile + " wasn't changed", SOURCE_FILE_CRC32 != FileUtils.checksumCRC32(targetFile));
try (PEFile peFile = new PEFile(targetFile)) {
List<CMSSignedData> signatures = peFile.getSignatures();
assertNotNull(signatures);
assertEquals(1, signatures.size());
CMSSignedData signature = signatures.get(0);
assertNotNull(signature);
}
}
public void testSigningEncryptedPEM() throws Exception {
cli.execute("--certfile=target/test-classes/certificate.pem", "--keyfile=target/test-classes/privatekey-encrypted.pkcs1.pem", "--keypass=password", "" + targetFile);
assertTrue("The file " + targetFile + " wasn't changed", SOURCE_FILE_CRC32 != FileUtils.checksumCRC32(targetFile));
try (PEFile peFile = new PEFile(targetFile)) {
List<CMSSignedData> signatures = peFile.getSignatures();
assertNotNull(signatures);
assertEquals(1, signatures.size());
CMSSignedData signature = signatures.get(0);
assertNotNull(signature);
}
}
public void testTimestampingAuthenticode() throws Exception {
File targetFile2 = new File("target/test-classes/wineyes-timestamped-with-cli-authenticode.exe");
FileUtils.copyFile(sourceFile, targetFile2);
cli.execute("--keystore=target/test-classes/" + keystore, "--alias=" + alias, "--keypass=" + keypass, "--tsaurl=http://timestamp.comodoca.com/authenticode", "--tsmode=authenticode", "" + targetFile2);
assertTrue("The file " + targetFile2 + " wasn't changed", SOURCE_FILE_CRC32 != FileUtils.checksumCRC32(targetFile2));
try (PEFile peFile = new PEFile(targetFile2)) {
List<CMSSignedData> signatures = peFile.getSignatures();
assertNotNull(signatures);
assertEquals(1, signatures.size());
CMSSignedData signature = signatures.get(0);
assertNotNull(signature);
}
}
public void testTimestampingRFC3161() throws Exception {
File targetFile2 = new File("target/test-classes/wineyes-timestamped-with-cli-rfc3161.exe");
FileUtils.copyFile(sourceFile, targetFile2);
cli.execute("--keystore=target/test-classes/" + keystore, "--alias=" + alias, "--keypass=" + keypass, "--tsaurl=http://timestamp.comodoca.com/rfc3161", "--tsmode=rfc3161", "" + targetFile2);
assertTrue("The file " + targetFile2 + " wasn't changed", SOURCE_FILE_CRC32 != FileUtils.checksumCRC32(targetFile2));
try (PEFile peFile = new PEFile(targetFile2)) {
List<CMSSignedData> signatures = peFile.getSignatures();
assertNotNull(signatures);
assertEquals(1, signatures.size());
CMSSignedData signature = signatures.get(0);
assertNotNull(signature);
}
}
public void testTimestampingWithProxyUnauthenticated() throws Exception {
final AtomicBoolean proxyUsed = new AtomicBoolean(false);
HttpProxyServer proxy = DefaultHttpProxyServer.bootstrap().withPort(12543)
.withFiltersSource(new HttpFiltersSourceAdapter() {
@Override
public HttpFilters filterRequest(HttpRequest originalRequest) {
proxyUsed.set(true);
return super.filterRequest(originalRequest);
}
})
.start();
try {
File targetFile2 = new File("target/test-classes/wineyes-timestamped-with-cli-rfc3161-proxy-unauthenticated.exe");
FileUtils.copyFile(sourceFile, targetFile2);
cli.execute("--keystore=target/test-classes/" + keystore, "--alias=" + alias, "--keypass=" + keypass,
"--tsaurl=http://timestamp.comodoca.com/rfc3161", "--tsmode=rfc3161", "--tsretries=1", "--tsretrywait=1",
"--proxyUrl=localhost:" + proxy.getListenAddress().getPort(),
"" + targetFile2);
assertTrue("The file " + targetFile2 + " wasn't changed", SOURCE_FILE_CRC32 != FileUtils.checksumCRC32(targetFile2));
assertTrue("The proxy wasn't used", proxyUsed.get());
try (PEFile peFile = new PEFile(targetFile2)) {
List<CMSSignedData> signatures = peFile.getSignatures();
assertNotNull(signatures);
assertEquals(1, signatures.size());
CMSSignedData signature = signatures.get(0);
assertNotNull(signature);
}
} finally {
proxy.stop();
}
}
public void testTimestampingWithProxyAuthenticated() throws Exception {
final AtomicBoolean proxyUsed = new AtomicBoolean(false);
HttpProxyServer proxy = DefaultHttpProxyServer.bootstrap().withPort(12544)
.withFiltersSource(new HttpFiltersSourceAdapter() {
@Override
public HttpFilters filterRequest(HttpRequest originalRequest) {
proxyUsed.set(true);
return super.filterRequest(originalRequest);
}
})
.withProxyAuthenticator(new ProxyAuthenticator() {
@Override
public boolean authenticate(String username, String password) {
return "jsign".equals(username) && "jsign".equals(password);
}
@Override
public String getRealm() {
return "Jsign Tests";
}
})
.start();
try {
File targetFile2 = new File("target/test-classes/wineyes-timestamped-with-cli-rfc3161-proxy-authenticated.exe");
FileUtils.copyFile(sourceFile, targetFile2);
cli.execute("--keystore=target/test-classes/" + keystore, "--alias=" + alias, "--keypass=" + keypass,
"--tsaurl=http://timestamp.comodoca.com/rfc3161", "--tsmode=rfc3161", "--tsretries=1", "--tsretrywait=1",
"--proxyUrl=http://localhost:" + proxy.getListenAddress().getPort(),
"--proxyUser=jsign",
"--proxyPass=jsign",
"" + targetFile2);
assertTrue("The file " + targetFile2 + " wasn't changed", SOURCE_FILE_CRC32 != FileUtils.checksumCRC32(targetFile2));
assertTrue("The proxy wasn't used", proxyUsed.get());
try (PEFile peFile = new PEFile(targetFile2)) {
List<CMSSignedData> signatures = peFile.getSignatures();
assertNotNull(signatures);
assertEquals(1, signatures.size());
CMSSignedData signature = signatures.get(0);
assertNotNull(signature);
}
} finally {
proxy.stop();
}
}
public void testReplaceSignature() throws Exception {
File targetFile2 = new File("target/test-classes/wineyes-re-signed.exe");
FileUtils.copyFile(sourceFile, targetFile2);
cli.execute("--keystore=target/test-classes/" + keystore, "--alias=" + alias, "--keypass=" + keypass, "" + targetFile2);
assertTrue("The file " + targetFile2 + " wasn't changed", SOURCE_FILE_CRC32 != FileUtils.checksumCRC32(targetFile2));
cli.execute("--keystore=target/test-classes/" + keystore, "--alias=" + alias, "--keypass=" + keypass, "--alg=SHA-512", "--replace", "" + targetFile2);
try (PEFile peFile = new PEFile(targetFile2)) {
List<CMSSignedData> signatures = peFile.getSignatures();
assertNotNull(signatures);
assertEquals(1, signatures.size());
assertNotNull(signatures.get(0));
assertEquals("Digest algorithm", DigestAlgorithm.SHA512.oid, signatures.get(0).getDigestAlgorithmIDs().iterator().next().getAlgorithm());
}
}
public void testExitOnError() {
NoExitSecurityManager manager = new NoExitSecurityManager();
System.setSecurityManager(manager);
try {
PESignerCLI.main("foo.exe");
fail("VM not terminated");
} catch (SecurityException e) {
// expected
assertEquals("Exit code", Integer.valueOf(1), manager.getStatus());
} finally {
System.setSecurityManager(null);
}
}
private static class NoExitSecurityManager extends SecurityManager {
private Integer status;
public Integer getStatus() {
return status;
}
public void checkPermission(Permission perm) { }
public void checkPermission(Permission perm, Object context) { }
public void checkExit(int status) {
this.status = status;
throw new SecurityException("Exit disabled");
}
}
public void testUnknownOption() throws Exception {
try {
cli.execute("--jsign");
fail("No exception thrown");
} catch (ParseException e) {
// expected
}
}
public void testUnknownPKCS11Provider() throws Exception {
try {
cli.execute("--storetype=PKCS11", "--keystore=SunPKCS11-jsigntest", "--keypass=password", "" + targetFile);
fail("No exception thrown");
} catch (SignerException e) {
assertEquals("exception message", "Security provider SunPKCS11-jsigntest not found", e.getMessage());
}
}
public void testMissingPKCS11Configuration() throws Exception {
try {
cli.execute("--storetype=PKCS11", "--keystore=jsigntest.cfg", "--keypass=password", "" + targetFile);
fail("No exception thrown");
} catch (SignerException e) {
assertEquals("keystore option should either refer to the SunPKCS11 configuration file or to the name of the provider configured in jre/lib/security/java.security", e.getMessage());
}
}
public void testBrokenPKCS11Configuration() throws Exception {
try {
cli.execute("--storetype=PKCS11", "--keystore=pom.xml", "--keypass=password", "" + targetFile);
} catch (ProviderException e) {
// expected
}
}
}