Skip to content

Commit

Permalink
New JCA security provider to be used with other signing tools
Browse files Browse the repository at this point in the history
  • Loading branch information
ebourg committed Oct 12, 2023
1 parent 9b42a52 commit 595a01c
Show file tree
Hide file tree
Showing 7 changed files with 342 additions and 76 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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
Expand Down
23 changes: 23 additions & 0 deletions docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ <h3>Features</h3>
<li>Build tools integration (<a href="#maven">Maven</a>, <a href="#gradle">Gradle</a>, <a href="#ant">Ant</a>)</li>
<li>Command line signing tool</li>
<li>Authenticode signing API (<a href="https://javadoc.io/doc/net.jsign/jsign-core">Javadoc</a>)</li>
<li>JCA security provider to use the keystores supported by Jsign with other tools such as jarsigner</li>
</ul>


Expand Down Expand Up @@ -686,6 +687,28 @@ <h3>API</h3>
<p>See the <a href="https://javadoc.io/doc/net.jsign/jsign-core">Javadoc</a> for more details about the API.</p>


<h3>JCA security provider</h3>

<p>Jsign implements a JCA security provider that can be used to sign JAR files with the <code>jarsigner</code> tool.</p>

<p>It requires Java 11 or later, and the syntax looks like this:</p>

<pre>
jarsigner -J-cp -Jjsign-5.1.jar -J--add-modules -Jjava.sql \
-providerClass net.jsign.jca.JsignJcaProvider \
-providerArg &lt;keystore&gt; \
-keystore NONE \
-storetype &lt;storetype&gt; \
-storepass &lt;storepass&gt; \
-keypass &lt;keypass&gt; \
-certchain &lt;certfile&gt; \
application.jar &lt;alias&gt;
</pre>

<p>The <code>keystore</code> parameter must be set to <code>NONE</code>, the actual value of the keystore is specified
with the <code>providerArg</code> parameter instead.</p>


<h3 id="files">Downloads</h3>

<ul>
Expand Down
105 changes: 105 additions & 0 deletions jsign-core/src/main/java/net/jsign/jca/AbstractKeyStoreSpi.java
Original file line number Diff line number Diff line change
@@ -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<String> 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) {
}
}
140 changes: 140 additions & 0 deletions jsign-core/src/main/java/net/jsign/jca/JsignJcaProvider.java
Original file line number Diff line number Diff line change
@@ -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.
*
* <p>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.</p>
*
* <p>Example:</p>
* <pre>
* Provider provider = new JsignJcaProvider();
* provider.configure(vaultname)
* KeyStore keystore = KeyStore.getInstance(AZUREKEYVAULT.name(), provider);
* keystore.load(null, accessToken);
* </pre>
*
* @since 5.1
*/
public class JsignJcaProvider extends Provider {

private String keystore;

public JsignJcaProvider() {
super("Jsign", 1.0, "Jsign security provider");

AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
for (KeyStoreType type : KeyStoreType.values()) {
putService(new ProviderService(this, "KeyStore", type.name(), JsignJcaKeyStore.class.getName(), () -> new JsignJcaKeyStore(type, keystore)));
}
for (String alg : new String[]{"RSA", "ECDSA"}) {
for (DigestAlgorithm digest : DigestAlgorithm.values()) {
if (digest != DigestAlgorithm.MD5) {
String algorithm = digest.name() + "with" + alg;
putService(new ProviderService(this, "Signature", algorithm, SigningServiceSignature.class.getName(), () -> new SigningServiceSignature(algorithm)));
}
}
}
return null;
});
}

public Provider configure(String configArg) throws InvalidParameterException {
this.keystore = configArg;

return this;
}

static class JsignJcaKeyStore extends AbstractKeyStoreSpi {

private KeyStoreBuilder builder = new KeyStoreBuilder();
private KeyStore keystore;

public JsignJcaKeyStore(KeyStoreType type, String keystore) {
builder.storetype(type);
builder.keystore(keystore);
builder.certfile("");
}

private KeyStore getKeyStore() throws KeyStoreException {
if (keystore == null) {
keystore = builder.build();
}

return keystore;
}

@Override
public Key engineGetKey(String alias, char[] password) throws UnrecoverableKeyException {
if (password != null) {
builder.keypass(new String(password));
}
try {
return getKeyStore().getKey(alias, password);
} catch (UnrecoverableKeyException e) {
e.printStackTrace(); // because jarsigner swallows the root cause and hides what's going on
throw e;
} catch (KeyStoreException | NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}

@Override
public Certificate[] engineGetCertificateChain(String alias) {
try {
return getKeyStore().getCertificateChain(alias);
} catch (KeyStoreException e) {
return null;
}
}

@Override
public Enumeration<String> engineAliases() {
try {
return getKeyStore().aliases();
} catch (KeyStoreException e) {
throw new RuntimeException(e);
}
}

@Override
public void engineLoad(InputStream stream, char[] password) {
if (password != null) {
builder.storepass(new String(password));
}
}
}
}
Loading

0 comments on commit 595a01c

Please sign in to comment.