From f22b49fb9a6ec3e4d0333ef22f23beab137be727 Mon Sep 17 00:00:00 2001
From: Emmanuel Bourg
Date: Mon, 26 Feb 2024 11:44:24 +0100
Subject: [PATCH] WIP
---
.../java/net/jsign/AbstractKeyStoreType.java | 85 +++
.../java/net/jsign/AmazonKeyStoreType.java | 66 ++
.../net/jsign/AzureKeyVaultKeyStoreType.java | 48 ++
.../AzureTrustedSigningKeyStoreType.java | 48 ++
.../net/jsign/DigiCertOneKeyStoreType.java | 46 ++
.../java/net/jsign/ESignerKeyStoreType.java | 53 ++
.../java/net/jsign/ETokenKeyStoreType.java | 35 ++
.../java/net/jsign/FileBasedKeyStoreType.java | 63 ++
.../java/net/jsign/GaraSignKeyStoreType.java | 62 ++
.../net/jsign/GoogleCloudKeyStoreType.java | 54 ++
.../net/jsign/HashiCorpVaultKeyStoreType.java | 51 ++
.../java/net/jsign/JCEKSKeyStoreType.java | 36 ++
.../main/java/net/jsign/JKSKeyStoreType.java | 36 ++
.../main/java/net/jsign/KeyStoreBuilder.java | 18 +-
.../src/main/java/net/jsign/KeyStoreType.java | 562 ++----------------
.../java/net/jsign/NitrokeyKeyStoreType.java | 35 ++
.../main/java/net/jsign/NoneKeyStoreType.java | 81 +++
.../java/net/jsign/OpenPGPKeyStoreType.java | 50 ++
.../java/net/jsign/OpenSCKeyStoreType.java | 35 ++
.../net/jsign/OracleCloudKeyStoreType.java | 67 +++
.../main/java/net/jsign/PIVKeyStoreType.java | 50 ++
.../java/net/jsign/PKCS11KeyStoreType.java | 78 +++
.../java/net/jsign/PKCS12KeyStoreType.java | 36 ++
.../net/jsign/SignServerKeyStoreType.java | 63 ++
.../java/net/jsign/YubikeyKeyStoreType.java | 46 ++
.../java/net/jsign/KeyStoreBuilderTest.java | 41 ++
.../test/java/net/jsign/KeyStoreTypeTest.java | 53 +-
27 files changed, 1340 insertions(+), 558 deletions(-)
create mode 100644 jsign-crypto/src/main/java/net/jsign/AbstractKeyStoreType.java
create mode 100644 jsign-crypto/src/main/java/net/jsign/AmazonKeyStoreType.java
create mode 100644 jsign-crypto/src/main/java/net/jsign/AzureKeyVaultKeyStoreType.java
create mode 100644 jsign-crypto/src/main/java/net/jsign/AzureTrustedSigningKeyStoreType.java
create mode 100644 jsign-crypto/src/main/java/net/jsign/DigiCertOneKeyStoreType.java
create mode 100644 jsign-crypto/src/main/java/net/jsign/ESignerKeyStoreType.java
create mode 100644 jsign-crypto/src/main/java/net/jsign/ETokenKeyStoreType.java
create mode 100644 jsign-crypto/src/main/java/net/jsign/FileBasedKeyStoreType.java
create mode 100644 jsign-crypto/src/main/java/net/jsign/GaraSignKeyStoreType.java
create mode 100644 jsign-crypto/src/main/java/net/jsign/GoogleCloudKeyStoreType.java
create mode 100644 jsign-crypto/src/main/java/net/jsign/HashiCorpVaultKeyStoreType.java
create mode 100644 jsign-crypto/src/main/java/net/jsign/JCEKSKeyStoreType.java
create mode 100644 jsign-crypto/src/main/java/net/jsign/JKSKeyStoreType.java
create mode 100644 jsign-crypto/src/main/java/net/jsign/NitrokeyKeyStoreType.java
create mode 100644 jsign-crypto/src/main/java/net/jsign/NoneKeyStoreType.java
create mode 100644 jsign-crypto/src/main/java/net/jsign/OpenPGPKeyStoreType.java
create mode 100644 jsign-crypto/src/main/java/net/jsign/OpenSCKeyStoreType.java
create mode 100644 jsign-crypto/src/main/java/net/jsign/OracleCloudKeyStoreType.java
create mode 100644 jsign-crypto/src/main/java/net/jsign/PIVKeyStoreType.java
create mode 100644 jsign-crypto/src/main/java/net/jsign/PKCS11KeyStoreType.java
create mode 100644 jsign-crypto/src/main/java/net/jsign/PKCS12KeyStoreType.java
create mode 100644 jsign-crypto/src/main/java/net/jsign/SignServerKeyStoreType.java
create mode 100644 jsign-crypto/src/main/java/net/jsign/YubikeyKeyStoreType.java
diff --git a/jsign-crypto/src/main/java/net/jsign/AbstractKeyStoreType.java b/jsign-crypto/src/main/java/net/jsign/AbstractKeyStoreType.java
new file mode 100644
index 00000000..216816ab
--- /dev/null
+++ b/jsign-crypto/src/main/java/net/jsign/AbstractKeyStoreType.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2024 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.FileInputStream;
+import java.io.IOException;
+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.Objects;
+import java.util.function.Function;
+
+abstract class AbstractKeyStoreType implements KeyStoreType {
+
+ public KeyStore getKeystore(KeyStoreBuilder params, Provider provider) throws KeyStoreException {
+ KeyStore ks;
+ try {
+ if (provider != null) {
+ ks = KeyStore.getInstance(name(), provider);
+ } else {
+ ks = KeyStore.getInstance(name());
+ }
+ } catch (KeyStoreException e) {
+ throw new KeyStoreException("keystore type '" + name() + "' is not supported" + (provider != null ? " with security provider " + provider.getName() : ""), e);
+ }
+
+ try {
+ boolean fileBased = this instanceof FileBasedKeyStoreType;
+ 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 keystore " + params.keystore(), e);
+ }
+
+ return ks;
+ }
+
+ 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);
+ }
+ };
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof KeyStoreType)) {
+ return false;
+ }
+ KeyStoreType that = (KeyStoreType) o;
+ return Objects.equals(name(), that.name());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(name());
+ }
+}
diff --git a/jsign-crypto/src/main/java/net/jsign/AmazonKeyStoreType.java b/jsign-crypto/src/main/java/net/jsign/AmazonKeyStoreType.java
new file mode 100644
index 00000000..d1307e04
--- /dev/null
+++ b/jsign-crypto/src/main/java/net/jsign/AmazonKeyStoreType.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2024 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.IOException;
+import java.net.UnknownServiceException;
+import java.security.Provider;
+
+import org.kohsuke.MetaInfServices;
+
+import net.jsign.jca.AmazonCredentials;
+import net.jsign.jca.AmazonSigningService;
+import net.jsign.jca.SigningServiceJcaProvider;
+
+@MetaInfServices(KeyStoreType.class)
+public class AmazonKeyStoreType extends AbstractKeyStoreType {
+
+ @Override
+ public String name() {
+ return "AWS";
+ }
+
+ @Override
+ public 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
+ 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/AzureKeyVaultKeyStoreType.java b/jsign-crypto/src/main/java/net/jsign/AzureKeyVaultKeyStoreType.java
new file mode 100644
index 00000000..1eab8fb1
--- /dev/null
+++ b/jsign-crypto/src/main/java/net/jsign/AzureKeyVaultKeyStoreType.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2024 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.security.Provider;
+
+import org.kohsuke.MetaInfServices;
+
+import net.jsign.jca.AzureKeyVaultSigningService;
+import net.jsign.jca.SigningServiceJcaProvider;
+
+@MetaInfServices(KeyStoreType.class)
+public class AzureKeyVaultKeyStoreType extends AbstractKeyStoreType {
+
+ @Override
+ public String name() {
+ return "AZUREKEYVAULT";
+ }
+
+ @Override
+ public 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
+ public Provider getProvider(KeyStoreBuilder params) {
+ return new SigningServiceJcaProvider(new AzureKeyVaultSigningService(params.keystore(), params.storepass()));
+ }
+}
diff --git a/jsign-crypto/src/main/java/net/jsign/AzureTrustedSigningKeyStoreType.java b/jsign-crypto/src/main/java/net/jsign/AzureTrustedSigningKeyStoreType.java
new file mode 100644
index 00000000..ed882b56
--- /dev/null
+++ b/jsign-crypto/src/main/java/net/jsign/AzureTrustedSigningKeyStoreType.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2024 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.security.Provider;
+
+import org.kohsuke.MetaInfServices;
+
+import net.jsign.jca.AzureTrustedSigningService;
+import net.jsign.jca.SigningServiceJcaProvider;
+
+@MetaInfServices(KeyStoreType.class)
+public class AzureTrustedSigningKeyStoreType extends AbstractKeyStoreType {
+
+ @Override
+ public String name() {
+ return "TRUSTEDSIGNING";
+ }
+
+ @Override
+ public 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
+ public Provider getProvider(KeyStoreBuilder params) {
+ return new SigningServiceJcaProvider(new AzureTrustedSigningService(params.keystore(), params.storepass()));
+ }
+}
diff --git a/jsign-crypto/src/main/java/net/jsign/DigiCertOneKeyStoreType.java b/jsign-crypto/src/main/java/net/jsign/DigiCertOneKeyStoreType.java
new file mode 100644
index 00000000..75dbc407
--- /dev/null
+++ b/jsign-crypto/src/main/java/net/jsign/DigiCertOneKeyStoreType.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2024 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.security.Provider;
+
+import org.kohsuke.MetaInfServices;
+
+import net.jsign.jca.DigiCertOneSigningService;
+import net.jsign.jca.SigningServiceJcaProvider;
+
+@MetaInfServices(KeyStoreType.class)
+public class DigiCertOneKeyStoreType extends AbstractKeyStoreType {
+
+ @Override
+ public String name() {
+ return "DIGICERTONE";
+ }
+
+ @Override
+ public 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
+ 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/ESignerKeyStoreType.java b/jsign-crypto/src/main/java/net/jsign/ESignerKeyStoreType.java
new file mode 100644
index 00000000..46c8a7f5
--- /dev/null
+++ b/jsign-crypto/src/main/java/net/jsign/ESignerKeyStoreType.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2024 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.IOException;
+import java.security.Provider;
+
+import org.kohsuke.MetaInfServices;
+
+import net.jsign.jca.ESignerSigningService;
+import net.jsign.jca.SigningServiceJcaProvider;
+
+@MetaInfServices(KeyStoreType.class)
+public class ESignerKeyStoreType extends AbstractKeyStoreType {
+
+ @Override
+ public String name() {
+ return "ESIGNER";
+ }
+
+ @Override
+ public 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
+ 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);
+ }
+ }
+}
+
diff --git a/jsign-crypto/src/main/java/net/jsign/ETokenKeyStoreType.java b/jsign-crypto/src/main/java/net/jsign/ETokenKeyStoreType.java
new file mode 100644
index 00000000..6e46e9a8
--- /dev/null
+++ b/jsign-crypto/src/main/java/net/jsign/ETokenKeyStoreType.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2024 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.security.Provider;
+
+import org.kohsuke.MetaInfServices;
+
+@MetaInfServices(KeyStoreType.class)
+public class ETokenKeyStoreType extends AbstractKeyStoreType {
+
+ @Override
+ public String name() {
+ return "ETOKEN";
+ }
+
+ @Override
+ public Provider getProvider(KeyStoreBuilder params) {
+ return SafeNetEToken.getProvider();
+ }
+}
diff --git a/jsign-crypto/src/main/java/net/jsign/FileBasedKeyStoreType.java b/jsign-crypto/src/main/java/net/jsign/FileBasedKeyStoreType.java
new file mode 100644
index 00000000..cecebacb
--- /dev/null
+++ b/jsign-crypto/src/main/java/net/jsign/FileBasedKeyStoreType.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2024 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.FileInputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+abstract class FileBasedKeyStoreType extends AbstractKeyStoreType {
+
+ @Override
+ public void validate(KeyStoreBuilder params) {
+ if (params.keystore() == null) {
+ throw new IllegalArgumentException("keystore " + params.parameterName() + " must be set");
+ }
+ if (!params.createFile(params.keystore()).exists()) {
+ throw new IllegalArgumentException("The keystore " + params.keystore() + " couldn't be found");
+ }
+ if (params.keypass() == null && params.storepass() != null) {
+ // reuse the storepass as the keypass
+ params.keypass(params.storepass());
+ }
+ }
+
+ boolean hasSignature(File file, long signature, long mask) {
+ if (file.exists()) {
+ try (FileInputStream in = new FileInputStream(file)) {
+ byte[] header = new byte[4];
+ in.read(header);
+ ByteBuffer buffer = ByteBuffer.wrap(header);
+ if ((buffer.getInt(0) & mask) == signature) {
+ return true;
+ }
+ } catch (IOException e) {
+ throw new RuntimeException("Unable to load the keystore " + file, e);
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Tells if the specified file is a keystore of this type.
+ *
+ * @param file the path to the keystore
+ */
+ abstract boolean isSupported(File file);
+}
diff --git a/jsign-crypto/src/main/java/net/jsign/GaraSignKeyStoreType.java b/jsign-crypto/src/main/java/net/jsign/GaraSignKeyStoreType.java
new file mode 100644
index 00000000..a04a4591
--- /dev/null
+++ b/jsign-crypto/src/main/java/net/jsign/GaraSignKeyStoreType.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2024 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.security.Provider;
+
+import org.kohsuke.MetaInfServices;
+
+import net.jsign.jca.GaraSignCredentials;
+import net.jsign.jca.GaraSignSigningService;
+import net.jsign.jca.SigningServiceJcaProvider;
+
+@MetaInfServices(KeyStoreType.class)
+public class GaraSignKeyStoreType extends AbstractKeyStoreType {
+
+ @Override
+ public String name() {
+ return "GARASIGN";
+ }
+
+ @Override
+ public 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
+ 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/GoogleCloudKeyStoreType.java b/jsign-crypto/src/main/java/net/jsign/GoogleCloudKeyStoreType.java
new file mode 100644
index 00000000..dfc8eb80
--- /dev/null
+++ b/jsign-crypto/src/main/java/net/jsign/GoogleCloudKeyStoreType.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2024 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.security.Provider;
+
+import org.kohsuke.MetaInfServices;
+
+import net.jsign.jca.GoogleCloudSigningService;
+import net.jsign.jca.SigningServiceJcaProvider;
+
+@MetaInfServices(KeyStoreType.class)
+public class GoogleCloudKeyStoreType extends AbstractKeyStoreType {
+
+ @Override
+ public String name() {
+ return "GOOGLECLOUD";
+ }
+
+ @Override
+ public 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
+ 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/HashiCorpVaultKeyStoreType.java b/jsign-crypto/src/main/java/net/jsign/HashiCorpVaultKeyStoreType.java
new file mode 100644
index 00000000..560d1ac8
--- /dev/null
+++ b/jsign-crypto/src/main/java/net/jsign/HashiCorpVaultKeyStoreType.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2024 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.security.Provider;
+
+import org.kohsuke.MetaInfServices;
+
+import net.jsign.jca.HashiCorpVaultSigningService;
+import net.jsign.jca.SigningServiceJcaProvider;
+
+@MetaInfServices(KeyStoreType.class)
+public class HashiCorpVaultKeyStoreType extends AbstractKeyStoreType {
+
+ @Override
+ public String name() {
+ return "HASHICORPVAULT";
+ }
+
+ @Override
+ public 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
+ 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/JCEKSKeyStoreType.java b/jsign-crypto/src/main/java/net/jsign/JCEKSKeyStoreType.java
new file mode 100644
index 00000000..87e84ce9
--- /dev/null
+++ b/jsign-crypto/src/main/java/net/jsign/JCEKSKeyStoreType.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2024 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 org.kohsuke.MetaInfServices;
+
+@MetaInfServices(KeyStoreType.class)
+public class JCEKSKeyStoreType extends FileBasedKeyStoreType {
+
+ @Override
+ public String name() {
+ return "JCEKS";
+ }
+
+ @Override
+ boolean isSupported(File file) {
+ String filename = file.getName().toLowerCase();
+ return hasSignature(file, 0xCECECECEL, 0xFFFFFFFFL) || filename.endsWith(".jceks");
+ }
+}
diff --git a/jsign-crypto/src/main/java/net/jsign/JKSKeyStoreType.java b/jsign-crypto/src/main/java/net/jsign/JKSKeyStoreType.java
new file mode 100644
index 00000000..628c9d9c
--- /dev/null
+++ b/jsign-crypto/src/main/java/net/jsign/JKSKeyStoreType.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2024 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 org.kohsuke.MetaInfServices;
+
+@MetaInfServices(KeyStoreType.class)
+public class JKSKeyStoreType extends FileBasedKeyStoreType {
+
+ @Override
+ public String name() {
+ return "JKS";
+ }
+
+ @Override
+ boolean isSupported(File file) {
+ String filename = file.getName().toLowerCase();
+ return hasSignature(file, 0xFEEDFEEDL, 0xFFFFFFFFL) || filename.endsWith(".jks");
+ }
+}
diff --git a/jsign-crypto/src/main/java/net/jsign/KeyStoreBuilder.java b/jsign-crypto/src/main/java/net/jsign/KeyStoreBuilder.java
index 51d75f0b..57c00423 100644
--- a/jsign-crypto/src/main/java/net/jsign/KeyStoreBuilder.java
+++ b/jsign-crypto/src/main/java/net/jsign/KeyStoreBuilder.java
@@ -24,6 +24,7 @@
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.Provider;
+import java.util.ServiceLoader;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -140,7 +141,7 @@ KeyStoreType storetype() {
if (!file.isFile()) {
throw new IllegalArgumentException("Keystore file '" + keystore + "' not found");
}
- storetype = KeyStoreType.of(file);
+ storetype = getType(file);
if (storetype == null) {
throw new IllegalArgumentException("Keystore type of '" + keystore + "' not recognized");
}
@@ -149,6 +150,21 @@ KeyStoreType storetype() {
return storetype;
}
+ /**
+ * Guess the type of the keystore from the header or the extension of the file.
+ *
+ * @param file the path to the keystore
+ */
+ static KeyStoreType getType(File file) {
+ for (KeyStoreType storetype : ServiceLoader.load(KeyStoreType.class)) {
+ if (storetype instanceof FileBasedKeyStoreType && ((FileBasedKeyStoreType) storetype).isSupported(file)) {
+ return storetype;
+ }
+ }
+
+ return null;
+ }
+
/**
* Sets the password to access the private key. The password can be loaded from a file by using the file:
* prefix followed by the path of the file, or from an environment variable by using the env:
prefix
diff --git a/jsign-crypto/src/main/java/net/jsign/KeyStoreType.java b/jsign-crypto/src/main/java/net/jsign/KeyStoreType.java
index 3f92aa29..5ef62cc3 100644
--- a/jsign-crypto/src/main/java/net/jsign/KeyStoreType.java
+++ b/jsign-crypto/src/main/java/net/jsign/KeyStoreType.java
@@ -16,179 +16,40 @@
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.ServiceLoader;
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 java.util.stream.StreamSupport;
/**
* Type of a keystore.
*
* @since 5.0
*/
-public enum KeyStoreType {
+public interface 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) {
- @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();
- ks.setKeyEntry("jsign", privateKey, keypass != null ? keypass.toCharArray() : new char[0], chain);
- } catch (Exception e) {
- throw new KeyStoreException(e);
- }
-
- return ks;
- }
- },
+ KeyStoreType NONE = KeyStoreType.valueOf("NONE");
/** Java keystore */
- JKS(true, false) {
- @Override
- void validate(KeyStoreBuilder params) {
- if (params.keystore() == null) {
- throw new IllegalArgumentException("keystore " + params.parameterName() + " must be set");
- }
- if (!params.createFile(params.keystore()).exists()) {
- throw new IllegalArgumentException("The keystore " + params.keystore() + " couldn't be found");
- }
- if (params.keypass() == null && params.storepass() != null) {
- // reuse the storepass as the keypass
- params.keypass(params.storepass());
- }
- }
- },
+ KeyStoreType JKS = KeyStoreType.valueOf("JKS");
/** JCE keystore */
- JCEKS(true, false) {
- @Override
- void validate(KeyStoreBuilder params) {
- if (params.keystore() == null) {
- throw new IllegalArgumentException("keystore " + params.parameterName() + " must be set");
- }
- if (!params.createFile(params.keystore()).exists()) {
- throw new IllegalArgumentException("The keystore " + params.keystore() + " couldn't be found");
- }
- if (params.keypass() == null && params.storepass() != null) {
- // reuse the storepass as the keypass
- params.keypass(params.storepass());
- }
- }
- },
+ KeyStoreType JCEKS = KeyStoreType.valueOf("JCEKS");
/** PKCS#12 keystore */
- PKCS12(true, false) {
- @Override
- void validate(KeyStoreBuilder params) {
- if (params.keystore() == null) {
- throw new IllegalArgumentException("keystore " + params.parameterName() + " must be set");
- }
- if (!params.createFile(params.keystore()).exists()) {
- throw new IllegalArgumentException("The keystore " + params.keystore() + " couldn't be found");
- }
- if (params.keypass() == null && params.storepass() != null) {
- // reuse the storepass as the keypass
- params.keypass(params.storepass());
- }
- }
- },
+ KeyStoreType PKCS12 = KeyStoreType.valueOf("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) {
- @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");
- }
- }
- },
+ KeyStoreType PKCS11 = KeyStoreType.valueOf("PKCS11");
/**
* OpenPGP card. OpenPGP cards contain up to 3 keys, one for signing, one for encryption, and one for authentication.
@@ -198,35 +59,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) {
- @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);
- }
- }
- },
+ KeyStoreType OPENPGP = KeyStoreType.valueOf("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) {
- @Override
- Provider getProvider(KeyStoreBuilder params) {
- return OpenSC.getProvider(params.keystore());
- }
- },
+ KeyStoreType OPENSC = KeyStoreType.valueOf("OPENSC");
/**
* PIV card. PIV cards contain up to 24 private keys and certificates. The alias to select the key is either,
@@ -235,23 +75,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) {
- @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);
- }
- }
- },
+ KeyStoreType PIV = KeyStoreType.valueOf("PIV");
/**
* Nitrokey HSM. This keystore requires the installation of OpenSC.
@@ -259,32 +83,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) {
- @Override
- Provider getProvider(KeyStoreBuilder params) {
- return OpenSC.getProvider(params.keystore() != null ? params.keystore() : "Nitrokey");
- }
- },
+ KeyStoreType NITROKEY = KeyStoreType.valueOf("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) {
- @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;
- }
- },
+ KeyStoreType YUBIKEY = KeyStoreType.valueOf("YUBIKEY");
/**
* AWS Key Management Service (KMS). AWS KMS stores only the private key, the certificate must be provided
@@ -298,131 +104,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) {
- @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)));
- }
- },
+ KeyStoreType AWS = KeyStoreType.valueOf("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, 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()));
- }
- },
+ KeyStoreType AZUREKEYVAULT = KeyStoreType.valueOf("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, 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]));
- }
- },
+ KeyStoreType DIGICERTONE = KeyStoreType.valueOf("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, 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);
- }
- }
- },
+ KeyStoreType ESIGNER = KeyStoreType.valueOf("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) {
- @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)));
- }
- },
+ KeyStoreType GOOGLECLOUD = KeyStoreType.valueOf("GOOGLECLOUD");
/**
* HashiCorp Vault secrets engine (Transit or GCPKMS). The certificate must be provided separately. The keystore
@@ -430,36 +139,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) {
- @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)));
- }
- },
+ KeyStoreType HASHICORPVAULT = KeyStoreType.valueOf("HASHICORPVAULT");
/**
* SafeNet eToken
* This keystore requires the installation of the SafeNet Authentication Client.
*/
- ETOKEN(false, true) {
- @Override
- Provider getProvider(KeyStoreBuilder params) {
- return SafeNetEToken.getProvider();
- }
- },
+ KeyStoreType ETOKEN = KeyStoreType.valueOf("ETOKEN");
/**
* Oracle Cloud Infrastructure Key Management Service. This keystore requires the configuration file
@@ -473,38 +159,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) {
- @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)));
- }
- },
+ KeyStoreType ORACLECLOUD = KeyStoreType.valueOf("ORACLECLOUD");
/**
* Azure Trusted Signing Service. The keystore parameter specifies the API endpoint (for example
@@ -513,52 +168,9 @@ Provider getProvider(KeyStoreBuilder params) {
*
* az account get-access-token --resource https://codesigning.azure.net
*/
- TRUSTEDSIGNING(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()));
- }
- },
+ KeyStoreType TRUSTEDSIGNING = KeyStoreType.valueOf("TRUSTEDSIGNING");
- GARASIGN(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));
- }
- },
+ KeyStoreType GARASIGN = KeyStoreType.valueOf("GARASIGN");
/**
* Keyfactor SignServer. This keystore requires a Plain Signer worker configured to allow client-side hashing (with
@@ -570,144 +182,56 @@ Provider getProvider(KeyStoreBuilder params) {
* specified in the keypass parameter. The keystore parameter references the URL of the SignServer REST API. The
* alias parameter specifies the id or the name of the worker.
*/
- SIGNSERVER(false, false) {
- @Override
- void validate(KeyStoreBuilder params) {
- if (params.keystore() == null) {
- throw new IllegalArgumentException("keystore " + params.parameterName() + " must specify the SignServer API endpoint (e.g. https://example.com/signserver/)");
- }
- 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: | or ");
- }
- }
-
- @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;
+ KeyStoreType SIGNSERVER = KeyStoreType.valueOf("SIGNSERVER");
- /** Tells if the keystore is actually a PKCS#11 keystore */
- private final boolean pkcs11;
-
- KeyStoreType(boolean fileBased, boolean pkcs11) {
- this.fileBased = fileBased;
- this.pkcs11 = pkcs11;
- }
+ /**
+ * Returns the name of the keystore type.
+ */
+ String name();
/**
* Validates the keystore parameters.
*/
- void validate(KeyStoreBuilder params) throws IllegalArgumentException {
+ default void validate(KeyStoreBuilder params) throws IllegalArgumentException {
}
/**
* Returns the security provider to use the keystore.
*/
- Provider getProvider(KeyStoreBuilder params) {
+ default 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);
- }
-
- 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;
- }
+ KeyStore getKeystore(KeyStoreBuilder params, Provider provider) throws KeyStoreException;
/**
* Returns the aliases of the keystore available for signing.
*/
- Set getAliases(KeyStore keystore) throws KeyStoreException {
+ default Set getAliases(KeyStore keystore) throws KeyStoreException {
return new LinkedHashSet<>(Collections.list(keystore.aliases()));
}
/**
- * Guess the type of the keystore from the header or the extension of the file.
+ * Returns the storetype with the specified name.
*
- * @param path the path to the keystore
+ * @param name the name of the storetype
+ * @return the storetype with the specified name
+ * @throws IllegalArgumentException if the storetype specified isn't supported
*/
- 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);
+ static KeyStoreType valueOf(String name) {
+ for (KeyStoreType storetype : ServiceLoader.load(KeyStoreType.class)) {
+ if (name.equals(storetype.name())) {
+ return storetype;
}
}
- // 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;
- }
+ throw new IllegalArgumentException("Unsupported KeyStore type: " + name);
}
- private 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);
- }
- };
+ static KeyStoreType[] values() {
+ return StreamSupport.stream(ServiceLoader.load(KeyStoreType.class).spliterator(), false).toArray(KeyStoreType[]::new);
}
}
diff --git a/jsign-crypto/src/main/java/net/jsign/NitrokeyKeyStoreType.java b/jsign-crypto/src/main/java/net/jsign/NitrokeyKeyStoreType.java
new file mode 100644
index 00000000..8afb7b44
--- /dev/null
+++ b/jsign-crypto/src/main/java/net/jsign/NitrokeyKeyStoreType.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2024 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.security.Provider;
+
+import org.kohsuke.MetaInfServices;
+
+@MetaInfServices(KeyStoreType.class)
+public class NitrokeyKeyStoreType extends PKCS11KeyStoreType {
+
+ @Override
+ public String name() {
+ return "NITROKEY";
+ }
+
+ @Override
+ public Provider getProvider(KeyStoreBuilder params) {
+ return OpenSC.getProvider(params.keystore() != null ? params.keystore() : "Nitrokey");
+ }
+}
diff --git a/jsign-crypto/src/main/java/net/jsign/NoneKeyStoreType.java b/jsign-crypto/src/main/java/net/jsign/NoneKeyStoreType.java
new file mode 100644
index 00000000..3f6b5f52
--- /dev/null
+++ b/jsign-crypto/src/main/java/net/jsign/NoneKeyStoreType.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2024 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.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.PrivateKey;
+import java.security.Provider;
+import java.security.cert.Certificate;
+
+import org.kohsuke.MetaInfServices;
+
+@MetaInfServices(KeyStoreType.class)
+public class NoneKeyStoreType extends AbstractKeyStoreType {
+
+ @Override
+ public String name() {
+ return "NONE";
+ }
+
+ @Override
+ public 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
+ 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();
+ 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/OpenPGPKeyStoreType.java b/jsign-crypto/src/main/java/net/jsign/OpenPGPKeyStoreType.java
new file mode 100644
index 00000000..24a81b76
--- /dev/null
+++ b/jsign-crypto/src/main/java/net/jsign/OpenPGPKeyStoreType.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2024 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.security.Provider;
+import javax.smartcardio.CardException;
+
+import org.kohsuke.MetaInfServices;
+
+import net.jsign.jca.OpenPGPCardSigningService;
+import net.jsign.jca.SigningServiceJcaProvider;
+
+@MetaInfServices(KeyStoreType.class)
+public class OpenPGPKeyStoreType extends AbstractKeyStoreType {
+
+ @Override
+ public String name() {
+ return "OPENPGP";
+ }
+
+ @Override
+ public void validate(KeyStoreBuilder params) {
+ 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/OpenSCKeyStoreType.java b/jsign-crypto/src/main/java/net/jsign/OpenSCKeyStoreType.java
new file mode 100644
index 00000000..41d47225
--- /dev/null
+++ b/jsign-crypto/src/main/java/net/jsign/OpenSCKeyStoreType.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2024 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.security.Provider;
+
+import org.kohsuke.MetaInfServices;
+
+@MetaInfServices(KeyStoreType.class)
+public class OpenSCKeyStoreType extends PKCS11KeyStoreType {
+
+ @Override
+ public String name() {
+ return "OPENSC";
+ }
+
+ @Override
+ public Provider getProvider(KeyStoreBuilder params) {
+ return OpenSC.getProvider(params.keystore());
+ }
+}
diff --git a/jsign-crypto/src/main/java/net/jsign/OracleCloudKeyStoreType.java b/jsign-crypto/src/main/java/net/jsign/OracleCloudKeyStoreType.java
new file mode 100644
index 00000000..136e9d59
--- /dev/null
+++ b/jsign-crypto/src/main/java/net/jsign/OracleCloudKeyStoreType.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2024 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 org.kohsuke.MetaInfServices;
+
+import net.jsign.jca.OracleCloudCredentials;
+import net.jsign.jca.OracleCloudSigningService;
+import net.jsign.jca.SigningServiceJcaProvider;
+
+@MetaInfServices(KeyStoreType.class)
+public class OracleCloudKeyStoreType extends AbstractKeyStoreType {
+
+ @Override
+ public String name() {
+ return "ORACLECLOUD";
+ }
+
+ @Override
+ public void validate(KeyStoreBuilder params) {
+ 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/PIVKeyStoreType.java b/jsign-crypto/src/main/java/net/jsign/PIVKeyStoreType.java
new file mode 100644
index 00000000..12613956
--- /dev/null
+++ b/jsign-crypto/src/main/java/net/jsign/PIVKeyStoreType.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2024 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.security.Provider;
+import javax.smartcardio.CardException;
+
+import org.kohsuke.MetaInfServices;
+
+import net.jsign.jca.PIVCardSigningService;
+import net.jsign.jca.SigningServiceJcaProvider;
+
+@MetaInfServices(KeyStoreType.class)
+public class PIVKeyStoreType extends AbstractKeyStoreType {
+
+ @Override
+ public String name() {
+ return "PIV";
+ }
+
+ @Override
+ public void validate(KeyStoreBuilder params) {
+ 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/PKCS11KeyStoreType.java b/jsign-crypto/src/main/java/net/jsign/PKCS11KeyStoreType.java
new file mode 100644
index 00000000..19f43dce
--- /dev/null
+++ b/jsign-crypto/src/main/java/net/jsign/PKCS11KeyStoreType.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2024 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.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.Provider;
+import java.security.Security;
+
+import org.kohsuke.MetaInfServices;
+
+@MetaInfServices(KeyStoreType.class)
+public class PKCS11KeyStoreType extends AbstractKeyStoreType {
+
+ @Override
+ public String name() {
+ return "PKCS11";
+ }
+
+ @Override
+ public void validate(KeyStoreBuilder params) {
+ 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");
+ }
+ }
+
+ @Override
+ public KeyStore getKeystore(KeyStoreBuilder params, Provider provider) throws KeyStoreException {
+ KeyStore ks;
+ try {
+ if (provider != null) {
+ ks = KeyStore.getInstance("PKCS11", provider);
+ } else {
+ ks = KeyStore.getInstance("PKCS11");
+ }
+ } catch (KeyStoreException e) {
+ throw new KeyStoreException("keystore type '" + name() + "' is not supported" + (provider != null ? " with security provider " + provider.getName() : ""), e);
+ }
+
+ try {
+ ks.load(null, params.storepass() != null ? params.storepass().toCharArray() : null);
+ } catch (Exception e) {
+ throw new KeyStoreException("Unable to load the keystore " + params.keystore(), e);
+ }
+
+ return ks;
+ }
+}
diff --git a/jsign-crypto/src/main/java/net/jsign/PKCS12KeyStoreType.java b/jsign-crypto/src/main/java/net/jsign/PKCS12KeyStoreType.java
new file mode 100644
index 00000000..2cca7d6a
--- /dev/null
+++ b/jsign-crypto/src/main/java/net/jsign/PKCS12KeyStoreType.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2024 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 org.kohsuke.MetaInfServices;
+
+@MetaInfServices(KeyStoreType.class)
+public class PKCS12KeyStoreType extends FileBasedKeyStoreType {
+
+ @Override
+ public String name() {
+ return "PKCS12";
+ }
+
+ @Override
+ boolean isSupported(File file) {
+ String filename = file.getName().toLowerCase();
+ return hasSignature(file, 0x30000000L, 0xFF000000L) || filename.endsWith(".p12") || filename.endsWith(".pfx");
+ }
+}
diff --git a/jsign-crypto/src/main/java/net/jsign/SignServerKeyStoreType.java b/jsign-crypto/src/main/java/net/jsign/SignServerKeyStoreType.java
new file mode 100644
index 00000000..178e7666
--- /dev/null
+++ b/jsign-crypto/src/main/java/net/jsign/SignServerKeyStoreType.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2024 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.security.Provider;
+
+import org.kohsuke.MetaInfServices;
+
+import net.jsign.jca.SignServerCredentials;
+import net.jsign.jca.SignServerSigningService;
+import net.jsign.jca.SigningServiceJcaProvider;
+
+@MetaInfServices(KeyStoreType.class)
+public class SignServerKeyStoreType extends AbstractKeyStoreType {
+
+ @Override
+ public String name() {
+ return "SIGNSERVER";
+ }
+
+ @Override
+ public void validate(KeyStoreBuilder params) {
+ if (params.keystore() == null) {
+ throw new IllegalArgumentException("keystore " + params.parameterName() + " must specify the SignServer API endpoint (e.g. https://example.com/signserver/)");
+ }
+ 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: | or ");
+ }
+ }
+
+ @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/YubikeyKeyStoreType.java b/jsign-crypto/src/main/java/net/jsign/YubikeyKeyStoreType.java
new file mode 100644
index 00000000..cee1f911
--- /dev/null
+++ b/jsign-crypto/src/main/java/net/jsign/YubikeyKeyStoreType.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2024 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.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.Provider;
+import java.util.Set;
+
+import org.kohsuke.MetaInfServices;
+
+@MetaInfServices(KeyStoreType.class)
+public class YubikeyKeyStoreType extends PKCS11KeyStoreType {
+
+ @Override
+ public String name() {
+ return "YUBIKEY";
+ }
+
+ @Override
+ public Provider getProvider(KeyStoreBuilder params) {
+ return YubiKey.getProvider();
+ }
+
+ @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;
+ }
+}
diff --git a/jsign-crypto/src/test/java/net/jsign/KeyStoreBuilderTest.java b/jsign-crypto/src/test/java/net/jsign/KeyStoreBuilderTest.java
index bcb029b2..2d3bae49 100644
--- a/jsign-crypto/src/test/java/net/jsign/KeyStoreBuilderTest.java
+++ b/jsign-crypto/src/test/java/net/jsign/KeyStoreBuilderTest.java
@@ -457,4 +457,45 @@ public void testLowerCaseStoreType() {
KeyStoreBuilder builder = new KeyStoreBuilder().storetype("pkcs12");
assertEquals("storetype", PKCS12, builder.storetype());
}
+
+ @Test
+ public void testGetType() {
+ assertEquals(PKCS12, KeyStoreBuilder.getType(new File("keystore.p12")));
+ assertEquals(PKCS12, KeyStoreBuilder.getType(new File("keystore.pfx")));
+ assertEquals(JCEKS, KeyStoreBuilder.getType(new File("keystore.jceks")));
+ assertEquals(JKS, KeyStoreBuilder.getType(new File("keystore.jks")));
+ assertNull(KeyStoreBuilder.getType(new File("keystore.unknown")));
+ }
+
+ @Test
+ public void testGetTypePKCS12FromHeader() throws Exception {
+ File source = new File("target/test-classes/keystores/keystore.p12");
+ File target = new File("target/test-classes/keystores/keystore.p12.ext");
+ FileUtils.copyFile(source, target);
+
+ assertEquals(PKCS12, KeyStoreBuilder.getType(target));
+ }
+
+ @Test
+ public void testGetTypeJCEKSFromHeader() throws Exception {
+ File source = new File("target/test-classes/keystores/keystore.jceks");
+ File target = new File("target/test-classes/keystores/keystore.jceks.ext");
+ FileUtils.copyFile(source, target);
+
+ assertEquals(JCEKS, KeyStoreBuilder.getType(target));
+ }
+
+ @Test
+ public void testGetTypeJKSFromHeader() throws Exception {
+ File source = new File("target/test-classes/keystores/keystore.jks");
+ File target = new File("target/test-classes/keystores/keystore.jks.ext");
+ FileUtils.copyFile(source, target);
+
+ assertEquals(JKS, KeyStoreBuilder.getType(target));
+ }
+
+ @Test
+ public void testGetTypeUnknown() {
+ assertNull(KeyStoreBuilder.getType(new File("target/test-classes/keystores/jsign-root-ca.pem")));
+ }
}
diff --git a/jsign-crypto/src/test/java/net/jsign/KeyStoreTypeTest.java b/jsign-crypto/src/test/java/net/jsign/KeyStoreTypeTest.java
index 73f027a5..26a5f5a7 100644
--- a/jsign-crypto/src/test/java/net/jsign/KeyStoreTypeTest.java
+++ b/jsign-crypto/src/test/java/net/jsign/KeyStoreTypeTest.java
@@ -16,54 +16,31 @@
package net.jsign;
-import java.io.File;
+import java.util.Arrays;
+import java.util.List;
-import org.apache.commons.io.FileUtils;
import org.junit.Test;
-import static net.jsign.KeyStoreType.*;
import static org.junit.Assert.*;
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")));
+ public void testValueOf() {
+ assertEquals(KeyStoreType.JCEKS, KeyStoreType.valueOf("JCEKS"));
+ assertEquals(KeyStoreType.OPENSC, KeyStoreType.valueOf("OPENSC"));
+ assertEquals(KeyStoreType.OPENPGP, KeyStoreType.valueOf("OPENPGP"));
+ assertEquals(KeyStoreType.DIGICERTONE, KeyStoreType.valueOf("DIGICERTONE"));
}
@Test
- public void testGetTypePKCS12FromHeader() throws Exception {
- File source = new File("target/test-classes/keystores/keystore.p12");
- File target = new File("target/test-classes/keystores/keystore.p12.ext");
- FileUtils.copyFile(source, target);
-
- assertEquals(PKCS12, KeyStoreType.of(target));
- }
-
- @Test
- public void testGetTypeJCEKSFromHeader() throws Exception {
- File source = new File("target/test-classes/keystores/keystore.jceks");
- File target = new File("target/test-classes/keystores/keystore.jceks.ext");
- FileUtils.copyFile(source, target);
-
- assertEquals(JCEKS, KeyStoreType.of(target));
- }
-
- @Test
- public void testGetTypeJKSFromHeader() throws Exception {
- File source = new File("target/test-classes/keystores/keystore.jks");
- File target = new File("target/test-classes/keystores/keystore.jks.ext");
- FileUtils.copyFile(source, target);
-
- assertEquals(JKS, KeyStoreType.of(target));
- }
-
- @Test
- public void testGetTypeUnknown() throws Exception {
- assertNull(KeyStoreType.of(new File("target/test-classes/keystores/jsign-root-ca.pem")));
+ public void testValues() {
+ List values = Arrays.asList(KeyStoreType.values());
+ assertTrue(values.contains(KeyStoreType.NONE));
+ assertTrue(values.contains(KeyStoreType.PKCS12));
+ assertTrue(values.contains(KeyStoreType.JKS));
+ assertTrue(values.contains(KeyStoreType.PKCS11));
+ assertTrue(values.contains(KeyStoreType.PIV));
+ assertTrue(values.contains(KeyStoreType.AWS));
}
}