Skip to content

Commit 5535caa

Browse files
schedinslawekjaranowski
authored andcommitted
[MJARSIGNER-74] Allow usage of multiple Time Stamping Authority (TSA) servers
Using multiple TSA URLs to try if first fail Adding support for tsapolicyid and tsadigestalg
1 parent 5a9a484 commit 5535caa

File tree

9 files changed

+680
-14
lines changed

9 files changed

+680
-14
lines changed

src/main/java/org/apache/maven/plugins/jarsigner/AbstractJarsignerMojo.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -155,10 +155,10 @@ public abstract class AbstractJarsignerMojo extends AbstractMojo {
155155
* <pre>
156156
* {@code
157157
* <configuration>
158-
* <arguments>
159-
* <argument>-signedjar</argument>
160-
* <argument>my-project_signed.jar</argument>
161-
* </arguments>
158+
* <arguments>
159+
* <argument>-signedjar</argument>
160+
* <argument>my-project_signed.jar</argument>
161+
* </arguments>
162162
* </configuration>
163163
* }</pre>
164164
*/

src/main/java/org/apache/maven/plugins/jarsigner/JarsignerSignMojo.java

Lines changed: 122 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import org.apache.maven.plugins.annotations.LifecyclePhase;
3434
import org.apache.maven.plugins.annotations.Mojo;
3535
import org.apache.maven.plugins.annotations.Parameter;
36+
import org.apache.maven.plugins.jarsigner.TsaSelector.TsaServer;
3637
import org.apache.maven.shared.jarsigner.JarSigner;
3738
import org.apache.maven.shared.jarsigner.JarSignerRequest;
3839
import org.apache.maven.shared.jarsigner.JarSignerSignRequest;
@@ -73,20 +74,107 @@ public class JarsignerSignMojo extends AbstractJarsignerMojo {
7374
private boolean removeExistingSignatures;
7475

7576
/**
77+
* <p>URL(s) to Time Stamping Authority (TSA) server(s) to use to timestamp the signing.
7678
* See <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/jarsigner.html#Options">options</a>.
79+
* Separate multiple TSA URLs with comma (without space) or a nested XML tag.</p>
80+
*
81+
* <pre>{@code
82+
* <configuration>
83+
* <tsa>http://timestamp.digicert.com,http://timestamp.globalsign.com/tsa/r6advanced1</tsa>
84+
* </configuration>
85+
* }</pre>
86+
*
87+
* <pre>{@code
88+
* <configuration>
89+
* <tsa>
90+
* <url>http://timestamp.digicert.com</url>
91+
* <url>http://timestamp.globalsign.com/tsa/r6advanced1</url>
92+
* </tsa>
93+
* </configuration>
94+
* }</pre>
95+
*
96+
* <p>Usage of multiple TSA servers only makes sense when {@link #maxTries} is more than 1. A different TSA server
97+
* will only be used at retries.</p>
98+
*
99+
* <p>Changed to a list since 3.1.0. Single XML element (without comma) is still supported.</p>
77100
*
78101
* @since 1.3
79102
*/
80103
@Parameter(property = "jarsigner.tsa")
81-
private String tsa;
104+
private String[] tsa;
82105

83106
/**
84-
* See <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/jarsigner.html#Options">options</a>.
107+
* <p>Alias(es) for certificate(s) in the active keystore used to find a TSA URL. From the certificate the X509v3
108+
* extension "Subject Information Access" field is examined to find the TSA server URL. See
109+
* <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/jarsigner.html#Options">options</a>.
110+
* Separate multiple aliases with comma (without space) or a nested XML tag.</p>
111+
*
112+
* <pre>{@code
113+
* <configuration>
114+
* <tsacert>alias1,alias2</tsacert>
115+
* </configuration>
116+
* }</pre>
117+
*
118+
* <pre>{@code
119+
* <configuration>
120+
* <tsacert>
121+
* <alias>alias1</alias>
122+
* <alias>alias2</alias>
123+
* </tsacert>
124+
* </configuration>
125+
* }</pre>
126+
*
127+
* <p>Should not be used at the same time as the {@link #tsa} parameter (because jarsigner will typically ignore
128+
* tsacert, if tsa is set).</p>
129+
*
130+
* <p>Usage of multiple aliases only makes sense when {@link #maxTries} is more than 1. A different TSA server
131+
* will only be used at retries.</p>
132+
*
133+
* <p>Changed to a list since 3.1.0. Single XML element (without comma) is still supported.</p>
85134
*
86135
* @since 1.3
87136
*/
88137
@Parameter(property = "jarsigner.tsacert")
89-
private String tsacert;
138+
private String[] tsacert;
139+
140+
/**
141+
* <p>OID(s) to send to the TSA server to identify the policy ID the server should use. If not specified TSA server
142+
* will choose a default policy ID. Each TSA server vendor will typically define their own policy OIDs. See
143+
* <a href="https://docs.oracle.com/javase/8/docs/technotes/tools/windows/jarsigner.html#CCHIFIAD">options</a>.
144+
* Separate multiple OIDs with comma (without space) or a nested XML tag.</p>
145+
*
146+
* <pre>{@code
147+
* <configuration>
148+
* <tsapolicyid>1.3.6.1.4.1.4146.2.3.1.2,2.16.840.1.114412.7.1</tsapolicyid>
149+
* </configuration>
150+
* }</pre>
151+
*
152+
* <pre>{@code
153+
* <configuration>
154+
* <tsapolicyid>
155+
* <oid>1.3.6.1.4.1.4146.2.3.1.2</oid>
156+
* <oid>2.16.840.1.114412.7.1</oid>
157+
* </tsapolicyid>
158+
* </configuration>
159+
* }</pre>
160+
*
161+
* <p>If used, the number of OIDs should be the same as the number of elements in {@link #tsa} or {@link #tsacert}.
162+
* The first OID will be used for the first TSA server, the second OID for the second TSA server and so on.</p>
163+
*
164+
* @since 3.1.0
165+
*/
166+
@Parameter(property = "jarsigner.tsapolicyid")
167+
private String[] tsapolicyid;
168+
169+
/**
170+
* The message digest algorithm to use in the messageImprint that the TSA server will timestamp. A default value
171+
* (for example {@code SHA-384}) will be selected by jarsigner if this parameter is not set. Only available in
172+
* Java 11 and later. See <a href="https://docs.oracle.com/en/java/javase/11/tools/jarsigner.html">options</a>.
173+
*
174+
* @since 3.1.0
175+
*/
176+
@Parameter(property = "jarsigner.tsadigestalg")
177+
private String tsadigestalg;
90178

91179
/**
92180
* Location of the extra certificate chain file. See
@@ -132,6 +220,8 @@ public class JarsignerSignMojo extends AbstractJarsignerMojo {
132220
/** Current WaitStrategy, to allow for sleeping after a signing failure. */
133221
private WaitStrategy waitStrategy = this::defaultWaitStrategy;
134222

223+
private TsaSelector tsaSelector;
224+
135225
/** Exponent limit for exponential wait after failure function. 2^20 = 1048576 sec ~= 12 days. */
136226
private static final int MAX_WAIT_EXPONENT_ATTEMPT = 20;
137227

@@ -175,6 +265,20 @@ protected void validateParameters() throws MojoExecutionException {
175265
getLog().warn(getMessage("invalidThreadCount", threadCount));
176266
threadCount = 1;
177267
}
268+
269+
if (tsa.length > 0 && tsacert.length > 0) {
270+
getLog().warn(getMessage("warnUsageTsaAndTsacertSimultaneous"));
271+
}
272+
if (tsapolicyid.length > tsa.length || tsapolicyid.length > tsacert.length) {
273+
getLog().warn(getMessage("warnUsageTsapolicyidTooMany", tsapolicyid.length, tsa.length, tsacert.length));
274+
}
275+
if (tsa.length > 1 && maxTries == 1) {
276+
getLog().warn(getMessage("warnUsageMultiTsaWithoutRetry", tsa.length));
277+
}
278+
if (tsacert.length > 1 && maxTries == 1) {
279+
getLog().warn(getMessage("warnUsageMultiTsacertWithoutRetry", tsacert.length));
280+
}
281+
tsaSelector = new TsaSelector(tsa, tsacert, tsapolicyid, tsadigestalg);
178282
}
179283

180284
/**
@@ -184,15 +288,22 @@ protected void validateParameters() throws MojoExecutionException {
184288
protected JarSignerRequest createRequest(File archive) throws MojoExecutionException {
185289
JarSignerSignRequest request = new JarSignerSignRequest();
186290
request.setSigfile(sigfile);
187-
request.setTsaLocation(tsa);
188-
request.setTsaAlias(tsacert);
291+
updateJarSignerRequestWithTsa(request, tsaSelector.getServer());
189292
request.setCertchain(certchain);
190293

191294
// Special handling for passwords through the Maven Security Dispatcher
192295
request.setKeypass(decrypt(keypass));
193296
return request;
194297
}
195298

299+
/** Modifies JarSignerRequest with TSA parameters */
300+
private void updateJarSignerRequestWithTsa(JarSignerSignRequest request, TsaServer tsaServer) {
301+
request.setTsaLocation(tsaServer.getTsaUrl());
302+
request.setTsaAlias(tsaServer.getTsaAlias());
303+
request.setTsapolicyid(tsaServer.getTsaPolicyId());
304+
request.setTsadigestalg(tsaServer.getTsaDigestAlt());
305+
}
306+
196307
/**
197308
* {@inheritDoc} Processing of files may be parallelized for increased performance.
198309
*/
@@ -202,7 +313,7 @@ protected void processArchives(List<File> archives) throws MojoExecutionExceptio
202313
List<Future<Void>> futures = archives.stream()
203314
.map(file -> executor.submit((Callable<Void>) () -> {
204315
processArchive(file);
205-
return null;
316+
return null; // Return dummy value to conform with Void type
206317
}))
207318
.collect(Collectors.toList());
208319
try {
@@ -236,15 +347,18 @@ protected void executeJarSigner(JarSigner jarSigner, JarSignerRequest request)
236347
for (int attempt = 0; attempt < maxTries; attempt++) {
237348
JavaToolResult result = jarSigner.execute(request);
238349
int resultCode = result.getExitCode();
239-
Commandline commandLine = result.getCommandline();
240350
if (resultCode == 0) {
241351
return;
242352
}
353+
tsaSelector.registerFailure(); // Could be TSA server problem or something unrelated to TSA
354+
243355
if (attempt < maxTries - 1) { // If not last attempt
244356
waitStrategy.waitAfterFailure(attempt, Duration.ofSeconds(maxRetryDelaySeconds));
357+
updateJarSignerRequestWithTsa((JarSignerSignRequest) request, tsaSelector.getServer());
245358
} else {
246359
// Last attempt failed, use this failure as resulting failure
247-
throw new MojoExecutionException(getMessage("failure", getCommandlineInfo(commandLine), resultCode));
360+
throw new MojoExecutionException(
361+
getMessage("failure", getCommandlineInfo(result.getCommandline()), resultCode));
248362
}
249363
}
250364
}
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.maven.plugins.jarsigner;
20+
21+
import java.util.ArrayList;
22+
import java.util.Collections;
23+
import java.util.List;
24+
import java.util.concurrent.atomic.AtomicInteger;
25+
26+
/**
27+
* Helper class to select a Time Stamping Authority (TSA) server along with parameters to send. The protocol is defined
28+
* in RFC 3161: Internet X.509 Public Key Infrastructure Time-Stamp Protocol (TSP).
29+
*
30+
* From a jarsigner perspective there are two things that are important:
31+
* 1. Finding a TSA server URL
32+
* 2. What parameters to use for TSA server communication.
33+
*
34+
* Finding a URL can be done in two ways:
35+
* a) The end-user has specified an explicit URL (the most common way)
36+
* b) The end-user has specified a keystore alias that points to a certificate in the active keystore. From the
37+
* certificate the X509v3 extension "Subject Information Access" field is examined to find the TSA server URL.
38+
* Example:
39+
* <pre>
40+
* [vagrant@podmanhost ~]$ openssl x509 -noout -ext subjectInfoAccess -in tsa-server.crt
41+
* Subject Information Access:
42+
* AD Time Stamping - URI:http://timestamp.globalsign.com/tsa/r6advanced1
43+
* </pre>
44+
*
45+
* Each TSA server vendor typically has defined its own OID for what "policy" to use in the timestamping process. For
46+
* example GlobalSign might use 1.3.6.1.4.1.4146.2.3.1.2. A DigiCert TSA server would not accept this OID. In most cases
47+
* there is no need for the end-user to specify this because the TSA server will choose a default.
48+
*
49+
* jarsigner will send a message digest to the TSA server along with the message digest algorithm. For example
50+
* {@code SHA-384}. A TSA server might reject the chosen algorithm, but typically most TSA servers supports the "common"
51+
* ones (like SHA-256, SHA-384 and SHA-512). In most cases there is no need for the end-user to specify this because the
52+
* jarsigner tool choose a good default.
53+
*/
54+
class TsaSelector {
55+
56+
/** The current TsaServer in use (if any). One per thread */
57+
private final ThreadLocal<TsaServer> currentTsaServer = new ThreadLocal<>();
58+
59+
/** List of TSA servers. Will at minimum contain a dummy/empty value */
60+
private final List<TsaServer> tsaServers;
61+
62+
TsaSelector(String[] tsa, String[] tsacert, String[] tsapolicyid, String tsadigestalg) {
63+
List<TsaServer> tsaServersTmp = new ArrayList<>();
64+
65+
for (int i = 0; i < Math.max(tsa.length, tsacert.length); i++) {
66+
String tsaUrl = i < tsa.length ? tsa[i] : null;
67+
String tsaAlias = i < tsacert.length ? tsacert[i] : null;
68+
String tsaPolicyId = i < tsapolicyid.length ? tsapolicyid[i] : null;
69+
tsaServersTmp.add(new TsaServer(tsaUrl, tsaAlias, tsaPolicyId, tsadigestalg));
70+
}
71+
72+
if (tsaServersTmp.isEmpty()) {
73+
tsaServersTmp.add(TsaServer.EMPTY);
74+
}
75+
this.tsaServers = Collections.unmodifiableList(tsaServersTmp);
76+
}
77+
78+
/**
79+
* Gets the next "best" TSA server to use.
80+
*
81+
* Uses a "best effort" approach without any synchronization. It may not select the "snapshot-consistent" best TSA
82+
* server, but good enough.
83+
*/
84+
TsaServer getServer() {
85+
TsaServer best = tsaServers.get(0);
86+
for (int i = 1; i < tsaServers.size(); i++) {
87+
if (best.failureCount.get() > tsaServers.get(i).failureCount.get()) {
88+
best = tsaServers.get(i);
89+
}
90+
}
91+
currentTsaServer.set(best);
92+
return best;
93+
}
94+
95+
/**
96+
* Register that the current used TsaServer was involved in a jarsigner execution that failed. This could be a
97+
* problem with the TsaServer, but it could also be other factors unrelated to the TsaServer. Regardless of the
98+
* cause of the failure it is registered as a failure for the current used TsaServer to be used when determining the
99+
* next TsaServer to try.
100+
*/
101+
void registerFailure() {
102+
if (currentTsaServer.get() != null) {
103+
currentTsaServer.get().failureCount.incrementAndGet();
104+
}
105+
}
106+
107+
/** Representation of a single TSA server and the parameters to use for it */
108+
static class TsaServer {
109+
private static final TsaServer EMPTY = new TsaServer(null, null, null, null);
110+
111+
private final AtomicInteger failureCount = new AtomicInteger(0);
112+
private final String tsaUrl;
113+
private final String tsaAlias;
114+
private final String tsaPolicyId;
115+
private final String tsaDigestAlt;
116+
117+
private TsaServer(String tsaUrl, String tsaAlias, String tsaPolicyId, String tsaDigestAlt) {
118+
this.tsaUrl = tsaUrl;
119+
this.tsaAlias = tsaAlias;
120+
this.tsaPolicyId = tsaPolicyId;
121+
this.tsaDigestAlt = tsaDigestAlt;
122+
}
123+
124+
String getTsaUrl() {
125+
return tsaUrl;
126+
}
127+
128+
String getTsaAlias() {
129+
return tsaAlias;
130+
}
131+
132+
String getTsaPolicyId() {
133+
return tsaPolicyId;
134+
}
135+
136+
String getTsaDigestAlt() {
137+
return tsaDigestAlt;
138+
}
139+
}
140+
}

src/main/resources/jarsigner.properties

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,7 @@ archiveNotSigned = Archive ''{0}'' is not signed
2727
invalidMaxTries = Invalid maxTries value. Was ''{0}'' but should be >= 1
2828
invalidMaxRetryDelaySeconds = Invalid maxRetryDelaySeconds value. Was ''{0}'' but should be >= 0
2929
invalidThreadCount = Invalid threadCount value. Was ''{0}'' but should be >= 1
30+
warnUsageTsaAndTsacertSimultaneous = Usage of both -tsa and -tsacert is undefined
31+
warnUsageTsapolicyidTooMany = Too many ({0}) number of OIDs given, but only {1} and {2} TSA URL and TSA certificate alias, respectively
32+
warnUsageMultiTsaWithoutRetry = {0} TSA URLs specified. Only first will be used because maxTries is set to 1
33+
warnUsageMultiTsacertWithoutRetry = {0} TSA certificate aliases specified. Only first will be used because maxTries is set to 1

0 commit comments

Comments
 (0)