Skip to content

Commit 595a01c

Browse files
committed
New JCA security provider to be used with other signing tools
1 parent 9b42a52 commit 595a01c

File tree

7 files changed

+342
-76
lines changed

7 files changed

+342
-76
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ Jsign is free to use and licensed under the [Apache License version 2.0](https:/
3535
* Build tools integration (Maven, Gradle, Ant)
3636
* Command line signing tool
3737
* Authenticode signing API ([Javadoc](https://javadoc.io/doc/net.jsign/jsign-core))
38+
* JCA security provider to use the keystores supported by Jsign with other tools such as jarsigner
3839

3940
See https://ebourg.github.io/jsign for more information.
4041

@@ -50,6 +51,7 @@ See https://ebourg.github.io/jsign for more information.
5051
* Only one call to the Google Cloud API is performed when the version of the key is specified in the alias parameter
5152
* JVM arguments can now be passed using the `JSIGN_OPTS` environment variable
5253
* API changes:
54+
* New `net.jsign.jca.JsignJcaProvider` JCA security provider to be used with other signing tools such as jarsigner
5355
* The signature can be removed by setting a null signature on the `Signable` object
5456
* `Signable.computeDigest(MessageDigest)` has been replaced by `Signable.computeDigest(DigestAlgorithm)`
5557
* The value of the `http.agent` system property is now appended to the user agent string set when calling REST services

docs/index.html

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ <h3>Features</h3>
7373
<li>Build tools integration (<a href="#maven">Maven</a>, <a href="#gradle">Gradle</a>, <a href="#ant">Ant</a>)</li>
7474
<li>Command line signing tool</li>
7575
<li>Authenticode signing API (<a href="https://javadoc.io/doc/net.jsign/jsign-core">Javadoc</a>)</li>
76+
<li>JCA security provider to use the keystores supported by Jsign with other tools such as jarsigner</li>
7677
</ul>
7778

7879

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

688689

690+
<h3>JCA security provider</h3>
691+
692+
<p>Jsign implements a JCA security provider that can be used to sign JAR files with the <code>jarsigner</code> tool.</p>
693+
694+
<p>It requires Java 11 or later, and the syntax looks like this:</p>
695+
696+
<pre>
697+
jarsigner -J-cp -Jjsign-5.1.jar -J--add-modules -Jjava.sql \
698+
-providerClass net.jsign.jca.JsignJcaProvider \
699+
-providerArg &lt;keystore&gt; \
700+
-keystore NONE \
701+
-storetype &lt;storetype&gt; \
702+
-storepass &lt;storepass&gt; \
703+
-keypass &lt;keypass&gt; \
704+
-certchain &lt;certfile&gt; \
705+
application.jar &lt;alias&gt;
706+
</pre>
707+
708+
<p>The <code>keystore</code> parameter must be set to <code>NONE</code>, the actual value of the keystore is specified
709+
with the <code>providerArg</code> parameter instead.</p>
710+
711+
689712
<h3 id="files">Downloads</h3>
690713

691714
<ul>
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/**
2+
* Copyright 2023 Emmanuel Bourg
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package net.jsign.jca;
18+
19+
import java.io.InputStream;
20+
import java.io.OutputStream;
21+
import java.security.Key;
22+
import java.security.KeyStoreSpi;
23+
import java.security.cert.Certificate;
24+
import java.util.Collections;
25+
import java.util.Date;
26+
import java.util.Enumeration;
27+
28+
/**
29+
* Base class for JCA keystore implementations.
30+
*
31+
* @since 5.1
32+
*/
33+
abstract class AbstractKeyStoreSpi extends KeyStoreSpi {
34+
35+
@Override
36+
public Certificate engineGetCertificate(String alias) {
37+
Certificate[] chain = engineGetCertificateChain(alias);
38+
return chain != null && chain.length > 0 ? chain[0] : null;
39+
}
40+
41+
@Override
42+
public Date engineGetCreationDate(String alias) {
43+
throw new UnsupportedOperationException();
44+
}
45+
46+
@Override
47+
public void engineSetKeyEntry(String alias, Key key, char[] password, Certificate[] chain) {
48+
throw new UnsupportedOperationException();
49+
}
50+
51+
@Override
52+
public void engineSetKeyEntry(String alias, byte[] key, Certificate[] chain) {
53+
throw new UnsupportedOperationException();
54+
}
55+
56+
@Override
57+
public void engineSetCertificateEntry(String alias, Certificate cert) {
58+
throw new UnsupportedOperationException();
59+
}
60+
61+
@Override
62+
public void engineDeleteEntry(String alias) {
63+
throw new UnsupportedOperationException();
64+
}
65+
66+
@Override
67+
public boolean engineContainsAlias(String alias) {
68+
Enumeration<String> aliases = engineAliases();
69+
while (aliases.hasMoreElements()) {
70+
if (aliases.nextElement().equals(alias)) {
71+
return true;
72+
}
73+
}
74+
return false;
75+
}
76+
77+
@Override
78+
public int engineSize() {
79+
return Collections.list(engineAliases()).size();
80+
}
81+
82+
@Override
83+
public boolean engineIsKeyEntry(String alias) {
84+
throw new UnsupportedOperationException();
85+
}
86+
87+
@Override
88+
public boolean engineIsCertificateEntry(String alias) {
89+
throw new UnsupportedOperationException();
90+
}
91+
92+
@Override
93+
public String engineGetCertificateAlias(Certificate cert) {
94+
throw new UnsupportedOperationException();
95+
}
96+
97+
@Override
98+
public void engineStore(OutputStream stream, char[] password) {
99+
throw new UnsupportedOperationException();
100+
}
101+
102+
@Override
103+
public void engineLoad(InputStream stream, char[] password) {
104+
}
105+
}
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
/**
2+
* Copyright 2023 Emmanuel Bourg
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package net.jsign.jca;
18+
19+
import java.io.InputStream;
20+
import java.security.AccessController;
21+
import java.security.InvalidParameterException;
22+
import java.security.Key;
23+
import java.security.KeyStore;
24+
import java.security.KeyStoreException;
25+
import java.security.NoSuchAlgorithmException;
26+
import java.security.PrivilegedAction;
27+
import java.security.Provider;
28+
import java.security.UnrecoverableKeyException;
29+
import java.security.cert.Certificate;
30+
import java.util.Enumeration;
31+
32+
import net.jsign.DigestAlgorithm;
33+
import net.jsign.KeyStoreBuilder;
34+
import net.jsign.KeyStoreType;
35+
36+
/**
37+
* JCA provider using a Jsign keystore and compatible with jarsigner.
38+
*
39+
* <p>The provider must be configured with the keystore parameter (the value depends on the keystore type).
40+
* The type of the keystore is one of the names from the {@link KeyStoreType} enum.</p>
41+
*
42+
* <p>Example:</p>
43+
* <pre>
44+
* Provider provider = new JsignJcaProvider();
45+
* provider.configure(vaultname)
46+
* KeyStore keystore = KeyStore.getInstance(AZUREKEYVAULT.name(), provider);
47+
* keystore.load(null, accessToken);
48+
* </pre>
49+
*
50+
* @since 5.1
51+
*/
52+
public class JsignJcaProvider extends Provider {
53+
54+
private String keystore;
55+
56+
public JsignJcaProvider() {
57+
super("Jsign", 1.0, "Jsign security provider");
58+
59+
AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
60+
for (KeyStoreType type : KeyStoreType.values()) {
61+
putService(new ProviderService(this, "KeyStore", type.name(), JsignJcaKeyStore.class.getName(), () -> new JsignJcaKeyStore(type, keystore)));
62+
}
63+
for (String alg : new String[]{"RSA", "ECDSA"}) {
64+
for (DigestAlgorithm digest : DigestAlgorithm.values()) {
65+
if (digest != DigestAlgorithm.MD5) {
66+
String algorithm = digest.name() + "with" + alg;
67+
putService(new ProviderService(this, "Signature", algorithm, SigningServiceSignature.class.getName(), () -> new SigningServiceSignature(algorithm)));
68+
}
69+
}
70+
}
71+
return null;
72+
});
73+
}
74+
75+
public Provider configure(String configArg) throws InvalidParameterException {
76+
this.keystore = configArg;
77+
78+
return this;
79+
}
80+
81+
static class JsignJcaKeyStore extends AbstractKeyStoreSpi {
82+
83+
private KeyStoreBuilder builder = new KeyStoreBuilder();
84+
private KeyStore keystore;
85+
86+
public JsignJcaKeyStore(KeyStoreType type, String keystore) {
87+
builder.storetype(type);
88+
builder.keystore(keystore);
89+
builder.certfile("");
90+
}
91+
92+
private KeyStore getKeyStore() throws KeyStoreException {
93+
if (keystore == null) {
94+
keystore = builder.build();
95+
}
96+
97+
return keystore;
98+
}
99+
100+
@Override
101+
public Key engineGetKey(String alias, char[] password) throws UnrecoverableKeyException {
102+
if (password != null) {
103+
builder.keypass(new String(password));
104+
}
105+
try {
106+
return getKeyStore().getKey(alias, password);
107+
} catch (UnrecoverableKeyException e) {
108+
e.printStackTrace(); // because jarsigner swallows the root cause and hides what's going on
109+
throw e;
110+
} catch (KeyStoreException | NoSuchAlgorithmException e) {
111+
throw new RuntimeException(e);
112+
}
113+
}
114+
115+
@Override
116+
public Certificate[] engineGetCertificateChain(String alias) {
117+
try {
118+
return getKeyStore().getCertificateChain(alias);
119+
} catch (KeyStoreException e) {
120+
return null;
121+
}
122+
}
123+
124+
@Override
125+
public Enumeration<String> engineAliases() {
126+
try {
127+
return getKeyStore().aliases();
128+
} catch (KeyStoreException e) {
129+
throw new RuntimeException(e);
130+
}
131+
}
132+
133+
@Override
134+
public void engineLoad(InputStream stream, char[] password) {
135+
if (password != null) {
136+
builder.storepass(new String(password));
137+
}
138+
}
139+
}
140+
}

0 commit comments

Comments
 (0)