diff --git a/README.md b/README.md
index 52ef76f7..92a1823d 100644
--- a/README.md
+++ b/README.md
@@ -32,6 +32,7 @@ Jsign is free to use and licensed under the [Apache License version 2.0](https:/
* Cloud key management systems:
* [AWS KMS](https://aws.amazon.com/kms/)
* [Azure Key Vault](https://azure.microsoft.com/services/key-vault/)
+ * [Azure Trusted Signing](https://learn.microsoft.com/en-us/azure/trusted-signing/)
* [DigiCert ONE](https://one.digicert.com)
* [Google Cloud KMS](https://cloud.google.com/security-key-management)
* [HashiCorp Vault](https://www.vaultproject.io/)
@@ -51,6 +52,7 @@ See https://ebourg.github.io/jsign for more information.
#### Version 6.1 (in development)
+* The Azure Trusted Signing service has been integrated
* The Oracle Cloud signing service has been integrated
* Signing of NuGet packages has been implemented (contributed by Sebastian Stamm)
* Jsign now checks if the certificate subject matches the app manifest publisher before signing APPX/MSIX packages
diff --git a/docs/index.html b/docs/index.html
index 205df81e..083881c3 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -70,6 +70,7 @@
Features
No, automatically detected for file based keystores. |
@@ -472,6 +474,7 @@ Command Line Tool
- GOOGLECLOUD: Google Cloud KMS
- HASHICORPVAULT: Google Cloud KMS via HashiCorp Vault
- ORACLECLOUD: Oracle Cloud Key Management Service
+ - TRUSTEDSIGNING: Azure Trusted Signing
-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
@@ -641,6 +644,35 @@ Signing with Azure Key Vault
The Azure account used must have the "Key Vault Crypto User" and "Key Vault Certificate User" roles.
+Signing with Azure Trusted Signing
+
+With the Azure Trusted Signing service
+the keystore
parameter specifies the endpoint URI, and the alias
combines the account name and
+the certificate profile. The Azure API access token is used as the keystore password.
+
+
+ jsign --storetype TRUSTEDSIGNING \
+ --keystore weu.codesigning.azure.net \
+ --storepass <api-access-token> \
+ --alias <account>/<profile> application.exe
+
+
+The access token can be obtained with the Azure CLI:
+
+
+ az account get-access-token --resource https://codesigning.azure.net
+
+
+The Azure account used must have the "Code Signing Certificate Profile Signer" role.
+
+The certificates issued by Azure Trusted Signing have a lifetime of 3 days only, and timestamping is necessary to
+ensure the long term validity of the signature. For this reason timestamping is automatically enabled when signing
+with this service.
+
+Implementation note: Jsign performs an extra call to the signing API to retrieve the current certificate chain before
+signing. When signing multiple files it's recommended to invoke Jsign only once with the list of files to avoid doubling
+the quota usage.
+
Signing with DigiCert ONE
Certificates and keys stored in the DigiCert ONE Secure Software Manager
diff --git a/jsign-cli/src/main/java/net/jsign/JsignCLI.java b/jsign-cli/src/main/java/net/jsign/JsignCLI.java
index f57b4f14..089329d5 100644
--- a/jsign-cli/src/main/java/net/jsign/JsignCLI.java
+++ b/jsign-cli/src/main/java/net/jsign/JsignCLI.java
@@ -78,7 +78,8 @@ public static void main(String... args) {
+ "- ESIGNER: SSL.com eSigner\n"
+ "- GOOGLECLOUD: Google Cloud KMS\n"
+ "- HASHICORPVAULT: Google Cloud KMS via HashiCorp Vault\n"
- + "- ORACLECLOUD: Oracle Cloud Key Management Service\n").build());
+ + "- ORACLECLOUD: Oracle Cloud Key Management Service\n"
+ + "- TRUSTEDSIGNING: Azure Trusted Signing\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-core/src/main/java/net/jsign/KeyStoreType.java b/jsign-core/src/main/java/net/jsign/KeyStoreType.java
index 36eab934..c30bf05c 100644
--- a/jsign-core/src/main/java/net/jsign/KeyStoreType.java
+++ b/jsign-core/src/main/java/net/jsign/KeyStoreType.java
@@ -37,6 +37,7 @@
import net.jsign.jca.AmazonCredentials;
import net.jsign.jca.AmazonSigningService;
import net.jsign.jca.AzureKeyVaultSigningService;
+import net.jsign.jca.AzureTrustedSigningService;
import net.jsign.jca.DigiCertOneSigningService;
import net.jsign.jca.ESignerSigningService;
import net.jsign.jca.GoogleCloudSigningService;
@@ -469,6 +470,30 @@ Provider getProvider(KeyStoreBuilder params) {
}
return new SigningServiceJcaProvider(new OracleCloudSigningService(credentials, getCertificateStore(params)));
}
+ },
+
+ /**
+ * Azure Trusted Signing Service. The keystore parameter specifies the API endpoint (for example
+ * weu.codesigning.azure.net
). The Azure API access token is used as the keystore password,
+ * it can be obtained using the Azure CLI with:
+ *
+ *
az account get-access-token --resource https://codesigning.azure.net
+ */
+ TRUSTEDSIGNING(false, false, false) {
+ @Override
+ void validate(KeyStoreBuilder params) {
+ if (params.keystore() == null) {
+ throw new IllegalArgumentException("keystore " + params.parameterName() + " must specify the Azure endpoint (.codesigning.azure.net)");
+ }
+ if (params.storepass() == null) {
+ throw new IllegalArgumentException("storepass " + params.parameterName() + " must specify the Azure API access token");
+ }
+ }
+
+ @Override
+ Provider getProvider(KeyStoreBuilder params) {
+ return new SigningServiceJcaProvider(new AzureTrustedSigningService(params.keystore(), params.storepass()));
+ }
};
diff --git a/jsign-core/src/main/java/net/jsign/SignerHelper.java b/jsign-core/src/main/java/net/jsign/SignerHelper.java
index 6e661e79..d4549700 100644
--- a/jsign-core/src/main/java/net/jsign/SignerHelper.java
+++ b/jsign-core/src/main/java/net/jsign/SignerHelper.java
@@ -384,6 +384,13 @@ private AuthenticodeSigner build() throws SignerException {
} catch (Exception e) {
throw new SignerException("Couldn't initialize proxy", e);
}
+
+ // enable timestamping with Azure Trusted Signing
+ if (tsaurl == null && storetype == KeyStoreType.TRUSTEDSIGNING) {
+ tsaurl = "http://timestamp.acs.microsoft.com/";
+ tsmode = TimestampingMode.RFC3161.name();
+ tsretries = 3;
+ }
// configure the signer
return new AuthenticodeSigner(chain, privateKey)
diff --git a/jsign-core/src/main/java/net/jsign/jca/AzureTrustedSigningService.java b/jsign-core/src/main/java/net/jsign/jca/AzureTrustedSigningService.java
new file mode 100644
index 00000000..6f247d56
--- /dev/null
+++ b/jsign-core/src/main/java/net/jsign/jca/AzureTrustedSigningService.java
@@ -0,0 +1,189 @@
+/**
+ * Copyright 2024 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.ByteArrayInputStream;
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.KeyStoreException;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.cedarsoftware.util.io.JsonWriter;
+
+import net.jsign.DigestAlgorithm;
+
+/**
+ * Signing service using the Azure Trusted Signing API.
+ *
+ * @since 6.1
+ */
+public class AzureTrustedSigningService implements SigningService {
+
+ /** Cache of certificate chains indexed by alias */
+ private final Map certificates = new HashMap<>();
+
+ private final RESTClient client;
+
+ /** Timeout in seconds for the signing operation */
+ private long timeout = 60;
+
+ /**
+ * Mapping between Java and Azure signing algorithms.
+ * @see Key Vault API - JonWebKeySignatureAlgorithm
+ */
+ private final Map algorithmMapping = new HashMap<>();
+ {
+ algorithmMapping.put("SHA256withRSA", "RS256");
+ algorithmMapping.put("SHA384withRSA", "RS384");
+ algorithmMapping.put("SHA512withRSA", "RS512");
+ algorithmMapping.put("SHA256withECDSA", "ES256");
+ algorithmMapping.put("SHA384withECDSA", "ES384");
+ algorithmMapping.put("SHA512withECDSA", "ES512");
+ algorithmMapping.put("SHA256withRSA/PSS", "PS256");
+ algorithmMapping.put("SHA384withRSA/PSS", "PS384");
+ algorithmMapping.put("SHA512withRSA/PSS", "PS512");
+ }
+
+ public AzureTrustedSigningService(String endpoint, String token) {
+ if (!endpoint.startsWith("http")) {
+ endpoint = "https://" + endpoint;
+ }
+ client = new RESTClient(endpoint, conn -> conn.setRequestProperty("Authorization", "Bearer " + token));
+ }
+
+ void setTimeout(int timeout) {
+ this.timeout = timeout;
+ }
+
+ @Override
+ public String getName() {
+ return "TrustedSigning";
+ }
+
+ @Override
+ public List aliases() throws KeyStoreException {
+ return new ArrayList<>();
+ }
+
+ @Override
+ public Certificate[] getCertificateChain(String alias) throws KeyStoreException {
+ if (!certificates.containsKey(alias)) {
+ try {
+ String account = alias.substring(0, alias.indexOf('/'));
+ String profile = alias.substring(alias.indexOf('/') + 1);
+ SignStatus status = sign(account, profile, "RS256", new byte[32]);
+ certificates.put(alias, status.getCertificateChain().toArray(new Certificate[0]));
+ } catch (Exception e) {
+ throw new KeyStoreException("Unable to retrieve the certificate chain '" + alias + "'", e);
+ }
+ }
+
+ return certificates.get(alias);
+ }
+
+ @Override
+ public SigningServicePrivateKey getPrivateKey(String alias, char[] password) throws UnrecoverableKeyException {
+ return new SigningServicePrivateKey(alias, "RSA", this);
+ }
+
+ @Override
+ public byte[] sign(SigningServicePrivateKey privateKey, String algorithm, byte[] data) throws GeneralSecurityException {
+ String alg = algorithmMapping.get(algorithm);
+ if (alg == null) {
+ throw new InvalidAlgorithmParameterException("Unsupported signing algorithm: " + algorithm);
+ }
+
+ DigestAlgorithm digestAlgorithm = DigestAlgorithm.of(algorithm.substring(0, algorithm.toLowerCase().indexOf("with")));
+ data = digestAlgorithm.getMessageDigest().digest(data);
+
+ String alias = privateKey.getId();
+ String account = alias.substring(0, alias.indexOf('/'));
+ String profile = alias.substring(alias.indexOf('/') + 1);
+ try {
+ SignStatus status = sign(account, profile, alg, data);
+ return status.signature;
+ } catch (IOException e) {
+ throw new GeneralSecurityException(e);
+ }
+ }
+
+ private SignStatus sign(String account, String profile, String algorithm, byte[] data) throws IOException {
+ Map request = new HashMap<>();
+ request.put("signatureAlgorithm", algorithm);
+ request.put("digest", Base64.getEncoder().encodeToString(data));
+
+ Map args = new HashMap<>();
+ args.put(JsonWriter.TYPE, "false");
+
+ Map response = client.post("/codesigningaccounts/" + account + "/certificateprofiles/" + profile + "/sign?api-version=2022-06-15-preview", JsonWriter.objectToJson(request, args));
+
+ String operationId = (String) response.get("operationId");
+
+ // poll until the operation is completed
+ long startTime = System.currentTimeMillis();
+ int i = 0;
+ while (System.currentTimeMillis() - startTime < timeout * 1000) {
+ try {
+ Thread.sleep(Math.min(1000, 50 + 10 * i++));
+ } catch (InterruptedException e) {
+ break;
+ }
+ response = client.get("/codesigningaccounts/" + account + "/certificateprofiles/" + profile + "/sign/" + operationId + "?api-version=2022-06-15-preview");
+ String status = (String) response.get("status");
+ if ("InProgress".equals(status)) {
+ continue;
+ }
+ if ("Succeeded".equals(status)) {
+ break;
+ }
+
+ throw new IOException("Signing operation " + operationId + " failed: " + status);
+ }
+
+ if (!"Succeeded".equals(response.get("status"))) {
+ throw new IOException("Signing operation " + operationId + " timed out");
+ }
+
+ SignStatus status = new SignStatus();
+ status.signature = Base64.getDecoder().decode((String) response.get("signature"));
+ status.signingCertificate = new String(Base64.getDecoder().decode((String) response.get("signingCertificate")));
+
+ return status;
+ }
+
+ private static class SignStatus {
+ public byte[] signature;
+ public String signingCertificate;
+
+ public Collection extends Certificate> getCertificateChain() throws CertificateException {
+ byte[] cerbin = Base64.getMimeDecoder().decode(signingCertificate);
+
+ CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
+ return certificateFactory.generateCertificates(new ByteArrayInputStream(cerbin));
+ }
+ }
+}
diff --git a/jsign-core/src/main/java/net/jsign/jca/RESTClient.java b/jsign-core/src/main/java/net/jsign/jca/RESTClient.java
index d9314819..fb31367c 100644
--- a/jsign-core/src/main/java/net/jsign/jca/RESTClient.java
+++ b/jsign-core/src/main/java/net/jsign/jca/RESTClient.java
@@ -26,6 +26,7 @@
import java.util.function.Consumer;
import com.cedarsoftware.util.io.JsonReader;
+import com.cedarsoftware.util.io.JsonWriter;
import org.apache.commons.io.IOUtils;
class RESTClient {
@@ -136,6 +137,10 @@ private String getErrorMessage(Map response) {
String error = (String) response.get("__type");
String description = (String) response.get("message");
message.append(error).append(": ").append(description);
+ } else if (response.containsKey("title") && response.containsKey("errors")) {
+ // error from Azure Code Signing API
+ String errors = JsonWriter.objectToJson(response.get("errors"));
+ message.append(response.get("status")).append(" - ").append(response.get("title")).append(": ").append(errors);
} else if (response.containsKey("code") && response.containsKey("message")) {
// error from OCI API
message.append(response.get("code")).append(": ").append(response.get("message"));
diff --git a/jsign-core/src/test/java/net/jsign/KeyStoreBuilderTest.java b/jsign-core/src/test/java/net/jsign/KeyStoreBuilderTest.java
index c79c148d..3a813134 100644
--- a/jsign-core/src/test/java/net/jsign/KeyStoreBuilderTest.java
+++ b/jsign-core/src/test/java/net/jsign/KeyStoreBuilderTest.java
@@ -329,6 +329,32 @@ public void testBuildOracleCloud() throws Exception {
assertNotNull("keystore", keystore);
}
+ @Test
+ public void testBuildTrustedSigning() throws Exception {
+ KeyStoreBuilder builder = new KeyStoreBuilder().storetype(TRUSTEDSIGNING);
+
+ try {
+ builder.build();
+ fail("Exception not thrown");
+ } catch (IllegalArgumentException e) {
+ assertEquals("message", "keystore parameter must specify the Azure endpoint (.codesigning.azure.net)", e.getMessage());
+ }
+
+ builder.keystore("https://weu.codesigning.azure.net");
+
+ try {
+ builder.build();
+ fail("Exception not thrown");
+ } catch (IllegalArgumentException e) {
+ assertEquals("message", "storepass parameter must specify the Azure API access token", e.getMessage());
+ }
+
+ builder.storepass("0123456789ABCDEF");
+
+ KeyStore keystore = builder.build();
+ assertNotNull("keystore", keystore);
+ }
+
@Test
public void testBuildJKS() throws Exception {
KeyStoreBuilder builder = new KeyStoreBuilder().storetype(JKS);
diff --git a/jsign-core/src/test/java/net/jsign/SignerHelperTest.java b/jsign-core/src/test/java/net/jsign/SignerHelperTest.java
index ebfdfddf..bf26beb5 100644
--- a/jsign-core/src/test/java/net/jsign/SignerHelperTest.java
+++ b/jsign-core/src/test/java/net/jsign/SignerHelperTest.java
@@ -311,6 +311,27 @@ public void testOracleCloud() throws Exception {
SignatureAssert.assertSigned(new PEFile(targetFile), SHA256);
}
+ @Test
+ public void testTrustedSigning() throws Exception {
+ File sourceFile = new File("target/test-classes/wineyes.exe");
+ File targetFile = new File("target/test-classes/wineyes-signed-with-azure-trusted-signing.exe");
+
+ FileUtils.copyFile(sourceFile, targetFile);
+
+ SignerHelper helper = new SignerHelper(new StdOutConsole(1), "option")
+ .storetype("TRUSTEDSIGNING")
+ .keystore("weu.codesigning.azure.net")
+ .storepass(Azure.getAccessToken("https://codesigning.azure.net"))
+ .alias("MyAccount/MyProfile")
+ .alg("SHA-256");
+
+ helper.sign(targetFile);
+
+ Signable signable = Signable.of(targetFile);
+ SignatureAssert.assertSigned(signable, SHA256);
+ SignatureAssert.assertTimestamped("Invalid timestamp", signable.getSignatures().get(0));
+ }
+
@Test
public void testPIV() throws Exception {
PIVCardTest.assumeCardPresent();
diff --git a/jsign-core/src/test/java/net/jsign/jca/Azure.java b/jsign-core/src/test/java/net/jsign/jca/Azure.java
index 59a2ebf9..99fa71c4 100644
--- a/jsign-core/src/test/java/net/jsign/jca/Azure.java
+++ b/jsign-core/src/test/java/net/jsign/jca/Azure.java
@@ -29,9 +29,16 @@ public class Azure {
* Generates an Azure access token using the CLI: az account get-access-token --resource "https://vault.azure.net"
*/
public static String getAccessToken() throws IOException, InterruptedException {
+ return getAccessToken("https://vault.azure.net");
+ }
+
+ /**
+ * Generates an Azure access token using the CLI: az account get-access-token --resource <resource>
+ */
+ public static String getAccessToken(String resource) throws IOException, InterruptedException {
Process process = null;
try {
- ProcessBuilder builder = new ProcessBuilder("az.cmd", "account", "get-access-token", "--resource", "https://vault.azure.net");
+ ProcessBuilder builder = new ProcessBuilder("az.cmd", "account", "get-access-token", "--resource", resource);
process = builder.start();
process.waitFor();
Assume.assumeTrue("Couldn't get Azure API token", process.exitValue() == 0);
diff --git a/jsign-core/src/test/java/net/jsign/jca/AzureTrustedSigningServiceTest.java b/jsign-core/src/test/java/net/jsign/jca/AzureTrustedSigningServiceTest.java
new file mode 100644
index 00000000..86108c3e
--- /dev/null
+++ b/jsign-core/src/test/java/net/jsign/jca/AzureTrustedSigningServiceTest.java
@@ -0,0 +1,212 @@
+/**
+ * Copyright 2024 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.FileInputStream;
+import java.security.GeneralSecurityException;
+import java.security.KeyStoreException;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.util.Collections;
+import java.util.List;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import static net.jadler.Jadler.*;
+import static org.junit.Assert.*;
+
+public class AzureTrustedSigningServiceTest {
+
+ @Before
+ public void setUp() {
+ initJadler().withDefaultResponseStatus(404);
+ }
+
+ @After
+ public void tearDown() {
+ closeJadler();
+ }
+
+ @Test
+ public void testGetAliases() throws Exception {
+ SigningService service = new AzureTrustedSigningService("http://localhost:" + port(), "token");
+ List aliases = service.aliases();
+
+ assertEquals("aliases", Collections.emptyList(), aliases);
+ }
+
+ @Test
+ public void testGetCertificateChain() throws Exception {
+ onRequest()
+ .havingMethodEqualTo("POST")
+ .havingPathEqualTo("/codesigningaccounts/MyAccount/certificateprofiles/MyProfile/sign")
+ .havingQueryStringEqualTo("api-version=2022-06-15-preview")
+ .respond()
+ .withStatus(202)
+ .withBody("{\"operationId\":\"1f234bd9-16cf-4283-9ee6-a460d31207bb\",\"status\":\"InProgress\",\"signature\":null,\"signingCertificate\":null}");
+ onRequest()
+ .havingMethodEqualTo("GET")
+ .havingPathEqualTo("/codesigningaccounts/MyAccount/certificateprofiles/MyProfile/sign/1f234bd9-16cf-4283-9ee6-a460d31207bb")
+ .havingQueryStringEqualTo("api-version=2022-06-15-preview")
+ .respond()
+ .withStatus(200)
+ .withBody("{\"operationId\":\"1f234bd9-16cf-4283-9ee6-a460d31207bb\",\"status\":\"InProgress\",\"signature\":null,\"signingCertificate\":null}")
+ .thenRespond()
+ .withStatus(200)
+ .withBody("{\"operationId\":\"1f234bd9-16cf-4283-9ee6-a460d31207bb\",\"status\":\"InProgress\",\"signature\":null,\"signingCertificate\":null}")
+ .thenRespond()
+ .withStatus(200)
+ .withBody(new FileInputStream("target/test-classes/services/trustedsigning-sign.json"));
+
+ SigningService service = new AzureTrustedSigningService("http://localhost:" + port(), "token");
+ Certificate[] chain = service.getCertificateChain("MyAccount/MyProfile");
+ assertNotNull("null chain", chain);
+ assertEquals("length", 4, chain.length);
+ assertEquals("subject 1", "CN=Emmanuel Bourg, O=Emmanuel Bourg, L=Paris, ST=Ile de France, C=FR", ((X509Certificate) chain[0]).getSubjectDN().getName());
+ assertEquals("subject 2", "CN=Microsoft ID Verified CS EOC CA 01, O=Microsoft Corporation, C=US", ((X509Certificate) chain[1]).getSubjectDN().getName());
+ assertEquals("subject 3", "CN=Microsoft ID Verified Code Signing PCA 2021, O=Microsoft Corporation, C=US", ((X509Certificate) chain[2]).getSubjectDN().getName());
+ assertEquals("subject 4", "CN=Microsoft Identity Verification Root Certificate Authority 2020, O=Microsoft Corporation, C=US", ((X509Certificate) chain[3]).getSubjectDN().getName());
+ }
+
+ @Test
+ public void testGetCertificateChainWithError() {
+ onRequest()
+ .havingMethodEqualTo("POST")
+ .havingPathEqualTo("/codesigningaccounts/MyAccount/certificateprofiles/MyProfile/sign")
+ .havingQueryStringEqualTo("api-version=2022-06-15-preview")
+ .respond()
+ .withStatus(403);
+
+ SigningService service = new AzureTrustedSigningService("http://localhost:" + port(), "token");
+ try {
+ service.getCertificateChain("MyAccount/MyProfile");
+ fail("No exception thrown");
+ } catch (KeyStoreException e) {
+ assertEquals("message", "Unable to retrieve the certificate chain 'MyAccount/MyProfile'", e.getMessage());
+ }
+ }
+
+ @Test
+ public void testGetPrivateKey() throws Exception {
+ SigningService service = new AzureTrustedSigningService("http://localhost:" + port(), "token");
+ SigningServicePrivateKey privateKey = service.getPrivateKey("MyAccount/MyProfile", null);
+ assertNotNull("null key", privateKey);
+ assertEquals("id", "MyAccount/MyProfile", privateKey.getId());
+ assertEquals("algorithm", "RSA", privateKey.getAlgorithm());
+ }
+
+ @Test
+ public void testSign() throws Exception {
+ onRequest()
+ .havingMethodEqualTo("POST")
+ .havingPathEqualTo("/codesigningaccounts/MyAccount/certificateprofiles/MyProfile/sign")
+ .havingQueryStringEqualTo("api-version=2022-06-15-preview")
+ .respond()
+ .withStatus(202)
+ .withBody("{\"operationId\":\"1f234bd9-16cf-4283-9ee6-a460d31207bb\",\"status\":\"InProgress\",\"signature\":null,\"signingCertificate\":null}");
+ onRequest()
+ .havingMethodEqualTo("GET")
+ .havingPathEqualTo("/codesigningaccounts/MyAccount/certificateprofiles/MyProfile/sign/1f234bd9-16cf-4283-9ee6-a460d31207bb")
+ .havingQueryStringEqualTo("api-version=2022-06-15-preview")
+ .respond()
+ .withStatus(200)
+ .withBody("{\"operationId\":\"1f234bd9-16cf-4283-9ee6-a460d31207bb\",\"status\":\"InProgress\",\"signature\":null,\"signingCertificate\":null}")
+ .thenRespond()
+ .withStatus(200)
+ .withBody("{\"operationId\":\"1f234bd9-16cf-4283-9ee6-a460d31207bb\",\"status\":\"InProgress\",\"signature\":null,\"signingCertificate\":null}")
+ .thenRespond()
+ .withStatus(200)
+ .withBody(new FileInputStream("target/test-classes/services/trustedsigning-sign.json"));
+
+ AzureTrustedSigningService service = new AzureTrustedSigningService("http://localhost:" + port(), "token");
+ SigningServicePrivateKey privateKey = service.getPrivateKey("MyAccount/MyProfile", null);
+
+ byte[] signature = service.sign(privateKey, "SHA256withRSA", "Hello".getBytes());
+
+ assertNotNull("null signature", signature);
+ assertEquals("length", 384, signature.length);
+ }
+
+ @Test
+ public void testSignWithTimeout() throws Exception {
+ onRequest()
+ .havingMethodEqualTo("POST")
+ .havingPathEqualTo("/codesigningaccounts/MyAccount/certificateprofiles/MyProfile/sign")
+ .havingQueryStringEqualTo("api-version=2022-06-15-preview")
+ .respond()
+ .withStatus(202)
+ .withBody("{\"operationId\":\"1f234bd9-16cf-4283-9ee6-a460d31207bb\",\"status\":\"InProgress\",\"signature\":null,\"signingCertificate\":null}");
+ onRequest()
+ .havingMethodEqualTo("GET")
+ .havingPathEqualTo("/codesigningaccounts/MyAccount/certificateprofiles/MyProfile/sign/1f234bd9-16cf-4283-9ee6-a460d31207bb")
+ .havingQueryStringEqualTo("api-version=2022-06-15-preview")
+ .respond()
+ .withStatus(200)
+ .withBody("{\"operationId\":\"1f234bd9-16cf-4283-9ee6-a460d31207bb\",\"status\":\"InProgress\",\"signature\":null,\"signingCertificate\":null}");
+
+ AzureTrustedSigningService service = new AzureTrustedSigningService("http://localhost:" + port(), "token");
+ service.setTimeout(2);
+ SigningServicePrivateKey privateKey = service.getPrivateKey("MyAccount/MyProfile", null);
+ try {
+ service.sign(privateKey, "SHA256withRSA", "Hello".getBytes());
+ fail("No exception thrown");
+ } catch (GeneralSecurityException e) {
+ assertEquals("message", "java.io.IOException: Signing operation 1f234bd9-16cf-4283-9ee6-a460d31207bb timed out", e.getMessage());
+ }
+ }
+
+ @Test
+ public void testSignWithFailure() throws Exception {
+ onRequest()
+ .havingMethodEqualTo("POST")
+ .havingPathEqualTo("/codesigningaccounts/MyAccount/certificateprofiles/MyProfile/sign")
+ .havingQueryStringEqualTo("api-version=2022-06-15-preview")
+ .respond()
+ .withStatus(202)
+ .withBody("{\"operationId\":\"1f234bd9-16cf-4283-9ee6-a460d31207bb\",\"status\":\"InProgress\",\"signature\":null,\"signingCertificate\":null}");
+ onRequest()
+ .havingMethodEqualTo("GET")
+ .havingPathEqualTo("/codesigningaccounts/MyAccount/certificateprofiles/MyProfile/sign/1f234bd9-16cf-4283-9ee6-a460d31207bb")
+ .havingQueryStringEqualTo("api-version=2022-06-15-preview")
+ .respond()
+ .withStatus(200)
+ .withBody("{\"operationId\":\"1f234bd9-16cf-4283-9ee6-a460d31207bb\",\"status\":\"Failed\",\"signature\":null,\"signingCertificate\":null}");
+
+ AzureTrustedSigningService service = new AzureTrustedSigningService("http://localhost:" + port(), "token");
+ SigningServicePrivateKey privateKey = service.getPrivateKey("MyAccount/MyProfile", null);
+ try {
+ service.sign(privateKey, "SHA256withRSA", "Hello".getBytes());
+ fail("No exception thrown");
+ } catch (GeneralSecurityException e) {
+ assertEquals("message", "java.io.IOException: Signing operation 1f234bd9-16cf-4283-9ee6-a460d31207bb failed: Failed", e.getMessage());
+ }
+ }
+
+ @Test
+ public void testSignWithInvalidAlgorithm() throws Exception {
+ SigningService service = new AzureTrustedSigningService("http://localhost:" + port(), "token");
+ SigningServicePrivateKey privateKey = service.getPrivateKey("MyAccount/MyProfile", null);
+ try {
+ service.sign(privateKey, "SHA1withRSA", "Hello".getBytes());
+ fail("No exception thrown");
+ } catch (GeneralSecurityException e) {
+ assertEquals("message", "Unsupported signing algorithm: SHA1withRSA", e.getMessage());
+ }
+ }
+}
diff --git a/jsign-core/src/test/java/net/jsign/jca/SigningServiceTest.java b/jsign-core/src/test/java/net/jsign/jca/SigningServiceTest.java
index 4400e1f1..cdc407ab 100644
--- a/jsign-core/src/test/java/net/jsign/jca/SigningServiceTest.java
+++ b/jsign-core/src/test/java/net/jsign/jca/SigningServiceTest.java
@@ -200,4 +200,14 @@ public void testOracleCloudProvider() throws Exception {
testCustomProvider(provider, keystore, "ocid1.key.oc1.eu-paris-1.h5tafwboaahxq.abrwiljrwkhgllb5zfqchmvdkmqnzutqeq5pz7yo6z7yhl2zyn2yncwzxiza", "");
}
+
+ @Test
+ public void testTrustedSigningProvider() throws Exception {
+ String token = Azure.getAccessToken("https://codesigning.azure.net");
+ Provider provider = new SigningServiceJcaProvider(new AzureTrustedSigningService("https://weu.codesigning.azure.net", token));
+ KeyStore keystore = KeyStore.getInstance("TRUSTEDSIGNING", provider);
+ keystore.load(null, "".toCharArray());
+
+ testCustomProvider(provider, keystore, "MyAccount/MyProfile", "");
+ }
}
diff --git a/jsign-core/src/test/resources/services/trustedsigning-sign.json b/jsign-core/src/test/resources/services/trustedsigning-sign.json
new file mode 100644
index 00000000..1e6e22ad
--- /dev/null
+++ b/jsign-core/src/test/resources/services/trustedsigning-sign.json
@@ -0,0 +1,6 @@
+{
+ "operationId": "1f234bd9-16cf-4283-9ee6-a460d31207bb",
+ "status": "Succeeded",
+ "signature": "fyrDRW1Xf063cYW+PCI2Adf4gX4PxFzBzvGHOkUxWbaofwxhZe8qm5QrxMLb4mMHCi6OQ9/RYCBxKBD4IiYzbyDd+dSlXcZQlkxixcwC3755glSNrlkObuPLQCbwMzdmfA+KchJCjpgbGO7Z7txNx6qp7nvuKagomPtNWxrp6MA1VLryfcE750ek/4zS0ik5wDYjaIbsT7t3qkTzR4Q5o1D4aaF/RcXwTjeELLgyxkYQd9E/9g88uwtSmXSSSRdQVzh4jU8rWhAbJy010DwunKmobZZsp0d47pI1IQBbXjCa4cW/PcidQtDkPOV+Py06cGjXUvqeBRUyNGzF8pOpkEVjHnfsSGH03cYAYBrj4oCwOtpOUcYMotyKsQ02zGDA8FBTDsVLtvRflQHJeKdpAFYom8fOERNmqdNiSu2NFKQK8l5w6a1pjfmxXRCG8JbD+xWgZSG2coAVk1xiSblse31DYsa4l+chyui0JESxo6jwyRAQw6OIvBIicnrJbmA3",
+ "signingCertificate": "TUlJaTN3WUpLb1pJaHZjTkFRY0NvSUlpMERDQ0lzd0NBUUV4QURDQ0J3QUdDU3FHU0liM0RRRUhBYUNDQnZFRQ0KZ2didE1JSUc2VENDQk5HZ0F3SUJBZ0lUTXdBQVlNYTJ4bFFoSlQ4dDVnQUFBQUJneGpBTkJna3Foa2lHOXcwQg0KQVF3RkFEQmFNUXN3Q1FZRFZRUUdFd0pWVXpFZU1Cd0dBMVVFQ2hNVlRXbGpjbTl6YjJaMElFTnZjbkJ2Y21GMA0KYVc5dU1Tc3dLUVlEVlFRREV5Sk5hV055YjNOdlpuUWdTVVFnVm1WeWFXWnBaV1FnUTFNZ1JVOURJRU5CSURBeA0KTUI0WERUSTBNRFF3TVRJeU5UY3lNRm9YRFRJME1EUXdOREl5TlRjeU1Gb3daekVMTUFrR0ExVUVCaE1DUmxJeA0KRmpBVUJnTlZCQWdURFVsc1pTQmtaU0JHY21GdVkyVXhEakFNQmdOVkJBY1RCVkJoY21sek1SY3dGUVlEVlFRSw0KRXc1RmJXMWhiblZsYkNCQ2IzVnlaekVYTUJVR0ExVUVBeE1PUlcxdFlXNTFaV3dnUW05MWNtY3dnZ0dpTUEwRw0KQ1NxR1NJYjNEUUVCQVFVQUE0SUJqd0F3Z2dHS0FvSUJnUUNlQ3JsN2pzdG1Kc0hiZHUzaGY2dk5WMVlVVFIwaQ0KQ3ZabEU0R2RtbW1kRHpvZDFxbGhYVjB1NDA2QU1CbmU5UkFWdTNDRWsrMkZ1SXVpRDVCQlNZQW1scTFDdFp5OA0KVWtUNUlybUV5NndlcDZITytXalAwWmRiSlozWGdPZVFlSktGVDNTZjh2SzN0Q3RVYlU2Q2tyb3NRM3grRFd2Rg0KcmtqcVkzb1dXa2s3c215cmszTWxNK3dwTzBnS2JTelFvZG1IaVdWVXphNkQzVWxPc2FFY0Z3NGxDY3FVeG92TQ0KSjAxSVFQQ2Nmbk4xeUpkQWttY2VESXR5dFZqUGlCZ2wzbzR6YmJZdXlRMExhRVpydmhtT1JsenVhVDBxWXVoSQ0KR1BwY3hFVnZNTzFIRC9yUy9JR2doMEhQMi94aTZmaDgxQUlzd3JrSEQ4Z3RrSTRXaXhRc3JjWGd1WFJhRThtdw0KcDNGbHdENi9Tb2xkSnRxOUJwbTEwSEZQTm5oRStwc0ZLVDZveWhuWnNuVjdxQlYyeW9nQWpOOHoxVitBYU9pYw0KYlowL2FvNG80ampSK2pHaW9mTU1RUFdiMU9PNkpXUS9ab3cxZHltUFYxVU5UTThBZC9PbW5nbUc2ZFVOWWlvMw0KNEtXZDYrVFNDK2JwTy9JSWhmMS91QkNxQnJrZGh6YjdMaXNDQXdFQUFhT0NBaGt3Z2dJVk1Bd0dBMVVkRXdFQg0KL3dRQ01BQXdEZ1lEVlIwUEFRSC9CQVFEQWdlQU1Ed0dBMVVkSlFRMU1ETUdDaXNHQVFRQmdqZGhBUUFHQ0NzRw0KQVFVRkJ3TURCaHNyQmdFRUFZSTNZWUdNL3ZaTWd1SENpMStDOGJIV0dkZnNsbXN3SFFZRFZSME9CQllFRkxIVQ0KOE9oY1VvSy9BQzJKZ3VWRlFxQldQRlVzTUI4R0ExVWRJd1FZTUJhQUZIYWNOblFUMFpCOVlWK3pBdXVBOUpsTA0KcFQ2Rk1HY0dBMVVkSHdSZ01GNHdYS0Jhb0ZpR1ZtaDBkSEE2THk5M2QzY3ViV2xqY205emIyWjBMbU52YlM5dw0KYTJsdmNITXZZM0pzTDAxcFkzSnZjMjltZENVeU1FbEVKVEl3Vm1WeWFXWnBaV1FsTWpCRFV5VXlNRVZQUXlVeQ0KTUVOQkpUSXdNREV1WTNKc01JR2xCZ2dyQmdFRkJRY0JBUVNCbURDQmxUQmtCZ2dyQmdFRkJRY3dBb1pZYUhSMA0KY0RvdkwzZDNkeTV0YVdOeWIzTnZablF1WTI5dEwzQnJhVzl3Y3k5alpYSjBjeTlOYVdOeWIzTnZablFsTWpCSg0KUkNVeU1GWmxjbWxtYVdWa0pUSXdRMU1sTWpCRlQwTWxNakJEUVNVeU1EQXhMbU55ZERBdEJnZ3JCZ0VGQlFjdw0KQVlZaGFIUjBjRG92TDI5dVpXOWpjM0F1YldsamNtOXpiMlowTG1OdmJTOXZZM053TUdZR0ExVWRJQVJmTUYwdw0KVVFZTUt3WUJCQUdDTjB5RGZRRUJNRUV3UHdZSUt3WUJCUVVIQWdFV00yaDBkSEE2THk5M2QzY3ViV2xqY205eg0KYjJaMExtTnZiUzl3YTJsdmNITXZSRzlqY3k5U1pYQnZjMmwwYjNKNUxtaDBiVEFJQmdabmdRd0JCQUV3RFFZSg0KS29aSWh2Y05BUUVNQlFBRGdnSUJBSDVOYWRKLzB1ZEpHUllsNFg1Y1FuOWpTR0gyVktzMWVibGVOZWltanFNZQ0Ka29vOTF1aUcvSjE2N3Jkc1cxZmVVeGJPUG5OQUxWNzJmYnR6QmQyRVQ2OGNoQWJXNTFuU2d4bjZtL0RTQmtrag0KUEh1WFlYblpHdlliQ3lvL0VZMDdmdUNkNWdOSXZqU05rYk5Qdk1OMU5ta1ptQzdKOFVoZ01YMFZkaHZVN3ozSQ0KOW4rM0dLTUdza0o2aWErYldkWDNnOGNnZXJCeEkzQlNyekhnNVJzcVpEK1IrVG1OTUpOYkxGek1hZjZCQ0RScA0KOElxalZ1c2xUc3lob1YxOS8yUDBmMUxzU2orQzZrc2s3MHNIMXA3a1piREpORCtMYis4NE1EUVVFZ3pHQnptTQ0KZlVpZHlnbyt2NFF4aldHcXRXc1daL3ZEblB3dElPMFR6ZkQzY2x6Z0V3UHUwT0FXT25vSk52dU5TclhjZzU0RA0KbnFqOTJKdytjMHlrMnRucU0vUHhzK0JhSmV5THZCLzBhUldIQ0tHK0JOVEFZbnNsNXB2NkxXL21hYk02UjQwYQ0KanpDRW5vcDRteXA3R3BtQnRNV0pEdXplWlZkQ0NtaUQwaGtiR0w5cnkwdUM0S2JmeU1wU0lVdFBNMXN6WmszZQ0KM3hjTXNiNXpEV05PS3JjTzhMT29kZ2RzSHhKN2R3M2dIWXBhTE9xUTlCZkUzaG9GWDZkdXdHR1pSKythN3B4cQ0KY2dqdXYraE1Rd2xGNEZ4VUpWcEpheXd6Sitva3ZzNGE4OEQrOTFabDJWcnpOOWU1V2dCbUwrdU9PSWphVDNjYw0Kak84WEt5VkJGU3JaejM5QTV6ekpCRDNPcC9xN1VCUWFDbC9TaW9uYjJ5LzRrNjVlVTA0cmJXOHg3NUF3ckUrWA0Kb0lJYnZUQ0NCdWt3Z2dUUm9BTUNBUUlDRXpNQUFHREd0c1pVSVNVL0xlWUFBQUFBWU1Zd0RRWUpLb1pJaHZjTg0KQVFFTUJRQXdXakVMTUFrR0ExVUVCaE1DVlZNeEhqQWNCZ05WQkFvVEZVMXBZM0p2YzI5bWRDQkRiM0p3YjNKaA0KZEdsdmJqRXJNQ2tHQTFVRUF4TWlUV2xqY205emIyWjBJRWxFSUZabGNtbG1hV1ZrSUVOVElFVlBReUJEUVNBdw0KTVRBZUZ3MHlOREEwTURFeU1qVTNNakJhRncweU5EQTBNRFF5TWpVM01qQmFNR2N4Q3pBSkJnTlZCQVlUQWtaUw0KTVJZd0ZBWURWUVFJRXcxSmJHVWdaR1VnUm5KaGJtTmxNUTR3REFZRFZRUUhFd1ZRWVhKcGN6RVhNQlVHQTFVRQ0KQ2hNT1JXMXRZVzUxWld3Z1FtOTFjbWN4RnpBVkJnTlZCQU1URGtWdGJXRnVkV1ZzSUVKdmRYSm5NSUlCb2pBTg0KQmdrcWhraUc5dzBCQVFFRkFBT0NBWThBTUlJQmlnS0NBWUVBbmdxNWU0N0xaaWJCMjNidDRYK3J6VmRXRkUwZA0KSWdyMlpST0JuWnBwblE4NkhkYXBZVjFkTHVOT2dEQVozdlVRRmJ0d2hKUHRoYmlMb2crUVFVbUFKcGF0UXJXYw0KdkZKRStTSzVoTXVzSHFlaHp2bG96OUdYV3lXZDE0RG5rSGlTaFU5MG4vTHl0N1FyVkcxT2dwSzZMRU44Zmcxcg0KeGE1STZtTjZGbHBKTzdKc3E1TnpKVFBzS1R0SUNtMHMwS0haaDRsbFZNMnVnOTFKVHJHaEhCY09KUW5LbE1hTA0KekNkTlNFRHduSDV6ZGNpWFFKSm5IZ3lMY3JWWXo0Z1lKZDZPTTIyMkxza05DMmhHYTc0WmprWmM3bWs5S21Mbw0KU0JqNlhNUkZiekR0UncvNjB2eUJvSWRCejl2OFl1bjRmTlFDTE1LNUJ3L0lMWkNPRm9zVUxLM0Y0TGwwV2hQSg0Kc0tkeFpjQSt2MHFKWFNiYXZRYVp0ZEJ4VHpaNFJQcWJCU2srcU1vWjJiSjFlNmdWZHNxSUFJemZNOVZmZ0dqbw0KbkcyZFAycU9LT0k0MGZveG9xSHpERUQxbTlUanVpVmtQMmFNTlhjcGoxZFZEVXpQQUhmenBwNEpodW5WRFdJcQ0KTitDbG5ldmswZ3ZtNlR2eUNJWDlmN2dRcWdhNUhZYzIreTRyQWdNQkFBR2pnZ0laTUlJQ0ZUQU1CZ05WSFJNQg0KQWY4RUFqQUFNQTRHQTFVZER3RUIvd1FFQXdJSGdEQThCZ05WSFNVRU5UQXpCZ29yQmdFRUFZSTNZUUVBQmdncg0KQmdFRkJRY0RBd1liS3dZQkJBR0NOMkdCalA3MlRJTGh3b3RmZ3ZHeDFoblg3SlpyTUIwR0ExVWREZ1FXQkJTeA0KMVBEb1hGS0N2d0F0aVlMbFJVS2dWanhWTERBZkJnTlZIU01FR0RBV2dCUjJuRFowRTlHUWZXRmZzd0xyZ1BTWg0KUzZVK2hUQm5CZ05WSFI4RVlEQmVNRnlnV3FCWWhsWm9kSFJ3T2k4dmQzZDNMbTFwWTNKdmMyOW1kQzVqYjIwdg0KY0d0cGIzQnpMMk55YkM5TmFXTnliM052Wm5RbE1qQkpSQ1V5TUZabGNtbG1hV1ZrSlRJd1ExTWxNakJGVDBNbA0KTWpCRFFTVXlNREF4TG1OeWJEQ0JwUVlJS3dZQkJRVUhBUUVFZ1pnd2daVXdaQVlJS3dZQkJRVUhNQUtHV0doMA0KZEhBNkx5OTNkM2N1YldsamNtOXpiMlowTG1OdmJTOXdhMmx2Y0hNdlkyVnlkSE12VFdsamNtOXpiMlowSlRJdw0KU1VRbE1qQldaWEpwWm1sbFpDVXlNRU5USlRJd1JVOURKVEl3UTBFbE1qQXdNUzVqY25Rd0xRWUlLd1lCQlFVSA0KTUFHR0lXaDBkSEE2THk5dmJtVnZZM053TG0xcFkzSnZjMjltZEM1amIyMHZiMk56Y0RCbUJnTlZIU0FFWHpCZA0KTUZFR0RDc0dBUVFCZ2pkTWczMEJBVEJCTUQ4R0NDc0dBUVVGQndJQkZqTm9kSFJ3T2k4dmQzZDNMbTFwWTNKdg0KYzI5bWRDNWpiMjB2Y0d0cGIzQnpMMFJ2WTNNdlVtVndiM05wZEc5eWVTNW9kRzB3Q0FZR1o0RU1BUVFCTUEwRw0KQ1NxR1NJYjNEUUVCREFVQUE0SUNBUUIrVFduU2Y5TG5TUmtXSmVGK1hFSi9ZMGhoOWxTck5YbTVYalhvcG82ag0KSHBLS1BkYm9odnlkZXU2M2JGdFgzbE1Xemo1elFDMWU5bjI3Y3dYZGhFK3ZISVFHMXVkWjBvTVorcHZ3MGdaSg0KSXp4N2wyRjUyUnIyR3dzcVB4R05PMzdnbmVZRFNMNDBqWkd6VDd6RGRUWnBHWmd1eWZGSVlERjlGWFliMU84OQ0KeVBaL3R4aWpCckpDZW9tdm0xblY5NFBISUhxd2NTTndVcTh4NE9VYkttUS9rZms1alRDVFd5eGN6R24rZ1FnMA0KYWZDS28xYnJKVTdNb2FGZGZmOWo5SDlTN0VvL2d1cExKTzlMQjlhZTVHV3d5VFEvaTIvdk9EQTBGQklNeGdjNQ0KakgxSW5jb0tQcitFTVkxaHFyVnJGbWY3dzV6OExTRHRFODN3OTNKYzRCTUQ3dERnRmpwNkNUYjdqVXExM0lPZQ0KQTU2by9kaWNQbk5NcE5yWjZqUHo4YlBnV2lYc2k3d2Y5R2tWaHdpaHZnVFV3R0o3SmVhYitpMXY1bW16T2tlTg0KR284d2hKNktlSnNxZXhxWmdiVEZpUTdzM21WWFFncG9nOUlaR3hpL2E4dExndUNtMzhqS1VpRkxUek5iTTJaTg0KM3Q4WERMRytjdzFqVGlxM0R2Q3pxSFlIYkI4U2UzY040QjJLV2l6cWtQUVh4TjRhQlYrbmJzQmhtVWZ2bXU2Yw0KYW5JSTdyL29URU1KUmVCY1ZDVmFTV3NzTXlmcUpMN09HdlBBL3ZkV1pkbGE4emZYdVZvQVppL3JqamlJMms5Mw0KSEl6dkZ5c2xRUlVxMmM5L1FPYzh5UVE5enFmNnUxQVVHZ3BmMG9xSjI5c3YrSk91WGxOT0syMXZNZStRTUt4UA0KbHpDQ0Ixb3dnZ1ZDb0FNQ0FRSUNFek1BQUFBR1NocjZ6d1ZoYW5RQUFBQUFBQVl3RFFZSktvWklodmNOQVFFTQ0KQlFBd1l6RUxNQWtHQTFVRUJoTUNWVk14SGpBY0JnTlZCQW9URlUxcFkzSnZjMjltZENCRGIzSndiM0poZEdsdg0KYmpFME1ESUdBMVVFQXhNclRXbGpjbTl6YjJaMElFbEVJRlpsY21sbWFXVmtJRU52WkdVZ1UybG5ibWx1WnlCUQ0KUTBFZ01qQXlNVEFlRncweU1UQTBNVE14TnpNeE5UUmFGdzB5TmpBME1UTXhOek14TlRSYU1Gb3hDekFKQmdOVg0KQkFZVEFsVlRNUjR3SEFZRFZRUUtFeFZOYVdOeWIzTnZablFnUTI5eWNHOXlZWFJwYjI0eEt6QXBCZ05WQkFNVA0KSWsxcFkzSnZjMjltZENCSlJDQldaWEpwWm1sbFpDQkRVeUJGVDBNZ1EwRWdNREV3Z2dJaU1BMEdDU3FHU0liMw0KRFFFQkFRVUFBNElDRHdBd2dnSUtBb0lDQVFESDQ4Zy85Q0hkeGhuQXU4WExxNjRuaDlPbmVXZnNhcXp1enlWTg0KWEorQTRsWS9Wb0FIQ1RiK2pGMVdOOUlkU3JneE05ZUtVdm51cUw5OGZ0aWQwUXJncWQzZTdseDUwWEN2Wm9kSg0KT25xK1g4OHZWMEF2MngrZ084MmwwYlEzOUh6Z0NGZzJrRkJPR2s3ajhHckdZS0NYZUloRitHSGFnVlU2NkpPSQ0KTlZhOWNHRHZwdHlPY2VjUVMxZk84QmJBbTdSc0ZUdWhGR3BCNTNoVmNtMGdKVzM1bWdwUktPcGpuQlNXRUIzQQ0KZUg3ZlVHZWtFOExNVzBwV0l1bnJNUzFISTdGRjZCcUFWVDdJdUJlKytaM1RzZ00zUkxaTXRpNkptTlBENlJ4Zw0KNjJnMkFxdnVUUUxvVDFaL2NmaU1kcStUWXpHb1dtMkI4dlNBdjdOdEp2NVVFMHFKVlBTYXJOY2tnbVphYXJEUQ0KcjRQY3dwK1lKNnZkN2NKdXMvNFhsRzBKdlJkb1RTNUZ3azlrbU5iQnlJTUhFRWh1UTBYZ1l2WGFHWG0vSjJBVQ0KeWJOQncyNmgwckpmLy9lVXNuV3JiYXVnZFZMVnlDMnd1Q21OWmhtVUdXRUpOeGNsNW5mRzVvbTlka0gydHdzSg0KZlhrNkJjdmJXMVJUQWtJc1RidFhrQVpuR1E3ZUxuaWFCSUt6QzA2WlpUZ0FwMzhIOTdjcTFlL3BjRlJFcTRDMQ0KNTdQVVNtQ1docG5CQjZQMlhsMDMxU0h4YlgwRm1EMGlVdVg3RWRGZmk4T0l4WUJSLy9zQTE3Z3loTDN3WGptdg0Kdm9nWW5TRUxUWVF5NHhuRUFTdkJtUFNXZlJvdm5jVE9VeHJra0tKRTV0dlJTZ3NkOFpKMDBtd3lEUzZQY01CQQ0KTjFWWk1RSURBUUFCbzRJQ0RqQ0NBZ293RGdZRFZSMFBBUUgvQkFRREFnR0dNQkFHQ1NzR0FRUUJnamNWQVFRRA0KQWdFQU1CMEdBMVVkRGdRV0JCUjJuRFowRTlHUWZXRmZzd0xyZ1BTWlM2VStoVEJVQmdOVkhTQUVUVEJMTUVrRw0KQkZVZElBQXdRVEEvQmdnckJnRUZCUWNDQVJZemFIUjBjRG92TDNkM2R5NXRhV055YjNOdlpuUXVZMjl0TDNCcg0KYVc5d2N5OUViMk56TDFKbGNHOXphWFJ2Y25rdWFIUnRNQmtHQ1NzR0FRUUJnamNVQWdRTUhnb0FVd0IxQUdJQQ0KUXdCQk1CSUdBMVVkRXdFQi93UUlNQVlCQWY4Q0FRQXdId1lEVlIwakJCZ3dGb0FVMlVFcHNBOFBZMnp2YWRmMQ0KelNtZXBFaHFNT1l3Y0FZRFZSMGZCR2t3WnpCbG9HT2dZWVpmYUhSMGNEb3ZMM2QzZHk1dGFXTnliM052Wm5RdQ0KWTI5dEwzQnJhVzl3Y3k5amNtd3ZUV2xqY205emIyWjBKVEl3U1VRbE1qQldaWEpwWm1sbFpDVXlNRU52WkdVbA0KTWpCVGFXZHVhVzVuSlRJd1VFTkJKVEl3TWpBeU1TNWpjbXd3Z2E0R0NDc0dBUVVGQndFQkJJR2hNSUdlTUcwRw0KQ0NzR0FRVUZCekFDaG1Gb2RIUndPaTh2ZDNkM0xtMXBZM0p2YzI5bWRDNWpiMjB2Y0d0cGIzQnpMMk5sY25Seg0KTDAxcFkzSnZjMjltZENVeU1FbEVKVEl3Vm1WeWFXWnBaV1FsTWpCRGIyUmxKVEl3VTJsbmJtbHVaeVV5TUZCRA0KUVNVeU1ESXdNakV1WTNKME1DMEdDQ3NHQVFVRkJ6QUJoaUZvZEhSd09pOHZiMjVsYjJOemNDNXRhV055YjNOdg0KWm5RdVkyOXRMMjlqYzNBd0RRWUpLb1pJaHZjTkFRRU1CUUFEZ2dJQkFHb3ZDWi9Zc0hWQ05TQnJRYnZNV1JzWg0KM3cwRkFzYy9RbzRVRlkwa1ZtSk8wcCtrN1J0blpaK3pxL20rb2dxTVRmWkRvenowYmhtUlZ5OWE0UUFENTIrTQ0KdE9GTEx6MWpULytiOVpOSXJCaTJKSFVUQ2Z2SFdURDhXRDNmQkNtellMVlpTUDdUVC9xNDJzWDUzZ3hVbkZYVQ0KZWdFZ1A3M2xraGJRcVNwbWltYzREakRtOC9oUGx3R210bEFDVS8rOHdiSUhRZjM2a2MyalNOUDFEeUI4b2szTQ0KZEwyTFVPQUdhYTU4WjFiMU1ISzZlandZQ0xNVXlFdVVpelR4dm1XS1VpUVRuUGNVd0JRQ3Y1ZUFnalVVMW1kdg0KamM0anBCM2JNNktOdU5oKzZ1eGRRSTBjTDVGTEFrYWJsUXZNL0taaUNDY242U0VrNnJ1aEtXbzhhbHV2dlNFWQ0KRjQvRDhudithWktxbnVGT0MzU1krS1JMV0xocW56SDQvZko2WmhLR2NXdUJYWHZuWk1qNEN6cjB0K0F1MkdRaA0KTzkvdHNVY0h5K1lpRnAxa0k1TFM5TUxIY0g3ODVWd1F3czA3WnNuUTcyS1J6VW1wSFFXK3JIdWNEQXhGS0hjVg0KV3FpeURNRnRhZFdSQW1ydWhZWEF4VjhVaGlmb3M5Rmt5M2p5N3FJeFFJVUZJOTEydzhEL3FUem1ZUy83VHhUbA0KWUpEdkoyUFVwVlhaTWV0Ny95WXNlSjZiM0IvOExPaUdwR2UzRXpZVC9INDBmTHBNRXlkSTlCR3FHRTErNDZCUQ0KTUJZUmlhVXo5a2NabzhodnZFNjk5WEl0RC91WHBoK2lCUGQ2bTNDbmdZNFpHTWZuUDZBYjJTa0VqSHhDdEdYbw0KNktXZVhGRVRHaVNZeCtVdnVYWFpNSUlIbmpDQ0JZYWdBd0lCQWdJVE13QUFBQWVIb3pTamU2V09IQUFBQUFBQQ0KQnpBTkJna3Foa2lHOXcwQkFRd0ZBREIzTVFzd0NRWURWUVFHRXdKVlV6RWVNQndHQTFVRUNoTVZUV2xqY205eg0KYjJaMElFTnZjbkJ2Y21GMGFXOXVNVWd3UmdZRFZRUURFejlOYVdOeWIzTnZablFnU1dSbGJuUnBkSGtnVm1WeQ0KYVdacFkyRjBhVzl1SUZKdmIzUWdRMlZ5ZEdsbWFXTmhkR1VnUVhWMGFHOXlhWFI1SURJd01qQXdIaGNOTWpFdw0KTkRBeE1qQXdOVEl3V2hjTk16WXdOREF4TWpBeE5USXdXakJqTVFzd0NRWURWUVFHRXdKVlV6RWVNQndHQTFVRQ0KQ2hNVlRXbGpjbTl6YjJaMElFTnZjbkJ2Y21GMGFXOXVNVFF3TWdZRFZRUURFeXROYVdOeWIzTnZablFnU1VRZw0KVm1WeWFXWnBaV1FnUTI5a1pTQlRhV2R1YVc1bklGQkRRU0F5TURJeE1JSUNJakFOQmdrcWhraUc5dzBCQVFFRg0KQUFPQ0FnOEFNSUlDQ2dLQ0FnRUFzdkRBcnhtSUtPTGRWSHBNU1d4cENGVUp0RkwvZWtyNHdlc2xLUGRuRjNjcA0KVGV1Vjh2ZXF0bUtWZ29rMnJPMEQwNUJweXZVRENnMXdkc29FdHV4QUNFR2NnSGZqUEYvblpzT2tnN2MwbVY4aA0KcE1UL0d2QjR1aER2V1hNSWVRUHNEZ0N6VUd6VHZvaTc2WURweERPeGhnZjhKdVhXSnpCRG9Mcm10VGhYMDFDRQ0KMVRDQ3ZIMnNaRC8rSHozUkR3bDJNc3ZEU2RYNXJKRFlWdVIzYmphajJRZnpaRm13ZmNjVEtxTUFIbHJ6NEI3YQ0KYzhnOXp5eGxUcGtUdUpHdEZuTEJHYXNvT25uNU55WWxmMHhGOS9ialZSbzRHemcyWWM3S1I3eWhUVk5pdVRHSA0KNWg0ZUI5YWptMU9DU2hJeWhyS3FnT2tjNHNtejZvYnhPK0h4S2VKOWJZbVBmNktMWFZOTHo4VWFlQVJvMEJhdA0Kdko4MnNMcjJncWxGQmRqMXNZZnFPZjAwUW0vM0I0WEdGUERLL0gwNGt0ZVpFWnNCUmMzVlQyZC9pVmQ3T1RMcA0KU0g5eUNPUlYzb0laUUIvUXI0bkQ0WVQvbFdraFZ0dzJ2MnMwVG5SSnViTC9oRk1JUWE4NnJjYUdNaE5zSnJoeQ0Kc0xOTk1lQmhpTWV6VTFzNXpwdXNmNTRxbFl1MnY1c1o1ekwwS3ZCRExIdEw4RjlnbjZqT3kzdjdKbTBiYkJIag0Kclc1eVFXN1MzNkFMQXQwM1FEcHdXMUpHMUh4dS9GVVhKYkJPMkF3d1ZHNEZyZStaUTVPZDhvdXd0NTlGcEJ4Vg0KT0JHZk40dk4ybTNmWngxZ3FuNTJHdmFpQno2b3pvcmdJRWpuK1BoVVhJTGhBVjVRL1pnQ0owdTIrbGRGR2pjQw0KQXdFQUFhT0NBalV3Z2dJeE1BNEdBMVVkRHdFQi93UUVBd0lCaGpBUUJna3JCZ0VFQVlJM0ZRRUVBd0lCQURBZA0KQmdOVkhRNEVGZ1FVMlVFcHNBOFBZMnp2YWRmMXpTbWVwRWhxTU9Zd1ZBWURWUjBnQkUwd1N6QkpCZ1JWSFNBQQ0KTUVFd1B3WUlLd1lCQlFVSEFnRVdNMmgwZEhBNkx5OTNkM2N1YldsamNtOXpiMlowTG1OdmJTOXdhMmx2Y0hNdg0KUkc5amN5OVNaWEJ2YzJsMGIzSjVMbWgwYlRBWkJna3JCZ0VFQVlJM0ZBSUVEQjRLQUZNQWRRQmlBRU1BUVRBUA0KQmdOVkhSTUJBZjhFQlRBREFRSC9NQjhHQTFVZEl3UVlNQmFBRk1oKzBtcUZLaHZLR1pnRUJ5ZlBVQkJQYUtpaQ0KTUlHRUJnTlZIUjhFZlRCN01IbWdkNkIxaG5Ob2RIUndPaTh2ZDNkM0xtMXBZM0p2YzI5bWRDNWpiMjB2Y0d0cA0KYjNCekwyTnliQzlOYVdOeWIzTnZablFsTWpCSlpHVnVkR2wwZVNVeU1GWmxjbWxtYVdOaGRHbHZiaVV5TUZKdg0KYjNRbE1qQkRaWEowYVdacFkyRjBaU1V5TUVGMWRHaHZjbWwwZVNVeU1ESXdNakF1WTNKc01JSERCZ2dyQmdFRg0KQlFjQkFRU0J0akNCc3pDQmdRWUlLd1lCQlFVSE1BS0dkV2gwZEhBNkx5OTNkM2N1YldsamNtOXpiMlowTG1Odg0KYlM5d2EybHZjSE12WTJWeWRITXZUV2xqY205emIyWjBKVEl3U1dSbGJuUnBkSGtsTWpCV1pYSnBabWxqWVhScA0KYjI0bE1qQlNiMjkwSlRJd1EyVnlkR2xtYVdOaGRHVWxNakJCZFhSb2IzSnBkSGtsTWpBeU1ESXdMbU55ZERBdA0KQmdnckJnRUZCUWN3QVlZaGFIUjBjRG92TDI5dVpXOWpjM0F1YldsamNtOXpiMlowTG1OdmJTOXZZM053TUEwRw0KQ1NxR1NJYjNEUUVCREFVQUE0SUNBUUIvSlNxZS90U3I2dDFtQ3R0WEkweTZYbXlRNDF1R1d6bDl4dytXWWh2Tw0KTDQ3QlYwOURnZm5tL3RVNGllZVo3TkFSNWJndW9yVENOcjU4SE9jQTF0Y3NIUXF0MHdKc2RDbHN1OGJwUUQ5ZQ0KL2FsK2xVZ1RVSkVWODBYaGNvN3hkZ1JyZWhieWhVZjRwa2VBaEJFakFCdklVcEQyTEtQaG81WjREUENUNS8wVA0KbEswMm5sUHdVYnY5VVJSRWhWWUN0c0RNKzMxT0ZVM2ZEVjhCbVFYdjVoVDJSdXJWc0pIWmdQNHkyNmRKRFZGKw0KM3BjYnR2aDdSNk5FRHVZSFlpaGZtRTJIZFFScTVqUnZMRTFFYjU5UFl3SVNGQ1gyRGFMWit6cFU0YlgwSTE2bg0KdEtxNHBvR09GYWFLdGpJQTF2UkVsSXRhT0tjd3RjMDRDQnJYU2Z5TDJPcDZtdk5JeFRrNE9hc3dJa1RYYkZMOA0KMVpLR0QrMjR1TUN3by9wTE5objdWSExmbnhsTVZ6SFFWTCtiSGE5S2hUeXp3ZEcvTDZ1ZGVySlFuMGNHcExRTQ0KU3RVdU5EQXJ4VzJ3RjE2UUdaMU50QldnS0E4S3F2NDhNOEhmRnFOaWZONit6dDZKMEd3enZVOGcwcllHZ1RaUg0KOHpERUlKZmVaeHdXREhwU3hCNUZKMVZWVTFMSUF0QjdvOVBYYmpYekdpZmFJTVlUelU0WUt0NHZNTnd3Qm1ldA0KUURIaGRBdFRQcGxPWHJuSTlTSTZIZVR0akREM2lVTi83eWdiYWhtWU9IazdWQjdmd1Q0emUrRXJDYk1oNmdIVg0KMVV1WFBpTGNpbG9OeEg2SzRhTWZaTjFvTFZrNllGZUlKRW9rdVBnTlBhNkVuVGlPTDYwY1BxZm55K0ZxOFVpdQ0KWnpDQ0Jjd3dnZ08wb0FNQ0FRSUNFRlNZMHRIVVd4bVZTQk41eUJIQWg1a3dEUVlKS29aSWh2Y05BUUVNQlFBdw0KZHpFTE1Ba0dBMVVFQmhNQ1ZWTXhIakFjQmdOVkJBb1RGVTFwWTNKdmMyOW1kQ0JEYjNKd2IzSmhkR2x2YmpGSQ0KTUVZR0ExVUVBeE0vVFdsamNtOXpiMlowSUVsa1pXNTBhWFI1SUZabGNtbG1hV05oZEdsdmJpQlNiMjkwSUVObA0KY25ScFptbGpZWFJsSUVGMWRHaHZjbWwwZVNBeU1ESXdNQjRYRFRJd01EUXhOakU0TXpZeE5sb1hEVFExTURReA0KTmpFNE5EUTBNRm93ZHpFTE1Ba0dBMVVFQmhNQ1ZWTXhIakFjQmdOVkJBb1RGVTFwWTNKdmMyOW1kQ0JEYjNKdw0KYjNKaGRHbHZiakZJTUVZR0ExVUVBeE0vVFdsamNtOXpiMlowSUVsa1pXNTBhWFI1SUZabGNtbG1hV05oZEdsdg0KYmlCU2IyOTBJRU5sY25ScFptbGpZWFJsSUVGMWRHaHZjbWwwZVNBeU1ESXdNSUlDSWpBTkJna3Foa2lHOXcwQg0KQVFFRkFBT0NBZzhBTUlJQ0NnS0NBZ0VBczVFcUI0TUdaLzJlbmVESHdMZWs1a0lFZncrbTIxLzcxVnJYUmFENw0KZHd2d2dQT21iVnBOZVZQWW9JYUVWMFVneDZKVSs4ZWl2NHJIYmpYem9oWEVMMDdqU29XV1NRMy92cG5ZRlBhOA0KSndmdVFwc3I5UXVTQnVUOWFSTmxxSkZ5OHBpRTY0TTlEdVRYY1JKSUljc043ZlpIU2JlYitjbkhGN2FFVC8rNA0KckpyWGMyZEpoZU9HdlRkQTBDV0cxTjYxd20xaWF0V3BlTHd0YjBuNTVXd1VGUDBVeDlObEZqZmV5MjY4WGltTg0KL1dLYkZTeldCZWE1aVRJem8yTEgxOVpTWndqRUx2UldLNTRMaDh6c3A3U21xdXNGelJsWHBUb0xCQ2Nja1dlZQ0KTFdJdEx4Nisyc0FneXdRWnlqUDdpYjZZNG5LZ2NqVytlZUdjZzIva2JSZHZrUE05QUlaMU9JN1E0RW1hdTl2VA0KK0RESzFWZUlhRTF5MDc5dGYzSFkvYjBOcnBKa1NMZGJiM2ttdGMyYmxTR0UwZThQTWoxN1Y0enpSUWRNZk9CZQ0KR0E0MWRvdHRuc3MyZEtzRitPQnpYVEpXbEdlWEpRckdOVDJVbCtmQlJJdUEvY0g0OUhRWjVURDJCdnNoVno0Rw0KSEl0ckZZWW5TWHVDazhwWjZIVkg2RDg0OU1kVGVhQzJ0T0pjVWUrOVh6akJFK1o0REpWYUxzVkFXU2pNRHlUQQ0KN0xvSmR5T1pPS2EySE5ySHVpQzIxemZZZnpldkNPTTdjZHR1Y3h0OW1YS3c1SVl6V1hTMUZnQjdVRzNHaGhQYQ0KL2NRNWdqMGtBSnBnMnJxVXdBVlJMRFNzVUprVGg3dXpCWUN5VFRBQ1hMZ21nMTIwWTNQdnJpT1ZUMkFvdmpmVg0KVzZVQ0F3RUFBYU5VTUZJd0RnWURWUjBQQVFIL0JBUURBZ0dHTUE4R0ExVWRFd0VCL3dRRk1BTUJBZjh3SFFZRA0KVlIwT0JCWUVGTWgrMG1xRktodktHWmdFQnlmUFVCQlBhS2lpTUJBR0NTc0dBUVFCZ2pjVkFRUURBZ0VBTUEwRw0KQ1NxR1NJYjNEUUVCREFVQUE0SUNBUUN2YXQzbUdlY3RsRU1aVHN2cFVKVmtwUU9SQW92aU5vQTdGYUpTd2hZWg0KdG1wYVhYUkRNUFNiLzJCMENiRWhIcEFXYmNVa2oxeG1pR1AwVDh4OThoSk1RQkNMQVovYXFjaXU4cFViejUwRg0KNjBrK2RLQm9XK1ZXTEdVY2duNVQybGJaUmhkNWtrWEVFRFlJVWlrWHl5K204bjdVYVNTS0hvK3djdzNNSEVxcg0Kc3FydHA1RmpBV1FpcURLNGZqSW9zMmR6TFpHMDNERUJDL2RIQ3FieDEwcnRWbURFTEFpamUwQ3d2SFFuVW9mVw0Kdm9qZE40cUpibWVJSGZYSlhhRCt0cXM2Z05jYWx6d1hOaUpCSHF4TjFZUG1QRGk5VHpEcFZLblR0Z1RETW5aaA0KdTdBWXhTc1lzOENBMWJlVnNGNVJUU0w4N0ZpcTZOaVV0S1V1N1pMZTV4aDhJVmZkVldQM3YyM05IOUttZHlodw0KeCtKYk9sc0kwbHRPeUFDV3MrR0ROcStHQ21WY2RQYnE3SHBxZEtEd1MrN3ZsS09zVVBLSDdkYzZNSVBKKzMxWA0KdnVYaitFSEs1V1N1czZQc1dPeUZtc3p2dWVyelZoaTVYSE9hcjhWM0Y0TloyemNhR0hKVXBVSFN0aU4xbzBPYQ0KNVhkOGxubTNRWTIvN055QW9KL1JkM1ZZWHpVVDRDVWFad3Q5emlYNkJ3cmtZU0hZMUJ6bEI4WTJtZlNXME1ZVg0KL2s3TjE2NkxuZHNXL1FUR2tyM1VpT2FwbzZxNzkyUTRPMS9NRE5BMXZuUVpBNmJGcWt5aVlUYUNQaDN6Szd5WA0KWGR0TGVEc3Q5VHZ2WUNQbzlld0xJemFWcjVobXYxUFRlN2hwU2lxV1ptbkVsTWIwWDI2c21IaUlnQVpjb3JMdA0Kb2pFQQ0K"
+}
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 ecb1b2bd..7ed36115 100644
--- a/jsign-maven-plugin/src/main/java/net/jsign/JsignMojo.java
+++ b/jsign-maven-plugin/src/main/java/net/jsign/JsignMojo.java
@@ -87,7 +87,7 @@ public class JsignMojo extends AbstractMojo {
/**
* The type of the keystore (JKS, JCEKS, PKCS12, PKCS11, ETOKEN, NITROKEY, OPENPGP, OPENSC, PIV, YUBIKEY, AWS,
- * AZUREKEYVAULT, DIGICERTONE, ESIGNER, GOOGLECLOUD, HASHICORPVAULT or ORACLECLOUD).
+ * AZUREKEYVAULT, DIGICERTONE, ESIGNER, GOOGLECLOUD, HASHICORPVAULT, ORACLECLOUD 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 9e90a6a1..e158fa2b 100644
--- a/jsign/src/deb/data/usr/share/bash-completion/completions/jsign
+++ b/jsign/src/deb/data/usr/share/bash-completion/completions/jsign
@@ -22,7 +22,7 @@ _jsign()
return 0
;;
--storetype)
- COMPREPLY=( $( compgen -W 'JKS JCEKS PKCS12 PKCS11 AWS AZUREKEYVAULT DIGICERTONE ESIGNER ETOKEN GOOGLECLOUD HASHICORPVAULT ORACLECLOUD YUBIKEY NITROKEY OPENPGP OPENSC PIV' -- "$cur" ) )
+ COMPREPLY=( $( compgen -W 'JKS JCEKS PKCS12 PKCS11 AWS AZUREKEYVAULT DIGICERTONE ESIGNER ETOKEN GOOGLECLOUD HASHICORPVAULT ORACLECLOUD TRUSTEDSIGNING YUBIKEY NITROKEY OPENPGP OPENSC PIV' -- "$cur" ) )
return 0
;;
--storepass|-a|--alias|--keypass|-t|--tsaurl|-r|--tsretries|-w|--tsretrywait|-n|--name|-u|--url|-e|--encoding)
diff --git a/jsign/src/deb/data/usr/share/man/man1/jsign.1 b/jsign/src/deb/data/usr/share/man/man1/jsign.1
index faa6a8bd..c78b69ac 100644
--- a/jsign/src/deb/data/usr/share/man/man1/jsign.1
+++ b/jsign/src/deb/data/usr/share/man/man1/jsign.1
@@ -75,6 +75,8 @@ The type of the keystore:
.br
- ORACLECLOUD : Oracle Cloud Key Management Service
.br
+- TRUSTEDSIGNING: Azure Trusted Signing
+.br
This option is not required for file based keystores (JKS, JCEKS and PKCS12).
.TP
@@ -315,6 +317,32 @@ The Azure account used must have the "Key Vault Crypto User" and "Key Vault Cert
.TP
+Signing with Azure Trusted Signing
+
+With the Azure Trusted Signing service the keystore parameter specifies the endpoint URI, and the alias combines
+the account name and the certificate profile. The Azure API access token is used as the keystore password.
+
+jsign --storetype TRUSTEDSIGNING \
+ --keystore weu.codesigning.azure.net \
+ --storepass \
+ --alias / application.exe
+
+The access token can be obtained with the Azure CLI:
+
+az account get-access-token --resource https://codesigning.azure.net
+
+The Azure account used must have the "Code Signing Certificate Profile Signer" role.
+
+The certificates issued by Azure Trusted Signing have a lifetime of 3 days only, and timestamping is necessary to
+ensure the long term validity of the signature. For this reason timestamping is automatically enabled when signing
+with this service.
+
+Implementation note: Jsign performs an extra call to the signing API to retrieve the current certificate chain before
+signing. When signing multiple files it's recommended to invoke Jsign only once with the list of files to avoid doubling
+the quota usage.
+
+.TP
+
Signing with DigiCert ONE:
Certificates and keys stored in the DigiCert ONE Secure Software Manager can be used directly without installing