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

ebourg / jsign / #371

20 May 2025 09:49AM UTC coverage: 83.194% (-0.1%) from 83.31%
#371

push

ebourg
Allow the keystore parameter to be specified with the ETOKEN storetype to distinguish between multiple connected devices (#300)

0 of 6 new or added lines in 2 files covered. (0.0%)

36 existing lines in 3 files now uncovered.

4861 of 5843 relevant lines covered (83.19%)

0.83 hits per line

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

632
    /** Tells if the keystore is contained in a local file */
633
    private final boolean fileBased;
634

635
    /** Tells if the keystore is actually a PKCS#11 keystore */
636
    private final boolean pkcs11;
637

638
    KeyStoreType(boolean fileBased, boolean pkcs11) {
1✔
639
        this.fileBased = fileBased;
1✔
640
        this.pkcs11 = pkcs11;
1✔
641
    }
1✔
642

643
    /**
644
     * Validates the keystore parameters.
645
     */
646
    void validate(KeyStoreBuilder params) throws IllegalArgumentException {
647
    }
×
648

649
    /**
650
     * Returns the security provider to use the keystore.
651
     */
652
    Provider getProvider(KeyStoreBuilder params) {
653
        return null;
1✔
654
    }
655

656
    /**
657
     * Build the keystore.
658
     */
659
    KeyStore getKeystore(KeyStoreBuilder params, Provider provider) throws KeyStoreException {
660
        KeyStore ks;
661
        try {
662
            KeyStoreType storetype = pkcs11 ? PKCS11 : this;
1✔
663
            if (provider != null) {
1✔
664
                ks = KeyStore.getInstance(storetype.name(), provider);
1✔
665
            } else {
666
                ks = KeyStore.getInstance(storetype.name());
1✔
667
            }
668
        } catch (KeyStoreException e) {
×
669
            throw new KeyStoreException("keystore type '" + name() + "' is not supported" + (provider != null ? " with security provider " + provider.getName() : ""), e);
×
670
        }
1✔
671

672
        try {
673
            try (FileInputStream in = fileBased ? new FileInputStream(params.createFile(params.keystore())) : null) {
1✔
674
                ks.load(in, params.storepass() != null ? params.storepass().toCharArray() : null);
1✔
675
            }
676
        } catch (Exception e) {
1✔
677
            throw new KeyStoreException("Unable to load the " + name() + " keystore" + (params.keystore() != null ? " " + params.keystore() : ""), e);
1✔
678
        }
1✔
679

680
        return ks;
1✔
681
    }
682

683
    /**
684
     * Returns the aliases of the keystore available for signing.
685
     */
686
    Set<String> getAliases(KeyStore keystore) throws KeyStoreException {
687
        return new LinkedHashSet<>(Collections.list(keystore.aliases()));
1✔
688
    }
689

690
    /**
691
     * Guess the type of the keystore from the header or the extension of the file.
692
     *
693
     * @param path   the path to the keystore
694
     */
695
    static KeyStoreType of(File path) {
696
        // guess the type of the keystore from the header of the file
697
        if (path.exists()) {
1✔
698
            try (FileInputStream in = new FileInputStream(path)) {
1✔
699
                byte[] header = new byte[4];
1✔
700
                in.read(header);
1✔
701
                ByteBuffer buffer = ByteBuffer.wrap(header);
1✔
702
                if (buffer.get(0) == 0x30) {
1✔
703
                    return PKCS12;
1✔
704
                } else if ((buffer.getInt(0) & 0xFFFFFFFFL) == 0xCECECECEL) {
1✔
705
                    return JCEKS;
1✔
706
                } else if ((buffer.getInt(0) & 0xFFFFFFFFL) == 0xFEEDFEEDL) {
1✔
707
                    return JKS;
1✔
708
                }
709
            } catch (IOException e) {
1✔
710
                throw new RuntimeException("Unable to load the keystore " + path, e);
×
711
            }
1✔
712
        }
713

714
        // guess the type of the keystore from the extension of the file
715
        String filename = path.getName().toLowerCase();
1✔
716
        if (filename.endsWith(".p12") || filename.endsWith(".pfx")) {
1✔
717
            return PKCS12;
1✔
718
        } else if (filename.endsWith(".jceks")) {
1✔
719
            return JCEKS;
1✔
720
        } else if (filename.endsWith(".jks")) {
1✔
721
            return JKS;
1✔
722
        } else {
723
            return null;
1✔
724
        }
725
    }
726

727
    private static Function<String, Certificate[]> getCertificateStore(KeyStoreBuilder params) {
728
        return alias -> {
1✔
729
            if (alias == null || alias.isEmpty()) {
×
730
                return null;
×
731
            }
732

733
            try {
734
                return CertificateUtils.loadCertificateChain(params.certfile());
×
735
            } catch (IOException | CertificateException e) {
×
736
                throw new RuntimeException("Failed to load the certificate from " + params.certfile(), e);
×
737
            }
738
        };
739
    }
740
}
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