alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

Ant example source code file (SignJar.java)

This example Ant source code file (SignJar.java) is included in the DevDaily.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Java - Ant tags/keywords

buildexception, buildexception, error_no_alias, exectask, file, file, filenamemapper, filenamemapper, fileresource, fileutils, io, jar, string, string, util, you

The SignJar.java source code

/*
 *  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.resources.FileResource;
import org.apache.tools.ant.util.FileUtils;
import org.apache.tools.ant.util.IdentityMapper;
import org.apache.tools.ant.util.FileNameMapper;

/**
 * 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
 * <tt>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 <a href="http://java.sun.com/j2se/1.5.0/docs/guide/security/time-of-signing-beta1.html">
 * beta documentation</a>
 * @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;

    /**
     * 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

    /**
     * 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;
    }

    /**
     * sign the jar(s)
     *
     * @throws BuildException on errors
     */
    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()) {
                FileResource fr = (FileResource) iter.next();

                //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.
     * <p/>
     * 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();
        final ExecTask cmd = createJarSigner();

        setCommonOptions(cmd);

        bindToKeystore(cmd);
        if (null != sigfile) {
            addValue(cmd, "-sigfile");
            String value = this.sigfile;
            addValue(cmd, value);
        }

        //DO NOT SET THE -signedjar OPTION if source==dest
        //unless you like fielding hotspot crash reports
        if (null != targetFile && !jarSource.equals(targetFile)) {
            addValue(cmd, "-signedjar");
            addValue(cmd, targetFile.getPath());
        }

        if (internalsf) {
            addValue(cmd, "-internalsf");
        }

        if (sectionsonly) {
            addValue(cmd, "-sectionsonly");
        }

        //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: "
            + jarSource.getAbsolutePath()
            + " to "
            + targetFile.getAbsolutePath()
            + " as " + alias);

        cmd.execute();

        // restore the lastModified attribute
        if (preserveLastModified) {
            targetFile.setLastModified(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.
     * <p/>
     * 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 (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.
     *
     * @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, alias);
        } 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;
    }
}

Other Ant examples (source code examples)

Here is a short list of links related to this Ant SignJar.java source code file:

... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.