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<1-20></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><api-key>|<keystore>|<password></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><username>|<password></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}