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

org.apache.maven.plugins.jarsigner.JarsignerSignMojo 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.maven.plugins.jarsigner;

import java.io.File;
import java.io.IOException;
import java.time.Duration;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.stream.Collectors;

import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.jarsigner.TsaSelector.TsaServer;
import org.apache.maven.shared.jarsigner.JarSigner;
import org.apache.maven.shared.jarsigner.JarSignerRequest;
import org.apache.maven.shared.jarsigner.JarSignerSignRequest;
import org.apache.maven.shared.jarsigner.JarSignerUtil;
import org.apache.maven.shared.utils.StringUtils;
import org.apache.maven.shared.utils.cli.Commandline;
import org.apache.maven.shared.utils.cli.javatool.JavaToolException;
import org.apache.maven.shared.utils.cli.javatool.JavaToolResult;

/**
 * Signs a project artifact and attachments using jarsigner.
 *
 * @author Christian Schulte
 * @since 1.0
 */
@Mojo(name = "sign", defaultPhase = LifecyclePhase.PACKAGE, threadSafe = true)
public class JarsignerSignMojo extends AbstractJarsignerMojo {

    /**
     * See options.
     */
    @Parameter(property = "jarsigner.keypass")
    private String keypass;

    /**
     * See options.
     */
    @Parameter(property = "jarsigner.sigfile")
    private String sigfile;

    /**
     * Indicates whether existing signatures should be removed from the processed JAR files prior to signing them. If
     * enabled, the resulting JAR will appear as being signed only once.
     *
     * @since 1.1
     */
    @Parameter(property = "jarsigner.removeExistingSignatures", defaultValue = "false")
    private boolean removeExistingSignatures;

    /**
     * 

URL(s) to Time Stamping Authority (TSA) server(s) to use to timestamp the signing. * See options. * Separate multiple TSA URLs with comma (without space) or a nested XML tag.

* *
{@code
     * 
     *   http://timestamp.digicert.com,http://timestamp.globalsign.com/tsa/r6advanced1
     * 
     * }
* *
{@code
     * 
     *   
     *     http://timestamp.digicert.com
     *     http://timestamp.globalsign.com/tsa/r6advanced1
     *   
     * 
     * }
* *

Usage of multiple TSA servers only makes sense when {@link #maxTries} is more than 1. A different TSA server * will only be used at retries.

* *

Changed to a list since 3.1.0. Single XML element (without comma) is still supported.

* * @since 1.3 */ @Parameter(property = "jarsigner.tsa") private String[] tsa; /** *

Alias(es) for certificate(s) in the active keystore used to find a TSA URL. From the certificate the X509v3 * extension "Subject Information Access" field is examined to find the TSA server URL. See * options. * Separate multiple aliases with comma (without space) or a nested XML tag.

* *
{@code
     * 
     *   alias1,alias2
     * 
     * }
* *
{@code
     * 
     *   
     *     alias1
     *     alias2
     *   
     * 
     * }
* *

Should not be used at the same time as the {@link #tsa} parameter (because jarsigner will typically ignore * tsacert, if tsa is set).

* *

Usage of multiple aliases only makes sense when {@link #maxTries} is more than 1. A different TSA server * will only be used at retries.

* *

Changed to a list since 3.1.0. Single XML element (without comma) is still supported.

* * @since 1.3 */ @Parameter(property = "jarsigner.tsacert") private String[] tsacert; /** *

OID(s) to send to the TSA server to identify the policy ID the server should use. If not specified TSA server * will choose a default policy ID. Each TSA server vendor will typically define their own policy OIDs. See * options. * Separate multiple OIDs with comma (without space) or a nested XML tag.

* *
{@code
     * 
     *   1.3.6.1.4.1.4146.2.3.1.2,2.16.840.1.114412.7.1
     * 
     * }
* *
{@code
     * 
     *   
     *     1.3.6.1.4.1.4146.2.3.1.2
     *     2.16.840.1.114412.7.1
     *   
     * 
     * }
* *

If used, the number of OIDs should be the same as the number of elements in {@link #tsa} or {@link #tsacert}. * The first OID will be used for the first TSA server, the second OID for the second TSA server and so on.

* * @since 3.1.0 */ @Parameter(property = "jarsigner.tsapolicyid") private String[] tsapolicyid; /** * The message digest algorithm to use in the messageImprint that the TSA server will timestamp. A default value * (for example {@code SHA-384}) will be selected by jarsigner if this parameter is not set. Only available in * Java 11 and later. See options. * * @since 3.1.0 */ @Parameter(property = "jarsigner.tsadigestalg") private String tsadigestalg; /** * Location of the extra certificate chain file. See * options. * * @since 1.5 */ @Parameter(property = "jarsigner.certchain", required = false) private File certchain; /** * How many times to try to sign a jar (assuming each previous attempt is a failure). This option may be desirable * if any network operations are used during signing, for example using a Time Stamp Authority or network based * PKCS11 HSM solution for storing code signing keys. * * The default value of 1 indicates that no retries should be made. * * @since 3.1.0 */ @Parameter(property = "jarsigner.maxTries", defaultValue = "1") private int maxTries; /** * Maximum delay, in seconds, to wait after a failed attempt before re-trying. The delay after a failed attempt * follows an exponential backoff strategy, with increasing delay times. * * @since 3.1.0 */ @Parameter(property = "jarsigner.maxRetryDelaySeconds", defaultValue = "0") private int maxRetryDelaySeconds; /** * Maximum number of parallel threads to use when signing jar files. Increases performance when signing multiple jar * files, especially when network operations are used during signing, for example when using a Time Stamp Authority * or network based PKCS11 HSM solution for storing code signing keys. Note: the logging from the signing process * will be interleaved, and harder to read, when using many threads. * * @since 3.1.0 */ @Parameter(property = "jarsigner.threadCount", defaultValue = "1") private int threadCount; /** Current WaitStrategy, to allow for sleeping after a signing failure. */ private WaitStrategy waitStrategy = this::defaultWaitStrategy; private TsaSelector tsaSelector; /** Exponent limit for exponential wait after failure function. 2^20 = 1048576 sec ~= 12 days. */ private static final int MAX_WAIT_EXPONENT_ATTEMPT = 20; @Override protected String getCommandlineInfo(final Commandline commandLine) { String commandLineInfo = commandLine != null ? commandLine.toString() : null; if (commandLineInfo != null) { commandLineInfo = StringUtils.replace(commandLineInfo, this.keypass, "'*****'"); } return commandLineInfo; } @Override protected void preProcessArchive(final File archive) throws MojoExecutionException { if (removeExistingSignatures) { try { JarSignerUtil.unsignArchive(archive); } catch (IOException e) { throw new MojoExecutionException("Failed to unsign archive " + archive + ": " + e.getMessage(), e); } } } @Override protected void validateParameters() throws MojoExecutionException { super.validateParameters(); if (maxTries < 1) { getLog().warn(getMessage("invalidMaxTries", maxTries)); maxTries = 1; } if (maxRetryDelaySeconds < 0) { getLog().warn(getMessage("invalidMaxRetryDelaySeconds", maxRetryDelaySeconds)); maxRetryDelaySeconds = 0; } if (threadCount < 1) { getLog().warn(getMessage("invalidThreadCount", threadCount)); threadCount = 1; } if (tsa.length > 0 && tsacert.length > 0) { getLog().warn(getMessage("warnUsageTsaAndTsacertSimultaneous")); } if (tsapolicyid.length > tsa.length || tsapolicyid.length > tsacert.length) { getLog().warn(getMessage("warnUsageTsapolicyidTooMany", tsapolicyid.length, tsa.length, tsacert.length)); } if (tsa.length > 1 && maxTries == 1) { getLog().warn(getMessage("warnUsageMultiTsaWithoutRetry", tsa.length)); } if (tsacert.length > 1 && maxTries == 1) { getLog().warn(getMessage("warnUsageMultiTsacertWithoutRetry", tsacert.length)); } tsaSelector = new TsaSelector(tsa, tsacert, tsapolicyid, tsadigestalg); } /** * {@inheritDoc} */ @Override protected JarSignerRequest createRequest(File archive) throws MojoExecutionException { JarSignerSignRequest request = new JarSignerSignRequest(); request.setSigfile(sigfile); updateJarSignerRequestWithTsa(request, tsaSelector.getServer()); request.setCertchain(certchain); // Special handling for passwords through the Maven Security Dispatcher request.setKeypass(decrypt(keypass)); return request; } /** Modifies JarSignerRequest with TSA parameters */ private void updateJarSignerRequestWithTsa(JarSignerSignRequest request, TsaServer tsaServer) { request.setTsaLocation(tsaServer.getTsaUrl()); request.setTsaAlias(tsaServer.getTsaAlias()); request.setTsapolicyid(tsaServer.getTsaPolicyId()); request.setTsadigestalg(tsaServer.getTsaDigestAlt()); } /** * {@inheritDoc} Processing of files may be parallelized for increased performance. */ @Override protected void processArchives(List archives) throws MojoExecutionException { ExecutorService executor = Executors.newFixedThreadPool(threadCount); List> futures = archives.stream() .map(file -> executor.submit((Callable) () -> { processArchive(file); return null; // Return dummy value to conform with Void type })) .collect(Collectors.toList()); try { for (Future future : futures) { future.get(); // Wait for completion. Result ignored, but may raise any Exception } } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new MojoExecutionException("Thread interrupted while waiting for jarsigner to complete", e); } catch (ExecutionException e) { if (e.getCause() instanceof MojoExecutionException) { throw (MojoExecutionException) e.getCause(); } throw new MojoExecutionException("Error processing archives", e); } finally { // Shutdown of thread pool. If an Exception occurred, remaining threads will be aborted "best effort" executor.shutdownNow(); } } /** * {@inheritDoc} * * Will retry signing up to maxTries times if it fails. * * @throws MojoExecutionException if all signing attempts fail */ @Override protected void executeJarSigner(JarSigner jarSigner, JarSignerRequest request) throws JavaToolException, MojoExecutionException { for (int attempt = 0; attempt < maxTries; attempt++) { JavaToolResult result = jarSigner.execute(request); int resultCode = result.getExitCode(); if (resultCode == 0) { return; } tsaSelector.registerFailure(); // Could be TSA server problem or something unrelated to TSA if (attempt < maxTries - 1) { // If not last attempt waitStrategy.waitAfterFailure(attempt, Duration.ofSeconds(maxRetryDelaySeconds)); updateJarSignerRequestWithTsa((JarSignerSignRequest) request, tsaSelector.getServer()); } else { // Last attempt failed, use this failure as resulting failure throw new MojoExecutionException( getMessage("failure", getCommandlineInfo(result.getCommandline()), resultCode)); } } } /** Set current WaitStrategy. Package private for testing. */ void setWaitStrategy(WaitStrategy waitStrategy) { this.waitStrategy = waitStrategy; } /** Wait/sleep after a signing failure before the next re-try should happen. */ @FunctionalInterface interface WaitStrategy { /** * Will be called after a signing failure, if a re-try is about to happen. May as a side effect sleep current * thread for some time. * * @param attempt the attempt number (0 is the first) * @param maxRetryDelay the maximum duration to sleep (may be zero) * @throws MojoExecutionException if the sleep was interrupted */ void waitAfterFailure(int attempt, Duration maxRetryDelay) throws MojoExecutionException; } private void defaultWaitStrategy(int attempt, Duration maxRetryDelay) throws MojoExecutionException { waitAfterFailure(attempt, maxRetryDelay, Thread::sleep); } /** Thread.sleep(long millis) interface to make testing easier */ @FunctionalInterface interface Sleeper { void sleep(long millis) throws InterruptedException; } /** Package private for testing */ void waitAfterFailure(int attempt, Duration maxRetryDelay, Sleeper sleeper) throws MojoExecutionException { // Use attempt as exponent in the exponential function, but limit it to avoid too big values. int exponentAttempt = Math.min(attempt, MAX_WAIT_EXPONENT_ATTEMPT); long delayMillis = (long) (Duration.ofSeconds(1).toMillis() * Math.pow(2, exponentAttempt)); delayMillis = Math.min(delayMillis, maxRetryDelay.toMillis()); if (delayMillis > 0) { getLog().info("Sleeping after failed attempt for " + (delayMillis / 1000) + " seconds..."); try { sleeper.sleep(delayMillis); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new MojoExecutionException("Thread interrupted while waiting after failure", e); } } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy