JsignTask.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.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Stream;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.BuildLogger;
import org.apache.tools.ant.DefaultLogger;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.types.FileSet;

import static org.apache.tools.ant.Project.*;

/**
 * Ant task for signing files with Authenticode.
 *
 * @author Emmanuel Bourg
 * @since 1.0
 */
public class JsignTask extends Task {

    /** The file to be signed. */
    private File file;

    /** The set of files to be signed. */
    private FileSet fileset;

    /** The operation to execute */
    private String command = "sign";

    /** The program name embedded in the signature. */
    private String name;

    /** The program URL embedded in the signature. */
    private String url;

    /** The digest algorithm to use for the signature. */
    private String algorithm;

    /** The keystore file, the SunPKCS11 configuration file or the cloud keystore name. */
    private String keystore;

    /** The password for the keystore. */
    private String storepass;

    /** The type of the keystore. */
    private String storetype;

    /** The alias of the certificate in the keystore. */
    private String alias;

    /** The file containing the certificate chain (PKCS#7 format). */
    private File certfile;

    /** The file containing the private key (PEM or PVK format) */
    private File keyfile;

    /** The password for the key in the store (if different from the keystore password) or in the keyfile. */
    private String keypass;

    /** The URL of the timestamping authority. */
    private String tsaurl;

    /** The protocol used for  the timestamping */
    private String tsmode;

    /** The number of retries for timestamping */
    private int tsretries = -1;

    /** The number of seconds to wait between timestamping retries */
    private int tsretrywait = -1;

    /** Tells if previous signatures should be replaced */
    private boolean replace;

    /** The encoding of the script to be signed (UTF-8 by default). */
    private String encoding = "UTF-8";

    /** Tells if a detached signature should be generated or reused. */
    private boolean detached;

    /** The value of the unsigned attribute */
    private String value;

    public void setCommand(String command) {
        this.command = command;
    }

    public void setFile(File file) {
        this.file = file;
    }

    public void addFileset(FileSet fileset) {
        this.fileset = fileset;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public void setAlg(String alg) {
        this.algorithm = alg;
    }

    public void setTsmode(String tsmode) {
        this.tsmode = tsmode;
    }

    public void setKeystore(String keystore) {
        this.keystore = keystore;
    }

    public void setStorepass(String storepass) {
        this.storepass = storepass;
    }

    public void setStoretype(String storetype) {
        this.storetype = storetype;
    }

    public void setAlias(String alias) {
        this.alias = alias;
    }

    public void setCertfile(File certfile) {
        this.certfile = certfile;
    }

    public void setKeyfile(File keyfile) {
        this.keyfile = keyfile;
    }

    public void setKeypass(String keypass) {
        this.keypass = keypass;
    }

    public void setTsaurl(String tsaurl) {
        this.tsaurl = tsaurl;
    }

    public void setTsretries(int tsretries) {
        this.tsretries = tsretries;
    }

    public void setTsretrywait(int tsretrywait) {
        this.tsretrywait = tsretrywait;
    }

    public void setReplace(boolean replace) {
        this.replace = replace;
    }

    public void setEncoding(String encoding) {
        this.encoding = encoding;
    }

    public void setDetached(boolean detached) {
        this.detached = detached;
    }

    public void setValue(String value) {
        this.value = value;
    }

    @Override
    public void execute() throws BuildException {
        try {
            // configure the logging
            Logger log = Logger.getLogger("net.jsign");
            log.setLevel(getLevel());
            log.setUseParentHandlers(false);
            Stream.of(log.getHandlers()).forEach(log::removeHandler);
            log.addHandler(new AntLogHandler(this));

            SignerHelper helper = new SignerHelper("attribute");
            helper.setBaseDir(getProject().getBaseDir());
            
            helper.command(command);
            helper.name(name);
            helper.url(url);
            helper.alg(algorithm);
            helper.keystore(keystore);
            helper.storepass(storepass);
            helper.storetype(storetype);
            helper.alias(alias);
            helper.certfile(certfile);
            helper.keyfile(keyfile);
            helper.keypass(keypass);
            helper.tsaurl(tsaurl);
            helper.tsmode(tsmode);
            helper.tsretries(tsretries);
            helper.tsretrywait(tsretrywait);
            helper.replace(replace);
            helper.encoding(encoding);
            helper.detached(detached);
            helper.value(value);

            if (fileset != null) {
                for(String fileElement : fileset.getDirectoryScanner().getIncludedFiles()) {
                    helper.execute(new File(fileset.getDir(), fileElement));
                }
            } else {
                helper.execute(file);
            }
        } catch (Exception e) {
            throw new BuildException(e.getMessage(), e, getLocation());
        }
    }

    /**
     * Returns the logging level equivalent to the Ant message output level.
     */
    private Level getLevel() {
        int messageOutputLevel = getMessageOutputLevel();
        switch (messageOutputLevel) {
            case MSG_ERR:
                return Level.SEVERE;
            case MSG_WARN:
                return Level.WARNING;
            case MSG_INFO:
                return Level.INFO;
            case MSG_VERBOSE:
                return Level.FINE;
            case MSG_DEBUG:
                return Level.FINEST;
            default:
                return Level.INFO;
        }
    }

    /**
     * Returns the Ant message output level.
     */
    private int getMessageOutputLevel() {
        for (Object listener : getProject().getBuildListeners()) {
            if (listener instanceof BuildLogger) {
                try {
                    Method method = BuildLogger.class.getMethod("getMessageOutputLevel"); // requires Ant 1.10.13
                    return (Integer) method.invoke(listener);
                } catch (Exception e) {
                }

                try {
                    Field field = DefaultLogger.class.getDeclaredField("msgOutputLevel");
                    field.setAccessible(true);
                    return (Integer) field.get(listener);
                } catch (Exception e) {
                }
            }
        }

        return MSG_INFO;
    }
}