mirror of
https://github.com/nextcloud/android.git
synced 2024-11-26 07:05:49 +03:00
use hashed/salted token
Signed-off-by: tobiasKaminsky <tobias@kaminsky.me>
This commit is contained in:
parent
415afeb4e3
commit
c73ffe0e0c
5 changed files with 116 additions and 55 deletions
|
@ -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) {
|
||||
|
|
|
@ -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(), "");
|
||||
|
||||
SharedPreferences sharedPreferences = context.getSharedPreferences(AccountAuthenticator.SSO_SHARED_PREFERENCE,
|
||||
Context.MODE_PRIVATE);
|
||||
String storedToken = sharedPreferences.getString(callingPackageName, "");
|
||||
return request.validateToken(storedToken);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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/
|
||||
|
|
|
@ -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.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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue