mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Merge branch 'master' into develop (Add fingerprint)
This commit is contained in:
17
.gitignore
vendored
17
.gitignore
vendored
@@ -1,3 +1,8 @@
|
|||||||
|
.DS_Store
|
||||||
|
/build
|
||||||
|
/app/build
|
||||||
|
/projectFilesBackup
|
||||||
|
|
||||||
# Built application files
|
# Built application files
|
||||||
*.apk
|
*.apk
|
||||||
*.ap_
|
*.ap_
|
||||||
@@ -34,6 +39,7 @@ captures/
|
|||||||
|
|
||||||
# Intellij
|
# Intellij
|
||||||
*.iml
|
*.iml
|
||||||
|
<<<<<<< HEAD
|
||||||
.idea/workspace.xml
|
.idea/workspace.xml
|
||||||
.idea/tasks.xml
|
.idea/tasks.xml
|
||||||
.idea/gradle.xml
|
.idea/gradle.xml
|
||||||
@@ -43,12 +49,19 @@ captures/
|
|||||||
# Keystore files
|
# Keystore files
|
||||||
# Uncomment the following line if you do not want to check your keystore files in.
|
# Uncomment the following line if you do not want to check your keystore files in.
|
||||||
#*.jks
|
#*.jks
|
||||||
|
=======
|
||||||
|
.idea/*
|
||||||
|
|
||||||
|
# Keystore files
|
||||||
|
*.jks
|
||||||
|
>>>>>>> master
|
||||||
|
|
||||||
# External native build folder generated in Android Studio 2.2 and later
|
# External native build folder generated in Android Studio 2.2 and later
|
||||||
.externalNativeBuild
|
.externalNativeBuild
|
||||||
|
|
||||||
# Google Services (e.g. APIs or Firebase)
|
# Google Services (e.g. APIs or Firebase)
|
||||||
google-services.json
|
google-services.json
|
||||||
|
<<<<<<< HEAD
|
||||||
|
|
||||||
# Freeline
|
# Freeline
|
||||||
freeline.py
|
freeline.py
|
||||||
@@ -56,4 +69,6 @@ freeline/
|
|||||||
freeline_project_description.json
|
freeline_project_description.json
|
||||||
|
|
||||||
# Iml Files
|
# Iml Files
|
||||||
app/app.iml
|
app/app.iml
|
||||||
|
=======
|
||||||
|
>>>>>>> master
|
||||||
|
|||||||
@@ -1,4 +1,12 @@
|
|||||||
|
KeePassDroid (2.2.0.0)
|
||||||
|
* Add Fingerprint integration
|
||||||
|
|
||||||
|
KeePassDroid (2.1.0.0)
|
||||||
|
* Add support for KDBXv4 (used in mainline KeePass >= 2.35)
|
||||||
* Updated Lithuanian translations
|
* Updated Lithuanian translations
|
||||||
|
* Updated Russian translations
|
||||||
|
* Updated French translations
|
||||||
|
* Add Samsung multi-window support
|
||||||
|
|
||||||
KeePassDroid (2.0.6.4)
|
KeePassDroid (2.0.6.4)
|
||||||
* Expose Storage Access Framework option
|
* Expose Storage Access Framework option
|
||||||
|
|||||||
@@ -12,13 +12,15 @@ Nicholas FitzRoy-Dale - auto launch intents
|
|||||||
yulin2 - responsiveness improvements
|
yulin2 - responsiveness improvements
|
||||||
Tadashi Saito
|
Tadashi Saito
|
||||||
vhschlenker
|
vhschlenker
|
||||||
|
bumper314 - Samsung multiwindow support
|
||||||
|
Hans Cappelle - fingerprint sensor integration
|
||||||
Jeremy Jamet - Material Design - Patches
|
Jeremy Jamet - Material Design - Patches
|
||||||
|
|
||||||
Translations:
|
Translations:
|
||||||
Diego Pierotto - Italian
|
Diego Pierotto - Italian
|
||||||
Laurent, Norman Obry, Nam, Bruno Parmentier - French
|
Laurent, Norman Obry, Nam, Bruno Parmentier, Credomo - French
|
||||||
Maciej Bieniek, cod3r - Polish
|
Maciej Bieniek, cod3r - Polish
|
||||||
Максим Сёмочкин, i.nedoboy, filimonic - Russian
|
Максим Сёмочкин, i.nedoboy, filimonic, bboa - Russian
|
||||||
MaWi, rvs2008, meviox, MaDill - German
|
MaWi, rvs2008, meviox, MaDill - German
|
||||||
yslandro - Norwegian Nynorsk
|
yslandro - Norwegian Nynorsk
|
||||||
王科峰 - Chinese
|
王科峰 - Chinese
|
||||||
|
|||||||
19
LICENSE
19
LICENSE
@@ -381,3 +381,22 @@ from Tavmjong Bah. For further information, contact: tavmjong @ free
|
|||||||
|
|
||||||
$Id: LICENSE 2133 2007-11-28 02:46:28Z lechimp $
|
$Id: LICENSE 2133 2007-11-28 02:46:28Z lechimp $
|
||||||
|
|
||||||
|
--
|
||||||
|
ChaCha7539Engine.java
|
||||||
|
|
||||||
|
Copyright (c) 2000 - 2017 The Legion of the Bouncy Castle Inc. (https://www.bouncycastle.org)
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
||||||
|
associated documentation files (the "Software"), to deal in the Software without restriction,
|
||||||
|
including without limitation the rights to use, copy, modify, merge, publish, distribute,
|
||||||
|
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or
|
||||||
|
substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
|
||||||
|
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||||
|
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|||||||
143
app/app.iml
143
app/app.iml
@@ -1,143 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<module external.linked.project.id=":app" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$/.." external.system.id="GRADLE" external.system.module.group="KeePassDroid" external.system.module.version="unspecified" type="JAVA_MODULE" version="4">
|
|
||||||
<component name="FacetManager">
|
|
||||||
<facet type="android-gradle" name="Android-Gradle">
|
|
||||||
<configuration>
|
|
||||||
<option name="GRADLE_PROJECT_PATH" value=":app" />
|
|
||||||
</configuration>
|
|
||||||
</facet>
|
|
||||||
<facet type="android" name="Android">
|
|
||||||
<configuration>
|
|
||||||
<option name="SELECTED_BUILD_VARIANT" value="debug" />
|
|
||||||
<option name="ASSEMBLE_TASK_NAME" value="assembleDebug" />
|
|
||||||
<option name="COMPILE_JAVA_TASK_NAME" value="compileDebugSources" />
|
|
||||||
<afterSyncTasks>
|
|
||||||
<task>generateDebugSources</task>
|
|
||||||
</afterSyncTasks>
|
|
||||||
<option name="ALLOW_USER_CONFIGURATION" value="false" />
|
|
||||||
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" />
|
|
||||||
<option name="RES_FOLDER_RELATIVE_PATH" value="/src/main/res" />
|
|
||||||
<option name="RES_FOLDERS_RELATIVE_PATH" value="file://$MODULE_DIR$/src/main/res" />
|
|
||||||
<option name="ASSETS_FOLDER_RELATIVE_PATH" value="/src/main/assets" />
|
|
||||||
</configuration>
|
|
||||||
</facet>
|
|
||||||
<facet type="native-android-gradle" name="Native-Android-Gradle">
|
|
||||||
<configuration>
|
|
||||||
<option name="SELECTED_BUILD_VARIANT" value="debug" />
|
|
||||||
</configuration>
|
|
||||||
</facet>
|
|
||||||
</component>
|
|
||||||
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7">
|
|
||||||
<output url="file://$MODULE_DIR$/build/intermediates/classes/debug" />
|
|
||||||
<output-test url="file://$MODULE_DIR$/build/intermediates/classes/test/debug" />
|
|
||||||
<exclude-output />
|
|
||||||
<content url="file://$MODULE_DIR$">
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/main/jni/argon2/src" isTestSource="false" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/main/jni/argon2/src/blake2" isTestSource="false" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/main/jni/argon2" isTestSource="false" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/main/jni/final_key" isTestSource="false" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/main/jni/final_key/aes" isTestSource="false" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/main/jni/final_key/sha" isTestSource="false" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/debug" isTestSource="false" generated="true" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/debug" isTestSource="false" generated="true" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/debug" isTestSource="false" generated="true" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/debug" isTestSource="false" generated="true" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/apt/debug" isTestSource="false" generated="true" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/debug" type="java-resource" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/resValues/debug" type="java-resource" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/androidTest/debug" isTestSource="true" generated="true" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/androidTest/debug" isTestSource="true" generated="true" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/androidTest/debug" isTestSource="true" generated="true" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/androidTest/debug" isTestSource="true" generated="true" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/apt/androidTest/debug" isTestSource="true" generated="true" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/androidTest/debug" type="java-test-resource" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/resValues/androidTest/debug" type="java-test-resource" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/res" type="java-resource" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/resources" type="java-resource" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/assets" type="java-resource" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/aidl" isTestSource="false" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/java" isTestSource="false" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/jni" isTestSource="false" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/rs" isTestSource="false" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/shaders" isTestSource="false" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/res" type="java-test-resource" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/resources" type="java-test-resource" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/assets" type="java-test-resource" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/aidl" isTestSource="true" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/java" isTestSource="true" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/jni" isTestSource="true" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/rs" isTestSource="true" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/shaders" isTestSource="true" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/main/res" type="java-resource" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/main/assets" type="java-resource" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/main/aidl" isTestSource="false" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/main/jni" isTestSource="false" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/main/rs" isTestSource="false" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/main/shaders" isTestSource="false" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/test/res" type="java-test-resource" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/test/resources" type="java-test-resource" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/test/assets" type="java-test-resource" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/test/aidl" isTestSource="true" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/test/jni" isTestSource="true" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/test/rs" isTestSource="true" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/test/shaders" isTestSource="true" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/res" type="java-test-resource" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/resources" type="java-test-resource" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/assets" type="java-test-resource" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/aidl" isTestSource="true" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/java" isTestSource="true" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/jni" isTestSource="true" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/rs" isTestSource="true" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/shaders" isTestSource="true" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/assets" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/blame" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/builds" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/classes" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/cmake" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-classes" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-runtime-classes" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-safeguard" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-verifier" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/instant-run-resources" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/instant-run-support" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/jniLibs" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/manifests" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/pre-dexed" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/reload-dex" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/res" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/restart-dex" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/rs" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/shaders" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/sourceFolderJavaResources" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/split-apk" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/symbols" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/transforms" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/build/outputs" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/build/tmp" />
|
|
||||||
</content>
|
|
||||||
<orderEntry type="jdk" jdkName="Android API 25 Platform" jdkType="Android SDK" />
|
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
|
||||||
<orderEntry type="library" exported="" name="com.madgag.spongycastle:core:1.54.0.0@jar" level="project" />
|
|
||||||
<orderEntry type="library" exported="" name="com.android.support:animated-vector-drawable-25.4.0" level="project" />
|
|
||||||
<orderEntry type="library" exported="" scope="TEST" name="__local_jars__:/home/joker/Projects/KeePassDX/app/libs/junit4.jar:unspecified@jar" level="project" />
|
|
||||||
<orderEntry type="library" exported="" name="com.android.support:design-25.4.0" level="project" />
|
|
||||||
<orderEntry type="library" exported="" name="com.android.support:transition-25.4.0" level="project" />
|
|
||||||
<orderEntry type="library" exported="" name="com.android.support:support-core-ui-25.4.0" level="project" />
|
|
||||||
<orderEntry type="library" exported="" name="com.android.support:appcompat-v7-25.4.0" level="project" />
|
|
||||||
<orderEntry type="library" exported="" name="com.android.support:support-compat-25.4.0" level="project" />
|
|
||||||
<orderEntry type="library" exported="" name="com.android.support:preference-v7-25.4.0" level="project" />
|
|
||||||
<orderEntry type="library" exported="" name="joda-time:joda-time:2.9.9@jar" level="project" />
|
|
||||||
<orderEntry type="library" exported="" name="com.madgag.spongycastle:prov:1.54.0.0@jar" level="project" />
|
|
||||||
<orderEntry type="library" exported="" name="com.android.support:support-annotations:25.4.0@jar" level="project" />
|
|
||||||
<orderEntry type="library" exported="" name="com.android.support:support-core-utils-25.4.0" level="project" />
|
|
||||||
<orderEntry type="library" exported="" name="com.android.support:support-v4-25.4.0" level="project" />
|
|
||||||
<orderEntry type="library" exported="" name="com.android.support:support-vector-drawable-25.4.0" level="project" />
|
|
||||||
<orderEntry type="library" exported="" name="com.android.support:support-fragment-25.4.0" level="project" />
|
|
||||||
<orderEntry type="library" exported="" name="com.android.support:recyclerview-v7-25.4.0" level="project" />
|
|
||||||
<orderEntry type="library" exported="" name="com.android.support:support-media-compat-25.4.0" level="project" />
|
|
||||||
</component>
|
|
||||||
</module>
|
|
||||||
@@ -2,36 +2,37 @@ apply plugin: 'com.android.application'
|
|||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion = 25
|
compileSdkVersion = 25
|
||||||
buildToolsVersion = "25.0.3"
|
buildToolsVersion = "26.0.2"
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "com.android.keepass"
|
applicationId = "com.android.keepass"
|
||||||
minSdkVersion 11
|
minSdkVersion 11
|
||||||
targetSdkVersion 25
|
targetSdkVersion 25
|
||||||
versionCode = 154
|
versionCode = 1
|
||||||
versionName = "2.5.0.0"
|
versionName = "2.5.0.0"
|
||||||
|
|
||||||
testApplicationId = "com.keepassdroid.tests"
|
testApplicationId = "com.keepassdroid.tests"
|
||||||
testInstrumentationRunner = "android.test.InstrumentationTestRunner"
|
testInstrumentationRunner = "android.test.InstrumentationTestRunner"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
cmake {
|
cmake {
|
||||||
path "src/main/jni/CMakeLists.txt"
|
path "src/main/jni/CMakeLists.txt"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
minifyEnabled = false
|
minifyEnabled = false
|
||||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
compileSdkVersion 25
|
||||||
|
buildToolsVersion '26.0.2'
|
||||||
|
dexOptions {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def supportVersion = "25.4.0"
|
def supportVersion = "25.4.0"
|
||||||
def spongycastleVersion = "1.54.0.0"
|
def spongycastleVersion = "1.58.0.0"
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
androidTestCompile files('libs/junit4.jar')
|
androidTestCompile files('libs/junit4.jar')
|
||||||
@@ -40,5 +41,5 @@ dependencies {
|
|||||||
compile "com.android.support:preference-v7:$supportVersion"
|
compile "com.android.support:preference-v7:$supportVersion"
|
||||||
compile "com.madgag.spongycastle:core:$spongycastleVersion"
|
compile "com.madgag.spongycastle:core:$spongycastleVersion"
|
||||||
compile "com.madgag.spongycastle:prov:$spongycastleVersion"
|
compile "com.madgag.spongycastle:prov:$spongycastleVersion"
|
||||||
compile "joda-time:joda-time:2.9.9"
|
compile 'joda-time:joda-time:2.9.9'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,14 +69,22 @@ public class Kdb4 extends AndroidTestCase {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testSaving() throws IOException, InvalidDBException, PwDbOutputException {
|
public void testSavingKDBXV3() throws IOException, InvalidDBException, PwDbOutputException {
|
||||||
|
testSaving("test.kdbx", "12345", "test-out.kdbx");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testSavingKDBXV4() throws IOException, InvalidDBException, PwDbOutputException {
|
||||||
|
testSaving("test-kdbxv4.kdbx", "1", "test-kdbxv4-out.kdbx");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testSaving(String inputFile, String password, String outputFile) throws IOException, InvalidDBException, PwDbOutputException {
|
||||||
Context ctx = getContext();
|
Context ctx = getContext();
|
||||||
|
|
||||||
AssetManager am = ctx.getAssets();
|
AssetManager am = ctx.getAssets();
|
||||||
InputStream is = am.open("test.kdbx", AssetManager.ACCESS_STREAMING);
|
InputStream is = am.open(inputFile, AssetManager.ACCESS_STREAMING);
|
||||||
|
|
||||||
ImporterV4 importer = new ImporterV4();
|
ImporterV4 importer = new ImporterV4();
|
||||||
PwDatabaseV4 db = importer.openDatabase(is, "12345", null);
|
PwDatabaseV4 db = importer.openDatabase(is, password, null);
|
||||||
is.close();
|
is.close();
|
||||||
|
|
||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
@@ -86,12 +94,12 @@ public class Kdb4 extends AndroidTestCase {
|
|||||||
|
|
||||||
byte[] data = bos.toByteArray();
|
byte[] data = bos.toByteArray();
|
||||||
|
|
||||||
FileOutputStream fos = new FileOutputStream(TestUtil.getSdPath("test-out.kdbx"), false);
|
FileOutputStream fos = new FileOutputStream(TestUtil.getSdPath(outputFile), false);
|
||||||
|
|
||||||
InputStream bis = new ByteArrayInputStream(data);
|
InputStream bis = new ByteArrayInputStream(data);
|
||||||
bis = new CopyInputStream(bis, fos);
|
bis = new CopyInputStream(bis, fos);
|
||||||
importer = new ImporterV4();
|
importer = new ImporterV4();
|
||||||
db = importer.openDatabase(bis, "12345", null);
|
db = importer.openDatabase(bis, password, null);
|
||||||
bis.close();
|
bis.close();
|
||||||
|
|
||||||
fos.close();
|
fos.close();
|
||||||
|
|||||||
5
app/src/androidTest/res/values-fr/strings.xml
Normal file
5
app/src/androidTest/res/values-fr/strings.xml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="hello">Salut le Monde, Fantôme</string>
|
||||||
|
<string name="app_name">KeePassDroid</string>
|
||||||
|
</resources>
|
||||||
@@ -8,9 +8,10 @@
|
|||||||
android:largeScreens="true"
|
android:largeScreens="true"
|
||||||
android:anyDensity="true"
|
android:anyDensity="true"
|
||||||
/>
|
/>
|
||||||
|
<uses-permission android:name="android.permission.USE_FINGERPRINT"/>
|
||||||
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||||
|
<uses-permission android:name="android.permission.VIBRATE"/>
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
|
||||||
<uses-permission android:name="android.permission.VIBRATE" />
|
|
||||||
<application
|
<application
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
@@ -104,5 +105,6 @@
|
|||||||
|
|
||||||
<service android:name="com.keepassdroid.services.TimeoutService" />
|
<service android:name="com.keepassdroid.services.TimeoutService" />
|
||||||
<meta-data android:name="com.a0soft.gphone.aTrackDog.webURL" android:value="http://keepassdroid.com" />
|
<meta-data android:name="com.a0soft.gphone.aTrackDog.webURL" android:value="http://keepassdroid.com" />
|
||||||
|
<meta-data android:name="com.sec.android.support.multiwindow" android:value="true" />
|
||||||
</application>
|
</application>
|
||||||
</manifest>
|
</manifest>
|
||||||
BIN
app/src/main/assets/test-kdbxv4.kdbx
Normal file
BIN
app/src/main/assets/test-kdbxv4.kdbx
Normal file
Binary file not shown.
@@ -37,6 +37,7 @@ import android.os.Bundle;
|
|||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import android.support.v7.widget.Toolbar;
|
import android.support.v7.widget.Toolbar;
|
||||||
|
import android.support.v4.app.NotificationCompat;
|
||||||
import android.text.SpannableString;
|
import android.text.SpannableString;
|
||||||
import android.text.method.LinkMovementMethod;
|
import android.text.method.LinkMovementMethod;
|
||||||
import android.text.method.PasswordTransformationMethod;
|
import android.text.method.PasswordTransformationMethod;
|
||||||
@@ -236,32 +237,20 @@ public class EntryActivity extends LockCloseHideActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Notification getNotification(String intentText, int descResId) {
|
private Notification getNotification(String intentText, int descResId) {
|
||||||
String description = getString(descResId);
|
|
||||||
|
String desc = getString(descResId);
|
||||||
|
|
||||||
Intent intent = new Intent(intentText);
|
Intent intent = new Intent(intentText);
|
||||||
PendingIntent pending = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
|
PendingIntent pending = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
|
||||||
|
|
||||||
Notification notification;
|
// no longer supported for api level >22
|
||||||
|
// notify.setLatestEventInfo(this, getString(R.string.app_name), desc, pending);
|
||||||
|
// so instead using compat builder and create new notification
|
||||||
|
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
|
||||||
|
Notification notify = builder.setContentIntent(pending).setContentText(desc).setContentTitle(getString(R.string.app_name))
|
||||||
|
.setSmallIcon(R.drawable.notify).setTicker(desc).setWhen(System.currentTimeMillis()).build();
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
|
return notify;
|
||||||
notification = new Notification(R.drawable.notify, description, System.currentTimeMillis());
|
|
||||||
try {
|
|
||||||
Method deprecatedMethod = notification.getClass().getMethod("setLatestEventInfo", Context.class, CharSequence.class, CharSequence.class, PendingIntent.class);
|
|
||||||
deprecatedMethod.invoke(notification, this, getString(R.string.app_name), description, pending);
|
|
||||||
} catch (NoSuchMethodException | IllegalAccessException | IllegalArgumentException
|
|
||||||
| InvocationTargetException e) {
|
|
||||||
Log.w("EntryActivity", "Method not found", e);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Use new API
|
|
||||||
Notification.Builder builder = new Notification.Builder(this)
|
|
||||||
.setContentIntent(pending)
|
|
||||||
.setSmallIcon(R.drawable.notify)
|
|
||||||
.setContentTitle(getString(R.string.app_name));
|
|
||||||
notification = builder.getNotification();
|
|
||||||
}
|
|
||||||
|
|
||||||
return notification;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getDateTime(Date dt) {
|
private String getDateTime(Date dt) {
|
||||||
|
|||||||
@@ -27,11 +27,15 @@ import android.content.Intent;
|
|||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
|
import android.support.v4.hardware.fingerprint.FingerprintManagerCompat;
|
||||||
import android.support.v7.widget.Toolbar;
|
import android.support.v7.widget.Toolbar;
|
||||||
|
import android.text.Editable;
|
||||||
import android.text.InputType;
|
import android.text.InputType;
|
||||||
|
import android.text.TextWatcher;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
@@ -40,7 +44,6 @@ import android.widget.Button;
|
|||||||
import android.widget.CheckBox;
|
import android.widget.CheckBox;
|
||||||
import android.widget.CompoundButton;
|
import android.widget.CompoundButton;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import android.widget.ImageButton;
|
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
@@ -55,6 +58,7 @@ import com.keepassdroid.database.edit.LoadDB;
|
|||||||
import com.keepassdroid.database.edit.OnFinish;
|
import com.keepassdroid.database.edit.OnFinish;
|
||||||
import com.keepassdroid.dialog.PasswordEncodingDialogHelper;
|
import com.keepassdroid.dialog.PasswordEncodingDialogHelper;
|
||||||
import com.keepassdroid.fileselect.BrowserDialog;
|
import com.keepassdroid.fileselect.BrowserDialog;
|
||||||
|
import com.keepassdroid.fingerprint.FingerPrintHelper;
|
||||||
import com.keepassdroid.intents.Intents;
|
import com.keepassdroid.intents.Intents;
|
||||||
import com.keepassdroid.settings.AppSettingsActivity;
|
import com.keepassdroid.settings.AppSettingsActivity;
|
||||||
import com.keepassdroid.utils.EmptyUtils;
|
import com.keepassdroid.utils.EmptyUtils;
|
||||||
@@ -65,7 +69,9 @@ import com.keepassdroid.utils.Util;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
|
|
||||||
public class PasswordActivity extends LockingActivity {
|
import javax.crypto.Cipher;
|
||||||
|
|
||||||
|
public class PasswordActivity extends LockingActivity implements FingerPrintHelper.FingerPrintCallback {
|
||||||
|
|
||||||
public static final String KEY_DEFAULT_FILENAME = "defaultFileName";
|
public static final String KEY_DEFAULT_FILENAME = "defaultFileName";
|
||||||
private static final String KEY_FILENAME = "fileName";
|
private static final String KEY_FILENAME = "fileName";
|
||||||
@@ -84,12 +90,25 @@ public class PasswordActivity extends LockingActivity {
|
|||||||
SharedPreferences prefs;
|
SharedPreferences prefs;
|
||||||
|
|
||||||
private boolean isShowPasswordChecked = false;
|
private boolean isShowPasswordChecked = false;
|
||||||
|
private FingerPrintHelper fingerPrintHelper;
|
||||||
|
private int mode;
|
||||||
|
private static final String PREF_KEY_VALUE_PREFIX = "valueFor_"; // key is a combination of db file name and this prefix
|
||||||
|
private static final String PREF_KEY_IV_PREFIX = "ivFor_"; // key is a combination of db file name and this prefix
|
||||||
|
private View fingerprintView;
|
||||||
|
private TextView confirmationView;
|
||||||
|
private EditText passwordView;
|
||||||
|
private Button confirmButton;
|
||||||
|
|
||||||
public static void Launch(Activity act, String fileName) throws FileNotFoundException {
|
public static void Launch(
|
||||||
Launch(act,fileName,"");
|
Activity act,
|
||||||
|
String fileName) throws FileNotFoundException {
|
||||||
|
Launch(act, fileName, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Launch(Activity act, String fileName, String keyFile) throws FileNotFoundException {
|
public static void Launch(
|
||||||
|
Activity act,
|
||||||
|
String fileName,
|
||||||
|
String keyFile) throws FileNotFoundException {
|
||||||
if (EmptyUtils.isNullOrEmpty(fileName)) {
|
if (EmptyUtils.isNullOrEmpty(fileName)) {
|
||||||
throw new FileNotFoundException();
|
throw new FileNotFoundException();
|
||||||
}
|
}
|
||||||
@@ -113,52 +132,55 @@ public class PasswordActivity extends LockingActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
protected void onActivityResult(
|
||||||
|
int requestCode,
|
||||||
|
int resultCode,
|
||||||
|
Intent data) {
|
||||||
super.onActivityResult(requestCode, resultCode, data);
|
super.onActivityResult(requestCode, resultCode, data);
|
||||||
|
|
||||||
switch (requestCode) {
|
switch (requestCode) {
|
||||||
|
|
||||||
case KeePass.EXIT_NORMAL:
|
case KeePass.EXIT_NORMAL:
|
||||||
setEditText(R.id.password, "");
|
setEditText(R.id.password, "");
|
||||||
App.getDB().clear();
|
App.getDB().clear();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KeePass.EXIT_LOCK:
|
case KeePass.EXIT_LOCK:
|
||||||
setResult(KeePass.EXIT_LOCK);
|
setResult(KeePass.EXIT_LOCK);
|
||||||
setEditText(R.id.password, "");
|
setEditText(R.id.password, "");
|
||||||
finish();
|
finish();
|
||||||
App.getDB().clear();
|
App.getDB().clear();
|
||||||
break;
|
break;
|
||||||
case FILE_BROWSE:
|
case FILE_BROWSE:
|
||||||
if (resultCode == RESULT_OK) {
|
if (resultCode == RESULT_OK) {
|
||||||
String filename = data.getDataString();
|
String filename = data.getDataString();
|
||||||
if (filename != null) {
|
if (filename != null) {
|
||||||
EditText fn = (EditText) findViewById(R.id.pass_keyfile);
|
EditText fn = (EditText) findViewById(R.id.pass_keyfile);
|
||||||
fn.setText(filename);
|
fn.setText(filename);
|
||||||
mKeyUri = UriUtil.parseDefaultFile(filename);
|
mKeyUri = UriUtil.parseDefaultFile(filename);
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case GET_CONTENT:
|
|
||||||
case OPEN_DOC:
|
|
||||||
if (resultCode == RESULT_OK) {
|
|
||||||
if (data != null) {
|
|
||||||
Uri uri = data.getData();
|
|
||||||
if (uri != null) {
|
|
||||||
if (requestCode==GET_CONTENT) {
|
|
||||||
uri = UriUtil.translate(this, uri);
|
|
||||||
}
|
|
||||||
String path = uri.toString();
|
|
||||||
if (path != null) {
|
|
||||||
EditText fn = (EditText) findViewById(R.id.pass_keyfile);
|
|
||||||
fn.setText(path);
|
|
||||||
|
|
||||||
}
|
|
||||||
mKeyUri = uri;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
break;
|
||||||
break;
|
case GET_CONTENT:
|
||||||
|
case OPEN_DOC:
|
||||||
|
if (resultCode == RESULT_OK) {
|
||||||
|
if (data != null) {
|
||||||
|
Uri uri = data.getData();
|
||||||
|
if (uri != null) {
|
||||||
|
if (requestCode == GET_CONTENT) {
|
||||||
|
uri = UriUtil.translate(this, uri);
|
||||||
|
}
|
||||||
|
String path = uri.toString();
|
||||||
|
if (path != null) {
|
||||||
|
EditText fn = (EditText) findViewById(R.id.pass_keyfile);
|
||||||
|
fn.setText(path);
|
||||||
|
|
||||||
|
}
|
||||||
|
mKeyUri = uri;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,7 +201,14 @@ public class PasswordActivity extends LockingActivity {
|
|||||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||||
getSupportActionBar().setDisplayShowHomeEnabled(true);
|
getSupportActionBar().setDisplayShowHomeEnabled(true);
|
||||||
|
|
||||||
|
confirmButton = (Button) findViewById(R.id.pass_ok);
|
||||||
|
fingerprintView = findViewById(R.id.fingerprint);
|
||||||
|
confirmationView = (TextView) findViewById(R.id.fingerprint_label);
|
||||||
|
passwordView = (EditText) findViewById(R.id.password);
|
||||||
|
|
||||||
new InitTask().execute(i);
|
new InitTask().execute(i);
|
||||||
|
|
||||||
|
initForFingerprint();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -195,6 +224,9 @@ public class PasswordActivity extends LockingActivity {
|
|||||||
|
|
||||||
// Clear the shutdown flag
|
// Clear the shutdown flag
|
||||||
App.clearShutdown();
|
App.clearShutdown();
|
||||||
|
|
||||||
|
// checks if fingerprint is available, will also start listening for fingerprints when available
|
||||||
|
checkAvailability();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void retrieveSettings() {
|
private void retrieveSettings() {
|
||||||
@@ -206,7 +238,7 @@ public class PasswordActivity extends LockingActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Uri getKeyFile(Uri dbUri) {
|
private Uri getKeyFile(Uri dbUri) {
|
||||||
if ( mRememberKeyfile ) {
|
if (mRememberKeyfile) {
|
||||||
|
|
||||||
return App.getFileHistory().getFileByName(dbUri);
|
return App.getFileHistory().getFileByName(dbUri);
|
||||||
} else {
|
} else {
|
||||||
@@ -229,15 +261,215 @@ public class PasswordActivity extends LockingActivity {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
private void errorMessage(int resId)
|
private void errorMessage(int resId) {
|
||||||
{
|
|
||||||
Toast.makeText(this, resId, Toast.LENGTH_LONG).show();
|
Toast.makeText(this, resId, Toast.LENGTH_LONG).show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fingerprint related code here
|
||||||
|
|
||||||
|
private void initForFingerprint() {
|
||||||
|
fingerPrintHelper = new FingerPrintHelper(this, this);
|
||||||
|
if (fingerPrintHelper.isFingerprintSupported()) {
|
||||||
|
|
||||||
|
// when text entered we can enable the logon/purchase button and if required update encryption/decryption mode
|
||||||
|
passwordView.addTextChangedListener(new TextWatcher() {
|
||||||
|
@Override
|
||||||
|
public void beforeTextChanged(
|
||||||
|
final CharSequence s,
|
||||||
|
final int start,
|
||||||
|
final int count,
|
||||||
|
final int after) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTextChanged(
|
||||||
|
final CharSequence s,
|
||||||
|
final int start,
|
||||||
|
final int before,
|
||||||
|
final int count) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterTextChanged(final Editable s) {
|
||||||
|
final boolean validInput = s.length() > 0;
|
||||||
|
// encrypt or decrypt mode based on how much input or not
|
||||||
|
confirmationView.setText(validInput ? R.string.store_with_fingerprint : R.string.scanning_fingerprint);
|
||||||
|
mode = validInput ? toggleMode(Cipher.ENCRYPT_MODE) : toggleMode(Cipher.DECRYPT_MODE);
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// callback for fingerprint findings
|
||||||
|
fingerPrintHelper.setAuthenticationCallback(new FingerprintManagerCompat.AuthenticationCallback() {
|
||||||
|
@Override
|
||||||
|
public void onAuthenticationError(
|
||||||
|
final int errorCode,
|
||||||
|
final CharSequence errString) {
|
||||||
|
|
||||||
|
// this is triggered on stop/start listening done by helper to switch between modes so don't restart here
|
||||||
|
// errorCode = 5
|
||||||
|
// errString = "Fingerprint operation canceled."
|
||||||
|
//onException();
|
||||||
|
//confirmationView.setText(errString);
|
||||||
|
// true false fingerprint readings are handled otherwise with the toast messages, see below in code
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAuthenticationHelp(
|
||||||
|
final int helpCode,
|
||||||
|
final CharSequence helpString) {
|
||||||
|
|
||||||
|
onException();
|
||||||
|
confirmationView.setText(helpString);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAuthenticationSucceeded(final FingerprintManagerCompat.AuthenticationResult result) {
|
||||||
|
|
||||||
|
if (mode == Cipher.ENCRYPT_MODE) {
|
||||||
|
|
||||||
|
// newly store the entered password in encrypted way
|
||||||
|
final String password = passwordView.getText().toString();
|
||||||
|
fingerPrintHelper.encryptData(password);
|
||||||
|
|
||||||
|
} else if (mode == Cipher.DECRYPT_MODE) {
|
||||||
|
|
||||||
|
// retrieve the encrypted value from preferences
|
||||||
|
final String encryptedValue = prefs.getString(getPreferenceKeyValue(), null);
|
||||||
|
if (encryptedValue != null) {
|
||||||
|
fingerPrintHelper.decryptData(encryptedValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAuthenticationFailed() {
|
||||||
|
onException();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getPreferenceKeyValue() {
|
||||||
|
// makes it possible to store passwords uniqly per database
|
||||||
|
return PREF_KEY_VALUE_PREFIX + (mDbUri != null ? mDbUri.getPath() : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getPreferenceKeyIvSpec() {
|
||||||
|
return PREF_KEY_IV_PREFIX + (mDbUri != null ? mDbUri.getPath() : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
private int toggleMode(final int newMode) {
|
||||||
|
// check if mode is different so we can update fingerprint helper
|
||||||
|
if (mode != newMode) {
|
||||||
|
mode = newMode;
|
||||||
|
switch (mode) {
|
||||||
|
case Cipher.ENCRYPT_MODE:
|
||||||
|
fingerPrintHelper.initEncryptData();
|
||||||
|
break;
|
||||||
|
case Cipher.DECRYPT_MODE:
|
||||||
|
final String ivSpecValue = prefs.getString(getPreferenceKeyIvSpec(), null);
|
||||||
|
fingerPrintHelper.initDecryptData(ivSpecValue);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return newMode;
|
||||||
|
}
|
||||||
|
// remains in current mode
|
||||||
|
return mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPause() {
|
||||||
|
super.onPause();
|
||||||
|
// stop listening when we go in background
|
||||||
|
if (fingerPrintHelper != null) {
|
||||||
|
fingerPrintHelper.stopListening();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkAvailability() {
|
||||||
|
// fingerprint not supported (by API level or hardware) so keep option hidden
|
||||||
|
if (!fingerPrintHelper.isFingerprintSupported()) {
|
||||||
|
|
||||||
|
fingerprintView.setVisibility(View.GONE);
|
||||||
|
// since this is a fingerprint example inform user, don't do this in your app
|
||||||
|
//confirmationView.setText(R.string.fingerprint_not_supported);
|
||||||
|
confirmationView.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
// fingerprint is available but not configured show icon but in disabled state with some information
|
||||||
|
else if (!fingerPrintHelper.hasEnrolledFingerprints()) {
|
||||||
|
|
||||||
|
fingerprintView.setVisibility(View.VISIBLE);
|
||||||
|
confirmationView.setVisibility(View.VISIBLE);
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||||
|
fingerprintView.setAlpha(0.3f);
|
||||||
|
}
|
||||||
|
// This happens when no fingerprints are registered. Listening won't start
|
||||||
|
confirmationView.setText(R.string.configure_fingerprint);
|
||||||
|
}
|
||||||
|
// finally fingerprint available and configured so we can use it
|
||||||
|
else {
|
||||||
|
|
||||||
|
fingerprintView.setVisibility(View.VISIBLE);
|
||||||
|
confirmationView.setVisibility(View.VISIBLE);
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||||
|
fingerprintView.setAlpha(1f);
|
||||||
|
}
|
||||||
|
// fingerprint available but no stored password found yet for this DB so show info don't listen
|
||||||
|
if (prefs.getString(getPreferenceKeyValue(), null) == null) {
|
||||||
|
|
||||||
|
confirmationView.setText(R.string.no_password_stored);
|
||||||
|
}
|
||||||
|
// all is set here so we can confirm to user and start listening for fingerprints
|
||||||
|
else {
|
||||||
|
|
||||||
|
confirmationView.setText(R.string.scanning_fingerprint);
|
||||||
|
// listen for decryption by default
|
||||||
|
toggleMode(Cipher.DECRYPT_MODE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleEncryptedResult(
|
||||||
|
final String value,
|
||||||
|
final String ivSpec) {
|
||||||
|
|
||||||
|
prefs.edit()
|
||||||
|
.putString(getPreferenceKeyValue(), value)
|
||||||
|
.putString(getPreferenceKeyIvSpec(), ivSpec)
|
||||||
|
.commit();
|
||||||
|
// and remove visual input to reset UI
|
||||||
|
passwordView.setText("");
|
||||||
|
confirmationView.setText(R.string.encrypted_value_stored);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleDecryptedResult(final String value) {
|
||||||
|
// on decrypt enter it for the purchase/login action
|
||||||
|
passwordView.setText(value);
|
||||||
|
confirmButton.performClick();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInvalidKeyException() {
|
||||||
|
Toast.makeText(this, R.string.fingerprint_invalid_key, Toast.LENGTH_SHORT).show();
|
||||||
|
checkAvailability(); // restarts listening
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onException() {
|
||||||
|
Toast.makeText(this, R.string.fingerprint_error, Toast.LENGTH_SHORT).show();
|
||||||
|
checkAvailability(); // restarts listening
|
||||||
|
}
|
||||||
|
|
||||||
private class DefaultCheckChange implements CompoundButton.OnCheckedChangeListener {
|
private class DefaultCheckChange implements CompoundButton.OnCheckedChangeListener {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCheckedChanged(CompoundButton buttonView,
|
public void onCheckedChanged(
|
||||||
|
CompoundButton buttonView,
|
||||||
boolean isChecked) {
|
boolean isChecked) {
|
||||||
|
|
||||||
String newDefaultFileName;
|
String newDefaultFileName;
|
||||||
@@ -268,13 +500,16 @@ public class PasswordActivity extends LockingActivity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadDatabase(String pass, String keyfile) {
|
private void loadDatabase(
|
||||||
|
String pass,
|
||||||
|
String keyfile) {
|
||||||
loadDatabase(pass, UriUtil.parseDefaultFile(keyfile));
|
loadDatabase(pass, UriUtil.parseDefaultFile(keyfile));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadDatabase(String pass, Uri keyfile)
|
private void loadDatabase(
|
||||||
{
|
String pass,
|
||||||
if ( pass.length() == 0 && (keyfile == null || keyfile.toString().length() == 0)) {
|
Uri keyfile) {
|
||||||
|
if (pass.length() == 0 && (keyfile == null || keyfile.toString().length() == 0)) {
|
||||||
errorMessage(R.string.error_nopass);
|
errorMessage(R.string.error_nopass);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -296,9 +531,11 @@ public class PasswordActivity extends LockingActivity {
|
|||||||
return Util.getEditText(this, resId);
|
return Util.getEditText(this, resId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setEditText(int resId, String str) {
|
private void setEditText(
|
||||||
TextView te = (TextView) findViewById(resId);
|
int resId,
|
||||||
assert(te == null);
|
String str) {
|
||||||
|
TextView te = (TextView) findViewById(resId);
|
||||||
|
assert (te == null);
|
||||||
|
|
||||||
if (te != null) {
|
if (te != null) {
|
||||||
te.setText(str);
|
te.setText(str);
|
||||||
@@ -317,7 +554,8 @@ public class PasswordActivity extends LockingActivity {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
switch ( item.getItemId() ) {
|
|
||||||
|
switch (item.getItemId()) {
|
||||||
case R.id.menu_about:
|
case R.id.menu_about:
|
||||||
AboutDialog dialog = new AboutDialog(this);
|
AboutDialog dialog = new AboutDialog(this);
|
||||||
dialog.show();
|
dialog.show();
|
||||||
@@ -336,9 +574,12 @@ public class PasswordActivity extends LockingActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private final class AfterLoad extends OnFinish {
|
private final class AfterLoad extends OnFinish {
|
||||||
|
|
||||||
private Database db;
|
private Database db;
|
||||||
|
|
||||||
public AfterLoad(Handler handler, Database db) {
|
public AfterLoad(
|
||||||
|
Handler handler,
|
||||||
|
Database db) {
|
||||||
super(handler);
|
super(handler);
|
||||||
|
|
||||||
this.db = db;
|
this.db = db;
|
||||||
@@ -346,17 +587,19 @@ public class PasswordActivity extends LockingActivity {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
if ( db.passwordEncodingError) {
|
if (db.passwordEncodingError) {
|
||||||
PasswordEncodingDialogHelper dialog = new PasswordEncodingDialogHelper();
|
PasswordEncodingDialogHelper dialog = new PasswordEncodingDialogHelper();
|
||||||
dialog.show(PasswordActivity.this, new OnClickListener() {
|
dialog.show(PasswordActivity.this, new OnClickListener() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
public void onClick(
|
||||||
|
DialogInterface dialog,
|
||||||
|
int which) {
|
||||||
GroupActivity.Launch(PasswordActivity.this);
|
GroupActivity.Launch(PasswordActivity.this);
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
} else if ( mSuccess ) {
|
} else if (mSuccess) {
|
||||||
GroupActivity.Launch(PasswordActivity.this);
|
GroupActivity.Launch(PasswordActivity.this);
|
||||||
} else {
|
} else {
|
||||||
displayMessage(PasswordActivity.this);
|
displayMessage(PasswordActivity.this);
|
||||||
@@ -365,23 +608,23 @@ public class PasswordActivity extends LockingActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private class InitTask extends AsyncTask<Intent, Void, Integer> {
|
private class InitTask extends AsyncTask<Intent, Void, Integer> {
|
||||||
|
|
||||||
String password = "";
|
String password = "";
|
||||||
boolean launch_immediately = false;
|
boolean launch_immediately = false;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Integer doInBackground(Intent... args) {
|
protected Integer doInBackground(Intent... args) {
|
||||||
Intent i = args[0];
|
Intent i = args[0];
|
||||||
String action = i.getAction();;
|
String action = i.getAction();
|
||||||
if ( action != null && action.equals(VIEW_INTENT) ) {
|
if (action != null && action.equals(VIEW_INTENT)) {
|
||||||
Uri incoming = i.getData();
|
Uri incoming = i.getData();
|
||||||
mDbUri = incoming;
|
mDbUri = incoming;
|
||||||
|
|
||||||
mKeyUri = ClipDataCompat.getUriFromIntent(i, KEY_KEYFILE);
|
mKeyUri = ClipDataCompat.getUriFromIntent(i, KEY_KEYFILE);
|
||||||
|
|
||||||
if (incoming == null) {
|
if (incoming == null) {
|
||||||
return R.string.error_can_not_handle_uri;
|
return R.string.error_can_not_handle_uri;
|
||||||
}
|
} else if (incoming.getScheme().equals("file")) {
|
||||||
else if (incoming.getScheme().equals("file")) {
|
|
||||||
String fileName = incoming.getPath();
|
String fileName = incoming.getPath();
|
||||||
|
|
||||||
if (fileName.length() == 0) {
|
if (fileName.length() == 0) {
|
||||||
@@ -395,14 +638,14 @@ public class PasswordActivity extends LockingActivity {
|
|||||||
return R.string.FileNotFound;
|
return R.string.FileNotFound;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(mKeyUri == null)
|
if (mKeyUri == null) {
|
||||||
mKeyUri = getKeyFile(mDbUri);
|
mKeyUri = getKeyFile(mDbUri);
|
||||||
}
|
}
|
||||||
else if (incoming.getScheme().equals("content")) {
|
} else if (incoming.getScheme().equals("content")) {
|
||||||
if(mKeyUri == null)
|
if (mKeyUri == null) {
|
||||||
mKeyUri = getKeyFile(mDbUri);
|
mKeyUri = getKeyFile(mDbUri);
|
||||||
}
|
}
|
||||||
else {
|
} else {
|
||||||
return R.string.error_can_not_handle_uri;
|
return R.string.error_can_not_handle_uri;
|
||||||
}
|
}
|
||||||
password = i.getStringExtra(KEY_PASSWORD);
|
password = i.getStringExtra(KEY_PASSWORD);
|
||||||
@@ -414,7 +657,7 @@ public class PasswordActivity extends LockingActivity {
|
|||||||
password = i.getStringExtra(KEY_PASSWORD);
|
password = i.getStringExtra(KEY_PASSWORD);
|
||||||
launch_immediately = i.getBooleanExtra(KEY_LAUNCH_IMMEDIATELY, false);
|
launch_immediately = i.getBooleanExtra(KEY_LAUNCH_IMMEDIATELY, false);
|
||||||
|
|
||||||
if ( mKeyUri == null || mKeyUri.toString().length() == 0) {
|
if (mKeyUri == null || mKeyUri.toString().length() == 0) {
|
||||||
mKeyUri = getKeyFile(mDbUri);
|
mKeyUri = getKeyFile(mDbUri);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -422,7 +665,7 @@ public class PasswordActivity extends LockingActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void onPostExecute(Integer result) {
|
public void onPostExecute(Integer result) {
|
||||||
if(result != null) {
|
if (result != null) {
|
||||||
Toast.makeText(PasswordActivity.this, result, Toast.LENGTH_LONG).show();
|
Toast.makeText(PasswordActivity.this, result, Toast.LENGTH_LONG).show();
|
||||||
finish();
|
finish();
|
||||||
return;
|
return;
|
||||||
@@ -467,8 +710,7 @@ public class PasswordActivity extends LockingActivity {
|
|||||||
i.addCategory(Intent.CATEGORY_OPENABLE);
|
i.addCategory(Intent.CATEGORY_OPENABLE);
|
||||||
i.setType("*/*");
|
i.setType("*/*");
|
||||||
startActivityForResult(i, OPEN_DOC);
|
startActivityForResult(i, OPEN_DOC);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
|
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
|
||||||
i.addCategory(Intent.CATEGORY_OPENABLE);
|
i.addCategory(Intent.CATEGORY_OPENABLE);
|
||||||
i.setType("*/*");
|
i.setType("*/*");
|
||||||
@@ -518,8 +760,9 @@ public class PasswordActivity extends LockingActivity {
|
|||||||
|
|
||||||
retrieveSettings();
|
retrieveSettings();
|
||||||
|
|
||||||
if (launch_immediately)
|
if (launch_immediately) {
|
||||||
loadDatabase(password, mKeyUri);
|
loadDatabase(password, mKeyUri);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,8 +20,10 @@
|
|||||||
package com.keepassdroid.collections;
|
package com.keepassdroid.collections;
|
||||||
|
|
||||||
import com.keepassdroid.stream.LEDataInputStream;
|
import com.keepassdroid.stream.LEDataInputStream;
|
||||||
|
import com.keepassdroid.stream.LEDataOutputStream;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -31,64 +33,60 @@ public class VariantDictionary {
|
|||||||
private static final int VdmCritical = 0xFF00;
|
private static final int VdmCritical = 0xFF00;
|
||||||
private static final int VdmInfo = 0x00FF;
|
private static final int VdmInfo = 0x00FF;
|
||||||
|
|
||||||
private Map<String, Object> dict = new HashMap<String, Object>();
|
private Map<String, VdType> dict = new HashMap<String, VdType>();
|
||||||
|
|
||||||
private enum VdType {
|
private class VdType {
|
||||||
None(0x00),
|
public static final byte None = 0x00;
|
||||||
UInt32(0x04),
|
public static final byte UInt32 = 0x04;
|
||||||
UInt64(0x05),
|
public static final byte UInt64 =0x05;
|
||||||
Bool(0x08),
|
public static final byte Bool =0x08;
|
||||||
Int32(0x0C),
|
public static final byte Int32 =0x0C;
|
||||||
Int64(0x0D),
|
public static final byte Int64 =0x0D;
|
||||||
String(0x18),
|
public static final byte String =0x18;
|
||||||
ByteArray(0x42);
|
public static final byte ByteArray =0x42;
|
||||||
|
|
||||||
|
public final byte type;
|
||||||
|
public final Object value;
|
||||||
|
|
||||||
private final byte value;
|
VdType(byte type, Object value) {
|
||||||
|
this.type = type;
|
||||||
VdType(int value) {
|
this.value = value;
|
||||||
this.value = (byte) value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean equals(byte type) {
|
}
|
||||||
return type == value;
|
|
||||||
|
private Object getValue(String name) {
|
||||||
|
VdType val = dict.get(name);
|
||||||
|
if (val == null) {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
byte getValue() {
|
return val.value;
|
||||||
return value;
|
}
|
||||||
}
|
private void putType(byte type, String name, Object value) {
|
||||||
|
dict.put(name, new VdType(type, value));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setUInt32(String name, long value) {
|
public void setUInt32(String name, long value) { putType(VdType.UInt32, name, value); }
|
||||||
dict.put(name, value);
|
public long getUInt32(String name) { return (long)dict.get(name).value; }
|
||||||
}
|
|
||||||
public long getUInt32(String name) { return (long)dict.get(name); }
|
|
||||||
|
|
||||||
public void setUInt64(String name, long value) {
|
public void setUInt64(String name, long value) { putType(VdType.UInt64, name, value); }
|
||||||
dict.put(name, value);
|
public long getUInt64(String name) { return (long)dict.get(name).value; }
|
||||||
}
|
|
||||||
public long getUInt64(String name) { return (long)dict.get(name); }
|
|
||||||
|
|
||||||
public void setBool(String name, boolean value) {
|
public void setBool(String name, boolean value) { putType(VdType.Bool, name, value); }
|
||||||
dict.put(name, value);
|
public boolean getBool(String name) { return (boolean)dict.get(name).value; }
|
||||||
}
|
|
||||||
|
|
||||||
public void setInt32(String name, int value) {
|
public void setInt32(String name, int value) { putType(VdType.Int32 ,name, value); }
|
||||||
dict.put(name, value);
|
public int getInt32(String name) { return (int)dict.get(name).value; }
|
||||||
}
|
|
||||||
|
|
||||||
public void setInt64(String name, long value) {
|
public void setInt64(String name, long value) { putType(VdType.Int64 ,name, value); }
|
||||||
dict.put(name, value);
|
public long getInt64(String name) { return (long)dict.get(name).value; }
|
||||||
}
|
|
||||||
|
|
||||||
public void setString(String name, String value) {
|
public void setString(String name, String value) { putType(VdType.String ,name, value); }
|
||||||
dict.put(name, value);
|
public String getString(String name) { return (String)getValue(name); }
|
||||||
}
|
|
||||||
|
|
||||||
public void setByteArray(String name, byte[] value) {
|
public void setByteArray(String name, byte[] value) { putType(VdType.ByteArray, name, value); }
|
||||||
dict.put(name, value);
|
public byte[] getByteArray(String name) { return (byte[])getValue(name); }
|
||||||
}
|
|
||||||
public byte[] getByteArray(String name) { return (byte[])dict.get(name); }
|
|
||||||
|
|
||||||
public static VariantDictionary deserialize(LEDataInputStream lis) throws IOException {
|
public static VariantDictionary deserialize(LEDataInputStream lis) throws IOException {
|
||||||
VariantDictionary d = new VariantDictionary();
|
VariantDictionary d = new VariantDictionary();
|
||||||
@@ -105,7 +103,7 @@ public class VariantDictionary {
|
|||||||
}
|
}
|
||||||
|
|
||||||
byte bType = (byte)type;
|
byte bType = (byte)type;
|
||||||
if (VdType.None.equals(bType)) {
|
if (bType == VdType.None) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,51 +120,125 @@ public class VariantDictionary {
|
|||||||
throw new IOException("Invalid format");
|
throw new IOException("Invalid format");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (VdType.UInt32.equals(bType)) {
|
switch (bType) {
|
||||||
if (valueLen == 4) {
|
case VdType.UInt32:
|
||||||
d.setUInt32(name, LEDataInputStream.readUInt(valueBuf, 0));
|
if (valueLen == 4) {
|
||||||
}
|
d.setUInt32(name, LEDataInputStream.readUInt(valueBuf, 0));
|
||||||
}
|
}
|
||||||
else if (VdType.UInt64.equals(bType)) {
|
break;
|
||||||
if (valueLen == 8) {
|
case VdType.UInt64:
|
||||||
d.setUInt64(name, LEDataInputStream.readLong(valueBuf, 0));
|
if (valueLen == 8) {
|
||||||
}
|
d.setUInt64(name, LEDataInputStream.readLong(valueBuf, 0));
|
||||||
}
|
}
|
||||||
else if (VdType.Bool.equals(bType)) {
|
break;
|
||||||
if (valueLen == 1) {
|
case VdType.Bool:
|
||||||
d.setBool(name, valueBuf[0] != 0);
|
if (valueLen == 1) {
|
||||||
}
|
d.setBool(name, valueBuf[0] != 0);
|
||||||
}
|
}
|
||||||
else if (VdType.Int32.equals(bType)) {
|
break;
|
||||||
if (valueLen == 4) {
|
case VdType.Int32:
|
||||||
d.setInt32(name, LEDataInputStream.readInt(valueBuf, 0));
|
if (valueLen == 4) {
|
||||||
}
|
d.setInt32(name, LEDataInputStream.readInt(valueBuf, 0));
|
||||||
}
|
}
|
||||||
else if (VdType.Int64.equals(bType)) {
|
break;
|
||||||
if (valueLen == 8) {
|
case VdType.Int64:
|
||||||
d.setInt64(name, LEDataInputStream.readLong(valueBuf, 0));
|
if (valueLen == 8) {
|
||||||
}
|
d.setInt64(name, LEDataInputStream.readLong(valueBuf, 0));
|
||||||
}
|
}
|
||||||
else if (VdType.String.equals(bType)) {
|
break;
|
||||||
d.setString(name, new String(valueBuf, "UTF-8"));
|
case VdType.String:
|
||||||
}
|
d.setString(name, new String(valueBuf, "UTF-8"));
|
||||||
else if (VdType.ByteArray.equals(bType)) {
|
break;
|
||||||
d.setByteArray(name, valueBuf);
|
case VdType.ByteArray:
|
||||||
}
|
d.setByteArray(name, valueBuf);
|
||||||
else {
|
break;
|
||||||
assert(false);
|
default:
|
||||||
|
assert (false);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return d;
|
return d;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void serialize(VariantDictionary d, LEDataOutputStream los) throws IOException{
|
||||||
|
if (los == null) {
|
||||||
|
assert(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
los.writeUShort(VdVersion);
|
||||||
|
|
||||||
|
for (Map.Entry<String, VdType> entry: d.dict.entrySet()) {
|
||||||
|
String name = entry.getKey();
|
||||||
|
byte[] nameBuf = null;
|
||||||
|
try {
|
||||||
|
nameBuf = name.getBytes("UTF-8");
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
assert(false);
|
||||||
|
throw new IOException("Couldn't encode parameter name.");
|
||||||
|
}
|
||||||
|
|
||||||
|
VdType vd = entry.getValue();
|
||||||
|
|
||||||
|
los.write(vd.type);
|
||||||
|
los.writeInt(nameBuf.length);
|
||||||
|
los.write(nameBuf);
|
||||||
|
|
||||||
|
byte[] buf;
|
||||||
|
switch (vd.type) {
|
||||||
|
case VdType.UInt32:
|
||||||
|
los.writeInt(4);
|
||||||
|
los.writeUInt((long)vd.value);
|
||||||
|
break;
|
||||||
|
case VdType.UInt64:
|
||||||
|
los.writeInt(8);
|
||||||
|
los.writeLong((long)vd.value);
|
||||||
|
break;
|
||||||
|
case VdType.Bool:
|
||||||
|
los.writeInt(1);
|
||||||
|
byte bool = (boolean)vd.value ? (byte)1 : (byte)0;
|
||||||
|
los.write(bool);
|
||||||
|
break;
|
||||||
|
case VdType.Int32:
|
||||||
|
los.writeInt(4);
|
||||||
|
los.writeInt((int)vd.value);
|
||||||
|
break;
|
||||||
|
case VdType.Int64:
|
||||||
|
los.writeInt(8);
|
||||||
|
los.writeLong((long)vd.value);
|
||||||
|
break;
|
||||||
|
case VdType.String:
|
||||||
|
String value = (String)vd.value;
|
||||||
|
buf = value.getBytes("UTF-8");
|
||||||
|
los.writeInt(buf.length);
|
||||||
|
los.write(buf);
|
||||||
|
break;
|
||||||
|
case VdType.ByteArray:
|
||||||
|
buf = (byte[])vd.value;
|
||||||
|
los.writeInt(buf.length);
|
||||||
|
los.write(buf);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
assert(false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
los.write(VdType.None);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public void copyTo(VariantDictionary d) {
|
public void copyTo(VariantDictionary d) {
|
||||||
for (Map.Entry<String, Object> entry : d.dict.entrySet()) {
|
for (Map.Entry<String, VdType> entry : d.dict.entrySet()) {
|
||||||
String key = entry.getKey();
|
String key = entry.getKey();
|
||||||
Object value = entry.getValue();
|
VdType value = entry.getValue();
|
||||||
|
|
||||||
dict.put(key, value);
|
dict.put(key, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
;
|
||||||
|
public int size() {
|
||||||
|
return dict.size();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ public class BuildCompat {
|
|||||||
public static final int VERSION_CODE_JELLY_BEAN = 16;
|
public static final int VERSION_CODE_JELLY_BEAN = 16;
|
||||||
public static final int VERSION_CODE_JELLY_BEAN_MR2 = 18;
|
public static final int VERSION_CODE_JELLY_BEAN_MR2 = 18;
|
||||||
public static final int VERSION_KITKAT = 19;
|
public static final int VERSION_KITKAT = 19;
|
||||||
|
public static final int VERSION_CODE_M = 23;
|
||||||
|
|
||||||
private static Field versionSDK;
|
private static Field versionSDK;
|
||||||
private static int versionInt;
|
private static int versionInt;
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import javax.crypto.spec.SecretKeySpec;
|
|||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
|
||||||
import com.keepassdroid.crypto.engine.AesEngine;
|
import com.keepassdroid.crypto.engine.AesEngine;
|
||||||
|
import com.keepassdroid.crypto.engine.ChaCha20Engine;
|
||||||
import com.keepassdroid.crypto.engine.CipherEngine;
|
import com.keepassdroid.crypto.engine.CipherEngine;
|
||||||
import com.keepassdroid.crypto.engine.TwofishEngine;
|
import com.keepassdroid.crypto.engine.TwofishEngine;
|
||||||
import com.keepassdroid.utils.Types;
|
import com.keepassdroid.utils.Types;
|
||||||
@@ -88,6 +89,8 @@ public class CipherFactory {
|
|||||||
return new AesEngine();
|
return new AesEngine();
|
||||||
} else if ( uuid.equals(TwofishEngine.CIPHER_UUID) ) {
|
} else if ( uuid.equals(TwofishEngine.CIPHER_UUID) ) {
|
||||||
return new TwofishEngine();
|
return new TwofishEngine();
|
||||||
|
} else if ( uuid.equals(ChaCha20Engine.CIPHER_UUID)) {
|
||||||
|
return new ChaCha20Engine();
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new NoSuchAlgorithmException("UUID unrecognized.");
|
throw new NoSuchAlgorithmException("UUID unrecognized.");
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
package com.keepassdroid.crypto;
|
package com.keepassdroid.crypto;
|
||||||
|
|
||||||
import org.spongycastle.crypto.StreamCipher;
|
import org.spongycastle.crypto.StreamCipher;
|
||||||
import org.spongycastle.crypto.engines.ChaChaEngine;
|
import org.spongycastle.crypto.engines.ChaCha7539Engine;
|
||||||
import org.spongycastle.crypto.engines.Salsa20Engine;
|
import org.spongycastle.crypto.engines.Salsa20Engine;
|
||||||
import org.spongycastle.crypto.params.KeyParameter;
|
import org.spongycastle.crypto.params.KeyParameter;
|
||||||
import org.spongycastle.crypto.params.ParametersWithIV;
|
import org.spongycastle.crypto.params.ParametersWithIV;
|
||||||
@@ -65,9 +65,9 @@ public class PwStreamCipherFactory {
|
|||||||
System.arraycopy(hash, 32, iv, 0, 12);
|
System.arraycopy(hash, 32, iv, 0, 12);
|
||||||
|
|
||||||
KeyParameter keyParam = new KeyParameter(key32);
|
KeyParameter keyParam = new KeyParameter(key32);
|
||||||
ParametersWithIV ivParam = new ParametersWithIV(keyParam, SALSA_IV);
|
ParametersWithIV ivParam = new ParametersWithIV(keyParam, iv);
|
||||||
|
|
||||||
StreamCipher cipher = new ChaChaEngine();
|
StreamCipher cipher = new ChaCha7539Engine();
|
||||||
cipher.init(true, ivParam);
|
cipher.init(true, ivParam);
|
||||||
|
|
||||||
return cipher;
|
return cipher;
|
||||||
|
|||||||
@@ -19,9 +19,14 @@
|
|||||||
*/
|
*/
|
||||||
package com.keepassdroid.crypto.engine;
|
package com.keepassdroid.crypto.engine;
|
||||||
|
|
||||||
|
import com.keepassdroid.utils.Types;
|
||||||
|
|
||||||
|
import org.spongycastle.jce.provider.BouncyCastleProvider;
|
||||||
|
|
||||||
import java.security.InvalidAlgorithmParameterException;
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
import java.security.InvalidKeyException;
|
import java.security.InvalidKeyException;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
import javax.crypto.Cipher;
|
import javax.crypto.Cipher;
|
||||||
import javax.crypto.NoSuchPaddingException;
|
import javax.crypto.NoSuchPaddingException;
|
||||||
@@ -29,6 +34,11 @@ import javax.crypto.spec.IvParameterSpec;
|
|||||||
import javax.crypto.spec.SecretKeySpec;
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
|
||||||
public class ChaCha20Engine extends CipherEngine {
|
public class ChaCha20Engine extends CipherEngine {
|
||||||
|
public static final UUID CIPHER_UUID = Types.bytestoUUID(
|
||||||
|
new byte[]{(byte)0xD6, (byte)0x03, (byte)0x8A, (byte)0x2B, (byte)0x8B, (byte)0x6F,
|
||||||
|
(byte)0x4C, (byte)0xB5, (byte)0xA5, (byte)0x24, (byte)0x33, (byte)0x9A, (byte)0x31,
|
||||||
|
(byte)0xDB, (byte)0xB5, (byte)0x9A});
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int ivLength() {
|
public int ivLength() {
|
||||||
return 12;
|
return 12;
|
||||||
@@ -36,8 +46,8 @@ public class ChaCha20Engine extends CipherEngine {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Cipher getCipher(int opmode, byte[] key, byte[] IV, boolean androidOverride) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {
|
public Cipher getCipher(int opmode, byte[] key, byte[] IV, boolean androidOverride) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {
|
||||||
Cipher cipher = Cipher.getInstance("Chacha");
|
Cipher cipher = Cipher.getInstance("Chacha7539", new BouncyCastleProvider());
|
||||||
cipher.init(opmode, new SecretKeySpec(key, "ChaCha"), new IvParameterSpec(IV));
|
cipher.init(opmode, new SecretKeySpec(key, "ChaCha7539"), new IvParameterSpec(IV));
|
||||||
return cipher;
|
return cipher;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,9 +21,11 @@ package com.keepassdroid.crypto.keyDerivation;
|
|||||||
|
|
||||||
import com.keepassdroid.collections.VariantDictionary;
|
import com.keepassdroid.collections.VariantDictionary;
|
||||||
import com.keepassdroid.stream.LEDataInputStream;
|
import com.keepassdroid.stream.LEDataInputStream;
|
||||||
|
import com.keepassdroid.stream.LEDataOutputStream;
|
||||||
import com.keepassdroid.utils.Types;
|
import com.keepassdroid.utils.Types;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@@ -59,4 +61,13 @@ public class KdfParameters extends VariantDictionary {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static byte[] serialize(KdfParameters kdf) throws IOException {
|
||||||
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
|
LEDataOutputStream los = new LEDataOutputStream(bos);
|
||||||
|
|
||||||
|
KdfParameters.serialize(kdf, los);
|
||||||
|
|
||||||
|
return bos.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import java.io.IOException;
|
|||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.acl.Group;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
@@ -101,7 +102,7 @@ public class PwDatabaseV4 extends PwDatabase {
|
|||||||
public List<PwIconCustom> customIcons = new ArrayList<PwIconCustom>();
|
public List<PwIconCustom> customIcons = new ArrayList<PwIconCustom>();
|
||||||
public Map<String, String> customData = new HashMap<String, String>();
|
public Map<String, String> customData = new HashMap<String, String>();
|
||||||
public KdfParameters kdfParameters = KdfFactory.getDefaultParameters();
|
public KdfParameters kdfParameters = KdfFactory.getDefaultParameters();
|
||||||
public VariantDictionary publicCustomData;
|
public VariantDictionary publicCustomData = new VariantDictionary();
|
||||||
|
|
||||||
public String localizedAppName = "KeePassDroid";
|
public String localizedAppName = "KeePassDroid";
|
||||||
|
|
||||||
@@ -459,4 +460,67 @@ public class PwDatabaseV4 extends PwDatabase {
|
|||||||
|
|
||||||
return filename.substring(0, lastExtDot);
|
return filename.substring(0, lastExtDot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class GroupHasCustomData extends GroupHandler<PwGroup> {
|
||||||
|
|
||||||
|
public boolean hasCustomData = false;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean operate(PwGroup group) {
|
||||||
|
if (group == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
PwGroupV4 g4 = (PwGroupV4) group;
|
||||||
|
if (g4.customData.size() > 0) {
|
||||||
|
hasCustomData = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class EntryHasCustomData extends EntryHandler<PwEntry> {
|
||||||
|
|
||||||
|
public boolean hasCustomData = false;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean operate(PwEntry entry) {
|
||||||
|
if (entry == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
PwEntryV4 e4 = (PwEntryV4)entry;
|
||||||
|
if (e4.customData.size() > 0) {
|
||||||
|
hasCustomData = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMinKdbxVersion() {
|
||||||
|
if (!AesKdf.CIPHER_UUID.equals(kdfParameters.kdfUUID)) {
|
||||||
|
return PwDbHeaderV4.FILE_VERSION_32;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (publicCustomData.size() > 0) {
|
||||||
|
return PwDbHeaderV4.FILE_VERSION_32;
|
||||||
|
}
|
||||||
|
|
||||||
|
EntryHasCustomData entryHandler = new EntryHasCustomData();
|
||||||
|
GroupHasCustomData groupHandler = new GroupHasCustomData();
|
||||||
|
|
||||||
|
if (rootGroup == null ) {
|
||||||
|
return PwDbHeaderV4.FILE_VERSION_32_3;
|
||||||
|
}
|
||||||
|
rootGroup.preOrderTraverseTree(groupHandler, entryHandler);
|
||||||
|
if (groupHandler.hasCustomData || entryHandler.hasCustomData) {
|
||||||
|
return PwDbHeaderV4.FILE_VERSION_32;
|
||||||
|
}
|
||||||
|
|
||||||
|
return PwDbHeaderV4.FILE_VERSION_32_3;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -22,14 +22,18 @@ package com.keepassdroid.database;
|
|||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
import java.security.DigestInputStream;
|
import java.security.DigestInputStream;
|
||||||
import java.security.InvalidKeyException;
|
import java.security.InvalidKeyException;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import com.keepassdroid.crypto.keyDerivation.AesKdf;
|
import com.keepassdroid.crypto.keyDerivation.AesKdf;
|
||||||
import com.keepassdroid.crypto.keyDerivation.KdfParameters;
|
import com.keepassdroid.crypto.keyDerivation.KdfParameters;
|
||||||
import com.keepassdroid.database.exception.InvalidDBVersionException;
|
import com.keepassdroid.database.exception.InvalidDBVersionException;
|
||||||
|
import com.keepassdroid.database.security.ProtectedBinary;
|
||||||
import com.keepassdroid.stream.CopyInputStream;
|
import com.keepassdroid.stream.CopyInputStream;
|
||||||
import com.keepassdroid.stream.HmacBlockStream;
|
import com.keepassdroid.stream.HmacBlockStream;
|
||||||
import com.keepassdroid.stream.LEDataInputStream;
|
import com.keepassdroid.stream.LEDataInputStream;
|
||||||
@@ -87,14 +91,15 @@ public class PwDbHeaderV4 extends PwDbHeader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private PwDatabaseV4 db;
|
private PwDatabaseV4 db;
|
||||||
public byte[] protectedStreamKey = new byte[32];
|
public byte[] innerRandomStreamKey = new byte[32];
|
||||||
public byte[] streamStartBytes = new byte[32];
|
public byte[] streamStartBytes = new byte[32];
|
||||||
public CrsAlgorithm innerRandomStream;
|
public CrsAlgorithm innerRandomStream;
|
||||||
public long version;
|
public long version;
|
||||||
|
public List<ProtectedBinary> binaries = new ArrayList<ProtectedBinary>();
|
||||||
|
|
||||||
public PwDbHeaderV4(PwDatabaseV4 d) {
|
public PwDbHeaderV4(PwDatabaseV4 d) {
|
||||||
db = d;
|
db = d;
|
||||||
|
version = d.getMinKdbxVersion();
|
||||||
masterSeed = new byte[32];
|
masterSeed = new byte[32];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,7 +203,7 @@ public class PwDbHeaderV4 extends PwDbHeader {
|
|||||||
|
|
||||||
case PwDbHeaderV4Fields.InnerRandomstreamKey:
|
case PwDbHeaderV4Fields.InnerRandomstreamKey:
|
||||||
assert(version < PwDbHeaderV4.FILE_VERSION_32_4);
|
assert(version < PwDbHeaderV4.FILE_VERSION_32_4);
|
||||||
protectedStreamKey = fieldData;
|
innerRandomStreamKey = fieldData;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case PwDbHeaderV4Fields.StreamStartBytes:
|
case PwDbHeaderV4Fields.StreamStartBytes:
|
||||||
@@ -310,10 +315,6 @@ public class PwDbHeaderV4 extends PwDbHeader {
|
|||||||
return hmac.doFinal(header);
|
return hmac.doFinal(header);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setMinimumVersion() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getTransformSeed() {
|
public byte[] getTransformSeed() {
|
||||||
assert(version < FILE_VERSION_32_4);
|
assert(version < FILE_VERSION_32_4);
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ import javax.crypto.Cipher;
|
|||||||
import javax.crypto.NoSuchPaddingException;
|
import javax.crypto.NoSuchPaddingException;
|
||||||
|
|
||||||
import org.spongycastle.crypto.StreamCipher;
|
import org.spongycastle.crypto.StreamCipher;
|
||||||
import org.spongycastle.util.encoders.Base64Encoder;
|
|
||||||
import org.xmlpull.v1.XmlPullParser;
|
import org.xmlpull.v1.XmlPullParser;
|
||||||
import org.xmlpull.v1.XmlPullParserException;
|
import org.xmlpull.v1.XmlPullParserException;
|
||||||
import org.xmlpull.v1.XmlPullParserFactory;
|
import org.xmlpull.v1.XmlPullParserFactory;
|
||||||
@@ -52,7 +51,6 @@ import com.keepassdroid.crypto.CipherFactory;
|
|||||||
import com.keepassdroid.crypto.PwStreamCipherFactory;
|
import com.keepassdroid.crypto.PwStreamCipherFactory;
|
||||||
import com.keepassdroid.crypto.engine.CipherEngine;
|
import com.keepassdroid.crypto.engine.CipherEngine;
|
||||||
import com.keepassdroid.database.BinaryPool;
|
import com.keepassdroid.database.BinaryPool;
|
||||||
import com.keepassdroid.database.CrsAlgorithm;
|
|
||||||
import com.keepassdroid.database.ITimeLogger;
|
import com.keepassdroid.database.ITimeLogger;
|
||||||
import com.keepassdroid.database.PwCompressionAlgorithm;
|
import com.keepassdroid.database.PwCompressionAlgorithm;
|
||||||
import com.keepassdroid.database.PwDatabaseV4;
|
import com.keepassdroid.database.PwDatabaseV4;
|
||||||
@@ -111,6 +109,7 @@ public class ImporterV4 extends Importer {
|
|||||||
db = createDB();
|
db = createDB();
|
||||||
|
|
||||||
PwDbHeaderV4 header = new PwDbHeaderV4(db);
|
PwDbHeaderV4 header = new PwDbHeaderV4(db);
|
||||||
|
header.binaries.clear();
|
||||||
|
|
||||||
PwDbHeaderV4.HeaderAndHash hh = header.loadFromFile(inStream);
|
PwDbHeaderV4.HeaderAndHash hh = header.loadFromFile(inStream);
|
||||||
version = header.version;
|
version = header.version;
|
||||||
@@ -192,12 +191,12 @@ public class ImporterV4 extends Importer {
|
|||||||
LoadInnerHeader(isXml, header);
|
LoadInnerHeader(isXml, header);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( header.protectedStreamKey == null ) {
|
if ( header.innerRandomStreamKey == null ) {
|
||||||
assert(false);
|
assert(false);
|
||||||
throw new IOException("Invalid stream key.");
|
throw new IOException("Invalid stream key.");
|
||||||
}
|
}
|
||||||
|
|
||||||
randomStream = PwStreamCipherFactory.getInstance(header.innerRandomStream, header.protectedStreamKey);
|
randomStream = PwStreamCipherFactory.getInstance(header.innerRandomStream, header.innerRandomStreamKey);
|
||||||
|
|
||||||
if ( randomStream == null ) {
|
if ( randomStream == null ) {
|
||||||
throw new ArcFourException();
|
throw new ArcFourException();
|
||||||
@@ -243,7 +242,7 @@ public class ImporterV4 extends Importer {
|
|||||||
header.setRandomStreamID(data);
|
header.setRandomStreamID(data);
|
||||||
break;
|
break;
|
||||||
case PwDbHeaderV4.PwDbInnerHeaderV4Fields.InnerRandomstreamKey:
|
case PwDbHeaderV4.PwDbInnerHeaderV4Fields.InnerRandomstreamKey:
|
||||||
header.protectedStreamKey = data;
|
header.innerRandomStreamKey = data;
|
||||||
break;
|
break;
|
||||||
case PwDbHeaderV4.PwDbInnerHeaderV4Fields.Binary:
|
case PwDbHeaderV4.PwDbInnerHeaderV4Fields.Binary:
|
||||||
if (data.length < 1) throw new IOException("Invalid binary format");
|
if (data.length < 1) throw new IOException("Invalid binary format");
|
||||||
|
|||||||
@@ -19,44 +19,74 @@
|
|||||||
*/
|
*/
|
||||||
package com.keepassdroid.database.save;
|
package com.keepassdroid.database.save;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.security.DigestOutputStream;
|
import java.security.DigestOutputStream;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
|
import com.keepassdroid.collections.VariantDictionary;
|
||||||
|
import com.keepassdroid.crypto.keyDerivation.KdfParameters;
|
||||||
import com.keepassdroid.database.PwDatabaseV4;
|
import com.keepassdroid.database.PwDatabaseV4;
|
||||||
import com.keepassdroid.database.PwDbHeader;
|
import com.keepassdroid.database.PwDbHeader;
|
||||||
import com.keepassdroid.database.PwDbHeaderV4;
|
import com.keepassdroid.database.PwDbHeaderV4;
|
||||||
import com.keepassdroid.database.PwDbHeaderV4.PwDbHeaderV4Fields;
|
import com.keepassdroid.database.PwDbHeaderV4.PwDbHeaderV4Fields;
|
||||||
import com.keepassdroid.database.exception.PwDbOutputException;
|
import com.keepassdroid.database.exception.PwDbOutputException;
|
||||||
|
import com.keepassdroid.stream.HmacBlockStream;
|
||||||
import com.keepassdroid.stream.LEDataOutputStream;
|
import com.keepassdroid.stream.LEDataOutputStream;
|
||||||
|
import com.keepassdroid.stream.MacOutputStream;
|
||||||
import com.keepassdroid.utils.Types;
|
import com.keepassdroid.utils.Types;
|
||||||
|
|
||||||
|
import javax.crypto.Mac;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
|
||||||
public class PwDbHeaderOutputV4 extends PwDbHeaderOutput {
|
public class PwDbHeaderOutputV4 extends PwDbHeaderOutput {
|
||||||
private PwDbHeaderV4 header;
|
private PwDbHeaderV4 header;
|
||||||
private LEDataOutputStream los;
|
private LEDataOutputStream los;
|
||||||
|
private MacOutputStream mos;
|
||||||
private DigestOutputStream dos;
|
private DigestOutputStream dos;
|
||||||
private PwDatabaseV4 db;
|
private PwDatabaseV4 db;
|
||||||
|
public byte[] headerHmac;
|
||||||
|
|
||||||
private static byte[] EndHeaderValue = {'\r', '\n', '\r', '\n'};
|
private static byte[] EndHeaderValue = {'\r', '\n', '\r', '\n'};
|
||||||
|
|
||||||
public PwDbHeaderOutputV4(PwDatabaseV4 d, PwDbHeaderV4 h, OutputStream os) throws PwDbOutputException {
|
public PwDbHeaderOutputV4(PwDatabaseV4 d, PwDbHeaderV4 h, OutputStream os) throws PwDbOutputException {
|
||||||
db = d;
|
db = d;
|
||||||
header = h;
|
header = h;
|
||||||
|
|
||||||
MessageDigest md = null;
|
MessageDigest md = null;
|
||||||
try {
|
try {
|
||||||
md = MessageDigest.getInstance("SHA-256");
|
md = MessageDigest.getInstance("SHA-256");
|
||||||
} catch (NoSuchAlgorithmException e) {
|
} catch (NoSuchAlgorithmException e) {
|
||||||
throw new PwDbOutputException("SHA-256 not implemented here.");
|
throw new PwDbOutputException("SHA-256 not implemented here.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
d.makeFinalKey(header.masterSeed, d.kdfParameters);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new PwDbOutputException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
Mac hmac;
|
||||||
|
try {
|
||||||
|
hmac = Mac.getInstance("HmacSHA256");
|
||||||
|
SecretKeySpec signingKey = new SecretKeySpec(HmacBlockStream.GetHmacKey64(db.hmacKey, Types.ULONG_MAX_VALUE), "HmacSHA256");
|
||||||
|
hmac.init(signingKey);
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
throw new PwDbOutputException(e);
|
||||||
|
} catch (InvalidKeyException e) {
|
||||||
|
throw new PwDbOutputException(e);
|
||||||
|
}
|
||||||
|
|
||||||
dos = new DigestOutputStream(os, md);
|
dos = new DigestOutputStream(os, md);
|
||||||
los = new LEDataOutputStream(dos);
|
mos = new MacOutputStream(dos, hmac);
|
||||||
|
los = new LEDataOutputStream(mos);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void output() throws IOException {
|
public void output() throws IOException {
|
||||||
|
|
||||||
los.writeUInt(PwDbHeader.PWM_DBSIG_1);
|
los.writeUInt(PwDbHeader.PWM_DBSIG_1);
|
||||||
los.writeUInt(PwDbHeaderV4.DBSIG_2);
|
los.writeUInt(PwDbHeaderV4.DBSIG_2);
|
||||||
los.writeUInt(header.version);
|
los.writeUInt(header.version);
|
||||||
@@ -65,16 +95,36 @@ public class PwDbHeaderOutputV4 extends PwDbHeaderOutput {
|
|||||||
writeHeaderField(PwDbHeaderV4Fields.CipherID, Types.UUIDtoBytes(db.dataCipher));
|
writeHeaderField(PwDbHeaderV4Fields.CipherID, Types.UUIDtoBytes(db.dataCipher));
|
||||||
writeHeaderField(PwDbHeaderV4Fields.CompressionFlags, LEDataOutputStream.writeIntBuf(db.compressionAlgorithm.id));
|
writeHeaderField(PwDbHeaderV4Fields.CompressionFlags, LEDataOutputStream.writeIntBuf(db.compressionAlgorithm.id));
|
||||||
writeHeaderField(PwDbHeaderV4Fields.MasterSeed, header.masterSeed);
|
writeHeaderField(PwDbHeaderV4Fields.MasterSeed, header.masterSeed);
|
||||||
writeHeaderField(PwDbHeaderV4Fields.TransformSeed, header.getTransformSeed());
|
|
||||||
writeHeaderField(PwDbHeaderV4Fields.TransformRounds, LEDataOutputStream.writeLongBuf(db.numKeyEncRounds));
|
if (header.version < PwDbHeaderV4.FILE_VERSION_32_4) {
|
||||||
writeHeaderField(PwDbHeaderV4Fields.EncryptionIV, header.encryptionIV);
|
writeHeaderField(PwDbHeaderV4Fields.TransformSeed, header.getTransformSeed());
|
||||||
writeHeaderField(PwDbHeaderV4Fields.InnerRandomstreamKey, header.protectedStreamKey);
|
writeHeaderField(PwDbHeaderV4Fields.TransformRounds, LEDataOutputStream.writeLongBuf(db.numKeyEncRounds));
|
||||||
writeHeaderField(PwDbHeaderV4Fields.StreamStartBytes, header.streamStartBytes);
|
} else {
|
||||||
writeHeaderField(PwDbHeaderV4Fields.InnerRandomStreamID, LEDataOutputStream.writeIntBuf(header.innerRandomStream.id));
|
writeHeaderField(PwDbHeaderV4Fields.KdfParameters, KdfParameters.serialize(db.kdfParameters));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (header.encryptionIV.length > 0) {
|
||||||
|
writeHeaderField(PwDbHeaderV4Fields.EncryptionIV, header.encryptionIV);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (header.version < PwDbHeaderV4.FILE_VERSION_32_4) {
|
||||||
|
writeHeaderField(PwDbHeaderV4Fields.InnerRandomstreamKey, header.innerRandomStreamKey);
|
||||||
|
writeHeaderField(PwDbHeaderV4Fields.StreamStartBytes, header.streamStartBytes);
|
||||||
|
writeHeaderField(PwDbHeaderV4Fields.InnerRandomStreamID, LEDataOutputStream.writeIntBuf(header.innerRandomStream.id));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (db.publicCustomData.size() > 0) {
|
||||||
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
|
LEDataOutputStream los = new LEDataOutputStream(bos);
|
||||||
|
VariantDictionary.serialize(db.publicCustomData, los);
|
||||||
|
writeHeaderField(PwDbHeaderV4Fields.PublicCustomData, bos.toByteArray());
|
||||||
|
}
|
||||||
|
|
||||||
writeHeaderField(PwDbHeaderV4Fields.EndOfHeader, EndHeaderValue);
|
writeHeaderField(PwDbHeaderV4Fields.EndOfHeader, EndHeaderValue);
|
||||||
|
|
||||||
los.flush();
|
los.flush();
|
||||||
hashOfHeader = dos.getMessageDigest().digest();
|
hashOfHeader = dos.getMessageDigest().digest();
|
||||||
|
headerHmac = mos.getMac();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeHeaderField(byte fieldId, byte[] pbData) throws IOException {
|
private void writeHeaderField(byte fieldId, byte[] pbData) throws IOException {
|
||||||
@@ -82,11 +132,19 @@ public class PwDbHeaderOutputV4 extends PwDbHeaderOutput {
|
|||||||
los.write(fieldId);
|
los.write(fieldId);
|
||||||
|
|
||||||
if (pbData != null) {
|
if (pbData != null) {
|
||||||
los.writeUShort(pbData.length);
|
writeHeaderFieldSize(pbData.length);
|
||||||
los.write(pbData);
|
los.write(pbData);
|
||||||
} else {
|
} else {
|
||||||
los.writeUShort(0);
|
writeHeaderFieldSize(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void writeHeaderFieldSize(int size) throws IOException {
|
||||||
|
if (header.version < PwDbHeaderV4.FILE_VERSION_32_4) {
|
||||||
|
los.writeUShort(size);
|
||||||
|
} else {
|
||||||
|
los.writeInt(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 Brian Pellin.
|
||||||
|
*
|
||||||
|
* This file is part of KeePassDroid.
|
||||||
|
*
|
||||||
|
* KeePassDroid is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* KeePassDroid is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with KeePassDroid. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package com.keepassdroid.database.save;
|
||||||
|
|
||||||
|
import com.keepassdroid.database.PwDatabaseV4;
|
||||||
|
import com.keepassdroid.database.PwDbHeaderV4;
|
||||||
|
import com.keepassdroid.database.PwDbHeaderV4.PwDbInnerHeaderV4Fields;
|
||||||
|
import com.keepassdroid.database.PwDbHeaderV4.KdbxBinaryFlags;
|
||||||
|
import com.keepassdroid.database.security.ProtectedBinary;
|
||||||
|
import com.keepassdroid.stream.LEDataOutputStream;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
public class PwDbInnerHeaderOutputV4 {
|
||||||
|
private PwDatabaseV4 db;
|
||||||
|
private PwDbHeaderV4 header;
|
||||||
|
private LEDataOutputStream los;
|
||||||
|
|
||||||
|
public PwDbInnerHeaderOutputV4(PwDatabaseV4 db, PwDbHeaderV4 header, OutputStream os) {
|
||||||
|
this.db = db;
|
||||||
|
this.header = header;
|
||||||
|
|
||||||
|
this.los = new LEDataOutputStream(os);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void output() throws IOException {
|
||||||
|
los.write(PwDbInnerHeaderV4Fields.InnerRandomStreamID);
|
||||||
|
los.writeInt(4);
|
||||||
|
los.writeInt(header.innerRandomStream.id);
|
||||||
|
|
||||||
|
int streamKeySize = header.innerRandomStreamKey.length;
|
||||||
|
los.write(PwDbInnerHeaderV4Fields.InnerRandomstreamKey);
|
||||||
|
los.writeInt(streamKeySize);
|
||||||
|
los.write(header.innerRandomStreamKey);
|
||||||
|
|
||||||
|
for (ProtectedBinary bin : header.binaries) {
|
||||||
|
byte flag = KdbxBinaryFlags.None;
|
||||||
|
if (bin.isProtected()) {
|
||||||
|
flag |= KdbxBinaryFlags.Protected;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] binData = bin.getData();
|
||||||
|
los.write(PwDbInnerHeaderV4Fields.Binary);
|
||||||
|
los.writeInt(bin.length() + 1);
|
||||||
|
los.write(flag);
|
||||||
|
los.write(binData);
|
||||||
|
|
||||||
|
Arrays.fill(binData, (byte)0);
|
||||||
|
}
|
||||||
|
|
||||||
|
los.write(PwDbInnerHeaderV4Fields.EndOfHeader);
|
||||||
|
los.writeInt(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -23,6 +23,7 @@ import static com.keepassdroid.database.PwDatabaseV4XML.*;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -35,6 +36,7 @@ import java.util.zip.GZIPOutputStream;
|
|||||||
import javax.crypto.Cipher;
|
import javax.crypto.Cipher;
|
||||||
import javax.crypto.CipherOutputStream;
|
import javax.crypto.CipherOutputStream;
|
||||||
|
|
||||||
|
import org.joda.time.DateTime;
|
||||||
import org.spongycastle.crypto.StreamCipher;
|
import org.spongycastle.crypto.StreamCipher;
|
||||||
import org.xmlpull.v1.XmlSerializer;
|
import org.xmlpull.v1.XmlSerializer;
|
||||||
|
|
||||||
@@ -69,6 +71,9 @@ import com.keepassdroid.database.exception.PwDbOutputException;
|
|||||||
import com.keepassdroid.database.security.ProtectedBinary;
|
import com.keepassdroid.database.security.ProtectedBinary;
|
||||||
import com.keepassdroid.database.security.ProtectedString;
|
import com.keepassdroid.database.security.ProtectedString;
|
||||||
import com.keepassdroid.stream.HashedBlockOutputStream;
|
import com.keepassdroid.stream.HashedBlockOutputStream;
|
||||||
|
import com.keepassdroid.stream.HmacBlockOutputStream;
|
||||||
|
import com.keepassdroid.stream.LEDataOutputStream;
|
||||||
|
import com.keepassdroid.utils.DateUtil;
|
||||||
import com.keepassdroid.utils.EmptyUtils;
|
import com.keepassdroid.utils.EmptyUtils;
|
||||||
import com.keepassdroid.utils.MemUtil;
|
import com.keepassdroid.utils.MemUtil;
|
||||||
import com.keepassdroid.utils.Types;
|
import com.keepassdroid.utils.Types;
|
||||||
@@ -81,7 +86,9 @@ public class PwDbV4Output extends PwDbOutput {
|
|||||||
private XmlSerializer xml;
|
private XmlSerializer xml;
|
||||||
private PwDbHeaderV4 header;
|
private PwDbHeaderV4 header;
|
||||||
private byte[] hashOfHeader;
|
private byte[] hashOfHeader;
|
||||||
|
private byte[] headerHmac;
|
||||||
|
private CipherEngine engine = null;
|
||||||
|
|
||||||
protected PwDbV4Output(PwDatabaseV4 pm, OutputStream os) {
|
protected PwDbV4Output(PwDatabaseV4 pm, OutputStream os) {
|
||||||
super(os);
|
super(os);
|
||||||
|
|
||||||
@@ -91,30 +98,53 @@ public class PwDbV4Output extends PwDbOutput {
|
|||||||
@Override
|
@Override
|
||||||
public void output() throws PwDbOutputException {
|
public void output() throws PwDbOutputException {
|
||||||
|
|
||||||
|
try {
|
||||||
header = (PwDbHeaderV4) outputHeader(mOS);
|
try {
|
||||||
|
engine = CipherFactory.getInstance(mPM.dataCipher);
|
||||||
CipherOutputStream cos = attachStreamEncryptor(header, mOS);
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
throw new PwDbOutputException("No such cipher", e);
|
||||||
OutputStream compressed;
|
|
||||||
try {
|
|
||||||
cos.write(header.streamStartBytes);
|
|
||||||
|
|
||||||
HashedBlockOutputStream hashed = new HashedBlockOutputStream(cos);
|
|
||||||
|
|
||||||
if ( mPM.compressionAlgorithm == PwCompressionAlgorithm.Gzip ) {
|
|
||||||
compressed = new GZIPOutputStream(hashed);
|
|
||||||
} else {
|
|
||||||
compressed = hashed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
header = (PwDbHeaderV4) outputHeader(mOS);
|
||||||
outputDatabase(compressed);
|
|
||||||
compressed.close();
|
OutputStream osPlain;
|
||||||
} catch (IllegalArgumentException e) {
|
if (header.version < PwDbHeaderV4.FILE_VERSION_32_4) {
|
||||||
throw new PwDbOutputException(e);
|
CipherOutputStream cos = attachStreamEncryptor(header, mOS);
|
||||||
} catch (IllegalStateException e) {
|
cos.write(header.streamStartBytes);
|
||||||
throw new PwDbOutputException(e);
|
|
||||||
|
HashedBlockOutputStream hashed = new HashedBlockOutputStream(cos);
|
||||||
|
osPlain = hashed;
|
||||||
|
} else {
|
||||||
|
mOS.write(hashOfHeader);
|
||||||
|
mOS.write(headerHmac);
|
||||||
|
|
||||||
|
HmacBlockOutputStream hbos = new HmacBlockOutputStream(mOS, mPM.hmacKey);
|
||||||
|
osPlain = attachStreamEncryptor(header, hbos);
|
||||||
|
}
|
||||||
|
|
||||||
|
OutputStream osXml;
|
||||||
|
try {
|
||||||
|
|
||||||
|
|
||||||
|
if (mPM.compressionAlgorithm == PwCompressionAlgorithm.Gzip) {
|
||||||
|
osXml = new GZIPOutputStream(osPlain);
|
||||||
|
} else {
|
||||||
|
osXml = osPlain;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (header.version >= PwDbHeaderV4.FILE_VERSION_32_4) {
|
||||||
|
PwDbInnerHeaderOutputV4 ihOut = new PwDbInnerHeaderOutputV4((PwDatabaseV4)mPM, header, osXml);
|
||||||
|
ihOut.output();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
outputDatabase(osXml);
|
||||||
|
osXml.close();
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
throw new PwDbOutputException(e);
|
||||||
|
} catch (IllegalStateException e) {
|
||||||
|
throw new PwDbOutputException(e);
|
||||||
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new PwDbOutputException(e);
|
throw new PwDbOutputException(e);
|
||||||
}
|
}
|
||||||
@@ -241,8 +271,10 @@ public class PwDbV4Output extends PwDbOutput {
|
|||||||
writeObject(ElemHistoryMaxSize, mPM.historyMaxSize);
|
writeObject(ElemHistoryMaxSize, mPM.historyMaxSize);
|
||||||
writeObject(ElemLastSelectedGroup, mPM.lastSelectedGroup);
|
writeObject(ElemLastSelectedGroup, mPM.lastSelectedGroup);
|
||||||
writeObject(ElemLastTopVisibleGroup, mPM.lastTopVisibleGroup);
|
writeObject(ElemLastTopVisibleGroup, mPM.lastTopVisibleGroup);
|
||||||
|
|
||||||
writeBinPool();
|
if (header.version < PwDbHeaderV4.FILE_VERSION_32_4) {
|
||||||
|
writeBinPool();
|
||||||
|
}
|
||||||
writeList(ElemCustomData, mPM.customData);
|
writeList(ElemCustomData, mPM.customData);
|
||||||
|
|
||||||
xml.endTag(null, ElemMeta);
|
xml.endTag(null, ElemMeta);
|
||||||
@@ -251,11 +283,9 @@ public class PwDbV4Output extends PwDbOutput {
|
|||||||
|
|
||||||
private CipherOutputStream attachStreamEncryptor(PwDbHeaderV4 header, OutputStream os) throws PwDbOutputException {
|
private CipherOutputStream attachStreamEncryptor(PwDbHeaderV4 header, OutputStream os) throws PwDbOutputException {
|
||||||
Cipher cipher;
|
Cipher cipher;
|
||||||
CipherEngine engine;
|
|
||||||
try {
|
try {
|
||||||
mPM.makeFinalKey(header.masterSeed, header.getTransformSeed(), (int)mPM.numKeyEncRounds);
|
//mPM.makeFinalKey(header.masterSeed, mPM.kdfParameters);
|
||||||
|
|
||||||
engine = CipherFactory.getInstance(mPM.dataCipher);
|
|
||||||
cipher = engine.getCipher(Cipher.ENCRYPT_MODE, mPM.finalKey, header.encryptionIV);
|
cipher = engine.getCipher(Cipher.ENCRYPT_MODE, mPM.finalKey, header.encryptionIV);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new PwDbOutputException("Invalid algorithm.", e);
|
throw new PwDbOutputException("Invalid algorithm.", e);
|
||||||
@@ -272,19 +302,34 @@ public class PwDbV4Output extends PwDbOutput {
|
|||||||
|
|
||||||
PwDbHeaderV4 h = (PwDbHeaderV4) header;
|
PwDbHeaderV4 h = (PwDbHeaderV4) header;
|
||||||
random.nextBytes(h.masterSeed);
|
random.nextBytes(h.masterSeed);
|
||||||
|
|
||||||
|
int ivLength = engine.ivLength();
|
||||||
|
if (ivLength != h.encryptionIV.length) {
|
||||||
|
h.encryptionIV = new byte[ivLength];
|
||||||
|
}
|
||||||
random.nextBytes(h.encryptionIV);
|
random.nextBytes(h.encryptionIV);
|
||||||
|
|
||||||
UUID kdfUUID = mPM.kdfParameters.kdfUUID;
|
UUID kdfUUID = mPM.kdfParameters.kdfUUID;
|
||||||
KdfEngine kdf = KdfFactory.get(kdfUUID);
|
KdfEngine kdf = KdfFactory.get(kdfUUID);
|
||||||
kdf.randomize(mPM.kdfParameters);
|
kdf.randomize(mPM.kdfParameters);
|
||||||
|
|
||||||
random.nextBytes(h.protectedStreamKey);
|
if (h.version < PwDbHeaderV4.FILE_VERSION_32_4) {
|
||||||
h.innerRandomStream = CrsAlgorithm.Salsa20;
|
h.innerRandomStream = CrsAlgorithm.Salsa20;
|
||||||
randomStream = PwStreamCipherFactory.getInstance(h.innerRandomStream, h.protectedStreamKey);
|
h.innerRandomStreamKey = new byte[32];
|
||||||
|
} else {
|
||||||
|
h.innerRandomStream = CrsAlgorithm.ChaCha20;
|
||||||
|
h.innerRandomStreamKey = new byte[64];
|
||||||
|
}
|
||||||
|
random.nextBytes(h.innerRandomStreamKey);
|
||||||
|
|
||||||
|
randomStream = PwStreamCipherFactory.getInstance(h.innerRandomStream, h.innerRandomStreamKey);
|
||||||
if (randomStream == null) {
|
if (randomStream == null) {
|
||||||
throw new PwDbOutputException("Invalid random cipher");
|
throw new PwDbOutputException("Invalid random cipher");
|
||||||
}
|
}
|
||||||
random.nextBytes(h.streamStartBytes);
|
|
||||||
|
if ( h.version < PwDbHeaderV4.FILE_VERSION_32_4) {
|
||||||
|
random.nextBytes(h.streamStartBytes);
|
||||||
|
}
|
||||||
|
|
||||||
return random;
|
return random;
|
||||||
}
|
}
|
||||||
@@ -293,7 +338,7 @@ public class PwDbV4Output extends PwDbOutput {
|
|||||||
public PwDbHeader outputHeader(OutputStream os) throws PwDbOutputException {
|
public PwDbHeader outputHeader(OutputStream os) throws PwDbOutputException {
|
||||||
PwDbHeaderV4 header = new PwDbHeaderV4(mPM);
|
PwDbHeaderV4 header = new PwDbHeaderV4(mPM);
|
||||||
setIVs(header);
|
setIVs(header);
|
||||||
|
|
||||||
PwDbHeaderOutputV4 pho = new PwDbHeaderOutputV4(mPM, header, os);
|
PwDbHeaderOutputV4 pho = new PwDbHeaderOutputV4(mPM, header, os);
|
||||||
try {
|
try {
|
||||||
pho.output();
|
pho.output();
|
||||||
@@ -302,6 +347,7 @@ public class PwDbV4Output extends PwDbOutput {
|
|||||||
}
|
}
|
||||||
|
|
||||||
hashOfHeader = pho.getHashOfHeader();
|
hashOfHeader = pho.getHashOfHeader();
|
||||||
|
headerHmac = pho.headerHmac;
|
||||||
|
|
||||||
return header;
|
return header;
|
||||||
}
|
}
|
||||||
@@ -433,7 +479,16 @@ public class PwDbV4Output extends PwDbOutput {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void writeObject(String name, Date value) throws IllegalArgumentException, IllegalStateException, IOException {
|
private void writeObject(String name, Date value) throws IllegalArgumentException, IllegalStateException, IOException {
|
||||||
writeObject(name, PwDatabaseV4XML.dateFormat.format(value));
|
if (header.version < PwDbHeaderV4.FILE_VERSION_32_4) {
|
||||||
|
writeObject(name, PwDatabaseV4XML.dateFormat.format(value));
|
||||||
|
} else {
|
||||||
|
DateTime dt = new DateTime(value);
|
||||||
|
long seconds = DateUtil.convertDateToKDBX4Time(dt);
|
||||||
|
byte[] buf = LEDataOutputStream.writeLongBuf(seconds);
|
||||||
|
String b64 = new String(Base64Coder.encode(buf));
|
||||||
|
writeObject(name, b64);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeObject(String name, long value) throws IllegalArgumentException, IllegalStateException, IOException {
|
private void writeObject(String name, long value) throws IllegalArgumentException, IllegalStateException, IOException {
|
||||||
|
|||||||
@@ -19,17 +19,24 @@
|
|||||||
*/
|
*/
|
||||||
package com.keepassdroid.fileselect;
|
package com.keepassdroid.fileselect;
|
||||||
|
|
||||||
|
|
||||||
|
import android.Manifest;
|
||||||
|
import android.app.Activity;
|
||||||
import android.content.ActivityNotFoundException;
|
import android.content.ActivityNotFoundException;
|
||||||
import android.content.ContentResolver;
|
import android.content.ContentResolver;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Environment;
|
import android.os.Environment;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import android.support.v7.app.AppCompatActivity;
|
import android.support.v7.app.AppCompatActivity;
|
||||||
import android.support.v7.widget.Toolbar;
|
import android.support.v7.widget.Toolbar;
|
||||||
|
import android.support.v4.app.ActivityCompat;
|
||||||
|
import android.support.v4.content.ContextCompat;
|
||||||
import android.view.ContextMenu;
|
import android.view.ContextMenu;
|
||||||
import android.view.ContextMenu.ContextMenuInfo;
|
import android.view.ContextMenu.ContextMenuInfo;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
@@ -75,6 +82,7 @@ import java.net.URLDecoder;
|
|||||||
|
|
||||||
public class FileSelectActivity extends AppCompatActivity {
|
public class FileSelectActivity extends AppCompatActivity {
|
||||||
|
|
||||||
|
private static final int MY_PERMISSIONS_REQUEST_EXTERNAL_STORAGE = 111;
|
||||||
private ListView mList;
|
private ListView mList;
|
||||||
private ListAdapter mAdapter;
|
private ListAdapter mAdapter;
|
||||||
|
|
||||||
@@ -223,7 +231,12 @@ public class FileSelectActivity extends AppCompatActivity {
|
|||||||
startActivityForResult(i, OPEN_DOC);
|
startActivityForResult(i, OPEN_DOC);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
|
Intent i;
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||||
|
i = new Intent(Intent.ACTION_OPEN_DOCUMENT);
|
||||||
|
} else {
|
||||||
|
i = new Intent(Intent.ACTION_GET_CONTENT);
|
||||||
|
}
|
||||||
i.addCategory(Intent.CATEGORY_OPENABLE);
|
i.addCategory(Intent.CATEGORY_OPENABLE);
|
||||||
i.setType("*/*");
|
i.setType("*/*");
|
||||||
|
|
||||||
@@ -413,6 +426,9 @@ public class FileSelectActivity extends AppCompatActivity {
|
|||||||
@Override
|
@Override
|
||||||
protected void onResume() {
|
protected void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
|
|
||||||
|
// check for storage permission
|
||||||
|
checkStoragePermission();
|
||||||
|
|
||||||
// Check to see if we need to change modes
|
// Check to see if we need to change modes
|
||||||
if ( fileHistory.hasRecentFiles() != recentMode ) {
|
if ( fileHistory.hasRecentFiles() != recentMode ) {
|
||||||
@@ -426,6 +442,60 @@ public class FileSelectActivity extends AppCompatActivity {
|
|||||||
fnv.updateExternalStorageWarning();
|
fnv.updateExternalStorageWarning();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void checkStoragePermission() {
|
||||||
|
// Here, thisActivity is the current activity
|
||||||
|
if (ContextCompat.checkSelfPermission(FileSelectActivity.this,
|
||||||
|
Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||||
|
!= PackageManager.PERMISSION_GRANTED) {
|
||||||
|
|
||||||
|
// Should we show an explanation?
|
||||||
|
//if (ActivityCompat.shouldShowRequestPermissionRationale(FileSelectActivity.this,
|
||||||
|
// Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
|
||||||
|
|
||||||
|
// Show an explanation to the user *asynchronously* -- don't block
|
||||||
|
// this thread waiting for the user's response! After the user
|
||||||
|
// sees the explanation, try again to request the permission.
|
||||||
|
|
||||||
|
//} else {
|
||||||
|
|
||||||
|
// No explanation needed, we can request the permission.
|
||||||
|
|
||||||
|
ActivityCompat.requestPermissions(FileSelectActivity.this,
|
||||||
|
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
|
||||||
|
MY_PERMISSIONS_REQUEST_EXTERNAL_STORAGE);
|
||||||
|
|
||||||
|
// MY_PERMISSIONS_REQUEST_READ_CONTACTS is an
|
||||||
|
// app-defined int constant. The callback method gets the
|
||||||
|
// result of the request.
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRequestPermissionsResult(int requestCode,
|
||||||
|
String permissions[], int[] grantResults) {
|
||||||
|
switch (requestCode) {
|
||||||
|
case MY_PERMISSIONS_REQUEST_EXTERNAL_STORAGE: {
|
||||||
|
// If request is cancelled, the result arrays are empty.
|
||||||
|
if (grantResults.length > 0
|
||||||
|
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||||
|
|
||||||
|
// permission was granted, yay! Do the
|
||||||
|
// contacts-related task you need to do.
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// permission denied, boo! Disable the
|
||||||
|
// functionality that depends on this permission.
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// other 'case' lines to check for other
|
||||||
|
// permissions this app might request
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onCreateOptionsMenu(Menu menu) {
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
super.onCreateOptionsMenu(menu);
|
super.onCreateOptionsMenu(menu);
|
||||||
|
|||||||
@@ -0,0 +1,286 @@
|
|||||||
|
package com.keepassdroid.fingerprint;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.app.KeyguardManager;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.support.v4.os.CancellationSignal;
|
||||||
|
import android.security.keystore.KeyGenParameterSpec;
|
||||||
|
import android.security.keystore.KeyPermanentlyInvalidatedException;
|
||||||
|
import android.security.keystore.KeyProperties;
|
||||||
|
import android.support.v4.hardware.fingerprint.FingerprintManagerCompat;
|
||||||
|
import android.util.Base64;
|
||||||
|
|
||||||
|
import com.keepassdroid.compat.BuildCompat;
|
||||||
|
|
||||||
|
import java.security.KeyStore;
|
||||||
|
|
||||||
|
import javax.crypto.Cipher;
|
||||||
|
import javax.crypto.KeyGenerator;
|
||||||
|
import javax.crypto.SecretKey;
|
||||||
|
import javax.crypto.spec.IvParameterSpec;
|
||||||
|
|
||||||
|
public class FingerPrintHelper {
|
||||||
|
|
||||||
|
private static final String ALIAS_KEY = "example-key";
|
||||||
|
|
||||||
|
private FingerprintManagerCompat fingerprintManager;
|
||||||
|
private KeyStore keyStore = null;
|
||||||
|
private KeyGenerator keyGenerator = null;
|
||||||
|
private Cipher cipher = null;
|
||||||
|
private KeyguardManager keyguardManager = null;
|
||||||
|
private FingerprintManagerCompat.CryptoObject cryptoObject = null;
|
||||||
|
|
||||||
|
private boolean initOk = false;
|
||||||
|
private FingerPrintCallback fingerPrintCallback;
|
||||||
|
private CancellationSignal cancellationSignal;
|
||||||
|
private FingerprintManagerCompat.AuthenticationCallback authenticationCallback;
|
||||||
|
|
||||||
|
public void setAuthenticationCallback(final FingerprintManagerCompat.AuthenticationCallback authenticationCallback) {
|
||||||
|
this.authenticationCallback = authenticationCallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("NewApi")
|
||||||
|
public void startListening() {
|
||||||
|
// no need to start listening when not initialised
|
||||||
|
if (!isFingerprintInitialized()) {
|
||||||
|
if (fingerPrintCallback != null) {
|
||||||
|
fingerPrintCallback.onException();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// starts listening for fingerprints with the initialised crypto object
|
||||||
|
cancellationSignal = new CancellationSignal();
|
||||||
|
fingerprintManager.authenticate(
|
||||||
|
cryptoObject,
|
||||||
|
0 /* flags */,
|
||||||
|
cancellationSignal,
|
||||||
|
authenticationCallback,
|
||||||
|
null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("NewApi")
|
||||||
|
public void stopListening() {
|
||||||
|
if (!isFingerprintInitialized()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (cancellationSignal != null) {
|
||||||
|
cancellationSignal.cancel();
|
||||||
|
cancellationSignal = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface FingerPrintCallback {
|
||||||
|
|
||||||
|
void handleEncryptedResult(String value, String ivSpec);
|
||||||
|
|
||||||
|
void handleDecryptedResult(String value);
|
||||||
|
|
||||||
|
void onInvalidKeyException();
|
||||||
|
|
||||||
|
void onException();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(BuildCompat.VERSION_CODE_M)
|
||||||
|
public FingerPrintHelper(
|
||||||
|
final Context context,
|
||||||
|
final FingerPrintCallback fingerPrintCallback) {
|
||||||
|
|
||||||
|
if (!isFingerprintSupported()) {
|
||||||
|
// really not much to do when no fingerprint support found
|
||||||
|
setInitOk(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.fingerprintManager = FingerprintManagerCompat.from(context);
|
||||||
|
this.keyguardManager = (KeyguardManager)context.getSystemService(Context.KEYGUARD_SERVICE);
|
||||||
|
this.fingerPrintCallback = fingerPrintCallback;
|
||||||
|
|
||||||
|
if (hasEnrolledFingerprints()) {
|
||||||
|
try {
|
||||||
|
this.keyStore = KeyStore.getInstance("AndroidKeyStore");
|
||||||
|
this.keyGenerator = KeyGenerator.getInstance(
|
||||||
|
KeyProperties.KEY_ALGORITHM_AES,
|
||||||
|
"AndroidKeyStore");
|
||||||
|
this.cipher = Cipher.getInstance(
|
||||||
|
KeyProperties.KEY_ALGORITHM_AES + "/"
|
||||||
|
+ KeyProperties.BLOCK_MODE_CBC + "/"
|
||||||
|
+ KeyProperties.ENCRYPTION_PADDING_PKCS7);
|
||||||
|
this.cryptoObject = new FingerprintManagerCompat.CryptoObject(cipher);
|
||||||
|
setInitOk(true);
|
||||||
|
} catch (final Exception e) {
|
||||||
|
setInitOk(false);
|
||||||
|
fingerPrintCallback.onException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFingerprintInitialized() {
|
||||||
|
return hasEnrolledFingerprints() && initOk;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("NewApi")
|
||||||
|
public void initEncryptData() {
|
||||||
|
|
||||||
|
if (!isFingerprintInitialized()) {
|
||||||
|
if (fingerPrintCallback != null) {
|
||||||
|
fingerPrintCallback.onException();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
createNewKeyIfNeeded(false); // no need to keep deleting existing keys
|
||||||
|
keyStore.load(null);
|
||||||
|
final SecretKey key = (SecretKey) keyStore.getKey(ALIAS_KEY, null);
|
||||||
|
cipher.init(Cipher.ENCRYPT_MODE, key);
|
||||||
|
|
||||||
|
stopListening();
|
||||||
|
startListening();
|
||||||
|
|
||||||
|
} catch (final KeyPermanentlyInvalidatedException invalidKeyException) {
|
||||||
|
fingerPrintCallback.onInvalidKeyException();
|
||||||
|
} catch (final Exception e) {
|
||||||
|
fingerPrintCallback.onException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("NewApi")
|
||||||
|
public void encryptData(final String value) {
|
||||||
|
|
||||||
|
if (!isFingerprintInitialized()) {
|
||||||
|
if (fingerPrintCallback != null) {
|
||||||
|
fingerPrintCallback.onException();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
// actual do encryption here
|
||||||
|
byte[] encrypted = cipher.doFinal(value.getBytes());
|
||||||
|
final String encryptedValue = Base64.encodeToString(encrypted, 0 /* flags */);
|
||||||
|
|
||||||
|
// passes updated iv spec on to callback so this can be stored for decryption
|
||||||
|
final IvParameterSpec spec = cipher.getParameters().getParameterSpec(IvParameterSpec.class);
|
||||||
|
final String ivSpecValue = Base64.encodeToString(spec.getIV(), Base64.DEFAULT);
|
||||||
|
fingerPrintCallback.handleEncryptedResult(encryptedValue, ivSpecValue);
|
||||||
|
|
||||||
|
} catch (final Exception e) {
|
||||||
|
fingerPrintCallback.onException();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("NewApi")
|
||||||
|
public void initDecryptData(final String ivSpecValue) {
|
||||||
|
|
||||||
|
if (!isFingerprintInitialized()) {
|
||||||
|
if (fingerPrintCallback != null) {
|
||||||
|
fingerPrintCallback.onException();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
createNewKeyIfNeeded(false);
|
||||||
|
keyStore.load(null);
|
||||||
|
final SecretKey key = (SecretKey) keyStore.getKey(ALIAS_KEY, null);
|
||||||
|
|
||||||
|
// important to restore spec here that was used for decryption
|
||||||
|
final byte[] iv = Base64.decode(ivSpecValue, Base64.DEFAULT);
|
||||||
|
final IvParameterSpec spec = new IvParameterSpec(iv);
|
||||||
|
cipher.init(Cipher.DECRYPT_MODE, key, spec);
|
||||||
|
|
||||||
|
stopListening();
|
||||||
|
startListening();
|
||||||
|
|
||||||
|
} catch (final KeyPermanentlyInvalidatedException invalidKeyException) {
|
||||||
|
fingerPrintCallback.onInvalidKeyException();
|
||||||
|
} catch (final Exception e) {
|
||||||
|
fingerPrintCallback.onException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("NewApi")
|
||||||
|
public void decryptData(final String encryptedValue) {
|
||||||
|
|
||||||
|
if (!isFingerprintInitialized()) {
|
||||||
|
if (fingerPrintCallback != null) {
|
||||||
|
fingerPrintCallback.onException();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
// actual decryption here
|
||||||
|
final byte[] encrypted = Base64.decode(encryptedValue, 0);
|
||||||
|
byte[] decrypted = cipher.doFinal(encrypted);
|
||||||
|
final String decryptedString = new String(decrypted);
|
||||||
|
|
||||||
|
//final String encryptedString = Base64.encodeToString(encrypted, 0 /* flags */);
|
||||||
|
fingerPrintCallback.handleDecryptedResult(decryptedString);
|
||||||
|
|
||||||
|
} catch (final Exception e) {
|
||||||
|
fingerPrintCallback.onException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("NewApi")
|
||||||
|
private void createNewKeyIfNeeded(final boolean allowDeleteExisting) {
|
||||||
|
if (!isFingerprintInitialized()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
keyStore.load(null);
|
||||||
|
if (allowDeleteExisting
|
||||||
|
&& keyStore.containsAlias(ALIAS_KEY)) {
|
||||||
|
|
||||||
|
keyStore.deleteEntry(ALIAS_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new key if needed
|
||||||
|
if (!keyStore.containsAlias(ALIAS_KEY)) {
|
||||||
|
// 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_KEY,
|
||||||
|
KeyProperties.PURPOSE_ENCRYPT |
|
||||||
|
KeyProperties.PURPOSE_DECRYPT)
|
||||||
|
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
|
||||||
|
// Require the user to authenticate with a fingerprint to authorize every use
|
||||||
|
// of the key
|
||||||
|
.setUserAuthenticationRequired(true)
|
||||||
|
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
|
||||||
|
.build());
|
||||||
|
keyGenerator.generateKey();
|
||||||
|
}
|
||||||
|
} catch (final Exception e) {
|
||||||
|
fingerPrintCallback.onException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("NewApi")
|
||||||
|
public boolean isHardwareDetected() {
|
||||||
|
return isFingerprintSupported()
|
||||||
|
&& fingerprintManager != null
|
||||||
|
&& fingerprintManager.isHardwareDetected();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("NewApi")
|
||||||
|
public boolean hasEnrolledFingerprints() {
|
||||||
|
// fingerprint hardware supported and api level OK
|
||||||
|
return isHardwareDetected()
|
||||||
|
// fingerprints enrolled
|
||||||
|
&& fingerprintManager != null
|
||||||
|
&& fingerprintManager.hasEnrolledFingerprints()
|
||||||
|
// and lockscreen configured
|
||||||
|
&& keyguardManager.isKeyguardSecure();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setInitOk(final boolean initOk) {
|
||||||
|
this.initOk = initOk;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFingerprintSupported() {
|
||||||
|
return Build.VERSION.SDK_INT >= BuildCompat.VERSION_CODE_M;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 Brian Pellin.
|
||||||
|
*
|
||||||
|
* This file is part of KeePassDroid.
|
||||||
|
*
|
||||||
|
* KeePassDroid is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* KeePassDroid is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with KeePassDroid. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package com.keepassdroid.stream;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
|
import javax.crypto.Mac;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
|
||||||
|
public class HmacBlockOutputStream extends OutputStream {
|
||||||
|
private static final int DEFAULT_BUFFER_SIZE = 1024 * 1024;
|
||||||
|
private LEDataOutputStream baseStream;
|
||||||
|
private byte[] key;
|
||||||
|
|
||||||
|
private byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
|
||||||
|
private int bufferPos = 0;
|
||||||
|
private long blockIndex = 0;
|
||||||
|
|
||||||
|
public HmacBlockOutputStream(OutputStream os, byte[] key) {
|
||||||
|
this.baseStream = new LEDataOutputStream(os);
|
||||||
|
this.key = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
if (bufferPos == 0) {
|
||||||
|
WriteSafeBlock();
|
||||||
|
} else {
|
||||||
|
WriteSafeBlock();
|
||||||
|
WriteSafeBlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
baseStream.flush();;
|
||||||
|
baseStream.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void flush() throws IOException {
|
||||||
|
baseStream.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(byte[] outBuffer) throws IOException {
|
||||||
|
write(outBuffer, 0, outBuffer.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(byte[] outBuffer, int offset, int count) throws IOException {
|
||||||
|
while (count > 0) {
|
||||||
|
if (bufferPos == buffer.length) {
|
||||||
|
WriteSafeBlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
int copy = Math.min(buffer.length - bufferPos, count);
|
||||||
|
assert(copy > 0);
|
||||||
|
|
||||||
|
System.arraycopy(outBuffer, offset, buffer, bufferPos, copy);
|
||||||
|
offset += copy;
|
||||||
|
bufferPos += copy;
|
||||||
|
|
||||||
|
count -= copy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(int oneByte) throws IOException {
|
||||||
|
byte[] outByte = new byte[1];
|
||||||
|
write(outByte, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteSafeBlock() throws IOException {
|
||||||
|
byte[] bufBlockIndex = LEDataOutputStream.writeLongBuf(blockIndex);
|
||||||
|
byte[] blockSizeBuf = LEDataOutputStream.writeIntBuf(bufferPos);
|
||||||
|
|
||||||
|
byte[] blockHmac;
|
||||||
|
byte[] blockKey = HmacBlockStream.GetHmacKey64(key, blockIndex);
|
||||||
|
|
||||||
|
Mac hmac;
|
||||||
|
try {
|
||||||
|
hmac = Mac.getInstance("HmacSHA256");
|
||||||
|
SecretKeySpec signingKey = new SecretKeySpec(blockKey, "HmacSHA256");
|
||||||
|
hmac.init(signingKey);
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
throw new IOException("Invalid Hmac");
|
||||||
|
} catch (InvalidKeyException e) {
|
||||||
|
throw new IOException("Invalid HMAC");
|
||||||
|
}
|
||||||
|
|
||||||
|
hmac.update(bufBlockIndex);
|
||||||
|
hmac.update(blockSizeBuf);
|
||||||
|
|
||||||
|
if (bufferPos > 0) {
|
||||||
|
hmac.update(buffer, 0, bufferPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
blockHmac = hmac.doFinal();
|
||||||
|
|
||||||
|
baseStream.write(blockHmac);
|
||||||
|
baseStream.write(blockSizeBuf);
|
||||||
|
|
||||||
|
if (bufferPos > 0) {
|
||||||
|
baseStream.write(buffer, 0, bufferPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
blockIndex++;
|
||||||
|
bufferPos = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 Brian Pellin.
|
||||||
|
*
|
||||||
|
* This file is part of KeePassDroid.
|
||||||
|
*
|
||||||
|
* KeePassDroid is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* KeePassDroid is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with KeePassDroid. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package com.keepassdroid.stream;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
import javax.crypto.Mac;
|
||||||
|
|
||||||
|
public class MacOutputStream extends OutputStream {
|
||||||
|
private Mac mac;
|
||||||
|
private OutputStream os;
|
||||||
|
|
||||||
|
public MacOutputStream(OutputStream os, Mac mac) {
|
||||||
|
this.mac = mac;
|
||||||
|
this.os = os;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void flush() throws IOException {
|
||||||
|
os.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
os.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(int oneByte) throws IOException {
|
||||||
|
mac.update((byte) oneByte);
|
||||||
|
os.write(oneByte);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(byte[] buffer, int offset, int count) throws IOException {
|
||||||
|
mac.update(buffer, offset, count);
|
||||||
|
os.write(buffer, offset, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(byte[] buffer) throws IOException {
|
||||||
|
mac.update(buffer, 0, buffer.length);
|
||||||
|
os.write(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getMac() {
|
||||||
|
return mac.doFinal();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,4 +30,8 @@ public class DateUtil {
|
|||||||
public static Date convertKDBX4Time(long seconds) {
|
public static Date convertKDBX4Time(long seconds) {
|
||||||
return dotNetEpoch.plus(seconds).toDate();
|
return dotNetEpoch.plus(seconds).toDate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static long convertDateToKDBX4Time(DateTime dt) {
|
||||||
|
return (dt.getMillis() / 1000) - (dotNetEpoch.getMillis() / 1000);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
app/src/main/res/drawable-hdpi/ic_fp_40px.png
Normal file
BIN
app/src/main/res/drawable-hdpi/ic_fp_40px.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.8 KiB |
BIN
app/src/main/res/drawable-xhdpi/ic_fp_40px.png
Normal file
BIN
app/src/main/res/drawable-xhdpi/ic_fp_40px.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
BIN
app/src/main/res/drawable-xxhdpi/ic_fp_40px.png
Normal file
BIN
app/src/main/res/drawable-xxhdpi/ic_fp_40px.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
@@ -18,6 +18,7 @@
|
|||||||
along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
-->
|
-->
|
||||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:id="@+id/container"
|
android:id="@+id/container"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@@ -76,22 +77,39 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/entry_and_or" />
|
android:text="@string/entry_and_or" />
|
||||||
|
|
||||||
<!-- TODO Add checkbox -->
|
<!-- added these 2 fingerprint related views -->
|
||||||
<CheckBox
|
<ImageView
|
||||||
android:id="@+id/password_checkBox"
|
android:id="@+id/fingerprint"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_above="@+id/pass_keyfile"
|
android:layout_marginLeft="@dimen/margin_small"
|
||||||
|
android:layout_marginRight="@dimen/margin_small"
|
||||||
android:layout_below="@id/password_label"
|
android:layout_below="@id/password_label"
|
||||||
android:visibility="gone" />
|
android:layout_alignParentRight="true"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:src="@drawable/ic_fp_40px"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/fingerprint_label"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignBottom="@+id/fingerprint"
|
||||||
|
android:layout_below="@+id/password_label"
|
||||||
|
android:layout_toLeftOf="@id/fingerprint"
|
||||||
|
android:layout_toStartOf="@id/fingerprint"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:text="@string/entry_and_or"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
<EditText
|
<EditText
|
||||||
android:id="@+id/password"
|
android:id="@+id/password"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_below="@id/password_label"
|
android:layout_below="@id/fingerprint_label"
|
||||||
android:layout_toEndOf="@+id/password_checkBox"
|
|
||||||
android:layout_toRightOf="@+id/password_checkBox"
|
|
||||||
android:hint="@string/hint_login_pass"
|
android:hint="@string/hint_login_pass"
|
||||||
android:inputType="textPassword"
|
android:inputType="textPassword"
|
||||||
android:maxLines="1" />
|
android:maxLines="1" />
|
||||||
@@ -103,7 +121,7 @@
|
|||||||
android:layout_above="@+id/browse_button"
|
android:layout_above="@+id/browse_button"
|
||||||
android:layout_alignParentEnd="true"
|
android:layout_alignParentEnd="true"
|
||||||
android:layout_alignParentRight="true"
|
android:layout_alignParentRight="true"
|
||||||
android:layout_below="@+id/password_label"
|
android:layout_below="@+id/fingerprint"
|
||||||
android:src="@drawable/ic_visibility_white_24dp"
|
android:src="@drawable/ic_visibility_white_24dp"
|
||||||
android:tint="@android:color/darker_gray" />
|
android:tint="@android:color/darker_gray" />
|
||||||
|
|
||||||
@@ -119,21 +137,12 @@
|
|||||||
android:src="@drawable/ic_folder_white_24dp"
|
android:src="@drawable/ic_folder_white_24dp"
|
||||||
android:tint="?attr/colorAccentCompat" />
|
android:tint="?attr/colorAccentCompat" />
|
||||||
|
|
||||||
<CheckBox
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_alignBottom="@+id/browse_button"
|
|
||||||
android:layout_below="@id/password"
|
|
||||||
android:visibility="gone" />
|
|
||||||
|
|
||||||
<EditText
|
<EditText
|
||||||
android:id="@+id/pass_keyfile"
|
android:id="@+id/pass_keyfile"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_below="@id/password"
|
android:layout_below="@id/password"
|
||||||
android:layout_toEndOf="@+id/password_checkBox"
|
|
||||||
android:layout_toLeftOf="@id/browse_button"
|
android:layout_toLeftOf="@id/browse_button"
|
||||||
android:layout_toRightOf="@+id/password_checkBox"
|
|
||||||
android:layout_toStartOf="@id/browse_button"
|
android:layout_toStartOf="@id/browse_button"
|
||||||
android:hint="@string/entry_keyfile"
|
android:hint="@string/entry_keyfile"
|
||||||
android:maxLines="1" />
|
android:maxLines="1" />
|
||||||
@@ -163,4 +172,99 @@
|
|||||||
android:layout_alignParentEnd="true"
|
android:layout_alignParentEnd="true"
|
||||||
android:text="@android:string/ok" />
|
android:text="@android:string/ok" />
|
||||||
|
|
||||||
|
<!--
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="fill_parent"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
<TextView android:id="@+id/filename_label"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/pass_filename" />
|
||||||
|
<ImageView android:id="@+id/divider1"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@id/filename_label"
|
||||||
|
android:scaleType="fitXY"
|
||||||
|
android:src="@android:drawable/divider_horizontal_dark"/>
|
||||||
|
<TextView android:id="@+id/filename"
|
||||||
|
style="@style/GroupText"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@id/divider1"
|
||||||
|
android:singleLine="true"/>
|
||||||
|
<ImageView android:id="@+id/divider2"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@id/filename"
|
||||||
|
android:scaleType="fitXY"
|
||||||
|
android:src="@android:drawable/divider_horizontal_dark"/>
|
||||||
|
<CheckBox android:id="@+id/default_database"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@id/divider2"
|
||||||
|
android:text="@string/default_checkbox"
|
||||||
|
/>
|
||||||
|
<TextView android:id="@+id/password_label"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@id/default_database"
|
||||||
|
android:text="@string/entry_and_or" />
|
||||||
|
<EditText android:id="@+id/password"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@id/password_label"
|
||||||
|
android:layout_toLeftOf="@+id/fingerprint"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:inputType="textPassword"
|
||||||
|
android:hint="@string/hint_login_pass"/>
|
||||||
|
|
||||||
|
added these 2 fingerprint related views
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/fingerprint"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginLeft="@dimen/margin_small"
|
||||||
|
android:layout_marginRight="@dimen/margin_small"
|
||||||
|
android:layout_below="@id/password_label"
|
||||||
|
android:layout_alignParentRight="true"
|
||||||
|
android:src="@drawable/ic_fp_40px"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible"
|
||||||
|
/>
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/fingerprint_label"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@id/fingerprint"
|
||||||
|
android:text="@string/entry_and_or"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ImageButton android:id="@+id/browse_button"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:src="@drawable/ic_launcher_folder_small"
|
||||||
|
android:layout_below="@id/fingerprint_label"
|
||||||
|
android:layout_alignParentRight="true"
|
||||||
|
/>
|
||||||
|
<EditText android:id="@+id/pass_keyfile"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@id/fingerprint_label"
|
||||||
|
android:layout_toLeftOf="@id/browse_button"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:hint="@string/entry_keyfile"/>
|
||||||
|
<Button android:id="@+id/pass_ok"
|
||||||
|
android:text="@android:string/ok"
|
||||||
|
android:layout_width="100sp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@id/pass_keyfile"/>
|
||||||
|
<CheckBox android:id="@+id/show_password"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignTop="@id/pass_ok"
|
||||||
|
android:layout_toRightOf="@id/pass_ok"
|
||||||
|
android:text="@string/show_password"/>
|
||||||
|
-->
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
@@ -13,23 +13,23 @@
|
|||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
GNU General Public License for more details.
|
GNU General Public License for more details.
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
French translation by Laurent, Norman
|
|
||||||
|
|
||||||
-->
|
-->
|
||||||
<resources>
|
<resources
|
||||||
<string name="about_feedback">Signaler une anomalie\u00A0:</string>
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
<string name="about_homepage">Site web\u00A0:</string>
|
tools:ignore="MissingTranslation">
|
||||||
|
<string name="about_feedback">Signaler une anomalie\\u00A0:</string>
|
||||||
|
<string name="about_homepage">Site web\\u00A0:</string>
|
||||||
<string name="AboutText">KeePass DX est une implémentation sur Android du gestionnaire de mots de passe KeePass.</string>
|
<string name="AboutText">KeePass DX est une implémentation sur Android du gestionnaire de mots de passe KeePass.</string>
|
||||||
<string name="accept">Accepter</string>
|
<string name="accept">Accepter</string>
|
||||||
<string name="add_entry">Ajouter une entrée</string>
|
<string name="add_entry">Ajouter une entrée</string>
|
||||||
<string name="add_group">Ajouter un groupe</string>
|
<string name="add_group">Ajouter un groupe</string>
|
||||||
<string name="add_group_title">Ajouter un groupe</string>
|
<string name="add_group_title">Ajouter un groupe</string>
|
||||||
|
<string name="add_string">Ajouter une chaîne</string>
|
||||||
<string name="algorithm">Algorithme</string>
|
<string name="algorithm">Algorithme</string>
|
||||||
<string name="algorithm_colon">Algorithme\u00A0:</string>
|
<string name="algorithm_colon">Algorithme\\u00A0:</string>
|
||||||
<string name="app_timeout">Application timeout</string>
|
<string name="app_timeout">Application timeout</string>
|
||||||
<string name="app_timeout_summary">Temps avant le verrouillage de la base de données lorsque l\'application est inactive.</string>
|
<string name="app_timeout_summary">Temps avant le verrouillage de la base de données lorsque l\'application est inactive.</string>
|
||||||
<string name="application">Application</string>
|
<string name="application">Application</string>
|
||||||
@@ -38,29 +38,29 @@
|
|||||||
<string name="beta_warning">Enregistrer des modifications dans les fichiers kdbx est EXPÉRIMENTAL. Faites des sauvegardes de votre base de données avant toute modification.</string>
|
<string name="beta_warning">Enregistrer des modifications dans les fichiers kdbx est EXPÉRIMENTAL. Faites des sauvegardes de votre base de données avant toute modification.</string>
|
||||||
<string name="brackets">Crochets</string>
|
<string name="brackets">Crochets</string>
|
||||||
<string name="browser_intall_text">File browsing requires the Open Intents File Manager, click below to install it. Due to some quirks in the file manager, browsing may not work correctly, the first time you browse.</string>
|
<string name="browser_intall_text">File browsing requires the Open Intents File Manager, click below to install it. Due to some quirks in the file manager, browsing may not work correctly, the first time you browse.</string>
|
||||||
<string name="building_search_idx">Reconstruction de l\'index de recherche…</string>
|
<string name="building_search_idx">Reconstruction de l\'index de recherche…</string>
|
||||||
<string name="cancel">Annuler</string>
|
<string name="cancel">Annuler</string>
|
||||||
<string name="ClearClipboard">Presse-papier vidé</string>
|
<string name="ClearClipboard">Presse-papier vidé</string>
|
||||||
<string name="clipboard_error_title">Erreur de presse-papier</string>
|
<string name="clipboard_error_title">Erreur de presse-papier</string>
|
||||||
<string name="clipboard_error">Certains appareils Android Samsung ont un bug dans l\'implémentation du presse-papier qui empêche la copie depuis des applications. Pour plus de détails, visitez\u00A0:</string>
|
<string name="clipboard_error">Certains appareils Android Samsung ont un bug dans l\'implémentation du presse-papier qui empêche la copie depuis des applications. Pour plus de détails, visitez\\u00A0:</string>
|
||||||
<string name="clipboard_error_clear">Le vidage du presse-papier a échoué</string>
|
<string name="clipboard_error_clear">Le vidage du presse-papier a échoué</string>
|
||||||
<string name="clipboard_timeout">Clipboard timeout</string>
|
<string name="clipboard_timeout">Clipboard timeout</string>
|
||||||
<string name="clipboard_timeout_summary">Temps avant le vidage du presse-papier après copie du nom d\'utilisateur ou du mot de passe</string>
|
<string name="clipboard_timeout_summary">Temps avant le vidage du presse-papier après copie du nom d\'utilisateur ou du mot de passe</string>
|
||||||
<string name="copy_username">Copier le nom d\'utilisateur dans le presse-papier</string>
|
<string name="copy_username">Copier le nom d\'utilisateur dans le presse-papier</string>
|
||||||
<string name="copy_password">Copier le mot de passe dans le presse-papier</string>
|
<string name="copy_password">Copier le mot de passe dans le presse-papier</string>
|
||||||
<string name="creating_db_key">Création de la clé de base de données…</string>
|
<string name="creating_db_key">Création de la clé de base de données…</string>
|
||||||
<string name="current_group">Groupe actuel\u00A0:</string>
|
<string name="current_group">Groupe actuel\\u00A0:</string>
|
||||||
<string name="current_group_root">Groupe actuel\u00A0: Racine</string>
|
<string name="current_group_root">Groupe actuel\\u00A0: Racine</string>
|
||||||
<string name="database">Base de données</string>
|
<string name="database">Base de données</string>
|
||||||
<string name="decrypting_db">Déchiffrement du contenu de la base de données…</string>
|
<string name="decrypting_db">Déchiffrement du contenu de la base de données…</string>
|
||||||
<string name="decrypting_entry">Déchiffrement de l\'entrée</string>
|
<string name="decrypting_entry">Déchiffrement de l\'entrée</string>
|
||||||
<string name="default_checkbox">Utiliser comme base de données par défaut</string>
|
<string name="default_checkbox">Utiliser comme base de données par défaut</string>
|
||||||
<string name="digits">Nombres</string>
|
<string name="digits">Nombres</string>
|
||||||
<string name="disclaimer_formal">KeePass DX Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft n\'offre ABSOLUMENT AUCUNE GARANTIE; il s\'agit d\'un logiciel libre, vous pouvez le redistribuer sous les conditions de la licence GPL v2 ou ultérieure.</string>
|
<string name="disclaimer_formal">KeePass DX Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft n\'offre ABSOLUMENT AUCUNE GARANTIE; il s\'agit d\'un logiciel libre, vous pouvez le redistribuer sous les conditions de la licence GPL v2 ou ultérieure.</string>
|
||||||
|
<string name="ellipsis">\\u2026</string>
|
||||||
<string name="enter_filename">Sélectionnez la base de données\u00A0:</string>
|
<string name="enter_filename">Sélectionnez la base de données\\u00A0:</string>
|
||||||
<string name="entry_accessed">Dernier accès</string>
|
<string name="entry_accessed">Dernier accès</string>
|
||||||
<string name="entry_and_or">Entrez un mot de passe et/ou un fichier de clé pour ouvrir la base de données\u00A0:</string>
|
<string name="entry_and_or">Entrez un mot de passe et/ou un fichier de clé pour ouvrir la base de données\\u00A0:</string>
|
||||||
<string name="entry_cancel">Annuler</string>
|
<string name="entry_cancel">Annuler</string>
|
||||||
<string name="entry_comment">Commentaires</string>
|
<string name="entry_comment">Commentaires</string>
|
||||||
<string name="entry_confpassword">Confirmer mot de passe</string>
|
<string name="entry_confpassword">Confirmer mot de passe</string>
|
||||||
@@ -82,7 +82,7 @@
|
|||||||
<string name="error_database_settings">Impossible de déterminer les paramètres de la base de données.</string>
|
<string name="error_database_settings">Impossible de déterminer les paramètres de la base de données.</string>
|
||||||
<string name="error_failed_to_launch_link">Échec lors de l\'ouverture du lien.</string>
|
<string name="error_failed_to_launch_link">Échec lors de l\'ouverture du lien.</string>
|
||||||
<string name="error_filename_required">Le nom de fichier est obligatoire.</string>
|
<string name="error_filename_required">Le nom de fichier est obligatoire.</string>
|
||||||
<string name="error_file_not_create">Impossible de créer le fichier\u00A0:</string>
|
<string name="error_file_not_create">Impossible de créer le fichier\\u00A0:</string>
|
||||||
<string name="error_invalid_db">Base de données invalide.</string>
|
<string name="error_invalid_db">Base de données invalide.</string>
|
||||||
<string name="error_invalid_path">Chemin invalide.</string>
|
<string name="error_invalid_path">Chemin invalide.</string>
|
||||||
<string name="error_no_name">Le nom est obligatoire.</string>
|
<string name="error_no_name">Le nom est obligatoire.</string>
|
||||||
@@ -98,6 +98,7 @@
|
|||||||
<string name="field_name">Nom du champ</string>
|
<string name="field_name">Nom du champ</string>
|
||||||
<string name="field_value">Valeur</string>
|
<string name="field_value">Valeur</string>
|
||||||
<string name="FileNotFound">Fichier non trouvé.</string>
|
<string name="FileNotFound">Fichier non trouvé.</string>
|
||||||
|
<string name="file_not_found_content">Fichier non trouvé. Essayer de l\'ouvrir à nouveau à partir de votre fournisseur de contenu.</string>
|
||||||
<string name="file_browser">Navigateur de fichiers</string>
|
<string name="file_browser">Navigateur de fichiers</string>
|
||||||
<string name="generate_password">Générer mot de passe</string>
|
<string name="generate_password">Générer mot de passe</string>
|
||||||
<string name="group">Groupe</string>
|
<string name="group">Groupe</string>
|
||||||
@@ -122,9 +123,9 @@
|
|||||||
<string name="length">Longueur</string>
|
<string name="length">Longueur</string>
|
||||||
<string name="list_size_title">Taille de la liste des groupes</string>
|
<string name="list_size_title">Taille de la liste des groupes</string>
|
||||||
<string name="list_size_summary">Taille de la police de caractères utilisée pour la liste des groupes</string>
|
<string name="list_size_summary">Taille de la police de caractères utilisée pour la liste des groupes</string>
|
||||||
<string name="loading_database">Ouverture de la base de données…</string>
|
<string name="loading_database">Ouverture de la base de données…</string>
|
||||||
<string name="lowercase">Minuscule</string>
|
<string name="lowercase">Minuscule</string>
|
||||||
|
<string name="MaskedPassword">*****</string>
|
||||||
<string name="maskpass_title">Afficher le mot de passe</string>
|
<string name="maskpass_title">Afficher le mot de passe</string>
|
||||||
<string name="maskpass_summary">Par défaut, masquer le mot de passe</string>
|
<string name="maskpass_summary">Par défaut, masquer le mot de passe</string>
|
||||||
<string name="menu_about">À propos</string>
|
<string name="menu_about">À propos</string>
|
||||||
@@ -150,15 +151,16 @@
|
|||||||
<string name="no_keys">Aucun élément.</string>
|
<string name="no_keys">Aucun élément.</string>
|
||||||
<string name="no_results">Aucun résultat pour cette recherche.</string>
|
<string name="no_results">Aucun résultat pour cette recherche.</string>
|
||||||
<string name="no_url_handler">Impossible d\'ouvrir cette URL.</string>
|
<string name="no_url_handler">Impossible d\'ouvrir cette URL.</string>
|
||||||
<string name="open_recent">Bases de données utilisées récemment\u00A0:</string>
|
<string name="open_recent">Bases de données utilisées récemment\\u00A0:</string>
|
||||||
<string name="omitbackup_title">Ignorer les sauvegardes</string>
|
<string name="omitbackup_title">Ignorer les sauvegardes</string>
|
||||||
<string name="omitbackup_summary">Ignorer le groupe Sauvegardes des résultats de recherche (uniquement pour .kdb)</string>
|
<string name="omitbackup_summary">Ignorer le groupe Sauvegardes des résultats de recherche (uniquement pour .kdb)</string>
|
||||||
<string name="pass_filename">Fichier de base de données KeePass</string>
|
<string name="pass_filename">Fichier de base de données KeePass</string>
|
||||||
<string name="password_title">Entrez le mot de passe de la base de données</string>
|
<string name="password_title">Entrez le mot de passe de la base de données</string>
|
||||||
<string name="progress_create">Création d\'une nouvelle base de données…</string>
|
<string name="progress_create">Création d\'une nouvelle base de données…</string>
|
||||||
<string name="progress_title">Veuillez patienter…</string>
|
<string name="progress_title">Veuillez patienter…</string>
|
||||||
<string name="protection">Protection</string>
|
<string name="protection">Protection</string>
|
||||||
<string name="read_only_warning">KeePass DX n\'a pas la permission d\'écrire dans le répertoire de la base de données. Celle-ci sera ouverte en lecture seule.</string>
|
<string name="read_only">Lecture seule</string>
|
||||||
|
<string name="read_only_warning">KeePassDroid n\'a pas la permission d\'écrire dans le répertoire de la base de données. Celle-ci sera ouverte en lecture seule.</string>
|
||||||
<string name="read_only_kitkat_warning">À partir d\'Android KitKat, certains appareils n\'autorisent plus les applications à écrire sur la carte SD.</string>
|
<string name="read_only_kitkat_warning">À partir d\'Android KitKat, certains appareils n\'autorisent plus les applications à écrire sur la carte SD.</string>
|
||||||
<string name="recentfile_title">Historique de fichiers récents</string>
|
<string name="recentfile_title">Historique de fichiers récents</string>
|
||||||
<string name="recentfile_summary">Mémoriser les fichiers récemment utilisés</string>
|
<string name="recentfile_summary">Mémoriser les fichiers récemment utilisés</string>
|
||||||
@@ -170,7 +172,7 @@
|
|||||||
<string name="rounds">Niveau du chiffrement</string>
|
<string name="rounds">Niveau du chiffrement</string>
|
||||||
<string name="rounds_explaination">Un niveau de chiffrement supérieur assure une protection supplémentaire contre les attaques de force brute, mais peut considérablement ralentir l\'ouverture et l\'enregistrement.</string>
|
<string name="rounds_explaination">Un niveau de chiffrement supérieur assure une protection supplémentaire contre les attaques de force brute, mais peut considérablement ralentir l\'ouverture et l\'enregistrement.</string>
|
||||||
<string name="rounds_hint">niveaux</string>
|
<string name="rounds_hint">niveaux</string>
|
||||||
<string name="saving_database">Enregistrement de la base de données…</string>
|
<string name="saving_database">Enregistrement de la base de données…</string>
|
||||||
<string name="space">Espace</string>
|
<string name="space">Espace</string>
|
||||||
<string name="search_label">Rechercher</string>
|
<string name="search_label">Rechercher</string>
|
||||||
<string name="show_password">Afficher le mot de passe</string>
|
<string name="show_password">Afficher le mot de passe</string>
|
||||||
@@ -183,19 +185,23 @@
|
|||||||
<string name="underline">Souligné</string>
|
<string name="underline">Souligné</string>
|
||||||
<string name="unsupported_db_version">Version de la base de données non supportée.</string>
|
<string name="unsupported_db_version">Version de la base de données non supportée.</string>
|
||||||
<string name="uppercase">Majuscule</string>
|
<string name="uppercase">Majuscule</string>
|
||||||
|
<string name="use_saf_summary">Utilisez l\'Environnement d\'Accès au Stockage d\'Android pour naviguer dans les fichiers (KitKat ou plus récent)</string>
|
||||||
|
<string name="use_saf_title">Environnement d\'Accès au Stockage</string>
|
||||||
|
<string name="warning">Alerte</string>
|
||||||
|
<string name="warning_password_encoding">Le format .kdb ne supporte que le jeu de caractère Latin1. Votre mot de passe doit contenir des caractères en dehors de ce jeu. Tous les caractères non-Latin1 sont convertis en un même caractère, ce qui diminue la sécurité de votre mot de passe. Le changement de votre mot de passe est recommandé.</string>
|
||||||
<string name="warning_read_only">Votre carte SD est actuellement en lecture seule. Vous ne pourrez pas enregistrer les changements dans la base de données.</string>
|
<string name="warning_read_only">Votre carte SD est actuellement en lecture seule. Vous ne pourrez pas enregistrer les changements dans la base de données.</string>
|
||||||
<string name="warning_unmounted">Votre carte SD n\'est actuellement pas montée sur votre appareil. Vous ne pourrez pas charger ou créer votre base de données.</string>
|
<string name="warning_unmounted">Votre carte SD n\'est actuellement pas montée sur votre appareil. Vous ne pourrez pas charger ou créer votre base de données.</string>
|
||||||
<string name="version_label">Version\u00A0:</string>
|
<string name="version_label">Version\\u00A0:</string>
|
||||||
|
|
||||||
<string-array name="clipboard_timeout_options">
|
<string-array name="clipboard_timeout_options">
|
||||||
<item>30 secondes</item>
|
<item>30 secondes</item>
|
||||||
<item>1 minute</item>
|
<item>1 minute</item>
|
||||||
<item>5 minutes</item>
|
<item>5 minutes</item>
|
||||||
<item>Jamais</item>
|
<item>Jamais</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
<string-array name="list_size_options">
|
<string-array name="list_size_options">
|
||||||
<item>Petit</item>
|
<item>Petit</item>
|
||||||
<item>Moyen</item>
|
<item>Moyen</item>
|
||||||
<item>Grand</item>
|
<item>Grand</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -56,7 +56,7 @@
|
|||||||
<string name="disclaimer_formal">"KeePass DX © 2009–2013
|
<string name="disclaimer_formal">"KeePass DX © 2009–2013
|
||||||
Разработчик Brian Pellin
|
Разработчик Brian Pellin
|
||||||
|
|
||||||
Программа предоставляется БЕЗ КАКИХ-ЛИБО ГАРАНТИЙ. Распространяется свободно по лицензии GPL v3 или новее"</string>
|
Программа предоставляется БЕЗ КАКИХ-ЛИБО ГАРАНТИЙ. Распространяется свободно по лицензии GPL v2 или новее"</string>
|
||||||
<string name="ellipsis">…</string>
|
<string name="ellipsis">…</string>
|
||||||
<string name="enter_filename">Путь к базе KeePass:</string>
|
<string name="enter_filename">Путь к базе KeePass:</string>
|
||||||
<string name="entry_accessed">Доступ:</string>
|
<string name="entry_accessed">Доступ:</string>
|
||||||
@@ -183,6 +183,10 @@
|
|||||||
<string name="underline">_Подчёркивание_</string>
|
<string name="underline">_Подчёркивание_</string>
|
||||||
<string name="unsupported_db_version">Неподдерживаемая версия базы</string>
|
<string name="unsupported_db_version">Неподдерживаемая версия базы</string>
|
||||||
<string name="uppercase">ЗАГЛАВНЫЕ</string>
|
<string name="uppercase">ЗАГЛАВНЫЕ</string>
|
||||||
|
<string name="use_saf_summary">Storage Access Framework для обзора файлов (KK+)</string>
|
||||||
|
<string name="use_saf_title">Обзор через SAF</string>
|
||||||
|
<string name="warning">Внимание</string>
|
||||||
|
<string name="warning_password_encoding">Формат .kdb поддерживает только кодировку Latin1. Пароль может содержать символы вне этой кодировки. Все не-Latin1 символы будут преобразованы в одинаковый символ, что снизит надёжность пароля. Рекомендуется изменить пароль.</string>
|
||||||
<string name="warning_read_only">Запись на карту памяти невозможна. Изменения не будут сохранены</string>
|
<string name="warning_read_only">Запись на карту памяти невозможна. Изменения не будут сохранены</string>
|
||||||
<string name="warning_unmounted">Карта памяти не подключена. Работа с базой невозможна</string>
|
<string name="warning_unmounted">Карта памяти не подключена. Работа с базой невозможна</string>
|
||||||
<string name="version_label">Версия:</string>
|
<string name="version_label">Версия:</string>
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
<color name="colorTextPrimary">#388e3c</color>
|
<color name="colorTextPrimary">#388e3c</color>
|
||||||
<color name="colorTextPrimaryInverse">#cccccc</color>
|
<color name="colorTextPrimaryInverse">#cccccc</color>
|
||||||
<color name="colorText">#424242</color>
|
<color name="colorText">#424242</color>
|
||||||
<color name="colorTextInverse">#bdbdbd</color>
|
<color name="colorTextInverse">#eeeeee</color>
|
||||||
<color name="colorPrimary">#43a047</color>
|
<color name="colorPrimary">#43a047</color>
|
||||||
<color name="colorPrimaryInverse">#bdbdbd</color>
|
<color name="colorPrimaryInverse">#bdbdbd</color>
|
||||||
<color name="colorPrimaryDark">#388e3c</color>
|
<color name="colorPrimaryDark">#388e3c</color>
|
||||||
|
|||||||
@@ -19,4 +19,9 @@
|
|||||||
<dimen name="default_margin">12dp</dimen>
|
<dimen name="default_margin">12dp</dimen>
|
||||||
<dimen name="button_margin">5dp</dimen>
|
<dimen name="button_margin">5dp</dimen>
|
||||||
<dimen name="fab_margin">24dp</dimen>
|
<dimen name="fab_margin">24dp</dimen>
|
||||||
|
<dimen name="margin_tiny">4dp</dimen>
|
||||||
|
<dimen name="margin_small">8dp</dimen>
|
||||||
|
<dimen name="margin_medium">16dp</dimen>
|
||||||
|
<dimen name="margin_large">32dp</dimen>
|
||||||
|
<dimen name="margin_huge">64dp</dimen>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -194,7 +194,14 @@
|
|||||||
<string name="warning_read_only">Your sd card is currently read-only. You may not be able to save changes to your database.</string>
|
<string name="warning_read_only">Your sd card is currently read-only. You may not be able to save changes to your database.</string>
|
||||||
<string name="warning_unmounted">Your sd card is not currently mounted on your device. You will not be able to load or create your database.</string>
|
<string name="warning_unmounted">Your sd card is not currently mounted on your device. You will not be able to load or create your database.</string>
|
||||||
<string name="version_label">Version:</string>
|
<string name="version_label">Version:</string>
|
||||||
|
<string name="configure_fingerprint">Fingerprint supported but not configured for device</string>
|
||||||
|
<string name="scanning_fingerprint">Listening for fingerprints</string>
|
||||||
|
<string name="encrypted_value_stored">Encrypted password stored</string>
|
||||||
|
<string name="fingerprint_invalid_key">Invalid Key problem</string>
|
||||||
|
<string name="fingerprint_error">Fingerprint problem</string>
|
||||||
|
<string name="store_with_fingerprint">Use fingerprint to store this password</string>
|
||||||
|
<string name="no_password_stored">No password stored yet for this database</string>
|
||||||
|
|
||||||
<string-array name="clipboard_timeout_options">
|
<string-array name="clipboard_timeout_options">
|
||||||
<item>30 seconds</item>
|
<item>30 seconds</item>
|
||||||
<item>1 minute</item>
|
<item>1 minute</item>
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
<item name="colorAccent">@color/colorAccent</item>
|
<item name="colorAccent">@color/colorAccent</item>
|
||||||
<item name="colorAccentCompat">@color/colorAccent</item>
|
<item name="colorAccentCompat">@color/colorAccent</item>
|
||||||
|
|
||||||
<item name="colorControlNormal">@color/white</item>
|
<item name="colorControlNormal">@color/colorTextInverse</item>
|
||||||
|
|
||||||
<item name="android:textAppearance">@style/KeepassoidStyle.TextAppearance</item>
|
<item name="android:textAppearance">@style/KeepassoidStyle.TextAppearance</item>
|
||||||
<item name="android:textAppearanceMedium">@style/KeepassoidStyle.TextAppearance</item>
|
<item name="android:textAppearanceMedium">@style/KeepassoidStyle.TextAppearance</item>
|
||||||
@@ -52,63 +52,62 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<!-- Button Style -->
|
<!-- Button Style -->
|
||||||
<style name="ButtonStyle" parent="@android:style/Widget.Button">
|
<style name="ButtonStyle" parent="android:style/Widget.Button">
|
||||||
<item name="android:textColor">@color/white</item>
|
<item name="android:textColor">@color/colorTextInverse</item>
|
||||||
<item name="android:background">@drawable/button_background</item>
|
<item name="android:background">@drawable/button_background</item>
|
||||||
</style>
|
</style>
|
||||||
<style name="ImageButtonStyle" parent="@android:style/Widget.ImageButton">
|
<style name="ImageButtonStyle" parent="android:style/Widget.ImageButton">
|
||||||
<item name="android:textColor">@color/white</item>
|
<item name="android:textColor">@color/colorTextInverse</item>
|
||||||
<item name="android:background">@drawable/button_background</item>
|
<item name="android:background">@drawable/button_background</item>
|
||||||
</style>
|
</style>
|
||||||
<style name="ButtonText" parent="@android:style/Widget.Button">
|
<style name="ButtonText" parent="android:style/Widget.Button">
|
||||||
<item name="android:textColor">@color/white</item>
|
<item name="android:textColor">@color/colorTextInverse</item>
|
||||||
<item name="android:background">@drawable/button_background</item>
|
<item name="android:background">@drawable/button_background</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<!-- Text Style -->
|
<!-- Text Style -->
|
||||||
<style name="KeepassoidStyle.TextAppearance" parent="@android:style/TextAppearance">
|
<style name="KeepassoidStyle.TextAppearance" parent="android:style/TextAppearance">
|
||||||
<item name="android:textColor">@color/colorText</item>
|
<item name="android:textColor">@color/colorText</item>
|
||||||
</style>
|
</style>
|
||||||
<style name="KeepassoidStyle.TextAppearance.Inverse" parent="@android:style/TextAppearance">
|
<style name="KeepassoidStyle.TextAppearance.Inverse" parent="android:style/TextAppearance">
|
||||||
<item name="android:textColor">@color/colorTextInverse</item>
|
<item name="android:textColor">@color/colorTextInverse</item>
|
||||||
</style>
|
</style>
|
||||||
<style name="KeepassoidStyle.TextAppearance.DefaultTextOnPrimary" parent="@android:style/TextAppearance">
|
<style name="KeepassoidStyle.TextAppearance.DefaultTextOnPrimary" parent="android:style/TextAppearance">
|
||||||
<item name="android:textColor">@color/white</item>
|
<item name="android:textColor">@color/colorTextInverse</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="ElementTextTitle" parent="android:TextAppearance">
|
<style name="ElementTextTitle" parent="android:TextAppearance">
|
||||||
<item name="@android:textColor">@color/colorText</item>
|
<item name="android:textColor">@color/colorText</item>
|
||||||
</style>
|
</style>
|
||||||
<style name="TinyTextStyle">
|
<style name="TinyTextStyle">
|
||||||
<item name="@android:textSize">12sp</item>
|
<item name="android:textSize">12sp</item>
|
||||||
</style>
|
</style>
|
||||||
<style name="SmallTextStyle" parent="android:TextAppearance">
|
<style name="SmallTextStyle" parent="android:TextAppearance">
|
||||||
<item name="android:textColor">@color/white</item>
|
<item name="android:textColor">@color/colorText</item>
|
||||||
<item name="@android:textColor">@color/colorText</item>
|
<item name="android:textSize">15sp</item>
|
||||||
<item name="@android:textSize">15sp</item>
|
|
||||||
</style>
|
</style>
|
||||||
<style name="DefaultTextStyle" parent="android:TextAppearance">
|
<style name="DefaultTextStyle" parent="android:TextAppearance">
|
||||||
<item name="@android:textColor">@color/colorText</item>
|
<item name="android:textColor">@color/colorText</item>
|
||||||
<item name="@android:textSize">16sp</item>
|
<item name="android:textSize">16sp</item>
|
||||||
</style>
|
</style>
|
||||||
<style name="TitleTextStyle" parent="android:TextAppearance">
|
<style name="TitleTextStyle" parent="android:TextAppearance">
|
||||||
<item name="@android:textColor">@color/colorTextPrimary</item>
|
<item name="android:textColor">@color/colorTextPrimary</item>
|
||||||
<item name="@android:textSize">16sp</item>
|
<item name="android:textSize">16sp</item>
|
||||||
</style>
|
</style>
|
||||||
<style name="SecondaryText">
|
<style name="SecondaryText">
|
||||||
<item name="@android:textSize">14sp</item>
|
<item name="android:textSize">14sp</item>
|
||||||
</style>
|
</style>
|
||||||
<style name="WarningTextStyle" parent="android:TextAppearance">
|
<style name="WarningTextStyle" parent="android:TextAppearance">
|
||||||
<item name="@android:textColor">@color/colorAccent</item>
|
<item name="android:textColor">@color/colorAccent</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<!-- Form -->
|
<!-- Form -->
|
||||||
<style name="KeepassoidStyle.TextAppearance.LabelTextStyle" parent="android:TextAppearance">
|
<style name="KeepassoidStyle.TextAppearance.LabelTextStyle" parent="android:TextAppearance">
|
||||||
<item name="@android:textSize">12sp</item>
|
<item name="android:textSize">12sp</item>
|
||||||
</style>
|
</style>
|
||||||
<style name="KeepassoidStyle.TextAppearance.TextEntryItem">
|
<style name="KeepassoidStyle.TextAppearance.TextEntryItem">
|
||||||
<item name="@android:padding">5sp</item>
|
<item name="android:padding">5sp</item>
|
||||||
<item name="@android:textSize">16sp</item>
|
<item name="android:textSize">16sp</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="Divider">
|
<style name="Divider">
|
||||||
@@ -120,13 +119,13 @@
|
|||||||
|
|
||||||
<!-- Toolbar Style -->
|
<!-- Toolbar Style -->
|
||||||
<style name="KeepassoidStyle.Toolbar.Dark" parent="Theme.AppCompat.Light.DarkActionBar">
|
<style name="KeepassoidStyle.Toolbar.Dark" parent="Theme.AppCompat.Light.DarkActionBar">
|
||||||
<item name="colorControlNormal">@android:color/white</item>
|
<item name="colorControlNormal">@color/colorTextInverse</item>
|
||||||
|
|
||||||
<item name="colorPrimary">@color/colorPrimary</item>
|
<item name="colorPrimary">@color/colorPrimary</item>
|
||||||
|
|
||||||
<item name="android:textColorPrimary">@android:color/white</item>
|
<item name="android:textColorPrimary">@color/colorTextInverse</item>
|
||||||
<item name="android:textColorSecondary">@android:color/white</item>
|
<item name="android:textColorSecondary">@color/colorTextInverse</item>
|
||||||
|
|
||||||
<item name="android:colorBackground">@android:color/white</item>
|
<item name="android:colorBackground">@color/colorTextInverse</item>
|
||||||
</style>
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -8,7 +8,7 @@ buildscript {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:2.3.3'
|
classpath 'com.android.tools.build:gradle:3.0.0'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,4 +1,4 @@
|
|||||||
#Wed Oct 25 20:52:47 CEST 2017
|
#Sat Oct 28 10:13:14 CDT 2017
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
|||||||
Reference in New Issue
Block a user