Skip to content

Commit

Permalink
Add server-side hashing for SignServer support
Browse files Browse the repository at this point in the history
  • Loading branch information
Vampire committed Nov 18, 2024
1 parent 6acd186 commit b260600
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 20 deletions.
24 changes: 18 additions & 6 deletions docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -887,17 +887,20 @@ <h4 id="example-hashicorpvault">Signing with HashiCorp Vault</h4>

<h4 id="example-signserver">Signing with Keyfactor SignServer</h4>

<p><a href="https://www.signserver.org">SignServer</a> is an on-premises open source signing service developed by Keyfactor.
<p><a href="https://www.signserver.org">SignServer</a> is an on-premises (or cloud) open source signing service developed by Keyfactor.
SignServer supports various signing operations handled by signer workers. Jsign requires a
<a href="https://doc.primekey.com/signserver/signserver-reference/signserver-workers/signserver-signers/plain-signer">Plain Signer</a>
worker configured with the <code>CLIENTSIDEHASHING</code> or <code>ALLOW_CLIENTSIDEHASHING_OVERRIDE</code> properties
worker. It is preferred to be configured with the <code>CLIENTSIDEHASHING</code> or <code>ALLOW_CLIENTSIDEHASHING_OVERRIDE</code> properties
set to <code>true</code>, and the <code>SIGNATUREALGORITHM</code> property set to <code>NONEwithRSA</code> or
<code>NONEwithECDSA</code>.</p>
<code>NONEwithECDSA</code>. If this is for example not possible like when using a Nitrokey HSM 2, the <code>CLIENTSIDEHASHING</code> must
not be set to <code>true</code> or <code>ALLOW_CLIENTSIDEHASHING_OVERRIDE</code> set to <code>true</code>, a proper
<code>SIGNATUREALGORITHM</code> set, and the worker name or id in the alias has to be suffixed with <code>|serverside</code>. </p>

<p>The authentication is performed by specifying the username/password or the TLS client certificate in the
<code>storepass</code> parameter. If the TLS client certificate is stored in a password protected keystore, the password
is specified in the <code>keypass</code> parameter. The <code>keystore</code> parameter references the URL of the
SignServer REST API. The <code>alias</code> parameter specifies the id or the name of the worker. </p>
<code>storepass</code> parameter if authentication is necessary. If the TLS client certificate is stored in
a password protected keystore, the password is specified in the <code>keypass</code> parameter.
The <code>keystore</code> parameter references the URL of the SignServer REST API. The <code>alias</code> parameter
specifies the id or the name of the worker. </p>

<p>Authenticating with a username and a password:</p>

Expand All @@ -920,6 +923,15 @@ <h4 id="example-signserver">Signing with Keyfactor SignServer</h4>
application.exe
</pre>

<p>Using server-side hashing:</p>

<pre>
jsign --storetype SIGNSERVER \
--keystore https://example.com/signserver \
--alias 'test|serverside' \
application.exe
</pre>


<h4 id="example-oraclecloud">Signing with Oracle Cloud Key Management Service</h4>

Expand Down
15 changes: 10 additions & 5 deletions jsign-crypto/src/main/java/net/jsign/KeyStoreType.java
Original file line number Diff line number Diff line change
Expand Up @@ -561,14 +561,19 @@ Provider getProvider(KeyStoreBuilder params) {
},

/**
* Keyfactor SignServer. This keystore requires a Plain Signer worker configured to allow client-side hashing (with
* the properties <code>CLIENTSIDEHASHING</code> or <code>ALLOW_CLIENTSIDEHASHING_OVERRIDE</code> set to true), and
* Keyfactor SignServer. This keystore requires a Plain Signer worker. It is preferred to be configured to allow
* client-side hashing (with the properties <code>CLIENTSIDEHASHING</code> or
* <code>ALLOW_CLIENTSIDEHASHING_OVERRIDE</code> set to <code>true</code>), and
* the <code>SIGNATUREALGORITHM</code> property set to <code>NONEwithRSA</code> or <code>NONEwithECDSA</code>.
* If this is for example not possible like when using a Nitrokey HSM 2, the <code>CLIENTSIDEHASHING</code> must
* not be set to <code>true</code> or <code>ALLOW_CLIENTSIDEHASHING_OVERRIDE</code> set to <code>true</code>,
* a proper <code>SIGNATUREALGORITHM</code> set, and the worker name or id in the alias has to be suffixed
* with <code>|serverside</code>.
*
* <p>The authentication is performed by specifying the username/password or the TLS client certificate in the
* storepass parameter. If the TLS client certificate is stored in a password protected keystore, the password is
* specified in the keypass parameter. The keystore parameter references the URL of the SignServer REST API. The
* alias parameter specifies the id or the name of the worker.</p>
* storepass parameter if authentication is necessary. If the TLS client certificate is stored in a password
* protected keystore, the password is specified in the keypass parameter. The keystore parameter references
* the URL of the SignServer REST API. The alias parameter specifies the id or the name of the worker.</p>
*/
SIGNSERVER(false, false) {
@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,29 @@ public List<String> aliases() throws KeyStoreException {
public Certificate[] getCertificateChain(String alias) throws KeyStoreException {
if (!certificates.containsKey(alias)) {
try {
Map<String, ?> response = client.post("/rest/v1/workers/" + alias + "/process", "{\"data\":\"\"}");
String worker = alias;
boolean serverside = false;
if (worker.endsWith("|serverside")) {
worker = worker.substring(0, worker.length() - 11);
serverside = true;
}

Map<String, Object> request = new HashMap<>();
if (serverside) {
request.put("data", "");
Map<String, String> metadata = new HashMap<>();
metadata.put("USING_CLIENTSUPPLIED_HASH", "false");
request.put("metaData", metadata);
} else {
request.put("data", "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=");
request.put("encoding", "BASE64");
Map<String, String> metadata = new HashMap<>();
metadata.put("USING_CLIENTSUPPLIED_HASH", "true");
metadata.put("CLIENTSIDE_HASHDIGESTALGORITHM", "SHA-256");
request.put("metaData", metadata);
}

Map<String, ?> response = client.post("/rest/v1/workers/" + worker + "/process", JsonWriter.format(request));
String encodedCertificate = response.get("signerCertificate").toString();
byte[] certificateBytes = Base64.getDecoder().decode(encodedCertificate);
Certificate certificate = CertificateFactory.getInstance("X.509")
Expand All @@ -120,19 +142,32 @@ public SigningServicePrivateKey getPrivateKey(String alias, char[] password) thr

@Override
public byte[] sign(SigningServicePrivateKey privateKey, String algorithm, byte[] data) throws GeneralSecurityException {
DigestAlgorithm digestAlgorithm = DigestAlgorithm.of(algorithm.substring(0, algorithm.toLowerCase().indexOf("with")));
data = digestAlgorithm.getMessageDigest().digest(data);
String worker = privateKey.getId();
boolean serverside = false;
if (worker.endsWith("|serverside")) {
worker = worker.substring(0, worker.length() - 11);
serverside = true;
}

Map<String, Object> request = new HashMap<>();
request.put("data", Base64.getEncoder().encodeToString(data));
if (serverside) {
request.put("data", Base64.getEncoder().encodeToString(data));
Map<String, String> metadata = new HashMap<>();
metadata.put("USING_CLIENTSUPPLIED_HASH", "false");
request.put("metaData", metadata);
} else {
DigestAlgorithm digestAlgorithm = DigestAlgorithm.of(algorithm.substring(0, algorithm.toLowerCase().indexOf("with")));
data = digestAlgorithm.getMessageDigest().digest(data);
request.put("data", Base64.getEncoder().encodeToString(data));
Map<String, String> metadata = new HashMap<>();
metadata.put("USING_CLIENTSUPPLIED_HASH", "true");
metadata.put("CLIENTSIDE_HASHDIGESTALGORITHM", digestAlgorithm.id);
request.put("metaData", metadata);
}
request.put("encoding", "BASE64");
Map<String, String> metadata = new HashMap<>();
metadata.put("USING_CLIENTSUPPLIED_HASH", "true");
metadata.put("CLIENTSIDE_HASHDIGESTALGORITHM", digestAlgorithm.id);
request.put("metaData", metadata);

try {
Map<String, ?> response = client.post("/rest/v1/workers/" + privateKey.getId() + "/process", JsonWriter.format(request));
Map<String, ?> response = client.post("/rest/v1/workers/" + worker + "/process", JsonWriter.format(request));
return Base64.getDecoder().decode((String) response.get("data"));
} catch (IOException e) {
throw new GeneralSecurityException(e);
Expand Down

0 comments on commit b260600

Please sign in to comment.