Merge pull request #5 from artem-smotrakov/better-signature-validation

SLING-10700: Improve TopologyRequestValidator code
diff --git a/src/main/java/org/apache/sling/discovery/base/connectors/ping/TopologyRequestValidator.java b/src/main/java/org/apache/sling/discovery/base/connectors/ping/TopologyRequestValidator.java
index fa70321..3a8118d 100644
--- a/src/main/java/org/apache/sling/discovery/base/connectors/ping/TopologyRequestValidator.java
+++ b/src/main/java/org/apache/sling/discovery/base/connectors/ping/TopologyRequestValidator.java
@@ -314,7 +314,7 @@
     }
 
     /**
-     * @param body
+     * @param toHash A string
      * @return a hash of body base64 encoded.
      */
     private String hash(String toHash) {
@@ -363,10 +363,13 @@
                 return false;
             }
             String[] parts = signature.split("/", 2);
+            if (parts.length < 2) {
+                return false;
+            }
             int keyNo = Integer.parseInt(parts[0]);
-            return hmac(keyNo, bodyHash).equals(parts[1]);
-        } catch (ArrayIndexOutOfBoundsException e) {
-            return false;
+            return MessageDigest.isEqual(
+                hmac(keyNo, bodyHash).getBytes("UTF-8"),
+                parts[1].getBytes("UTF-8"));
         } catch (IllegalArgumentException e) {
             return false;
         } catch (InvalidKeyException e) {
@@ -428,13 +431,12 @@
      * @throws NoSuchPaddingException
      * @throws InvalidKeySpecException
      * @throws InvalidAlgorithmParameterException
-     * @throws JSONException
      */
     private String decrypt(JsonArray jsonArray) throws IllegalBlockSizeException,
             BadPaddingException, UnsupportedEncodingException, InvalidKeyException,
             NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, InvalidKeySpecException {
         Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
-        cipher.init(Cipher.DECRYPT_MODE, getCiperKey(Base64.decodeBase64(jsonArray.get(0).toString().getBytes("UTF-8"))), new IvParameterSpec(Base64.decodeBase64(jsonArray.get(1).toString().getBytes("UTF-8"))));
+        cipher.init(Cipher.DECRYPT_MODE, getCipherKey(Base64.decodeBase64(jsonArray.get(0).toString().getBytes("UTF-8"))), new IvParameterSpec(Base64.decodeBase64(jsonArray.get(1).toString().getBytes("UTF-8"))));
         return new String(cipher.doFinal(Base64.decodeBase64(jsonArray.get(2).toString().getBytes("UTF-8"))));
     }
 
@@ -442,7 +444,6 @@
      * Encrypt a payload with the numbed key/
      *
      * @param payload the payload.
-     * @param keyNo the key number.
      * @return an encrypted version.
      * @throws IllegalBlockSizeException
      * @throws BadPaddingException
@@ -459,7 +460,7 @@
         Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
         byte[] salt = new byte[9];
         random.nextBytes(salt);
-        cipher.init(Cipher.ENCRYPT_MODE, getCiperKey(salt));
+        cipher.init(Cipher.ENCRYPT_MODE, getCipherKey(salt));
         AlgorithmParameters params = cipher.getParameters();
         List<String> encrypted = new ArrayList<String>();
         encrypted.add(new String(Base64.encodeBase64(salt)));
@@ -470,19 +471,18 @@
 
     /**
      * @param salt number of the key.
-     * @return the CupherKey.
-     * @throws UnsupportedEncodingException
+     * @return the CipherKey.
      * @throws NoSuchAlgorithmException
      * @throws InvalidKeySpecException
      */
-    private Key getCiperKey(byte[] salt) throws UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeySpecException {
-        SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
-        // hashing the password 65K times takes 151ms, hashing 256 times takes 2ms.
-        // Since the salt has 2^^72 values, 256 times is probably good enough.
-        KeySpec spec = new PBEKeySpec(sharedKey.toCharArray(), salt, 256, 128);
+    private Key getCipherKey(byte[] salt) throws NoSuchAlgorithmException, InvalidKeySpecException {
+        // RFC 2898 suggests the salt to be at least 64 bits long
+        // The NIST guidelines suggest the iteration count to be at least 10000
+        // Using longer hashes (SHA-256 and higher) increases the attacker's costs
+        SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
+        KeySpec spec = new PBEKeySpec(sharedKey.toCharArray(), salt, 10000, 128);
         SecretKey tmp = factory.generateSecret(spec);
-        SecretKey key = new SecretKeySpec(tmp.getEncoded(), "AES");
-        return key;
+        return new SecretKeySpec(tmp.getEncoded(), "AES");
     }
 
     /**
@@ -552,9 +552,11 @@
         if (contentEncoding!=null && contentEncoding.contains("gzip")) {
             // then treat the request body as gzip:
             final GZIPInputStream gzipIn = new GZIPInputStream(request.getInputStream());
-            final String gunzippedEncodedJson = IOUtils.toString(gzipIn);
-            gzipIn.close();
-            return gunzippedEncodedJson;
+            try {
+                return IOUtils.toString(gzipIn);
+            } finally {
+                gzipIn.close();
+            }
         } else {
             // otherwise assume plain-text:
             return IOUtils.toString(request.getReader());
@@ -572,9 +574,11 @@
                 contentEncoding.getValue().contains("gzip")) {
             // then the server sent gzip - treat it so:
             final GZIPInputStream gzipIn = new GZIPInputStream(response.getEntity().getContent());
-            final String gunzippedEncodedJson = IOUtils.toString(gzipIn);
-            gzipIn.close();
-            return gunzippedEncodedJson;
+            try {
+                return IOUtils.toString(gzipIn);
+            } finally {
+                gzipIn.close();
+            }
         } else {
         	// otherwise the server sent plaintext:
         	return IOUtils.toString(response.getEntity().getContent(), "UTF-8");