001/*
002 * Copyright 2023 Emmanuel Bourg
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package net.jsign;
018
019import java.io.File;
020import java.io.FileInputStream;
021import java.io.IOException;
022import java.net.UnknownServiceException;
023import java.nio.ByteBuffer;
024import java.security.KeyStore;
025import java.security.KeyStoreException;
026import java.security.PrivateKey;
027import java.security.Provider;
028import java.security.Security;
029import java.security.cert.Certificate;
030import java.security.cert.CertificateException;
031import java.util.Collections;
032import java.util.LinkedHashSet;
033import java.util.Set;
034import java.util.function.Function;
035
036import javax.smartcardio.CardException;
037
038import net.jsign.jca.AmazonCredentials;
039import net.jsign.jca.AmazonSigningService;
040import net.jsign.jca.AzureKeyVaultSigningService;
041import net.jsign.jca.AzureTrustedSigningService;
042import net.jsign.jca.CryptoCertumCardSigningService;
043import net.jsign.jca.DigiCertOneSigningService;
044import net.jsign.jca.ESignerSigningService;
045import net.jsign.jca.GaraSignCredentials;
046import net.jsign.jca.GaraSignSigningService;
047import net.jsign.jca.GoogleCloudSigningService;
048import net.jsign.jca.HashiCorpVaultSigningService;
049import net.jsign.jca.OpenPGPCardSigningService;
050import net.jsign.jca.OracleCloudCredentials;
051import net.jsign.jca.OracleCloudSigningService;
052import net.jsign.jca.PIVCardSigningService;
053import net.jsign.jca.SignPathSigningService;
054import net.jsign.jca.SignServerCredentials;
055import net.jsign.jca.SignServerSigningService;
056import net.jsign.jca.SigningServiceJcaProvider;
057
058/**
059 * Type of a keystore.
060 *
061 * @since 5.0
062 */
063public enum KeyStoreType {
064
065    /** Not a keystore, a private key file and a certificate file are provided separately and assembled into an in-memory keystore */
066    NONE(true, false) {
067        @Override
068        void validate(KeyStoreBuilder params) {
069            if (params.keyfile() == null) {
070                throw new IllegalArgumentException("keyfile " + params.parameterName() + " must be set");
071            }
072            if (!params.keyfile().exists()) {
073                throw new IllegalArgumentException("The keyfile " + params.keyfile() + " couldn't be found");
074            }
075            if (params.certfile() == null) {
076                throw new IllegalArgumentException("certfile " + params.parameterName() + " must be set");
077            }
078            if (!params.certfile().exists()) {
079                throw new IllegalArgumentException("The certfile " + params.certfile() + " couldn't be found");
080            }
081        }
082
083        @Override
084        KeyStore getKeystore(KeyStoreBuilder params, Provider provider) throws KeyStoreException {
085            // load the certificate chain
086            Certificate[] chain;
087            try {
088                chain = CertificateUtils.loadCertificateChain(params.certfile());
089            } catch (Exception e) {
090                throw new KeyStoreException("Failed to load the certificate from " + params.certfile(), e);
091            }
092
093            // load the private key
094            PrivateKey privateKey;
095            try {
096                privateKey = PrivateKeyUtils.load(params.keyfile(), params.keypass() != null ? params.keypass() : params.storepass());
097            } catch (Exception e) {
098                throw new KeyStoreException("Failed to load the private key from " + params.keyfile(), e);
099            }
100
101            // build the in-memory keystore
102            KeyStore ks = KeyStore.getInstance("JKS");
103            try {
104                ks.load(null, null);
105                String keypass = params.keypass();
106                ks.setKeyEntry("jsign", privateKey, keypass != null ? keypass.toCharArray() : new char[0], chain);
107            } catch (Exception e) {
108                throw new KeyStoreException(e);
109            }
110
111            return ks;
112        }
113    },
114
115    /** Java keystore */
116    JKS(true, false) {
117        @Override
118        void validate(KeyStoreBuilder params) {
119            if (params.keystore() == null) {
120                throw new IllegalArgumentException("keystore " + params.parameterName() + " must be set");
121            }
122            if (!params.createFile(params.keystore()).exists()) {
123                throw new IllegalArgumentException("The keystore " + params.keystore() + " couldn't be found");
124            }
125            if (params.keypass() == null && params.storepass() != null) {
126                // reuse the storepass as the keypass
127                params.keypass(params.storepass());
128            }
129        }
130    },
131
132    /** JCE keystore */
133    JCEKS(true, false) {
134        @Override
135        void validate(KeyStoreBuilder params) {
136            if (params.keystore() == null) {
137                throw new IllegalArgumentException("keystore " + params.parameterName() + " must be set");
138            }
139            if (!params.createFile(params.keystore()).exists()) {
140                throw new IllegalArgumentException("The keystore " + params.keystore() + " couldn't be found");
141            }
142            if (params.keypass() == null && params.storepass() != null) {
143                // reuse the storepass as the keypass
144                params.keypass(params.storepass());
145            }
146        }
147    },
148
149    /** PKCS#12 keystore */
150    PKCS12(true, false) {
151        @Override
152        void validate(KeyStoreBuilder params) {
153            if (params.keystore() == null) {
154                throw new IllegalArgumentException("keystore " + params.parameterName() + " must be set");
155            }
156            if (!params.createFile(params.keystore()).exists()) {
157                throw new IllegalArgumentException("The keystore " + params.keystore() + " couldn't be found");
158            }
159            if (params.keypass() == null && params.storepass() != null) {
160                // reuse the storepass as the keypass
161                params.keypass(params.storepass());
162            }
163        }
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) {
172        @Override
173        void validate(KeyStoreBuilder params) {
174            if (params.keystore() == null) {
175                throw new IllegalArgumentException("keystore " + params.parameterName() + " must be set");
176            }
177        }
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()) {
183                return ProviderUtils.createSunPKCS11Provider(params.keystore());
184            } else if (params.keystore().startsWith("SunPKCS11-")) {
185                Provider provider = Security.getProvider(params.keystore());
186                if (provider == null) {
187                    throw new IllegalArgumentException("Security provider " + params.keystore() + " not found");
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");
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) {
205        @Override
206        void validate(KeyStoreBuilder params) {
207            if (params.storepass() == null) {
208                throw new IllegalArgumentException("storepass " + params.parameterName() + " must specify the PIN");
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) {
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) {
242        @Override
243        void validate(KeyStoreBuilder params) {
244            if (params.storepass() == null) {
245                throw new IllegalArgumentException("storepass " + params.parameterName() + " must specify the PIN");
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) {
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) {
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) {
306        @Override
307        void validate(KeyStoreBuilder params) {
308            if (params.keystore() == null) {
309                throw new IllegalArgumentException("keystore " + params.parameterName() + " must specify the AWS region");
310            }
311            if (params.certfile() == null) {
312                throw new IllegalArgumentException("certfile " + params.parameterName() + " must be set");
313            }
314        }
315
316        @Override
317        Provider getProvider(KeyStoreBuilder params) {
318            AmazonCredentials credentials;
319            if (params.storepass() != null) {
320                credentials = AmazonCredentials.parse(params.storepass());
321            } else {
322                try {
323                    credentials = AmazonCredentials.getDefault();
324                } catch (UnknownServiceException e) {
325                    throw new IllegalArgumentException("storepass " + params.parameterName()
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)));
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) {
343        @Override
344        void validate(KeyStoreBuilder params) {
345            if (params.keystore() == null) {
346                throw new IllegalArgumentException("keystore " + params.parameterName() + " must specify the Azure vault name");
347            }
348            if (params.storepass() == null) {
349                throw new IllegalArgumentException("storepass " + params.parameterName() + " must specify the Azure API access token");
350            }
351        }
352
353        @Override
354        Provider getProvider(KeyStoreBuilder params) {
355            return new SigningServiceJcaProvider(new AzureKeyVaultSigningService(params.keystore(), params.storepass()));
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) {
365        @Override
366        void validate(KeyStoreBuilder params) {
367            if (params.storepass() == null || params.storepass().split("\\|").length != 3) {
368                throw new IllegalArgumentException("storepass " + params.parameterName() + " must specify the DigiCert ONE API key and the client certificate: <apikey>|<keystore>|<password>");
369            }
370        }
371
372        @Override
373        Provider getProvider(KeyStoreBuilder params) {
374            String[] elements = params.storepass().split("\\|");
375            return new SigningServiceJcaProvider(new DigiCertOneSigningService(params.keystore(), elements[0], params.createFile(elements[1]), elements[2]));
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) {
384        @Override
385        void validate(KeyStoreBuilder params) {
386            if (params.storepass() == null || !params.storepass().contains("|")) {
387                throw new IllegalArgumentException("storepass " + params.parameterName() + " must specify the SSL.com username and password: <username>|<password>");
388            }
389        }
390
391        @Override
392        Provider getProvider(KeyStoreBuilder params) {
393            String[] elements = params.storepass().split("\\|", 2);
394            String endpoint = params.keystore() != null ? params.keystore() : "https://cs.ssl.com";
395            try {
396                return new SigningServiceJcaProvider(new ESignerSigningService(endpoint, elements[0], elements[1]));
397            } catch (IOException e) {
398                throw new IllegalStateException("Authentication failed with SSL.com", e);
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) {
409        @Override
410        void validate(KeyStoreBuilder params) {
411            if (params.keystore() == null) {
412                throw new IllegalArgumentException("keystore " + params.parameterName() + " must specify the Google Cloud keyring");
413            }
414            if (!params.keystore().matches("projects/[^/]+/locations/[^/]+/keyRings/[^/]+")) {
415                throw new IllegalArgumentException("keystore " + params.parameterName() + " must specify the path of the keyring (projects/{projectName}/locations/{location}/keyRings/{keyringName})");
416            }
417            if (params.storepass() == null) {
418                throw new IllegalArgumentException("storepass " + params.parameterName() + " must specify the Google Cloud API access token");
419            }
420            if (params.certfile() == null) {
421                throw new IllegalArgumentException("certfile " + params.parameterName() + " must be set");
422            }
423        }
424
425        @Override
426        Provider getProvider(KeyStoreBuilder params) {
427            return new SigningServiceJcaProvider(new GoogleCloudSigningService(params.keystore(), params.storepass(), getCertificateStore(params)));
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) {
438        @Override
439        void validate(KeyStoreBuilder params) {
440            if (params.keystore() == null) {
441                throw new IllegalArgumentException("keystore " + params.parameterName() + " must specify the HashiCorp Vault secrets engine URL");
442            }
443            if (params.storepass() == null) {
444                throw new IllegalArgumentException("storepass " + params.parameterName() + " must specify the HashiCorp Vault token");
445            }
446            if (params.certfile() == null) {
447                throw new IllegalArgumentException("certfile " + params.parameterName() + " must be set");
448            }
449        }
450
451        @Override
452        Provider getProvider(KeyStoreBuilder params) {
453            return new SigningServiceJcaProvider(new HashiCorpVaultSigningService(params.keystore(), params.storepass(), getCertificateStore(params)));
454        }
455    },
456
457    /**
458     * SafeNet eToken
459     * This keystore requires the installation of the SafeNet Authentication Client.
460     */
461    ETOKEN(false, true) {
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) {
481        @Override
482        void validate(KeyStoreBuilder params) {
483            if (params.certfile() == null) {
484                throw new IllegalArgumentException("certfile " + params.parameterName() + " must be set");
485            }
486        }
487
488        @Override
489        Provider getProvider(KeyStoreBuilder params) {
490            OracleCloudCredentials credentials = new OracleCloudCredentials();
491            try {
492                File config = null;
493                String profile = null;
494                if (params.storepass() != null) {
495                    String[] elements = params.storepass().split("\\|", 2);
496                    config = new File(elements[0]);
497                    if (elements.length > 1) {
498                        profile = elements[1];
499                    }
500                }
501                credentials.load(config, profile);
502                credentials.loadFromEnvironment();
503                if (params.keypass() != null) {
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            }
509            return new SigningServiceJcaProvider(new OracleCloudSigningService(credentials, getCertificateStore(params)));
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) {
521        @Override
522        void validate(KeyStoreBuilder params) {
523            if (params.keystore() == null) {
524                throw new IllegalArgumentException("keystore " + params.parameterName() + " must specify the Azure endpoint (<region>.codesigning.azure.net)");
525            }
526            if (params.storepass() == null) {
527                throw new IllegalArgumentException("storepass " + params.parameterName() + " must specify the Azure API access token");
528            }
529        }
530
531        @Override
532        Provider getProvider(KeyStoreBuilder params) {
533            return new SigningServiceJcaProvider(new AzureTrustedSigningService(params.keystore(), params.storepass()));
534        }
535    },
536
537    GARASIGN(false, false) {
538        @Override
539        void validate(KeyStoreBuilder params) {
540            if (params.storepass() == null || params.storepass().split("\\|").length > 3) {
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>");
542            }
543        }
544
545        @Override
546        Provider getProvider(KeyStoreBuilder params) {
547            String[] elements = params.storepass().split("\\|");
548            String username = null;
549            String password = null;
550            String certificate = null;
551            if (elements.length == 1) {
552                certificate = elements[0];
553            } else if (elements.length == 2) {
554                username = elements[0];
555                password = elements[1];
556            } else if (elements.length == 3) {
557                username = elements[0];
558                password = elements[1];
559                certificate = elements[2];
560            }
561
562            GaraSignCredentials credentials = new GaraSignCredentials(username, password, certificate, params.keypass());
563            return new SigningServiceJcaProvider(new GaraSignSigningService(params.keystore(), credentials));
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) {
581        @Override
582        void validate(KeyStoreBuilder params) {
583            if (params.keystore() == null) {
584                throw new IllegalArgumentException("keystore " + params.parameterName() + " must specify the SignServer API endpoint (e.g. https://example.com/signserver/)");
585            }
586            if (params.storepass() != null && params.storepass().split("\\|").length > 2) {
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>");
588            }
589        }
590
591        @Override
592        Provider getProvider(KeyStoreBuilder params) {
593            String username = null;
594            String password = null;
595            String certificate = null;
596            if (params.storepass() != null) {
597                String[] elements = params.storepass().split("\\|");
598                if (elements.length == 1) {
599                    certificate = elements[0];
600                } else if (elements.length == 2) {
601                    username = elements[0];
602                    password = elements[1];
603                }
604            }
605
606            SignServerCredentials credentials = new SignServerCredentials(username, password, certificate, params.keypass());
607            return new SigningServiceJcaProvider(new SignServerSigningService(params.keystore(), credentials));
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) {
617        @Override
618        void validate(KeyStoreBuilder params) {
619            if (params.keystore() == null) {
620                throw new IllegalArgumentException("keystore " + params.parameterName() + " must specify the SignPath organization id (e.g. eacd4b78-6038-4450-9eec-4acd1c7ba6f1)");
621            }
622            if (params.storepass() == null) {
623                throw new IllegalArgumentException("storepass " + params.parameterName() + " must specify the SignPath API access token");
624            }
625        }
626
627        @Override
628        Provider getProvider(KeyStoreBuilder params) {
629            return new SigningServiceJcaProvider(new SignPathSigningService(params.keystore(), params.storepass()));
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) {
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) {
662        this.fileBased = fileBased;
663        this.pkcs11 = pkcs11;
664    }
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;
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;
686            if (provider != null) {
687                ks = KeyStore.getInstance(storetype.name(), provider);
688            } else {
689                ks = KeyStore.getInstance(storetype.name());
690            }
691        } catch (KeyStoreException e) {
692            throw new KeyStoreException("keystore type '" + name() + "' is not supported" + (provider != null ? " with security provider " + provider.getName() : ""), e);
693        }
694
695        try {
696            try (FileInputStream in = fileBased ? new FileInputStream(params.createFile(params.keystore())) : null) {
697                ks.load(in, params.storepass() != null ? params.storepass().toCharArray() : null);
698            }
699        } catch (Exception e) {
700            throw new KeyStoreException("Unable to load the " + name() + " keystore" + (params.keystore() != null ? " " + params.keystore() : ""), e);
701        }
702
703        return ks;
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()));
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()) {
721            try (FileInputStream in = new FileInputStream(path)) {
722                byte[] header = new byte[4];
723                in.read(header);
724                ByteBuffer buffer = ByteBuffer.wrap(header);
725                if (buffer.get(0) == 0x30) {
726                    return PKCS12;
727                } else if ((buffer.getInt(0) & 0xFFFFFFFFL) == 0xCECECECEL) {
728                    return JCEKS;
729                } else if ((buffer.getInt(0) & 0xFFFFFFFFL) == 0xFEEDFEEDL) {
730                    return JKS;
731                }
732            } catch (IOException e) {
733                throw new RuntimeException("Unable to load the keystore " + path, e);
734            }
735        }
736
737        // guess the type of the keystore from the extension of the file
738        String filename = path.getName().toLowerCase();
739        if (filename.endsWith(".p12") || filename.endsWith(".pfx")) {
740            return PKCS12;
741        } else if (filename.endsWith(".jceks")) {
742            return JCEKS;
743        } else if (filename.endsWith(".jks")) {
744            return JKS;
745        } else {
746            return null;
747        }
748    }
749
750    private static Function<String, Certificate[]> getCertificateStore(KeyStoreBuilder params) {
751        return alias -> {
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}