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

ebourg / jsign / #403

07 Jul 2026 08:25AM UTC coverage: 80.687% (-0.001%) from 80.688%
#403

push

ebourg
Retry loading PKCS#11 keystores if the token is not ready (#345)

5 of 6 new or added lines in 1 file covered. (83.33%)

5072 of 6286 relevant lines covered (80.69%)

0.81 hits per line

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

84.97
/jsign-crypto/src/main/java/net/jsign/KeyStoreType.java
1
/*
2
 * Copyright 2023 Emmanuel Bourg
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.FileInputStream;
21
import java.io.IOException;
22
import java.net.UnknownServiceException;
23
import java.nio.ByteBuffer;
24
import java.security.KeyStore;
25
import java.security.KeyStoreException;
26
import java.security.PrivateKey;
27
import java.security.Provider;
28
import java.security.Security;
29
import java.security.cert.Certificate;
30
import java.security.cert.CertificateException;
31
import java.util.Collections;
32
import java.util.LinkedHashSet;
33
import java.util.Set;
34
import java.util.function.Function;
35

36
import javax.smartcardio.CardException;
37

38
import net.jsign.jca.AmazonCredentials;
39
import net.jsign.jca.AmazonSigningService;
40
import net.jsign.jca.AzureKeyVaultSigningService;
41
import net.jsign.jca.AzureTrustedSigningService;
42
import net.jsign.jca.CryptoCertumCardSigningService;
43
import net.jsign.jca.DigiCertOneSigningService;
44
import net.jsign.jca.CodeSignSecureCredentials;
45
import net.jsign.jca.CodeSignSecureSigningService;
46
import net.jsign.jca.ESignerSigningService;
47
import net.jsign.jca.GaraSignCredentials;
48
import net.jsign.jca.GaraSignSigningService;
49
import net.jsign.jca.GoogleCloudSigningService;
50
import net.jsign.jca.HashiCorpVaultSigningService;
51
import net.jsign.jca.OpenPGPCardSigningService;
52
import net.jsign.jca.OracleCloudCredentials;
53
import net.jsign.jca.OracleCloudSigningService;
54
import net.jsign.jca.PIVCardSigningService;
55
import net.jsign.jca.SignPathSigningService;
56
import net.jsign.jca.SignServerCredentials;
57
import net.jsign.jca.SignServerSigningService;
58
import net.jsign.jca.SigningServiceJcaProvider;
59

60
/**
61
 * Type of a keystore.
62
 *
63
 * @since 5.0
64
 */
