From 94fcfd556a1f858a7b0bbb3a1c9b6d39d7230613 Mon Sep 17 00:00:00 2001 From: Emmanuel Bourg Date: Fri, 5 Jan 2024 13:20:41 +0100 Subject: [PATCH] Fixed JsignJcaProvider to support the PKCS11 based keystores (#192) --- .../net/jsign/jca/AbstractSignatureSpi.java | 61 +++++++++++++ .../java/net/jsign/jca/JsignJcaProvider.java | 85 ++++++++++++++++++- .../jsign/jca/SigningServiceSignature.java | 33 +------ .../src/test/java/net/jsign/YubikeyTest.java | 2 +- .../net/jsign/jca/JsignJcaProviderTest.java | 23 ++++- 5 files changed, 169 insertions(+), 35 deletions(-) create mode 100644 jsign-core/src/main/java/net/jsign/jca/AbstractSignatureSpi.java diff --git a/jsign-core/src/main/java/net/jsign/jca/AbstractSignatureSpi.java b/jsign-core/src/main/java/net/jsign/jca/AbstractSignatureSpi.java new file mode 100644 index 00000000..9e6f884d --- /dev/null +++ b/jsign-core/src/main/java/net/jsign/jca/AbstractSignatureSpi.java @@ -0,0 +1,61 @@ +/** + * 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.security.InvalidParameterException; +import java.security.PublicKey; +import java.security.SignatureException; +import java.security.SignatureSpi; + +/** + * Base class for JCA signature implementations. + * + * @since 5.1 + */ +public abstract class AbstractSignatureSpi extends SignatureSpi { + + protected final String signingAlgorithm; + + public AbstractSignatureSpi(String signingAlgorithm) { + this.signingAlgorithm = signingAlgorithm; + } + + @Override + protected void engineInitVerify(PublicKey publicKey) { + throw new UnsupportedOperationException(); + } + + @Override + protected boolean engineVerify(byte[] sigBytes) { + throw new UnsupportedOperationException(); + } + + @Override + protected void engineUpdate(byte b) throws SignatureException { + engineUpdate(new byte[]{b}, 0, 1); + } + + @Override + protected void engineSetParameter(String param, Object value) throws InvalidParameterException { + throw new UnsupportedOperationException(); + } + + @Override + protected Object engineGetParameter(String param) throws InvalidParameterException { + throw new UnsupportedOperationException(); + } +} diff --git a/jsign-core/src/main/java/net/jsign/jca/JsignJcaProvider.java b/jsign-core/src/main/java/net/jsign/jca/JsignJcaProvider.java index f3861b21..5a334ba7 100644 --- a/jsign-core/src/main/java/net/jsign/jca/JsignJcaProvider.java +++ b/jsign-core/src/main/java/net/jsign/jca/JsignJcaProvider.java @@ -18,13 +18,17 @@ import java.io.InputStream; import java.security.AccessController; +import java.security.InvalidKeyException; import java.security.InvalidParameterException; import java.security.Key; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; import java.security.PrivilegedAction; import java.security.Provider; +import java.security.Signature; +import java.security.SignatureException; import java.security.UnrecoverableKeyException; import java.security.cert.Certificate; import java.util.Enumeration; @@ -45,6 +49,13 @@ * provider.configure(vaultname) * KeyStore keystore = KeyStore.getInstance(AZUREKEYVAULT.name(), provider); * keystore.load(null, accessToken); + * + * PrivateKey key = (PrivateKey) keystore.getKey(alias, null); + * + * Signature signature = Signature.getInstance("SHA256withRSA", provider); + * signature.initSign(key); + * signature.update("Lorem ipsum dolor sit amet".getBytes()); + * signature.sign(); * * * @since 5.1 @@ -64,7 +75,7 @@ public JsignJcaProvider() { 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))); + putService(new ProviderService(this, "Signature", algorithm, JsignJcaSignature.class.getName(), () -> new JsignJcaSignature(algorithm))); } } } @@ -108,7 +119,7 @@ public Key engineGetKey(String alias, char[] password) throws UnrecoverableKeyEx builder.keypass(new String(password)); } try { - return getKeyStore().getKey(alias, password); + return new JsignJcaPrivateKey((PrivateKey) getKeyStore().getKey(alias, password), builder.provider()); } catch (UnrecoverableKeyException e) { e.printStackTrace(); // because jarsigner swallows the root cause and hides what's going on throw e; @@ -142,4 +153,74 @@ public void engineLoad(InputStream stream, char[] password) { } } } + + static class JsignJcaPrivateKey implements PrivateKey { + + private final PrivateKey privateKey; + private final Provider provider; + + public JsignJcaPrivateKey(PrivateKey key, Provider provider) { + this.privateKey = key; + this.provider = provider; + } + + @Override + public String getAlgorithm() { + return privateKey.getAlgorithm(); + } + + @Override + public String getFormat() { + return privateKey.getFormat(); + } + + @Override + public byte[] getEncoded() { + return privateKey.getEncoded(); + } + + public PrivateKey getPrivateKey() { + return privateKey; + } + + public Provider getProvider() { + return provider; + } + } + + static class JsignJcaSignature extends AbstractSignatureSpi { + + private Signature signature; + + public JsignJcaSignature(String signingAlgorithm) { + super(signingAlgorithm); + } + + @Override + protected void engineInitSign(PrivateKey privateKey) throws InvalidKeyException { + JsignJcaPrivateKey key = (JsignJcaPrivateKey) privateKey; + + try { + signature = Signature.getInstance(signingAlgorithm, key.getProvider()); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + signature.initSign(key.getPrivateKey()); + } + + @Override + protected void engineUpdate(byte b) throws SignatureException { + signature.update(b); + } + + @Override + protected void engineUpdate(byte[] b, int off, int len) throws SignatureException { + signature.update(b, off, len); + } + + @Override + protected byte[] engineSign() throws SignatureException { + return signature.sign(); + } + } } diff --git a/jsign-core/src/main/java/net/jsign/jca/SigningServiceSignature.java b/jsign-core/src/main/java/net/jsign/jca/SigningServiceSignature.java index dae0207f..9664b4bb 100644 --- a/jsign-core/src/main/java/net/jsign/jca/SigningServiceSignature.java +++ b/jsign-core/src/main/java/net/jsign/jca/SigningServiceSignature.java @@ -17,25 +17,16 @@ package net.jsign.jca; import java.security.GeneralSecurityException; -import java.security.InvalidParameterException; import java.security.PrivateKey; -import java.security.PublicKey; import java.security.SignatureException; -import java.security.SignatureSpi; -class SigningServiceSignature extends SignatureSpi { +class SigningServiceSignature extends AbstractSignatureSpi { - private final String signingAlgorithm; private SigningServicePrivateKey privateKey; private byte[] data; public SigningServiceSignature(String signingAlgorithm) { - this.signingAlgorithm = signingAlgorithm; - } - - @Override - protected void engineInitVerify(PublicKey publicKey) { - throw new UnsupportedOperationException(); + super(signingAlgorithm); } @Override @@ -43,11 +34,6 @@ protected void engineInitSign(PrivateKey privateKey) { this.privateKey = (SigningServicePrivateKey) privateKey; } - @Override - protected void engineUpdate(byte b) { - throw new UnsupportedOperationException(); - } - @Override protected void engineUpdate(byte[] b, int off, int len) { data = new byte[len]; @@ -62,19 +48,4 @@ protected byte[] engineSign() throws SignatureException { throw new SignatureException(e); } } - - @Override - protected boolean engineVerify(byte[] sigBytes) { - throw new UnsupportedOperationException(); - } - - @Override - protected void engineSetParameter(String param, Object value) throws InvalidParameterException { - throw new UnsupportedOperationException(); - } - - @Override - protected Object engineGetParameter(String param) throws InvalidParameterException { - throw new UnsupportedOperationException(); - } } diff --git a/jsign-core/src/test/java/net/jsign/YubikeyTest.java b/jsign-core/src/test/java/net/jsign/YubikeyTest.java index d51a4b6d..1d217852 100644 --- a/jsign-core/src/test/java/net/jsign/YubikeyTest.java +++ b/jsign-core/src/test/java/net/jsign/YubikeyTest.java @@ -26,7 +26,7 @@ public class YubikeyTest { - private void assumeYubikey() { + public static void assumeYubikey() { Assume.assumeTrue("libykcs11 isn't installed", new File(System.getenv("ProgramFiles") + "/Yubico/Yubico PIV Tool/bin/libykcs11.dll").exists() || new File("/usr/lib/x86_64-linux-gnu/libykcs11.so").exists()); diff --git a/jsign-core/src/test/java/net/jsign/jca/JsignJcaProviderTest.java b/jsign-core/src/test/java/net/jsign/jca/JsignJcaProviderTest.java index 6ddf662b..148b7175 100644 --- a/jsign-core/src/test/java/net/jsign/jca/JsignJcaProviderTest.java +++ b/jsign-core/src/test/java/net/jsign/jca/JsignJcaProviderTest.java @@ -24,6 +24,7 @@ import net.jsign.DigestAlgorithm; import net.jsign.KeyStoreType; +import net.jsign.YubikeyTest; import static org.junit.Assert.*; @@ -48,7 +49,7 @@ public void testServices() { } @Test - public void testKeyStore() throws Exception { + public void testKeyStoreSigningService() throws Exception { JsignJcaProvider provider = new JsignJcaProvider("https://cs-try.ssl.com"); KeyStore keystore = KeyStore.getInstance("ESIGNER", provider); @@ -64,4 +65,24 @@ public void testKeyStore() throws Exception { assertNotNull("Signature", signature.sign()); } + + @Test + public void testKeyStorePKCS11() throws Exception { + YubikeyTest.assumeYubikey(); + + JsignJcaProvider provider = new JsignJcaProvider(); + + KeyStore keystore = KeyStore.getInstance("YUBIKEY", provider); + keystore.load(null, "123456".toCharArray()); + String alias = keystore.aliases().nextElement(); + + PrivateKey key = (PrivateKey) keystore.getKey(alias, null); + assertNotNull("key not found", key); + + Signature signature = Signature.getInstance("SHA256withRSA", provider); + signature.initSign(key); + signature.update("Lorem ipsum dolor sit amet".getBytes()); + + assertNotNull("Signature", signature.sign()); + } }