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

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

The 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 static com.google.common.base.Objects.*;

import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.jar.Attributes;

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.ResourceCollection;
import org.apache.tools.ant.types.resources.FileProvider;
import org.apache.tools.ant.types.resources.FileResource;
import org.apache.tools.ant.types.resources.Restrict;
import org.apache.tools.ant.types.resources.selectors.And;
import org.apache.tools.ant.types.resources.selectors.Name;
import org.apache.tools.ant.types.resources.selectors.Not;
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;
import org.bitstrings.maven.nbm.utils.JarPack200;
import org.bitstrings.maven.nbm.utils.JarUtils;

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

    // +p -->
    public static interface SigningListener
    {
        void beforeSigning( JarConfigResolved jarConfig );
    }

    private SigningListener jarListener;

    public void setSigningListener(SigningListener jarListener)
    {
        this.jarListener = jarListener;
    }

    public SigningListener getJarListener()
    {
        return jarListener;
    }

    public static class JarConfigResolved
    {
        private boolean unsignFirst;

        private Attributes extraManifestAttributes;

        public JarConfigResolved( boolean unsignFirst, Attributes extraManifestAttributes )
        {
            this.unsignFirst = unsignFirst;
            this.extraManifestAttributes = extraManifestAttributes;
        }

        public boolean isUnsignFirst()
        {
            return unsignFirst;
        }

        public Attributes getExtraManifestAttributes()
        {
            return extraManifestAttributes;
        }
    }

    public static class JarsConfig
    {
        private String includes;

        private String excludes;

        private Boolean unsignFirst;

        private List extraManifestAttributes;

        private List removeAttributes;


        public String getIncludes()
        {
            return includes;
        }

        public void setIncludes(String includes)
        {
            this.includes = includes;
        }

        public String getExcludes()
        {
            return excludes;
        }

        public Boolean getUnsignFirst()
        {
            return unsignFirst;
        }

        public void setUnsignFirst(Boolean unsignFirst)
        {
            this.unsignFirst = unsignFirst;
        }

        public void setExcludes(String excludes)
        {
            this.excludes = excludes;
        }

        public List getExtraManifestAttributes()
        {
            return extraManifestAttributes;
        }

        public void setExtraManifestAttributes(List extraManifestAttributes)
        {
            this.extraManifestAttributes = extraManifestAttributes;
        }

        public List getRemoveAttributes()
        {
            return removeAttributes;
        }

        public void setRemoveAttributes(List removeAttributes)
        {
            this.removeAttributes = removeAttributes;
        }
    }
    // <-- +p

    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;

    // +p
    private int retryCount = 1;

    // +p
    private boolean unsignFirst;

    // +p
    private List extraManifestAttributes;

    // +p
    private List jarsConfigs;

    // +p
    private File basedir;

    // +p
    private boolean destFlatten;

    // +p
    private boolean pack200 = false;

    // +p
    private Integer pack200Effort;

    // +p
    private JarPack200 jarPack200;

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

    public boolean isUnsignFirst()
    {
        return unsignFirst;
    }

    public void setUnsignFirst(boolean unsignFirst)
    {
        this.unsignFirst = unsignFirst;
    }

    public List getExtraManifestAttributes()
    {
        return extraManifestAttributes;
    }

    public void setExtraManifestAttributes(List extraManifestAttributes)
    {
        this.extraManifestAttributes = extraManifestAttributes;
    }

    public List getJarsConfigs()
    {
        return jarsConfigs;
    }

    public void setJarsConfigs(List jarsConfigs)
    {
        this.jarsConfigs = jarsConfigs;
    }

    public File getBasedir()
    {
        return basedir;
    }

    public void setBasedir(File basedir)
    {
        this.basedir = basedir;
    }

    public boolean isDestFlatten()
    {
        return destFlatten;
    }

    public void setDestFlatten(boolean destFlatten)
    {
        this.destFlatten = destFlatten;
    }

    public boolean isPack200()
    {
        return pack200;
    }

    public void setPack200(boolean pack200)
    {
        this.pack200 = pack200;
    }

    public Integer getPack200Effort()
    {
        return pack200Effort;
    }

    public void setPack200Effort(Integer pack200Effort)
    {
        this.pack200Effort = pack200Effort;
    }

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

        if ( pack200 )
        {
            jarPack200 =
                pack200Effort == null
                        ? new JarPack200()
                        : new JarPack200( pack200Effort );
        }

        beginExecution();

        try
        {
            final Attributes globalExtraManifestAttributes = buildManifestAttributes( extraManifestAttributes );

            final JarConfigResolved defaultJarsConfigInternal =
                        new JarConfigResolved( unsignFirst, globalExtraManifestAttributes );

            //special case single jar handling with signedjar attribute set
            if (hasJar && hasSignedJar) {

                FileResource source = new FileResource( getProject(), jar );

                signOneJar(
                        jar, signedjar,
                        firstNonNull(
                            buildJarsConfigInternalMap( source, globalExtraManifestAttributes ).get( source.getName() ),
                            defaultJarsConfigInternal ) );

                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();
            }


            // +p -->
            Map jarsConfigsMap =
                            buildJarsConfigInternalMap( sources, globalExtraManifestAttributes );

            //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 = iter.next();

                FileResource fr = ResourceUtils
                    .asFileResource( r.as(FileProvider.class) );

                if ( getBasedir() != null )
                {
                    fr.setBaseDir( getBasedir() );
                }

                //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,
                        firstNonNull( jarsConfigsMap.get( fr.getName() ), defaultJarsConfigInternal ) );
            }
        }
        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, JarConfigResolved jarConfig ) throws BuildException { File targetFile = jarTarget; if ( targetFile == null ) { targetFile = jarSource; } if ( isUpToDate( jarSource, targetFile ) ) { return; } long lastModified = jarSource.lastModified(); if ( destFlatten ) { targetFile = new File( destDir, targetFile.getName() ); } // +p --> boolean unsignFirstCombined = jarConfig.unsignFirst; Attributes manifestAttributes = jarConfig.extraManifestAttributes; File realSource = jarSource; File tmpJar = null; if ( unsignFirstCombined || ( ( manifestAttributes != null ) && !manifestAttributes.isEmpty() ) ) { try { tmpJar = File.createTempFile( jarSource.getName(), ".tmp.jar" ); tmpJar.deleteOnExit(); JarUtils.archiveModifier( jarSource, tmpJar, null, unsignFirstCombined, manifestAttributes ); jarSource = tmpJar; } catch ( IOException e ) { throw new BuildException( e ); } } if ( jarListener != null ) { jarListener.beforeSigning( jarConfig ); } // +p // pack200 -> REPACK if ( pack200 ) { try { jarPack200.repack( jarSource ); } catch ( IOException e ) { throw new BuildException( "Unable to repack " + jarSource, e ); } } // <-- +p for (int tries = 1; 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 archive" + ( tries == 1 ? "" : " (try:" + tries + ")" ) + ": " + realSource.getAbsolutePath() + " to " + targetFile.getAbsolutePath() + " as " + alias ); try { cmd.execute(); } catch (BuildException e) { log( e, Project.MSG_INFO ); if ( tries == retryCount ) { throw e; } continue; } break; } // restore the lastModified attribute if ( preserveLastModified ) { FILE_UTILS.setFileLastModified( targetFile, lastModified ); } // +p // pack200 -> PACK if ( pack200 ) { final File packedFile = jarPack200.getPackedFileFromJarFile( targetFile ); try { jarPack200.pack( targetFile, packedFile ); } catch ( IOException e ) { throw new BuildException( "Unable to repack " + jarSource, e ); } // restore the lastModified attribute if ( preserveLastModified ) { FILE_UTILS.setFileLastModified( packedFile, lastModified ); } } if ( tmpJar != null ) { tmpJar.delete(); } } /** * 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; } private Attributes buildManifestAttributes( List properties ) { Attributes manifestAttributes = null; if ( ( properties != null ) && !properties.isEmpty() ) { manifestAttributes = new Attributes(); for ( Property property : properties ) { manifestAttributes.putValue( property.getName(), property.getValue() ); } } return manifestAttributes; } private Map buildJarsConfigInternalMap( ResourceCollection sources, Attributes moreExtraManifestAttributes ) { HashMap jarConfigResolvedMap = new LinkedHashMap(); if ( jarsConfigs != null ) { for ( JarsConfig jarsConfig : jarsConfigs ) { boolean unsignFirstCombined = firstNonNull( jarsConfig.getUnsignFirst(), unsignFirst ); Attributes extraManifestAttributes = buildManifestAttributes( jarsConfig.getExtraManifestAttributes() ); if ( extraManifestAttributes == null ) { extraManifestAttributes = moreExtraManifestAttributes; } else if ( moreExtraManifestAttributes != null ) { extraManifestAttributes.putAll( moreExtraManifestAttributes ); } for ( String attrToRemove : jarsConfig.getRemoveAttributes() ) { extraManifestAttributes.putValue( attrToRemove, null ); } Restrict restrict = new Restrict(); restrict.setProject( getProject() ); restrict.add( sources ); And patternFilter = new And(); if ( jarsConfig.getIncludes() != null ) { Name includesFilter = new Name(); includesFilter.setName( jarsConfig.getIncludes() ); patternFilter.add( includesFilter ); } if ( jarsConfig.getExcludes() != null ) { Name excludes = new Name(); excludes.setName( jarsConfig.getExcludes() ); Not excludesFilter = new Not(); excludesFilter.add( excludes ); patternFilter.add( excludesFilter ); } if ( patternFilter.hasSelectors() ) { restrict.add( patternFilter ); } Iterator iter = restrict.iterator(); while ( iter.hasNext() ) { Resource resource = iter.next(); FileResource fr = ResourceUtils .asFileResource( resource.as( FileProvider.class ) ); if ( getBasedir() != null ) { fr.setBaseDir( getBasedir() ); } jarConfigResolvedMap.put( fr.getName(), new JarConfigResolved( unsignFirstCombined, extraManifestAttributes ) ); } } } return jarConfigResolvedMap; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy