JsignTaskTest.java

/*
 * 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.OutputStream;
import java.io.PrintStream;

import org.apache.commons.io.FileUtils;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.DefaultLogger;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.ProjectHelper;
import org.junit.Before;
import org.junit.Test;

import net.jsign.msi.MSIFile;
import net.jsign.pe.PEFile;
import net.jsign.script.PowerShellScript;

import static net.jsign.DigestAlgorithm.*;
import static org.junit.Assert.*;

public class JsignTaskTest {

    private Project project;
    
    private final File sourceFile = new File("target/test-classes/wineyes.exe");
    private final File targetFile = new File("target/test-classes/wineyes-signed-with-ant.exe");
    
    private static final long SOURCE_FILE_CRC32 = 0xA6A363D8L;

    @Before
    public void setUp() throws Exception {
        project = new Project();
        project.setCoreLoader(getClass().getClassLoader());
        project.init();

        File buildFile = new File("target/test-classes/testbuild.xml");
        project.setBaseDir(buildFile.getParentFile());

        final ProjectHelper helper = ProjectHelper.getProjectHelper();
        helper.parse(project, buildFile);

        // 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);

        redirectOutput(System.out);
    }

    /**
     * Redirects the Ant output to the specified stream.
     */
    private void redirectOutput(OutputStream out) {
        DefaultLogger logger = new DefaultLogger();
        logger.setOutputPrintStream(new PrintStream(out));
        logger.setMessageOutputLevel(Project.MSG_INFO);
        project.addBuildListener(logger);
    }

    @Test
    public void testMissingKeyStore() {
        assertThrows(BuildException.class, () -> project.executeTarget("missing-keystore"));
    }

    @Test
    public void testUnsupportedKeyStoreType() {
        assertThrows(BuildException.class, () -> project.executeTarget("unsupported-keystore"));
    }

    @Test
    public void testKeyStoreNotFound() {
        assertThrows(BuildException.class, () -> project.executeTarget("keystore-not-found"));
    }

    @Test
    public void testCorruptedKeyStore() {
        assertThrows(BuildException.class, () -> project.executeTarget("corrupted-keystore"));
    }

    @Test
    public void testMissingAlias() {
        assertThrows(BuildException.class, () -> project.executeTarget("missing-alias"));
    }

    @Test
    public void testAliasNotFound() {
        assertThrows(BuildException.class, () -> project.executeTarget("alias-not-found"));
    }

    @Test
    public void testCertificateNotFound() {
        assertThrows(BuildException.class, () -> project.executeTarget("certificate-not-found"));
    }

    @Test
    public void testMissingFile() {
        assertThrows(BuildException.class, () -> project.executeTarget("missing-file"));
    }

    @Test
    public void testFileNotFound() {
        assertThrows(BuildException.class, () -> project.executeTarget("file-not-found"));
    }

    @Test
    public void testCorruptedFile() {
        assertThrows(BuildException.class, () -> project.executeTarget("corrupted-file"));
    }

    @Test
    public void testConflictingAttributes() {
        assertThrows(BuildException.class, () -> project.executeTarget("conflicting-attributes"));
    }

    @Test
    public void testMissingCertFile() {
        assertThrows(BuildException.class, () -> project.executeTarget("missing-certfile"));
    }

    @Test
    public void testMissingKeyFile() {
        assertThrows(BuildException.class, () -> project.executeTarget("missing-keyfile"));
    }

    @Test
    public void testCertFileNotFound() {
        assertThrows(BuildException.class, () -> project.executeTarget("certfile-not-found"));
    }

    @Test
    public void testKeyFileNotFound() {
        assertThrows(BuildException.class, () -> project.executeTarget("keyfile-not-found"));
    }

    @Test
    public void testCorruptedCertFile() {
        assertThrows(BuildException.class, () -> project.executeTarget("corrupted-certfile"));
    }

    @Test
    public void testCorruptedKeyFile() {
        assertThrows(BuildException.class, () -> project.executeTarget("corrupted-keyfile"));
    }

    @Test
    public void testUnsupportedDigestAlgorithm() {
        assertThrows(BuildException.class, () -> project.executeTarget("unsupported-digest-algorithm"));
    }

    @Test
    public void testSigning() throws Exception {
        project.executeTarget("signing");
        
        assertTrue("The file " + targetFile + " wasn't changed", SOURCE_FILE_CRC32 != FileUtils.checksumCRC32(targetFile));

        try (PEFile peFile = new PEFile(targetFile)) {
            SignatureAssert.assertSigned(peFile, SHA1);
        }
    }

    @Test
    public void testSigningMultipleFiles() throws Exception {
        FileUtils.copyFile(sourceFile, targetFile);

        project.executeTarget("signing-multiple-files");

        File targetFile2 = new File("target/test-classes/wineyes-multiple-files-test.exe");

        assertTrue("The file " + targetFile + " wasn't changed", SOURCE_FILE_CRC32 != FileUtils.checksumCRC32(targetFile));
        assertTrue("The file " + targetFile2 + " wasn't changed", SOURCE_FILE_CRC32 != FileUtils.checksumCRC32(targetFile2));

        try (PEFile peFile = new PEFile(targetFile)) {
            SignatureAssert.assertSigned(peFile, SHA1);
        }
        try (PEFile peFile = new PEFile(targetFile2)) {
            SignatureAssert.assertSigned(peFile, SHA1);
        }
    }

    @Test
    public void testSigningPowerShell() throws Exception {
        File sourceFile = new File("target/test-classes/hello-world.ps1");
        File targetFile = new File("target/test-classes/hello-world-signed-with-ant.ps1");
        
        FileUtils.copyFile(sourceFile, targetFile);
        
        project.executeTarget("signing-powershell");
        
        PowerShellScript script = new PowerShellScript(targetFile);

        SignatureAssert.assertSigned(script, SHA1);
    }

    @Test
    public void testSigningMSI() throws Exception {
        File sourceFile = new File("target/test-classes/minimal.msi");
        File targetFile = new File("target/test-classes/minimal-signed-with-ant.msi");
        
        FileUtils.copyFile(sourceFile, targetFile);
        
        project.executeTarget("signing-msi");
        
        try (MSIFile file = new MSIFile(targetFile)) {
            SignatureAssert.assertSigned(file, SHA1);
        }
    }

    @Test
    public void testSigningPKCS12() throws Exception {
        project.executeTarget("signing-pkcs12");
        
        assertTrue("The file " + targetFile + " wasn't changed", SOURCE_FILE_CRC32 != FileUtils.checksumCRC32(targetFile));

        try (PEFile peFile = new PEFile(targetFile)) {
            SignatureAssert.assertSigned(peFile, SHA256);
        }
    }

    @Test
    public void testSigningPVKSPC() throws Exception {
        project.executeTarget("signing-pvk-spc");
        
        assertTrue("The file " + targetFile + " wasn't changed", SOURCE_FILE_CRC32 != FileUtils.checksumCRC32(targetFile));

        try (PEFile peFile = new PEFile(targetFile)) {
            SignatureAssert.assertSigned(peFile, SHA256);
        }
    }

    @Test
    public void testTimestampingAuthenticode() throws Exception {
        project.executeTarget("timestamping-authenticode");
        
        File targetFile2 = new File("target/test-classes/wineyes-timestamped-with-ant-authenticode.exe");
        
        assertTrue("The file " + targetFile2 + " wasn't changed", SOURCE_FILE_CRC32 != FileUtils.checksumCRC32(targetFile2));

        try (PEFile peFile = new PEFile(targetFile2)) {
            SignatureAssert.assertSigned(peFile, SHA256);
        }
    }

    @Test
    public void testTimestampingRFC3161() throws Exception {
        project.executeTarget("timestamping-rfc3161");
        
        File targetFile2 = new File("target/test-classes/wineyes-timestamped-with-ant-rfc3161.exe");
        
        assertTrue("The file " + targetFile2 + " wasn't changed", SOURCE_FILE_CRC32 != FileUtils.checksumCRC32(targetFile2));

        try (PEFile peFile = new PEFile(targetFile2)) {
            SignatureAssert.assertSigned(peFile, SHA256);
        }
    }

    @Test
    public void testReplaceSignature() throws Exception {
        project.executeTarget("replace-signature");
        
        File targetFile2 = new File("target/test-classes/wineyes-re-signed.exe");
        
        assertTrue("The file " + targetFile2 + " wasn't changed", SOURCE_FILE_CRC32 != FileUtils.checksumCRC32(targetFile2));
        
        try (PEFile peFile = new PEFile(targetFile2)) {
            SignatureAssert.assertSigned(peFile, SHA512);
        }
    }

    @Test
    public void testDetachedSignature() {
        project.executeTarget("detach-signature");

        assertTrue("Signature wasn't detached", new File("target/test-classes/wineyes-signed-detached.exe.sig").exists());
    }

    @Test
    public void testTag() {
        BuildException e = assertThrows( BuildException.class, () -> project.executeTarget( "tag-unsigned-file" ) );
        assertTrue("message", e.getMessage().startsWith("No signature found in"));
    }
}