1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.plugins.jarsigner;
20
21 import java.io.File;
22 import java.io.IOException;
23 import java.time.Duration;
24 import java.util.List;
25 import java.util.concurrent.Callable;
26 import java.util.concurrent.ExecutionException;
27 import java.util.concurrent.ExecutorService;
28 import java.util.concurrent.Executors;
29 import java.util.concurrent.Future;
30 import java.util.stream.Collectors;
31
32 import org.apache.maven.plugin.MojoExecutionException;
33 import org.apache.maven.plugins.annotations.LifecyclePhase;
34 import org.apache.maven.plugins.annotations.Mojo;
35 import org.apache.maven.plugins.annotations.Parameter;
36 import org.apache.maven.plugins.jarsigner.TsaSelector.TsaServer;
37 import org.apache.maven.shared.jarsigner.JarSigner;
38 import org.apache.maven.shared.jarsigner.JarSignerRequest;
39 import org.apache.maven.shared.jarsigner.JarSignerSignRequest;
40 import org.apache.maven.shared.jarsigner.JarSignerUtil;
41 import org.apache.maven.shared.utils.StringUtils;
42 import org.apache.maven.shared.utils.cli.Commandline;
43 import org.apache.maven.shared.utils.cli.javatool.JavaToolException;
44 import org.apache.maven.shared.utils.cli.javatool.JavaToolResult;
45
46
47
48
49
50
51
52 @Mojo(name = "sign", defaultPhase = LifecyclePhase.PACKAGE, threadSafe = true)
53 public class JarsignerSignMojo extends AbstractJarsignerMojo {
54
55
56
57
58 @Parameter(property = "jarsigner.keypass")
59 private String keypass;
60
61
62
63
64 @Parameter(property = "jarsigner.sigfile")
65 private String sigfile;
66
67
68
69
70
71
72
73 @Parameter(property = "jarsigner.removeExistingSignatures", defaultValue = "false")
74 private boolean removeExistingSignatures;
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103 @Parameter(property = "jarsigner.tsa")
104 private String[] tsa;
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137 @Parameter(property = "jarsigner.tsacert")
138 private String[] tsacert;
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166 @Parameter(property = "jarsigner.tsapolicyid")
167 private String[] tsapolicyid;
168
169
170
171
172
173
174
175
176 @Parameter(property = "jarsigner.tsadigestalg")
177 private String tsadigestalg;
178
179
180
181
182
183
184
185 @Parameter(property = "jarsigner.certchain", required = false)
186 private File certchain;
187
188
189
190
191
192
193
194
195
196
197 @Parameter(property = "jarsigner.maxTries", defaultValue = "1")
198 private int maxTries;
199
200
201
202
203
204
205
206 @Parameter(property = "jarsigner.maxRetryDelaySeconds", defaultValue = "0")
207 private int maxRetryDelaySeconds;
208
209
210
211
212
213
214
215
216
217 @Parameter(property = "jarsigner.threadCount", defaultValue = "1")
218 private int threadCount;
219
220
221 private WaitStrategy waitStrategy = this::defaultWaitStrategy;
222
223 private TsaSelector tsaSelector;
224
225
226 private static final int MAX_WAIT_EXPONENT_ATTEMPT = 20;
227
228 @Override
229 protected String getCommandlineInfo(final Commandline commandLine) {
230 String commandLineInfo = commandLine != null ? commandLine.toString() : null;
231
232 if (commandLineInfo != null) {
233 commandLineInfo = StringUtils.replace(commandLineInfo, this.keypass, "'*****'");
234 }
235
236 return commandLineInfo;
237 }
238
239 @Override
240 protected void preProcessArchive(final File archive) throws MojoExecutionException {
241 if (removeExistingSignatures) {
242 try {
243 JarSignerUtil.unsignArchive(archive);
244 } catch (IOException e) {
245 throw new MojoExecutionException("Failed to unsign archive " + archive + ": " + e.getMessage(), e);
246 }
247 }
248 }
249
250 @Override
251 protected void validateParameters() throws MojoExecutionException {
252 super.validateParameters();
253
254 if (maxTries < 1) {
255 getLog().warn(getMessage("invalidMaxTries", maxTries));
256 maxTries = 1;
257 }
258
259 if (maxRetryDelaySeconds < 0) {
260 getLog().warn(getMessage("invalidMaxRetryDelaySeconds", maxRetryDelaySeconds));
261 maxRetryDelaySeconds = 0;
262 }
263
264 if (threadCount < 1) {
265 getLog().warn(getMessage("invalidThreadCount", threadCount));
266 threadCount = 1;
267 }
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);
282 }
283
284
285
286
287 @Override
288 protected JarSignerRequest createRequest(File archive) throws MojoExecutionException {
289 JarSignerSignRequest request = new JarSignerSignRequest();
290 request.setSigfile(sigfile);
291 updateJarSignerRequestWithTsa(request, tsaSelector.getServer());
292 request.setCertchain(certchain);
293
294
295 request.setKeypass(decrypt(keypass));
296 return request;
297 }
298
299
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
307
308
309
310 @Override
311 protected void processArchives(List<File> archives) throws MojoExecutionException {
312 ExecutorService executor = Executors.newFixedThreadPool(threadCount);
313 List<Future<Void>> futures = archives.stream()
314 .map(file -> executor.submit((Callable<Void>) () -> {
315 processArchive(file);
316 return null;
317 }))
318 .collect(Collectors.toList());
319 try {
320 for (Future<Void> future : futures) {
321 future.get();
322 }
323 } catch (InterruptedException e) {
324 Thread.currentThread().interrupt();
325 throw new MojoExecutionException("Thread interrupted while waiting for jarsigner to complete", e);
326 } catch (ExecutionException e) {
327 if (e.getCause() instanceof MojoExecutionException) {
328 throw (MojoExecutionException) e.getCause();
329 }
330 throw new MojoExecutionException("Error processing archives", e);
331 } finally {
332
333 executor.shutdownNow();
334 }
335 }
336
337
338
339
340
341
342
343
344 @Override
345 protected void executeJarSigner(JarSigner jarSigner, JarSignerRequest request)
346 throws JavaToolException, MojoExecutionException {
347 for (int attempt = 0; attempt < maxTries; attempt++) {
348 JavaToolResult result = jarSigner.execute(request);
349 int resultCode = result.getExitCode();
350 if (resultCode == 0) {
351 return;
352 }
353 tsaSelector.registerFailure();
354
355 if (attempt < maxTries - 1) {
356 waitStrategy.waitAfterFailure(attempt, Duration.ofSeconds(maxRetryDelaySeconds));
357 updateJarSignerRequestWithTsa((JarSignerSignRequest) request, tsaSelector.getServer());
358 } else {
359
360 throw new MojoExecutionException(
361 getMessage("failure", getCommandlineInfo(result.getCommandline()), resultCode));
362 }
363 }
364 }
365
366
367 void setWaitStrategy(WaitStrategy waitStrategy) {
368 this.waitStrategy = waitStrategy;
369 }
370
371
372 @FunctionalInterface
373 interface WaitStrategy {
374
375
376
377
378
379
380
381
382 void waitAfterFailure(int attempt, Duration maxRetryDelay) throws MojoExecutionException;
383 }
384
385 private void defaultWaitStrategy(int attempt, Duration maxRetryDelay) throws MojoExecutionException {
386 waitAfterFailure(attempt, maxRetryDelay, Thread::sleep);
387 }
388
389
390 @FunctionalInterface
391 interface Sleeper {
392 void sleep(long millis) throws InterruptedException;
393 }
394
395
396 void waitAfterFailure(int attempt, Duration maxRetryDelay, Sleeper sleeper) throws MojoExecutionException {
397
398 int exponentAttempt = Math.min(attempt, MAX_WAIT_EXPONENT_ATTEMPT);
399 long delayMillis = (long) (Duration.ofSeconds(1).toMillis() * Math.pow(2, exponentAttempt));
400 delayMillis = Math.min(delayMillis, maxRetryDelay.toMillis());
401 if (delayMillis > 0) {
402 getLog().info("Sleeping after failed attempt for " + (delayMillis / 1000) + " seconds...");
403 try {
404 sleeper.sleep(delayMillis);
405 } catch (InterruptedException e) {
406 Thread.currentThread().interrupt();
407 throw new MojoExecutionException("Thread interrupted while waiting after failure", e);
408 }
409 }
410 }
411 }