Skip to content

Commit

Permalink
Support higher signing algorithms
Browse files Browse the repository at this point in the history
  • Loading branch information
Berenz committed Nov 27, 2019
1 parent 822a169 commit 580a386
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 14 deletions.
57 changes: 57 additions & 0 deletions js/qz-tray.js
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,9 @@ var qz = (function() {
_qz.websocket.pendingCalls[obj.uid] = obj.promise;
}

//ensure we know how this was signed
obj.signAlgorithm = _qz.security.signAlgorithm;

// track requesting monitor
obj.position = {
x: screen ? ((screen.availWidth || screen.width) / 2) + (screen.left || screen.availLeft) : 0,
Expand Down Expand Up @@ -241,6 +244,7 @@ var qz = (function() {
}).then(function(signature) {
_qz.log.trace("Signature for call", signature);
obj.signature = signature;

_qz.signContent = undefined;
_qz.websocket.connection.send(_qz.tools.stringify(obj));
});
Expand Down Expand Up @@ -334,6 +338,9 @@ var qz = (function() {
//websocket setup, query what version is connected
qz.api.getVersion().then(function(version) {
_qz.websocket.connection.version = version;

//algorithm can be declared before a connection, check for incompatibilities now that we have one
_qz.security.isAlgorithmCompatible();
});
},

Expand Down Expand Up @@ -513,6 +520,22 @@ var qz = (function() {
/** Called to create new promise (using {@link _qz.security.signaturePromise}) for signed calls. */
callSign: function(toSign) {
return _qz.tools.promise(_qz.security.signaturePromise(toSign));
},

/** Signing algorithm used on signatures */
signAlgorithm: "SHA1",
/** Check if QZ version supports chosen algorithm */
isAlgorithmCompatible: function() {
//if not connected yet we will assume compatibility exists for the time being
if (_qz.websocket.connection) {
var semver = _qz.websocket.connection.version.split(/[.-]/g);
if (semver[0] === "2" && semver[1] === "0") {
_qz.log.warn("Connected to an older version of QZ, alternate signature algorithms are not supported");
return false;
}
}

return true;
}
},

Expand Down Expand Up @@ -1988,10 +2011,44 @@ var qz = (function() {
*
* @param {Function} promiseGen <code>Function({function} toSign)</code> Should return a function, <code>Function({function} resolve)</code>, that
* will sign the content and resolve the created promise.
*
* @memberof qz.security
*/
setSignaturePromise: function(promiseGen) {
_qz.security.signaturePromise = promiseGen;
},

/**
* Set which signing algorithm QZ will check signatures against.
*
* @param {string} algorithm The algorithm used in signing. Valid values: <code>[SHA1 | SHA256 | SHA512]</code>
* @since 2.1.0
*
* @memberof qz.security
*/
setSignatureAlgorithm: function(algorithm) {
//warn for incompatibilities if known
if (!_qz.security.isAlgorithmCompatible()) {
return;
}

if (["SHA1", "SHA256", "SHA512"].indexOf(algorithm.toUpperCase()) < 0) {
_qz.log.error("Signing algorithm '" + algorithm + "' is not supported.");
} else {
_qz.security.signAlgorithm = algorithm;
}
},

/**
* Get the signing algorithm QZ will be checking signatures against.
*
* @returns {string} The algorithm used in signing.
* @since 2.1.0
*
* @memberof qz.security
*/
getSignatureAlgorithm: function() {
return _qz.security.signAlgorithm;
}
},

Expand Down
Binary file removed lib/websocket/simplersa.jar
Binary file not shown.
36 changes: 23 additions & 13 deletions src/qz/auth/Certificate.java
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
package qz.auth;

import com.estontorise.simplersa.RSAKeyImpl;
import com.estontorise.simplersa.RSAToolFactory;
import com.estontorise.simplersa.interfaces.RSAKey;
import com.estontorise.simplersa.interfaces.RSATool;
import org.apache.commons.codec.binary.StringUtils;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.ssl.Base64;
import org.apache.commons.ssl.X509CertificateChainBuilder;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.x509.X509Name;
import org.bouncycastle.jce.PrincipalUtil;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import qz.common.Constants;
Expand All @@ -20,8 +17,7 @@

import javax.security.cert.CertificateParsingException;
import java.io.*;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.*;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
Expand All @@ -43,6 +39,18 @@ public class Certificate {

private static final Logger log = LoggerFactory.getLogger(Certificate.class);

public enum Algorithm {
SHA1("SHA1withRSA"),
SHA256("SHA256withRSA"),
SHA512("SHA512withRSA");

String name;

Algorithm(String name) {
this.name = name;
}
}

public static Certificate trustedRootCert = null;
public static final String[] saveFields = new String[] {"fingerprint", "commonName", "organization", "validFrom", "validTo", "valid"};

Expand Down Expand Up @@ -83,6 +91,8 @@ public class Certificate {

static {
try {
Security.addProvider(new BouncyCastleProvider());

String overridePath;
Properties trayProperties = PrintSocketServer.getTrayProperties();
if (trayProperties != null && trayProperties.containsKey("authcert.override")) {
Expand Down Expand Up @@ -278,17 +288,17 @@ public static Certificate loadCertificate(HashMap<String,String> data) {
* @param data the data to check
* @return true if signature valid, false if not
*/
public boolean isSignatureValid(String signature, String data) {
public boolean isSignatureValid(Algorithm algorithm, String signature, String data) {
if (!signature.isEmpty()) {
RSATool tool = RSAToolFactory.getRSATool();
RSAKey thePublicKey = new RSAKeyImpl(theCertificate.getPublicKey());

//On errors, assume failure.
try {
String hash = DigestUtils.sha256Hex(data);
return tool.verifyWithKey(StringUtils.getBytesUtf8(hash), Base64.decodeBase64(signature), thePublicKey);
Signature verifier = Signature.getInstance(algorithm.name);
verifier.initVerify(theCertificate.getPublicKey());
verifier.update(StringUtils.getBytesUtf8(DigestUtils.sha256Hex(data)));

return verifier.verify(Base64.decodeBase64(signature));
}
catch(Exception e) {
catch(GeneralSecurityException e) {
log.error("Unable to verify signature", e);
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/qz/ws/PrintSocketClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -249,8 +249,9 @@ public void onMessage(Session session, Reader reader) throws IOException {
private boolean validSignature(Certificate certificate, JSONObject message) throws JSONException {
JSONObject copy = new JSONObject(message, new String[] {"call", "params", "timestamp"});
String signature = message.optString("signature");
String algorithm = message.optString("signAlgorithm", "SHA1").toUpperCase();

return certificate.isSignatureValid(signature, copy.toString().replaceAll("\\\\/", "/"));
return certificate.isSignatureValid(Certificate.Algorithm.valueOf(algorithm), signature, copy.toString().replaceAll("\\\\/", "/"));
}

/**
Expand Down

0 comments on commit 580a386

Please sign in to comment.