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)