65
public enum KeyStoreType {
1✔
66

67
    /** Not a keystore, a private key file and a certificate file are provided separately and assembled into an in-memory keystore */
68
    NONE(true, false) {
1✔
69
        @Override
70
        void validate(KeyStoreBuilder params) {
71
            if (params.keyfile() == null) {
1✔
72
                throw new IllegalArgumentException("keyfile " + params.parameterName() + " must be set");
1✔
73
            }
74
            if (!params.keyfile().exists()) {
1✔
75
                throw new IllegalArgumentException("The keyfile " + params.keyfile() + " couldn't be found");
1✔
76
            }
77
            if (params.certfile() == null) {
1✔
78
                throw new IllegalArgumentException("certfile " + params.parameterName() + " must be set");
1✔
79
            }
80
            if (!params.certfile().exists()) {
1✔
81
                throw new IllegalArgumentException("The certfile " + params.certfile() + " couldn't be found");
1✔
82
            }
83
        }
1✔
84

85
        @Override
86
        KeyStore getKeystore(KeyStoreBuilder params, Provider provider) throws KeyStoreException {
87
            // load the certificate chain
88
            Certificate[] chain;
89
            try {
90
                chain = CertificateUtils.loadCertificateChain(params.certfile());
1✔
91
            } catch (Exception e) {
1✔
92
                throw new KeyStoreException("Failed to load the certificate from " + params.certfile(), e);
1✔
93
            }
1✔
94

95
            // load the private key
96
            PrivateKey privateKey;
97
            try {
98
                privateKey = PrivateKeyUtils.load(params.keyfile(), params.keypass() != null ? params.keypass() : params.storepass());
1✔
99
            } catch (Exception e) {
1✔
100
                throw new KeyStoreException("Failed to load the private key from " + params.keyfile(), e);
1✔
101
            }
1✔
102

103
            // build the in-memory keystore
104
            KeyStore ks = KeyStore.getInstance("JKS");
1✔
105
            try {
106
                ks.load(null, null);
1✔
107
                String keypass = params.keypass();
1✔
108
                ks.setKeyEntry("jsign", privateKey, keypass != null ? keypass.toCharArray() : new char[0], chain);
1✔
109
            } catch (Exception e) {
×
110
                throw new KeyStoreException(e);
×
111
            }
1✔
112

113
            return ks;
1✔
114
        }
115
    },
116

117
    /** Java keystore */
118
    JKS(true, false) {
1✔
119
        @Override
120
        void validate(KeyStoreBuilder params) {
121
            if (params.keystore() == null) {
1✔
122
                throw new IllegalArgumentException("keystore " + params.parameterName() + " must be set");
1✔
123
            }
124
            if (!params.createFile(params.keystore()).exists()) {
1✔
125
                throw new IllegalArgumentException("The keystore " + params.keystore() + " couldn't be found");
1✔
126
            }
127
            if (params.keypass() == null && params.storepass() != null) {
1✔
128
                // reuse the storepass as the keypass
129
                params.keypass(params.storepass());
1✔
130
            }
131
        }
1✔
132
    },
133

134
    /** JCE keystore */
135
    JCEKS(true, false) {
1✔
136
        @Override
137
        void validate(KeyStoreBuilder params) {
138
            if (params.keystore() == null) {
1✔
139
                throw new IllegalArgumentException("keystore " + params.parameterName() + " must be set");
1✔
140
            }
141
            if (!params.createFile(params.keystore()).exists()) {
1✔
142
                throw new IllegalArgumentException("The keystore " + params.keystore() + " couldn't be found");
1✔
143
            }
144
            if (params.keypass() == null && params.storepass() != null) {
1✔
145
                // reuse the storepass as the keypass
146
                params.keypass(params.storepass());
1✔
147
            }
148
        }
1✔
149
    },
150

151
    /** PKCS#12 keystore */
152
    PKCS12(true, false) {
1✔
153
        @Override
154
        void validate(KeyStoreBuilder params) {
155
            if (params.keystore() == null) {
1✔
156
                throw new IllegalArgumentException("keystore " + params.parameterName() + " must be set");
1✔
157
            }
158
            if (!params.createFile(params.keystore()).exists()) {
1✔
159
                throw new IllegalArgumentException("The keystore " + params.keystore() + " couldn't be found");
1✔
160
            }
161
            if (params.keypass() == null && params.storepass() != null) {
1✔
162
                // reuse the storepass as the keypass
163
                params.keypass(params.storepass());
1✔
164
            }
165
        }
1✔
166
    },
167

168
    /**
169
     * PKCS#11 hardware token. The keystore parameter specifies either the name of the provider defined
170
     * in <code>jre/lib/security/java.security</code> or the path to the
171
     * <a href="https://docs.oracle.com/javase/8/docs/technotes/guides/security/p11guide.html#Config">SunPKCS11 configuration file</a>.
172
     */
173
    PKCS11(false, true) {
1✔
174
        @Override
175
        void validate(KeyStoreBuilder params) {
176
            if (params.keystore() == null) {
1✔
177
                throw new IllegalArgumentException("keystore " + params.parameterName() + " must be set");
1✔
178
            }
179
        }
1✔
180

181
        @Override
182
        Provider getProvider(KeyStoreBuilder params) {
183
            // the keystore parameter is either the provider name or the SunPKCS11 configuration file
184
            if (params.createFile(params.keystore()).exists()) {
1✔
185
                return ProviderUtils.createSunPKCS11Provider(params.keystore());
×
186
            } else if (params.keystore().startsWith("SunPKCS11-")) {
1✔
187
                Provider provider = Security.getProvider(params.keystore());
1✔
188
                if (provider == null) {
1✔
189
                    throw new IllegalArgumentException("Security provider " + params.keystore() + " not found");
1✔
190
                }
191
                return provider;
×
192
            } else {
193
                throw new IllegalArgumentException("keystore " + params.parameterName() + " should either refer to the SunPKCS11 configuration file or to the name of the provider configured in jre/lib/security/java.security");
1✔
194
            }
195
        }
196
    },
197

198
    /**
199
     * OpenPGP card. OpenPGP cards contain up to 3 keys, one for signing, one for encryption, and one for authentication.
200
     * All of them can be used for code signing (except encryption keys based on an elliptic curve). The alias
201
     * to select the key is either, <code>SIGNATURE</code>, <code>ENCRYPTION</code> or <code>AUTHENTICATION</code>.
202
     * This keystore can be used with a Nitrokey (non-HSM models) or a Yubikey. If multiple devices are connected,
203
     * the keystore parameter can be used to specify the name of the one to use. This keystore type doesn't require
204
     * any external library to be installed.
205
     */
206
    OPENPGP(false, false) {
1✔
207
        @Override
208
        void validate(KeyStoreBuilder params) {
209
            if (params.storepass() == null) {
1✔
210
                throw new IllegalArgumentException("storepass " + params.parameterName() + " must specify the PIN");
1✔
211
            }
212
        }
×
213

214
        @Override
215
        Provider getProvider(KeyStoreBuilder params) {
216
            try {
217
                return new SigningServiceJcaProvider(new OpenPGPCardSigningService(params.keystore(), params.storepass(), params.certfile() != null ? getCertificateStore(params) : null));
×
218
            } catch (CardException e) {
×
219
                throw new IllegalStateException("Failed to initialize the OpenPGP card", e);
×
220
            }
221
        }
222
    },
223

224
    /**
225
     * OpenSC supported smart card.
226
     * This keystore requires the installation of <a href="https://github.com/OpenSC/OpenSC">OpenSC</a>.
227
     * If multiple devices are connected, the keystore parameter can be used to specify the name of the one to use.
228
     */
229
    OPENSC(false, true) {
1✔
230
        @Override
231
        Provider getProvider(KeyStoreBuilder params) {
232
            return OpenSC.getProvider(params.keystore());
×
233
        }
234
    },
235

236
    /**
237
     * PIV card. PIV cards contain up to 24 private keys and certificates. The alias to select the key is either,
238
     * <code>AUTHENTICATION</code>, <code>SIGNATURE</code>, <code>KEY_MANAGEMENT</code>, <code>CARD_AUTHENTICATION</code>,
239
     * or <code>RETIRED&lt;1-20&gt;</code>. Slot numbers are also accepted (for example <code>9c</code> for the digital
240
     * signature key). If multiple devices are connected, the keystore parameter can be used to specify the name
241
     * of the one to use. This keystore type doesn't require any external library to be installed.
242
     */
243
    PIV(false, false) {
1✔
244
        @Override
245
        void validate(KeyStoreBuilder params) {
246
            if (params.storepass() == null) {
1✔
247
                throw new IllegalArgumentException("storepass " + params.parameterName() + " must specify the PIN");
1✔
248
            }
249
        }
×
250

251
        @Override
252
        Provider getProvider(KeyStoreBuilder params) {
253
            try {
254
                return new SigningServiceJcaProvider(new PIVCardSigningService(params.keystore(), params.storepass(), params.certfile() != null ? getCertificateStore(params) : null));
×
255
            } catch (CardException e) {
×
256
                throw new IllegalStateException("Failed to initialize the PIV card", e);
×
257
            }
258
        }
259
    },
260

261
    /**
262
     * Nitrokey HSM. This keystore requires the installation of <a href="https://github.com/OpenSC/OpenSC">OpenSC</a>.
263
     * Other Nitrokeys based on the OpenPGP card standard are also supported with this storetype, but an X.509
264
     * certificate must be imported into the Nitrokey (using the gnupg writecert command). Keys without certificates
265
     * are ignored. Otherwise the {@link #OPENPGP} type should be used.
266
     */
267
    NITROKEY(false, true) {
1✔
268
        @Override
269
        Provider getProvider(KeyStoreBuilder params) {
270
            return OpenSC.getProvider(params.keystore() != null ? params.keystore() : "Nitrokey");
×
271
        }
272
    },
273

274
    /**
275
     * YubiKey PIV. This keystore requires the ykcs11 library from the <a href="https://developers.yubico.com/yubico-piv-tool/">Yubico PIV Tool</a>
276
     * to be installed at the default location. On Windows, the path to the library must be specified in the
277
     * <code>PATH</code> environment variable.
278
     */
279
    YUBIKEY(false, true) {
1✔
280
        @Override
281
        Provider getProvider(KeyStoreBuilder params) {
282
            return YubiKey.getProvider();
×
283
        }
284

285
        @Override
286
        Set<String> getAliases(KeyStore keystore) throws KeyStoreException {
287
            Set<String> aliases = super.getAliases(keystore);
×
288
            // the attestation certificate is never used for signing
289
            aliases.remove("X.509 Certificate for PIV Attestation");
×
290
            return aliases;
×
291
        }
292
    },
293

294
    /**
295
     * AWS Key Management Service (KMS). AWS KMS stores only the private key, the certificate must be provided
296
     * separately. The keystore parameter references the AWS region.
297
     *
298
     * <p>The AWS access key, secret key, and optionally the session token, are concatenated and used as
299
     * the storepass parameter; if the latter is not provided, Jsign attempts to fetch the credentials from
300
     * the environment variables (<code>AWS_ACCESS_KEY_ID</code>, <code>AWS_SECRET_ACCESS_KEY</code> and
301
     * <code>AWS_SESSION_TOKEN</code>), from the ECS container credentials endpoint, or from the IMDSv2
302
     * service when running on an AWS EC2 instance.</p>
303
     *
304
     * <p>In any case, the credentials must allow the following actions: <code>kms:ListKeys</code>,
305
     * <code>kms:DescribeKey</code> and <code>kms:Sign</code>.</p>
306
     * */
307
    AWS(false, false) {
1✔
308
        @Override
309
        void validate(KeyStoreBuilder params) {
310
            if (params.keystore() == null) {
1✔
311
                throw new IllegalArgumentException("keystore " + params.parameterName() + " must specify the AWS region");
1✔
312
            }
313
            if (params.certfile() == null) {
1✔
314
                throw new IllegalArgumentException("certfile " + params.parameterName() + " must be set");
1✔
315
            }
316
        }
1✔
317

318
        @Override
319
        Provider getProvider(KeyStoreBuilder params) {
320
            AmazonCredentials credentials;
321
            if (params.storepass() != null) {
1✔
322
                credentials = AmazonCredentials.parse(params.storepass());
1✔
323
            } else {
324
                try {
325
                    credentials = AmazonCredentials.getDefault();
×
326
                } catch (UnknownServiceException e) {
1✔
327
                    throw new IllegalArgumentException("storepass " + params.parameterName()
1✔
328
                            + " must specify the AWS credentials: <accessKey>|<secretKey>[|<sessionToken>]"
329
                            + ", when not running from ECS or an EC2 instance", e);
330
                } catch (IOException e) {
×
331
                    throw new RuntimeException("Failed fetching temporary credentials from ECS and IMDSv2 services", e);
×
332
                }
×
333
            }
334

335
            return new SigningServiceJcaProvider(new AmazonSigningService(params.keystore(), credentials, getCertificateStore(params)));
1✔
336
        }
337
    },
338

339
    /**
340
     * Azure Key Vault. The keystore parameter specifies the name of the key vault, either the short name
341
     * (e.g. <code>myvault</code>), or the full URL (e.g. <code>https://myvault.vault.azure.net</code>).
342
     * The Azure API access token is used as the keystore password.
343
     */
344
    AZUREKEYVAULT(false, false) {
1✔
345
        @Override
346
        void validate(KeyStoreBuilder params) {
347
            if (params.keystore() == null) {
1✔
348
                throw new IllegalArgumentException("keystore " + params.parameterName() + " must specify the Azure vault name");
1✔
349
            }
350
            if (params.storepass() == null) {
1✔
351
                throw new IllegalArgumentException("storepass " + params.parameterName() + " must specify the Azure API access token");
1✔
352
            }
353
        }
1✔
354

355
        @Override
356
        Provider getProvider(KeyStoreBuilder params) {
357
            return new SigningServiceJcaProvider(new AzureKeyVaultSigningService(params.keystore(), params.storepass()));
1✔
358
        }
359
    },
360

361
    /**
362
     * DigiCert ONE. Certificates and keys stored in the DigiCert ONE Secure Software Manager can be used directly
363
     * without installing the DigiCert client tools. The API key, the PKCS#12 keystore holding the client certificate
364
     * and its password are combined to form the storepass parameter: <code>&lt;api-key&gt;|&lt;keystore&gt;|&lt;password&gt;</code>.
365
     */
366
    DIGICERTONE(false, false) {
1✔
367
        @Override
368
        void validate(KeyStoreBuilder params) {
369
            if (params.storepass() == null || params.storepass().split("\\|").length != 3) {
1✔
370
                throw new IllegalArgumentException("storepass " + params.parameterName() + " must specify the DigiCert ONE API key and the client certificate: <apikey>|<keystore>|<password>");
1✔
371
            }
372
        }
1✔
373

374
        @Override
375
        Provider getProvider(KeyStoreBuilder params) {
376
            String[] elements = params.storepass().split("\\|");
1✔
377
            return new SigningServiceJcaProvider(new DigiCertOneSigningService(params.keystore(), elements[0], params.createFile(elements[1]), elements[2]));
1✔
378
        }
379
    },
380

381
    /**
382
     * SSL.com eSigner. The SSL.com username and password are used as the keystore password (<code>&lt;username&gt;|&lt;password&gt;</code>),
383
     * and the base64 encoded TOTP secret is used as the key password.
384
     */
385
    ESIGNER(false, false) {
1✔
386
        @Override
387
        void validate(KeyStoreBuilder params) {
388
            if (params.storepass() == null || !params.storepass().contains("|")) {
1✔
389
                throw new IllegalArgumentException("storepass " + params.parameterName() + " must specify the SSL.com username and password: <username>|<password>");
1✔
390
            }
391
        }
1✔
392

393
        @Override
394
        Provider getProvider(KeyStoreBuilder params) {
395
            String[] elements = params.storepass().split("\\|", 2);
1✔
396
            String endpoint = params.keystore() != null ? params.keystore() : "https://cs.ssl.com";
1✔
397
            try {
398
                return new SigningServiceJcaProvider(new ESignerSigningService(endpoint, elements[0], elements[1]));
1✔
399
            } catch (IOException e) {
1✔
400
                throw new IllegalStateException("Authentication failed with SSL.com", e);
1✔
401
            }
402
        }
403
    },
404

405
    /**
406
     * Google Cloud KMS. Google Cloud KMS stores only the private key, the certificate must be provided separately.
407
     * The keystore parameter references the path of the keyring. The alias can specify either the full path of the key,
408
     * or only the short name. If the version is omitted the most recent one will be picked automatically.
409
     */
410
    GOOGLECLOUD(false, false) {
1✔
411
        @Override
412
        void validate(KeyStoreBuilder params) {
413
            if (params.keystore() == null) {
1✔
414
                throw new IllegalArgumentException("keystore " + params.parameterName() + " must specify the Google Cloud keyring");
1✔
415
            }
416
            if (!params.keystore().matches("projects/[^/]+/locations/[^/]+/keyRings/[^/]+")) {
1✔
417
                throw new IllegalArgumentException("keystore " + params.parameterName() + " must specify the path of the keyring (projects/{projectName}/locations/{location}/keyRings/{keyringName})");
1✔
418
            }
419
            if (params.storepass() == null) {
1✔
420
                throw new IllegalArgumentException("storepass " + params.parameterName() + " must specify the Google Cloud API access token");
1✔
421
            }
422
            if (params.certfile() == null) {
1✔
423
                throw new IllegalArgumentException("certfile " + params.parameterName() + " must be set");
1✔
424
            }
425
        }
1✔
426

427
        @Override
428
        Provider getProvider(KeyStoreBuilder params) {
429
            return new SigningServiceJcaProvider(new GoogleCloudSigningService(params.keystore(), params.storepass(), getCertificateStore(params)));
1✔
430
        }
431
    },
432

433
    /**
434
     * HashiCorp Vault secrets engine (Transit or GCPKMS). The certificate must be provided separately. The keystore
435
     * parameter references the URL of the HashiCorp Vault secrets engine (<code>https://vault.example.com/v1/gcpkms</code>).
436
     * The alias parameter specifies the name of the key in Vault. For the Google Cloud KMS secrets engine, the version
437
     * of the Google Cloud key is appended to the key name, separated by a colon character. (<code>mykey:1</code>).
438
     */
439
    HASHICORPVAULT(false, false) {
1✔
440
        @Override
441
        void validate(KeyStoreBuilder params) {
442
            if (params.keystore() == null) {
1✔
443
                throw new IllegalArgumentException("keystore " + params.parameterName() + " must specify the HashiCorp Vault secrets engine URL");
1✔
444
            }
445
            if (params.storepass() == null) {
1✔
446
                throw new IllegalArgumentException("storepass " + params.parameterName() + " must specify the HashiCorp Vault token");
1✔
447
            }
448
            if (params.certfile() == null) {
1✔
449
                throw new IllegalArgumentException("certfile " + params.parameterName() + " must be set");
1✔
450
            }
451
        }
1✔
452

453
        @Override
454
        Provider getProvider(KeyStoreBuilder params) {
455
            return new SigningServiceJcaProvider(new HashiCorpVaultSigningService(params.keystore(), params.storepass(), getCertificateStore(params)));
1✔
456
        }
457
    },
458

459
    /**
460
     * SafeNet eToken
461
     * This keystore requires the installation of the SafeNet Authentication Client.
462
     */
463
    ETOKEN(false, true) {
1✔
464
        @Override
465
        Provider getProvider(KeyStoreBuilder params) {
466
            return SafeNetEToken.getProvider(params.keystore());
×
467
        }
468
    },
469

470
    /**
471
     * Oracle Cloud Infrastructure Key Management Service. This keystore requires the <a href="https://docs.oracle.com/en-us/iaas/Content/API/Concepts/sdkconfig.htm">configuration file</a>
472
     * or the <a href="https://docs.oracle.com/en-us/iaas/Content/API/SDKDocs/clienvironmentvariables.htm">environment
473
     * variables</a> used by the OCI CLI. The storepass parameter specifies the path to the configuration file
474
     * (<code>~/.oci/config</code> by default). If the configuration file contains multiple profiles, the name of the
475
     * non-default profile to use is appended to the storepass (for example <code>~/.oci/config|PROFILE</code>).
476
     * The keypass parameter may be used to specify the passphrase of the key file used for signing the requests to
477
     * the OCI API if it isn't set in the configuration file.
478
     *
479
     * <p>The certificate must be provided separately using the certfile parameter. The alias specifies the OCID
480
     * of the key.</p>
481
     */
482
    ORACLECLOUD(false, false) {
1✔
483
        @Override
484
        void validate(KeyStoreBuilder params) {
485
            if (params.certfile() == null) {
1✔
486
                throw new IllegalArgumentException("certfile " + params.parameterName() + " must be set");
1✔
487
            }
488
        }
1✔
489

490
        @Override
491
        Provider getProvider(KeyStoreBuilder params) {
492
            OracleCloudCredentials credentials = new OracleCloudCredentials();
1✔
493
            try {
494
                File config = null;
1✔
495
                String profile = null;
1✔
496
                if (params.storepass() != null) {
1✔
497
                    String[] elements = params.storepass().split("\\|", 2);
1✔
498
                    config = new File(elements[0]);
1✔
499
                    if (elements.length > 1) {
1✔
500
                        profile = elements[1];
1✔
501
                    }
502
                }
503
                credentials.load(config, profile);
1✔
504
                credentials.loadFromEnvironment();
1✔
505
                if (params.keypass() != null) {
1✔
506
                    credentials.setPassphrase(params.keypass());
×
507
                }
508
            } catch (IOException e) {
×
509
                throw new RuntimeException("An error occurred while fetching the Oracle Cloud credentials", e);
×
510
            }
1✔
511
            return new SigningServiceJcaProvider(new OracleCloudSigningService(credentials, getCertificateStore(params)));
1✔
512
        }
513
    },
514

515
    /**
516
     * Azure Artifact Signing Service. The keystore parameter specifies the API endpoint (for example
517
     * <code>weu.codesigning.azure.net</code>). The Azure API access token is used as the keystore password,
518
     * it can be obtained using the Azure CLI with:
519
     *
520
     * <pre>  az account get-access-token --resource https://codesigning.azure.net</pre>
521
     */
522
    TRUSTEDSIGNING(false, false) {
1✔
523
        @Override
524
        void validate(KeyStoreBuilder params) {
525
            if (params.keystore() == null) {
1✔
526
                throw new IllegalArgumentException("keystore " + params.parameterName() + " must specify the Azure endpoint (<region>.codesigning.azure.net)");
1✔
527
            }
528
            if (params.storepass() == null) {
1✔
529
                throw new IllegalArgumentException("storepass " + params.parameterName() + " must specify the Azure API access token");
1✔
530
            }
531
        }
1✔
532

533
        @Override
534
        Provider getProvider(KeyStoreBuilder params) {
535
            return new SigningServiceJcaProvider(new AzureTrustedSigningService(params.keystore(), params.storepass()));
1✔
536
        }
537
    },
538

539
    GARASIGN(false, false) {
1✔
540
        @Override
541
        void validate(KeyStoreBuilder params) {
542
            if (params.storepass() == null || params.storepass().split("\\|").length > 3) {
1✔
543
                throw new IllegalArgumentException("storepass " + params.parameterName() + " must specify the GaraSign username/password and/or the path to the keystore containing the TLS client certificate: <username>|<password>, <certificate>, or <username>|<password>|<certificate>");
1✔
544
            }
545
        }
1✔
546

547
        @Override
548
        Provider getProvider(KeyStoreBuilder params) {
549
            String[] elements = params.storepass().split("\\|");
1✔
550
            String username = null;
1✔
551
            String password = null;
1✔
552
            String certificate = null;
1✔
553
            if (elements.length == 1) {
1✔
554
                certificate = elements[0];
1✔
555
            } else if (elements.length == 2) {
1✔
556
                username = elements[0];
1✔
557
                password = elements[1];
1✔
558
            } else if (elements.length == 3) {
1✔
559
                username = elements[0];
1✔
560
                password = elements[1];
1✔
561
                certificate = elements[2];
1✔
562
            }
563

564
            GaraSignCredentials credentials = new GaraSignCredentials(username, password, certificate, params.keypass());
1✔
565
            return new SigningServiceJcaProvider(new GaraSignSigningService(params.keystore(), credentials));
1✔
566
        }
567
    },
568

569
    /**
570
     * Keyfactor SignServer. This keystore requires a Plain Signer worker, preferably configured to allow client-side
571
     * hashing (with the properties <code>CLIENTSIDEHASHING</code> or <code>ALLOW_CLIENTSIDEHASHING_OVERRIDE</code> set
572
     * to true), and the <code>SIGNATUREALGORITHM</code> property set to <code>NONEwithRSA</code> or <code>NONEwithECDSA</code>.
573
     * The worker may be configured with server-side hashing (i.e. with <code>CLIENTSIDEHASHING</code> and
574
     * <code>ALLOW_CLIENTSIDEHASHING_OVERRIDE</code> set to <code>false</code>, and a proper <code>SIGNATUREALGORITHM</code>
575
     * set), in this case the worker name or id in the alias has to be suffixed with <code>|serverside</code>.
576
     *
577
     * <p>If necessary the authentication is performed by specifying the username/password or the TLS client certificate
578
     * in the storepass parameter. If the TLS client certificate is stored in a password protected keystore, the password
579
     * is specified in the keypass parameter. The keystore parameter references the URL of the SignServer REST API. The
580
     * alias parameter specifies the id or the name of the worker.</p>
581
     */
582
    SIGNSERVER(false, false) {
1✔
583
        @Override
584
        void validate(KeyStoreBuilder params) {
585
            if (params.keystore() == null) {
1✔
586
                throw new IllegalArgumentException("keystore " + params.parameterName() + " must specify the SignServer API endpoint (e.g. https://example.com/signserver/)");
1✔
587
            }
588
            if (params.storepass() != null && params.storepass().split("\\|").length > 2) {
1✔
589
                throw new IllegalArgumentException("storepass " + params.parameterName() + " must specify the SignServer username/password or the path to the keystore containing the TLS client certificate: <username>|<password> or <certificate>");
1✔
590
            }
591
        }
1✔
592

593
        @Override
594
        Provider getProvider(KeyStoreBuilder params) {
595
            String username = null;
1✔
596
            String password = null;
1✔
597
            String certificate = null;
1✔
598
            if (params.storepass() != null) {
1✔
599
                String[] elements = params.storepass().split("\\|");
1✔
600
                if (elements.length == 1) {
1✔
601
                    certificate = elements[0];
×
602
                } else if (elements.length == 2) {
1✔
603
                    username = elements[0];
1✔
604
                    password = elements[1];
1✔
605
                }
606
            }
607

608
            SignServerCredentials credentials = new SignServerCredentials(username, password, certificate, params.keypass());
1✔
609
            return new SigningServiceJcaProvider(new SignServerSigningService(params.keystore(), credentials));
1✔
610
        }
611
    },
612

613
    /**
614
     * SignPath. The keystore parameter specifies the organization, and the storepass parameter the API access token.
615
     * The alias parameter is the concatenation of the project slug and the signing policy slug, separated by a slash
616
     * character (e.g. <code>myproject/mypolicy</code>).
617
     */
618
    SIGNPATH(false, false) {
1✔
619
        @Override
620
        void validate(KeyStoreBuilder params) {
621
            if (params.keystore() == null) {
1✔
622
                throw new IllegalArgumentException("keystore " + params.parameterName() + " must specify the SignPath organization id (e.g. eacd4b78-6038-4450-9eec-4acd1c7ba6f1)");
1✔
623
            }
624
            if (params.storepass() == null) {
1✔
625
                throw new IllegalArgumentException("storepass " + params.parameterName() + " must specify the SignPath API access token");
1✔
626
            }
627
        }
1✔
628

629
        @Override
630
        Provider getProvider(KeyStoreBuilder params) {
631
            return new SigningServiceJcaProvider(new SignPathSigningService(params.keystore(), params.storepass()));
1✔
632
        }
633
    },
634

635
    /**
636
     * CryptoCertum card. No PKCS#11 module is required, Jsign communicates directly with the card and uses the keys
637
     * and certificates stored in the common file (the secure profile containing eIDAS certificates is not supported).
638
     */
639
    CRYPTOCERTUM(false, false) {
1✔
640
        @Override
641
        void validate(KeyStoreBuilder params) {
642
            if (params.storepass() == null) {
×
643
                throw new IllegalArgumentException("storepass " + params.parameterName() + " must specify the PIN");
×
644
            }
645
        }
×
646

647
        @Override
648
        Provider getProvider(KeyStoreBuilder params) {
649
            try {
650
                return new SigningServiceJcaProvider(new CryptoCertumCardSigningService(params.storepass()));
×
651
            } catch (CardException e) {
×
652
                throw new IllegalStateException("Failed to initialize the CryptoCertum card", e);
×
653
            }
654
        }
655
    },
656

657
    /**
658
     * Encryption Consulting CodeSign Secure. The keystore parameter specifies the API endpoint. The storepass parameter
659
     * is the concatenation of the CodeSign Secure username, password and the path to the PKCS#12 keystore containing
660
     * the TLS client certificate, separated by a pipe character. If the TLS client certificate is stored in a password
661
     * protected keystore, the password is specified in the keypass parameter.
662
     */
663
    CODESIGNSECURE(false, false) {
1✔
664
        @Override
665
        void validate(KeyStoreBuilder params) {
666
            if (params.storepass() == null || params.storepass().split("\\|").length != 3) {
1✔
667
                throw new IllegalArgumentException("storepass " + params.parameterName() + " must specify the CodeSign Secure username, password and the path to the keystore containing the TLS client certificate: <username>|<password>|<certificate>");
1✔
668
            }
669
        }
1✔
670

671
        @Override
672
        Provider getProvider(KeyStoreBuilder params) {
673
            String[] elements = params.storepass().split("\\|");
1✔
674
            String username = elements[0];
1✔
675
            String password = elements[1];
1✔
676
            String certificate = elements[2];
1✔
677

678
            CodeSignSecureCredentials credentials = new CodeSignSecureCredentials(username, password, certificate, params.keypass());
1✔
679
            return new SigningServiceJcaProvider(new CodeSignSecureSigningService(params.keystore(), credentials));
1✔
680
        }
681
    };
682

683
    /** Tells if the keystore is contained in a local file */
684
    private final boolean fileBased;
685

686
    /** Tells if the keystore is actually a PKCS#11 keystore */
687
    private final boolean pkcs11;
688

689
    KeyStoreType(boolean fileBased, boolean pkcs11) {
1✔
690
        this.fileBased = fileBased;
1✔
691
        this.pkcs11 = pkcs11;
1✔
692
    }
1✔
693

694
    /**
695
     * Validates the keystore parameters.
696
     */
697
    void validate(KeyStoreBuilder params) throws IllegalArgumentException {
698
    }
×
699

700
    /**
701
     * Returns the security provider to use the keystore.
702
     */
703
    Provider getProvider(KeyStoreBuilder params) {
704
        return null;
1✔
705
    }
706

707
    /**
708
     * Builds the keystore and ensure it's loaded.
709
     */
710
    KeyStore getKeystore(KeyStoreBuilder params, Provider provider) throws KeyStoreException {
711
        KeyStore ks;
712
        try {
713
            KeyStoreType storetype = pkcs11 ? PKCS11 : this;
1✔
714
            if (provider != null) {
1✔
715
                ks = KeyStore.getInstance(storetype.name(), provider);
1✔
716
            } else {
717
                ks = KeyStore.getInstance(storetype.name());
1✔
718
            }
719
        } catch (KeyStoreException e) {
×
720
            throw new KeyStoreException("keystore type '" + name() + "' is not supported" + (provider != null ? " with security provider " + provider.getName() : ""), e);
×
721
        }
1✔
722

723
        try {
724
            try (FileInputStream in = fileBased ? new FileInputStream(params.createFile(params.keystore())) : null) {
1✔
725
                char[] password = params.storepass() != null ? params.storepass().toCharArray() : null;
1✔
726

727
                // multiple attempts to load the keystore for PKCS#11, as some tokens may not be ready immediately
728
                // after being initialized (see #345)
729
                int attempts = pkcs11 ? 20 : 1;
1✔
730
                while (attempts-- > 0) {
1✔
731
                    ks.load(in, password);
1✔
732
                    if (pkcs11 && ks.size() == 0 && attempts > 0) {
1✔
NEW
733
                        Thread.sleep(50);
×
734
                    }
735
                }
736
            }
737
        } catch (Exception e) {
1✔
738
            throw new KeyStoreException("Unable to load the " + name() + " keystore" + (params.keystore() != null ? " " + params.keystore() : ""), e);
1✔
739
        }
1✔
740

741
        return ks;
1✔
742
    }
743

744
    /**
745
     * Returns the aliases of the keystore available for signing.
746
     */
747
    Set<String> getAliases(KeyStore keystore) throws KeyStoreException {
748
        return new LinkedHashSet<>(Collections.list(keystore.aliases()));
1✔
749
    }
750

751
    /**
752
     * Guess the type of the keystore from the header or the extension of the file.
753
     *
754
     * @param path   the path to the keystore
755
     */
756
    static KeyStoreType of(File path) {
757
        // guess the type of the keystore from the header of the file
758
        if (path.exists()) {
1✔
759
            try (FileInputStream in = new FileInputStream(path)) {
1✔
760
                byte[] header = new byte[4];
1✔
761
                in.read(header);
1✔
762
                ByteBuffer buffer = ByteBuffer.wrap(header);
1✔
763
                if (buffer.get(0) == 0x30) {
1✔
764
                    return PKCS12;
1✔
765
                } else if ((buffer.getInt(0) & 0xFFFFFFFFL) == 0xCECECECEL) {
1✔
766
                    return JCEKS;
1✔
767
                } else if ((buffer.getInt(0) & 0xFFFFFFFFL) == 0xFEEDFEEDL) {
1✔
768
                    return JKS;
1✔
769
                }
770
            } catch (IOException e) {
1✔
771
                throw new RuntimeException("Unable to load the keystore " + path, e);
×
772
            }
1✔
773
        }
774

775
        // guess the type of the keystore from the extension of the file
776
        String filename = path.getName().toLowerCase();
1✔
777
        if (filename.endsWith(".p12") || filename.endsWith(".pfx")) {
1✔
778
            return PKCS12;
1✔
779
        } else if (filename.endsWith(".jceks")) {
1✔
780
            return JCEKS;
1✔
781
        } else if (filename.endsWith(".jks")) {
1✔
782
            return JKS;
1✔
783
        } else {
784
            return null;
1✔
785
        }
786
    }
787

788
    private static Function<String, Certificate[]> getCertificateStore(KeyStoreBuilder params) {
789
        return alias -> {
1✔
790
            if (alias == null || alias.isEmpty()) {
×
791
                return null;
×
792
            }
793

794
            try {
795
                return CertificateUtils.loadCertificateChain(params.certfile());
×
796
            } catch (IOException | CertificateException e) {
×
797
                throw new RuntimeException("Failed to load the certificate from " + params.certfile(), e);
×
798
            }
799
        };
800
    }
801
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc