• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ebourg / jsign / #360

05 Feb 2025 09:39AM UTC coverage: 83.213% (-0.006%) from 83.219%
#360

push

ebourg
Always propagate the underlying exception when throwing a SignerException

2 of 3 new or added lines in 1 file covered. (66.67%)

4 existing lines in 2 files now uncovered.

4739 of 5695 relevant lines covered (83.21%)

0.83 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.95
/jsign-core/src/main/java/net/jsign/SignerHelper.java
1
/**
2
 * Copyright 2017 Emmanuel Bourg and contributors
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16

17
package net.jsign;
18

19
import java.io.File;
20
import java.io.FileWriter;
21
import java.io.IOException;
22
import java.net.Authenticator;
23
import java.net.InetSocketAddress;
24
import java.net.MalformedURLException;
25
import java.net.PasswordAuthentication;
26
import java.net.Proxy;
27
import java.net.ProxySelector;
28
import java.net.SocketAddress;
29
import java.net.URI;
30
import java.net.URL;
31
import java.nio.charset.Charset;
32
import java.nio.file.Files;
33
import java.security.KeyStore;
34
import java.security.KeyStoreException;
35
import java.security.PrivateKey;
36
import java.security.Provider;
37
import java.security.cert.Certificate;
38
import java.util.ArrayList;
39
import java.util.Base64;
40
import java.util.Collection;
41
import java.util.Collections;
42
import java.util.Date;
43
import java.util.LinkedHashSet;
44
import java.util.List;
45
import java.util.Set;
46
import java.util.logging.Logger;
47

48
import org.bouncycastle.asn1.ASN1Encodable;
49
import org.bouncycastle.asn1.ASN1InputStream;
50
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
51
import org.bouncycastle.asn1.DEROctetString;
52
import org.bouncycastle.asn1.DERSet;
53
import org.bouncycastle.asn1.DERUTF8String;
54
import org.bouncycastle.asn1.cms.AttributeTable;
55
import org.bouncycastle.asn1.cms.ContentInfo;
56
import org.bouncycastle.cert.X509CertificateHolder;
57
import org.bouncycastle.cms.CMSException;
58
import org.bouncycastle.cms.CMSProcessable;
59
import org.bouncycastle.cms.CMSSignedData;
60
import org.bouncycastle.cms.SignerId;
61
import org.bouncycastle.cms.SignerInformation;
62
import org.bouncycastle.cms.SignerInformationStore;
63
import org.bouncycastle.operator.DefaultAlgorithmNameFinder;
64
import org.bouncycastle.util.encoders.Hex;
65

66
import net.jsign.asn1.authenticode.AuthenticodeObjectIdentifiers;
67
import net.jsign.timestamp.Timestamper;
68
import net.jsign.timestamp.TimestampingMode;
69

70
/**
71
 * Helper class to create AuthenticodeSigner instances with untyped parameters.
72
 * This is used internally to share the parameter validation logic
73
 * between the Ant task, the Maven/Gradle plugins and the CLI tool.
74
 *
75
 * @since 2.0
76
 */
77
class SignerHelper {
78
    public static final String PARAM_COMMAND = "command";
79
    public static final String PARAM_KEYSTORE = "keystore";
80
    public static final String PARAM_STOREPASS = "storepass";
81
    public static final String PARAM_STORETYPE = "storetype";
82
    public static final String PARAM_ALIAS = "alias";
83
    public static final String PARAM_KEYPASS = "keypass";
84
    public static final String PARAM_KEYFILE = "keyfile";
85
    public static final String PARAM_CERTFILE = "certfile";
86
    public static final String PARAM_ALG = "alg";
87
    public static final String PARAM_TSAURL = "tsaurl";
88
    public static final String PARAM_TSMODE = "tsmode";
89
    public static final String PARAM_TSRETRIES = "tsretries";
90
    public static final String PARAM_TSRETRY_WAIT = "tsretrywait";
91
    public static final String PARAM_NAME = "name";
92
    public static final String PARAM_URL = "url";
93
    public static final String PARAM_PROXY_URL = "proxyUrl";
94
    public static final String PARAM_PROXY_USER = "proxyUser";
95
    public static final String PARAM_PROXY_PASS = "proxyPass";
96
    public static final String PARAM_REPLACE = "replace";
97
    public static final String PARAM_ENCODING = "encoding";
98
    public static final String PARAM_DETACHED = "detached";
99
    public static final String PARAM_FORMAT = "format";
100
    public static final String PARAM_VALUE = "value";
101

102
    private final Logger log = Logger.getLogger(getClass().getName());
1✔
103

104
    /** The name used to refer to a configuration parameter */
105
    private final String parameterName;
106

107
    /** The command to execute */
108
    private String command = "sign";
1✔
109

110
    private final KeyStoreBuilder ksparams;
111
    private String alias;
112
    private String tsaurl;
113
    private String tsmode;
114
    private int tsretries = -1;
1✔
115
    private int tsretrywait = -1;
1✔
116
    private String alg;
117
    private String name;
118
    private String url;
119
    private String proxyUrl;
120
    private String proxyUser;
121
    private String proxyPass;
122
    private boolean replace;
123
    private Charset encoding;
124
    private boolean detached;
125
    private String format;
126
    private String value;
127

128
    private AuthenticodeSigner signer;
129

130
    public SignerHelper(String parameterName) {
1✔
131
        this.parameterName = parameterName;
1✔
132
        this.ksparams = new KeyStoreBuilder(parameterName);
1✔
133
    }
1✔
134

135
    public SignerHelper command(String command) {
136
        this.command = command;
1✔
137
        return this;
1✔
138
    }
139

140
    public SignerHelper keystore(String keystore) {
141
        ksparams.keystore(keystore);
1✔
142
        return this;
1✔
143
    }
144

145
    public SignerHelper storepass(String storepass) {
146
        ksparams.storepass(storepass);
1✔
147
        return this;
1✔
148
    }
149

150
    public SignerHelper storetype(String storetype) {
151
        ksparams.storetype(storetype);
1✔
152
        return this;
1✔
153
    }
154

155
    public SignerHelper alias(String alias) {
156
        this.alias = alias;
1✔
157
        return this;
1✔
158
    }
159

160
    public SignerHelper keypass(String keypass) {
161
        ksparams.keypass(keypass);
1✔
162
        return this;
1✔
163
    }
164

165
    public SignerHelper keyfile(String keyfile) {
166
        ksparams.keyfile(keyfile);
1✔
167
        return this;
1✔
168
    }
169

170
    public SignerHelper keyfile(File keyfile) {
171
        ksparams.keyfile(keyfile);
1✔
172
        return this;
1✔
173
    }
174

175
    public SignerHelper certfile(String certfile) {
176
        ksparams.certfile(certfile);
1✔
177
        return this;
1✔
178
    }
179

180
    public SignerHelper certfile(File certfile) {
181
        ksparams.certfile(certfile);
1✔
182
        return this;
1✔
183
    }
184

185
    public SignerHelper alg(String alg) {
186
        this.alg = alg;
1✔
187
        return this;
1✔
188
    }
189

190
    public SignerHelper tsaurl(String tsaurl) {
191
        this.tsaurl = tsaurl;
1✔
192
        return this;
1✔
193
    }
194

195
    public SignerHelper tsmode(String tsmode) {
196
        this.tsmode = tsmode;
1✔
197
        return this;
1✔
198
    }
199

200
    public SignerHelper tsretries(int tsretries) {
201
        this.tsretries = tsretries;
1✔
202
        return this;
1✔
203
    }
204

205
    public SignerHelper tsretrywait(int tsretrywait) {
206
        this.tsretrywait = tsretrywait;
1✔
207
        return this;
1✔
208
    }
209

210
    public SignerHelper name(String name) {
211
        this.name = name;
1✔
212
        return this;
1✔
213
    }
214

215
    public SignerHelper url(String url) {
216
        this.url = url;
1✔
217
        return this;
1✔
218
    }
219

220
    public SignerHelper proxyUrl(String proxyUrl) {
221
        this.proxyUrl = proxyUrl;
1✔
222
        return this;
1✔
223
    }
224

225
    public SignerHelper proxyUser(String proxyUser) {
226
        this.proxyUser = proxyUser;
1✔
227
        return this;
1✔
228
    }
229

230
    public SignerHelper proxyPass(String proxyPass) {
231
        this.proxyPass = proxyPass;
1✔
232
        return this;
1✔
233
    }
234

235
    public SignerHelper replace(boolean replace) {
236
        this.replace = replace;
1✔
237
        return this;
1✔
238
    }
239

240
    public SignerHelper encoding(String encoding) {
241
        this.encoding = Charset.forName(encoding);
1✔
242
        return this;
1✔
243
    }
244

245
    public SignerHelper detached(boolean detached) {
246
        this.detached = detached;
1✔
247
        return this;
1✔
248
    }
249

250
    public SignerHelper format(String format) {
251
        this.format = format;
1✔
252
        return this;
1✔
253
    }
254

255
    public SignerHelper value(String value) {
256
        this.value = value;
1✔
257
        return this;
1✔
258
    }
259

260
    public SignerHelper param(String key, String value) {
261
        if (value == null) {
1✔
262
            return this;
1✔
263
        }
264
        
265
        switch (key) {
1✔
266
            case PARAM_COMMAND:    return command(value);
×
267
            case PARAM_KEYSTORE:   return keystore(value);
1✔
268
            case PARAM_STOREPASS:  return storepass(value);
1✔
269
            case PARAM_STORETYPE:  return storetype(value);
1✔
270
            case PARAM_ALIAS:      return alias(value);
1✔
271
            case PARAM_KEYPASS:    return keypass(value);
1✔
272
            case PARAM_KEYFILE:    return keyfile(value);
1✔
273
            case PARAM_CERTFILE:   return certfile(value);
1✔
274
            case PARAM_ALG:        return alg(value);
1✔
275
            case PARAM_TSAURL:     return tsaurl(value);
1✔
276
            case PARAM_TSMODE:     return tsmode(value);
1✔
277
            case PARAM_TSRETRIES:  return tsretries(Integer.parseInt(value));
1✔
278
            case PARAM_TSRETRY_WAIT: return tsretrywait(Integer.parseInt(value));
1✔
279
            case PARAM_NAME:       return name(value);
1✔
280
            case PARAM_URL:        return url(value);
1✔
281
            case PARAM_PROXY_URL:  return proxyUrl(value);
1✔
282
            case PARAM_PROXY_USER: return proxyUser(value);
1✔
283
            case PARAM_PROXY_PASS: return proxyPass(value);
1✔
284
            case PARAM_REPLACE:    return replace("true".equalsIgnoreCase(value));
×
285
            case PARAM_ENCODING:   return encoding(value);
1✔
286
            case PARAM_DETACHED:   return detached("true".equalsIgnoreCase(value));
×
287
            case PARAM_FORMAT:     return format(value);
×
288
            case PARAM_VALUE:      return value(value);
1✔
289
            default:
290
                throw new IllegalArgumentException("Unknown " + parameterName + ": " + key);
×
291
        }
292
    }
293

294
    void setBaseDir(File basedir) {
295
        ksparams.setBaseDir(basedir);
1✔
296
    }
1✔
297

298
    public void execute(String file) throws SignerException {
299
        execute(ksparams.createFile(file));
×
300
    }
×
301

302
    public void execute(File file) throws SignerException {
303
        switch (command) {
1✔
304
            case "sign":
305
                sign(file);
1✔
306
                break;
1✔
307
            case "timestamp":
308
                timestamp(file);
1✔
309
                break;
1✔
310
            case "extract":
311
                extract(file);
1✔
312
                break;
1✔
313
            case "remove":
314
                remove(file);
1✔
315
                break;
1✔
316
            case "tag":
317
                tag(file);
1✔
318
                break;
1✔
319
            default:
320
                throw new SignerException("Unknown command '" + command + "'");
1✔
321
        }
322
    }
1✔
323

324
    private AuthenticodeSigner build() throws SignerException {
325
        KeyStore ks;
326
        try {
327
            ks = ksparams.build();
1✔
328
        } catch (KeyStoreException e) {
1✔
329
            throw new SignerException("Failed to load the keystore " + (ksparams.keystore() != null ? ksparams.keystore() : ""), e);
1✔
330
        }
1✔
331
        KeyStoreType storetype = ksparams.storetype();
1✔
332
        Provider provider = ksparams.provider();
1✔
333

334
        Set<String> aliases = null;
1✔
335
        if (alias == null) {
1✔
336
            // guess the alias if there is only one in the keystore
337
            try {
338
                aliases = storetype.getAliases(ks);
1✔
339
            } catch (KeyStoreException e) {
×
340
                throw new SignerException(e.getMessage(), e);
×
341
            }
1✔
342

343
            if (aliases.isEmpty()) {
1✔
344
                throw new SignerException("No certificate found in the keystore " + (provider != null ? provider.getName() : ksparams.keystore()));
×
345
            } else if (aliases.size() == 1) {
1✔
346
                alias = aliases.iterator().next();
1✔
347
            } else {
348
                throw new SignerException("alias " + parameterName + " must be set to select a certificate (available aliases: " + String.join(", ", aliases) + ")");
1✔
349
            }
350
        }
351

352
        Certificate[] chain;
353
        try {
354
            chain = ks.getCertificateChain(alias);
1✔
355
        } catch (KeyStoreException e) {
×
356
            throw new SignerException(e.getMessage(), e);
×
357
        }
1✔
358
        if (chain == null) {
1✔
359
            String message = "No certificate found under the alias '" + alias + "' in the keystore " + (provider != null ? provider.getName() : ksparams.keystore());
1✔
360
            if (aliases == null) {
1✔
361
                try {
362
                    aliases = new LinkedHashSet<>(Collections.list(ks.aliases()));
1✔
363
                    if (aliases.isEmpty()) {
1✔
364
                        message = "No certificate found in the keystore " + (provider != null ? provider.getName() : ksparams.keystore());
1✔
365
                    } else if (aliases.contains(alias)) {
1✔
366
                        message = "The keystore password must be specified";
1✔
367
                    } else {
368
                        message += " (available aliases: " + String.join(", ", aliases) + ")";
1✔
369
                    }
370
                } catch (KeyStoreException e) {
×
371
                    message += " (couldn't load the list of available aliases: " + e.getMessage() + ")";
×
372
                }
1✔
373
            }
374
            throw new SignerException(message);
1✔
375
        }
376
        if (ksparams.certfile() != null) {
1✔
377
            // replace the certificate chain from the keystore with the complete chain from file
378
            try {
379
                chain = CertificateUtils.loadCertificateChain(ksparams.certfile());
1✔
380
            } catch (Exception e) {
×
381
                throw new SignerException("Failed to load the certificate from " + ksparams.certfile(), e);
×
382
            }
1✔
383
        }
384

385
        String keypass = ksparams.keypass();
1✔
386
        char[] password = keypass != null ? keypass.toCharArray() : new char[0];
1✔
387

388
        PrivateKey privateKey;
389
        try {
390
            privateKey = (PrivateKey) ks.getKey(alias, password);
1✔
391
        } catch (Exception e) {
1✔
392
            throw new SignerException("Failed to retrieve the private key from the keystore", e);
1✔
393
        }
1✔
394

395
        if (alg != null && DigestAlgorithm.of(alg) == null) {
1✔
396
            throw new SignerException("The digest algorithm " + alg + " is not supported");
1✔
397
        }
398

399
        try {
400
            initializeProxy(proxyUrl, proxyUser, proxyPass);
1✔
401
        } catch (Exception e) {
×
402
            throw new SignerException("Couldn't initialize proxy", e);
×
403
        }
1✔
404

405
        // enable timestamping with Azure Trusted Signing
406
        if (tsaurl == null && storetype == KeyStoreType.TRUSTEDSIGNING) {
1✔
407
            tsaurl = "http://timestamp.acs.microsoft.com/";
×
408
            tsmode = TimestampingMode.RFC3161.name();
×
409
            tsretries = 3;
×
410
        }
411
        
412
        // configure the signer
413
        return new AuthenticodeSigner(chain, privateKey)
1✔
414
                .withProgramName(name)
1✔
415
                .withProgramURL(url)
1✔
416
                .withDigestAlgorithm(DigestAlgorithm.of(alg))
1✔
417
                .withSignatureProvider(provider)
1✔
418
                .withSignaturesReplaced(replace)
1✔
419
                .withTimestamping(tsaurl != null || tsmode != null)
1✔
420
                .withTimestampingMode(tsmode != null ? TimestampingMode.of(tsmode) : TimestampingMode.AUTHENTICODE)
1✔
421
                .withTimestampingRetries(tsretries)
1✔
422
                .withTimestampingRetryWait(tsretrywait)
1✔
423
                .withTimestampingAuthority(tsaurl != null ? tsaurl.split(",") : null);
1✔
424
    }
425

426
    public void sign(String file) throws SignerException {
427
        sign(ksparams.createFile(file));
×
428
    }
×
429

430
    public void sign(File file) throws SignerException {
431
        if (file == null) {
1✔
432
            throw new SignerException("file must be set");
1✔
433
        }
434
        if (!file.exists()) {
1✔
435
            throw new SignerException("The file " + file + " couldn't be found");
1✔
436
        }
437
        
438
        try (Signable signable = Signable.of(file, encoding)) {
1✔
439
            File detachedSignature = getDetachedSignature(file);
1✔
440
            if (detached && detachedSignature.exists()) {
1✔
441
                try {
442
                    log.info("Attaching Authenticode signature to " + file);
1✔
443
                    attach(signable, detachedSignature);
1✔
444
                } catch (Exception e) {
×
445
                    throw new SignerException("Couldn't attach the signature to " + file, e);
×
446
                }
1✔
447

448
            } else {
449
                if (signer == null) {
1✔
450
                    signer = build();
1✔
451
                }
452

453
                log.info("Adding Authenticode signature to " + file);
1✔
454
                signer.sign(signable);
1✔
455

456
                if (detached) {
1✔
457
                    detach(signable, detachedSignature);
1✔
458
                }
459
            }
460

461
        } catch (UnsupportedOperationException | IllegalArgumentException e) {
1✔
462
            throw new SignerException(e.getMessage(), e);
1✔
463
        } catch (SignerException e) {
1✔
464
            throw e;
1✔
465
        } catch (Exception e) {
1✔
466
            throw new SignerException("Couldn't sign " + file, e);
1✔
467
        }
1✔
468
    }
1✔
469

470
    private void attach(Signable signable, File detachedSignature) throws IOException, CMSException {
471
        byte[] signatureBytes = Files.readAllBytes(detachedSignature.toPath());
1✔
472
        CMSSignedData signedData = new CMSSignedData((CMSProcessable) null, ContentInfo.getInstance(new ASN1InputStream(signatureBytes).readObject()));
1✔
473

474
        signable.setSignature(signedData);
1✔
475
        signable.save();
1✔
476
        // todo warn if the hashes don't match
477
    }
1✔
478

479
    private void detach(Signable signable, File detachedSignature) throws IOException {
480
        CMSSignedData signedData = signable.getSignatures().get(0);
1✔
481
        byte[] content = signedData.toASN1Structure().getEncoded("DER");
1✔
482
        if (format == null || "DER".equalsIgnoreCase(format)) {
1✔
483
            Files.write(detachedSignature.toPath(), content);
1✔
484
        } else if ("PEM".equalsIgnoreCase(format)) {
1✔
485
            try (FileWriter out = new FileWriter(detachedSignature)) {
1✔
486
                String encoded = Base64.getEncoder().encodeToString(content);
1✔
487
                out.write("-----BEGIN PKCS7-----\n");
1✔
488
                for (int i = 0; i < encoded.length(); i += 64) {
1✔
489
                    out.write(encoded.substring(i, Math.min(i + 64, encoded.length())));
1✔
490
                    out.write('\n');
1✔
491
                }
492
                out.write("-----END PKCS7-----\n");
1✔
493
            }
494
        } else {
495
            throw new IllegalArgumentException("Unknown output format '" + format + "'");
1✔
496
        }
497
    }
1✔
498

499
    private File getDetachedSignature(File file) {
500
        return new File(file.getParentFile(), file.getName() + ".sig");
1✔
501
    }
502

503
    private void extract(File file) throws SignerException {
504
        if (!file.exists()) {
1✔
505
            throw new SignerException("Couldn't find " + file);
1✔
506
        }
507

508
        try (Signable signable = Signable.of(file)) {
1✔
509
            List<CMSSignedData> signatures = signable.getSignatures();
1✔
510
            if (signatures.isEmpty()) {
1✔
511
                throw new SignerException("No signature found in " + file);
1✔
512
            }
513

514
            File detachedSignature = getDetachedSignature(file);
1✔
515
            if ("PEM".equalsIgnoreCase(format)) {
1✔
516
                detachedSignature = new File(detachedSignature.getParentFile(), detachedSignature.getName() + ".pem");
1✔
517
            }
518
            log.info("Extracting signature to " + detachedSignature);
1✔
519
            detach(signable, detachedSignature);
1✔
520
        } catch (UnsupportedOperationException | IllegalArgumentException e) {
1✔
521
            throw new SignerException(e.getMessage(), e);
1✔
522
        } catch (SignerException e) {
1✔
523
            throw e;
1✔
524
        } catch (Exception e) {
×
525
            throw new SignerException("Couldn't extract the signature from " + file, e);
×
526
        }
1✔
527
    }
1✔
528

529
    private void remove(File file) throws SignerException {
530
        if (!file.exists()) {
1✔
531
            throw new SignerException("Couldn't find " + file);
1✔
532
        }
533

534
        try (Signable signable = Signable.of(file)) {
1✔
535
            List<CMSSignedData> signatures = signable.getSignatures();
1✔
536
            if (signatures.isEmpty()) {
1✔
537
                log.severe("No signature found in " + file);
1✔
538
                return;
1✔
539
            }
540

541
            log.info("Removing signature from " + file);
1✔
542
            signable.setSignature(null);
1✔
543
            signable.save();
1✔
544
        } catch (UnsupportedOperationException | IllegalArgumentException e) {
1✔
NEW
545
            throw new SignerException(e.getMessage(), e);
×
546
        } catch (Exception e) {
×
547
            throw new SignerException("Couldn't remove the signature from " + file, e);
×
548
        }
1✔
549
    }
1✔
550

551
    private void tag(File file) throws SignerException {
552
        if (!file.exists()) {
1✔
553
            throw new SignerException("Couldn't find " + file);
1✔
554
        }
555

556
        try (Signable signable = Signable.of(file)) {
1✔
557
            List<CMSSignedData> signatures = signable.getSignatures();
1✔
558
            if (signatures.isEmpty()) {
1✔
559
                throw new SignerException("No signature found in " + file);
1✔
560
            }
561

562
            log.info("Adding tag to " + file);
1✔
563
            CMSSignedData signature = signatures.get(0);
1✔
564
            signature = addUnsignedAttribute(signature, AuthenticodeObjectIdentifiers.JSIGN_UNSIGNED_DATA_OBJID, getTagValue());
1✔
565
            signable.setSignature(signature);
1✔
566
        } catch (SignerException e) {
1✔
567
            throw e;
1✔
568
        } catch (Exception e) {
1✔
569
            throw new SignerException("Couldn't modify the signature of " + file, e);
1✔
570
        }
1✔
571
    }
1✔
572

573
    private CMSSignedData addUnsignedAttribute(CMSSignedData signature, ASN1ObjectIdentifier oid, ASN1Encodable value) {
574
        SignerInformationStore store = signature.getSignerInfos();
1✔
575
        Collection<SignerInformation> signers = store.getSigners();
1✔
576
        SignerInformation signer = signers.iterator().next();
1✔
577

578
        AttributeTable attributes = signer.getUnsignedAttributes();
1✔
579
        if (attributes == null) {
1✔
580
            attributes = new AttributeTable(new DERSet());
1✔
581
        }
582
        attributes = attributes.add(oid, value);
1✔
583

584
        signers.remove(signer);
1✔
585
        signers.add(SignerInformation.replaceUnsignedAttributes(signer, attributes));
1✔
586
        return CMSSignedData.replaceSigners(signature, new SignerInformationStore(signers));
1✔
587
    }
588

589
    private ASN1Encodable getTagValue() throws IOException {
590
        if (value == null) {
1✔
591
            byte[] array = new byte[1024];
1✔
592
            String begin = "-----BEGIN TAG-----";
1✔
593
            System.arraycopy(begin.getBytes(), 0, array, 0, begin.length());
1✔
594
            String end = "-----END TAG-----";
1✔
595
            System.arraycopy(end.getBytes(), 0, array, array.length - end.length(), end.length());
1✔
596
            return new DEROctetString(array);
1✔
597

598
        } else if (value.startsWith("0x")) {
1✔
599
            byte[] array = Hex.decode(value.substring(2));
1✔
600
            return new DEROctetString(array);
1✔
601

602
        } else if (value.startsWith("file:")) {
1✔
603
            byte[] array = Files.readAllBytes(new File(value.substring("file:".length())).toPath());
1✔
604
            return new DEROctetString(array);
1✔
605

606
        } else {
607
            return new DERUTF8String(value);
1✔
608
        }
609
    }
610

611
    private void timestamp(File file) throws SignerException {
612
        if (!file.exists()) {
1✔
613
            throw new SignerException("Couldn't find " + file);
1✔
614
        }
615

616
        try {
617
            initializeProxy(proxyUrl, proxyUser, proxyPass);
1✔
618
        } catch (Exception e) {
×
619
            throw new SignerException("Couldn't initialize proxy", e);
×
620
        }
1✔
621

622
        try (Signable signable = Signable.of(file)) {
1✔
623
            if (signable.getSignatures().isEmpty()) {
1✔
624
                throw new SignerException("No signature found in " + file);
1✔
625
            }
626

627
            Timestamper timestamper = Timestamper.create(tsmode != null ? TimestampingMode.of(tsmode) : TimestampingMode.AUTHENTICODE);
1✔
628
            timestamper.setRetries(tsretries);
1✔
629
            timestamper.setRetryWait(tsretrywait);
1✔
630
            if (tsaurl != null) {
1✔
631
                timestamper.setURLs(tsaurl.split(","));
1✔
632
            }
633
            DigestAlgorithm digestAlgorithm = alg != null ? DigestAlgorithm.of(alg) : DigestAlgorithm.getDefault();
1✔
634

635
            List<CMSSignedData> signatures = new ArrayList<>();
1✔
636
            for (CMSSignedData signature : signable.getSignatures()) {
1✔
637
                SignerInformation signerInformation = signature.getSignerInfos().iterator().next();
1✔
638
                SignerId signerId = signerInformation.getSID();
1✔
639
                X509CertificateHolder certificate = (X509CertificateHolder) signature.getCertificates().getMatches(signerId).iterator().next();
1✔
640

641
                String digestAlgorithmName = new DefaultAlgorithmNameFinder().getAlgorithmName(signerInformation.getDigestAlgorithmID()); 
1✔
642
                String keyAlgorithmName = new DefaultAlgorithmNameFinder().getAlgorithmName(new ASN1ObjectIdentifier(signerInformation.getEncryptionAlgOID()));
1✔
643
                String name = digestAlgorithmName + "/" + keyAlgorithmName + " signature from '" + certificate.getSubject() + "'";
1✔
644

645
                if (SignatureUtils.isTimestamped(signature) && !replace) {
1✔
646
                    log.fine(name + " already timestamped");
1✔
647
                    signatures.add(signature);
1✔
648
                    continue;
1✔
649
                }
650

651
                boolean expired = certificate.getNotAfter().before(new Date());
1✔
652
                if (expired) {
1✔
653
                    log.fine(name + " is expired, skipping");
×
654
                    signatures.add(signature);
×
655
                    continue;
×
656
                }
657

658
                log.info("Adding timestamp to " + name);
1✔
659
                signature = SignatureUtils.removeTimestamp(signature);
1✔
660
                signature = timestamper.timestamp(digestAlgorithm, signature);
1✔
661

662
                signatures.add(signature);
1✔
663
            }
1✔
664

665
            CMSSignedData signature = signatures.get(0);
1✔
666
            if (signatures.size() > 1) {
1✔
667
                Collection<CMSSignedData> nestedSignatures = signatures.subList(1, signatures.size());
1✔
668
                signature = SignatureUtils.addNestedSignature(signature, true, nestedSignatures.toArray(new CMSSignedData[0]));
1✔
669
            }
670
            signable.setSignature(signature);
1✔
671
        } catch (IOException | CMSException e) {
×
672
            throw new SignerException("Couldn't timestamp " + file, e);
×
673
        }
1✔
674
    }
1✔
675

676
    /**
677
     * Initializes the proxy.
678
     *
679
     * @param proxyUrl       the url of the proxy (either as hostname:port or http[s]://hostname:port)
680
     * @param proxyUser      the username for the proxy authentication
681
     * @param proxyPassword  the password for the proxy authentication
682
     */
683
    private void initializeProxy(String proxyUrl, final String proxyUser, final String proxyPassword) throws MalformedURLException {
684
        // Do nothing if there is no proxy url.
685
        if (proxyUrl != null && !proxyUrl.trim().isEmpty()) {
1✔
686
            if (!proxyUrl.trim().startsWith("http")) {
1✔
687
                proxyUrl = "http://" + proxyUrl.trim();
1✔
688
            }
689
            final URL url = new URL(proxyUrl);
1✔
690
            final int port = url.getPort() < 0 ? 80 : url.getPort();
1✔
691

692
            ProxySelector.setDefault(new ProxySelector() {
1✔
693
                public List<Proxy> select(URI uri) {
694
                    Proxy proxy;
695
                    if (uri.getScheme().equals("socket")) {
1✔
696
                        proxy = new Proxy(Proxy.Type.SOCKS, new InetSocketAddress(url.getHost(), port));
×
697
                    } else {
698
                        proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(url.getHost(), port));
1✔
699
                    }
700
                    log.fine("Proxy selected for " + uri + " : " + proxy);
1✔
701
                    return Collections.singletonList(proxy);
1✔
702
                }
703

704
                public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
705
                }
×
706
            });
707

708
            if (proxyUser != null && !proxyUser.isEmpty() && proxyPassword != null) {
1✔
709
                Authenticator.setDefault(new Authenticator() {
1✔
710
                    protected PasswordAuthentication getPasswordAuthentication() {
711
                        return new PasswordAuthentication(proxyUser, proxyPassword.toCharArray());
1✔
712
                    }
713
                });
714
            }
715
        }
716
    }
1✔
717
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc