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

ebourg / jsign / #381

22 Sep 2025 04:50PM UTC coverage: 83.226% (+0.1%) from 83.103%
#381

push

ebourg
Reorganized the documentation (command line first)

4927 of 5920 relevant lines covered (83.23%)

0.83 hits per line

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

88.98
/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("No file specified");
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.setSignatures(SignatureUtils.getSignatures(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
        List<CMSSignedData> signatures = signable.getSignatures();
1✔
481

482
        // ensure the secondary signatures are nested in the first one (for EFI files)
483
        CMSSignedData signedData = signatures.get(0);
1✔
484
        if (signatures.size() > 1) {
1✔
485
            List<CMSSignedData> nestedSignatures = signatures.subList(1, signatures.size());
1✔
486
            signedData = SignatureUtils.addNestedSignature(signedData, true, nestedSignatures.toArray(new CMSSignedData[0]));
1✔
487
        }
488

489
        byte[] content = signedData.toASN1Structure().getEncoded("DER");
1✔
490
        if (format == null || "DER".equalsIgnoreCase(format)) {
1✔
491
            Files.write(detachedSignature.toPath(), content);
1✔
492
        } else if ("PEM".equalsIgnoreCase(format)) {
1✔
493
            try (FileWriter out = new FileWriter(detachedSignature)) {
1✔
494
                String encoded = Base64.getEncoder().encodeToString(content);
1✔
495
                out.write("-----BEGIN PKCS7-----\n");
1✔
496
                for (int i = 0; i < encoded.length(); i += 64) {
1✔
497
                    out.write(encoded.substring(i, Math.min(i + 64, encoded.length())));
1✔
498
                    out.write('\n');
1✔
499
                }
500
                out.write("-----END PKCS7-----\n");
1✔
501
            }
502
        } else {
503
            throw new IllegalArgumentException("Unknown output format '" + format + "'");
1✔
504
        }
505
    }
1✔
506

507
    private File getDetachedSignature(File file) {
508
        return new File(file.getParentFile(), file.getName() + ".sig");
1✔
509
    }
510

511
    private void extract(File file) throws SignerException {
512
        if (!file.exists()) {
1✔
513
            throw new SignerException("Couldn't find " + file);
1✔
514
        }
515

516
        try (Signable signable = Signable.of(file)) {
1✔
517
            List<CMSSignedData> signatures = signable.getSignatures();
1✔
518
            if (signatures.isEmpty()) {
1✔
519
                throw new SignerException("No signature found in " + file);
1✔
520
            }
521

522
            File detachedSignature = getDetachedSignature(file);
1✔
523
            if ("PEM".equalsIgnoreCase(format)) {
1✔
524
                detachedSignature = new File(detachedSignature.getParentFile(), detachedSignature.getName() + ".pem");
1✔
525
            }
526
            log.info("Extracting signature to " + detachedSignature);
1✔
527
            detach(signable, detachedSignature);
1✔
528
        } catch (UnsupportedOperationException | IllegalArgumentException e) {
1✔
529
            throw new SignerException(e.getMessage(), e);
1✔
530
        } catch (SignerException e) {
1✔
531
            throw e;
1✔
532
        } catch (Exception e) {
×
533
            throw new SignerException("Couldn't extract the signature from " + file, e);
×
534
        }
1✔
535
    }
1✔
536

537
    private void remove(File file) throws SignerException {
538
        if (!file.exists()) {
1✔
539
            throw new SignerException("Couldn't find " + file);
1✔
540
        }
541

542
        try (Signable signable = Signable.of(file)) {
1✔
543
            List<CMSSignedData> signatures = signable.getSignatures();
1✔
544
            if (signatures.isEmpty()) {
1✔
545
                log.severe("No signature found in " + file);
1✔
546
                return;
1✔
547
            }
548

549
            log.info("Removing " + signatures.size() + " signature" + (signatures.size() > 1 ? "s" : "") + " from " + file);
1✔
550
            signable.setSignatures(null);
1✔
551
            signable.save();
1✔
552
        } catch (UnsupportedOperationException | IllegalArgumentException e) {
1✔
553
            throw new SignerException(e.getMessage(), e);
×
554
        } catch (Exception e) {
×
555
            throw new SignerException("Couldn't remove the signature from " + file, e);
×
556
        }
1✔
557
    }
1✔
558

559
    private void tag(File file) throws SignerException {
560
        if (!file.exists()) {
1✔
561
            throw new SignerException("Couldn't find " + file);
1✔
562
        }
563

564
        try (Signable signable = Signable.of(file)) {
1✔
565
            List<CMSSignedData> signatures = signable.getSignatures();
1✔
566
            if (signatures.isEmpty()) {
1✔
567
                throw new SignerException("No signature found in " + file);
1✔
568
            }
569

570
            log.info("Adding tag to " + file);
1✔
571
            signatures.set(0, addUnsignedAttribute(signatures.get(0), AuthenticodeObjectIdentifiers.JSIGN_UNSIGNED_DATA_OBJID, getTagValue()));
1✔
572
            signable.setSignatures(signatures);
1✔
573
            signable.save();
1✔
574
        } catch (SignerException e) {
1✔
575
            throw e;
1✔
576
        } catch (Exception e) {
1✔
577
            throw new SignerException("Couldn't modify the signature of " + file, e);
1✔
578
        }
1✔
579
    }
1✔
580

581
    private CMSSignedData addUnsignedAttribute(CMSSignedData signature, ASN1ObjectIdentifier oid, ASN1Encodable value) {
582
        SignerInformationStore store = signature.getSignerInfos();
1✔
583
        Collection<SignerInformation> signers = store.getSigners();
1✔
584
        SignerInformation signer = signers.iterator().next();
1✔
585

586
        AttributeTable attributes = signer.getUnsignedAttributes();
1✔
587
        if (attributes == null) {
1✔
588
            attributes = new AttributeTable(new DERSet());
1✔
589
        }
590
        attributes = attributes.add(oid, value);
1✔
591

592
        signers.remove(signer);
1✔
593
        signers.add(SignerInformation.replaceUnsignedAttributes(signer, attributes));
1✔
594
        return CMSSignedData.replaceSigners(signature, new SignerInformationStore(signers));
1✔
595
    }
596

597
    private ASN1Encodable getTagValue() throws IOException {
598
        if (value == null) {
1✔
599
            byte[] array = new byte[1024];
1✔
600
            String begin = "-----BEGIN TAG-----";
1✔
601
            System.arraycopy(begin.getBytes(), 0, array, 0, begin.length());
1✔
602
            String end = "-----END TAG-----";
1✔
603
            System.arraycopy(end.getBytes(), 0, array, array.length - end.length(), end.length());
1✔
604
            return new DEROctetString(array);
1✔
605

606
        } else if (value.startsWith("0x")) {
1✔
607
            byte[] array = Hex.decode(value.substring(2));
1✔
608
            return new DEROctetString(array);
1✔
609

610
        } else if (value.startsWith("file:")) {
1✔
611
            byte[] array = Files.readAllBytes(new File(value.substring("file:".length())).toPath());
1✔
612
            return new DEROctetString(array);
1✔
613

614
        } else {
615
            return new DERUTF8String(value);
1✔
616
        }
617
    }
618

619
    private void timestamp(File file) throws SignerException {
620
        if (!file.exists()) {
1✔
621
            throw new SignerException("Couldn't find " + file);
1✔
622
        }
623

624
        try {
625
            initializeProxy(proxyUrl, proxyUser, proxyPass);
1✔
626
        } catch (Exception e) {
×
627
            throw new SignerException("Couldn't initialize proxy", e);
×
628
        }
1✔
629

630
        try (Signable signable = Signable.of(file)) {
1✔
631
            if (signable.getSignatures().isEmpty()) {
1✔
632
                throw new SignerException("No signature found in " + file);
1✔
633
            }
634

635
            Timestamper timestamper = Timestamper.create(tsmode != null ? TimestampingMode.of(tsmode) : TimestampingMode.AUTHENTICODE);
1✔
636
            timestamper.setRetries(tsretries);
1✔
637
            timestamper.setRetryWait(tsretrywait);
1✔
638
            if (tsaurl != null) {
1✔
639
                timestamper.setURLs(tsaurl.split(","));
1✔
640
            }
641
            DigestAlgorithm digestAlgorithm = alg != null ? DigestAlgorithm.of(alg) : DigestAlgorithm.getDefault();
1✔
642

643
            List<CMSSignedData> signatures = new ArrayList<>();
1✔
644
            for (CMSSignedData signature : signable.getSignatures()) {
1✔
645
                SignerInformation signerInformation = signature.getSignerInfos().iterator().next();
1✔
646
                SignerId signerId = signerInformation.getSID();
1✔
647
                X509CertificateHolder certificate = (X509CertificateHolder) signature.getCertificates().getMatches(signerId).iterator().next();
1✔
648

649
                String digestAlgorithmName = new DefaultAlgorithmNameFinder().getAlgorithmName(signerInformation.getDigestAlgorithmID()); 
1✔
650
                String keyAlgorithmName = new DefaultAlgorithmNameFinder().getAlgorithmName(new ASN1ObjectIdentifier(signerInformation.getEncryptionAlgOID()));
1✔
651
                String name = digestAlgorithmName + "/" + keyAlgorithmName + " signature from '" + certificate.getSubject() + "'";
1✔
652

653
                if (SignatureUtils.isTimestamped(signature) && !replace) {
1✔
654
                    log.fine(name + " already timestamped");
1✔
655
                    signatures.add(signature);
1✔
656
                    continue;
1✔
657
                }
658

659
                boolean expired = certificate.getNotAfter().before(new Date());
1✔
660
                if (expired) {
1✔
661
                    log.fine(name + " is expired, skipping");
×
662
                    signatures.add(signature);
×
663
                    continue;
×
664
                }
665

666
                log.info("Adding timestamp to " + name);
1✔
667
                signature = SignatureUtils.removeTimestamp(signature);
1✔
668
                signature = timestamper.timestamp(digestAlgorithm, signature);
1✔
669

670
                signatures.add(signature);
1✔
671
            }
1✔
672

673
            signable.setSignatures(signatures);
1✔
674
            signable.save();
1✔
675
        } catch (IOException | CMSException e) {
×
676
            throw new SignerException("Couldn't timestamp " + file, e);
×
677
        }
1✔
678
    }
1✔
679

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

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

708
                public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
709
                }
×
710
            });
711

712
            if (proxyUser != null && !proxyUser.isEmpty() && proxyPassword != null) {
1✔
713
                Authenticator.setDefault(new Authenticator() {
1✔
714
                    protected PasswordAuthentication getPasswordAuthentication() {
715
                        return new PasswordAuthentication(proxyUser, proxyPassword.toCharArray());
1✔
716
                    }
717
                });
718
            }
719
        }
720
    }
1✔
721
}
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