All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.tools.ant.taskdefs.SignJar Maven / Gradle / Ivy

There is a newer version: 4.1
Show newest version
/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You 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 org.apache.tools.ant.taskdefs;

import java.io.File;
import java.io.IOException;
import java.util.Iterator;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.taskdefs.condition.IsSigned;
import org.apache.tools.ant.types.Path;
import org.apache.tools.ant.types.Resource;
import org.apache.tools.ant.types.resources.FileProvider;
import org.apache.tools.ant.types.resources.FileResource;
import org.apache.tools.ant.util.FileNameMapper;
import org.apache.tools.ant.util.FileUtils;
import org.apache.tools.ant.util.IdentityMapper;
import org.apache.tools.ant.util.ResourceUtils;

/**
 * Signs JAR or ZIP files with the javasign command line tool. The tool detailed
 * dependency checking: files are only signed if they are not signed. The
 * signjar attribute can point to the file to generate; if this file
 * exists then its modification date is used as a cue as to whether to resign
 * any JAR file.
 *
 * Timestamp driven signing is based on the unstable and inadequately documented
 * information in the Java1.5 docs
 * @see 
 * beta documentation
 * @ant.task category="java"
 * @since Ant 1.1
 */
public class SignJar extends AbstractJarSignerTask {
    // CheckStyle:VisibilityModifier OFF - bc

    private static final FileUtils FILE_UTILS = FileUtils.getFileUtils();

    /**
     * name to a signature file
     */
    protected String sigfile;

    /**
     * name of a single jar
     */
    protected File signedjar;

    /**
     * flag for internal sf signing
     */
    protected boolean internalsf;

    /**
     * sign sections only?
     */
    protected boolean sectionsonly;

    /**
     * flag to preserve timestamp on modified files
     */
    private boolean preserveLastModified;

    /**
     * Whether to assume a jar which has an appropriate .SF file in is already
     * signed.
     */
    protected boolean lazy;

    /**
     * the output directory when using paths.
     */
    protected File destDir;

    /**
     * mapper for todir work
     */
    private FileNameMapper mapper;

    /**
     * URL for a tsa; null implies no tsa support
     */
    protected String tsaurl;

    /**
     * alias for the TSA in the keystore
     */
    protected String tsacert;

    /**
     * force signing even if the jar is already signed.
     */
    private boolean force = false;

    /**
     * signature algorithm
     */
    private String sigAlg;

    /**
     * digest algorithm
     */
    private String digestAlg;

    private int retryCount = 1;

    /**
     * error string for unit test verification: {@value}
     */
    public static final String ERROR_TODIR_AND_SIGNEDJAR
            = "'destdir' and 'signedjar' cannot both be set";
    /**
     * error string for unit test verification: {@value}
     */
    public static final String ERROR_TOO_MANY_MAPPERS = "Too many mappers";
    /**
     * error string for unit test verification {@value}
     */
    public static final String ERROR_SIGNEDJAR_AND_PATHS
        = "You cannot specify the signed JAR when using paths or filesets";
    /**
     * error string for unit test verification: {@value}
     */
    public static final String ERROR_BAD_MAP = "Cannot map source file to anything sensible: ";
    /**
     * error string for unit test verification: {@value}
     */
    public static final String ERROR_MAPPER_WITHOUT_DEST
        = "The destDir attribute is required if a mapper is set";
    /**
     * error string for unit test verification: {@value}
     */
    public static final String ERROR_NO_ALIAS = "alias attribute must be set";
    /**
     * error string for unit test verification: {@value}
     */
    public static final String ERROR_NO_STOREPASS = "storepass attribute must be set";
    // CheckStyle:VisibilityModifier ON

    public void setRetryCount( int retryCount )
    {
        this.retryCount = retryCount;
    }

    /**
     * name of .SF/.DSA file; optional
     *
     * @param sigfile the name of the .SF/.DSA file
     */
    public void setSigfile(final String sigfile) {
        this.sigfile = sigfile;
    }

    /**
     * name of signed JAR file; optional
     *
     * @param signedjar the name of the signed jar file
     */
    public void setSignedjar(final File signedjar) {
        this.signedjar = signedjar;
    }

    /**
     * Flag to include the .SF file inside the signature; optional; default
     * false
     *
     * @param internalsf if true include the .SF file inside the signature
     */
    public void setInternalsf(final boolean internalsf) {
        this.internalsf = internalsf;
    }

    /**
     * flag to compute hash of entire manifest; optional, default false
     *
     * @param sectionsonly flag to compute hash of entire manifest
     */
    public void setSectionsonly(final boolean sectionsonly) {
        this.sectionsonly = sectionsonly;
    }

