You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by am...@apache.org on 2018/08/10 06:36:07 UTC

svn commit: r1837776 - in /jackrabbit/oak/trunk/oak-blob-plugins/src: main/java/org/apache/jackrabbit/oak/plugins/blob/datastore/directaccess/ test/java/org/apache/jackrabbit/oak/plugins/blob/datastore/directaccess/

Author: amitj
Date: Fri Aug 10 06:36:07 2018
New Revision: 1837776

URL: http://svn.apache.org/viewvc?rev=1837776&view=rev
Log:
OAK-7692: [DirectBinaryAccess] Upload token HMAC signature must be base64 encoded

Patch from Alexander Klimetschek

Added:
    jackrabbit/oak/trunk/oak-blob-plugins/src/test/java/org/apache/jackrabbit/oak/plugins/blob/datastore/directaccess/DataRecordUploadTokenTest.java   (with props)
Modified:
    jackrabbit/oak/trunk/oak-blob-plugins/src/main/java/org/apache/jackrabbit/oak/plugins/blob/datastore/directaccess/DataRecordUploadToken.java

Modified: jackrabbit/oak/trunk/oak-blob-plugins/src/main/java/org/apache/jackrabbit/oak/plugins/blob/datastore/directaccess/DataRecordUploadToken.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-blob-plugins/src/main/java/org/apache/jackrabbit/oak/plugins/blob/datastore/directaccess/DataRecordUploadToken.java?rev=1837776&r1=1837775&r2=1837776&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-blob-plugins/src/main/java/org/apache/jackrabbit/oak/plugins/blob/datastore/directaccess/DataRecordUploadToken.java (original)
+++ jackrabbit/oak/trunk/oak-blob-plugins/src/main/java/org/apache/jackrabbit/oak/plugins/blob/datastore/directaccess/DataRecordUploadToken.java Fri Aug 10 06:36:07 2018
@@ -18,6 +18,7 @@
  */
 package org.apache.jackrabbit.oak.plugins.blob.datastore.directaccess;
 
+import java.nio.charset.StandardCharsets;
 import java.security.InvalidKeyException;
 import java.security.NoSuchAlgorithmException;
 import java.time.Instant;
@@ -27,8 +28,9 @@ import javax.crypto.Mac;
 import javax.crypto.spec.SecretKeySpec;
 
 import com.google.common.base.Joiner;
+
+import org.apache.commons.codec.binary.Base64;
 import org.apache.jackrabbit.oak.spi.blob.AbstractSharedBackend;
-import org.apache.jackrabbit.util.Base64;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 import org.slf4j.Logger;
@@ -93,22 +95,22 @@ public class DataRecordUploadToken {
      */
     public static DataRecordUploadToken fromEncodedToken(@NotNull String encoded, @NotNull byte[] secret)
             throws IllegalArgumentException {
-        String[] parts = encoded.split("#", 2);
+        final String[] parts = encoded.split("#", 2);
         if (parts.length < 2) {
-            throw new IllegalArgumentException("Encoded string is missing the signature");
+            throw new IllegalArgumentException("Invalid upload token");
         }
 
-        String toBeDecoded = parts[0];
-        String expectedSig = Base64.decode(parts[1]);
-        String actualSig = getSignedString(toBeDecoded, secret);
+        final String toBeDecoded = parts[0];
+        final String expectedSig = parts[1];
+        final String actualSig = getSignedString(toBeDecoded, secret);
         if (!expectedSig.equals(actualSig)) {
-            throw new IllegalArgumentException("Upload token signature does not match");
+            throw new IllegalArgumentException("Invalid upload token");
         }
 
-        String decoded = Base64.decode(toBeDecoded);
+        String decoded = decodeBase64(toBeDecoded);
         String decodedParts[] = decoded.split("#");
         if (decodedParts.length < 2) {
-            throw new IllegalArgumentException("Not all upload token parts provided");
+            throw new IllegalArgumentException("Invalid upload token");
         }
 
         return new DataRecordUploadToken(decodedParts[0], decodedParts.length > 2 ? decodedParts[2] : null);
@@ -133,19 +135,20 @@ public class DataRecordUploadToken {
         String toBeEncoded = uploadId.isPresent() ?
                 Joiner.on("#").join(blobId, now, uploadId.get()) :
                 Joiner.on("#").join(blobId, now);
-        String toBeSigned = Base64.encode(toBeEncoded);
+        String toBeSigned = encodeBase64(toBeEncoded);
 
         String sig = getSignedString(toBeSigned, secret);
         return sig != null ? Joiner.on("#").join(toBeSigned, sig) : toBeSigned;
     }
 
+    /** Returns the base64 encoded HMAC signature */
     private static String getSignedString(String toBeSigned, byte[] secret) {
         try {
             final String algorithm = "HmacSHA1";
             Mac mac = Mac.getInstance(algorithm);
             mac.init(new SecretKeySpec(secret, algorithm));
-            byte[] hash = mac.doFinal(toBeSigned.getBytes());
-            return new String(hash);
+            byte[] hash = mac.doFinal(toBeSigned.getBytes(StandardCharsets.UTF_8));
+            return encodeBase64(hash);
         }
         catch (NoSuchAlgorithmException | InvalidKeyException e) {
             LOG.warn("Could not sign upload token", e);
@@ -153,6 +156,18 @@ public class DataRecordUploadToken {
         return null;
     }
 
+    private static String encodeBase64(String string) {
+        return Base64.encodeBase64String(string.getBytes(StandardCharsets.UTF_8));
+    }
+
+    private static String encodeBase64(byte[] bytes) {
+        return Base64.encodeBase64String(bytes);
+    }
+
+    private static String decodeBase64(String encodedString) {
+        return new String(Base64.decodeBase64(encodedString), StandardCharsets.UTF_8);
+    }
+
     /**
      * Returns the blob ID of this instance.
      *

Added: jackrabbit/oak/trunk/oak-blob-plugins/src/test/java/org/apache/jackrabbit/oak/plugins/blob/datastore/directaccess/DataRecordUploadTokenTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-blob-plugins/src/test/java/org/apache/jackrabbit/oak/plugins/blob/datastore/directaccess/DataRecordUploadTokenTest.java?rev=1837776&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-blob-plugins/src/test/java/org/apache/jackrabbit/oak/plugins/blob/datastore/directaccess/DataRecordUploadTokenTest.java (added)
+++ jackrabbit/oak/trunk/oak-blob-plugins/src/test/java/org/apache/jackrabbit/oak/plugins/blob/datastore/directaccess/DataRecordUploadTokenTest.java Fri Aug 10 06:36:07 2018
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.jackrabbit.oak.plugins.blob.datastore.directaccess;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import java.nio.charset.StandardCharsets;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.lang3.StringUtils;
+import org.junit.Test;
+
+public class DataRecordUploadTokenTest {
+
+    private static final String BLOB_ID = "blob";
+    private static final String UPLOAD_ID = "upload";
+    private static final byte[] SECRET = "1234567890".getBytes(StandardCharsets.UTF_8);
+
+    @Test
+    public void testUploadToken() {
+        String encodedToken = new DataRecordUploadToken(BLOB_ID, UPLOAD_ID).getEncodedToken(SECRET);
+
+        // also check token can be parsed and is valid
+        DataRecordUploadToken parsedToken = DataRecordUploadToken.fromEncodedToken(encodedToken, SECRET);
+        assertEquals(BLOB_ID, parsedToken.getBlobId());
+        assertTrue(parsedToken.getUploadId().isPresent());
+        assertEquals(UPLOAD_ID, parsedToken.getUploadId().get());
+    }
+
+    @Test
+    public void testUploadTokenIsAscii() {
+
+        // run a few times to rule out the (low) chance it is ascii just by chance; the seed will change regularly
+        for (int i = 0; i < 1000; i++) {
+            String encodedToken = new DataRecordUploadToken(BLOB_ID, UPLOAD_ID).getEncodedToken(SECRET);
+            assertTrue("upload token is not ascii: " + encodedToken, StringUtils.isAsciiPrintable(encodedToken));
+
+            // also check token can be parsed and is valid
+            DataRecordUploadToken parsedToken = DataRecordUploadToken.fromEncodedToken(encodedToken, SECRET);
+            assertEquals(BLOB_ID, parsedToken.getBlobId());
+            assertTrue(parsedToken.getUploadId().isPresent());
+            assertEquals(UPLOAD_ID, parsedToken.getUploadId().get());
+        }
+    }
+
+    @Test
+    public void testUploadTokenSignature() {
+        // simple test to check the signature is present and validated
+        String spoofedToken = Base64.encodeBase64String((BLOB_ID + "#" + UPLOAD_ID).getBytes(StandardCharsets.UTF_8));
+
+        try {
+            DataRecordUploadToken.fromEncodedToken(spoofedToken, SECRET);
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+}

Propchange: jackrabbit/oak/trunk/oak-blob-plugins/src/test/java/org/apache/jackrabbit/oak/plugins/blob/datastore/directaccess/DataRecordUploadTokenTest.java
------------------------------------------------------------------------------
    svn:eol-style = native