diff --git a/docs/index.html b/docs/index.html
index 9d7244cd..58d30470 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -234,6 +234,7 @@
Attributes
SIGNPATH
: SignPath
SIGNSERVER
: Keyfactor SignServer
TRUSTEDSIGNING
: Azure Trusted Signing
+ VENAFI
: Venafi CodeSign Protect
No, automatically detected for file based keystores. |
@@ -558,6 +559,7 @@ Command Line Tool
- SIGNPATH: SignPath
- SIGNSERVER: Keyfactor SignServer
- TRUSTEDSIGNING: Azure Trusted Signing
+ - VENAFI: Venafi CodeSign Protect
-a,--alias <NAME> The alias of the certificate used for signing in the keystore
--keypass <PASSWORD> The password of the private key. When using a keystore,
this parameter can be omitted if the keystore shares the
@@ -998,6 +1000,22 @@ Signing with SignPath
application.exe
+Signing with Venafi CodeSign Protect
+
+Signing with Venafi CodeSign Protect requires version 23.1 or later
+and requires a minimum scope of codesignclient
as part of the default VenafiCodeSignClient
API integration within the Trust Protection Platform.
+The keystore
parameter references the URL of the Venafi CodeSign Protect server. The storepass
parameter represents
+the Key User credential assigned to the CodeSign Protect project. The alias
parameter is the certificate label which can be obtained from
+the pkcs11config list
command.
+
+
+ jsign --storetype VENAFI \
+ --keystore https://example.tpp.local \
+ --storepass "<username>|<password>" \
+ --alias my-certificate-label \
+ application.exe
+
+
diff --git a/jsign-cli/src/main/java/net/jsign/JsignCLI.java b/jsign-cli/src/main/java/net/jsign/JsignCLI.java
index fa7ec1a8..ebeeca61 100644
--- a/jsign-cli/src/main/java/net/jsign/JsignCLI.java
+++ b/jsign-cli/src/main/java/net/jsign/JsignCLI.java
@@ -99,7 +99,8 @@ private Map getOptions() {
+ "- ORACLECLOUD: Oracle Cloud Key Management Service\n"
+ "- SIGNPATH: SignPath\n"
+ "- SIGNSERVER: Keyfactor SignServer\n"
- + "- TRUSTEDSIGNING: Azure Trusted Signing\n").build());
+ + "- TRUSTEDSIGNING: Azure Trusted Signing\n"
+ + "- VENAFI: Venafi CodeSign Protect\n").build());
options.addOption(Option.builder("a").hasArg().longOpt(PARAM_ALIAS).argName("NAME").desc("The alias of the certificate used for signing in the keystore").build());
options.addOption(Option.builder().hasArg().longOpt(PARAM_KEYPASS).argName("PASSWORD").desc("The password of the private key. When using a keystore, this parameter can be omitted if the keystore shares the same password").build());
options.addOption(Option.builder().hasArg().longOpt(PARAM_KEYFILE).argName("FILE").desc("The file containing the private key. PEM and PVK files are supported").type(File.class).build());
diff --git a/jsign-crypto/src/main/java/net/jsign/KeyStoreType.java b/jsign-crypto/src/main/java/net/jsign/KeyStoreType.java
index 5a0cf7ac..2c821e96 100644
--- a/jsign-crypto/src/main/java/net/jsign/KeyStoreType.java
+++ b/jsign-crypto/src/main/java/net/jsign/KeyStoreType.java
@@ -52,6 +52,8 @@
import net.jsign.jca.SignServerCredentials;
import net.jsign.jca.SignServerSigningService;
import net.jsign.jca.SigningServiceJcaProvider;
+import net.jsign.jca.VenafiSigningService;
+import net.jsign.jca.VenafiCredentials;
/**
* Type of a keystore.
@@ -561,6 +563,36 @@ Provider getProvider(KeyStoreBuilder params) {
}
},
+
+ VENAFI(false, false) {
+ @Override
+ void validate(KeyStoreBuilder params) {
+ if (params.storepass() == null || params.storepass().split("\\|").length > 2) {
+ throw new IllegalArgumentException("storepass " + params.parameterName() + " must specify the Venafi CodeSign Protect username/password: |");
+ }
+ }
+
+ @Override
+ Provider getProvider(KeyStoreBuilder params) {
+ String[] elements = params.storepass().split("\\|");
+ String username = null;
+ String password = null;
+ if (elements.length == 2) {
+ username = elements[0];
+ password = elements[1];
+ }
+
+ try {
+ VenafiCredentials credentials = new VenafiCredentials(username, password, null, params.keypass());
+ return new SigningServiceJcaProvider(new VenafiSigningService(params.keystore(), credentials));
+ } catch (IOException e) {
+ throw new IllegalStateException("Authentication failed with Venafi", e);
+
+ }
+ }
+ },
+
+
/**
* Keyfactor SignServer. This keystore requires a Plain Signer worker, preferably configured to allow client-side
* hashing (with the properties CLIENTSIDEHASHING
or ALLOW_CLIENTSIDEHASHING_OVERRIDE
set
diff --git a/jsign-crypto/src/main/java/net/jsign/jca/VenafiCredentials.java b/jsign-crypto/src/main/java/net/jsign/jca/VenafiCredentials.java
new file mode 100644
index 00000000..a65c0c06
--- /dev/null
+++ b/jsign-crypto/src/main/java/net/jsign/jca/VenafiCredentials.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2025 Ivan Wallis
+ *
+ * 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.IOException;
+import java.security.GeneralSecurityException;
+import java.security.KeyStore;
+import java.security.SecureRandom;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+
+import net.jsign.KeyStoreBuilder;
+
+/**
+ * Credentials for the Venafi CodeSign Protect.
+ *
+ * @since 7.2
+ */
+public class VenafiCredentials {
+
+ public String username;
+ public String password;
+ public KeyStore.Builder keystore;
+ public String sessionToken;
+
+ public VenafiCredentials(String username, String password, String keystore, String storepass) {
+ this(username, password, new KeyStoreBuilder().keystore(keystore).storepass(storepass).builder());
+ }
+
+ public VenafiCredentials(String username, String password, KeyStore.Builder keystore) {
+ this.username = username;
+ this.password = password;
+ this.keystore = keystore;
+ }
+
+ public String getSessionToken(String endpoint) throws IOException {
+ if (sessionToken == null) {
+ RESTClient client = new RESTClient(endpoint)
+ .errorHandler(response -> response.get("error") + ": " + response.get("error_description"));
+
+ Map request = new LinkedHashMap<>();
+ request.put("client_id", "VenafiCodeSignClient");
+ request.put("scope", "codesignclient");
+ if (username != null && password != null) {
+ request.put("username", username);
+ request.put("password", password);
+ }
+
+ Map response = client.post("/vedauth/authorize/oauth", JsonWriter.format(request));
+ sessionToken = (String) response.get("access_token");
+ }
+
+ return sessionToken;
+ }
+}
diff --git a/jsign-crypto/src/main/java/net/jsign/jca/VenafiSigningService.java b/jsign-crypto/src/main/java/net/jsign/jca/VenafiSigningService.java
new file mode 100644
index 00000000..c36258fc
--- /dev/null
+++ b/jsign-crypto/src/main/java/net/jsign/jca/VenafiSigningService.java
@@ -0,0 +1,251 @@
+/**
+ * Copyright 2025 Ivan Wallis
+ *
+ * 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.ByteArrayInputStream;
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.Key;
+import java.security.KeyStoreException;
+import java.security.UnrecoverableKeyException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.LinkedHashMap;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.io.ByteArrayOutputStream;
+import java.math.BigInteger;
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.DLSequence;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1OutputStream;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.x509.DigestInfo;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+
+
+
+
+import net.jsign.DigestAlgorithm;
+
+/**
+ * Signing service using the Venafi CodeSign Protect REST Service API.
+ *
+ * @since 7.2
+ * @see Venafi CodeSign Protect REST API
+ */
+public class VenafiSigningService implements SigningService {
+
+ /** Cache of certificates indexed by alias */
+ private final Map> certificates = new LinkedHashMap<>();
+
+ /** The API endpoint of the Venafi CodeSign Protect */
+ private final String endpoint;
+
+ private final RESTClient client;
+
+ /** The credentials to authenticate with the service */
+ private final VenafiCredentials credentials;
+
+ /** The name of the Venafi CodeSign Protect certificate KeyId */
+ private String KeyId;
+
+ /* Add ASN.1 DER prefix to MessageDigest */
+
+ public static byte[] addASN1Prefix(DigestAlgorithm digestAlgorithm, byte[] hash) throws IOException {
+ return new DigestInfo(new AlgorithmIdentifier(digestAlgorithm.oid, DERNull.INSTANCE), hash).getEncoded("DER");
+ }
+
+ /**
+ * Creates a new Venafi CodeSign Protect service.
+ *
+ * @param endpoint the Venafi API endpoint (for example https://demo.venafitpp.local/vedhsm/api/sign/)
+ * @param credentials the Venafi credentials
+ */
+ public VenafiSigningService(String endpoint, VenafiCredentials credentials) throws IOException {
+ if (!endpoint.startsWith("http")) {
+ endpoint = "https://" + endpoint;
+ }
+ this.endpoint = endpoint;
+ this.credentials = credentials;
+ String token = credentials.getSessionToken(endpoint);
+ this.client = new RESTClient(endpoint)
+ .authentication(conn -> conn.setRequestProperty("Authorization", "Bearer " + token ))
+ .errorHandler(response -> response.get("error") + ": " + response.get("error_description"));
+
+ }
+
+ @Override
+ public String getName() {
+ return "Venafi";
+ }
+
+ private void loadKeyStore(String alias) throws KeyStoreException {
+ if (certificates.isEmpty()) {
+ try {
+ Map request = new LinkedHashMap<>();
+ request.put("EnvironmentFilter", new Integer[] { 0});
+ request.put("ObjectTypeFilter", new Integer[] { 1});
+ request.put("IncludeChains", true);
+ request.put("LabelFilter", new String[] { alias });
+
+ Map response = client.post("/vedhsm/api/getobjects", JsonWriter.format(request));
+
+ Object[] keys = (Object[]) response.get("Certificates");
+ for (Object key : keys) {
+ String name = (String) ((Map) key).get("Label");
+ KeyId = (String) ((Map) key).get("KeyId");
+ certificates.put(name, (Map) key);
+ }
+ } catch (IOException e) {
+ throw new KeyStoreException("Unable to retrieve the Venafi keystore with alias: " + alias, e);
+ }
+ }
+ }
+
+ @Override
+ public List aliases() throws KeyStoreException {
+ loadKeyStore("");
+ return new ArrayList<>(certificates.keySet());
+ }
+
+ @Override
+ public Certificate[] getCertificateChain(String alias) throws KeyStoreException {
+
+ try {
+ loadKeyStore(alias);
+
+ Map key = certificates.get(alias);
+ if (key == null) {
+ throw new KeyStoreException("Unable to retrieve Venafi certificate '" + alias + "'. Verify that the Project/Environment is a valid Certificate environment type.");
+ }
+
+ String pem = (String) key.get("Value");
+ Certificate certificate = CertificateFactory.getInstance("X.509").generateCertificate(new ByteArrayInputStream(Base64.getDecoder().decode(pem)));
+
+ return new Certificate[]{certificate};
+ } catch (CertificateException e) {
+ throw new KeyStoreException("Unable to retrieve Venafi certificate '" + alias + "'. Verify that the Project/Environment is a valid Certificate environment type.", e);
+ }
+
+ }
+
+ @Override
+ public SigningServicePrivateKey getPrivateKey(String alias, char[] password) throws UnrecoverableKeyException {
+ try {
+ Certificate[] chain = getCertificateChain(alias);
+ String algorithm = chain[0].getPublicKey().getAlgorithm();
+ return new SigningServicePrivateKey(alias, algorithm, this);
+ } catch (KeyStoreException e) {
+ throw (UnrecoverableKeyException) new UnrecoverableKeyException().initCause(e);
+ }
+
+ }
+
+ /* This function encodes the ECDSA signature response from Venafi CodeSign Protect /vedhsm/api/sign REST API */
+ public byte[] encodeASN1Signature(byte[] sigBytes) throws IOException {
+
+ // Split the sigbytes into r and s components
+ BigInteger r = new BigInteger(1, Arrays.copyOfRange(sigBytes, 0, sigBytes.length / 2));
+ BigInteger s = new BigInteger(1, Arrays.copyOfRange(sigBytes, sigBytes.length / 2, sigBytes.length));
+
+ // Create an ASN1 sequence containing r and s
+ DLSequence components = new DLSequence(new ASN1Encodable[] {
+ new ASN1Integer(r),
+ new ASN1Integer(s)
+ });
+
+ // Marshal the components to ASN1 encoding
+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ ASN1OutputStream asn1OutputStream = ASN1OutputStream.create(byteArrayOutputStream);
+ asn1OutputStream.writeObject(components);
+ asn1OutputStream.close();
+
+ return byteArrayOutputStream.toByteArray();
+ }
+
+ @Override
+ public byte[] sign(SigningServicePrivateKey privateKey, String algorithm, byte[] data) throws GeneralSecurityException {
+ try {
+ DigestAlgorithm digestAlgorithm = DigestAlgorithm.of(algorithm.substring(0, algorithm.toLowerCase().indexOf("with")));
+ data = digestAlgorithm.getMessageDigest().digest(data);
+
+ byte[] arr_combined = addASN1Prefix(digestAlgorithm, data);
+
+ Map request = new HashMap<>();
+
+ Map clientInfo = new HashMap<>();
+ clientInfo.put("ClientLibraryName", "jsign");
+
+ Map processInfo = new HashMap<>();
+ clientInfo.put("Executable", "jsign");
+
+ request.put("ClientInfo", clientInfo);
+ request.put("ProcessInfo", processInfo);
+ request.put("KeyId", KeyId);
+ request.put("Data", Base64.getEncoder().encodeToString(arr_combined));
+
+ switch (privateKey.getAlgorithm()) {
+ case "RSA":
+ request.put("Mechanism", 1); // RSA
+ break;
+ case "EC":
+ request.put("Mechanism", 4161); // ECDSA
+ break;
+ default:
+ request.put("Mechanism", 1); // RSA
+ }
+
+ Map response = client.post("/vedhsm/api/sign", JsonWriter.format(request));
+ String status = (String) response.get("Error");
+ if (status != null) {
+ throw new IOException("Signing operation failed: " + response.get("Error"));
+ }
+ String signature = (String) response.get("ResultData");
+
+ if ("EC".equals(privateKey.getAlgorithm())) {
+ return encodeASN1Signature(Base64.getDecoder().decode(signature));
+ } else {
+ return Base64.getDecoder().decode(signature);
+ }
+
+ } catch (IOException e) {
+ throw new GeneralSecurityException(e);
+ }
+ }
+
+ private byte[] decode(Object[] array) {
+ byte[] data = new byte[array.length];
+ for (int i = 0; i < array.length; i++) {
+ data[i] = ((Number) array[i]).byteValue();
+ }
+ return data;
+ }
+}
diff --git a/jsign-crypto/src/test/java/net/jsign/KeyStoreBuilderTest.java b/jsign-crypto/src/test/java/net/jsign/KeyStoreBuilderTest.java
index 65be95ce..a23c973c 100644
--- a/jsign-crypto/src/test/java/net/jsign/KeyStoreBuilderTest.java
+++ b/jsign-crypto/src/test/java/net/jsign/KeyStoreBuilderTest.java
@@ -305,6 +305,19 @@ public void testBuildGaraSign() throws Exception {
assertNotNull("keystore", keystore);
}
+ @Test
+ public void testBuildVenafi() throws Exception {
+ KeyStoreBuilder builder = new KeyStoreBuilder().storetype(VENAFI).keystore("https://tpp.example.local");
+
+ Exception e = assertThrows(IllegalArgumentException.class, builder::build);
+ assertEquals("message", "storepass parameter must specify the Venafi username/password", e.getMessage());
+
+ builder.storepass("username|password");
+
+ KeyStore keystore = builder.build();
+ assertNotNull("keystore", keystore);
+ }
+
@Test
public void testBuildSignPath() throws Exception {
KeyStoreBuilder builder = new KeyStoreBuilder().storetype(SIGNPATH);
diff --git a/jsign-crypto/src/test/java/net/jsign/jca/SigningServiceTest.java b/jsign-crypto/src/test/java/net/jsign/jca/SigningServiceTest.java
index 531446fc..ecbecbeb 100644
--- a/jsign-crypto/src/test/java/net/jsign/jca/SigningServiceTest.java
+++ b/jsign-crypto/src/test/java/net/jsign/jca/SigningServiceTest.java
@@ -229,6 +229,17 @@ public void testGaraSignProvider() throws Exception {
assertEquals("message", "Failed to authenticate with GaraSign: Error authenticating user", e.getCause().getMessage());
}
+ @Test
+ public void testVenafiProvider() throws Exception {
+ VenafiCredentials credentials = new VenafiCredentials("demo_user", "password", "target/test-classes/keystores/keystore.p12", "password");
+ Provider provider = new SigningServiceJcaProvider(new VenafiSigningService(null, credentials));
+ KeyStore keystore = KeyStore.getInstance("VENAFI", provider);
+ keystore.load(null, "".toCharArray());
+
+ Exception e = assertThrows(Exception.class, () -> testCustomProvider(provider, keystore, "windows_codesign", ""));
+ assertEquals("message", "Failed to authenticate with Venafi: Error authenticating user", e.getCause().getMessage());
+ }
+
@Test
public void testSignServerProvider() throws Exception {
SignServerCredentials credentials = new SignServerCredentials("username", "password", "target/test-classes/keystores/keystore.p12", "password");
diff --git a/jsign-crypto/src/test/java/net/jsign/jca/VenafiSigningServiceTest.java b/jsign-crypto/src/test/java/net/jsign/jca/VenafiSigningServiceTest.java
new file mode 100644
index 00000000..6fdafccd
--- /dev/null
+++ b/jsign-crypto/src/test/java/net/jsign/jca/VenafiSigningServiceTest.java
@@ -0,0 +1,198 @@
+/**
+ * Copyright 2025 Ivan Wallis
+ *
+ * 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.FileReader;
+import java.security.GeneralSecurityException;
+import java.security.KeyStoreException;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Collections;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import static net.jadler.Jadler.*;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+public class VenafiSigningServiceTest {
+
+ @Before
+ public void setUp() {
+ initJadler().withDefaultResponseStatus(404);
+ }
+
+ @After
+ public void tearDown() {
+ closeJadler();
+ }
+
+ @Test
+ public void testGetAliases() throws Exception {
+ SigningService service = new VenafiSigningService("http://localhost:" + port(), new VenafiCredentials("username", "password", null));
+ List aliases = service.aliases();
+
+ assertEquals("aliases", Collections.emptyList(), aliases);
+ }
+
+ @Test
+ public void testGetCertificateChain() throws Exception {
+ onRequest()
+ .havingMethodEqualTo("POST")
+ .havingPathEqualTo("/vedauth/authorize/oauth")
+ .respond()
+ .withStatus(200)
+ .withBody(new FileReader("target/test-classes/services/venafi-authorize.json"));
+ onRequest()
+ .havingMethodEqualTo("POST")
+ .havingPathEqualTo("/vedhsm/api/getobjects")
+ .havingHeaderEqualTo("Authorization", "Bearer da49LbfHokLO+fKwFIneJg==")
+ .respond()
+ .withStatus(200)
+ .withBody(new FileReader("target/test-classes/services/venafi-keystore.json"));
+
+ SigningService service = new VenafiSigningService("http://localhost:" + port(), new VenafiCredentials("username", "password", null));
+ Certificate[] chain = service.getCertificateChain("project-test-cert");
+ assertNotNull("null chain", chain);
+ assertEquals("length", 1, chain.length);
+ assertEquals("subject 1", "CN=test_signer, OU=test, O=Venafi, L=Salt Lake City, ST=UT, C=US", ((X509Certificate) chain[0]).getSubjectDN().getName());
+ }
+
+ @Test
+ public void testGetCertificateChainWithInvalidAlias() throws Exception {
+ onRequest()
+ .havingMethodEqualTo("POST")
+ .havingPathEqualTo("/vedauth/authorize/oauth")
+ .respond()
+ .withStatus(200)
+ .withBody(new FileReader("target/test-classes/services/venafi-authorize.json"));
+ onRequest()
+ .havingMethodEqualTo("POST")
+ .havingPathEqualTo("/vedhsm/api/getobjects")
+ .havingHeaderEqualTo("Authorization", "Bearer da49LbfHokLO+fKwFIneJg==")
+ .respond()
+ .withStatus(200)
+ .withBody(new FileReader("target/test-classes/services/venafi-keystore.json"));
+
+ SigningService service = new VenafiSigningService("http://localhost:" + port(), new VenafiCredentials("username", "password", null));
+
+ Exception e = assertThrows(KeyStoreException.class, () -> service.getCertificateChain("jsign"));
+ assertEquals("message", "Unable to retrieve Venafi certificate 'jsign'", e.getMessage());
+ }
+
+ @Test
+ public void testSignRSA() throws Exception {
+ onRequest()
+ .havingMethodEqualTo("POST")
+ .havingPathEqualTo("/vedauth/authorize/oauth")
+ .respond()
+ .withStatus(200)
+ .withBody(new FileReader("target/test-classes/services/venafi-authorize.json"));
+ onRequest()
+ .havingMethodEqualTo("POST")
+ .havingPathEqualTo("/vedhsm/api/sign")
+ .havingHeaderEqualTo("Authorization", "Bearer da49LbfHokLO+fKwFIneJg==")
+ .respond()
+ .withStatus(200)
+ .withBody(new FileReader("target/test-classes/services/venafi-sign-rsa.json"));
+
+ SigningService service = new VenafiSigningService("http://localhost:" + port(), new VenafiCredentials("username", "password", null));
+ SigningServicePrivateKey privateKey = service.getPrivateKey("project-test-rsa-cert", null);
+
+ byte[] signature = service.sign(privateKey, "SHA256withRSA", "Hello".getBytes());
+
+ assertNotNull("null signature", signature);
+ assertEquals("length", 256, signature.length);
+ }
+
+ @Test
+ public void testSignECDSA() throws Exception {
+ onRequest()
+ .havingMethodEqualTo("POST")
+ .havingPathEqualTo("/vedauth/authorize/oauth")
+ .respond()
+ .withStatus(200)
+ .withBody(new FileReader("target/test-classes/services/venafi-authorize.json"));
+ onRequest()
+ .havingMethodEqualTo("POST")
+ .havingPathEqualTo("/vedhsm/api/sign")
+ .havingHeaderEqualTo("Authorization", "Bearer da49LbfHokLO+fKwFIneJg==")
+ .respond()
+ .withStatus(200)
+ .withBody(new FileReader("target/test-classes/services/venafi-sign-ecdsa.json"));
+
+ SigningService service = new VenafiSigningService("http://localhost:" + port(), new VenafiCredentials("username", "password", null));
+ SigningServicePrivateKey privateKey = service.getPrivateKey("project-test-ecdsa-cert", null);
+
+ byte[] signature = service.sign(privateKey, "SHA256withECDSA", "Hello".getBytes());
+
+ assertNotNull("null signature", signature);
+ assertEquals("length", 256, signature.length);
+ }
+
+
+ @Test
+ public void testSignRSAWithFailure() throws Exception {
+ onRequest()
+ .havingMethodEqualTo("POST")
+ .havingPathEqualTo("/vedauth/authorize/oauth")
+ .respond()
+ .withStatus(200)
+ .withBody(new FileReader("target/test-classes/services/venafi-authorize.json"));
+ onRequest()
+ .havingMethodEqualTo("POST")
+ .havingPathEqualTo("/vedhsm/api/sign")
+ .havingHeaderEqualTo("Authorization", "Bearer da49LbfHokLO+fKwFIneJg==")
+ .respond()
+ .withStatus(200)
+ .withBody(new FileReader("target/test-classes/services/venafi-sign-rsa-error.json"));
+
+ SigningService service = new VenafiSigningService("http://localhost:" + port(), new VenafiCredentials("username", "password", null));
+ SigningServicePrivateKey privateKey = service.getPrivateKey("project-test-rsa-cert", null);
+
+ Exception e = assertThrows(GeneralSecurityException.class, () -> service.sign(privateKey, "SHA256withRSA", "Hello".getBytes()));
+ assertEquals("message", "java.io.IOException: Private Key Access: 'sign' operation failed. More info: Error signing data (Failed to sign data using engine: 'Software', error: Call to C_Sign failed [DataInvalid])", e.getMessage());
+ }
+
+ @Test
+ public void testSignECDSAWithFailure() throws Exception {
+ onRequest()
+ .havingMethodEqualTo("POST")
+ .havingPathEqualTo("/vedauth/authorize/oauth")
+ .respond()
+ .withStatus(200)
+ .withBody(new FileReader("target/test-classes/services/venafi-authorize.json"));
+ onRequest()
+ .havingMethodEqualTo("POST")
+ .havingPathEqualTo("/vedhsm/api/sign")
+ .havingHeaderEqualTo("Authorization", "Bearer da49LbfHokLO+fKwFIneJg==")
+ .respond()
+ .withStatus(200)
+ .withBody(new FileReader("target/test-classes/services/venafi-sign-ecdsa-error.json"));
+
+ SigningService service = new VenafiSigningService("http://localhost:" + port(), new VenafiCredentials("username", "password", null));
+ SigningServicePrivateKey privateKey = service.getPrivateKey("project-test-ecdsa-cert", null);
+
+ Exception e = assertThrows(GeneralSecurityException.class, () -> service.sign(privateKey, "SHA256withRSA", "Hello".getBytes()));
+ assertEquals("message", "java.io.IOException: Signing operation failed: Private Key Access: 'sign' operation failed. More info: Error signing data (Failed to sign data using engine: 'Software', error: Mechanism RSA_PKCS not supported by key 'CryptokiECPrivateKey [4294967295])", e.getMessage());
+ }
+}
diff --git a/jsign-crypto/src/test/resources/services/venafi-authorize.json b/jsign-crypto/src/test/resources/services/venafi-authorize.json
new file mode 100644
index 00000000..dd87e3d4
--- /dev/null
+++ b/jsign-crypto/src/test/resources/services/venafi-authorize.json
@@ -0,0 +1,10 @@
+{
+ "access_token": "FpBFnGsryqrIiuzewOc3aw==",
+ "refresh_token": "enLssS1xDzxZ\/M0EJMlRIQ==",
+ "expires_in": 7775999,
+ "expires": 1747751472,
+ "token_type": "Bearer",
+ "scope": "codesignclient",
+ "identity": "local:{412fb225-b109-48c9-85d6-3abf45f725dc}",
+ "refresh_until": 1771511472
+}
\ No newline at end of file
diff --git a/jsign-crypto/src/test/resources/services/venafi-keystore.json b/jsign-crypto/src/test/resources/services/venafi-keystore.json
new file mode 100644
index 00000000..d6df2d68
--- /dev/null
+++ b/jsign-crypto/src/test/resources/services/venafi-keystore.json
@@ -0,0 +1,28 @@
+{
+ "Certificates": [
+ {
+ "Authentication": false,
+ "CreatedOn": "2025-02-13T16:30:28.6214597Z",
+ "Encipherment": false,
+ "EnvironmentType": 0,
+ "Handle": 28460,
+ "Id": "dnNpZ24tc2VsZnNpZ25lZC1wMjU2",
+ "KeyContext": null,
+ "KeyId": "{9bad128b-c5df-4ff4-b412-c32ab54a9e86}",
+ "Label": "test-selfsigned-p256",
+ "ObjectType": 1,
+ "Signing": true,
+ "Token": true,
+ "CheckValue": "2Af2",
+ "EndDate": "20260213",
+ "Issuer": "MIGJMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExETAPBgNVBAcTCFNhbiBKb3NlMRowGAYDVQQLExFHbG9iYWwgQXJjaGl0ZWN0czEVMBMGA1UEChMMVmVuYWZpLCBJbmMuMScwJQYDVQQDEx5zZWxmc2lnbmVkLXAyNTYudmVuYWZpZGVtby5jb20=",
+ "StartDate": "20250213",
+ "Subject": "MIGJMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExETAPBgNVBAcTCFNhbiBKb3NlMRowGAYDVQQLExFHbG9iYWwgQXJjaGl0ZWN0czEVMBMGA1UEChMMVmVuYWZpLCBJbmMuMScwJQYDVQQDEx5zZWxmc2lnbmVkLXAyNTYudmVuYWZpZGVtby5jb20=",
+ "Trusted": true,
+ "Value": "MIICYzCCAgqgAwIBAgIRANrShCO1C61GuyDwmzVpEaUwCgYIKoZIzj0EAwIwgYkxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTERMA8GA1UEBxMIU2FuIEpvc2UxGjAYBgNVBAsTEUdsb2JhbCBBcmNoaXRlY3RzMRUwEwYDVQQKEwxWZW5hZmksIEluYy4xJzAlBgNVBAMTHnNlbGZzaWduZWQtcDI1Ni52ZW5hZmlkZW1vLmNvbTAeFw0yNTAyMTMxNjMwMjlaFw0yNjAyMTMxNjMwMjlaMIGJMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExETAPBgNVBAcTCFNhbiBKb3NlMRowGAYDVQQLExFHbG9iYWwgQXJjaGl0ZWN0czEVMBMGA1UEChMMVmVuYWZpLCBJbmMuMScwJQYDVQQDEx5zZWxmc2lnbmVkLXAyNTYudmVuYWZpZGVtby5jb20wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATRYtLccF+Yo19eypFHjo3\/zVinlHYDYBQ29dlnjFHe1Z7QCm98SjcJlSpisFv3K3zPCbgjepsuWmo\/niYnjY2No1EwTzAdBgNVHQ4EFgQU+UTc1jX5gB96yQpAf7xHmWTewCQwCQYDVR0TBAIwADAOBgNVHQ8BAf8EBAMCBoAwEwYDVR0lBAwwCgYIKwYBBQUHAwMwCgYIKoZIzj0EAwIDRwAwRAIgL+v9pndvdbaDI09C6VOQfniysQfiyaP3iTNJgcufSNUCIFx8Pt3qZZwjpc4XWYjllXzjpElARw4HM+pLmuESP9DJ"
+ }
+ ],
+ "PrivateKeys": [],
+ "PublicKeys": [],
+ "Success": true
+}
diff --git a/jsign-crypto/src/test/resources/services/venafi-sign-ecdsa-error.json b/jsign-crypto/src/test/resources/services/venafi-sign-ecdsa-error.json
new file mode 100644
index 00000000..7fc04294
--- /dev/null
+++ b/jsign-crypto/src/test/resources/services/venafi-sign-ecdsa-error.json
@@ -0,0 +1,4 @@
+{
+ "Error": "Private Key Access: 'sign' operation failed. More info: Error signing data (Failed to sign data using engine: 'Software', error: Mechanism RSA_PKCS not supported by key 'CryptokiECPrivateKey [4294967295])",
+ "RequestThumbprint": "3F8B611F3B2FD02264368E3E52E1473B7B6EF2EDAADD58A8821476136C3319E7"
+}
diff --git a/jsign-crypto/src/test/resources/services/venafi-sign-ecdsa.json b/jsign-crypto/src/test/resources/services/venafi-sign-ecdsa.json
new file mode 100644
index 00000000..ab2dce7d
--- /dev/null
+++ b/jsign-crypto/src/test/resources/services/venafi-sign-ecdsa.json
@@ -0,0 +1,4 @@
+{
+ "ResultData": "v54401Dj+fKDo6ISPxcn0o0HDDYgxQXjP\/2I1TX9VXB\/LDr6MC67ikgVSVZz31MEGqqaLx4gyJDRSz\/cbLGlmw==",
+ "Success": true
+}
\ No newline at end of file
diff --git a/jsign-crypto/src/test/resources/services/venafi-sign-rsa-error.json b/jsign-crypto/src/test/resources/services/venafi-sign-rsa-error.json
new file mode 100644
index 00000000..aa822747
--- /dev/null
+++ b/jsign-crypto/src/test/resources/services/venafi-sign-rsa-error.json
@@ -0,0 +1,4 @@
+{
+ "Error": "Private Key Access: 'sign' operation failed. More info: Error signing data (Failed to sign data using engine: 'Software', error: Call to C_Sign failed [DataInvalid])",
+ "RequestThumbprint": "C8B78011FF6C2E6C936544B1D0D5455C29890BEE745ADEE5A9E03A6D72E6A1C6"
+}
diff --git a/jsign-crypto/src/test/resources/services/venafi-sign-rsa.json b/jsign-crypto/src/test/resources/services/venafi-sign-rsa.json
new file mode 100644
index 00000000..b90d96a8
--- /dev/null
+++ b/jsign-crypto/src/test/resources/services/venafi-sign-rsa.json
@@ -0,0 +1,4 @@
+{
+ "ResultData": "HLNy8jjS+p6bwlg0UEHjL1MQ1dBuuNi82KwWaPOPie0KfE+MyZ5QcgQZIzOS40\/fovWHuSdDLXTFHLyyfOZuXzD4bb9Rjveo2h4bt7ge6UAovvJjU1JOXylkwon0DeDQhuvM0En1O9cfsPvY9zzUn+MkWlyfmWic8COF3RyzSOVDeE\/kIFVWeIHFvIOwHKo5b\/fwURvDguFw6aa1Gh6A7Xz8bfvz+SU5G8ixd63kdUPQyzQih0+sp3aNtF7bjqg4wFRxga3JOMIM+tJjdT2huVWU4hCtJU\/CrbiFxD6X\/+egu4tyfKaTH8an7woVOW4BINd5N7lkeUJi7dDLa1nNPg==",
+ "Success": true
+}
\ No newline at end of file
diff --git a/jsign-maven-plugin/src/main/java/net/jsign/JsignMojo.java b/jsign-maven-plugin/src/main/java/net/jsign/JsignMojo.java
index 71b0785c..df126766 100644
--- a/jsign-maven-plugin/src/main/java/net/jsign/JsignMojo.java
+++ b/jsign-maven-plugin/src/main/java/net/jsign/JsignMojo.java
@@ -90,8 +90,9 @@ public class JsignMojo extends AbstractMojo {
/**
* The type of the keystore (JKS, JCEKS, PKCS12, PKCS11, ETOKEN, NITROKEY, OPENPGP, OPENSC, PIV, YUBIKEY, AWS,
- * AZUREKEYVAULT, DIGICERTONE, ESIGNER, GARASIGN, GOOGLECLOUD, HASHICORPVAULT, ORACLECLOUD, SIGNPATH, SIGNSERVER
- * or TRUSTEDSIGNING).
+ * AZUREKEYVAULT, DIGICERTONE, ESIGNER, GARASIGN, GOOGLECLOUD, HASHICORPVAULT, ORACLECLOUD, SIGNPATH, SIGNSERVER,
+ * VENAFI or TRUSTEDSIGNING).
+
*/
@Parameter( property = "jsign.storetype" )
private String storetype;
diff --git a/jsign/src/deb/data/usr/share/bash-completion/completions/jsign b/jsign/src/deb/data/usr/share/bash-completion/completions/jsign
index 06cd0e2f..6942447a 100644
--- a/jsign/src/deb/data/usr/share/bash-completion/completions/jsign
+++ b/jsign/src/deb/data/usr/share/bash-completion/completions/jsign
@@ -42,7 +42,7 @@ _jsign()
return 0
;;
--storetype)
- COMPREPLY=( $( compgen -W 'JKS JCEKS PKCS12 PKCS11 AWS AZUREKEYVAULT DIGICERTONE ESIGNER ETOKEN GARASIGN GOOGLECLOUD HASHICORPVAULT ORACLECLOUD SIGNPATH SIGNSERVER TRUSTEDSIGNING YUBIKEY NITROKEY OPENPGP OPENSC PIV' -- "$cur" ) )
+ COMPREPLY=( $( compgen -W 'JKS JCEKS PKCS12 PKCS11 AWS AZUREKEYVAULT DIGICERTONE ESIGNER ETOKEN GARASIGN GOOGLECLOUD HASHICORPVAULT ORACLECLOUD SIGNPATH SIGNSERVER VENAFI TRUSTEDSIGNING YUBIKEY NITROKEY OPENPGP OPENSC PIV' -- "$cur" ) )
return 0
;;
--storepass|-a|--alias|--keypass|-t|--tsaurl|-r|--tsretries|-w|--tsretrywait|-n|--name|-u|--url|--proxyUrl|--proxyUser|--proxyPass|--value)