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

ebourg / jsign / #386

24 Oct 2025 09:55AM UTC coverage: 80.64% (-2.4%) from 83.057%
#386

push

ebourg
CI build with Java 25

4965 of 6157 relevant lines covered (80.64%)

0.81 hits per line

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

84.5
/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.ESignerSigningService;
45
import net.jsign.jca.GaraSignCredentials;
46
import net.jsign.jca.GaraSignSigningService;
47
import net.jsign.jca.GoogleCloudSigningService;
48
import net.jsign.jca.HashiCorpVaultSigningService;
49
import net.jsign.jca.OpenPGPCardSigningService;
50
import net.jsign.jca.OracleCloudCredentials;
51
import net.jsign.jca.OracleCloudSigningService;
52
import net.jsign.jca.PIVCardSigningService;
53
import net.jsign.jca.SignPathSigningService;
54
import net.jsign.jca.SignServerCredentials;
55
import net.jsign.jca.SignServerSigningService;
56
import net.jsign.jca.SigningServiceJcaProvider;
57

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

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

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

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

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

111
            return ks;
1✔
112
        }
113
    },
114

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

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

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

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

179
        @Override
180
        Provider getProvider(KeyStoreBuilder params) {
181
            // the keystore parameter is either the provider name or the SunPKCS11 configuration file
182
            if (params.createFile(params.keystore()).exists()) {
1✔
183
                return ProviderUtils.createSunPKCS11Provider(params.keystore());
×
184
            } else if (params.keystore().startsWith("SunPKCS11-")) {
1✔
185
                Provider provider = Security.getProvider(params.keystore());
1✔
186
                if (provider == null) {
1✔
187
                    throw new IllegalArgumentException("Security provider " + params.keystore() + " not found");
1✔
188
                }
189
                return provider;
×
190
            } else {
191
                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✔
192
            }
193
        }
194
    },
195

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

537
    GARASIGN(false, false) {
1✔
538
        @Override
539
        void validate(KeyStoreBuilder params) {
540
            if (params.storepass() == null || params.storepass().split("\\|").length > 3) {
1✔
541
                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✔
542
            }
543
        }
1✔
544

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

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

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

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

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

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

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

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

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

655
    /** Tells if the keystore is contained in a local file */
656
    private final boolean fileBased;
657

658
    /** Tells if the keystore is actually a PKCS#11 keystore */
659
    private final boolean pkcs11;
660

661
    KeyStoreType(boolean fileBased, boolean pkcs11) {
1✔
662
        this.fileBased = fileBased;
1✔
663
        this.pkcs11 = pkcs11;
1✔
664
    }
1✔
665

666
    /**
667
     * Validates the keystore parameters.
668
     */
669
    void validate(KeyStoreBuilder params) throws IllegalArgumentException {
670
    }
×
671

672
    /**
673
     * Returns the security provider to use the keystore.
674
     */
675
    Provider getProvider(KeyStoreBuilder params) {
676
        return null;
1✔
677
    }
678

679
    /**
680
     * Build the keystore.
681
     */
682
    KeyStore getKeystore(KeyStoreBuilder params, Provider provider) throws KeyStoreException {
683
        KeyStore ks;
684
        try {
685
            KeyStoreType storetype = pkcs11 ? PKCS11 : this;
1✔
686
            if (provider != null) {
1✔
687
                ks = KeyStore.getInstance(storetype.name(), provider);
1✔
688
            } else {
689
                ks = KeyStore.getInstance(storetype.name());
1✔
690
            }
691
        } catch (KeyStoreException e) {
×
692
            throw new KeyStoreException("keystore type '" + name() + "' is not supported" + (provider != null ? " with security provider " + provider.getName() : ""), e);
×
693
        }
1✔
694

695
        try {
696
            try (FileInputStream in = fileBased ? new FileInputStream(params.createFile(params.keystore())) : null) {
1✔
697
                ks.load(in, params.storepass() != null ? params.storepass().toCharArray() : null);
1✔
698
            }
699
        } catch (Exception e) {
1✔
700
            throw new KeyStoreException("Unable to load the " + name() + " keystore" + (params.keystore() != null ? " " + params.keystore() : ""), e);
1✔
701
        }
1✔
702

703
        return ks;
1✔
704
    }
705

706
    /**
707
     * Returns the aliases of the keystore available for signing.
708
     */
709
    Set<String> getAliases(KeyStore keystore) throws KeyStoreException {
710
        return new LinkedHashSet<>(Collections.list(keystore.aliases()));
1✔
711
    }
712

713
    /**
714
     * Guess the type of the keystore from the header or the extension of the file.
715
     *
716
     * @param path   the path to the keystore
717
     */
718
    static KeyStoreType of(File path) {
719
        // guess the type of the keystore from the header of the file
720
        if (path.exists()) {
1✔
721
            try (FileInputStream in = new FileInputStream(path)) {
1✔
722
                byte[] header = new byte[4];
1✔
723
                in.read(header);
1✔
724
                ByteBuffer buffer = ByteBuffer.wrap(header);
1✔
725
                if (buffer.get(0) == 0x30) {
1✔
726
                    return PKCS12;
1✔
727
                } else if ((buffer.getInt(0) & 0xFFFFFFFFL) == 0xCECECECEL) {
1✔
728
                    return JCEKS;
1✔
729
                } else if ((buffer.getInt(0) & 0xFFFFFFFFL) == 0xFEEDFEEDL) {
1✔
730
                    return JKS;
1✔
731
                }
732
            } catch (IOException e) {
1✔
733
                throw new RuntimeException("Unable to load the keystore " + path, e);
×
734
            }
1✔
735
        }
736

737
        // guess the type of the keystore from the extension of the file
738
        String filename = path.getName().toLowerCase();
1✔
739
        if (filename.endsWith(".p12") || filename.endsWith(".pfx")) {
1✔
740
            return PKCS12;
1✔
741
        } else if (filename.endsWith(".jceks")) {
1✔
742
            return JCEKS;
1✔
743
        } else if (filename.endsWith(".jks")) {
1✔
744
            return JKS;
1✔
745
        } else {
746
            return null;
1✔
747
        }
748
    }
749

750
    private static Function<String, Certificate[]> getCertificateStore(KeyStoreBuilder params) {
751
        return alias -> {
1✔
752
            if (alias == null || alias.isEmpty()) {
×
753
                return null;
×
754
            }
755

756
            try {
757
                return CertificateUtils.loadCertificateChain(params.certfile());
×
758
            } catch (IOException | CertificateException e) {
×
759
                throw new RuntimeException("Failed to load the certificate from " + params.certfile(), e);
×
760
            }
761
        };
762
    }
763
}
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