Migrate from password manager to key chain (#36)
* Update gradle Change .gitignore & clean project * Change password manager * Change password managerpull/2/head
parent
71f0f79fb9
commit
3402342531
@ -1,22 +0,0 @@ |
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<project version="4"> |
||||
<component name="CompilerConfiguration"> |
||||
<resourceExtensions /> |
||||
<wildcardResourcePatterns> |
||||
<entry name="!?*.java" /> |
||||
<entry name="!?*.form" /> |
||||
<entry name="!?*.class" /> |
||||
<entry name="!?*.groovy" /> |
||||
<entry name="!?*.scala" /> |
||||
<entry name="!?*.flex" /> |
||||
<entry name="!?*.kt" /> |
||||
<entry name="!?*.clj" /> |
||||
<entry name="!?*.aj" /> |
||||
</wildcardResourcePatterns> |
||||
<annotationProcessing> |
||||
<profile default="true" name="Default" enabled="false"> |
||||
<processorPath useClasspath="true" /> |
||||
</profile> |
||||
</annotationProcessing> |
||||
</component> |
||||
</project> |
@ -1,3 +0,0 @@ |
||||
<component name="CopyrightManager"> |
||||
<settings default="" /> |
||||
</component> |
@ -1,19 +0,0 @@ |
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<project version="4"> |
||||
<component name="GradleSettings"> |
||||
<option name="linkedExternalProjectsSettings"> |
||||
<GradleProjectSettings> |
||||
<option name="distributionType" value="DEFAULT_WRAPPED" /> |
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" /> |
||||
<option name="modules"> |
||||
<set> |
||||
<option value="$PROJECT_DIR$" /> |
||||
<option value="$PROJECT_DIR$/app" /> |
||||
<option value="$PROJECT_DIR$/tn" /> |
||||
</set> |
||||
</option> |
||||
<option name="resolveModulePerSourceSet" value="false" /> |
||||
</GradleProjectSettings> |
||||
</option> |
||||
</component> |
||||
</project> |
@ -1,36 +0,0 @@ |
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<project version="4"> |
||||
<component name="NullableNotNullManager"> |
||||
<option name="myDefaultNullable" value="android.support.annotation.Nullable" /> |
||||
<option name="myDefaultNotNull" value="android.support.annotation.NonNull" /> |
||||
<option name="myNullables"> |
||||
<value> |
||||
<list size="4"> |
||||
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" /> |
||||
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" /> |
||||
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" /> |
||||
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.Nullable" /> |
||||
</list> |
||||
</value> |
||||
</option> |
||||
<option name="myNotNulls"> |
||||
<value> |
||||
<list size="4"> |
||||
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" /> |
||||
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" /> |
||||
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" /> |
||||
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.NonNull" /> |
||||
</list> |
||||
</value> |
||||
</option> |
||||
</component> |
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK"> |
||||
<output url="file://$PROJECT_DIR$/build/classes" /> |
||||
</component> |
||||
<component name="ProjectType"> |
||||
<option name="id" value="Android" /> |
||||
</component> |
||||
<component name="SvnBranchConfigurationManager"> |
||||
<option name="mySupportsUserInfoFilter" value="true" /> |
||||
</component> |
||||
</project> |
@ -1,10 +0,0 @@ |
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<project version="4"> |
||||
<component name="ProjectModuleManager"> |
||||
<modules> |
||||
<module fileurl="file://$PROJECT_DIR$/app/app.iml" filepath="$PROJECT_DIR$/app/app.iml" /> |
||||
<module fileurl="file://$PROJECT_DIR$/tn/tn.iml" filepath="$PROJECT_DIR$/tn/tn.iml" /> |
||||
<module fileurl="file://$PROJECT_DIR$/trust-wallet-android.iml" filepath="$PROJECT_DIR$/trust-wallet-android.iml" /> |
||||
</modules> |
||||
</component> |
||||
</project> |
@ -1,12 +0,0 @@ |
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<project version="4"> |
||||
<component name="RunConfigurationProducerService"> |
||||
<option name="ignoredProducers"> |
||||
<set> |
||||
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" /> |
||||
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" /> |
||||
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" /> |
||||
</set> |
||||
</option> |
||||
</component> |
||||
</project> |
@ -1,7 +0,0 @@ |
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<project version="4"> |
||||
<component name="VcsDirectoryMappings"> |
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" /> |
||||
<mapping directory="$PROJECT_DIR$/app" vcs="svn" /> |
||||
</component> |
||||
</project> |
@ -0,0 +1,30 @@ |
||||
package com.wallet.crypto.trustapp.controller; |
||||
|
||||
import android.support.annotation.Nullable; |
||||
|
||||
public class ServiceErrorException extends Exception { |
||||
|
||||
public static final int INVALID_DATA = 1; |
||||
public static final int KEY_STORE_ERROR = 1001; |
||||
public static final int FAIL_TO_SAVE_IV_FILE = 1002; |
||||
public static final int KEY_STORE_SECRET = 1003; |
||||
public static final int USER_NOT_AUTHENTICATED = 1004; |
||||
public static final int KEY_IS_GONE = 1005; |
||||
public static final int IV_OR_ALIAS_NO_ON_DISK = 1006; |
||||
public static final int INVALID_KEY = 1007; |
||||
|
||||
public final int code; |
||||
|
||||
public ServiceErrorException(int code, @Nullable String message, Throwable throwable) { |
||||
super(message, throwable); |
||||
this.code = code; |
||||
} |
||||
|
||||
public ServiceErrorException(int code, @Nullable String message) { |
||||
this(code, message, null); |
||||
} |
||||
|
||||
public ServiceErrorException(int code) { |
||||
this(code, null); |
||||
} |
||||
} |
@ -0,0 +1,308 @@ |
||||
package com.wallet.crypto.trustapp.util; |
||||
|
||||
import android.app.Activity; |
||||
import android.app.KeyguardManager; |
||||
import android.content.Context; |
||||
import android.content.Intent; |
||||
import android.security.keystore.KeyGenParameterSpec; |
||||
import android.security.keystore.KeyProperties; |
||||
import android.security.keystore.UserNotAuthenticatedException; |
||||
import android.util.Log; |
||||
|
||||
import com.wallet.crypto.trustapp.R; |
||||
import com.wallet.crypto.trustapp.controller.ServiceErrorException; |
||||
|
||||
import java.io.ByteArrayOutputStream; |
||||
import java.io.File; |
||||
import java.io.FileInputStream; |
||||
import java.io.FileNotFoundException; |
||||
import java.io.FileOutputStream; |
||||
import java.io.IOException; |
||||
import java.io.InputStream; |
||||
import java.security.InvalidAlgorithmParameterException; |
||||
import java.security.InvalidKeyException; |
||||
import java.security.KeyStore; |
||||
import java.security.KeyStoreException; |
||||
import java.security.NoSuchAlgorithmException; |
||||
import java.security.UnrecoverableKeyException; |
||||
import java.security.cert.CertificateException; |
||||
|
||||
import javax.crypto.Cipher; |
||||
import javax.crypto.CipherInputStream; |
||||
import javax.crypto.CipherOutputStream; |
||||
import javax.crypto.KeyGenerator; |
||||
import javax.crypto.NoSuchPaddingException; |
||||
import javax.crypto.SecretKey; |
||||
import javax.crypto.spec.IvParameterSpec; |
||||
|
||||
import static com.wallet.crypto.trustapp.controller.ServiceErrorException.INVALID_KEY; |
||||
import static com.wallet.crypto.trustapp.controller.ServiceErrorException.IV_OR_ALIAS_NO_ON_DISK; |
||||
import static com.wallet.crypto.trustapp.controller.ServiceErrorException.KEY_IS_GONE; |
||||
import static com.wallet.crypto.trustapp.controller.ServiceErrorException.KEY_STORE_ERROR; |
||||
import static com.wallet.crypto.trustapp.controller.ServiceErrorException.USER_NOT_AUTHENTICATED; |
||||
|
||||
public class KS { |
||||
private static final String TAG = "KS"; |
||||
|
||||
private static final String ANDROID_KEY_STORE = "AndroidKeyStore"; |
||||
private static final String BLOCK_MODE = KeyProperties.BLOCK_MODE_CBC; |
||||
private static final int AUTH_DURATION_SEC = 600; |
||||
private static final String PADDING = KeyProperties.ENCRYPTION_PADDING_PKCS7; |
||||
private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS7Padding"; |
||||
|
||||
private synchronized static boolean setData( |
||||
Context context, |
||||
byte[] data, |
||||
String alias, |
||||
String aliasFile, |
||||
String aliasIV) throws ServiceErrorException { |
||||
if (data == null) { |
||||
throw new ServiceErrorException( |
||||
ServiceErrorException.INVALID_DATA, "keystore insert data is null"); |
||||
} |
||||
KeyStore keyStore; |
||||
try { |
||||
keyStore = KeyStore.getInstance(ANDROID_KEY_STORE); |
||||
keyStore.load(null); |
||||
// Create the keys if necessary
|
||||
if (!keyStore.containsAlias(alias)) { |
||||
KeyGenerator keyGenerator = KeyGenerator.getInstance( |
||||
KeyProperties.KEY_ALGORITHM_AES, |
||||
ANDROID_KEY_STORE); |
||||
|
||||
// Set the alias of the entry in Android KeyStore where the key will appear
|
||||
// and the constrains (purposes) in the constructor of the Builder
|
||||
keyGenerator.init(new KeyGenParameterSpec.Builder( |
||||
alias, |
||||
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) |
||||
.setBlockModes(BLOCK_MODE) |
||||
.setKeySize(256) |
||||
.setUserAuthenticationRequired(true) |
||||
.setUserAuthenticationValidityDurationSeconds(AUTH_DURATION_SEC) |
||||
.setRandomizedEncryptionRequired(true) |
||||
.setEncryptionPaddings(PADDING) |
||||
.build()); |
||||
keyGenerator.generateKey(); |
||||
} |
||||
String encryptedDataFilePath = getFilePath(context, aliasFile); |
||||
SecretKey secret = (SecretKey) keyStore.getKey(alias, null); |
||||
if (secret == null) { |
||||
throw new ServiceErrorException( |
||||
ServiceErrorException.KEY_STORE_SECRET, |
||||
"secret is null on setData: " + alias); |
||||
} |
||||
Cipher inCipher = Cipher.getInstance(CIPHER_ALGORITHM); |
||||
inCipher.init(Cipher.ENCRYPT_MODE, secret); |
||||
byte[] iv = inCipher.getIV(); |
||||
String path = getFilePath(context, aliasIV); |
||||
boolean success = writeBytesToFile(path, iv); |
||||
if (!success) { |
||||
keyStore.deleteEntry(alias); |
||||
throw new ServiceErrorException( |
||||
ServiceErrorException.FAIL_TO_SAVE_IV_FILE, |
||||
"Failed to save the iv file for: " + alias); |
||||
} |
||||
CipherOutputStream cipherOutputStream = null; |
||||
try { |
||||
cipherOutputStream = new CipherOutputStream( |
||||
new FileOutputStream(encryptedDataFilePath), |
||||
inCipher); |
||||
cipherOutputStream.write(data); |
||||
} catch (Exception ex) { |
||||
throw new ServiceErrorException( |
||||
ServiceErrorException.KEY_STORE_ERROR, |
||||
"Failed to save the file for: " + alias); |
||||
} finally { |
||||
if (cipherOutputStream != null) { |
||||
cipherOutputStream.close(); |
||||
} |
||||
} |
||||
return true; |
||||
} catch (UserNotAuthenticatedException e) { |
||||
throw new ServiceErrorException(USER_NOT_AUTHENTICATED); |
||||
} catch (ServiceErrorException ex) { |
||||
Log.d(TAG, "Key store error", ex); |
||||
throw ex; |
||||
} catch (Exception ex) { |
||||
Log.d(TAG, "Key store error", ex); |
||||
throw new ServiceErrorException(KEY_STORE_ERROR); |
||||
} |
||||
} |
||||
|
||||
private synchronized static byte[] getData( |
||||
final Context context, |
||||
String alias, |
||||
String aliasFile, |
||||
String aliasIV) |
||||
throws ServiceErrorException { |
||||
KeyStore keyStore; |
||||
String encryptedDataFilePath = getFilePath(context, aliasFile); |
||||
try { |
||||
keyStore = KeyStore.getInstance(ANDROID_KEY_STORE); |
||||
keyStore.load(null); |
||||
SecretKey secretKey = (SecretKey) keyStore.getKey(alias, null); |
||||
if (secretKey == null) { |
||||
/* no such key, the key is just simply not there */ |
||||
boolean fileExists = new File(encryptedDataFilePath).exists(); |
||||
if (!fileExists) { |
||||
return null;/* file also not there, fine then */ |
||||
} |
||||
throw new ServiceErrorException( |
||||
KEY_IS_GONE, |
||||
"file is present but the key is gone: " + alias); |
||||
} |
||||
|
||||
boolean ivExists = new File(getFilePath(context, aliasIV)).exists(); |
||||
boolean aliasExists = new File(getFilePath(context, aliasFile)).exists(); |
||||
if (!ivExists || !aliasExists) { |
||||
removeAliasAndFiles(context, alias, aliasFile, aliasIV); |
||||
//report it if one exists and not the other.
|
||||
if (ivExists != aliasExists) { |
||||
throw new ServiceErrorException( |
||||
IV_OR_ALIAS_NO_ON_DISK, |
||||
"file is present but the key is gone: " + alias); |
||||
} else { |
||||
throw new ServiceErrorException( |
||||
IV_OR_ALIAS_NO_ON_DISK, |
||||
"!ivExists && !aliasExists: " + alias); |
||||
} |
||||
} |
||||
|
||||
byte[] iv = readBytesFromFile(getFilePath(context, aliasIV)); |
||||
if (iv == null || iv.length == 0) { |
||||
throw new NullPointerException("iv is missing for " + alias); |
||||
} |
||||
Cipher outCipher = Cipher.getInstance(CIPHER_ALGORITHM); |
||||
outCipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv)); |
||||
CipherInputStream cipherInputStream = new CipherInputStream(new FileInputStream(encryptedDataFilePath), outCipher); |
||||
return readBytesFromStream(cipherInputStream); |
||||
} catch (InvalidKeyException e) { |
||||
if (e instanceof UserNotAuthenticatedException) { |
||||
// showAuthenticationScreen(context, requestCode);
|
||||
throw new ServiceErrorException(USER_NOT_AUTHENTICATED); |
||||
} else { |
||||
throw new ServiceErrorException(INVALID_KEY); |
||||
} |
||||
} catch (IOException | CertificateException | KeyStoreException | UnrecoverableKeyException | NoSuchAlgorithmException | NoSuchPaddingException | InvalidAlgorithmParameterException e) { |
||||
throw new ServiceErrorException(KEY_STORE_ERROR); |
||||
} |
||||
} |
||||
|
||||
private synchronized static String getFilePath(Context context, String fileName) { |
||||
return new File(context.getFilesDir(), fileName).getAbsolutePath(); |
||||
} |
||||
|
||||
private static boolean writeBytesToFile(String path, byte[] data) { |
||||
FileOutputStream fos = null; |
||||
try { |
||||
File file = new File(path); |
||||
fos = new FileOutputStream(file); |
||||
// Writes bytes from the specified byte array to this file output stream
|
||||
fos.write(data); |
||||
return true; |
||||
} catch (FileNotFoundException e) { |
||||
System.out.println("File not found" + e); |
||||
} catch (IOException ioe) { |
||||
System.out.println("Exception while writing file " + ioe); |
||||
} finally { |
||||
// close the streams using close method
|
||||
try { |
||||
if (fos != null) { |
||||
fos.close(); |
||||
} |
||||
} catch (IOException ioe) { |
||||
System.out.println("Error while closing stream: " + ioe); |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
public synchronized static void removeAliasAndFiles(Context context, String alias, String dataFileName, String ivFileName) { |
||||
KeyStore keyStore; |
||||
try { |
||||
keyStore = KeyStore.getInstance(ANDROID_KEY_STORE); |
||||
keyStore.load(null); |
||||
keyStore.deleteEntry(alias); |
||||
new File(getFilePath(context, dataFileName)).delete(); |
||||
new File(getFilePath(context, ivFileName)).delete(); |
||||
} catch (KeyStoreException | CertificateException | NoSuchAlgorithmException | IOException e) { |
||||
e.printStackTrace(); |
||||
} |
||||
} |
||||
|
||||
public static byte[] readBytesFromStream(InputStream in) { |
||||
// this dynamically extends to take the bytes you read
|
||||
ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream(); |
||||
// this is storage overwritten on each iteration with bytes
|
||||
int bufferSize = 1024; |
||||
byte[] buffer = new byte[bufferSize]; |
||||
// we need to know how may bytes were read to write them to the byteBuffer
|
||||
int len; |
||||
try { |
||||
while ((len = in.read(buffer)) != -1) { |
||||
byteBuffer.write(buffer, 0, len); |
||||
} |
||||
} catch (IOException e) { |
||||
e.printStackTrace(); |
||||
} finally { |
||||
try { |
||||
byteBuffer.close(); |
||||
} catch (IOException e) { |
||||
e.printStackTrace(); |
||||
} |
||||
if (in != null) try { |
||||
in.close(); |
||||
} catch (IOException e) { |
||||
e.printStackTrace(); |
||||
} |
||||
} |
||||
// and then we can return your byte array.
|
||||
return byteBuffer.toByteArray(); |
||||
} |
||||
|
||||
private static byte[] readBytesFromFile(String path) { |
||||
byte[] bytes = null; |
||||
FileInputStream fin; |
||||
try { |
||||
File file = new File(path); |
||||
fin = new FileInputStream(file); |
||||
bytes = readBytesFromStream(fin); |
||||
} catch (IOException e) { |
||||
e.printStackTrace(); |
||||
} |
||||
return bytes; |
||||
} |
||||
|
||||
public static boolean put(Context context, String address, String password) throws ServiceErrorException { |
||||
return setData(context, password.getBytes(), address, address, address+"iv"); |
||||
} |
||||
|
||||
public static byte[] get(Context context, String address) throws ServiceErrorException { |
||||
return getData(context, address, address, address+"iv"); |
||||
} |
||||
|
||||
public static void showAuthenticationScreen(Context context, int requestCode) { |
||||
// Create the Confirm Credentials screen. You can customize the title and description. Or
|
||||
// we will provide a generic one for you if you leave it null
|
||||
Log.e(TAG, "showAuthenticationScreen: "); |
||||
if (context instanceof Activity) { |
||||
Activity app = (Activity) context; |
||||
KeyguardManager mKeyguardManager = (KeyguardManager) app.getSystemService(Context.KEYGUARD_SERVICE); |
||||
if (mKeyguardManager == null) { |
||||
return; |
||||
} |
||||
Intent intent = mKeyguardManager |
||||
.createConfirmDeviceCredentialIntent( |
||||
context.getString(R.string.unlock_screen_title_android), |
||||
context.getString(R.string.unlock_screen_prompt_android)); |
||||
if (intent != null) { |
||||
app.startActivityForResult(intent, requestCode); |
||||
} else { |
||||
Log.e(TAG, "showAuthenticationScreen: failed to create intent for auth"); |
||||
app.finish(); |
||||
} |
||||
} else { |
||||
Log.e(TAG, "showAuthenticationScreen: context is not activity!"); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,31 @@ |
||||
package com.wallet.crypto.trustapp.util; |
||||
|
||||
import android.content.Context; |
||||
import android.content.SharedPreferences; |
||||
import android.preference.PreferenceManager; |
||||
|
||||
import com.wallet.crypto.trustapp.controller.ServiceErrorException; |
||||
import com.wallet.pwd.trustapp.PasswordManager; |
||||
|
||||
import java.util.Map; |
||||
|
||||
public class PMMigrateHelper { |
||||
public static void migrate(Context context) throws ServiceErrorException { |
||||
SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context); |
||||
Map<String, ?> passwords = pref.getAll(); |
||||
for (String key : passwords.keySet()) { |
||||
if (key.contains("-pwd")) { |
||||
String address = key.replace("-pwd", ""); |
||||
try { |
||||
KS.put(context, address.toLowerCase(), PasswordManager.getPassword(address, context)); |
||||
pref.edit().remove(key).apply(); |
||||
} catch (ServiceErrorException ex) { |
||||
throw ex; |
||||
} catch (Exception ex) { |
||||
ex.printStackTrace(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
} |
||||
} |
@ -0,0 +1,27 @@ |
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<FrameLayout |
||||
xmlns:android="http://schemas.android.com/apk/res/android" |
||||
android:padding="32dp" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="match_parent" |
||||
> |
||||
<LinearLayout |
||||
android:layout_gravity="center" |
||||
android:orientation="horizontal" |
||||
android:layout_width="wrap_content" |
||||
android:layout_height="wrap_content" |
||||
> |
||||
<ProgressBar |
||||
android:layout_width="wrap_content" |
||||
android:layout_height="wrap_content" |
||||
/> |
||||
<TextView |
||||
android:text="@string/sending_progress" |
||||
android:layout_gravity="center_vertical" |
||||
android:layout_marginLeft="8dp" |
||||
android:layout_marginEnd="8dp" |
||||
android:layout_width="wrap_content" |
||||
android:layout_height="wrap_content" |
||||
/> |
||||
</LinearLayout> |
||||
</FrameLayout> |
Loading…
Reference in new issue