From d83bfe37a347f990093817979e874fd6694f3e03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Kautler?= Date: Mon, 11 Nov 2024 00:36:10 +0100 Subject: [PATCH] Make key store types extensible conveniently --- .../src/test/java/net/jsign/JsignCLITest.java | 45 +- .../src/main/java/net/jsign/SignerHelper.java | 14 +- .../src/test/java/net/jsign/PESignerTest.java | 35 +- .../main/java/net/jsign/CertificateUtils.java | 2 +- .../main/java/net/jsign/KeyStoreBuilder.java | 59 +- .../src/main/java/net/jsign/KeyStoreType.java | 582 +----------------- .../java/net/jsign/jca/JsignJcaProvider.java | 11 +- .../net/jsign/ks/AbstractJsignKeyStore.java | 110 ++++ .../main/java/net/jsign/ks/AwsKeyStore.java | 81 +++ .../net/jsign/ks/AzureKeyVaultKeyStore.java | 54 ++ .../jsign/ks/AzureTrustedSigningKeyStore.java | 56 ++ .../net/jsign/ks/DigiCertOneKeyStore.java | 52 ++ .../java/net/jsign/ks/ESignerKeyStore.java | 62 ++ .../java/net/jsign/ks/GaraSignKeyStore.java | 63 ++ .../net/jsign/ks/GoogleCloudKeyStore.java | 62 ++ .../net/jsign/ks/HashiCorpVaultKeyStore.java | 60 ++ .../main/java/net/jsign/ks/JavaKeyStore.java | 40 ++ .../main/java/net/jsign/ks/JceKeyStore.java | 40 ++ .../main/java/net/jsign/ks/JsignKeyStore.java | 126 ++++ .../net/jsign/ks/JsignKeyStoreDiscovery.java | 53 ++ .../java/net/jsign/ks/NitroKeyKeyStore.java | 43 ++ .../main/java/net/jsign/ks/NoneKeyStore.java | 91 +++ .../java/net/jsign/ks/OpenPGPKeyStore.java | 61 ++ .../{OpenSC.java => ks/OpenSCKeyStore.java} | 338 +++++----- .../net/jsign/ks/OracleCloudKeyStore.java | 82 +++ .../main/java/net/jsign/ks/PivKeyStore.java | 60 ++ .../java/net/jsign/ks/Pkcs11KeyStore.java | 61 ++ .../java/net/jsign/ks/Pkcs12KeyStore.java | 40 ++ .../net/jsign/{ => ks}/ProviderUtils.java | 2 +- .../SafeNetETokenKeyStore.java} | 229 +++---- .../java/net/jsign/ks/SignServerKeyStore.java | 61 ++ .../{YubiKey.java => ks/YubiKeyKeyStore.java} | 317 +++++----- .../services/net.jsign.ks.JsignKeyStore | 21 + .../java/net/jsign/KeyStoreBuilderTest.java | 13 +- .../test/java/net/jsign/KeyStoreTypeTest.java | 29 +- .../net/jsign/jca/JsignJcaProviderTest.java | 8 +- .../java/net/jsign/{ => ks}/OpenSCTest.java | 6 +- .../net/jsign/{ => ks}/SafeNetETokenTest.java | 7 +- .../java/net/jsign/{ => ks}/YubikeyTest.java | 11 +- 39 files changed, 2001 insertions(+), 1086 deletions(-) create mode 100644 jsign-crypto/src/main/java/net/jsign/ks/AbstractJsignKeyStore.java create mode 100644 jsign-crypto/src/main/java/net/jsign/ks/AwsKeyStore.java create mode 100644 jsign-crypto/src/main/java/net/jsign/ks/AzureKeyVaultKeyStore.java create mode 100644 jsign-crypto/src/main/java/net/jsign/ks/AzureTrustedSigningKeyStore.java create mode 100644 jsign-crypto/src/main/java/net/jsign/ks/DigiCertOneKeyStore.java create mode 100644 jsign-crypto/src/main/java/net/jsign/ks/ESignerKeyStore.java create mode 100644 jsign-crypto/src/main/java/net/jsign/ks/GaraSignKeyStore.java create mode 100644 jsign-crypto/src/main/java/net/jsign/ks/GoogleCloudKeyStore.java create mode 100644 jsign-crypto/src/main/java/net/jsign/ks/HashiCorpVaultKeyStore.java create mode 100644 jsign-crypto/src/main/java/net/jsign/ks/JavaKeyStore.java create mode 100644 jsign-crypto/src/main/java/net/jsign/ks/JceKeyStore.java create mode 100644 jsign-crypto/src/main/java/net/jsign/ks/JsignKeyStore.java create mode 100644 jsign-crypto/src/main/java/net/jsign/ks/JsignKeyStoreDiscovery.java create mode 100644 jsign-crypto/src/main/java/net/jsign/ks/NitroKeyKeyStore.java create mode 100644 jsign-crypto/src/main/java/net/jsign/ks/NoneKeyStore.java create mode 100644 jsign-crypto/src/main/java/net/jsign/ks/OpenPGPKeyStore.java rename jsign-crypto/src/main/java/net/jsign/{OpenSC.java => ks/OpenSCKeyStore.java} (87%) create mode 100644 jsign-crypto/src/main/java/net/jsign/ks/OracleCloudKeyStore.java create mode 100644 jsign-crypto/src/main/java/net/jsign/ks/PivKeyStore.java create mode 100644 jsign-crypto/src/main/java/net/jsign/ks/Pkcs11KeyStore.java create mode 100644 jsign-crypto/src/main/java/net/jsign/ks/Pkcs12KeyStore.java rename jsign-crypto/src/main/java/net/jsign/{ => ks}/ProviderUtils.java (99%) rename jsign-crypto/src/main/java/net/jsign/{SafeNetEToken.java => ks/SafeNetETokenKeyStore.java} (85%) create mode 100644 jsign-crypto/src/main/java/net/jsign/ks/SignServerKeyStore.java rename jsign-crypto/src/main/java/net/jsign/{YubiKey.java => ks/YubiKeyKeyStore.java} (77%) create mode 100644 jsign-crypto/src/main/resources/META-INF/services/net.jsign.ks.JsignKeyStore rename jsign-crypto/src/test/java/net/jsign/{ => ks}/OpenSCTest.java (91%) rename jsign-crypto/src/test/java/net/jsign/{ => ks}/SafeNetETokenTest.java (87%) rename jsign-crypto/src/test/java/net/jsign/{ => ks}/YubikeyTest.java (86%) diff --git a/jsign-cli/src/test/java/net/jsign/JsignCLITest.java b/jsign-cli/src/test/java/net/jsign/JsignCLITest.java index c5463fb4..56747dad 100644 --- a/jsign-cli/src/test/java/net/jsign/JsignCLITest.java +++ b/jsign-cli/src/test/java/net/jsign/JsignCLITest.java @@ -28,6 +28,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import io.netty.handler.codec.http.HttpRequest; +import net.jsign.ks.YubiKeyKeyStore; import org.apache.commons.cli.ParseException; import org.apache.commons.io.ByteOrderMark; import org.apache.commons.io.FileUtils; @@ -54,7 +55,7 @@ public class JsignCLITest { private JsignCLI cli; private File sourceFile = new File("target/test-classes/wineyes.exe"); private File targetFile = new File("target/test-classes/wineyes-signed-with-cli.exe"); - + private String keystore = "keystore.jks"; private String alias = "test"; private String keypass = "password"; @@ -64,12 +65,12 @@ public class JsignCLITest { @Before public void setUp() throws Exception { cli = new JsignCLI(); - + // remove the files signed previously if (targetFile.exists()) { assertTrue("Unable to remove the previously signed file", targetFile.delete()); } - + assertEquals("Source file CRC32", SOURCE_FILE_CRC32, FileUtils.checksumCRC32(sourceFile)); Thread.sleep(100); FileUtils.copyFile(sourceFile, targetFile); @@ -226,7 +227,7 @@ public void testSigningMultipleFiles() throws Exception { public void testSigningMultipleFilesWithListFile() throws Exception { File listFile = new File("target/test-classes/files.txt"); Files.write(listFile.toPath(), Arrays.asList("# first file", '"' + targetFile.getPath() + '"', " ", "# second file", targetFile.getAbsolutePath())); - + cli.execute("--name=WinEyes", "--url=http://www.steelblue.com/WinEyes", "--alg=SHA-1", "--keystore=target/test-classes/keystores/" + keystore, "--keypass=" + keypass, "@" + listFile); assertTrue("The file " + targetFile + " wasn't changed", SOURCE_FILE_CRC32 != FileUtils.checksumCRC32(targetFile)); @@ -278,7 +279,7 @@ public void testSigningPowerShell() throws Exception { File sourceFile = new File("target/test-classes/hello-world.ps1"); File targetFile = new File("target/test-classes/hello-world-signed-with-cli.ps1"); FileUtils.copyFile(sourceFile, targetFile); - + cli.execute("--alg=SHA-1", "--replace", "--encoding=ISO-8859-1", "--keystore=target/test-classes/keystores/" + keystore, "--alias=" + alias, "--keypass=" + keypass, "" + targetFile); PowerShellScript script = new PowerShellScript(targetFile); @@ -291,7 +292,7 @@ public void testSigningPowerShellWithDefaultEncoding() throws Exception { File sourceFile = new File("target/test-classes/hello-world.ps1"); File targetFile = new File("target/test-classes/hello-world-signed-with-cli.ps1"); FileUtils.copyFile(sourceFile, targetFile); - + cli.execute("--alg=SHA-1", "--replace", "--keystore=target/test-classes/keystores/" + keystore, "--alias=" + alias, "--keypass=" + keypass, "" + targetFile); PowerShellScript script = new PowerShellScript(targetFile); @@ -304,7 +305,7 @@ public void testSigningMSI() throws Exception { File sourceFile = new File("target/test-classes/minimal.msi"); File targetFile = new File("target/test-classes/minimal-signed-with-cli.msi"); FileUtils.copyFile(sourceFile, targetFile); - + cli.execute("--alg=SHA-1", "--replace", "--keystore=target/test-classes/keystores/" + keystore, "--alias=" + alias, "--keypass=" + keypass, "" + targetFile); try (MSIFile file = new MSIFile(targetFile)) { @@ -315,7 +316,7 @@ public void testSigningMSI() throws Exception { @Test public void testSigningPKCS12() throws Exception { cli.execute("--name=WinEyes", "--url=http://www.steelblue.com/WinEyes", "--alg=SHA-256", "--keystore=target/test-classes/keystores/keystore.p12", "--alias=test", "--storepass=password", "" + targetFile); - + assertTrue("The file " + targetFile + " wasn't changed", SOURCE_FILE_CRC32 != FileUtils.checksumCRC32(targetFile)); try (PEFile peFile = new PEFile(targetFile)) { @@ -337,7 +338,7 @@ public void testSigningJCEKS() throws Exception { @Test public void testSigningPVKSPC() throws Exception { cli.execute("--url=http://www.steelblue.com/WinEyes", "--certfile=target/test-classes/keystores/jsign-test-certificate-full-chain.spc", "--keyfile=target/test-classes/keystores/privatekey-encrypted.pvk", "--storepass=password", "" + targetFile); - + assertTrue("The file " + targetFile + " wasn't changed", SOURCE_FILE_CRC32 != FileUtils.checksumCRC32(targetFile)); try (PEFile peFile = new PEFile(targetFile)) { @@ -348,7 +349,7 @@ public void testSigningPVKSPC() throws Exception { @Test public void testSigningPEM() throws Exception { cli.execute("--certfile=target/test-classes/keystores/jsign-test-certificate.pem", "--keyfile=target/test-classes/keystores/privatekey.pkcs8.pem", "--keypass=password", "" + targetFile); - + assertTrue("The file " + targetFile + " wasn't changed", SOURCE_FILE_CRC32 != FileUtils.checksumCRC32(targetFile)); try (PEFile peFile = new PEFile(targetFile)) { @@ -359,7 +360,7 @@ public void testSigningPEM() throws Exception { @Test public void testSigningEncryptedPEM() throws Exception { cli.execute("--certfile=target/test-classes/keystores/jsign-test-certificate.pem", "--keyfile=target/test-classes/keystores/privatekey-encrypted.pkcs1.pem", "--keypass=password", "" + targetFile); - + assertTrue("The file " + targetFile + " wasn't changed", SOURCE_FILE_CRC32 != FileUtils.checksumCRC32(targetFile)); try (PEFile peFile = new PEFile(targetFile)) { @@ -369,7 +370,7 @@ public void testSigningEncryptedPEM() throws Exception { @Test public void testSigningWithYubikey() throws Exception { - Assume.assumeTrue("No Yubikey detected", YubiKey.isPresent()); + Assume.assumeTrue("No Yubikey detected", YubiKeyKeyStore.isPresent()); cli.execute("--storetype=YUBIKEY", "--certfile=target/test-classes/keystores/jsign-test-certificate-full-chain.spc", "--storepass=123456", "--alias=X.509 Certificate for Digital Signature", "" + targetFile, "" + targetFile); } @@ -379,7 +380,7 @@ public void testTimestampingAuthenticode() throws Exception { File targetFile2 = new File("target/test-classes/wineyes-timestamped-with-cli-authenticode.exe"); FileUtils.copyFile(sourceFile, targetFile2); cli.execute("--keystore=target/test-classes/keystores/" + keystore, "--alias=" + alias, "--keypass=" + keypass, "--tsaurl=http://timestamp.sectigo.com", "--tsmode=authenticode", "" + targetFile2); - + assertTrue("The file " + targetFile2 + " wasn't changed", SOURCE_FILE_CRC32 != FileUtils.checksumCRC32(targetFile2)); try (PEFile peFile = new PEFile(targetFile2)) { @@ -412,7 +413,7 @@ public HttpFilters filterRequest(HttpRequest originalRequest) { } }) .start(); - + try { File targetFile2 = new File("target/test-classes/wineyes-timestamped-with-cli-rfc3161-proxy-unauthenticated.exe"); FileUtils.copyFile(sourceFile, targetFile2); @@ -420,10 +421,10 @@ public HttpFilters filterRequest(HttpRequest originalRequest) { "--tsaurl=http://timestamp.sectigo.com", "--tsmode=rfc3161", "--tsretries=1", "--tsretrywait=1", "--proxyUrl=localhost:" + proxy.getListenAddress().getPort(), "" + targetFile2); - + assertTrue("The file " + targetFile2 + " wasn't changed", SOURCE_FILE_CRC32 != FileUtils.checksumCRC32(targetFile2)); assertTrue("The proxy wasn't used", proxyUsed.get()); - + try (PEFile peFile = new PEFile(targetFile2)) { SignatureAssert.assertSigned(peFile, SHA256); } @@ -465,10 +466,10 @@ public String getRealm() { "--proxyUser=jsign", "--proxyPass=jsign", "" + targetFile2); - + assertTrue("The file " + targetFile2 + " wasn't changed", SOURCE_FILE_CRC32 != FileUtils.checksumCRC32(targetFile2)); assertTrue("The proxy wasn't used", proxyUsed.get()); - + try (PEFile peFile = new PEFile(targetFile2)) { SignatureAssert.assertSigned(peFile, SHA256); } @@ -482,11 +483,11 @@ public void testReplaceSignature() throws Exception { File targetFile2 = new File("target/test-classes/wineyes-re-signed.exe"); FileUtils.copyFile(sourceFile, targetFile2); cli.execute("--keystore=target/test-classes/keystores/" + keystore, "--alias=" + alias, "--keypass=" + keypass, "" + targetFile2); - + assertTrue("The file " + targetFile2 + " wasn't changed", SOURCE_FILE_CRC32 != FileUtils.checksumCRC32(targetFile2)); - + cli.execute("--keystore=target/test-classes/keystores/" + keystore, "--alias=" + alias, "--keypass=" + keypass, "--alg=SHA-512", "--replace", "" + targetFile2); - + try (PEFile peFile = new PEFile(targetFile2)) { SignatureAssert.assertSigned(peFile, SHA512); } @@ -525,7 +526,7 @@ public Integer getStatus() { } public void checkPermission(Permission perm) { } - + public void checkPermission(Permission perm, Object context) { } public void checkExit(int status) { diff --git a/jsign-core/src/main/java/net/jsign/SignerHelper.java b/jsign-core/src/main/java/net/jsign/SignerHelper.java index fac1c6ae..e91d9f55 100644 --- a/jsign-core/src/main/java/net/jsign/SignerHelper.java +++ b/jsign-core/src/main/java/net/jsign/SignerHelper.java @@ -45,6 +45,8 @@ import java.util.Set; import java.util.logging.Logger; +import net.jsign.ks.AzureTrustedSigningKeyStore; +import net.jsign.ks.JsignKeyStore; import org.bouncycastle.asn1.ASN1Encodable; import org.bouncycastle.asn1.ASN1InputStream; import org.bouncycastle.asn1.ASN1ObjectIdentifier; @@ -262,7 +264,7 @@ public SignerHelper param(String key, String value) { if (value == null) { return this; } - + switch (key) { case PARAM_COMMAND: return command(value); case PARAM_KEYSTORE: return keystore(value); @@ -329,7 +331,7 @@ private AuthenticodeSigner build() throws SignerException { } catch (KeyStoreException e) { throw new SignerException("Failed to load the keystore " + (ksparams.keystore() != null ? ksparams.keystore() : ""), e); } - KeyStoreType storetype = ksparams.storetype(); + JsignKeyStore storetype = ksparams.storetype(); Provider provider = ksparams.provider(); Set aliases = null; @@ -420,12 +422,12 @@ private AuthenticodeSigner build() throws SignerException { } // enable timestamping with Azure Trusted Signing - if (tsaurl == null && storetype == KeyStoreType.TRUSTEDSIGNING) { + if ((tsaurl == null) && (storetype instanceof AzureTrustedSigningKeyStore)) { tsaurl = "http://timestamp.acs.microsoft.com/"; tsmode = TimestampingMode.RFC3161.name(); tsretries = 3; } - + // configure the signer return new AuthenticodeSigner(chain, privateKey) .withProgramName(name) @@ -451,7 +453,7 @@ public void sign(File file) throws SignerException { if (!file.exists()) { throw new SignerException("The file " + file + " couldn't be found"); } - + try (Signable signable = Signable.of(file, encoding)) { File detachedSignature = getDetachedSignature(file); if (detached && detachedSignature.exists()) { @@ -655,7 +657,7 @@ private void timestamp(File file) throws SignerException { SignerId signerId = signerInformation.getSID(); X509CertificateHolder certificate = (X509CertificateHolder) signature.getCertificates().getMatches(signerId).iterator().next(); - String digestAlgorithmName = new DefaultAlgorithmNameFinder().getAlgorithmName(signerInformation.getDigestAlgorithmID()); + String digestAlgorithmName = new DefaultAlgorithmNameFinder().getAlgorithmName(signerInformation.getDigestAlgorithmID()); String keyAlgorithmName = new DefaultAlgorithmNameFinder().getAlgorithmName(new ASN1ObjectIdentifier(signerInformation.getEncryptionAlgOID())); String name = digestAlgorithmName + "/" + keyAlgorithmName + " signature from '" + certificate.getSubject() + "'"; diff --git a/jsign-core/src/test/java/net/jsign/PESignerTest.java b/jsign-core/src/test/java/net/jsign/PESignerTest.java index dbf014e6..355596c5 100644 --- a/jsign-core/src/test/java/net/jsign/PESignerTest.java +++ b/jsign-core/src/test/java/net/jsign/PESignerTest.java @@ -27,6 +27,7 @@ import java.util.Collection; import java.util.HashSet; +import net.jsign.ks.YubiKeyKeyStore; import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.JavaVersion; import org.apache.commons.lang3.SystemUtils; @@ -66,7 +67,7 @@ private KeyStore getKeyStore() throws Exception { public void testSign() throws Exception { File sourceFile = new File("target/test-classes/wineyes.exe"); File targetFile = new File("target/test-classes/wineyes-signed.exe"); - + FileUtils.copyFile(sourceFile, targetFile); PESigner signer = new PESigner(getKeyStore(), ALIAS, PRIVATE_KEY_PASSWORD) @@ -100,7 +101,7 @@ public void testSignWithUnknownKeyStoreEntry() throws Exception { public void testSigningWithKeyAndChain() throws Exception { File sourceFile = new File("target/test-classes/wineyes.exe"); File targetFile = new File("target/test-classes/wineyes-signed-key-chain.exe"); - + FileUtils.copyFile(sourceFile, targetFile); Certificate[] chain; @@ -136,7 +137,7 @@ public void testSigningWithKeyAndChain() throws Exception { @Test public void testSigningWithYubikey() throws Exception { - Assume.assumeTrue("No Yubikey detected", YubiKey.isPresent()); + Assume.assumeTrue("No Yubikey detected", YubiKeyKeyStore.isPresent()); File sourceFile = new File("target/test-classes/wineyes.exe"); File targetFile = new File("target/test-classes/wineyes-signed-yubikey.exe"); @@ -170,7 +171,7 @@ public void testNullChain() throws Exception { public void testSigningWithMismatchingKeyAndCertificate() throws Exception { File sourceFile = new File("target/test-classes/wineyes.exe"); File targetFile = new File("target/test-classes/wineyes-signed-mismatching-key-certificate.exe"); - + FileUtils.copyFile(sourceFile, targetFile); Certificate[] chain; @@ -208,7 +209,7 @@ public void testTimestampRFC3161() throws Exception { public void testTimestamp(TimestampingMode mode, DigestAlgorithm alg) throws Exception { File sourceFile = new File("target/test-classes/wineyes.exe"); File targetFile = new File("target/test-classes/wineyes-timestamped-" + mode.name().toLowerCase() + ".exe"); - + FileUtils.copyFile(sourceFile, targetFile); PESigner signer = new PESigner(getKeyStore(), ALIAS, PRIVATE_KEY_PASSWORD); @@ -240,7 +241,7 @@ public void testWithTimestamper() throws Exception { signer.withDigestAlgorithm(SHA1); signer.withTimestamping(true); signer.withTimestamper(new AuthenticodeTimestamper() { - + @Override protected CMSSignedData timestamp(DigestAlgorithm algo, byte[] encryptedDigest) throws IOException, TimestampingException { called.add(true); @@ -263,7 +264,7 @@ protected CMSSignedData timestamp(DigestAlgorithm algo, byte[] encryptedDigest) public void testSignTwice() throws Exception { File sourceFile = new File("target/test-classes/wineyes.exe"); File targetFile = new File("target/test-classes/wineyes-signed-twice.exe"); - + FileUtils.copyFile(sourceFile, targetFile); try (PEFile peFile = new PEFile(targetFile)) { @@ -292,7 +293,7 @@ public void testSignTwice() throws Exception { public void testSignThreeTimes() throws Exception { File sourceFile = new File("target/test-classes/wineyes.exe"); File targetFile = new File("target/test-classes/wineyes-signed-three-times.exe"); - + FileUtils.copyFile(sourceFile, targetFile); try (PEFile peFile = new PEFile(targetFile)) { @@ -329,7 +330,7 @@ public void testSignThreeTimes() throws Exception { public void testReplaceSignature() throws Exception { File sourceFile = new File("target/test-classes/wineyes.exe"); File targetFile = new File("target/test-classes/wineyes-re-signed.exe"); - + FileUtils.copyFile(sourceFile, targetFile); try (PEFile peFile = new PEFile(targetFile)) { @@ -365,16 +366,16 @@ public void testInvalidRFC3161TimestampingAuthority() throws Exception { public void testInvalidTimestampingAuthority(TimestampingMode mode) throws Exception { File sourceFile = new File("target/test-classes/wineyes.exe"); File targetFile = new File("target/test-classes/wineyes-timestamped-unavailable-" + mode.name().toLowerCase() + ".exe"); - + FileUtils.copyFile(sourceFile, targetFile); - + PESigner signer = new PESigner(getKeyStore(), ALIAS, PRIVATE_KEY_PASSWORD); signer.withDigestAlgorithm(SHA1); signer.withTimestamping(true); signer.withTimestampingMode(mode); signer.withTimestampingAuthority("http://www.google.com/" + mode.name().toLowerCase()); signer.withTimestampingRetries(1); - + try (PEFile peFile = new PEFile(targetFile)) { signer.sign(peFile); fail("TimestampingException not thrown"); @@ -399,16 +400,16 @@ public void testBrokenRFC3161TimestampingAuthority() throws Exception { public void testBrokenTimestampingAuthority(TimestampingMode mode) throws Exception { File sourceFile = new File("target/test-classes/wineyes.exe"); File targetFile = new File("target/test-classes/wineyes-timestamped-broken-" + mode.name().toLowerCase() + ".exe"); - + FileUtils.copyFile(sourceFile, targetFile); - + PESigner signer = new PESigner(getKeyStore(), ALIAS, PRIVATE_KEY_PASSWORD); signer.withDigestAlgorithm(SHA1); signer.withTimestamping(true); signer.withTimestampingMode(mode); signer.withTimestampingAuthority("http://github.com"); signer.withTimestampingRetries(1); - + try (PEFile peFile = new PEFile(targetFile)) { signer.sign(peFile); fail("TimestampingException not thrown"); @@ -446,7 +447,7 @@ public void testRFC3161TimestampingFailover() throws Exception { public void testTimestampingFailover(TimestampingMode mode, String validURL) throws Exception { File sourceFile = new File("target/test-classes/wineyes.exe"); File targetFile = new File("target/test-classes/wineyes-timestamped-failover-" + mode.name().toLowerCase() + ".exe"); - + FileUtils.copyFile(sourceFile, targetFile); PESigner signer = new PESigner(getKeyStore(), ALIAS, PRIVATE_KEY_PASSWORD); @@ -502,7 +503,7 @@ public void testWithSignatureAlgorithmSHA1withRSA() throws Exception { @Test public void testWithSignatureAlgorithmSHA256withRSAandMGF1() throws Exception { Security.addProvider(new BouncyCastleProvider()); - + File sourceFile = new File("target/test-classes/wineyes.exe"); File targetFile = new File("target/test-classes/wineyes-signed.exe"); diff --git a/jsign-crypto/src/main/java/net/jsign/CertificateUtils.java b/jsign-crypto/src/main/java/net/jsign/CertificateUtils.java index 9bad5e7f..832bab38 100644 --- a/jsign-crypto/src/main/java/net/jsign/CertificateUtils.java +++ b/jsign-crypto/src/main/java/net/jsign/CertificateUtils.java @@ -45,7 +45,7 @@ /** * @since 5.0 */ -class CertificateUtils { +public class CertificateUtils { private CertificateUtils() { } diff --git a/jsign-crypto/src/main/java/net/jsign/KeyStoreBuilder.java b/jsign-crypto/src/main/java/net/jsign/KeyStoreBuilder.java index 51d75f0b..4f413286 100644 --- a/jsign-crypto/src/main/java/net/jsign/KeyStoreBuilder.java +++ b/jsign-crypto/src/main/java/net/jsign/KeyStoreBuilder.java @@ -16,6 +16,10 @@ package net.jsign; +import net.jsign.ks.JsignKeyStore; +import net.jsign.ks.JsignKeyStoreDiscovery; +import net.jsign.ks.NoneKeyStore; + import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -25,9 +29,8 @@ import java.security.KeyStoreException; import java.security.Provider; import java.util.stream.Collectors; -import java.util.stream.Stream; -import static net.jsign.KeyStoreType.*; +import static net.jsign.KeyStoreType.NONE; /** * Keystore builder. @@ -47,7 +50,7 @@ public class KeyStoreBuilder { private String keystore; private String storepass; - private KeyStoreType storetype; + private JsignKeyStore storetype; private String keypass; private File keyfile; private File certfile; @@ -64,7 +67,7 @@ public KeyStoreBuilder() { this.parameterName = parameterName; } - String parameterName() { + public String parameterName() { return parameterName; } @@ -84,7 +87,7 @@ public KeyStoreBuilder keystore(String keystore) { return this; } - String keystore() { + public String keystore() { return keystore; } @@ -98,7 +101,7 @@ public KeyStoreBuilder storepass(String storepass) { return this; } - String storepass() { + public String storepass() { storepass = readPassword("storepass", storepass); return storepass; } @@ -106,11 +109,19 @@ String storepass() { /** * Sets the type of the keystore. */ - public KeyStoreBuilder storetype(KeyStoreType storetype) { + public KeyStoreBuilder storetype(JsignKeyStore storetype) { this.storetype = storetype; return this; } + /** + * Sets the type of the keystore. + */ + public KeyStoreBuilder storetype(KeyStoreType storetype) { + this.storetype = storetype.getJsignKeyStore(); + return this; + } + /** * Sets the type of the keystore. * @@ -118,29 +129,35 @@ public KeyStoreBuilder storetype(KeyStoreType storetype) { * @throws IllegalArgumentException if the type is not recognized */ public KeyStoreBuilder storetype(String storetype) { - try { - this.storetype = storetype != null ? KeyStoreType.valueOf(storetype.toUpperCase()) : null; - } catch (IllegalArgumentException e) { - String expectedTypes = Stream.of(KeyStoreType.values()) - .filter(type -> type != NONE).map(KeyStoreType::name) - .collect(Collectors.joining(", ")); - throw new IllegalArgumentException("Unknown keystore type '" + storetype + "' (expected types: " + expectedTypes + ")"); + if (storetype == null) { + this.storetype = null; + } else { + this.storetype = JsignKeyStoreDiscovery.getKeyStore(storetype.toUpperCase()); + if (this.storetype == null) { + String noneType = NONE.getJsignKeyStore().getType(); + String expectedTypes = JsignKeyStoreDiscovery + .getKeyStoreTypes() + .stream() + .filter(type -> !noneType.equals(type)) + .collect(Collectors.joining(", ")); + throw new IllegalArgumentException("Unknown keystore type '" + storetype + "' (expected types: " + expectedTypes + ")"); + } } return this; } - KeyStoreType storetype() { + public JsignKeyStore storetype() { if (storetype == null) { if (keystore == null) { // no keystore specified, keyfile and certfile are expected - storetype = NONE; + storetype = NONE.getJsignKeyStore(); } else { // the keystore type wasn't specified, let's try to guess it File file = createFile(keystore); if (!file.isFile()) { throw new IllegalArgumentException("Keystore file '" + keystore + "' not found"); } - storetype = KeyStoreType.of(file); + storetype = JsignKeyStore.of(file); if (storetype == null) { throw new IllegalArgumentException("Keystore type of '" + keystore + "' not recognized"); } @@ -159,7 +176,7 @@ public KeyStoreBuilder keypass(String keypass) { return this; } - String keypass() { + public String keypass() { keypass = readPassword("keypass", keypass); return keypass; } @@ -179,7 +196,7 @@ public KeyStoreBuilder keyfile(File keyfile) { return this; } - File keyfile() { + public File keyfile() { return keyfile; } @@ -198,7 +215,7 @@ public KeyStoreBuilder certfile(File certfile) { return this; } - File certfile() { + public File certfile() { return certfile; } @@ -206,7 +223,7 @@ void setBaseDir(File basedir) { this.basedir = basedir; } - File createFile(String file) { + public File createFile(String file) { if (file == null) { return null; } diff --git a/jsign-crypto/src/main/java/net/jsign/KeyStoreType.java b/jsign-crypto/src/main/java/net/jsign/KeyStoreType.java index 81023876..6f8f6b1f 100644 --- a/jsign-crypto/src/main/java/net/jsign/KeyStoreType.java +++ b/jsign-crypto/src/main/java/net/jsign/KeyStoreType.java @@ -16,41 +16,8 @@ package net.jsign; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.net.UnknownServiceException; -import java.nio.ByteBuffer; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.PrivateKey; -import java.security.Provider; -import java.security.Security; -import java.security.cert.Certificate; -import java.security.cert.CertificateException; -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.Set; -import java.util.function.Function; -import javax.smartcardio.CardException; - -import net.jsign.jca.AmazonCredentials; -import net.jsign.jca.AmazonSigningService; -import net.jsign.jca.AzureKeyVaultSigningService; -import net.jsign.jca.AzureTrustedSigningService; -import net.jsign.jca.DigiCertOneSigningService; -import net.jsign.jca.ESignerSigningService; -import net.jsign.jca.GaraSignCredentials; -import net.jsign.jca.GaraSignSigningService; -import net.jsign.jca.GoogleCloudSigningService; -import net.jsign.jca.HashiCorpVaultSigningService; -import net.jsign.jca.OpenPGPCardSigningService; -import net.jsign.jca.OracleCloudCredentials; -import net.jsign.jca.OracleCloudSigningService; -import net.jsign.jca.PIVCardSigningService; -import net.jsign.jca.SignServerCredentials; -import net.jsign.jca.SignServerSigningService; -import net.jsign.jca.SigningServiceJcaProvider; +import net.jsign.ks.JsignKeyStore; +import net.jsign.ks.JsignKeyStoreDiscovery; /** * Type of a keystore. @@ -60,117 +27,23 @@ public enum KeyStoreType { /** Not a keystore, a private key file and a certificate file are provided separately and assembled into an in-memory keystore */ - NONE(true, false, false) { - @Override - void validate(KeyStoreBuilder params) { - if (params.keyfile() == null) { - throw new IllegalArgumentException("keyfile " + params.parameterName() + " must be set"); - } - if (!params.keyfile().exists()) { - throw new IllegalArgumentException("The keyfile " + params.keyfile() + " couldn't be found"); - } - if (params.certfile() == null) { - throw new IllegalArgumentException("certfile " + params.parameterName() + " must be set"); - } - if (!params.certfile().exists()) { - throw new IllegalArgumentException("The certfile " + params.certfile() + " couldn't be found"); - } - } - - @Override - KeyStore getKeystore(KeyStoreBuilder params, Provider provider) throws KeyStoreException { - // load the certificate chain - Certificate[] chain; - try { - chain = CertificateUtils.loadCertificateChain(params.certfile()); - } catch (Exception e) { - throw new KeyStoreException("Failed to load the certificate from " + params.certfile(), e); - } - - // load the private key - PrivateKey privateKey; - try { - privateKey = PrivateKeyUtils.load(params.keyfile(), params.keypass() != null ? params.keypass() : params.storepass()); - } catch (Exception e) { - throw new KeyStoreException("Failed to load the private key from " + params.keyfile(), e); - } - - // build the in-memory keystore - KeyStore ks = KeyStore.getInstance("JKS"); - try { - ks.load(null, null); - String keypass = params.keypass(); - if (keypass == null) { - keypass = params.storepass(); - } - ks.setKeyEntry("jsign", privateKey, keypass != null ? keypass.toCharArray() : new char[0], chain); - } catch (Exception e) { - throw new KeyStoreException(e); - } - - return ks; - } - }, + NONE, /** Java keystore */ - JKS(true, true, false) { - @Override - void validate(KeyStoreBuilder params) { - if (params.keystore() == null) { - throw new IllegalArgumentException("keystore " + params.parameterName() + " must be set"); - } - } - }, + JKS, /** JCE keystore */ - JCEKS(true, true, false) { - @Override - void validate(KeyStoreBuilder params) { - if (params.keystore() == null) { - throw new IllegalArgumentException("keystore " + params.parameterName() + " must be set"); - } - } - }, + JCEKS, /** PKCS#12 keystore */ - PKCS12(true, true, false) { - @Override - void validate(KeyStoreBuilder params) { - if (params.keystore() == null) { - throw new IllegalArgumentException("keystore " + params.parameterName() + " must be set"); - } - } - }, + PKCS12, /** * PKCS#11 hardware token. The keystore parameter specifies either the name of the provider defined * in jre/lib/security/java.security or the path to the * SunPKCS11 configuration file. */ - PKCS11(false, true, true) { - @Override - void validate(KeyStoreBuilder params) { - if (params.keystore() == null) { - throw new IllegalArgumentException("keystore " + params.parameterName() + " must be set"); - } - } - - @Override - Provider getProvider(KeyStoreBuilder params) { - // the keystore parameter is either the provider name or the SunPKCS11 configuration file - if (params.createFile(params.keystore()).exists()) { - return ProviderUtils.createSunPKCS11Provider(params.keystore()); - } else if (params.keystore().startsWith("SunPKCS11-")) { - Provider provider = Security.getProvider(params.keystore()); - if (provider == null) { - throw new IllegalArgumentException("Security provider " + params.keystore() + " not found"); - } - return provider; - } else { - 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"); - } - } - }, + PKCS11, /** * OpenPGP card. OpenPGP cards contain up to 3 keys, one for signing, one for encryption, and one for authentication. @@ -180,35 +53,14 @@ Provider getProvider(KeyStoreBuilder params) { * the keystore parameter can be used to specify the name of the one to use. This keystore type doesn't require * any external library to be installed. */ - OPENPGP(false, false, false) { - @Override - void validate(KeyStoreBuilder params) { - if (params.storepass() == null) { - throw new IllegalArgumentException("storepass " + params.parameterName() + " must specify the PIN"); - } - } - - @Override - Provider getProvider(KeyStoreBuilder params) { - try { - return new SigningServiceJcaProvider(new OpenPGPCardSigningService(params.keystore(), params.storepass(), params.certfile() != null ? getCertificateStore(params) : null)); - } catch (CardException e) { - throw new IllegalStateException("Failed to initialize the OpenPGP card", e); - } - } - }, + OPENPGP, /** * OpenSC supported smart card. * This keystore requires the installation of OpenSC. * If multiple devices are connected, the keystore parameter can be used to specify the name of the one to use. */ - OPENSC(false, true, true) { - @Override - Provider getProvider(KeyStoreBuilder params) { - return OpenSC.getProvider(params.keystore()); - } - }, + OPENSC, /** * PIV card. PIV cards contain up to 24 private keys and certificates. The alias to select the key is either, @@ -217,23 +69,7 @@ Provider getProvider(KeyStoreBuilder params) { * signature key). If multiple devices are connected, the keystore parameter can be used to specify the name * of the one to use. This keystore type doesn't require any external library to be installed. */ - PIV(false, false, false) { - @Override - void validate(KeyStoreBuilder params) { - if (params.storepass() == null) { - throw new IllegalArgumentException("storepass " + params.parameterName() + " must specify the PIN"); - } - } - - @Override - Provider getProvider(KeyStoreBuilder params) { - try { - return new SigningServiceJcaProvider(new PIVCardSigningService(params.keystore(), params.storepass(), params.certfile() != null ? getCertificateStore(params) : null)); - } catch (CardException e) { - throw new IllegalStateException("Failed to initialize the PIV card", e); - } - } - }, + PIV, /** * Nitrokey HSM. This keystore requires the installation of OpenSC. @@ -241,32 +77,14 @@ Provider getProvider(KeyStoreBuilder params) { * certificate must be imported into the Nitrokey (using the gnupg writecert command). Keys without certificates * are ignored. Otherwise the {@link #OPENPGP} type should be used. */ - NITROKEY(false, true, true) { - @Override - Provider getProvider(KeyStoreBuilder params) { - return OpenSC.getProvider(params.keystore() != null ? params.keystore() : "Nitrokey"); - } - }, + NITROKEY, /** * YubiKey PIV. This keystore requires the ykcs11 library from the Yubico PIV Tool * to be installed at the default location. On Windows, the path to the library must be specified in the * PATH environment variable. */ - YUBIKEY(false, true, true) { - @Override - Provider getProvider(KeyStoreBuilder params) { - return YubiKey.getProvider(); - } - - @Override - Set getAliases(KeyStore keystore) throws KeyStoreException { - Set aliases = super.getAliases(keystore); - // the attestation certificate is never used for signing - aliases.remove("X.509 Certificate for PIV Attestation"); - return aliases; - } - }, + YUBIKEY, /** * AWS Key Management Service (KMS). AWS KMS stores only the private key, the certificate must be provided @@ -280,136 +98,34 @@ Set getAliases(KeyStore keystore) throws KeyStoreException { *

In any case, the credentials must allow the following actions: kms:ListKeys, * kms:DescribeKey and kms:Sign.

* */ - AWS(false, false, false) { - @Override - void validate(KeyStoreBuilder params) { - if (params.keystore() == null) { - throw new IllegalArgumentException("keystore " + params.parameterName() + " must specify the AWS region"); - } - if (params.certfile() == null) { - throw new IllegalArgumentException("certfile " + params.parameterName() + " must be set"); - } - } - - @Override - Provider getProvider(KeyStoreBuilder params) { - AmazonCredentials credentials; - if (params.storepass() != null) { - credentials = AmazonCredentials.parse(params.storepass()); - } else { - try { - credentials = AmazonCredentials.getDefault(); - } catch (UnknownServiceException e) { - throw new IllegalArgumentException("storepass " + params.parameterName() - + " must specify the AWS credentials: |[|]" - + ", when not running from an EC2 instance (" + e.getMessage() + ")", e); - } catch (IOException e) { - throw new RuntimeException("An error occurred while fetching temporary credentials from IMDSv2 service", e); - } - } - - return new SigningServiceJcaProvider(new AmazonSigningService(params.keystore(), credentials, getCertificateStore(params))); - } - }, + AWS, /** * Azure Key Vault. The keystore parameter specifies the name of the key vault, either the short name * (e.g. myvault), or the full URL (e.g. https://myvault.vault.azure.net). * The Azure API access token is used as the keystore password. */ - AZUREKEYVAULT(false, true, false) { - @Override - void validate(KeyStoreBuilder params) { - if (params.keystore() == null) { - throw new IllegalArgumentException("keystore " + params.parameterName() + " must specify the Azure vault name"); - } - if (params.storepass() == null) { - throw new IllegalArgumentException("storepass " + params.parameterName() + " must specify the Azure API access token"); - } - } - - @Override - Provider getProvider(KeyStoreBuilder params) { - return new SigningServiceJcaProvider(new AzureKeyVaultSigningService(params.keystore(), params.storepass())); - } - }, + AZUREKEYVAULT, /** * DigiCert ONE. Certificates and keys stored in the DigiCert ONE Secure Software Manager can be used directly * without installing the DigiCert client tools. The API key, the PKCS#12 keystore holding the client certificate * and its password are combined to form the storepass parameter: <api-key>|<keystore>|<password>. */ - DIGICERTONE(false, true, false) { - @Override - void validate(KeyStoreBuilder params) { - if (params.storepass() == null || params.storepass().split("\\|").length != 3) { - throw new IllegalArgumentException("storepass " + params.parameterName() + " must specify the DigiCert ONE API key and the client certificate: ||"); - } - } - - @Override - Provider getProvider(KeyStoreBuilder params) { - String[] elements = params.storepass().split("\\|"); - return new SigningServiceJcaProvider(new DigiCertOneSigningService(params.keystore(), elements[0], params.createFile(elements[1]), elements[2])); - } - }, + DIGICERTONE, /** * SSL.com eSigner. The SSL.com username and password are used as the keystore password (<username>|<password>), * and the base64 encoded TOTP secret is used as the key password. */ - ESIGNER(false, true, false) { - @Override - void validate(KeyStoreBuilder params) { - if (params.storepass() == null || !params.storepass().contains("|")) { - throw new IllegalArgumentException("storepass " + params.parameterName() + " must specify the SSL.com username and password: |"); - } - } - - @Override - Provider getProvider(KeyStoreBuilder params) { - String[] elements = params.storepass().split("\\|", 2); - String endpoint = params.keystore() != null ? params.keystore() : "https://cs.ssl.com"; - try { - return new SigningServiceJcaProvider(new ESignerSigningService(endpoint, elements[0], elements[1])); - } catch (IOException e) { - throw new IllegalStateException("Authentication failed with SSL.com", e); - } - } - - @Override - boolean reuseKeyStorePassword() { - return false; - } - }, + ESIGNER, /** * Google Cloud KMS. Google Cloud KMS stores only the private key, the certificate must be provided separately. * The keystore parameter references the path of the keyring. The alias can specify either the full path of the key, * or only the short name. If the version is omitted the most recent one will be picked automatically. */ - GOOGLECLOUD(false, false, false) { - @Override - void validate(KeyStoreBuilder params) { - if (params.keystore() == null) { - throw new IllegalArgumentException("keystore " + params.parameterName() + " must specify the Goole Cloud keyring"); - } - if (!params.keystore().matches("projects/[^/]+/locations/[^/]+/keyRings/[^/]+")) { - throw new IllegalArgumentException("keystore " + params.parameterName() + " must specify the path of the keyring (projects/{projectName}/locations/{location}/keyRings/{keyringName})"); - } - if (params.storepass() == null) { - throw new IllegalArgumentException("storepass " + params.parameterName() + " must specify the Goole Cloud API access token"); - } - if (params.certfile() == null) { - throw new IllegalArgumentException("certfile " + params.parameterName() + " must be set"); - } - } - - @Override - Provider getProvider(KeyStoreBuilder params) { - return new SigningServiceJcaProvider(new GoogleCloudSigningService(params.keystore(), params.storepass(), getCertificateStore(params))); - } - }, + GOOGLECLOUD, /** * HashiCorp Vault secrets engine (Transit or GCPKMS). The certificate must be provided separately. The keystore @@ -417,36 +133,13 @@ Provider getProvider(KeyStoreBuilder params) { * The alias parameter specifies the name of the key in Vault. For the Google Cloud KMS secrets engine, the version * of the Google Cloud key is appended to the key name, separated by a colon character. (mykey:1). */ - HASHICORPVAULT(false, false, false) { - @Override - void validate(KeyStoreBuilder params) { - if (params.keystore() == null) { - throw new IllegalArgumentException("keystore " + params.parameterName() + " must specify the HashiCorp Vault secrets engine URL"); - } - if (params.storepass() == null) { - throw new IllegalArgumentException("storepass " + params.parameterName() + " must specify the HashiCorp Vault token"); - } - if (params.certfile() == null) { - throw new IllegalArgumentException("certfile " + params.parameterName() + " must be set"); - } - } - - @Override - Provider getProvider(KeyStoreBuilder params) { - return new SigningServiceJcaProvider(new HashiCorpVaultSigningService(params.keystore(), params.storepass(), getCertificateStore(params))); - } - }, + HASHICORPVAULT, /** * SafeNet eToken * This keystore requires the installation of the SafeNet Authentication Client. */ - ETOKEN(false, true, true) { - @Override - Provider getProvider(KeyStoreBuilder params) { - return SafeNetEToken.getProvider(); - } - }, + ETOKEN, /** * Oracle Cloud Infrastructure Key Management Service. This keystore requires the configuration file @@ -460,38 +153,7 @@ Provider getProvider(KeyStoreBuilder params) { *

The certificate must be provided separately using the certfile parameter. The alias specifies the OCID * of the key.

*/ - ORACLECLOUD(false, false, false) { - @Override - void validate(KeyStoreBuilder params) { - if (params.certfile() == null) { - throw new IllegalArgumentException("certfile " + params.parameterName() + " must be set"); - } - } - - @Override - Provider getProvider(KeyStoreBuilder params) { - OracleCloudCredentials credentials = new OracleCloudCredentials(); - try { - File config = null; - String profile = null; - if (params.storepass() != null) { - String[] elements = params.storepass().split("\\|", 2); - config = new File(elements[0]); - if (elements.length > 1) { - profile = elements[1]; - } - } - credentials.load(config, profile); - credentials.loadFromEnvironment(); - if (params.keypass() != null) { - credentials.setPassphrase(params.keypass()); - } - } catch (IOException e) { - throw new RuntimeException("An error occurred while fetching the Oracle Cloud credentials", e); - } - return new SigningServiceJcaProvider(new OracleCloudSigningService(credentials, getCertificateStore(params))); - } - }, + ORACLECLOUD, /** * Azure Trusted Signing Service. The keystore parameter specifies the API endpoint (for example @@ -500,207 +162,13 @@ Provider getProvider(KeyStoreBuilder params) { * *
  az account get-access-token --resource https://codesigning.azure.net
*/ - TRUSTEDSIGNING(false, false, false) { - @Override - void validate(KeyStoreBuilder params) { - if (params.keystore() == null) { - throw new IllegalArgumentException("keystore " + params.parameterName() + " must specify the Azure endpoint (.codesigning.azure.net)"); - } - if (params.storepass() == null) { - throw new IllegalArgumentException("storepass " + params.parameterName() + " must specify the Azure API access token"); - } - } - - @Override - Provider getProvider(KeyStoreBuilder params) { - return new SigningServiceJcaProvider(new AzureTrustedSigningService(params.keystore(), params.storepass())); - } - }, - - GARASIGN(false, false, false) { - @Override - void validate(KeyStoreBuilder params) { - if (params.storepass() == null || params.storepass().split("\\|").length > 3) { - throw new IllegalArgumentException("storepass " + params.parameterName() + " must specify the GaraSign username/password and/or the path to the keystore containing the TLS client certificate: |, , or ||"); - } - } - - @Override - Provider getProvider(KeyStoreBuilder params) { - String[] elements = params.storepass().split("\\|"); - String username = null; - String password = null; - String certificate = null; - if (elements.length == 1) { - certificate = elements[0]; - } else if (elements.length == 2) { - username = elements[0]; - password = elements[1]; - } else if (elements.length == 3) { - username = elements[0]; - password = elements[1]; - certificate = elements[2]; - } - - GaraSignCredentials credentials = new GaraSignCredentials(username, password, certificate, params.keypass()); - return new SigningServiceJcaProvider(new GaraSignSigningService(params.keystore(), credentials)); - } - }, - - SIGNSERVER(false, false, false) { - @Override - void validate(KeyStoreBuilder params) { - if (params.storepass() != null && params.storepass().split("\\|").length > 2) { - throw new IllegalArgumentException("storepass " + params.parameterName() + " must specify the SignServer username/password or the path to the keystore containing the TLS client certificate: |, "); - } - } - - @Override - Provider getProvider(KeyStoreBuilder params) { - String username = null; - String password = null; - String certificate = null; - if (params.storepass() != null) { - String[] elements = params.storepass().split("\\|"); - if (elements.length == 1) { - certificate = elements[0]; - } else if (elements.length == 2) { - username = elements[0]; - password = elements[1]; - } - } - - SignServerCredentials credentials = new SignServerCredentials(username, password, certificate, params.keypass()); - return new SigningServiceJcaProvider(new SignServerSigningService(params.keystore(), credentials)); - } - }; - - - /** Tells if the keystore is contained in a local file */ - private final boolean fileBased; - - /** Tells if the keystore contains the certificate */ - private final boolean certificate; - - /** Tells if the keystore is actually a PKCS#11 keystore */ - private final boolean pkcs11; - - KeyStoreType(boolean fileBased, boolean certificate, boolean pkcs11) { - this.fileBased = fileBased; - this.certificate = certificate; - this.pkcs11 = pkcs11; - } + TRUSTEDSIGNING, - boolean hasCertificate() { - return certificate; - } - - /** - * Validates the keystore parameters. - */ - void validate(KeyStoreBuilder params) throws IllegalArgumentException { - } - - /** - * Returns the security provider to use the keystore. - */ - Provider getProvider(KeyStoreBuilder params) { - return null; - } - - /** - * Build the keystore. - */ - KeyStore getKeystore(KeyStoreBuilder params, Provider provider) throws KeyStoreException { - KeyStore ks; - try { - KeyStoreType storetype = pkcs11 ? PKCS11 : this; - if (provider != null) { - ks = KeyStore.getInstance(storetype.name(), provider); - } else { - ks = KeyStore.getInstance(storetype.name()); - } - } catch (KeyStoreException e) { - throw new KeyStoreException("keystore type '" + name() + "' is not supported" + (provider != null ? " with security provider " + provider.getName() : ""), e); - } - - if (fileBased && (params.keystore() == null || !params.createFile(params.keystore()).exists())) { - throw new KeyStoreException("The keystore " + params.keystore() + " couldn't be found"); - } - - try { - try (FileInputStream in = fileBased ? new FileInputStream(params.createFile(params.keystore())) : null) { - ks.load(in, params.storepass() != null ? params.storepass().toCharArray() : null); - } - } catch (Exception e) { - throw new KeyStoreException("Unable to load the " + name() + " keystore" + (params.keystore() != null ? " " + params.keystore() : ""), e); - } - - return ks; - } - - /** - * Returns the aliases of the keystore available for signing. - */ - Set getAliases(KeyStore keystore) throws KeyStoreException { - return new LinkedHashSet<>(Collections.list(keystore.aliases())); - } - - /** - * Tells if the keystore password can be reused as the key password. - */ - boolean reuseKeyStorePassword() { - return true; - } - - /** - * Guess the type of the keystore from the header or the extension of the file. - * - * @param path the path to the keystore - */ - static KeyStoreType of(File path) { - // guess the type of the keystore from the header of the file - if (path.exists()) { - try (FileInputStream in = new FileInputStream(path)) { - byte[] header = new byte[4]; - in.read(header); - ByteBuffer buffer = ByteBuffer.wrap(header); - if (buffer.get(0) == 0x30) { - return PKCS12; - } else if ((buffer.getInt(0) & 0xFFFFFFFFL) == 0xCECECECEL) { - return JCEKS; - } else if ((buffer.getInt(0) & 0xFFFFFFFFL) == 0xFEEDFEEDL) { - return JKS; - } - } catch (IOException e) { - throw new RuntimeException("Unable to load the keystore " + path, e); - } - } - - // guess the type of the keystore from the extension of the file - String filename = path.getName().toLowerCase(); - if (filename.endsWith(".p12") || filename.endsWith(".pfx")) { - return PKCS12; - } else if (filename.endsWith(".jceks")) { - return JCEKS; - } else if (filename.endsWith(".jks")) { - return JKS; - } else { - return null; - } - } + GARASIGN, - private static Function getCertificateStore(KeyStoreBuilder params) { - return alias -> { - if (alias == null || alias.isEmpty()) { - return null; - } + SIGNSERVER; - try { - return CertificateUtils.loadCertificateChain(params.certfile()); - } catch (IOException | CertificateException e) { - throw new RuntimeException("Failed to load the certificate from " + params.certfile(), e); - } - }; + JsignKeyStore getJsignKeyStore() { + return JsignKeyStoreDiscovery.getKeyStore(this); } } diff --git a/jsign-crypto/src/main/java/net/jsign/jca/JsignJcaProvider.java b/jsign-crypto/src/main/java/net/jsign/jca/JsignJcaProvider.java index 0c3adfa3..a30cd20a 100644 --- a/jsign-crypto/src/main/java/net/jsign/jca/JsignJcaProvider.java +++ b/jsign-crypto/src/main/java/net/jsign/jca/JsignJcaProvider.java @@ -35,13 +35,14 @@ import net.jsign.DigestAlgorithm; import net.jsign.KeyStoreBuilder; -import net.jsign.KeyStoreType; +import net.jsign.ks.JsignKeyStore; +import net.jsign.ks.JsignKeyStoreDiscovery; /** * JCA provider using a Jsign keystore and compatible with jarsigner and apksigner. * *

The provider must be configured with the keystore parameter (the value depends on the keystore type). - * The type of the keystore is one of the names from the {@link KeyStoreType} enum.

+ * The type of the keystore is one of the types from the {@link JsignKeyStore} implementations.

* *

Example:

*
@@ -68,8 +69,8 @@ public JsignJcaProvider() {
         super("Jsign", 1.0, "Jsign security provider");
 
         AccessController.doPrivileged((PrivilegedAction) () -> {
-            for (KeyStoreType type : KeyStoreType.values()) {
-                putService(new ProviderService(this, "KeyStore", type.name(), JsignJcaKeyStore.class.getName(), () -> new JsignJcaKeyStore(type, keystore)));
+            for (String type : JsignKeyStoreDiscovery.getKeyStoreTypes()) {
+                putService(new ProviderService(this, "KeyStore", type, JsignJcaKeyStore.class.getName(), () -> new JsignJcaKeyStore(JsignKeyStoreDiscovery.getKeyStore(type), keystore)));
             }
             for (String alg : new String[]{"RSA", "ECDSA"}) {
                 for (DigestAlgorithm digest : DigestAlgorithm.values()) {
@@ -99,7 +100,7 @@ static class JsignJcaKeyStore extends AbstractKeyStoreSpi {
         private KeyStoreBuilder builder = new KeyStoreBuilder();
         private KeyStore keystore;
 
-        public JsignJcaKeyStore(KeyStoreType type, String keystore) {
+        public JsignJcaKeyStore(JsignKeyStore type, String keystore) {
             builder.storetype(type);
             builder.keystore(keystore);
             builder.certfile("");
diff --git a/jsign-crypto/src/main/java/net/jsign/ks/AbstractJsignKeyStore.java b/jsign-crypto/src/main/java/net/jsign/ks/AbstractJsignKeyStore.java
new file mode 100644
index 00000000..64bc495d
--- /dev/null
+++ b/jsign-crypto/src/main/java/net/jsign/ks/AbstractJsignKeyStore.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2024 Björn Kautler
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.jsign.ks;
+
+import net.jsign.KeyStoreBuilder;
+
+import java.io.FileInputStream;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.Provider;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import static net.jsign.KeyStoreType.PKCS11;
+
+public abstract class AbstractJsignKeyStore implements JsignKeyStore {
+    /**
+     * Tells if the keystore is contained in a local file
+     */
+    private final boolean fileBased;
+
+    /**
+     * Tells if the keystore contains the certificate
+     */
+    private final boolean certificate;
+
+    /**
+     * Tells if the keystore is actually a PKCS#11 keystore
+     */
+    private final boolean pkcs11;
+
+    /**
+     * @param fileBased   tells if the keystore is contained in a local file
+     * @param certificate tells if the keystore contains the certificate
+     * @param pkcs11      tells if the keystore is actually a PKCS#11 keystore
+     */
+    public AbstractJsignKeyStore(boolean fileBased, boolean certificate, boolean pkcs11) {
+        this.fileBased = fileBased;
+        this.certificate = certificate;
+        this.pkcs11 = pkcs11;
+    }
+
+    @Override
+    public boolean hasCertificate() {
+        return certificate;
+    }
+
+    @Override
+    public void validate(KeyStoreBuilder params) throws IllegalArgumentException {
+    }
+
+    @Override
+    public Provider getProvider(KeyStoreBuilder params) {
+        return null;
+    }
+
+    @Override
+    public boolean reuseKeyStorePassword() {
+        return true;
+    }
+
+    @Override
+    public KeyStore getKeystore(KeyStoreBuilder params, Provider provider) throws KeyStoreException  {
+        KeyStore ks;
+        try {
+            JsignKeyStore storetype = pkcs11 ? JsignKeyStoreDiscovery.getKeyStore(PKCS11) : this;
+            if (provider != null) {
+                ks = KeyStore.getInstance(storetype.getType(), provider);
+            } else {
+                ks = KeyStore.getInstance(storetype.getType());
+            }
+        } catch (KeyStoreException e) {
+            throw new KeyStoreException("keystore type '" + getType() + "' is not supported" + (provider != null ? " with security provider " + provider.getName() : ""), e);
+        }
+
+        if (fileBased && (params.keystore() == null || !params.createFile(params.keystore()).exists())) {
+            throw new KeyStoreException("The keystore " + params.keystore() + " couldn't be found");
+        }
+
+        try {
+            try (FileInputStream in = fileBased ? new FileInputStream(params.createFile(params.keystore())) : null) {
+                ks.load(in, params.storepass() != null ? params.storepass().toCharArray() : null);
+            }
+        } catch (Exception e) {
+            throw new KeyStoreException("Unable to load the " + getType() + " keystore" + (params.keystore() != null ? " " + params.keystore() : ""), e);
+        }
+
+        return ks;
+    }
+
+    @Override
+    public Set getAliases(KeyStore keystore) throws KeyStoreException {
+        return new LinkedHashSet<>(Collections.list(keystore.aliases()));
+    }
+}
diff --git a/jsign-crypto/src/main/java/net/jsign/ks/AwsKeyStore.java b/jsign-crypto/src/main/java/net/jsign/ks/AwsKeyStore.java
new file mode 100644
index 00000000..4aff7afd
--- /dev/null
+++ b/jsign-crypto/src/main/java/net/jsign/ks/AwsKeyStore.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2024 Björn Kautler
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.jsign.ks;
+
+import net.jsign.KeyStoreBuilder;
+import net.jsign.jca.AmazonCredentials;
+import net.jsign.jca.AmazonSigningService;
+import net.jsign.jca.SigningServiceJcaProvider;
+
+import java.io.IOException;
+import java.net.UnknownServiceException;
+import java.security.Provider;
+
+import static net.jsign.ks.JsignKeyStore.getCertificateStore;
+
+/**
+ * AWS Key Management Service (KMS). AWS KMS stores only the private key, the certificate must be provided
+ * separately. The keystore parameter references the AWS region.
+ *
+ * 

The AWS access key, secret key, and optionally the session token, are concatenated and used as + * the storepass parameter; if the latter is not provided, Jsign attempts to fetch the credentials from + * the environment variables (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and + * AWS_SESSION_TOKEN) or from the IMDSv2 service when running on an AWS EC2 instance.

+ * + *

In any case, the credentials must allow the following actions: kms:ListKeys, + * kms:DescribeKey and kms:Sign.

+ */ +public class AwsKeyStore extends AbstractJsignKeyStore { + public AwsKeyStore() { + super(false, false, false); + } + + @Override + public String getType() { + return "AWS"; + } + + @Override + public void validate(KeyStoreBuilder params) throws IllegalArgumentException { + if (params.keystore() == null) { + throw new IllegalArgumentException("keystore " + params.parameterName() + " must specify the AWS region"); + } + if (params.certfile() == null) { + throw new IllegalArgumentException("certfile " + params.parameterName() + " must be set"); + } + } + + @Override + public Provider getProvider(KeyStoreBuilder params) { + AmazonCredentials credentials; + if (params.storepass() != null) { + credentials = AmazonCredentials.parse(params.storepass()); + } else { + try { + credentials = AmazonCredentials.getDefault(); + } catch (UnknownServiceException e) { + throw new IllegalArgumentException("storepass " + params.parameterName() + + " must specify the AWS credentials: |[|]" + + ", when not running from an EC2 instance (" + e.getMessage() + ")", e); + } catch (IOException e) { + throw new RuntimeException("An error occurred while fetching temporary credentials from IMDSv2 service", e); + } + } + + return new SigningServiceJcaProvider(new AmazonSigningService(params.keystore(), credentials, getCertificateStore(params))); + } +} diff --git a/jsign-crypto/src/main/java/net/jsign/ks/AzureKeyVaultKeyStore.java b/jsign-crypto/src/main/java/net/jsign/ks/AzureKeyVaultKeyStore.java new file mode 100644 index 00000000..a50a28ed --- /dev/null +++ b/jsign-crypto/src/main/java/net/jsign/ks/AzureKeyVaultKeyStore.java @@ -0,0 +1,54 @@ +/* + * Copyright 2024 Björn Kautler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.jsign.ks; + +import net.jsign.KeyStoreBuilder; +import net.jsign.jca.AzureKeyVaultSigningService; +import net.jsign.jca.SigningServiceJcaProvider; + +import java.security.Provider; + +/** + * Azure Key Vault. The keystore parameter specifies the name of the key vault, either the short name + * (e.g. myvault), or the full URL (e.g. https://myvault.vault.azure.net). + * The Azure API access token is used as the keystore password. + */ +public class AzureKeyVaultKeyStore extends AbstractJsignKeyStore { + public AzureKeyVaultKeyStore() { + super(false, true, false); + } + + @Override + public String getType() { + return "AZUREKEYVAULT"; + } + + @Override + public void validate(KeyStoreBuilder params) throws IllegalArgumentException { + if (params.keystore() == null) { + throw new IllegalArgumentException("keystore " + params.parameterName() + " must specify the Azure vault name"); + } + if (params.storepass() == null) { + throw new IllegalArgumentException("storepass " + params.parameterName() + " must specify the Azure API access token"); + } + } + + @Override + public Provider getProvider(KeyStoreBuilder params) { + return new SigningServiceJcaProvider(new AzureKeyVaultSigningService(params.keystore(), params.storepass())); + } +} diff --git a/jsign-crypto/src/main/java/net/jsign/ks/AzureTrustedSigningKeyStore.java b/jsign-crypto/src/main/java/net/jsign/ks/AzureTrustedSigningKeyStore.java new file mode 100644 index 00000000..5c7d9626 --- /dev/null +++ b/jsign-crypto/src/main/java/net/jsign/ks/AzureTrustedSigningKeyStore.java @@ -0,0 +1,56 @@ +/* + * Copyright 2024 Björn Kautler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.jsign.ks; + +import net.jsign.KeyStoreBuilder; +import net.jsign.jca.AzureTrustedSigningService; +import net.jsign.jca.SigningServiceJcaProvider; + +import java.security.Provider; + +/** + * Azure Trusted Signing Service. The keystore parameter specifies the API endpoint (for example + * weu.codesigning.azure.net). The Azure API access token is used as the keystore password, + * it can be obtained using the Azure CLI with: + * + *
  az account get-access-token --resource https://codesigning.azure.net
+ */ +public class AzureTrustedSigningKeyStore extends AbstractJsignKeyStore { + public AzureTrustedSigningKeyStore() { + super(false, false, false); + } + + @Override + public String getType() { + return "TRUSTEDSIGNING"; + } + + @Override + public void validate(KeyStoreBuilder params) throws IllegalArgumentException { + if (params.keystore() == null) { + throw new IllegalArgumentException("keystore " + params.parameterName() + " must specify the Azure endpoint (.codesigning.azure.net)"); + } + if (params.storepass() == null) { + throw new IllegalArgumentException("storepass " + params.parameterName() + " must specify the Azure API access token"); + } + } + + @Override + public Provider getProvider(KeyStoreBuilder params) { + return new SigningServiceJcaProvider(new AzureTrustedSigningService(params.keystore(), params.storepass())); + } +} diff --git a/jsign-crypto/src/main/java/net/jsign/ks/DigiCertOneKeyStore.java b/jsign-crypto/src/main/java/net/jsign/ks/DigiCertOneKeyStore.java new file mode 100644 index 00000000..1217e905 --- /dev/null +++ b/jsign-crypto/src/main/java/net/jsign/ks/DigiCertOneKeyStore.java @@ -0,0 +1,52 @@ +/* + * Copyright 2024 Björn Kautler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.jsign.ks; + +import net.jsign.KeyStoreBuilder; +import net.jsign.jca.DigiCertOneSigningService; +import net.jsign.jca.SigningServiceJcaProvider; + +import java.security.Provider; + +/** + * DigiCert ONE. Certificates and keys stored in the DigiCert ONE Secure Software Manager can be used directly + * without installing the DigiCert client tools. The API key, the PKCS#12 keystore holding the client certificate + * and its password are combined to form the storepass parameter: <api-key>|<keystore>|<password>. + */ +public class DigiCertOneKeyStore extends AbstractJsignKeyStore { + public DigiCertOneKeyStore() { + super(false, true, false); + } + + @Override + public String getType() { + return "DIGICERTONE"; + } + + @Override + public void validate(KeyStoreBuilder params) throws IllegalArgumentException { + if (params.storepass() == null || params.storepass().split("\\|").length != 3) { + throw new IllegalArgumentException("storepass " + params.parameterName() + " must specify the DigiCert ONE API key and the client certificate: ||"); + } + } + + @Override + public Provider getProvider(KeyStoreBuilder params) { + String[] elements = params.storepass().split("\\|"); + return new SigningServiceJcaProvider(new DigiCertOneSigningService(params.keystore(), elements[0], params.createFile(elements[1]), elements[2])); + } +} diff --git a/jsign-crypto/src/main/java/net/jsign/ks/ESignerKeyStore.java b/jsign-crypto/src/main/java/net/jsign/ks/ESignerKeyStore.java new file mode 100644 index 00000000..db051163 --- /dev/null +++ b/jsign-crypto/src/main/java/net/jsign/ks/ESignerKeyStore.java @@ -0,0 +1,62 @@ +/* + * Copyright 2024 Björn Kautler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.jsign.ks; + +import net.jsign.KeyStoreBuilder; +import net.jsign.jca.ESignerSigningService; +import net.jsign.jca.SigningServiceJcaProvider; + +import java.io.IOException; +import java.security.Provider; + +/** + * SSL.com eSigner. The SSL.com username and password are used as the keystore password (<username>|<password>), + * and the base64 encoded TOTP secret is used as the key password. + */ +public class ESignerKeyStore extends AbstractJsignKeyStore { + public ESignerKeyStore() { + super(false, true, false); + } + + @Override + public String getType() { + return "ESIGNER"; + } + + @Override + public void validate(KeyStoreBuilder params) throws IllegalArgumentException { + if (params.storepass() == null || !params.storepass().contains("|")) { + throw new IllegalArgumentException("storepass " + params.parameterName() + " must specify the SSL.com username and password: |"); + } + } + + @Override + public Provider getProvider(KeyStoreBuilder params) { + String[] elements = params.storepass().split("\\|", 2); + String endpoint = params.keystore() != null ? params.keystore() : "https://cs.ssl.com"; + try { + return new SigningServiceJcaProvider(new ESignerSigningService(endpoint, elements[0], elements[1])); + } catch (IOException e) { + throw new IllegalStateException("Authentication failed with SSL.com", e); + } + } + + @Override + public boolean reuseKeyStorePassword() { + return false; + } +} diff --git a/jsign-crypto/src/main/java/net/jsign/ks/GaraSignKeyStore.java b/jsign-crypto/src/main/java/net/jsign/ks/GaraSignKeyStore.java new file mode 100644 index 00000000..300e2103 --- /dev/null +++ b/jsign-crypto/src/main/java/net/jsign/ks/GaraSignKeyStore.java @@ -0,0 +1,63 @@ +/* + * Copyright 2024 Björn Kautler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.jsign.ks; + +import net.jsign.KeyStoreBuilder; +import net.jsign.jca.GaraSignCredentials; +import net.jsign.jca.GaraSignSigningService; +import net.jsign.jca.SigningServiceJcaProvider; + +import java.security.Provider; + +public class GaraSignKeyStore extends AbstractJsignKeyStore { + public GaraSignKeyStore() { + super(false, false, false); + } + + @Override + public String getType() { + return "GARASIGN"; + } + + @Override + public void validate(KeyStoreBuilder params) throws IllegalArgumentException { + if (params.storepass() == null || params.storepass().split("\\|").length > 3) { + throw new IllegalArgumentException("storepass " + params.parameterName() + " must specify the GaraSign username/password and/or the path to the keystore containing the TLS client certificate: |, , or ||"); + } + } + + @Override + public Provider getProvider(KeyStoreBuilder params) { + String[] elements = params.storepass().split("\\|"); + String username = null; + String password = null; + String certificate = null; + if (elements.length == 1) { + certificate = elements[0]; + } else if (elements.length == 2) { + username = elements[0]; + password = elements[1]; + } else if (elements.length == 3) { + username = elements[0]; + password = elements[1]; + certificate = elements[2]; + } + + GaraSignCredentials credentials = new GaraSignCredentials(username, password, certificate, params.keypass()); + return new SigningServiceJcaProvider(new GaraSignSigningService(params.keystore(), credentials)); + } +} diff --git a/jsign-crypto/src/main/java/net/jsign/ks/GoogleCloudKeyStore.java b/jsign-crypto/src/main/java/net/jsign/ks/GoogleCloudKeyStore.java new file mode 100644 index 00000000..60bad3a2 --- /dev/null +++ b/jsign-crypto/src/main/java/net/jsign/ks/GoogleCloudKeyStore.java @@ -0,0 +1,62 @@ +/* + * Copyright 2024 Björn Kautler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.jsign.ks; + +import net.jsign.KeyStoreBuilder; +import net.jsign.jca.GoogleCloudSigningService; +import net.jsign.jca.SigningServiceJcaProvider; + +import java.security.Provider; + +import static net.jsign.ks.JsignKeyStore.getCertificateStore; + +/** + * Google Cloud KMS. Google Cloud KMS stores only the private key, the certificate must be provided separately. + * The keystore parameter references the path of the keyring. The alias can specify either the full path of the key, + * or only the short name. If the version is omitted the most recent one will be picked automatically. + */ +public class GoogleCloudKeyStore extends AbstractJsignKeyStore { + public GoogleCloudKeyStore() { + super(false, false, false); + } + + @Override + public String getType() { + return "GOOGLECLOUD"; + } + + @Override + public void validate(KeyStoreBuilder params) throws IllegalArgumentException { + if (params.keystore() == null) { + throw new IllegalArgumentException("keystore " + params.parameterName() + " must specify the Goole Cloud keyring"); + } + if (!params.keystore().matches("projects/[^/]+/locations/[^/]+/keyRings/[^/]+")) { + throw new IllegalArgumentException("keystore " + params.parameterName() + " must specify the path of the keyring (projects/{projectName}/locations/{location}/keyRings/{keyringName})"); + } + if (params.storepass() == null) { + throw new IllegalArgumentException("storepass " + params.parameterName() + " must specify the Goole Cloud API access token"); + } + if (params.certfile() == null) { + throw new IllegalArgumentException("certfile " + params.parameterName() + " must be set"); + } + } + + @Override + public Provider getProvider(KeyStoreBuilder params) { + return new SigningServiceJcaProvider(new GoogleCloudSigningService(params.keystore(), params.storepass(), getCertificateStore(params))); + } +} diff --git a/jsign-crypto/src/main/java/net/jsign/ks/HashiCorpVaultKeyStore.java b/jsign-crypto/src/main/java/net/jsign/ks/HashiCorpVaultKeyStore.java new file mode 100644 index 00000000..85a2668b --- /dev/null +++ b/jsign-crypto/src/main/java/net/jsign/ks/HashiCorpVaultKeyStore.java @@ -0,0 +1,60 @@ +/* + * Copyright 2024 Björn Kautler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.jsign.ks; + +import net.jsign.KeyStoreBuilder; +import net.jsign.jca.HashiCorpVaultSigningService; +import net.jsign.jca.SigningServiceJcaProvider; + +import java.security.Provider; + +import static net.jsign.ks.JsignKeyStore.getCertificateStore; + +/** + * HashiCorp Vault secrets engine (Transit or GCPKMS). The certificate must be provided separately. The keystore + * parameter references the URL of the HashiCorp Vault secrets engine (https://vault.example.com/v1/gcpkms). + * The alias parameter specifies the name of the key in Vault. For the Google Cloud KMS secrets engine, the version + * of the Google Cloud key is appended to the key name, separated by a colon character. (mykey:1). + */ +public class HashiCorpVaultKeyStore extends AbstractJsignKeyStore { + public HashiCorpVaultKeyStore() { + super(false, false, false); + } + + @Override + public String getType() { + return "HASHICORPVAULT"; + } + + @Override + public void validate(KeyStoreBuilder params) throws IllegalArgumentException { + if (params.keystore() == null) { + throw new IllegalArgumentException("keystore " + params.parameterName() + " must specify the HashiCorp Vault secrets engine URL"); + } + if (params.storepass() == null) { + throw new IllegalArgumentException("storepass " + params.parameterName() + " must specify the HashiCorp Vault token"); + } + if (params.certfile() == null) { + throw new IllegalArgumentException("certfile " + params.parameterName() + " must be set"); + } + } + + @Override + public Provider getProvider(KeyStoreBuilder params) { + return new SigningServiceJcaProvider(new HashiCorpVaultSigningService(params.keystore(), params.storepass(), getCertificateStore(params))); + } +} diff --git a/jsign-crypto/src/main/java/net/jsign/ks/JavaKeyStore.java b/jsign-crypto/src/main/java/net/jsign/ks/JavaKeyStore.java new file mode 100644 index 00000000..4e8df060 --- /dev/null +++ b/jsign-crypto/src/main/java/net/jsign/ks/JavaKeyStore.java @@ -0,0 +1,40 @@ +/* + * Copyright 2024 Björn Kautler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.jsign.ks; + +import net.jsign.KeyStoreBuilder; + +/** + * Java keystore + */ +public class JavaKeyStore extends AbstractJsignKeyStore { + public JavaKeyStore() { + super(true, true, false); + } + + @Override + public String getType() { + return "JKS"; + } + + @Override + public void validate(KeyStoreBuilder params) throws IllegalArgumentException { + if (params.keystore() == null) { + throw new IllegalArgumentException("keystore " + params.parameterName() + " must be set"); + } + } +} diff --git a/jsign-crypto/src/main/java/net/jsign/ks/JceKeyStore.java b/jsign-crypto/src/main/java/net/jsign/ks/JceKeyStore.java new file mode 100644 index 00000000..4c902a18 --- /dev/null +++ b/jsign-crypto/src/main/java/net/jsign/ks/JceKeyStore.java @@ -0,0 +1,40 @@ +/* + * Copyright 2024 Björn Kautler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.jsign.ks; + +import net.jsign.KeyStoreBuilder; + +/** + * JCE keystore + */ +public class JceKeyStore extends AbstractJsignKeyStore { + public JceKeyStore() { + super(true, true, false); + } + + @Override + public String getType() { + return "JCEKS"; + } + + @Override + public void validate(KeyStoreBuilder params) throws IllegalArgumentException { + if (params.keystore() == null) { + throw new IllegalArgumentException("keystore " + params.parameterName() + " must be set"); + } + } +} diff --git a/jsign-crypto/src/main/java/net/jsign/ks/JsignKeyStore.java b/jsign-crypto/src/main/java/net/jsign/ks/JsignKeyStore.java new file mode 100644 index 00000000..bd54c481 --- /dev/null +++ b/jsign-crypto/src/main/java/net/jsign/ks/JsignKeyStore.java @@ -0,0 +1,126 @@ +/* + * Copyright 2024 Björn Kautler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.jsign.ks; + +import net.jsign.CertificateUtils; +import net.jsign.KeyStoreBuilder; +import net.jsign.KeyStoreType; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.Provider; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.util.Set; +import java.util.function.Function; + +import static net.jsign.KeyStoreType.JCEKS; +import static net.jsign.KeyStoreType.JKS; +import static net.jsign.KeyStoreType.PKCS12; + +public interface JsignKeyStore { + + /** + * The keystore type identifier used to select a specific keystore type. + */ + String getType(); + + /** + * Tells if the keystore contains the certificate. + */ + boolean hasCertificate(); + + /** + * Validates the keystore parameters. + */ + void validate(KeyStoreBuilder params) throws IllegalArgumentException; + + /** + * Returns the security provider to use the keystore. + */ + Provider getProvider(KeyStoreBuilder params); + + /** + * Build the keystore. + */ + KeyStore getKeystore(KeyStoreBuilder params, Provider provider) throws KeyStoreException; + + /** + * Returns the aliases of the keystore available for signing. + */ + Set getAliases(KeyStore keystore) throws KeyStoreException; + + /** + * Tells if the keystore password can be reused as the key password. + */ + boolean reuseKeyStorePassword(); + + static Function getCertificateStore(KeyStoreBuilder params) { + return alias -> { + if (alias == null || alias.isEmpty()) { + return null; + } + + try { + return CertificateUtils.loadCertificateChain(params.certfile()); + } catch (IOException | CertificateException e) { + throw new RuntimeException("Failed to load the certificate from " + params.certfile(), e); + } + }; + } + + /** + * Guess the type of the keystore from the header or the extension of the file. + * + * @param path the path to the keystore + */ + static JsignKeyStore of(File path) { + // guess the type of the keystore from the header of the file + if (path.exists()) { + try (FileInputStream in = new FileInputStream(path)) { + byte[] header = new byte[4]; + in.read(header); + ByteBuffer buffer = ByteBuffer.wrap(header); + if (buffer.get(0) == 0x30) { + return JsignKeyStoreDiscovery.getKeyStore(PKCS12); + } else if ((buffer.getInt(0) & 0xFFFFFFFFL) == 0xCECECECEL) { + return JsignKeyStoreDiscovery.getKeyStore(JCEKS); + } else if ((buffer.getInt(0) & 0xFFFFFFFFL) == 0xFEEDFEEDL) { + return JsignKeyStoreDiscovery.getKeyStore(JKS); + } + } catch (IOException e) { + throw new RuntimeException("Unable to load the keystore " + path, e); + } + } + + // guess the type of the keystore from the extension of the file + String filename = path.getName().toLowerCase(); + if (filename.endsWith(".p12") || filename.endsWith(".pfx")) { + return JsignKeyStoreDiscovery.getKeyStore(PKCS12); + } else if (filename.endsWith(".jceks")) { + return JsignKeyStoreDiscovery.getKeyStore(JCEKS); + } else if (filename.endsWith(".jks")) { + return JsignKeyStoreDiscovery.getKeyStore(JKS); + } else { + return null; + } + } +} diff --git a/jsign-crypto/src/main/java/net/jsign/ks/JsignKeyStoreDiscovery.java b/jsign-crypto/src/main/java/net/jsign/ks/JsignKeyStoreDiscovery.java new file mode 100644 index 00000000..9e2053f2 --- /dev/null +++ b/jsign-crypto/src/main/java/net/jsign/ks/JsignKeyStoreDiscovery.java @@ -0,0 +1,53 @@ +/* + * Copyright 2024 Björn Kautler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.jsign.ks; + +import net.jsign.KeyStoreType; + +import java.util.HashMap; +import java.util.Map; +import java.util.ServiceLoader; +import java.util.Set; + +public class JsignKeyStoreDiscovery { + private static Map keyStoresByType = new HashMap<>(); + + static { + Map keyStoresByType = new HashMap<>(); + for (JsignKeyStore keyStore : ServiceLoader.load(JsignKeyStore.class)) { + if (keyStoresByType.put(keyStore.getType(), keyStore) != null) { + throw new IllegalStateException("Duplicate key store type: " + keyStore.getType()); + } + } + JsignKeyStoreDiscovery.keyStoresByType = keyStoresByType; + } + + private JsignKeyStoreDiscovery() { + } + + public static JsignKeyStore getKeyStore(KeyStoreType type) { + return keyStoresByType.get(type.name()); + } + + public static JsignKeyStore getKeyStore(String type) { + return keyStoresByType.get(type); + } + + public static Set getKeyStoreTypes() { + return keyStoresByType.keySet(); + } +} diff --git a/jsign-crypto/src/main/java/net/jsign/ks/NitroKeyKeyStore.java b/jsign-crypto/src/main/java/net/jsign/ks/NitroKeyKeyStore.java new file mode 100644 index 00000000..d3270e1d --- /dev/null +++ b/jsign-crypto/src/main/java/net/jsign/ks/NitroKeyKeyStore.java @@ -0,0 +1,43 @@ +/* + * Copyright 2024 Björn Kautler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.jsign.ks; + +import net.jsign.KeyStoreBuilder; + +import java.security.Provider; + +/** + * Nitrokey HSM. This keystore requires the installation of OpenSC. + * Other Nitrokeys based on the OpenPGP card standard are also supported with this storetype, but an X.509 + * certificate must be imported into the Nitrokey (using the gnupg writecert command). Keys without certificates + * are ignored. Otherwise, the {@link OpenPGPKeyStore} type should be used. + */ +public class NitroKeyKeyStore extends AbstractJsignKeyStore { + public NitroKeyKeyStore() { + super(false, true, true); + } + + @Override + public String getType() { + return "NITROKEY"; + } + + @Override + public Provider getProvider(KeyStoreBuilder params) { + return OpenSCKeyStore.getProvider(params.keystore() != null ? params.keystore() : "Nitrokey"); + } +} diff --git a/jsign-crypto/src/main/java/net/jsign/ks/NoneKeyStore.java b/jsign-crypto/src/main/java/net/jsign/ks/NoneKeyStore.java new file mode 100644 index 00000000..aa17243d --- /dev/null +++ b/jsign-crypto/src/main/java/net/jsign/ks/NoneKeyStore.java @@ -0,0 +1,91 @@ +/* + * Copyright 2024 Björn Kautler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.jsign.ks; + +import net.jsign.CertificateUtils; +import net.jsign.KeyStoreBuilder; +import net.jsign.PrivateKeyUtils; + +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.PrivateKey; +import java.security.Provider; +import java.security.cert.Certificate; + +/** + * Not a keystore, a private key file and a certificate file are provided separately and assembled into an in-memory keystore + */ +public class NoneKeyStore extends AbstractJsignKeyStore { + public NoneKeyStore() { + super(true, false, false); + } + + @Override + public String getType() { + return "NONE"; + } + + @Override + public void validate(KeyStoreBuilder params) throws IllegalArgumentException { + if (params.keyfile() == null) { + throw new IllegalArgumentException("keyfile " + params.parameterName() + " must be set"); + } + if (!params.keyfile().exists()) { + throw new IllegalArgumentException("The keyfile " + params.keyfile() + " couldn't be found"); + } + if (params.certfile() == null) { + throw new IllegalArgumentException("certfile " + params.parameterName() + " must be set"); + } + if (!params.certfile().exists()) { + throw new IllegalArgumentException("The certfile " + params.certfile() + " couldn't be found"); + } + } + + @Override + public KeyStore getKeystore(KeyStoreBuilder params, Provider provider) throws KeyStoreException { + // load the certificate chain + Certificate[] chain; + try { + chain = CertificateUtils.loadCertificateChain(params.certfile()); + } catch (Exception e) { + throw new KeyStoreException("Failed to load the certificate from " + params.certfile(), e); + } + + // load the private key + PrivateKey privateKey; + try { + privateKey = PrivateKeyUtils.load(params.keyfile(), params.keypass() != null ? params.keypass() : params.storepass()); + } catch (Exception e) { + throw new KeyStoreException("Failed to load the private key from " + params.keyfile(), e); + } + + // build the in-memory keystore + KeyStore ks = KeyStore.getInstance("JKS"); + try { + ks.load(null, null); + String keypass = params.keypass(); + if (keypass == null) { + keypass = params.storepass(); + } + ks.setKeyEntry("jsign", privateKey, keypass != null ? keypass.toCharArray() : new char[0], chain); + } catch (Exception e) { + throw new KeyStoreException(e); + } + + return ks; + } +} diff --git a/jsign-crypto/src/main/java/net/jsign/ks/OpenPGPKeyStore.java b/jsign-crypto/src/main/java/net/jsign/ks/OpenPGPKeyStore.java new file mode 100644 index 00000000..630a2294 --- /dev/null +++ b/jsign-crypto/src/main/java/net/jsign/ks/OpenPGPKeyStore.java @@ -0,0 +1,61 @@ +/* + * Copyright 2024 Björn Kautler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.jsign.ks; + +import net.jsign.KeyStoreBuilder; +import net.jsign.jca.OpenPGPCardSigningService; +import net.jsign.jca.SigningServiceJcaProvider; + +import javax.smartcardio.CardException; +import java.security.Provider; + +import static net.jsign.ks.JsignKeyStore.getCertificateStore; + +/** + * OpenPGP card. OpenPGP cards contain up to 3 keys, one for signing, one for encryption, and one for authentication. + * All of them can be used for code signing (except encryption keys based on an elliptic curve). The alias + * to select the key is either, SIGNATURE, ENCRYPTION or AUTHENTICATION. + * This keystore can be used with a Nitrokey (non-HSM models) or a Yubikey. If multiple devices are connected, + * the keystore parameter can be used to specify the name of the one to use. This keystore type doesn't require + * any external library to be installed. + */ +public class OpenPGPKeyStore extends AbstractJsignKeyStore { + public OpenPGPKeyStore() { + super(false, false, false); + } + + @Override + public String getType() { + return "OPENPGP"; + } + + @Override + public void validate(KeyStoreBuilder params) throws IllegalArgumentException { + if (params.storepass() == null) { + throw new IllegalArgumentException("storepass " + params.parameterName() + " must specify the PIN"); + } + } + + @Override + public Provider getProvider(KeyStoreBuilder params) { + try { + return new SigningServiceJcaProvider(new OpenPGPCardSigningService(params.keystore(), params.storepass(), params.certfile() != null ? getCertificateStore(params) : null)); + } catch (CardException e) { + throw new IllegalStateException("Failed to initialize the OpenPGP card", e); + } + } +} diff --git a/jsign-crypto/src/main/java/net/jsign/OpenSC.java b/jsign-crypto/src/main/java/net/jsign/ks/OpenSCKeyStore.java similarity index 87% rename from jsign-crypto/src/main/java/net/jsign/OpenSC.java rename to jsign-crypto/src/main/java/net/jsign/ks/OpenSCKeyStore.java index 17f508e3..4e757a7c 100644 --- a/jsign-crypto/src/main/java/net/jsign/OpenSC.java +++ b/jsign-crypto/src/main/java/net/jsign/ks/OpenSCKeyStore.java @@ -1,162 +1,176 @@ -/** - * Copyright 2023 Emmanuel Bourg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.jsign; - -import java.io.File; -import java.io.IOException; -import java.security.Provider; -import java.security.ProviderException; -import java.util.ArrayList; -import java.util.List; - -import sun.security.pkcs11.wrapper.CK_SLOT_INFO; -import sun.security.pkcs11.wrapper.CK_TOKEN_INFO; -import sun.security.pkcs11.wrapper.PKCS11; -import sun.security.pkcs11.wrapper.PKCS11Exception; - -/** - * Helper class for working with OpenSC. - * - * @since 5.0 - */ -class OpenSC { - - /** - * Returns the security provider for OpenSC. - * - * @param name the name of the token - * @return the OpenSC security provider - * @throws ProviderException thrown if the provider can't be initialized - */ - static Provider getProvider(String name) { - return ProviderUtils.createSunPKCS11Provider(getSunPKCS11Configuration(name)); - } - - /** - * Returns the SunPKCS11 configuration for OpenSC. - * - * @param name the name or the slot id of the token - * @throws ProviderException thrown if the PKCS11 modules cannot be found - */ - static String getSunPKCS11Configuration(String name) { - File library = getOpenSCLibrary(); - if (!library.exists()) { - throw new ProviderException("OpenSC PKCS11 module is not installed (" + library + " is missing)"); - } - String configuration = "--name=opensc\nlibrary = \"" + library.getAbsolutePath().replace("\\", "\\\\") + "\"\n"; - try { - long slot; - try { - slot = Integer.parseInt(name); - } catch (Exception e) { - slot = getTokenSlot(library, name); - } - if (slot >= 0) { - configuration += "slot=" + slot; - } - } catch (Exception e) { - throw new ProviderException(e); - } - return configuration; - } - - /** - * Returns the slot index associated to the token. - * - * @param libraryPath the path to the PKCS11 library - * @param name the partial name of the token - */ - static long getTokenSlot(File libraryPath, String name) throws PKCS11Exception, IOException { - PKCS11 pkcs11 = PKCS11.getInstance(libraryPath.getAbsolutePath(), "C_GetFunctionList", null, false); - long[] slots = pkcs11.C_GetSlotList(true); - - List descriptions = new ArrayList<>(); - List matches = new ArrayList<>(); - for (long slot : slots) { - CK_SLOT_INFO info = pkcs11.C_GetSlotInfo(slot); - String description = new String(info.slotDescription).trim(); - if (name == null || description.toLowerCase().contains(name.toLowerCase())) { - CK_TOKEN_INFO tokenInfo = pkcs11.C_GetTokenInfo(slot); - String label = new String(tokenInfo.label).trim(); - if (label.equals("OpenPGP card (User PIN (sig))")) { - // OpenPGP cards such as the Nitrokey 3 are exposed as two slots with the same name by OpenSC. - // Only the first one contains the signing key and the certificate, so the second one is ignored. - continue; - } - - matches.add(slot); - } - descriptions.add(description); - } - - if (matches.size() == 1) { - return matches.get(0); - } - - if (matches.isEmpty()) { - throw new RuntimeException(descriptions.isEmpty() ? "No PKCS11 token found" : "No PKCS11 token found matching '" + name + "' (available tokens: " + String.join(", ", descriptions) + ")"); - } else { - throw new RuntimeException("Multiple PKCS11 tokens found" + (name != null ? " matching '" + name + "'" : "") + ", please specify the name of the token to use (available tokens: " + String.join(", ", descriptions) + ")"); - } - } - - /** - * Attempts to locate the opensc-pkcs11 library on the system. - */ - static File getOpenSCLibrary() { - String osname = System.getProperty("os.name"); - String arch = System.getProperty("sun.arch.data.model"); - - if (osname.contains("Windows")) { - String programfiles; - if ("32".equals(arch) && System.getenv("ProgramFiles(x86)") != null) { - programfiles = System.getenv("ProgramFiles(x86)"); - } else { - programfiles = System.getenv("ProgramFiles"); - } - return new File(programfiles + "/OpenSC Project/OpenSC/pkcs11/opensc-pkcs11.dll"); - - } else if (osname.contains("Mac")) { - return new File("/Library/OpenSC/lib/opensc-pkcs11.so"); - - } else { - // Linux - List paths = new ArrayList<>(); - if ("32".equals(arch)) { - paths.add("/usr/lib/opensc-pkcs11.so"); - paths.add("/usr/lib/i386-linux-gnu/opensc-pkcs11.so"); - paths.add("/usr/lib/arm-linux-gnueabi/opensc-pkcs11.so"); - paths.add("/usr/lib/arm-linux-gnueabihf/opensc-pkcs11.so"); - } else { - paths.add("/usr/lib64/opensc-pkcs11.so"); - paths.add("/usr/lib/x86_64-linux-gnu/opensc-pkcs11.so"); - paths.add("/usr/lib/aarch64-linux-gnu/opensc-pkcs11.so"); - paths.add("/usr/lib/mips64el-linux-gnuabi64/opensc-pkcs11.so"); - paths.add("/usr/lib/riscv64-linux-gnu/opensc-pkcs11.so"); - } - - for (String path : paths) { - File library = new File(path); - if (library.exists()) { - return library; - } - } - - return new File("/usr/local/lib/opensc-pkcs11.so"); - } - } -} +/* + * Copyright 2024 Björn Kautler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.jsign.ks; + +import net.jsign.KeyStoreBuilder; +import sun.security.pkcs11.wrapper.CK_SLOT_INFO; +import sun.security.pkcs11.wrapper.CK_TOKEN_INFO; +import sun.security.pkcs11.wrapper.PKCS11; +import sun.security.pkcs11.wrapper.PKCS11Exception; + +import java.io.File; +import java.io.IOException; +import java.security.Provider; +import java.security.ProviderException; +import java.util.ArrayList; +import java.util.List; + +/** + * OpenSC supported smart card. + * This keystore requires the installation of OpenSC. + * If multiple devices are connected, the keystore parameter can be used to specify the name of the one to use. + */ +public class OpenSCKeyStore extends AbstractJsignKeyStore { + public OpenSCKeyStore() { + super(false, true, true); + } + + @Override + public String getType() { + return "OPENSC"; + } + + @Override + public Provider getProvider(KeyStoreBuilder params) { + return getProvider(params.keystore()); + } + + /** + * Returns the security provider for OpenSC. + * + * @param name the name of the token + * @return the OpenSC security provider + * @throws ProviderException thrown if the provider can't be initialized + */ + static Provider getProvider(String name) { + return ProviderUtils.createSunPKCS11Provider(getSunPKCS11Configuration(name)); + } + + /** + * Returns the SunPKCS11 configuration for OpenSC. + * + * @param name the name or the slot id of the token + * @throws ProviderException thrown if the PKCS11 modules cannot be found + */ + static String getSunPKCS11Configuration(String name) { + File library = getOpenSCLibrary(); + if (!library.exists()) { + throw new ProviderException("OpenSC PKCS11 module is not installed (" + library + " is missing)"); + } + String configuration = "--name=opensc\nlibrary = \"" + library.getAbsolutePath().replace("\\", "\\\\") + "\"\n"; + try { + long slot; + try { + slot = Integer.parseInt(name); + } catch (Exception e) { + slot = getTokenSlot(library, name); + } + if (slot >= 0) { + configuration += "slot=" + slot; + } + } catch (Exception e) { + throw new ProviderException(e); + } + return configuration; + } + + /** + * Returns the slot index associated to the token. + * + * @param libraryPath the path to the PKCS11 library + * @param name the partial name of the token + */ + static long getTokenSlot(File libraryPath, String name) throws PKCS11Exception, IOException { + PKCS11 pkcs11 = PKCS11.getInstance(libraryPath.getAbsolutePath(), "C_GetFunctionList", null, false); + long[] slots = pkcs11.C_GetSlotList(true); + + List descriptions = new ArrayList<>(); + List matches = new ArrayList<>(); + for (long slot : slots) { + CK_SLOT_INFO info = pkcs11.C_GetSlotInfo(slot); + String description = new String(info.slotDescription).trim(); + if (name == null || description.toLowerCase().contains(name.toLowerCase())) { + CK_TOKEN_INFO tokenInfo = pkcs11.C_GetTokenInfo(slot); + String label = new String(tokenInfo.label).trim(); + if (label.equals("OpenPGP card (User PIN (sig))")) { + // OpenPGP cards such as the Nitrokey 3 are exposed as two slots with the same name by OpenSC. + // Only the first one contains the signing key and the certificate, so the second one is ignored. + continue; + } + + matches.add(slot); + } + descriptions.add(description); + } + + if (matches.size() == 1) { + return matches.get(0); + } + + if (matches.isEmpty()) { + throw new RuntimeException(descriptions.isEmpty() ? "No PKCS11 token found" : "No PKCS11 token found matching '" + name + "' (available tokens: " + String.join(", ", descriptions) + ")"); + } else { + throw new RuntimeException("Multiple PKCS11 tokens found" + (name != null ? " matching '" + name + "'" : "") + ", please specify the name of the token to use (available tokens: " + String.join(", ", descriptions) + ")"); + } + } + + /** + * Attempts to locate the opensc-pkcs11 library on the system. + */ + static File getOpenSCLibrary() { + String osname = System.getProperty("os.name"); + String arch = System.getProperty("sun.arch.data.model"); + + if (osname.contains("Windows")) { + String programfiles; + if ("32".equals(arch) && System.getenv("ProgramFiles(x86)") != null) { + programfiles = System.getenv("ProgramFiles(x86)"); + } else { + programfiles = System.getenv("ProgramFiles"); + } + return new File(programfiles + "/OpenSC Project/OpenSC/pkcs11/opensc-pkcs11.dll"); + + } else if (osname.contains("Mac")) { + return new File("/Library/OpenSC/lib/opensc-pkcs11.so"); + + } else { + // Linux + List paths = new ArrayList<>(); + if ("32".equals(arch)) { + paths.add("/usr/lib/opensc-pkcs11.so"); + paths.add("/usr/lib/i386-linux-gnu/opensc-pkcs11.so"); + paths.add("/usr/lib/arm-linux-gnueabi/opensc-pkcs11.so"); + paths.add("/usr/lib/arm-linux-gnueabihf/opensc-pkcs11.so"); + } else { + paths.add("/usr/lib64/opensc-pkcs11.so"); + paths.add("/usr/lib/x86_64-linux-gnu/opensc-pkcs11.so"); + paths.add("/usr/lib/aarch64-linux-gnu/opensc-pkcs11.so"); + paths.add("/usr/lib/mips64el-linux-gnuabi64/opensc-pkcs11.so"); + paths.add("/usr/lib/riscv64-linux-gnu/opensc-pkcs11.so"); + } + + for (String path : paths) { + File library = new File(path); + if (library.exists()) { + return library; + } + } + + return new File("/usr/local/lib/opensc-pkcs11.so"); + } + } +} diff --git a/jsign-crypto/src/main/java/net/jsign/ks/OracleCloudKeyStore.java b/jsign-crypto/src/main/java/net/jsign/ks/OracleCloudKeyStore.java new file mode 100644 index 00000000..588ff1ec --- /dev/null +++ b/jsign-crypto/src/main/java/net/jsign/ks/OracleCloudKeyStore.java @@ -0,0 +1,82 @@ +/* + * Copyright 2024 Björn Kautler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.jsign.ks; + +import net.jsign.KeyStoreBuilder; +import net.jsign.jca.OracleCloudCredentials; +import net.jsign.jca.OracleCloudSigningService; +import net.jsign.jca.SigningServiceJcaProvider; + +import java.io.File; +import java.io.IOException; +import java.security.Provider; + +import static net.jsign.ks.JsignKeyStore.getCertificateStore; + +/** + * Oracle Cloud Infrastructure Key Management Service. This keystore requires the configuration file + * or the environment + * variables used by the OCI CLI. The storepass parameter specifies the path to the configuration file + * (~/.oci/config by default). If the configuration file contains multiple profiles, the name of the + * non-default profile to use is appended to the storepass (for example ~/.oci/config|PROFILE). + * The keypass parameter may be used to specify the passphrase of the key file used for signing the requests to + * the OCI API if it isn't set in the configuration file. + * + *

The certificate must be provided separately using the certfile parameter. The alias specifies the OCID + * of the key.

+ */ +public class OracleCloudKeyStore extends AbstractJsignKeyStore { + public OracleCloudKeyStore() { + super(false, false, false); + } + + @Override + public String getType() { + return "ORACLECLOUD"; + } + + @Override + public void validate(KeyStoreBuilder params) throws IllegalArgumentException { + if (params.certfile() == null) { + throw new IllegalArgumentException("certfile " + params.parameterName() + " must be set"); + } + } + + @Override + public Provider getProvider(KeyStoreBuilder params) { + OracleCloudCredentials credentials = new OracleCloudCredentials(); + try { + File config = null; + String profile = null; + if (params.storepass() != null) { + String[] elements = params.storepass().split("\\|", 2); + config = new File(elements[0]); + if (elements.length > 1) { + profile = elements[1]; + } + } + credentials.load(config, profile); + credentials.loadFromEnvironment(); + if (params.keypass() != null) { + credentials.setPassphrase(params.keypass()); + } + } catch (IOException e) { + throw new RuntimeException("An error occurred while fetching the Oracle Cloud credentials", e); + } + return new SigningServiceJcaProvider(new OracleCloudSigningService(credentials, getCertificateStore(params))); + } +} diff --git a/jsign-crypto/src/main/java/net/jsign/ks/PivKeyStore.java b/jsign-crypto/src/main/java/net/jsign/ks/PivKeyStore.java new file mode 100644 index 00000000..b4e0137f --- /dev/null +++ b/jsign-crypto/src/main/java/net/jsign/ks/PivKeyStore.java @@ -0,0 +1,60 @@ +/* + * Copyright 2024 Björn Kautler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.jsign.ks; + +import net.jsign.KeyStoreBuilder; +import net.jsign.jca.PIVCardSigningService; +import net.jsign.jca.SigningServiceJcaProvider; + +import javax.smartcardio.CardException; +import java.security.Provider; + +import static net.jsign.ks.JsignKeyStore.getCertificateStore; + +/** + * PIV card. PIV cards contain up to 24 private keys and certificates. The alias to select the key is either, + * AUTHENTICATION, SIGNATURE, KEY_MANAGEMENT, CARD_AUTHENTICATION, + * or RETIRED<1-20>. Slot numbers are also accepted (for example 9c for the digital + * signature key). If multiple devices are connected, the keystore parameter can be used to specify the name + * of the one to use. This keystore type doesn't require any external library to be installed. + */ +public class PivKeyStore extends AbstractJsignKeyStore { + public PivKeyStore() { + super(false, false, false); + } + + @Override + public String getType() { + return "PIV"; + } + + @Override + public void validate(KeyStoreBuilder params) throws IllegalArgumentException { + if (params.storepass() == null) { + throw new IllegalArgumentException("storepass " + params.parameterName() + " must specify the PIN"); + } + } + + @Override + public Provider getProvider(KeyStoreBuilder params) { + try { + return new SigningServiceJcaProvider(new PIVCardSigningService(params.keystore(), params.storepass(), params.certfile() != null ? getCertificateStore(params) : null)); + } catch (CardException e) { + throw new IllegalStateException("Failed to initialize the PIV card", e); + } + } +} diff --git a/jsign-crypto/src/main/java/net/jsign/ks/Pkcs11KeyStore.java b/jsign-crypto/src/main/java/net/jsign/ks/Pkcs11KeyStore.java new file mode 100644 index 00000000..65947106 --- /dev/null +++ b/jsign-crypto/src/main/java/net/jsign/ks/Pkcs11KeyStore.java @@ -0,0 +1,61 @@ +/* + * Copyright 2024 Björn Kautler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.jsign.ks; + +import net.jsign.KeyStoreBuilder; + +import java.security.Provider; +import java.security.Security; + +/** + * PKCS#11 hardware token. The keystore parameter specifies either the name of the provider defined + * in jre/lib/security/java.security or the path to the + * SunPKCS11 configuration file. + */ +public class Pkcs11KeyStore extends AbstractJsignKeyStore { + public Pkcs11KeyStore() { + super(false, true, true); + } + + @Override + public String getType() { + return "PKCS11"; + } + + @Override + public void validate(KeyStoreBuilder params) throws IllegalArgumentException { + if (params.keystore() == null) { + throw new IllegalArgumentException("keystore " + params.parameterName() + " must be set"); + } + } + + @Override + public Provider getProvider(KeyStoreBuilder params) { + // the keystore parameter is either the provider name or the SunPKCS11 configuration file + if (params.createFile(params.keystore()).exists()) { + return ProviderUtils.createSunPKCS11Provider(params.keystore()); + } else if (params.keystore().startsWith("SunPKCS11-")) { + Provider provider = Security.getProvider(params.keystore()); + if (provider == null) { + throw new IllegalArgumentException("Security provider " + params.keystore() + " not found"); + } + return provider; + } else { + 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"); + } + } +} diff --git a/jsign-crypto/src/main/java/net/jsign/ks/Pkcs12KeyStore.java b/jsign-crypto/src/main/java/net/jsign/ks/Pkcs12KeyStore.java new file mode 100644 index 00000000..2288758d --- /dev/null +++ b/jsign-crypto/src/main/java/net/jsign/ks/Pkcs12KeyStore.java @@ -0,0 +1,40 @@ +/* + * Copyright 2024 Björn Kautler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.jsign.ks; + +import net.jsign.KeyStoreBuilder; + +/** + * PKCS#12 keystore + */ +public class Pkcs12KeyStore extends AbstractJsignKeyStore { + public Pkcs12KeyStore() { + super(true, true, false); + } + + @Override + public String getType() { + return "PKCS12"; + } + + @Override + public void validate(KeyStoreBuilder params) throws IllegalArgumentException { + if (params.keystore() == null) { + throw new IllegalArgumentException("keystore " + params.parameterName() + " must be set"); + } + } +} diff --git a/jsign-crypto/src/main/java/net/jsign/ProviderUtils.java b/jsign-crypto/src/main/java/net/jsign/ks/ProviderUtils.java similarity index 99% rename from jsign-crypto/src/main/java/net/jsign/ProviderUtils.java rename to jsign-crypto/src/main/java/net/jsign/ks/ProviderUtils.java index 6a6e3143..5ca90db7 100644 --- a/jsign-crypto/src/main/java/net/jsign/ProviderUtils.java +++ b/jsign-crypto/src/main/java/net/jsign/ks/ProviderUtils.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package net.jsign; +package net.jsign.ks; import java.lang.reflect.Constructor; import java.lang.reflect.Method; diff --git a/jsign-crypto/src/main/java/net/jsign/SafeNetEToken.java b/jsign-crypto/src/main/java/net/jsign/ks/SafeNetETokenKeyStore.java similarity index 85% rename from jsign-crypto/src/main/java/net/jsign/SafeNetEToken.java rename to jsign-crypto/src/main/java/net/jsign/ks/SafeNetETokenKeyStore.java index 67e40bd8..1475fbae 100644 --- a/jsign-crypto/src/main/java/net/jsign/SafeNetEToken.java +++ b/jsign-crypto/src/main/java/net/jsign/ks/SafeNetETokenKeyStore.java @@ -1,113 +1,116 @@ -/** - * Copyright 2023 Emmanuel Bourg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.jsign; - -import java.io.File; -import java.io.IOException; -import java.security.Provider; -import java.security.ProviderException; -import java.util.ArrayList; -import java.util.List; - -import sun.security.pkcs11.wrapper.PKCS11; -import sun.security.pkcs11.wrapper.PKCS11Exception; - -/** - * Helper class for working with SafeNet eTokens. - * - * @since 6.0 - */ -class SafeNetEToken { - - /** - * Returns the security provider for the SafeNet eToken. - * - * @return the SafeNet eTokens security provider - * @throws ProviderException thrown if the provider can't be initialized - */ - static Provider getProvider() { - return ProviderUtils.createSunPKCS11Provider(getSunPKCS11Configuration()); - } - - /** - * Returns the SunPKCS11 configuration of the SafeNet eToken. - * - * @throws ProviderException thrown if the PKCS11 modules cannot be found - */ - static String getSunPKCS11Configuration() { - File library = getPKCS11Library(); - if (!library.exists()) { - throw new ProviderException("SafeNet eToken PKCS11 module is not installed (" + library + " is missing)"); - } - String configuration = "--name=\"SafeNet eToken\"\nlibrary = \"" + library.getAbsolutePath().replace("\\", "\\\\") + "\"\n"; - try { - long slot = getTokenSlot(library); - if (slot >= 0) { - configuration += "slot=" + slot; - } - } catch (Exception e) { - throw new ProviderException(e); - } - return configuration; - } - - /** - * Returns the slot index associated to the token. - */ - static long getTokenSlot(File libraryPath) throws PKCS11Exception, IOException { - PKCS11 pkcs11 = PKCS11.getInstance(libraryPath.getAbsolutePath(), "C_GetFunctionList", null, false); - long[] slots = pkcs11.C_GetSlotList(true); - return slots.length > 0 ? slots[0] : -1; - } - - /** - * Attempts to locate the SafeNet eToken PKCS11 library on the system. - */ - static File getPKCS11Library() { - String osname = System.getProperty("os.name"); - String arch = System.getProperty("sun.arch.data.model"); - - if (osname.contains("Windows")) { - return new File(System.getenv("windir") + "/system32/eTPKCS11.dll"); - - } else if (osname.contains("Mac")) { - return new File("/usr/local/lib/libeTPkcs11.dylib"); - - } else { - // Linux - List paths = new ArrayList<>(); - if ("64".equals(arch)) { - paths.add("/usr/lib64/pkcs11/libeTPkcs11.so"); - paths.add("/usr/lib64/libeTPkcs11.so"); - paths.add("/usr/lib64/libeToken.so"); - } - paths.add("/usr/lib/pkcs11/libeTPkcs11.so"); - paths.add("/usr/lib/pkcs11/libeToken.so"); - paths.add("/usr/lib/libeTPkcs11.so"); - paths.add("/usr/lib/libeToken.so"); - - for (String path : paths) { - File library = new File(path); - if (library.exists()) { - return library; - } - } - - return new File("/usr/local/lib/libeToken.so"); - } - } -} +/* + * Copyright 2024 Björn Kautler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.jsign.ks; + +import net.jsign.KeyStoreBuilder; +import sun.security.pkcs11.wrapper.PKCS11; +import sun.security.pkcs11.wrapper.PKCS11Exception; + +import java.io.File; +import java.io.IOException; +import java.security.Provider; +import java.security.ProviderException; +import java.util.ArrayList; +import java.util.List; + +/** + * SafeNet eToken + * This keystore requires the installation of the SafeNet Authentication Client. + */ +public class SafeNetETokenKeyStore extends AbstractJsignKeyStore { + public SafeNetETokenKeyStore() { + super(false, true, true); + } + + @Override + public String getType() { + return "ETOKEN"; + } + + @Override + public Provider getProvider(KeyStoreBuilder params) { + return ProviderUtils.createSunPKCS11Provider(getSunPKCS11Configuration()); + } + + /** + * Returns the SunPKCS11 configuration of the SafeNet eToken. + * + * @throws ProviderException thrown if the PKCS11 modules cannot be found + */ + static String getSunPKCS11Configuration() { + File library = getPKCS11Library(); + if (!library.exists()) { + throw new ProviderException("SafeNet eToken PKCS11 module is not installed (" + library + " is missing)"); + } + String configuration = "--name=\"SafeNet eToken\"\nlibrary = \"" + library.getAbsolutePath().replace("\\", "\\\\") + "\"\n"; + try { + long slot = getTokenSlot(library); + if (slot >= 0) { + configuration += "slot=" + slot; + } + } catch (Exception e) { + throw new ProviderException(e); + } + return configuration; + } + + /** + * Returns the slot index associated to the token. + */ + static long getTokenSlot(File libraryPath) throws PKCS11Exception, IOException { + PKCS11 pkcs11 = PKCS11.getInstance(libraryPath.getAbsolutePath(), "C_GetFunctionList", null, false); + long[] slots = pkcs11.C_GetSlotList(true); + return slots.length > 0 ? slots[0] : -1; + } + + /** + * Attempts to locate the SafeNet eToken PKCS11 library on the system. + */ + static File getPKCS11Library() { + String osname = System.getProperty("os.name"); + String arch = System.getProperty("sun.arch.data.model"); + + if (osname.contains("Windows")) { + return new File(System.getenv("windir") + "/system32/eTPKCS11.dll"); + + } else if (osname.contains("Mac")) { + return new File("/usr/local/lib/libeTPkcs11.dylib"); + + } else { + // Linux + List paths = new ArrayList<>(); + if ("64".equals(arch)) { + paths.add("/usr/lib64/pkcs11/libeTPkcs11.so"); + paths.add("/usr/lib64/libeTPkcs11.so"); + paths.add("/usr/lib64/libeToken.so"); + } + paths.add("/usr/lib/pkcs11/libeTPkcs11.so"); + paths.add("/usr/lib/pkcs11/libeToken.so"); + paths.add("/usr/lib/libeTPkcs11.so"); + paths.add("/usr/lib/libeToken.so"); + + for (String path : paths) { + File library = new File(path); + if (library.exists()) { + return library; + } + } + + return new File("/usr/local/lib/libeToken.so"); + } + } +} diff --git a/jsign-crypto/src/main/java/net/jsign/ks/SignServerKeyStore.java b/jsign-crypto/src/main/java/net/jsign/ks/SignServerKeyStore.java new file mode 100644 index 00000000..4318bb0d --- /dev/null +++ b/jsign-crypto/src/main/java/net/jsign/ks/SignServerKeyStore.java @@ -0,0 +1,61 @@ +/* + * Copyright 2024 Björn Kautler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.jsign.ks; + +import net.jsign.KeyStoreBuilder; +import net.jsign.jca.SignServerCredentials; +import net.jsign.jca.SignServerSigningService; +import net.jsign.jca.SigningServiceJcaProvider; + +import java.security.Provider; + +public class SignServerKeyStore extends AbstractJsignKeyStore { + public SignServerKeyStore() { + super(false, false, false); + } + + @Override + public String getType() { + return "SIGNSERVER"; + } + + @Override + public void validate(KeyStoreBuilder params) throws IllegalArgumentException { + if (params.storepass() != null && params.storepass().split("\\|").length > 2) { + throw new IllegalArgumentException("storepass " + params.parameterName() + " must specify the SignServer username/password or the path to the keystore containing the TLS client certificate: |, "); + } + } + + @Override + public Provider getProvider(KeyStoreBuilder params) { + String username = null; + String password = null; + String certificate = null; + if (params.storepass() != null) { + String[] elements = params.storepass().split("\\|"); + if (elements.length == 1) { + certificate = elements[0]; + } else if (elements.length == 2) { + username = elements[0]; + password = elements[1]; + } + } + + SignServerCredentials credentials = new SignServerCredentials(username, password, certificate, params.keypass()); + return new SigningServiceJcaProvider(new SignServerSigningService(params.keystore(), credentials)); + } +} diff --git a/jsign-crypto/src/main/java/net/jsign/YubiKey.java b/jsign-crypto/src/main/java/net/jsign/ks/YubiKeyKeyStore.java similarity index 77% rename from jsign-crypto/src/main/java/net/jsign/YubiKey.java rename to jsign-crypto/src/main/java/net/jsign/ks/YubiKeyKeyStore.java index 17ff85fd..00bc2cc3 100644 --- a/jsign-crypto/src/main/java/net/jsign/YubiKey.java +++ b/jsign-crypto/src/main/java/net/jsign/ks/YubiKeyKeyStore.java @@ -1,152 +1,165 @@ -/** - * Copyright 2021 Emmanuel Bourg - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.jsign; - -import java.io.File; -import java.io.IOException; -import java.security.AuthProvider; -import java.security.Provider; -import java.security.ProviderException; -import java.util.ArrayList; -import java.util.List; -import java.util.logging.Logger; - -import sun.security.pkcs11.wrapper.PKCS11; -import sun.security.pkcs11.wrapper.PKCS11Exception; - -import net.jsign.jca.AutoLoginProvider; - -/** - * Helper class for working with YubiKeys. - * - * @since 4.0 - */ -class YubiKey { - - /** - * Returns the security provider for the YubiKey. - * - * @return the YubiKey security provider - * @throws ProviderException thrown if the provider can't be initialized - * @since 4.0 - */ - static Provider getProvider() { - return new AutoLoginProvider((AuthProvider) ProviderUtils.createSunPKCS11Provider(getSunPKCS11Configuration())); - } - - /** - * Returns the SunPKCS11 configuration of the YubiKey. - * - * @throws ProviderException thrown if the PKCS11 modules cannot be found - * @since 4.0 - */ - static String getSunPKCS11Configuration() { - File libykcs11 = getYkcs11Library(); - if (!libykcs11.exists()) { - throw new ProviderException("YubiKey PKCS11 module (ykcs11) is not installed (" + libykcs11 + " is missing)"); - } - String configuration = "--name=yubikey\nlibrary = \"" + libykcs11.getAbsolutePath().replace("\\", "\\\\") + "\"\n"; - try { - long slot = getTokenSlot(libykcs11); - if (slot >= 0) { - configuration += "slot=" + slot; - } - } catch (Exception e) { - throw new ProviderException(e); - } - return configuration; - } - - /** - * Returns the slot index associated to the token. - * - * @since 4.1 - */ - static long getTokenSlot(File libraryPath) throws PKCS11Exception, IOException { - PKCS11 pkcs11 = PKCS11.getInstance(libraryPath.getAbsolutePath(), "C_GetFunctionList", null, false); - long[] slots = pkcs11.C_GetSlotList(true); - return slots.length > 0 ? slots[0] : -1; - } - - /** - * Tells if a YubiKey is present on the system. - */ - static boolean isPresent() { - try { - return getTokenSlot(getYkcs11Library()) >= 0; - } catch (Exception e) { - return false; - } - } - - /** - * Attempts to locate the ykcs11 library on the system. - * - * @since 4.0 - */ - static File getYkcs11Library() { - String osname = System.getProperty("os.name"); - String arch = System.getProperty("sun.arch.data.model"); - - if (osname.contains("Windows")) { - String programfiles; - if ("32".equals(arch) && System.getenv("ProgramFiles(x86)") != null) { - programfiles = System.getenv("ProgramFiles(x86)"); - } else { - programfiles = System.getenv("ProgramFiles"); - } - File libykcs11 = new File(programfiles + "/Yubico/Yubico PIV Tool/bin/libykcs11.dll"); - - if (!System.getenv("PATH").contains("Yubico PIV Tool\\bin")) { - Logger log = Logger.getLogger(YubiKey.class.getName()); - log.warning("The YubiKey library path (" + libykcs11.getParentFile().getAbsolutePath().replace('/', '\\') + ") is missing from the PATH environment variable"); - } - - return libykcs11; - - } else if (osname.contains("Mac")) { - return new File("/usr/local/lib/libykcs11.dylib"); - - } else { - // Linux - List paths = new ArrayList<>(); - if ("32".equals(arch)) { - paths.add("/usr/lib/libykcs11.so"); - paths.add("/usr/lib/libykcs11.so.1"); - paths.add("/usr/lib/i386-linux-gnu/libykcs11.so"); - paths.add("/usr/lib/arm-linux-gnueabi/libykcs11.so"); - paths.add("/usr/lib/arm-linux-gnueabihf/libykcs11.so"); - } else { - paths.add("/usr/lib64/libykcs11.so"); - paths.add("/usr/lib64/libykcs11.so.1"); - paths.add("/usr/lib/x86_64-linux-gnu/libykcs11.so"); - paths.add("/usr/lib/aarch64-linux-gnu/libykcs11.so"); - paths.add("/usr/lib/mips64el-linux-gnuabi64/libykcs11.so"); - paths.add("/usr/lib/riscv64-linux-gnu/libykcs11.so"); - } - - for (String path : paths) { - File libykcs11 = new File(path); - if (libykcs11.exists()) { - return libykcs11; - } - } - - return new File("/usr/local/lib/libykcs11.so"); - } - } -} +/* + * Copyright 2024 Björn Kautler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.jsign.ks; + +import net.jsign.KeyStoreBuilder; +import net.jsign.jca.AutoLoginProvider; +import sun.security.pkcs11.wrapper.PKCS11; +import sun.security.pkcs11.wrapper.PKCS11Exception; + +import java.io.File; +import java.io.IOException; +import java.security.AuthProvider; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.Provider; +import java.security.ProviderException; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.logging.Logger; + +/** + * YubiKey PIV. This keystore requires the ykcs11 library from the Yubico PIV Tool + * to be installed at the default location. On Windows, the path to the library must be specified in the + * PATH environment variable. + */ +public class YubiKeyKeyStore extends AbstractJsignKeyStore { + public YubiKeyKeyStore() { + super(false, true, true); + } + + @Override + public String getType() { + return "YUBIKEY"; + } + + @Override + public Provider getProvider(KeyStoreBuilder params) { + return new AutoLoginProvider((AuthProvider) ProviderUtils.createSunPKCS11Provider(getSunPKCS11Configuration())); + } + + @Override + public Set getAliases(KeyStore keystore) throws KeyStoreException { + Set aliases = super.getAliases(keystore); + // the attestation certificate is never used for signing + aliases.remove("X.509 Certificate for PIV Attestation"); + return aliases; + } + + /** + * Returns the SunPKCS11 configuration of the YubiKey. + * + * @throws ProviderException thrown if the PKCS11 modules cannot be found + * @since 4.0 + */ + static String getSunPKCS11Configuration() { + File libykcs11 = getYkcs11Library(); + if (!libykcs11.exists()) { + throw new ProviderException("YubiKey PKCS11 module (ykcs11) is not installed (" + libykcs11 + " is missing)"); + } + String configuration = "--name=yubikey\nlibrary = \"" + libykcs11.getAbsolutePath().replace("\\", "\\\\") + "\"\n"; + try { + long slot = getTokenSlot(libykcs11); + if (slot >= 0) { + configuration += "slot=" + slot; + } + } catch (Exception e) { + throw new ProviderException(e); + } + return configuration; + } + + /** + * Returns the slot index associated to the token. + * + * @since 4.1 + */ + static long getTokenSlot(File libraryPath) throws PKCS11Exception, IOException { + PKCS11 pkcs11 = PKCS11.getInstance(libraryPath.getAbsolutePath(), "C_GetFunctionList", null, false); + long[] slots = pkcs11.C_GetSlotList(true); + return slots.length > 0 ? slots[0] : -1; + } + + /** + * Tells if a YubiKey is present on the system. + */ + public static boolean isPresent() { + try { + return getTokenSlot(getYkcs11Library()) >= 0; + } catch (Exception e) { + return false; + } + } + + /** + * Attempts to locate the ykcs11 library on the system. + * + * @since 4.0 + */ + static File getYkcs11Library() { + String osname = System.getProperty("os.name"); + String arch = System.getProperty("sun.arch.data.model"); + + if (osname.contains("Windows")) { + String programfiles; + if ("32".equals(arch) && System.getenv("ProgramFiles(x86)") != null) { + programfiles = System.getenv("ProgramFiles(x86)"); + } else { + programfiles = System.getenv("ProgramFiles"); + } + File libykcs11 = new File(programfiles + "/Yubico/Yubico PIV Tool/bin/libykcs11.dll"); + + if (!System.getenv("PATH").contains("Yubico PIV Tool\\bin")) { + Logger log = Logger.getLogger(YubiKeyKeyStore.class.getName()); + log.warning("The YubiKey library path (" + libykcs11.getParentFile().getAbsolutePath().replace('/', '\\') + ") is missing from the PATH environment variable"); + } + + return libykcs11; + + } else if (osname.contains("Mac")) { + return new File("/usr/local/lib/libykcs11.dylib"); + + } else { + // Linux + List paths = new ArrayList<>(); + if ("32".equals(arch)) { + paths.add("/usr/lib/libykcs11.so"); + paths.add("/usr/lib/libykcs11.so.1"); + paths.add("/usr/lib/i386-linux-gnu/libykcs11.so"); + paths.add("/usr/lib/arm-linux-gnueabi/libykcs11.so"); + paths.add("/usr/lib/arm-linux-gnueabihf/libykcs11.so"); + } else { + paths.add("/usr/lib64/libykcs11.so"); + paths.add("/usr/lib64/libykcs11.so.1"); + paths.add("/usr/lib/x86_64-linux-gnu/libykcs11.so"); + paths.add("/usr/lib/aarch64-linux-gnu/libykcs11.so"); + paths.add("/usr/lib/mips64el-linux-gnuabi64/libykcs11.so"); + paths.add("/usr/lib/riscv64-linux-gnu/libykcs11.so"); + } + + for (String path : paths) { + File libykcs11 = new File(path); + if (libykcs11.exists()) { + return libykcs11; + } + } + + return new File("/usr/local/lib/libykcs11.so"); + } + } +} diff --git a/jsign-crypto/src/main/resources/META-INF/services/net.jsign.ks.JsignKeyStore b/jsign-crypto/src/main/resources/META-INF/services/net.jsign.ks.JsignKeyStore new file mode 100644 index 00000000..0188c3d7 --- /dev/null +++ b/jsign-crypto/src/main/resources/META-INF/services/net.jsign.ks.JsignKeyStore @@ -0,0 +1,21 @@ +net.jsign.ks.AwsKeyStore +net.jsign.ks.AzureKeyVaultKeyStore +net.jsign.ks.AzureTrustedSigningKeyStore +net.jsign.ks.DigiCertOneKeyStore +net.jsign.ks.ESignerKeyStore +net.jsign.ks.GaraSignKeyStore +net.jsign.ks.GoogleCloudKeyStore +net.jsign.ks.HashiCorpVaultKeyStore +net.jsign.ks.JavaKeyStore +net.jsign.ks.JceKeyStore +net.jsign.ks.NitroKeyKeyStore +net.jsign.ks.NoneKeyStore +net.jsign.ks.OpenPGPKeyStore +net.jsign.ks.OpenSCKeyStore +net.jsign.ks.OracleCloudKeyStore +net.jsign.ks.PivKeyStore +net.jsign.ks.Pkcs11KeyStore +net.jsign.ks.Pkcs12KeyStore +net.jsign.ks.SafeNetETokenKeyStore +net.jsign.ks.SignServerKeyStore +net.jsign.ks.YubiKeyKeyStore diff --git a/jsign-crypto/src/test/java/net/jsign/KeyStoreBuilderTest.java b/jsign-crypto/src/test/java/net/jsign/KeyStoreBuilderTest.java index a9e28c5f..75c3f83c 100644 --- a/jsign-crypto/src/test/java/net/jsign/KeyStoreBuilderTest.java +++ b/jsign-crypto/src/test/java/net/jsign/KeyStoreBuilderTest.java @@ -21,6 +21,7 @@ import java.security.KeyStore; import java.security.ProviderException; +import net.jsign.ks.Pkcs12KeyStore; import org.apache.commons.io.FileUtils; import org.junit.Assume; import org.junit.Test; @@ -29,7 +30,13 @@ import net.jsign.jca.PIVCardTest; import static net.jsign.KeyStoreType.*; -import static org.junit.Assert.*; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.isA; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; public class KeyStoreBuilderTest { @@ -478,7 +485,7 @@ public void testBuildWithoutStoreType() throws Exception { builder.keystore("target/test-classes/keystores/keystore.p12"); - assertEquals("storetype", PKCS12, builder.storetype()); + assertThat("storetype", builder.storetype(), isA(Pkcs12KeyStore.class)); KeyStore keystore = builder.build(); assertNotNull("keystore", keystore); @@ -555,6 +562,6 @@ public void testBuildPIV() throws Exception { @Test public void testLowerCaseStoreType() { KeyStoreBuilder builder = new KeyStoreBuilder().storetype("pkcs12"); - assertEquals("storetype", PKCS12, builder.storetype()); + assertThat("storetype", builder.storetype(), isA(Pkcs12KeyStore.class)); } } diff --git a/jsign-crypto/src/test/java/net/jsign/KeyStoreTypeTest.java b/jsign-crypto/src/test/java/net/jsign/KeyStoreTypeTest.java index 73f027a5..1a74aa4e 100644 --- a/jsign-crypto/src/test/java/net/jsign/KeyStoreTypeTest.java +++ b/jsign-crypto/src/test/java/net/jsign/KeyStoreTypeTest.java @@ -18,21 +18,28 @@ import java.io.File; +import net.jsign.ks.JavaKeyStore; +import net.jsign.ks.JceKeyStore; +import net.jsign.ks.JsignKeyStore; +import net.jsign.ks.JsignKeyStoreDiscovery; +import net.jsign.ks.Pkcs12KeyStore; import org.apache.commons.io.FileUtils; +import org.hamcrest.MatcherAssert; import org.junit.Test; -import static net.jsign.KeyStoreType.*; -import static org.junit.Assert.*; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.isA; +import static org.junit.Assert.assertNull; public class KeyStoreTypeTest { @Test public void testGetType() throws Exception { - assertEquals(PKCS12, KeyStoreType.of(new File("keystore.p12"))); - assertEquals(PKCS12, KeyStoreType.of(new File("keystore.pfx"))); - assertEquals(JCEKS, KeyStoreType.of(new File("keystore.jceks"))); - assertEquals(JKS, KeyStoreType.of(new File("keystore.jks"))); - assertNull(KeyStoreType.of(new File("keystore.unknown"))); + assertThat(JsignKeyStore.of(new File("keystore.p12")), isA(Pkcs12KeyStore.class)); + assertThat(JsignKeyStore.of(new File("keystore.pfx")), isA(Pkcs12KeyStore.class)); + assertThat(JsignKeyStore.of(new File("keystore.jceks")), isA(JceKeyStore.class)); + assertThat(JsignKeyStore.of(new File("keystore.jks")), isA(JavaKeyStore.class)); + assertNull(JsignKeyStore.of(new File("keystore.unknown"))); } @Test @@ -41,7 +48,7 @@ public void testGetTypePKCS12FromHeader() throws Exception { File target = new File("target/test-classes/keystores/keystore.p12.ext"); FileUtils.copyFile(source, target); - assertEquals(PKCS12, KeyStoreType.of(target)); + assertThat(JsignKeyStore.of(target), isA(Pkcs12KeyStore.class)); } @Test @@ -50,7 +57,7 @@ public void testGetTypeJCEKSFromHeader() throws Exception { File target = new File("target/test-classes/keystores/keystore.jceks.ext"); FileUtils.copyFile(source, target); - assertEquals(JCEKS, KeyStoreType.of(target)); + assertThat(JsignKeyStore.of(target), isA(JceKeyStore.class)); } @Test @@ -59,11 +66,11 @@ public void testGetTypeJKSFromHeader() throws Exception { File target = new File("target/test-classes/keystores/keystore.jks.ext"); FileUtils.copyFile(source, target); - assertEquals(JKS, KeyStoreType.of(target)); + assertThat(JsignKeyStore.of(target), isA(JavaKeyStore.class)); } @Test public void testGetTypeUnknown() throws Exception { - assertNull(KeyStoreType.of(new File("target/test-classes/keystores/jsign-root-ca.pem"))); + assertNull(JsignKeyStore.of(new File("target/test-classes/keystores/jsign-root-ca.pem"))); } } diff --git a/jsign-crypto/src/test/java/net/jsign/jca/JsignJcaProviderTest.java b/jsign-crypto/src/test/java/net/jsign/jca/JsignJcaProviderTest.java index 46d3eaca..a23212bf 100644 --- a/jsign-crypto/src/test/java/net/jsign/jca/JsignJcaProviderTest.java +++ b/jsign-crypto/src/test/java/net/jsign/jca/JsignJcaProviderTest.java @@ -20,11 +20,11 @@ import java.security.PrivateKey; import java.security.Signature; +import net.jsign.ks.JsignKeyStoreDiscovery; import org.junit.Test; import net.jsign.DigestAlgorithm; -import net.jsign.KeyStoreType; -import net.jsign.YubikeyTest; +import net.jsign.ks.YubikeyTest; import static org.junit.Assert.*; @@ -34,8 +34,8 @@ public class JsignJcaProviderTest { public void testServices() { JsignJcaProvider provider = new JsignJcaProvider(); - for (KeyStoreType type : KeyStoreType.values()) { - assertNotNull("KeyStore " + type.name(), provider.getService("KeyStore", type.name())); + for (String type : JsignKeyStoreDiscovery.getKeyStoreTypes()) { + assertNotNull("KeyStore " + type, provider.getService("KeyStore", type)); } for (String alg : new String[]{"RSA", "ECDSA"}) { diff --git a/jsign-crypto/src/test/java/net/jsign/OpenSCTest.java b/jsign-crypto/src/test/java/net/jsign/ks/OpenSCTest.java similarity index 91% rename from jsign-crypto/src/test/java/net/jsign/OpenSCTest.java rename to jsign-crypto/src/test/java/net/jsign/ks/OpenSCTest.java index 45ad0511..a12e6f52 100644 --- a/jsign-crypto/src/test/java/net/jsign/OpenSCTest.java +++ b/jsign-crypto/src/test/java/net/jsign/ks/OpenSCTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package net.jsign; +package net.jsign.ks; import java.io.File; import java.security.Provider; @@ -37,7 +37,7 @@ private void assumeOpenSC() { public void testGetProvider() { assumeOpenSC(); try { - Provider provider = OpenSC.getProvider(null); + Provider provider = OpenSCKeyStore.getProvider((String) null); assertNotNull("provider", provider); } catch (RuntimeException e) { assertEquals("message", "No PKCS11 token found", ExceptionUtils.getRootCause(e).getMessage()); @@ -47,7 +47,7 @@ public void testGetProvider() { @Test public void testGetLibrary() { assumeOpenSC(); - File library = OpenSC.getOpenSCLibrary(); + File library = OpenSCKeyStore.getOpenSCLibrary(); assertNotNull("native library", library); assertTrue("native library not found", library.exists()); } diff --git a/jsign-crypto/src/test/java/net/jsign/SafeNetETokenTest.java b/jsign-crypto/src/test/java/net/jsign/ks/SafeNetETokenTest.java similarity index 87% rename from jsign-crypto/src/test/java/net/jsign/SafeNetETokenTest.java rename to jsign-crypto/src/test/java/net/jsign/ks/SafeNetETokenTest.java index 413e998b..46d3250f 100644 --- a/jsign-crypto/src/test/java/net/jsign/SafeNetETokenTest.java +++ b/jsign-crypto/src/test/java/net/jsign/ks/SafeNetETokenTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package net.jsign; +package net.jsign.ks; import java.io.File; import java.security.Provider; @@ -23,6 +23,7 @@ import org.junit.Assume; import org.junit.Test; +import static net.jsign.KeyStoreType.ETOKEN; import static org.junit.Assert.*; public class SafeNetETokenTest { @@ -37,7 +38,7 @@ private void assumeSafeNetEToken() { public void testGetProvider() { assumeSafeNetEToken(); try { - Provider provider = SafeNetEToken.getProvider(); + Provider provider = JsignKeyStoreDiscovery.getKeyStore(ETOKEN).getProvider(null); assertNotNull("provider", provider); } catch (RuntimeException e) { assertEquals("message", "No PKCS11 token found", ExceptionUtils.getRootCause(e).getMessage()); @@ -47,7 +48,7 @@ public void testGetProvider() { @Test public void testGetLibrary() { assumeSafeNetEToken(); - File library = SafeNetEToken.getPKCS11Library(); + File library = SafeNetETokenKeyStore.getPKCS11Library(); assertNotNull("native library", library); assertTrue("native library not found", library.exists()); } diff --git a/jsign-crypto/src/test/java/net/jsign/YubikeyTest.java b/jsign-crypto/src/test/java/net/jsign/ks/YubikeyTest.java similarity index 86% rename from jsign-crypto/src/test/java/net/jsign/YubikeyTest.java rename to jsign-crypto/src/test/java/net/jsign/ks/YubikeyTest.java index 9b9892f5..785b7e3f 100644 --- a/jsign-crypto/src/test/java/net/jsign/YubikeyTest.java +++ b/jsign-crypto/src/test/java/net/jsign/ks/YubikeyTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package net.jsign; +package net.jsign.ks; import java.io.File; import java.security.KeyStore; @@ -25,6 +25,7 @@ import org.junit.Assume; import org.junit.Test; +import static net.jsign.KeyStoreType.YUBIKEY; import static org.junit.Assert.*; public class YubikeyTest { @@ -33,20 +34,20 @@ public static void assumeYubikey() { Assume.assumeTrue("libykcs11 isn't installed", new File(System.getenv("ProgramFiles") + "/Yubico/Yubico PIV Tool/bin/libykcs11.dll").exists() || new File("/usr/lib/x86_64-linux-gnu/libykcs11.so").exists()); - Assume.assumeTrue("No Yubikey detected", YubiKey.isPresent()); + Assume.assumeTrue("No Yubikey detected", YubiKeyKeyStore.isPresent()); } @Test public void testGetProvider() { assumeYubikey(); - Provider provider = YubiKey.getProvider(); + Provider provider = JsignKeyStoreDiscovery.getKeyStore(YUBIKEY).getProvider(null); assertNotNull("provider", provider); } @Test public void testGetLibrary() { assumeYubikey(); - File library = YubiKey.getYkcs11Library(); + File library = YubiKeyKeyStore.getYkcs11Library(); assertNotNull("native library", library); assertTrue("native library not found", library.exists()); } @@ -55,7 +56,7 @@ public void testGetLibrary() { public void testAutoLogin() throws Exception { assumeYubikey(); - Provider provider = YubiKey.getProvider(); + Provider provider = JsignKeyStoreDiscovery.getKeyStore(YUBIKEY).getProvider(null); KeyStore keystore = KeyStore.getInstance("PKCS11", provider); assertEquals("provider", provider, keystore.getProvider()); keystore.load(() -> new KeyStore.PasswordProtection("123456".toCharArray()));