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.