    /**
     * flag to control whether the presence of a signature file means a JAR is
     * signed; optional, default false
     *
     * @param lazy flag to control whether the presence of a signature
     */
    public void setLazy(final boolean lazy) {
        this.lazy = lazy;
    }

    /**
     * Optionally sets the output directory to be used.
     *
     * @param destDir the directory in which to place signed jars
     * @since Ant 1.7
     */
    public void setDestDir(File destDir) {
        this.destDir = destDir;
    }


    /**
     * add a mapper to determine file naming policy. Only used with toDir
     * processing.
     *
     * @param newMapper the mapper to add.
     * @since Ant 1.7
     */
    public void add(FileNameMapper newMapper) {
        if (mapper != null) {
            throw new BuildException(ERROR_TOO_MANY_MAPPERS);
        }
        mapper = newMapper;
    }

    /**
     * get the active mapper; may be null
     * @return mapper or null
     * @since Ant 1.7
     */
    public FileNameMapper getMapper() {
        return mapper;
    }

    /**
     * get the -tsaurl url
     * @return url or null
     * @since Ant 1.7
     */
    public String getTsaurl() {
        return tsaurl;
    }

    /**
     *
     * @param tsaurl the tsa url.
     * @since Ant 1.7
     */
    public void setTsaurl(String tsaurl) {
        this.tsaurl = tsaurl;
    }

    /**
     * get the -tsacert option
     * @since Ant 1.7
     * @return a certificate alias or null
     */
    public String getTsacert() {
        return tsacert;
    }

    /**
     * set the alias in the keystore of the TSA to use;
     * @param tsacert the cert alias.
     */
    public void setTsacert(String tsacert) {
        this.tsacert = tsacert;
    }

    /**
     * Whether to force signing of a jar even it is already signed.
     * @since Ant 1.8.0
     */
    public void setForce(boolean b) {
        force = b;
    }

    /**
     * Should the task force signing of a jar even it is already
     * signed?
     * @since Ant 1.8.0
     */
    public boolean isForce() {
        return force;
    }

    /**
     * Signature Algorithm; optional
     *
     * @param sigAlg the signature algorithm
     */
    public void setSigAlg(String sigAlg) {
        this.sigAlg = sigAlg;
    }

    /**
     * Signature Algorithm; optional
     */
    public String getSigAlg() {
        return sigAlg;
    }

    /**
     * Digest Algorithm; optional
     *
     * @param digestAlg the digest algorithm
     */
    public void setDigestAlg(String digestAlg) {
        this.digestAlg = digestAlg;
    }

    /**
     * Digest Algorithm; optional
     */
    public String getDigestAlg() {
        return digestAlg;
    }

    /**
     * sign the jar(s)
     *
     * @throws BuildException on errors
     */
    @Override
    public void execute() throws BuildException {
        //validation logic
        final boolean hasJar = jar != null;
        final boolean hasSignedJar = signedjar != null;
        final boolean hasDestDir = destDir != null;
        final boolean hasMapper = mapper != null;

        if (!hasJar && !hasResources()) {
            throw new BuildException(ERROR_NO_SOURCE);
        }
        if (null == alias) {
            throw new BuildException(ERROR_NO_ALIAS);
        }

        if (null == storepass) {
            throw new BuildException(ERROR_NO_STOREPASS);
        }

        if (hasDestDir && hasSignedJar) {
            throw new BuildException(ERROR_TODIR_AND_SIGNEDJAR);
        }


        if (hasResources() && hasSignedJar) {
            throw new BuildException(ERROR_SIGNEDJAR_AND_PATHS);
        }

        //this isnt strictly needed, but by being fussy now,
        //we can change implementation details later
        if (!hasDestDir && hasMapper) {
            throw new BuildException(ERROR_MAPPER_WITHOUT_DEST);
        }

        beginExecution();


        try {
            //special case single jar handling with signedjar attribute set
            if (hasJar && hasSignedJar) {
                // single jar processing
                signOneJar(jar, signedjar);
                //return here.
                return;
            }

            //the rest of the method treats single jar like
            //a nested path with one file

            Path sources = createUnifiedSourcePath();
            //set up our mapping policy
            FileNameMapper destMapper;
            if (hasMapper) {
                destMapper = mapper;
            } else {
                //no mapper? use the identity policy
                destMapper = new IdentityMapper();
            }


            //at this point the paths are set up with lists of files,
            //and the mapper is ready to map from source dirs to dest files
            //now we iterate through every JAR giving source and dest names
            // deal with the paths
            Iterator iter = sources.iterator();
            while (iter.hasNext()) {
                Resource r = (Resource) iter.next();
                FileResource fr = ResourceUtils
                    .asFileResource((FileProvider) r.as(FileProvider.class));

                //calculate our destination directory; it is either the destDir
                //attribute, or the base dir of the fileset (for in situ updates)
                File toDir = hasDestDir ? destDir : fr.getBaseDir();

                //determine the destination filename via the mapper
                String[] destFilenames = destMapper.mapFileName(fr.getName());
                if (destFilenames == null || destFilenames.length != 1) {
                    //we only like simple mappers.
                    throw new BuildException(ERROR_BAD_MAP + fr.getFile());
                }
                File destFile = new File(toDir, destFilenames[0]);
                signOneJar(fr.getFile(), destFile);
            }
        } finally {
            endExecution();
        }
    }

