Эх сурвалжийг харах

use hashed/salted token

Signed-off-by: tobiasKaminsky <tobias@kaminsky.me>
tobiasKaminsky 6 жил өмнө
parent
commit
c73ffe0e0c

+ 14 - 1
src/androidTest/java/com/owncloud/android/util/EncryptionTestIT.java

@@ -258,6 +258,19 @@ public class EncryptionTestIT {
         }
     }
 
+    @Test
+    public void testSHA512() {
+        // sent to 3rd party app in cleartext
+        String token = "4ae5978bf5354cd284b539015d442141";
+        String salt = EncryptionUtils.encodeBytesToBase64String(EncryptionUtils.randomBytes(EncryptionUtils.saltLength));
+
+        // stored in database
+        String hashedToken = EncryptionUtils.generateSHA512(token, salt);
+
+        // check: use passed cleartext and salt to verify hashed token
+        assertTrue(EncryptionUtils.verifySHA512(hashedToken, token));
+    }
+
 
     // Helper
     private boolean compareJsonStrings(String expected, String actual) {
@@ -362,4 +375,4 @@ public class EncryptionTestIT {
 
         return temp;
     }
-}
+}

+ 6 - 7
src/main/java/com/nextcloud/android/sso/InputStreamBinder.java

@@ -33,8 +33,8 @@ import android.util.Log;
 import com.nextcloud.android.sso.aidl.IInputStreamService;
 import com.nextcloud.android.sso.aidl.NextcloudRequest;
 import com.nextcloud.android.sso.aidl.ParcelFileDescriptorUtil;
+import com.owncloud.android.authentication.AccountAuthenticator;
 import com.owncloud.android.authentication.AccountUtils;
-import com.owncloud.android.db.PreferenceManager;
 import com.owncloud.android.lib.common.OwnCloudAccount;
 import com.owncloud.android.lib.common.OwnCloudClient;
 import com.owncloud.android.lib.common.OwnCloudClientManager;
@@ -217,12 +217,11 @@ public class InputStreamBinder extends IInputStreamService.Stub {
     }
 
     private boolean isValid(NextcloudRequest request) {
-        if(request.getPackageName() == null) {
-            String callingPackageName = context.getPackageManager().getNameForUid(Binder.getCallingUid());
-            request.setPackageName(callingPackageName);
-        }
-        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
-        String storedToken = sharedPreferences.getString(request.getPackageName(), "");
+        String callingPackageName = context.getPackageManager().getNameForUid(Binder.getCallingUid());
+
+        SharedPreferences sharedPreferences = context.getSharedPreferences(AccountAuthenticator.SSO_SHARED_PREFERENCE,
+                Context.MODE_PRIVATE);
+        String storedToken = sharedPreferences.getString(callingPackageName, "");
         return request.validateToken(storedToken);
     }
 }

+ 7 - 6
src/main/java/com/nextcloud/android/sso/aidl/NextcloudRequest.java

@@ -19,6 +19,8 @@
 
 package com.nextcloud.android.sso.aidl;
 
+import com.owncloud.android.utils.EncryptionUtils;
+
 import java.io.Serializable;
 import java.util.HashMap;
 import java.util.List;
@@ -81,11 +83,6 @@ public class NextcloudRequest implements Serializable {
             return this;
         }
 
-        public Builder setPackageName(String packageName) {
-            ncr.packageName = packageName;
-            return this;
-        }
-
         public Builder setAccountName(String accountName) {
             ncr.accountName = accountName;
             return this;
@@ -151,9 +148,13 @@ public class NextcloudRequest implements Serializable {
     }
 
     public boolean validateToken(String token) {
+        String salt = this.token.split("\\$")[1]; // TODO extract "$"
+
+        String newHash = EncryptionUtils.generateSHA512(token, salt);
+
         // As discussed with Lukas R. at the Nextcloud Conf 2018, always compare whole strings
         // and don't exit prematurely if the string does not match anymore to prevent timing-attacks
-        return isEqual(this.token.getBytes(), token.getBytes());
+        return isEqual(this.token.getBytes(), newHash.getBytes());
     }
 
     // Taken from http://codahale.com/a-lesson-in-timing-attacks/

+ 12 - 12
src/main/java/com/owncloud/android/authentication/AccountAuthenticator.java

@@ -36,11 +36,11 @@ import android.widget.Toast;
 import com.nextcloud.android.sso.Constants;
 import com.owncloud.android.MainApp;
 import com.owncloud.android.R;
-import com.owncloud.android.db.PreferenceManager;
 import com.owncloud.android.lib.common.OwnCloudAccount;
 import com.owncloud.android.lib.common.accounts.AccountTypeUtils;
 import com.owncloud.android.lib.common.accounts.AccountUtils;
 import com.owncloud.android.lib.common.utils.Log_OC;
+import com.owncloud.android.utils.EncryptionUtils;
 
 import java.util.UUID;
 
@@ -64,6 +64,8 @@ public class AccountAuthenticator extends AbstractAccountAuthenticator {
     public static final String KEY_REQUIRED_FEATURES = "requiredFeatures";
     public static final String KEY_LOGIN_OPTIONS = "loginOptions";
     public static final String KEY_ACCOUNT = "account";
+    public static final String SSO_SHARED_PREFERENCE = "sso";
+    
     private static final String NEXTCLOUD_SSO = "NextcloudSSO";
     
     private static final String TAG = AccountAuthenticator.class.getSimpleName();
@@ -171,17 +173,16 @@ public class AccountAuthenticator extends AbstractAccountAuthenticator {
                 return result;
             }
 
-            // get or create token
-            SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(mContext);
-            String token = sharedPreferences.getString(packageName, "");
+            // create token
+            SharedPreferences sharedPreferences = mContext.getSharedPreferences(SSO_SHARED_PREFERENCE,
+                    Context.MODE_PRIVATE);
+            String token = UUID.randomUUID().toString().replaceAll("-", "");
 
-            if (token.isEmpty()) {
-                token = UUID.randomUUID().toString().replaceAll("-", "");
+            String hashedTokenWithSalt = EncryptionUtils.generateSHA512(token);
 
-                SharedPreferences.Editor editor = sharedPreferences.edit();
-                editor.putString(packageName, token);
-                editor.apply();
-            }
+            SharedPreferences.Editor editor = sharedPreferences.edit();
+            editor.putString(packageName, hashedTokenWithSalt);
+            editor.apply();
                         
             String serverUrl;
             String userId;
@@ -206,8 +207,7 @@ public class AccountAuthenticator extends AbstractAccountAuthenticator {
             return result;
         }
 
-
-        /// validate parameters
+        // validate parameters
         try {
             validateAccountType(account.type);
             validateAuthTokenType(authTokenType);

+ 77 - 29
src/main/java/com/owncloud/android/utils/EncryptionUtils.java

@@ -57,7 +57,6 @@ import java.security.KeyPair;
 import java.security.KeyPairGenerator;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
 import java.security.PrivateKey;
 import java.security.PublicKey;
 import java.security.SecureRandom;
@@ -65,7 +64,6 @@ import java.security.cert.CertificateException;
 import java.security.cert.CertificateFactory;
 import java.security.cert.X509Certificate;
 import java.security.spec.InvalidKeySpecException;
-import java.security.spec.InvalidParameterSpecException;
 import java.security.spec.KeySpec;
 import java.security.spec.PKCS8EncodedKeySpec;
 import java.util.ArrayList;
@@ -80,7 +78,6 @@ import javax.crypto.KeyGenerator;
 import javax.crypto.NoSuchPaddingException;
 import javax.crypto.SecretKey;
 import javax.crypto.SecretKeyFactory;
-import javax.crypto.ShortBufferException;
 import javax.crypto.spec.GCMParameterSpec;
 import javax.crypto.spec.IvParameterSpec;
 import javax.crypto.spec.PBEKeySpec;
@@ -98,6 +95,7 @@ public class EncryptionUtils {
     public static final String MNEMONIC = "MNEMONIC";
     public static final int ivLength = 16;
     public static final int saltLength = 40;
+    public static final String HASH_DELIMITER = "$";
 
     private static final String ivDelimiter = "fA=="; // "|" base64 encoded
     private static final int iterationCount = 1024;
@@ -132,9 +130,9 @@ public class EncryptionUtils {
     @RequiresApi(api = Build.VERSION_CODES.KITKAT)
     public static EncryptedFolderMetadata encryptFolderMetadata(DecryptedFolderMetadata decryptedFolderMetadata,
                                                                 String privateKey)
-            throws IOException, NoSuchAlgorithmException, ShortBufferException, InvalidKeyException,
+            throws NoSuchAlgorithmException, InvalidKeyException,
             InvalidAlgorithmParameterException, NoSuchPaddingException, BadPaddingException,
-            NoSuchProviderException, IllegalBlockSizeException, InvalidKeySpecException, CertificateException {
+            IllegalBlockSizeException, InvalidKeySpecException {
 
         HashMap<String, EncryptedFolderMetadata.EncryptedFile> files = new HashMap<>();
         EncryptedFolderMetadata encryptedFolderMetadata = new EncryptedFolderMetadata(decryptedFolderMetadata
@@ -171,9 +169,9 @@ public class EncryptionUtils {
     @RequiresApi(api = Build.VERSION_CODES.KITKAT)
     public static DecryptedFolderMetadata decryptFolderMetaData(EncryptedFolderMetadata encryptedFolderMetadata,
                                                                 String privateKey)
-            throws IOException, NoSuchAlgorithmException, ShortBufferException, InvalidKeyException,
+            throws NoSuchAlgorithmException, InvalidKeyException,
             InvalidAlgorithmParameterException, NoSuchPaddingException, BadPaddingException,
-            NoSuchProviderException, IllegalBlockSizeException, CertificateException, InvalidKeySpecException {
+            IllegalBlockSizeException, InvalidKeySpecException {
 
         HashMap<String, DecryptedFolderMetadata.DecryptedFile> files = new HashMap<>();
         DecryptedFolderMetadata decryptedFolderMetadata = new DecryptedFolderMetadata(
@@ -278,9 +276,9 @@ public class EncryptionUtils {
      */
     @RequiresApi(api = Build.VERSION_CODES.KITKAT)
     public static EncryptedFile encryptFile(OCFile ocFile, byte[] encryptionKeyBytes, byte[] iv)
-            throws NoSuchProviderException, NoSuchAlgorithmException,
+            throws NoSuchAlgorithmException,
             InvalidAlgorithmParameterException, NoSuchPaddingException, InvalidKeyException,
-            BadPaddingException, IllegalBlockSizeException, IOException, ShortBufferException {
+            BadPaddingException, IllegalBlockSizeException, IOException {
         File file = new File(ocFile.getStoragePath());
 
         return encryptFile(file, encryptionKeyBytes, iv);
@@ -294,9 +292,9 @@ public class EncryptionUtils {
      */
     @RequiresApi(api = Build.VERSION_CODES.KITKAT)
     public static EncryptedFile encryptFile(File file, byte[] encryptionKeyBytes, byte[] iv)
-            throws NoSuchProviderException, NoSuchAlgorithmException,
+            throws NoSuchAlgorithmException,
             InvalidAlgorithmParameterException, NoSuchPaddingException, InvalidKeyException,
-            BadPaddingException, IllegalBlockSizeException, IOException, ShortBufferException {
+            BadPaddingException, IllegalBlockSizeException, IOException {
 
         Cipher cipher = Cipher.getInstance(AES_CIPHER);
 
@@ -325,9 +323,9 @@ public class EncryptionUtils {
      */
     @RequiresApi(api = Build.VERSION_CODES.KITKAT)
     public static byte[] decryptFile(File file, byte[] encryptionKeyBytes, byte[] iv, byte[] authenticationTag)
-            throws NoSuchProviderException, NoSuchAlgorithmException,
+            throws NoSuchAlgorithmException,
             InvalidAlgorithmParameterException, NoSuchPaddingException, InvalidKeyException,
-            BadPaddingException, IllegalBlockSizeException, IOException, ShortBufferException {
+            BadPaddingException, IllegalBlockSizeException, IOException {
 
 
         Cipher cipher = Cipher.getInstance(AES_CIPHER);
@@ -370,9 +368,9 @@ public class EncryptionUtils {
      */
     @RequiresApi(api = Build.VERSION_CODES.KITKAT)
     public static String encryptStringAsymmetric(String string, String cert)
-            throws NoSuchProviderException, NoSuchAlgorithmException,
-            InvalidAlgorithmParameterException, NoSuchPaddingException, InvalidKeyException,
-            BadPaddingException, IllegalBlockSizeException, IOException, ShortBufferException, InvalidKeySpecException,
+            throws NoSuchAlgorithmException,
+            NoSuchPaddingException, InvalidKeyException,
+            BadPaddingException, IllegalBlockSizeException, IOException,
             CertificateException {
 
         Cipher cipher = Cipher.getInstance(RSA_CIPHER);
@@ -406,9 +404,9 @@ public class EncryptionUtils {
      */
     @RequiresApi(api = Build.VERSION_CODES.KITKAT)
     public static String decryptStringAsymmetric(String string, String privateKeyString)
-            throws NoSuchProviderException, NoSuchAlgorithmException,
-            InvalidAlgorithmParameterException, NoSuchPaddingException, InvalidKeyException,
-            BadPaddingException, IllegalBlockSizeException, IOException, ShortBufferException, CertificateException,
+            throws NoSuchAlgorithmException,
+            NoSuchPaddingException, InvalidKeyException,
+            BadPaddingException, IllegalBlockSizeException,
             InvalidKeySpecException {
 
         Cipher cipher = Cipher.getInstance(RSA_CIPHER);
@@ -437,10 +435,9 @@ public class EncryptionUtils {
      */
     @RequiresApi(api = Build.VERSION_CODES.KITKAT)
     public static String encryptStringSymmetric(String string, byte[] encryptionKeyBytes)
-            throws NoSuchProviderException, NoSuchAlgorithmException,
+            throws NoSuchAlgorithmException,
             InvalidAlgorithmParameterException, NoSuchPaddingException, InvalidKeyException,
-            BadPaddingException, IllegalBlockSizeException, IOException, ShortBufferException, InvalidKeySpecException,
-            CertificateException {
+            BadPaddingException, IllegalBlockSizeException {
 
         Cipher cipher = Cipher.getInstance(AES_CIPHER);
         byte[] iv = randomBytes(ivLength);
@@ -469,10 +466,9 @@ public class EncryptionUtils {
      */
     @RequiresApi(api = Build.VERSION_CODES.KITKAT)
     public static String decryptStringSymmetric(String string, byte[] encryptionKeyBytes)
-            throws NoSuchProviderException, NoSuchAlgorithmException,
+            throws NoSuchAlgorithmException,
             InvalidAlgorithmParameterException, NoSuchPaddingException, InvalidKeyException,
-            BadPaddingException, IllegalBlockSizeException, IOException, ShortBufferException, CertificateException,
-            InvalidKeySpecException {
+            BadPaddingException, IllegalBlockSizeException {
 
         Cipher cipher = Cipher.getInstance(AES_CIPHER);
 
@@ -502,8 +498,8 @@ public class EncryptionUtils {
      * @return encrypted string, bytes first encoded base64, IV separated with "|", then to string
      */
     public static String encryptPrivateKey(String privateKey, String keyPhrase) throws NoSuchPaddingException,
-            NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException, BadPaddingException,
-            IllegalBlockSizeException, InvalidKeySpecException, InvalidParameterSpecException {
+            NoSuchAlgorithmException, InvalidKeyException, BadPaddingException,
+            IllegalBlockSizeException, InvalidKeySpecException {
         Cipher cipher = Cipher.getInstance(AES_CIPHER);
 
         SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
@@ -533,7 +529,7 @@ public class EncryptionUtils {
      * @return decrypted string
      */
     public static String decryptPrivateKey(String privateKey, String keyPhrase) throws NoSuchPaddingException,
-            NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException, BadPaddingException,
+            NoSuchAlgorithmException, InvalidKeyException, BadPaddingException,
             IllegalBlockSizeException, InvalidKeySpecException, InvalidAlgorithmParameterException {
 
         // split up iv, salt
@@ -559,7 +555,7 @@ public class EncryptionUtils {
                 .replace("-----END PRIVATE KEY-----", "");
     }
 
-    public static String privateKeyToPEM(PrivateKey privateKey) throws IOException {
+    public static String privateKeyToPEM(PrivateKey privateKey) {
         String privateKeyString = encodeBytesToBase64String(privateKey.getEncoded());
 
         return "-----BEGIN PRIVATE KEY-----\n" + privateKeyString.replaceAll("(.{65})", "$1\n")
@@ -642,4 +638,56 @@ public class EncryptionUtils {
 
         return iv;
     }
+
+    /**
+     * Generate a SHA512 with appended salt
+     *
+     * @param token token to be hashed
+     * @return SHA512 with appended salt, delimiter HASH_DELIMITER
+     */
+    public static String generateSHA512(String token) {
+        String salt = EncryptionUtils.encodeBytesToBase64String(EncryptionUtils.randomBytes(EncryptionUtils.saltLength));
+
+        return generateSHA512(token, salt);
+    }
+
+    /**
+     * Generate a SHA512 with appended salt
+     *
+     * @param token token to be hashed
+     * @return SHA512 with appended salt, delimiter HASH_DELIMITER
+     */
+    public static String generateSHA512(String token, String salt) {
+        MessageDigest digest;
+        String hashedToken = "";
+        byte[] hash;
+        try {
+            digest = MessageDigest.getInstance("SHA-512");
+            digest.update(salt.getBytes());
+            hash = digest.digest(token.getBytes());
+
+            StringBuilder stringBuilder = new StringBuilder();
+            for (byte hashByte : hash) {
+                stringBuilder.append(Integer.toString((hashByte & 0xff) + 0x100, 16).substring(1));
+            }
+
+            stringBuilder.append(HASH_DELIMITER);
+            stringBuilder.append(salt);
+
+            hashedToken = stringBuilder.toString();
+
+        } catch (NoSuchAlgorithmException e) {
+            Log_OC.e(TAG, "Generating SHA512 failed", e);
+        }
+
+        return hashedToken;
+    }
+
+    public static boolean verifySHA512(String hashWithSalt, String compareToken) {
+        String salt = hashWithSalt.split("\\" + HASH_DELIMITER)[1];
+
+        String newHash = generateSHA512(compareToken, salt);
+
+        return hashWithSalt.equals(newHash);
+    }
 }