diff --git a/README.md b/README.md
index e579fc97..736013ef 100644
--- a/README.md
+++ b/README.md
@@ -35,6 +35,7 @@ Jsign is free to use and licensed under the [Apache License version 2.0](https:/
* Build tools integration (Maven, Gradle, Ant)
* Command line signing tool
* Authenticode signing API ([Javadoc](https://javadoc.io/doc/net.jsign/jsign-core))
+* JCA security provider to use the keystores supported by Jsign with other tools such as jarsigner
See https://ebourg.github.io/jsign for more information.
@@ -50,6 +51,7 @@ See https://ebourg.github.io/jsign for more information.
* Only one call to the Google Cloud API is performed when the version of the key is specified in the alias parameter
* JVM arguments can now be passed using the `JSIGN_OPTS` environment variable
* API changes:
+ * New `net.jsign.jca.JsignJcaProvider` JCA security provider to be used with other signing tools such as jarsigner
* The signature can be removed by setting a null signature on the `Signable` object
* `Signable.computeDigest(MessageDigest)` has been replaced by `Signable.computeDigest(DigestAlgorithm)`
* The value of the `http.agent` system property is now appended to the user agent string set when calling REST services
diff --git a/docs/index.html b/docs/index.html
index 9106c42d..6eda6be2 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -73,6 +73,7 @@
The keystore parameter must be set to NONE, the actual value of the keystore is specified
+with the providerArg parameter instead.
+
+
Downloads
diff --git a/jsign-core/src/main/java/net/jsign/jca/AbstractKeyStoreSpi.java b/jsign-core/src/main/java/net/jsign/jca/AbstractKeyStoreSpi.java
new file mode 100644
index 00000000..b2cbda78
--- /dev/null
+++ b/jsign-core/src/main/java/net/jsign/jca/AbstractKeyStoreSpi.java
@@ -0,0 +1,105 @@
+/**
+ * Copyright 2023 Emmanuel Bourg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.jsign.jca;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.Key;
+import java.security.KeyStoreSpi;
+import java.security.cert.Certificate;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Enumeration;
+
+/**
+ * Base class for JCA keystore implementations.
+ *
+ * @since 5.1
+ */
+abstract class AbstractKeyStoreSpi extends KeyStoreSpi {
+
+ @Override
+ public Certificate engineGetCertificate(String alias) {
+ Certificate[] chain = engineGetCertificateChain(alias);
+ return chain != null && chain.length > 0 ? chain[0] : null;
+ }
+
+ @Override
+ public Date engineGetCreationDate(String alias) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void engineSetKeyEntry(String alias, Key key, char[] password, Certificate[] chain) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void engineSetKeyEntry(String alias, byte[] key, Certificate[] chain) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void engineSetCertificateEntry(String alias, Certificate cert) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void engineDeleteEntry(String alias) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean engineContainsAlias(String alias) {
+ Enumeration aliases = engineAliases();
+ while (aliases.hasMoreElements()) {
+ if (aliases.nextElement().equals(alias)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public int engineSize() {
+ return Collections.list(engineAliases()).size();
+ }
+
+ @Override
+ public boolean engineIsKeyEntry(String alias) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean engineIsCertificateEntry(String alias) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String engineGetCertificateAlias(Certificate cert) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void engineStore(OutputStream stream, char[] password) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void engineLoad(InputStream stream, char[] password) {
+ }
+}
diff --git a/jsign-core/src/main/java/net/jsign/jca/JsignJcaProvider.java b/jsign-core/src/main/java/net/jsign/jca/JsignJcaProvider.java
new file mode 100644
index 00000000..6b657ac4
--- /dev/null
+++ b/jsign-core/src/main/java/net/jsign/jca/JsignJcaProvider.java
@@ -0,0 +1,140 @@
+/**
+ * Copyright 2023 Emmanuel Bourg
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.jsign.jca;
+
+import java.io.InputStream;
+import java.security.AccessController;
+import java.security.InvalidParameterException;
+import java.security.Key;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivilegedAction;
+import java.security.Provider;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.Certificate;
+import java.util.Enumeration;
+
+import net.jsign.DigestAlgorithm;
+import net.jsign.KeyStoreBuilder;
+import net.jsign.KeyStoreType;
+
+/**
+ * JCA provider using a Jsign keystore and compatible with jarsigner.
+ *
+ *
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.