    /**
     * Sign one jar.
     * 

* The signing only takes place if {@link #isUpToDate(File, File)} indicates * that it is needed. * * @param jarSource source to sign * @param jarTarget target; may be null * @throws BuildException */ private void signOneJar(File jarSource, File jarTarget) throws BuildException { File targetFile = jarTarget; if (targetFile == null) { targetFile = jarSource; } if (isUpToDate(jarSource, targetFile)) { return; } long lastModified = jarSource.lastModified(); for (int tries = 0; tries < retryCount;) { tries++; final ExecTask cmd = createJarSigner(); setCommonOptions(cmd); bindToKeystore(cmd); if (null != sigfile) { addValue(cmd, "-sigfile"); String value = this.sigfile; addValue(cmd, value); } try { //DO NOT SET THE -signedjar OPTION if source==dest //unless you like fielding hotspot crash reports if (!FILE_UTILS.areSame(jarSource, targetFile)) { addValue(cmd, "-signedjar"); addValue(cmd, targetFile.getPath()); } } catch (IOException ioex) { throw new BuildException(ioex); } if (internalsf) { addValue(cmd, "-internalsf"); } if (sectionsonly) { addValue(cmd, "-sectionsonly"); } if (sigAlg != null) { addValue(cmd, "-sigalg"); addValue(cmd, sigAlg); } if (digestAlg != null) { addValue(cmd, "-digestalg"); addValue(cmd, digestAlg); } //add -tsa operations if declared addTimestampAuthorityCommands(cmd); //JAR source is required addValue(cmd, jarSource.getPath()); //alias is required for signing addValue(cmd, alias); log( "Signing JAR" + ( retryCount == 1 ? "" : " (try:" + tries + ")" ) + ": " + jarSource.getAbsolutePath() + " to " + targetFile.getAbsolutePath() + " as " + alias); try { cmd.execute(); } catch (BuildException e) { if (tries == retryCount) { throw e; } continue; } break; } // restore the lastModified attribute if (preserveLastModified) { FILE_UTILS.setFileLastModified(targetFile, lastModified); } } /** * If the tsa parameters are set, this passes them to the command. * There is no validation of java version, as third party JDKs * may implement this on earlier/later jarsigner implementations. * @param cmd the exec task. */ private void addTimestampAuthorityCommands(final ExecTask cmd) { if (tsaurl != null) { addValue(cmd, "-tsa"); addValue(cmd, tsaurl); } if (tsacert != null) { addValue(cmd, "-tsacert"); addValue(cmd, tsacert); } } /** * Compare a jar file with its corresponding signed jar. The logic for this * is complex, and best explained in the source itself. Essentially if * either file doesnt exist, or the destfile has an out of date timestamp, * then the return value is false. *

* If we are signing ourself, the check {@link #isSigned(File)} is used to * trigger the process. * * @param jarFile the unsigned jar file * @param signedjarFile the result signed jar file * @return true if the signedjarFile is considered up to date */ protected boolean isUpToDate(File jarFile, File signedjarFile) { if (isForce() || null == jarFile || !jarFile.exists()) { //these are pathological cases, but retained in case somebody //subclassed us. return false; } //we normally compare destination with source File destFile = signedjarFile; if (destFile == null) { //but if no dest is specified, compare source to source destFile = jarFile; } //if, by any means, the destfile and source match, if (jarFile.equals(destFile)) { if (lazy) { //we check the presence of signatures on lazy signing return isSigned(jarFile); } //unsigned or non-lazy self signings are always false return false; } //if they are different, the timestamps are used return FILE_UTILS.isUpToDate(jarFile, destFile); } /** * test for a file being signed, by looking for a signature in the META-INF * directory with our alias/sigfile. * * @param file the file to be checked * @return true if the file is signed * @see IsSigned#isSigned(File, String) */ protected boolean isSigned(File file) { try { return IsSigned.isSigned(file, sigfile == null ? alias : sigfile); } catch (IOException e) { //just log this log(e.toString(), Project.MSG_VERBOSE); return false; } } /** * true to indicate that the signed jar modification date remains the same * as the original. Defaults to false * * @param preserveLastModified if true preserve the last modified time */ public void setPreserveLastModified(boolean preserveLastModified) { this.preserveLastModified = preserveLastModified; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy