Merge branch 'extract-database' of github.com:GianpaMX/KeePassDX into GianpaMX-extract-database

This commit is contained in:
J-Jamet 2023-05-08 15:19:32 +02:00
commit 48cc8b3f75
207 changed files with 1469 additions and 862 deletions

1
database/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/build

62
database/build.gradle Normal file
View file

@ -0,0 +1,62 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-parcelize'
apply plugin: 'kotlin-kapt'
android {
compileSdkVersion 32
buildToolsVersion "32.0.0"
ndkVersion "21.4.7075529"
defaultConfig {
minSdkVersion 15
targetSdk 32
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro"
kapt {
arguments {
arg("room.incremental", "true")
arg("room.schemaLocation", "$projectDir/schemas".toString())
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
def room_version = "2.4.3"
dependencies {
implementation "androidx.core:core-ktx:$android_core_version"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4'
// Color
implementation 'com.github.Kunzisoft:AndroidClearChroma:2.6'
// Time
implementation 'joda-time:joda-time:2.10.13'
// Apache Commons
implementation 'commons-io:commons-io:2.8.0'
implementation 'commons-codec:commons-codec:1.15'
// Database
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"
implementation project(path: ':crypto')
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
}

View file

21
database/proguard-rules.pro vendored Normal file
View file

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View file

@ -0,0 +1,84 @@
{
"formatVersion": 1,
"database": {
"version": 1,
"identityHash": "56438e5f7372ef3e36e33b782aed245d",
"entities": [
{
"tableName": "file_database_history",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`database_uri` TEXT NOT NULL, `database_alias` TEXT NOT NULL, `keyfile_uri` TEXT, `updated` INTEGER NOT NULL, PRIMARY KEY(`database_uri`))",
"fields": [
{
"fieldPath": "databaseUri",
"columnName": "database_uri",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "databaseAlias",
"columnName": "database_alias",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "keyFileUri",
"columnName": "keyfile_uri",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "updated",
"columnName": "updated",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"database_uri"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "cipher_database",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`database_uri` TEXT NOT NULL, `encrypted_value` TEXT NOT NULL, `specs_parameters` TEXT NOT NULL, PRIMARY KEY(`database_uri`))",
"fields": [
{
"fieldPath": "databaseUri",
"columnName": "database_uri",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "encryptedValue",
"columnName": "encrypted_value",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "specParameters",
"columnName": "specs_parameters",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"database_uri"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '56438e5f7372ef3e36e33b782aed245d')"
]
}
}

View file

@ -0,0 +1,90 @@
{
"formatVersion": 1,
"database": {
"version": 2,
"identityHash": "f8fb4aed546de19ae7ca0797f49b26a4",
"entities": [
{
"tableName": "file_database_history",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`database_uri` TEXT NOT NULL, `database_alias` TEXT NOT NULL, `keyfile_uri` TEXT, `hardware_key` TEXT, `updated` INTEGER NOT NULL, PRIMARY KEY(`database_uri`))",
"fields": [
{
"fieldPath": "databaseUri",
"columnName": "database_uri",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "databaseAlias",
"columnName": "database_alias",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "keyFileUri",
"columnName": "keyfile_uri",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "hardwareKey",
"columnName": "hardware_key",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "updated",
"columnName": "updated",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"database_uri"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "cipher_database",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`database_uri` TEXT NOT NULL, `encrypted_value` TEXT NOT NULL, `specs_parameters` TEXT NOT NULL, PRIMARY KEY(`database_uri`))",
"fields": [
{
"fieldPath": "databaseUri",
"columnName": "database_uri",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "encryptedValue",
"columnName": "encrypted_value",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "specParameters",
"columnName": "specs_parameters",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"database_uri"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'f8fb4aed546de19ae7ca0797f49b26a4')"
]
}
}

View file

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="com.kunzisoft.keepass.database" />

View file

@ -0,0 +1,48 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.app.database
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import android.content.Context
import androidx.room.AutoMigration
@Database(
version = 2,
entities = [FileDatabaseHistoryEntity::class, CipherDatabaseEntity::class],
autoMigrations = [
AutoMigration (from = 1, to = 2)
]
)
abstract class AppDatabase : RoomDatabase() {
abstract fun fileDatabaseHistoryDao(): FileDatabaseHistoryDao
abstract fun cipherDatabaseDao(): CipherDatabaseDao
companion object {
fun getDatabase(applicationContext: Context): AppDatabase {
return Room.databaseBuilder(
applicationContext,
AppDatabase::class.java, "com.kunzisoft.keepass.database"
).build()
}
}
}

View file

@ -0,0 +1,41 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.app.database
import androidx.room.*
@Dao
interface CipherDatabaseDao {
@Query("SELECT * FROM cipher_database WHERE database_uri = :databaseUriString")
fun getByDatabaseUri(databaseUriString: String): CipherDatabaseEntity?
@Insert
fun add(vararg fileDatabaseHistory: CipherDatabaseEntity)
@Update
fun update(vararg fileDatabaseHistory: CipherDatabaseEntity)
@Query("DELETE FROM cipher_database WHERE database_uri = :databaseUriString")
fun deleteByDatabaseUri(databaseUriString: String)
@Query("DELETE FROM cipher_database")
fun deleteAll()
}

View file

@ -0,0 +1,85 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.app.database
import android.os.Parcel
import android.os.Parcelable
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "cipher_database")
data class CipherDatabaseEntity(
@PrimaryKey
@ColumnInfo(name = "database_uri")
val databaseUri: String,
@ColumnInfo(name = "encrypted_value")
var encryptedValue: String,
@ColumnInfo(name = "specs_parameters")
var specParameters: String
): Parcelable {
constructor(parcel: Parcel) : this(
parcel.readString()!!,
parcel.readString()!!,
parcel.readString()!!)
fun replaceContent(copy: CipherDatabaseEntity) {
this.encryptedValue = copy.encryptedValue
this.specParameters = copy.specParameters
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(databaseUri)
parcel.writeString(encryptedValue)
parcel.writeString(specParameters)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<CipherDatabaseEntity> {
override fun createFromParcel(parcel: Parcel): CipherDatabaseEntity {
return CipherDatabaseEntity(parcel)
}
override fun newArray(size: Int): Array<CipherDatabaseEntity?> {
return arrayOfNulls(size)
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as CipherDatabaseEntity
if (databaseUri != other.databaseUri) return false
return true
}
override fun hashCode(): Int {
return databaseUri.hashCode()
}
}

View file

@ -0,0 +1,49 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.app.database
import androidx.room.*
@Dao
interface FileDatabaseHistoryDao {
@Query("SELECT * FROM file_database_history ORDER BY updated DESC")
fun getAll(): List<FileDatabaseHistoryEntity>
@Query("SELECT * FROM file_database_history WHERE database_uri = :databaseUriString")
fun getByDatabaseUri(databaseUriString: String): FileDatabaseHistoryEntity?
@Insert
fun add(vararg fileDatabaseHistory: FileDatabaseHistoryEntity)
@Update
fun update(vararg fileDatabaseHistory: FileDatabaseHistoryEntity)
@Delete
fun delete(fileDatabaseHistory: FileDatabaseHistoryEntity): Int
@Query("UPDATE file_database_history SET keyfile_uri=null WHERE database_uri = :databaseUriString")
fun deleteKeyFileByDatabaseUri(databaseUriString: String)
@Query("UPDATE file_database_history SET keyfile_uri=null")
fun deleteAllKeyFiles()
@Query("DELETE FROM file_database_history")
fun deleteAll()
}

View file

@ -0,0 +1,58 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.app.database
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "file_database_history")
data class FileDatabaseHistoryEntity(
@PrimaryKey
@ColumnInfo(name = "database_uri")
val databaseUri: String,
@ColumnInfo(name = "database_alias")
var databaseAlias: String,
@ColumnInfo(name = "keyfile_uri")
var keyFileUri: String?,
@ColumnInfo(name = "hardware_key")
var hardwareKey: String?,
@ColumnInfo(name = "updated")
val updated: Long
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as FileDatabaseHistoryEntity
if (databaseUri != other.databaseUri) return false
return true
}
override fun hashCode(): Int {
return databaseUri.hashCode()
}
}

View file

@ -0,0 +1,50 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.app.database
import kotlinx.coroutines.*
/**
* Class to invoke action in a separate IO thread
*/
class IOActionTask<T>(
private val action: () -> T ,
private val afterActionListener: ((T?) -> Unit)? = null) {
private val mainScope = CoroutineScope(Dispatchers.Main)
fun execute() {
mainScope.launch {
withContext(Dispatchers.IO) {
val asyncResult: Deferred<T?> = async {
try {
action.invoke()
} catch (e: Exception) {
e.printStackTrace()
null
}
}
withContext(Dispatchers.Main) {
afterActionListener?.invoke(asyncResult.await())
}
}
}
}
}

View file

@ -0,0 +1,77 @@
/*
* Copyright 2021 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.action
import android.content.Context
import android.net.Uri
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.MainCredential
import com.kunzisoft.keepass.database.element.binary.BinaryData
import com.kunzisoft.keepass.database.exception.DatabaseException
import com.kunzisoft.keepass.hardware.HardwareKey
import com.kunzisoft.keepass.settings.DatabasePreferencesUtil
import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
class MergeDatabaseRunnable(
context: Context,
private val mDatabaseToMergeUri: Uri?,
private val mDatabaseToMergeMainCredential: MainCredential?,
private val mDatabaseToMergeChallengeResponseRetriever: (HardwareKey, ByteArray?) -> ByteArray,
database: Database,
saveDatabase: Boolean,
challengeResponseRetriever: (HardwareKey, ByteArray?) -> ByteArray,
private val progressTaskUpdater: ProgressTaskUpdater?,
private val mLoadDatabaseResult: ((Result) -> Unit)?)
: SaveDatabaseRunnable(context, database, saveDatabase, null, challengeResponseRetriever) {
override fun onStartRun() {
database.wasReloaded = true
super.onStartRun()
}
override fun onActionRun() {
try {
database.mergeData(
context.contentResolver,
mDatabaseToMergeUri,
mDatabaseToMergeMainCredential,
mDatabaseToMergeChallengeResponseRetriever,
{ memoryWanted ->
BinaryData.canMemoryBeAllocatedInRAM(context, memoryWanted)
},
progressTaskUpdater
)
} catch (e: DatabaseException) {
setError(e)
}
if (result.isSuccess) {
// Register the current time to init the lock timer
DatabasePreferencesUtil.saveCurrentTime(context)
}
super.onActionRun()
}
override fun onFinishRun() {
super.onFinishRun()
mLoadDatabaseResult?.invoke(result)
}
}

View file

@ -0,0 +1,67 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.action
import android.content.Context
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.binary.BinaryData
import com.kunzisoft.keepass.database.exception.DatabaseException
import com.kunzisoft.keepass.settings.DatabasePreferencesUtil
import com.kunzisoft.keepass.tasks.ActionRunnable
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
import com.kunzisoft.keepass.utils.UriUtilDatabase
class ReloadDatabaseRunnable(
private val context: Context,
private val mDatabase: Database,
private val progressTaskUpdater: ProgressTaskUpdater?,
private val mLoadDatabaseResult: ((Result) -> Unit)?,
) : ActionRunnable() {
override fun onStartRun() {
// Clear before we load
mDatabase.clearIndexesAndBinaries(UriUtilDatabase.getBinaryDir(
context))
mDatabase.wasReloaded = true
}
override fun onActionRun() {
try {
mDatabase.reloadData(context.contentResolver,
{ memoryWanted ->
BinaryData.canMemoryBeAllocatedInRAM(context, memoryWanted)
},
progressTaskUpdater)
} catch (e: DatabaseException) {
setError(e)
}
if (result.isSuccess) {
// Register the current time to init the lock timer
DatabasePreferencesUtil.saveCurrentTime(context)
} else {
mDatabase.clearAndClose(context)
}
}
override fun onFinishRun() {
mLoadDatabaseResult?.invoke(result)
}
}

View file

@ -0,0 +1,42 @@
/*
* Copyright 2020 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.action
import android.content.Context
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.hardware.HardwareKey
class RemoveUnlinkedDataDatabaseRunnable (
context: Context,
database: Database,
saveDatabase: Boolean,
challengeResponseRetriever: (HardwareKey, ByteArray?) -> ByteArray)
: SaveDatabaseRunnable(context, database, saveDatabase, null, challengeResponseRetriever) {
override fun onActionRun() {
try {
database.removeUnlinkedAttachments()
} catch (e: Exception) {
setError(e)
}
super.onActionRun()
}
}

View file

@ -0,0 +1,62 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.action
import android.content.Context
import android.net.Uri
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.exception.DatabaseException
import com.kunzisoft.keepass.hardware.HardwareKey
import com.kunzisoft.keepass.database.element.MainCredential
import com.kunzisoft.keepass.tasks.ActionRunnable
open class SaveDatabaseRunnable(protected var context: Context,
protected var database: Database,
private var saveDatabase: Boolean,
private var mainCredential: MainCredential?, // If null, uses composite Key
private var challengeResponseRetriever: (HardwareKey, ByteArray?) -> ByteArray,
private var databaseCopyUri: Uri? = null)
: ActionRunnable() {
var mAfterSaveDatabase: ((Result) -> Unit)? = null
override fun onStartRun() {}
override fun onActionRun() {
database.checkVersion()
if (saveDatabase && result.isSuccess) {
try {
database.saveData(
context.contentResolver,
context.cacheDir,
databaseCopyUri,
mainCredential,
challengeResponseRetriever)
} catch (e: DatabaseException) {
setError(e)
}
}
}
override fun onFinishRun() {
// Need to call super.onFinishRun() in child class
mAfterSaveDatabase?.invoke(result)
}
}

View file

@ -0,0 +1,68 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.action
import android.content.Context
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
import com.kunzisoft.keepass.hardware.HardwareKey
class UpdateCompressionBinariesDatabaseRunnable (
context: Context,
database: Database,
private val oldCompressionAlgorithm: CompressionAlgorithm,
private val newCompressionAlgorithm: CompressionAlgorithm,
saveDatabase: Boolean,
challengeResponseRetriever: (HardwareKey, ByteArray?) -> ByteArray)
: SaveDatabaseRunnable(context, database, saveDatabase, null, challengeResponseRetriever) {
override fun onStartRun() {
// Set new compression
if (database.allowDataCompression) {
try {
database.apply {
updateDataBinaryCompression(oldCompressionAlgorithm, newCompressionAlgorithm)
compressionAlgorithm = newCompressionAlgorithm
}
} catch (e: Exception) {
setError(e)
}
}
super.onStartRun()
}
override fun onFinishRun() {
super.onFinishRun()
if (database.allowDataCompression) {
if (!result.isSuccess) {
try {
database.apply {
compressionAlgorithm = oldCompressionAlgorithm
updateDataBinaryCompression(newCompressionAlgorithm, oldCompressionAlgorithm)
}
} catch (e: Exception) {
setError(e)
}
}
}
}
}

View file

@ -0,0 +1,46 @@
/*
* Copyright 2020 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.action.history
import android.content.Context
import com.kunzisoft.keepass.database.action.SaveDatabaseRunnable
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.hardware.HardwareKey
class DeleteEntryHistoryDatabaseRunnable (
context: Context,
database: Database,
private val mainEntry: Entry,
private val entryHistoryPosition: Int,
saveDatabase: Boolean,
challengeResponseRetriever: (HardwareKey, ByteArray?) -> ByteArray)
: SaveDatabaseRunnable(context, database, saveDatabase, null, challengeResponseRetriever) {
override fun onStartRun() {
try {
database.removeEntryHistory(mainEntry, entryHistoryPosition)
} catch (e: Exception) {
setError(e)
}
super.onStartRun()
}
}

View file

@ -0,0 +1,72 @@
/*
* Copyright 2020 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.action.history
import android.content.Context
import com.kunzisoft.keepass.database.action.node.UpdateEntryRunnable
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.hardware.HardwareKey
import com.kunzisoft.keepass.tasks.ActionRunnable
class RestoreEntryHistoryDatabaseRunnable (
private val context: Context,
private val database: Database,
private val mainEntry: Entry,
private val entryHistoryPosition: Int,
private val saveDatabase: Boolean,
private val challengeResponseRetriever: (HardwareKey, ByteArray?) -> ByteArray)
: ActionRunnable() {
private var updateEntryRunnable: UpdateEntryRunnable? = null
override fun onStartRun() {
try {
val historyToRestore = Entry(mainEntry.getHistory()[entryHistoryPosition])
// Copy history of main entry in the restore entry
mainEntry.getHistory().forEach {
historyToRestore.addEntryToHistory(it)
}
// Update the entry with the fresh formatted entry to restore
updateEntryRunnable = UpdateEntryRunnable(
context,
database,
mainEntry,
historyToRestore,
saveDatabase,
null,
challengeResponseRetriever
)
updateEntryRunnable?.onStartRun()
} catch (e: Exception) {
setError(e)
}
}
override fun onActionRun() {
updateEntryRunnable?.onActionRun()
}
override fun onFinishRun() {
updateEntryRunnable?.onFinishRun()
}
}

View file

@ -0,0 +1,60 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.action.node
import android.content.Context
import com.kunzisoft.keepass.database.action.SaveDatabaseRunnable
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.hardware.HardwareKey
abstract class ActionNodeDatabaseRunnable(
context: Context,
database: Database,
private val afterActionNodesFinish: AfterActionNodesFinish?,
save: Boolean,
challengeResponseRetriever: (HardwareKey, ByteArray?) -> ByteArray)
: SaveDatabaseRunnable(context, database, save, null, challengeResponseRetriever) {
/**
* Function do to a node action
*/
abstract fun nodeAction()
override fun onStartRun() {
try {
nodeAction()
} catch (e: Exception) {
setError(e)
}
super.onStartRun()
}
/**
* Function do get the finish node action
*/
abstract fun nodeFinish(): ActionNodesValues
override fun onFinishRun() {
super.onFinishRun()
afterActionNodesFinish?.apply {
onActionNodesFinish(result, nodeFinish())
}
}
}

View file

@ -0,0 +1,57 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.action.node
import android.content.Context
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.hardware.HardwareKey
class AddEntryRunnable constructor(
context: Context,
database: Database,
private val mNewEntry: Entry,
private val mParent: Group,
save: Boolean,
afterActionNodesFinish: AfterActionNodesFinish?,
challengeResponseRetriever: (HardwareKey, ByteArray?) -> ByteArray)
: ActionNodeDatabaseRunnable(context, database, afterActionNodesFinish, save, challengeResponseRetriever) {
override fun nodeAction() {
mNewEntry.touch(modified = true, touchParents = true)
mParent.touch(modified = true, touchParents = true)
database.addEntryTo(mNewEntry, mParent)
}
override fun nodeFinish(): ActionNodesValues {
if (!result.isSuccess) {
mNewEntry.parent?.let {
database.removeEntryFrom(mNewEntry, it)
}
}
val oldNodesReturn = ArrayList<Node>()
val newNodesReturn = ArrayList<Node>()
newNodesReturn.add(mNewEntry)
return ActionNodesValues(oldNodesReturn, newNodesReturn)
}
}

View file

@ -0,0 +1,54 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.action.node
import android.content.Context
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.hardware.HardwareKey
class AddGroupRunnable constructor(
context: Context,
database: Database,
private val mNewGroup: Group,
private val mParent: Group,
save: Boolean,
afterActionNodesFinish: AfterActionNodesFinish?,
challengeResponseRetriever: (HardwareKey, ByteArray?) -> ByteArray)
: ActionNodeDatabaseRunnable(context, database, afterActionNodesFinish, save, challengeResponseRetriever) {
override fun nodeAction() {
mNewGroup.touch(modified = true, touchParents = true)
mParent.touch(modified = true, touchParents = true)
database.addGroupTo(mNewGroup, mParent)
}
override fun nodeFinish(): ActionNodesValues {
if (!result.isSuccess) {
database.removeGroupFrom(mNewGroup, mParent)
}
val oldNodesReturn = ArrayList<Node>()
val newNodesReturn = ArrayList<Node>()
newNodesReturn.add(mNewGroup)
return ActionNodesValues(oldNodesReturn, newNodesReturn)
}
}

View file

@ -0,0 +1,37 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.action.node
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.tasks.ActionRunnable
/**
* Callback method who return the node(s) modified after an action
* - Add : @param oldNodes empty, @param newNodes CreatedNodes
* - Copy : @param oldNodes NodesToCopy, @param newNodes NodesCopied
* - Delete : @param oldNodes NodesToDelete, @param newNodes empty
* - Move : @param oldNodes empty, @param newNodes NodesToMove
* - Update : @param oldNodes NodesToUpdate, @param newNodes NodesUpdated
*/
class ActionNodesValues(val oldNodes: List<Node>, val newNodes: List<Node>)
abstract class AfterActionNodesFinish {
abstract fun onActionNodesFinish(result: ActionRunnable.Result, actionNodesValues: ActionNodesValues)
}

View file

@ -0,0 +1,89 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.action.node
import android.content.Context
import android.util.Log
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.database.exception.CopyEntryDatabaseException
import com.kunzisoft.keepass.database.exception.CopyGroupDatabaseException
import com.kunzisoft.keepass.hardware.HardwareKey
class CopyNodesRunnable constructor(
context: Context,
database: Database,
private val mNodesToCopy: List<Node>,
private val mNewParent: Group,
save: Boolean,
afterActionNodesFinish: AfterActionNodesFinish?,
challengeResponseRetriever: (HardwareKey, ByteArray?) -> ByteArray)
: ActionNodeDatabaseRunnable(context, database, afterActionNodesFinish, save, challengeResponseRetriever) {
private var mEntriesCopied = ArrayList<Entry>()
override fun nodeAction() {
foreachNode@ for(currentNode in mNodesToCopy) {
when (currentNode.type) {
Type.GROUP -> {
Log.e(TAG, "Copy not allowed for group")// Only finish thread
setError(CopyGroupDatabaseException())
break@foreachNode
}
Type.ENTRY -> {
// Root can contains entry
if (mNewParent != database.rootGroup || database.rootCanContainsEntry()) {
// Update entry with new values
mNewParent.touch(modified = false, touchParents = true)
val entryCopied = database.copyEntryTo(currentNode as Entry, mNewParent)
entryCopied.touch(modified = true, touchParents = true)
mEntriesCopied.add(entryCopied)
} else {
// Only finish thread
setError(CopyEntryDatabaseException())
break@foreachNode
}
}
}
}
}
override fun nodeFinish(): ActionNodesValues {
if (!result.isSuccess) {
// If we fail to save, try to delete the copy
mEntriesCopied.forEach {
try {
database.deleteEntry(it)
} catch (e: Exception) {
Log.i(TAG, "Unable to delete the copied entry")
}
}
}
return ActionNodesValues(mNodesToCopy, mEntriesCopied)
}
companion object {
private val TAG = CopyNodesRunnable::class.java.name
}
}

View file

@ -0,0 +1,113 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.action.node
import android.content.Context
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.hardware.HardwareKey
class DeleteNodesRunnable(context: Context,
database: Database,
private val mNodesToDelete: List<Node>,
private val recyclerBinTitle: String,
save: Boolean,
afterActionNodesFinish: AfterActionNodesFinish,
challengeResponseRetriever: (HardwareKey, ByteArray?) -> ByteArray)
: ActionNodeDatabaseRunnable(context, database, afterActionNodesFinish, save, challengeResponseRetriever) {
private var mOldParent: Group? = null
private var mCanRecycle: Boolean = false
private var mNodesToDeleteBackup = ArrayList<Node>()
override fun nodeAction() {
foreachNode@ for(nodeToDelete in mNodesToDelete) {
mOldParent = nodeToDelete.parent
nodeToDelete.touch(modified = true, touchParents = true)
when (nodeToDelete.type) {
Type.GROUP -> {
val groupToDelete = nodeToDelete as Group
// Create a copy to keep the old ref and remove it visually
mNodesToDeleteBackup.add(Group(groupToDelete))
// Remove Node from parent
mCanRecycle = database.canRecycle(groupToDelete)
if (mCanRecycle) {
database.recycle(groupToDelete, recyclerBinTitle)
groupToDelete.setPreviousParentGroup(mOldParent)
groupToDelete.touch(modified = true, touchParents = true)
} else {
database.deleteGroup(groupToDelete)
}
}
Type.ENTRY -> {
val entryToDelete = nodeToDelete as Entry
// Create a copy to keep the old ref and remove it visually
mNodesToDeleteBackup.add(Entry(entryToDelete))
// Remove Node from parent
mCanRecycle = database.canRecycle(entryToDelete)
if (mCanRecycle) {
database.recycle(entryToDelete, recyclerBinTitle)
entryToDelete.setPreviousParentGroup(mOldParent)
entryToDelete.touch(modified = true, touchParents = true)
} else {
database.deleteEntry(entryToDelete)
}
// Remove the oldest attachments
entryToDelete.getAttachments(database.attachmentPool).forEach {
database.removeAttachmentIfNotUsed(it)
}
}
}
}
}
override fun nodeFinish(): ActionNodesValues {
if (!result.isSuccess) {
if (mCanRecycle) {
mOldParent?.let {
mNodesToDeleteBackup.forEach { backupNode ->
when (backupNode.type) {
Type.GROUP -> {
database.undoRecycle(backupNode as Group, it)
}
Type.ENTRY -> {
database.undoRecycle(backupNode as Entry, it)
}
}
}
}
}
// else {
// Let's not bother recovering from a failure to save a deleted tree. It is too much work.
// TODO database.undoDeleteGroupFrom(mGroup, mParent);
// }
}
// Return a copy of unchanged nodes as old param
// and nodes deleted or moved in recycle bin as new param
return ActionNodesValues(mNodesToDeleteBackup, mNodesToDelete)
}
}

View file

@ -0,0 +1,111 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.action.node
import android.content.Context
import android.util.Log
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.database.exception.MoveEntryDatabaseException
import com.kunzisoft.keepass.database.exception.MoveGroupDatabaseException
import com.kunzisoft.keepass.hardware.HardwareKey
class MoveNodesRunnable constructor(
context: Context,
database: Database,
private val mNodesToMove: List<Node>,
private val mNewParent: Group,
save: Boolean,
afterActionNodesFinish: AfterActionNodesFinish?,
challengeResponseRetriever: (HardwareKey, ByteArray?) -> ByteArray)
: ActionNodeDatabaseRunnable(context, database, afterActionNodesFinish, save, challengeResponseRetriever) {
private var mOldParent: Group? = null
override fun nodeAction() {
foreachNode@ for(nodeToMove in mNodesToMove) {
// Move node in new parent
mOldParent = nodeToMove.parent
nodeToMove.touch(modified = true, touchParents = true)
when (nodeToMove.type) {
Type.GROUP -> {
val groupToMove = nodeToMove as Group
// Move group if the parent change
if (mOldParent != mNewParent
// and if not in the current group
&& groupToMove != mNewParent
&& !mNewParent.isContainedIn(groupToMove)) {
database.moveGroupTo(groupToMove, mNewParent)
groupToMove.setPreviousParentGroup(mOldParent)
groupToMove.touch(modified = true, touchParents = true)
} else {
// Only finish thread
setError(MoveGroupDatabaseException())
break@foreachNode
}
}
Type.ENTRY -> {
val entryToMove = nodeToMove as Entry
// Move only if the parent change
if (mOldParent != mNewParent
// and root can contains entry
&& (mNewParent != database.rootGroup || database.rootCanContainsEntry())) {
database.moveEntryTo(entryToMove, mNewParent)
entryToMove.setPreviousParentGroup(mOldParent)
entryToMove.touch(modified = true, touchParents = true)
} else {
// Only finish thread
setError(MoveEntryDatabaseException())
break@foreachNode
}
}
}
}
}
override fun nodeFinish(): ActionNodesValues {
if (!result.isSuccess) {
try {
mNodesToMove.forEach { nodeToMove ->
// If we fail to save, try to move in the first place
if (mOldParent != null &&
mOldParent != nodeToMove.parent) {
when (nodeToMove.type) {
Type.GROUP -> database.moveGroupTo(nodeToMove as Group, mOldParent!!)
Type.ENTRY -> database.moveEntryTo(nodeToMove as Entry, mOldParent!!)
}
}
}
} catch (e: Exception) {
Log.i(TAG, "Unable to replace the node")
}
}
return ActionNodesValues(ArrayList(), mNodesToMove)
}
companion object {
private val TAG = MoveNodesRunnable::class.java.name
}
}

View file

@ -0,0 +1,29 @@
/*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.action.node
/** "Delegate" class for operating on each group when traversing all of
* them
* @author bpellin
*/
abstract class NodeHandler<T> {
abstract fun operate(node: T): Boolean
}

View file

@ -0,0 +1,87 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.action.node
import android.content.Context
import com.kunzisoft.keepass.database.element.Attachment
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.hardware.HardwareKey
class UpdateEntryRunnable constructor(
context: Context,
database: Database,
private val mOldEntry: Entry,
private val mNewEntry: Entry,
save: Boolean,
afterActionNodesFinish: AfterActionNodesFinish?,
challengeResponseRetriever: (HardwareKey, ByteArray?) -> ByteArray)
: ActionNodeDatabaseRunnable(context, database, afterActionNodesFinish, save, challengeResponseRetriever) {
override fun nodeAction() {
if (mOldEntry.nodeId == mNewEntry.nodeId) {
// WARNING : Re attribute parent removed in entry edit activity to save memory
mNewEntry.addParentFrom(mOldEntry)
// Build oldest attachments
val oldEntryAttachments = mOldEntry.getAttachments(database.attachmentPool, true)
val newEntryAttachments = mNewEntry.getAttachments(database.attachmentPool, true)
val attachmentsToRemove = ArrayList<Attachment>(oldEntryAttachments)
// Not use equals because only check name
newEntryAttachments.forEach { newAttachment ->
oldEntryAttachments.forEach { oldAttachment ->
if (oldAttachment.name == newAttachment.name
&& oldAttachment.binaryData == newAttachment.binaryData
)
attachmentsToRemove.remove(oldAttachment)
}
}
// Update entry with new values
mNewEntry.touch(modified = true, touchParents = true)
// Create an entry history (an entry history don't have history)
mNewEntry.addEntryToHistory(Entry(mOldEntry, copyHistory = false))
database.removeOldestEntryHistory(mNewEntry, database.attachmentPool)
// Only change data in index
database.updateEntry(mNewEntry)
// Remove oldest attachments
attachmentsToRemove.forEach {
database.removeAttachmentIfNotUsed(it)
}
}
}
override fun nodeFinish(): ActionNodesValues {
if (!result.isSuccess) {
// If we fail to save, back out changes to global structure
database.updateEntry(mOldEntry)
}
val oldNodesReturn = ArrayList<Node>()
oldNodesReturn.add(mOldEntry)
val newNodesReturn = ArrayList<Node>()
newNodesReturn.add(mNewEntry)
return ActionNodesValues(oldNodesReturn, newNodesReturn)
}
}

View file

@ -0,0 +1,70 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.action.node
import android.content.Context
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.hardware.HardwareKey
class UpdateGroupRunnable constructor(
context: Context,
database: Database,
private val mOldGroup: Group,
private val mNewGroup: Group,
save: Boolean,
afterActionNodesFinish: AfterActionNodesFinish?,
challengeResponseRetriever: (HardwareKey, ByteArray?) -> ByteArray)
: ActionNodeDatabaseRunnable(context, database, afterActionNodesFinish, save, challengeResponseRetriever) {
override fun nodeAction() {
if (mOldGroup.nodeId == mNewGroup.nodeId) {
// WARNING : Re attribute parent and children removed in group activity to save memory
mNewGroup.addParentFrom(mOldGroup)
mNewGroup.addChildrenFrom(mOldGroup)
// Update group with new values
mNewGroup.touch(modified = true, touchParents = true)
if (database.rootGroup == mOldGroup) {
database.rootGroup = mNewGroup
}
// Only change data in index
database.updateGroup(mNewGroup)
}
}
override fun nodeFinish(): ActionNodesValues {
if (!result.isSuccess) {
// If we fail to save, back out changes to global structure
if (database.rootGroup == mNewGroup) {
database.rootGroup = mOldGroup
}
database.updateGroup(mOldGroup)
}
val oldNodesReturn = ArrayList<Node>()
oldNodesReturn.add(mOldGroup)
val newNodesReturn = ArrayList<Node>()
newNodesReturn.add(mNewGroup)
return ActionNodesValues(oldNodesReturn, newNodesReturn)
}
}

View file

@ -0,0 +1,40 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.crypto
import com.kunzisoft.encrypt.CipherFactory
import java.security.InvalidAlgorithmParameterException
import java.security.InvalidKeyException
import java.security.NoSuchAlgorithmException
import javax.crypto.Cipher
import javax.crypto.NoSuchPaddingException
class AesEngine : CipherEngine() {
@Throws(NoSuchAlgorithmException::class, NoSuchPaddingException::class, InvalidKeyException::class, InvalidAlgorithmParameterException::class)
override fun getCipher(opmode: Int, key: ByteArray, IV: ByteArray): Cipher {
return CipherFactory.getAES(opmode, key, IV)
}
override fun getEncryptionAlgorithm(): EncryptionAlgorithm {
return EncryptionAlgorithm.AESRijndael
}
}

View file

@ -0,0 +1,43 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.crypto
import com.kunzisoft.encrypt.CipherFactory
import java.security.InvalidAlgorithmParameterException
import java.security.InvalidKeyException
import java.security.NoSuchAlgorithmException
import javax.crypto.Cipher
import javax.crypto.NoSuchPaddingException
class ChaCha20Engine : CipherEngine() {
override fun ivLength(): Int {
return 12
}
@Throws(NoSuchAlgorithmException::class, NoSuchPaddingException::class, InvalidKeyException::class, InvalidAlgorithmParameterException::class)
override fun getCipher(opmode: Int, key: ByteArray, IV: ByteArray): Cipher {
return CipherFactory.getChacha20(opmode, key, IV)
}
override fun getEncryptionAlgorithm(): EncryptionAlgorithm {
return EncryptionAlgorithm.ChaCha20
}
}

View file

@ -0,0 +1,47 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.crypto
import java.security.InvalidAlgorithmParameterException
import java.security.InvalidKeyException
import java.security.NoSuchAlgorithmException
import javax.crypto.Cipher
import javax.crypto.NoSuchPaddingException
abstract class CipherEngine {
fun keyLength(): Int {
return 32
}
open fun ivLength(): Int {
return 16
}
// Used only with padding workaround
var forcePaddingCompatibility = false
@Throws(NoSuchAlgorithmException::class, NoSuchPaddingException::class, InvalidKeyException::class, InvalidAlgorithmParameterException::class)
abstract fun getCipher(opmode: Int, key: ByteArray, IV: ByteArray): Cipher
abstract fun getEncryptionAlgorithm(): EncryptionAlgorithm
}

View file

@ -0,0 +1,54 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.crypto
import com.kunzisoft.encrypt.HashManager
import com.kunzisoft.keepass.utils.UnsignedInt
import com.kunzisoft.encrypt.StreamCipher
enum class CrsAlgorithm(val id: UnsignedInt) {
Null(UnsignedInt(0)),
ArcFourVariant(UnsignedInt(1)),
Salsa20(UnsignedInt(2)),
ChaCha20(UnsignedInt(3));
companion object {
@Throws(Exception::class)
fun getCipher(algorithm: CrsAlgorithm?, key: ByteArray): StreamCipher {
return when (algorithm) {
Salsa20 -> HashManager.getSalsa20(key)
ChaCha20 -> HashManager.getChaCha20(key)
else -> throw Exception("Invalid random cipher")
}
}
fun fromId(num: UnsignedInt): CrsAlgorithm? {
for (e in values()) {
if (e.id == num) {
return e
}
}
return null
}
}
}

View file

@ -0,0 +1,133 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.crypto
import com.kunzisoft.keepass.utils.bytes16ToUuid
import java.security.NoSuchAlgorithmException
import java.util.*
enum class EncryptionAlgorithm {
AESRijndael,
Twofish,
ChaCha20;
val cipherEngine: CipherEngine
get() {
return when (this) {
AESRijndael -> AesEngine()
Twofish -> TwofishEngine()
ChaCha20 -> ChaCha20Engine()
}
}
val uuid: UUID
get() {
return when (this) {
AESRijndael -> AES_UUID
Twofish -> TWOFISH_UUID
ChaCha20 -> CHACHA20_UUID
}
}
override fun toString(): String {
return when (this) {
AESRijndael -> "Rijndael (AES)"
Twofish -> "Twofish"
ChaCha20 -> "ChaCha20"
}
}
companion object {
/**
* Generate appropriate cipher based on KeePass 2.x UUID's
*/
@Throws(NoSuchAlgorithmException::class)
fun getFrom(uuid: UUID): EncryptionAlgorithm {
return when (uuid) {
AES_UUID -> AESRijndael
TWOFISH_UUID -> Twofish
CHACHA20_UUID -> ChaCha20
else -> throw NoSuchAlgorithmException("UUID unrecognized.")
}
}
private val AES_UUID: UUID by lazy {
bytes16ToUuid(
byteArrayOf(0x31.toByte(),
0xC1.toByte(),
0xF2.toByte(),
0xE6.toByte(),
0xBF.toByte(),
0x71.toByte(),
0x43.toByte(),
0x50.toByte(),
0xBE.toByte(),
0x58.toByte(),
0x05.toByte(),
0x21.toByte(),
0x6A.toByte(),
0xFC.toByte(),
0x5A.toByte(),
0xFF.toByte()))
}
private val TWOFISH_UUID: UUID by lazy {
bytes16ToUuid(
byteArrayOf(0xAD.toByte(),
0x68.toByte(),
0xF2.toByte(),
0x9F.toByte(),
0x57.toByte(),
0x6F.toByte(),
0x4B.toByte(),
0xB9.toByte(),
0xA3.toByte(),
0x6A.toByte(),
0xD4.toByte(),
0x7A.toByte(),
0xF9.toByte(),
0x65.toByte(),
0x34.toByte(),
0x6C.toByte()))
}
private val CHACHA20_UUID: UUID by lazy {
bytes16ToUuid(
byteArrayOf(0xD6.toByte(),
0x03.toByte(),
0x8A.toByte(),
0x2B.toByte(),
0x8B.toByte(),
0x6F.toByte(),
0x4C.toByte(),
0xB5.toByte(),
0xA5.toByte(),
0x24.toByte(),
0x33.toByte(),
0x9A.toByte(),
0x31.toByte(),
0xDB.toByte(),
0xB5.toByte(),
0x9A.toByte()))
}
}
}

View file

@ -0,0 +1,56 @@
/*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.crypto
import java.io.IOException
import java.security.InvalidKeyException
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
object HmacBlock {
fun getHmacSha256(blockKey: ByteArray): Mac {
val hmac: Mac
try {
hmac = Mac.getInstance("HmacSHA256")
val signingKey = SecretKeySpec(blockKey, "HmacSHA256")
hmac.init(signingKey)
} catch (e: NoSuchAlgorithmException) {
throw IOException("No HmacAlogirthm")
} catch (e: InvalidKeyException) {
throw IOException("Invalid Hmac Key")
}
return hmac
}
fun getHmacKey64(key: ByteArray, blockIndex: ByteArray): ByteArray {
val hash: MessageDigest
try {
hash = MessageDigest.getInstance("SHA-512")
} catch (e: NoSuchAlgorithmException) {
throw RuntimeException(e)
}
hash.update(blockIndex)
hash.update(key)
return hash.digest()
}
}

View file

@ -0,0 +1,39 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.crypto
import com.kunzisoft.encrypt.CipherFactory
import java.security.InvalidAlgorithmParameterException
import java.security.InvalidKeyException
import java.security.NoSuchAlgorithmException
import javax.crypto.Cipher
import javax.crypto.NoSuchPaddingException
class TwofishEngine : CipherEngine() {
@Throws(NoSuchAlgorithmException::class, NoSuchPaddingException::class, InvalidKeyException::class, InvalidAlgorithmParameterException::class)
override fun getCipher(opmode: Int, key: ByteArray, IV: ByteArray): Cipher {
return CipherFactory.getTwofish(opmode, key, IV, forcePaddingCompatibility)
}
override fun getEncryptionAlgorithm(): EncryptionAlgorithm {
return EncryptionAlgorithm.Twofish
}
}

View file

@ -0,0 +1,244 @@
/*
* Copyright 2020 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.crypto
import com.kunzisoft.keepass.utils.*
import java.io.*
import java.nio.charset.Charset
import java.util.*
open class VariantDictionary {
constructor()
constructor(d: VariantDictionary) {
for ((key, value) in d.dict) {
dict[key] = value
}
}
private val dict: MutableMap<String, VdType> = HashMap()
private fun getValue(name: String): Any? {
return dict[name]?.value ?: return null
}
private fun putType(type: Byte, name: String, value: Any) {
dict[name] = VdType(type, value)
}
fun setUInt32(name: String, value: UnsignedInt) {
putType(VdType.UInt32, name, value)
}
fun getUInt32(name: String): UnsignedInt? {
return dict[name]?.value as UnsignedInt?
}
fun setUInt64(name: String, value: UnsignedLong) {
putType(VdType.UInt64, name, value)
}
fun getUInt64(name: String): UnsignedLong? {
return dict[name]?.value as UnsignedLong?
}
fun setBool(name: String, value: Boolean) {
putType(VdType.Bool, name, value)
}
fun getBool(name: String): Boolean? {
return dict[name]?.value as Boolean?
}
fun setInt32(name: String, value: Int) {
putType(VdType.Int32, name, value)
}
fun getInt32(name: String): Int? {
return dict[name]?.value as Int?
}
fun setInt64(name: String, value: Long) {
putType(VdType.Int64, name, value)
}
fun getInt64(name: String): Long? {
return dict[name]?.value as Long?
}
fun setString(name: String, value: String) {
putType(VdType.String, name, value)
}
fun getString(name: String): String? {
return getValue(name) as String?
}
fun setByteArray(name: String, value: ByteArray) {
putType(VdType.ByteArray, name, value)
}
fun getByteArray(name: String): ByteArray? {
return getValue(name) as ByteArray?
}
fun size(): Int {
return dict.size
}
companion object {
private const val VdVersion = 0x0100
private const val VdmCritical = 0xFF00
private const val VdmInfo = 0x00FF
private val UTF8Charset = Charset.forName("UTF-8")
@Throws(IOException::class)
fun deserialize(data: ByteArray): VariantDictionary {
val inputStream = ByteArrayInputStream(data)
return deserialize(inputStream)
}
@Throws(IOException::class)
fun serialize(variantDictionary: VariantDictionary): ByteArray {
val byteArrayOutputStream = ByteArrayOutputStream()
serialize(variantDictionary, byteArrayOutputStream)
return byteArrayOutputStream.toByteArray()
}
@Throws(IOException::class)
fun deserialize(inputStream: InputStream): VariantDictionary {
val dictionary = VariantDictionary()
val version = inputStream.readBytes2ToUShort()
if (version and VdmCritical > VdVersion and VdmCritical) {
throw IOException("Invalid format")
}
while (true) {
val type = inputStream.read()
if (type < 0) {
throw IOException("Invalid format")
}
val bType = type.toByte()
if (bType == VdType.None) {
break
}
val nameLen = inputStream.readBytes4ToUInt().toKotlinInt()
val nameBuf = inputStream.readBytesLength(nameLen)
if (nameLen != nameBuf.size) {
throw IOException("Invalid format")
}
val name = String(nameBuf, UTF8Charset)
val valueLen = inputStream.readBytes4ToUInt().toKotlinInt()
val valueBuf = inputStream.readBytesLength(valueLen)
if (valueLen != valueBuf.size) {
throw IOException("Invalid format")
}
when (bType) {
VdType.UInt32 -> if (valueLen == 4) {
dictionary.setUInt32(name, bytes4ToUInt(valueBuf))
}
VdType.UInt64 -> if (valueLen == 8) {
dictionary.setUInt64(name, bytes64ToULong(valueBuf))
}
VdType.Bool -> if (valueLen == 1) {
dictionary.setBool(name, valueBuf[0] != 0.toByte())
}
VdType.Int32 -> if (valueLen == 4) {
dictionary.setInt32(name, bytes4ToUInt(valueBuf).toKotlinInt())
}
VdType.Int64 -> if (valueLen == 8) {
dictionary.setInt64(name, bytes64ToLong(valueBuf))
}
VdType.String -> dictionary.setString(name, String(valueBuf, UTF8Charset))
VdType.ByteArray -> dictionary.setByteArray(name, valueBuf)
else -> {
}
}
}
return dictionary
}
@Throws(IOException::class)
fun serialize(variantDictionary: VariantDictionary,
outputStream: OutputStream?) {
if (outputStream == null) {
return
}
outputStream.write2BytesUShort(VdVersion)
for ((name, vd) in variantDictionary.dict) {
val nameBuf = name.toByteArray(UTF8Charset)
outputStream.writeByte(vd.type)
outputStream.write4BytesUInt(UnsignedInt(nameBuf.size))
outputStream.write(nameBuf)
var buf: ByteArray
when (vd.type) {
VdType.UInt32 -> {
outputStream.write4BytesUInt(UnsignedInt(4))
outputStream.write4BytesUInt(vd.value as UnsignedInt)
}
VdType.UInt64 -> {
outputStream.write4BytesUInt(UnsignedInt(8))
outputStream.write8BytesLong(vd.value as UnsignedLong)
}
VdType.Bool -> {
outputStream.write4BytesUInt(UnsignedInt(1))
outputStream.writeBooleanByte(vd.value as Boolean)
}
VdType.Int32 -> {
outputStream.write4BytesUInt(UnsignedInt(4))
outputStream.write4BytesUInt(UnsignedInt(vd.value as Int))
}
VdType.Int64 -> {
outputStream.write4BytesUInt(UnsignedInt(8))
outputStream.write8BytesLong(vd.value as Long)
}
VdType.String -> {
val value = vd.value as String
buf = value.toByteArray(UTF8Charset)
outputStream.write4BytesUInt(UnsignedInt(buf.size))
outputStream.write(buf)
}
VdType.ByteArray -> {
buf = vd.value as ByteArray
outputStream.write4BytesUInt(UnsignedInt(buf.size))
outputStream.write(buf)
}
else -> {
}
}
}
outputStream.write(VdType.None.toInt())
}
}
class VdType(val type: Byte, val value: Any) {
companion object {
const val None: Byte = 0x00
const val UInt32: Byte = 0x04
const val UInt64: Byte = 0x05
const val Bool: Byte = 0x08
const val Int32: Byte = 0x0C
const val Int64: Byte = 0x0D
const val String: Byte = 0x18
const val ByteArray: Byte = 0x42
}
}
}

View file

@ -0,0 +1,108 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.crypto.kdf
import com.kunzisoft.encrypt.HashManager
import com.kunzisoft.keepass.utils.UnsignedLong
import com.kunzisoft.encrypt.aes.AESTransformer
import com.kunzisoft.keepass.utils.bytes16ToUuid
import java.io.IOException
import java.security.SecureRandom
import java.util.*
class AesKdf : KdfEngine() {
init {
uuid = CIPHER_UUID
}
override val defaultParameters: KdfParameters
get() {
return KdfParameters(uuid!!).apply {
setParamUUID()
setUInt64(PARAM_ROUNDS, UnsignedLong(defaultKeyRounds))
}
}
override val defaultKeyRounds = 500000L
@Throws(IOException::class)
override fun transform(masterKey: ByteArray, kdfParameters: KdfParameters): ByteArray {
var seed = kdfParameters.getByteArray(PARAM_SEED)
if (seed != null && seed.size != 32) {
seed = HashManager.hashSha256(seed)
}
var currentMasterKey = masterKey
if (currentMasterKey.size != 32) {
currentMasterKey = HashManager.hashSha256(currentMasterKey)
}
val rounds = kdfParameters.getUInt64(PARAM_ROUNDS)?.toKotlinLong()
return AESTransformer.transformKey(seed, currentMasterKey, rounds) ?: ByteArray(0)
}
override fun randomize(kdfParameters: KdfParameters) {
val random = SecureRandom()
val seed = ByteArray(32)
random.nextBytes(seed)
kdfParameters.setByteArray(PARAM_SEED, seed)
}
override fun getKeyRounds(kdfParameters: KdfParameters): Long {
return kdfParameters.getUInt64(PARAM_ROUNDS)?.toKotlinLong() ?: defaultKeyRounds
}
override fun setKeyRounds(kdfParameters: KdfParameters, keyRounds: Long) {
kdfParameters.setUInt64(PARAM_ROUNDS, UnsignedLong(keyRounds))
}
override fun toString(): String {
return "AES"
}
companion object {
val CIPHER_UUID: UUID = bytes16ToUuid(
byteArrayOf(0xC9.toByte(),
0xD9.toByte(),
0xF3.toByte(),
0x9A.toByte(),
0x62.toByte(),
0x8A.toByte(),
0x44.toByte(),
0x60.toByte(),
0xBF.toByte(),
0x74.toByte(),
0x0D.toByte(),
0x08.toByte(),
0xC1.toByte(),
0x8A.toByte(),
0x4F.toByte(),
0xEA.toByte()))
const val PARAM_ROUNDS = "R" // UInt64
const val PARAM_SEED = "S" // Byte array
}
}

View file

@ -0,0 +1,208 @@
/*
* Copyright 2020 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.crypto.kdf
import com.kunzisoft.keepass.utils.UnsignedInt
import com.kunzisoft.keepass.utils.UnsignedLong
import com.kunzisoft.encrypt.argon2.Argon2Transformer
import com.kunzisoft.encrypt.argon2.Argon2Type
import com.kunzisoft.keepass.utils.bytes16ToUuid
import java.io.IOException
import java.security.SecureRandom
import java.util.*
class Argon2Kdf(private val type: Type) : KdfEngine() {
init {
uuid = type.CIPHER_UUID
}
override val defaultParameters: KdfParameters
get() {
val p = KdfParameters(uuid!!)
p.setParamUUID()
p.setUInt32(PARAM_PARALLELISM, DEFAULT_PARALLELISM)
p.setUInt64(PARAM_MEMORY, DEFAULT_MEMORY)
p.setUInt64(PARAM_ITERATIONS, DEFAULT_ITERATIONS)
p.setUInt32(PARAM_VERSION, MAX_VERSION)
return p
}
override val defaultKeyRounds: Long
get() = DEFAULT_ITERATIONS.toKotlinLong()
@Throws(IOException::class)
override fun transform(masterKey: ByteArray, kdfParameters: KdfParameters): ByteArray {
val salt = kdfParameters.getByteArray(PARAM_SALT) ?: ByteArray(0)
val parallelism = kdfParameters.getUInt32(PARAM_PARALLELISM)?.toKotlinLong() ?: DEFAULT_PARALLELISM.toKotlinLong()
val memory = kdfParameters.getUInt64(PARAM_MEMORY)?.toKotlinLong()?.div(MEMORY_BLOCK_SIZE) ?: DEFAULT_MEMORY.toKotlinLong()
val iterations = kdfParameters.getUInt64(PARAM_ITERATIONS)?.toKotlinLong() ?: DEFAULT_ITERATIONS.toKotlinLong()
val version = kdfParameters.getUInt32(PARAM_VERSION)?.toKotlinInt() ?: MAX_VERSION.toKotlinInt()
// Not used
// val secretKey = kdfParameters.getByteArray(PARAM_SECRET_KEY)
// val assocData = kdfParameters.getByteArray(PARAM_ASSOC_DATA)
val argonType = if (type == Type.ARGON2_ID) Argon2Type.ARGON2_ID else Argon2Type.ARGON2_D
return Argon2Transformer.transformKey(
argonType,
masterKey,
salt,
parallelism,
memory,
iterations,
version)
}
override fun randomize(kdfParameters: KdfParameters) {
val random = SecureRandom()
val salt = ByteArray(32)
random.nextBytes(salt)
kdfParameters.setByteArray(PARAM_SALT, salt)
}
override fun getKeyRounds(kdfParameters: KdfParameters): Long {
return kdfParameters.getUInt64(PARAM_ITERATIONS)?.toKotlinLong() ?: defaultKeyRounds
}
override fun setKeyRounds(kdfParameters: KdfParameters, keyRounds: Long) {
kdfParameters.setUInt64(PARAM_ITERATIONS, UnsignedLong(keyRounds))
}
override val minKeyRounds: Long
get() = MIN_ITERATIONS.toKotlinLong()
override val maxKeyRounds: Long
get() = MAX_ITERATIONS.toKotlinLong()
override fun getMemoryUsage(kdfParameters: KdfParameters): Long {
return kdfParameters.getUInt64(PARAM_MEMORY)?.toKotlinLong() ?: defaultMemoryUsage
}
override fun setMemoryUsage(kdfParameters: KdfParameters, memory: Long) {
kdfParameters.setUInt64(PARAM_MEMORY, UnsignedLong(memory))
}
override val defaultMemoryUsage: Long
get() = DEFAULT_MEMORY.toKotlinLong()
override val minMemoryUsage: Long
get() = MIN_MEMORY.toKotlinLong()
override val maxMemoryUsage: Long
get() = MAX_MEMORY
override fun getParallelism(kdfParameters: KdfParameters): Long {
return kdfParameters.getUInt32(PARAM_PARALLELISM)?.let {
UnsignedInt(it).toKotlinLong()
} ?: defaultParallelism
}
override fun setParallelism(kdfParameters: KdfParameters, parallelism: Long) {
kdfParameters.setUInt32(PARAM_PARALLELISM, UnsignedInt.fromKotlinLong(parallelism))
}
override fun toString(): String {
return "$type"
}
override val defaultParallelism: Long
get() = DEFAULT_PARALLELISM.toKotlinLong()
override val minParallelism: Long
get() = MIN_PARALLELISM.toKotlinLong()
override val maxParallelism: Long
get() = MAX_PARALLELISM.toKotlinLong()
enum class Type(val CIPHER_UUID: UUID, private val typeName: String) {
ARGON2_D(bytes16ToUuid(
byteArrayOf(0xEF.toByte(),
0x63.toByte(),
0x6D.toByte(),
0xDF.toByte(),
0x8C.toByte(),
0x29.toByte(),
0x44.toByte(),
0x4B.toByte(),
0x91.toByte(),
0xF7.toByte(),
0xA9.toByte(),
0xA4.toByte(),
0x03.toByte(),
0xE3.toByte(),
0x0A.toByte(),
0x0C.toByte())), "Argon2d"),
ARGON2_ID(bytes16ToUuid(
byteArrayOf(0x9E.toByte(),
0x29.toByte(),
0x8B.toByte(),
0x19.toByte(),
0x56.toByte(),
0xDB.toByte(),
0x47.toByte(),
0x73.toByte(),
0xB2.toByte(),
0x3D.toByte(),
0xFC.toByte(),
0x3E.toByte(),
0xC6.toByte(),
0xF0.toByte(),
0xA1.toByte(),
0xE6.toByte())), "Argon2id");
override fun toString(): String {
return typeName
}
}
companion object {
private const val PARAM_SALT = "S" // byte[]
private const val PARAM_PARALLELISM = "P" // UInt32
private const val PARAM_MEMORY = "M" // UInt64
private const val PARAM_ITERATIONS = "I" // UInt64
private const val PARAM_VERSION = "V" // UInt32
private const val PARAM_SECRET_KEY = "K" // byte[]
private const val PARAM_ASSOC_DATA = "A" // byte[]
private val MIN_VERSION = UnsignedInt(0x10)
private val MAX_VERSION = UnsignedInt(0x13)
private val DEFAULT_ITERATIONS = UnsignedLong(3L)
private val MIN_ITERATIONS = UnsignedLong(1L)
private val MAX_ITERATIONS = UnsignedLong(4294967295L)
private val DEFAULT_MEMORY = UnsignedLong((1024L * 1024L * 16L))
private val MIN_MEMORY = UnsignedLong(1024L * 8L)
private val MAX_MEMORY = UnsignedInt.MAX_VALUE.toKotlinLong()
private const val MEMORY_BLOCK_SIZE: Long = 1024L
private val DEFAULT_PARALLELISM = UnsignedInt(4)
private val MIN_PARALLELISM = UnsignedInt.fromKotlinLong(1L)
private val MAX_PARALLELISM = UnsignedInt.fromKotlinLong(((1 shl 24) - 1).toLong())
}
}

View file

@ -0,0 +1,100 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.crypto.kdf
import com.kunzisoft.keepass.utils.UnsignedInt
import java.io.IOException
import java.io.Serializable
import java.util.*
// TODO Parcelable
abstract class KdfEngine : Serializable {
var uuid: UUID? = null
abstract val defaultParameters: KdfParameters
@Throws(IOException::class)
abstract fun transform(masterKey: ByteArray, kdfParameters: KdfParameters): ByteArray
abstract fun randomize(kdfParameters: KdfParameters)
/*
* ITERATIONS
*/
abstract fun getKeyRounds(kdfParameters: KdfParameters): Long
abstract fun setKeyRounds(kdfParameters: KdfParameters, keyRounds: Long)
abstract val defaultKeyRounds: Long
open val minKeyRounds: Long
get() = 1
open val maxKeyRounds: Long
get() = UnsignedInt.MAX_VALUE.toKotlinLong()
/*
* MEMORY
*/
open fun getMemoryUsage(kdfParameters: KdfParameters): Long {
return UNKNOWN_VALUE
}
open fun setMemoryUsage(kdfParameters: KdfParameters, memory: Long) {
// Do nothing by default
}
open val defaultMemoryUsage: Long
get() = UNKNOWN_VALUE
open val minMemoryUsage: Long
get() = 1
open val maxMemoryUsage: Long
get() = UnsignedInt.MAX_VALUE.toKotlinLong()
/*
* PARALLELISM
*/
open fun getParallelism(kdfParameters: KdfParameters): Long {
return UNKNOWN_VALUE
}
open fun setParallelism(kdfParameters: KdfParameters, parallelism: Long) {
// Do nothing by default
}
open val defaultParallelism: Long
get() = UNKNOWN_VALUE
open val minParallelism: Long
get() = 1L
open val maxParallelism: Long
get() = UnsignedInt.MAX_VALUE.toKotlinLong()
companion object {
const val UNKNOWN_VALUE: Long = -1L
}
}

View file

@ -0,0 +1,26 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.crypto.kdf
object KdfFactory {
var aesKdf = AesKdf()
var argon2dKdf = Argon2Kdf(Argon2Kdf.Type.ARGON2_D)
var argon2idKdf = Argon2Kdf(Argon2Kdf.Type.ARGON2_ID)
}

View file

@ -0,0 +1,64 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.crypto.kdf
import com.kunzisoft.keepass.utils.bytes16ToUuid
import com.kunzisoft.keepass.utils.uuidTo16Bytes
import com.kunzisoft.keepass.database.crypto.VariantDictionary
import java.io.IOException
import java.util.*
class KdfParameters: VariantDictionary {
val uuid: UUID
constructor(uuid: UUID): super() {
this.uuid = uuid
}
constructor(uuid: UUID, d: VariantDictionary): super(d) {
this.uuid = uuid
}
fun setParamUUID() {
setByteArray(PARAM_UUID, uuidTo16Bytes(uuid))
}
companion object {
private const val PARAM_UUID = "\$UUID"
@Throws(IOException::class)
fun deserialize(data: ByteArray): KdfParameters? {
val dictionary = VariantDictionary.deserialize(data)
val uuidBytes = dictionary.getByteArray(PARAM_UUID) ?: return null
val uuid = bytes16ToUuid(uuidBytes)
return KdfParameters(uuid, dictionary)
}
@Throws(IOException::class)
fun serialize(kdfParameters: KdfParameters): ByteArray {
return VariantDictionary.serialize(kdfParameters)
}
}
}

View file

@ -0,0 +1,71 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element
import android.os.Parcel
import android.os.Parcelable
import com.kunzisoft.keepass.database.element.binary.BinaryByte
import com.kunzisoft.keepass.database.element.binary.BinaryData
data class Attachment(var name: String,
var binaryData: BinaryData) : Parcelable {
constructor(parcel: Parcel) : this(
parcel.readString() ?: "",
parcel.readParcelable(BinaryData::class.java.classLoader) ?: BinaryByte()
)
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(name)
parcel.writeParcelable(binaryData, flags)
}
override fun describeContents(): Int {
return 0
}
override fun toString(): String {
return "$name at $binaryData"
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is Attachment) return false
if (name != other.name) return false
return true
}
override fun hashCode(): Int {
return name.hashCode()
}
companion object CREATOR : Parcelable.Creator<Attachment> {
override fun createFromParcel(parcel: Parcel): Attachment {
return Attachment(parcel)
}
override fun newArray(size: Int): Array<Attachment?> {
return arrayOfNulls(size)
}
}
}

View file

@ -0,0 +1,34 @@
package com.kunzisoft.keepass.database.element
import com.kunzisoft.keepass.hardware.HardwareKey
data class CompositeKey(var passwordData: ByteArray? = null,
var keyFileData: ByteArray? = null,
var hardwareKey: HardwareKey? = null) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as CompositeKey
if (passwordData != null) {
if (other.passwordData == null) return false
if (!passwordData.contentEquals(other.passwordData)) return false
} else if (other.passwordData != null) return false
if (keyFileData != null) {
if (other.keyFileData == null) return false
if (!keyFileData.contentEquals(other.keyFileData)) return false
} else if (other.keyFileData != null) return false
if (hardwareKey != other.hardwareKey) return false
return true
}
override fun hashCode(): Int {
var result = passwordData?.contentHashCode() ?: 0
result = 31 * result + (keyFileData?.contentHashCode() ?: 0)
result = 31 * result + (hardwareKey?.hashCode() ?: 0)
return result
}
}

View file

@ -0,0 +1,92 @@
/*
* Copyright 2020 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element
import android.os.Parcel
import android.os.Parcelable
import com.kunzisoft.keepass.utils.ParcelableUtil
import java.util.*
class CustomData : Parcelable {
private val mCustomDataItems = HashMap<String, CustomDataItem>()
constructor()
constructor(toCopy: CustomData) {
mCustomDataItems.clear()
mCustomDataItems.putAll(toCopy.mCustomDataItems)
}
constructor(parcel: Parcel) {
mCustomDataItems.clear()
mCustomDataItems.putAll(ParcelableUtil
.readStringParcelableMap(parcel, CustomDataItem::class.java)
)
}
fun get(key: String): CustomDataItem? {
return mCustomDataItems[key]
}
fun put(customDataItem: CustomDataItem) {
mCustomDataItems[customDataItem.key] = customDataItem
}
fun containsItemWithValue(value: String): Boolean {
return mCustomDataItems.any { mapEntry -> mapEntry.value.value.equals(value, true) }
}
fun containsItemWithLastModificationTime(): Boolean {
return mCustomDataItems.any { mapEntry -> mapEntry.value.lastModificationTime != null }
}
fun isNotEmpty(): Boolean {
return mCustomDataItems.isNotEmpty()
}
fun doForEachItems(action: (CustomDataItem) -> Unit) {
for ((_, value) in mCustomDataItems) {
action.invoke(value)
}
}
override fun toString(): String {
return mCustomDataItems.toString()
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
ParcelableUtil.writeStringParcelableMap(parcel, flags, mCustomDataItems)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<CustomData> {
override fun createFromParcel(parcel: Parcel): CustomData {
return CustomData(parcel)
}
override fun newArray(size: Int): Array<CustomData?> {
return arrayOfNulls(size)
}
}
}

View file

@ -0,0 +1,47 @@
package com.kunzisoft.keepass.database.element
import android.os.Parcel
import android.os.Parcelable
class CustomDataItem : Parcelable {
val key: String
var value: String
var lastModificationTime: DateInstant? = null
constructor(parcel: Parcel) {
key = parcel.readString() ?: ""
value = parcel.readString() ?: ""
lastModificationTime = parcel.readParcelable(DateInstant::class.java.classLoader)
}
constructor(key: String, value: String, lastModificationTime: DateInstant? = null) {
this.key = key
this.value = value
this.lastModificationTime = lastModificationTime
}
override fun toString(): String {
return value
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(key)
parcel.writeString(value)
parcel.writeParcelable(lastModificationTime, flags)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<CustomDataItem> {
override fun createFromParcel(parcel: Parcel): CustomDataItem {
return CustomDataItem(parcel)
}
override fun newArray(size: Int): Array<CustomDataItem?> {
return arrayOfNulls(size)
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,241 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element
import android.content.res.Resources
import android.os.Parcel
import android.os.Parcelable
import androidx.core.os.ConfigurationCompat
import com.kunzisoft.keepass.utils.readEnum
import com.kunzisoft.keepass.utils.writeEnum
import org.joda.time.*
import java.text.DateFormat
import java.text.SimpleDateFormat
import java.util.*
class DateInstant : Parcelable {
private var jDate: Date = Date()
private var mType: Type = Type.DATE_TIME
val date: Date
get() = jDate
var type: Type
get() = mType
set(value) {
mType = value
}
constructor(source: DateInstant) {
this.jDate = Date(source.jDate.time)
this.mType = source.mType
}
constructor(date: Date, type: Type = Type.DATE_TIME) {
jDate = Date(date.time)
mType = type
}
constructor(millis: Long, type: Type = Type.DATE_TIME) {
jDate = Date(millis)
mType = type
}
private fun parse(value: String, type: Type): Date {
return when (type) {
Type.DATE -> dateFormat.parse(value) ?: jDate
Type.TIME -> timeFormat.parse(value) ?: jDate
else -> dateTimeFormat.parse(value) ?: jDate
}
}
constructor(string: String, type: Type = Type.DATE_TIME) {
try {
jDate = parse(string, type)
mType = type
} catch (e: Exception) {
// Retry with second format
try {
when (type) {
Type.TIME -> {
jDate = parse(string, Type.DATE)
mType = Type.DATE
}
else -> {
jDate = parse(string, Type.TIME)
mType = Type.TIME
}
}
} catch (e: Exception) {
// Retry with third format
when (type) {
Type.DATE, Type.TIME -> {
jDate = parse(string, Type.DATE_TIME)
mType = Type.DATE_TIME
}
else -> {
jDate = parse(string, Type.DATE)
mType = Type.DATE
}
}
}
}
}
constructor(type: Type) {
mType = type
}
constructor() {
jDate = Date()
}
constructor(parcel: Parcel) {
jDate = parcel.readSerializable() as? Date? ?: jDate
mType = parcel.readEnum<Type>() ?: mType
}
override fun describeContents(): Int {
return 0
}
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeSerializable(jDate)
dest.writeEnum(mType)
}
fun getDateTimeString(resources: Resources): String {
return when (mType) {
Type.DATE -> DateFormat.getDateInstance(
DateFormat.MEDIUM,
ConfigurationCompat.getLocales(resources.configuration)[0])
.format(jDate)
Type.TIME -> DateFormat.getTimeInstance(
DateFormat.SHORT,
ConfigurationCompat.getLocales(resources.configuration)[0])
.format(jDate)
else -> DateFormat.getDateTimeInstance(
DateFormat.MEDIUM,
DateFormat.SHORT,
ConfigurationCompat.getLocales(resources.configuration)[0])
.format(jDate)
}
}
fun getYearInt(): Int {
val dateFormat = SimpleDateFormat("yyyy", Locale.ENGLISH)
return dateFormat.format(date).toInt()
}
fun getMonthInt(): Int {
val dateFormat = SimpleDateFormat("MM", Locale.ENGLISH)
return dateFormat.format(date).toInt()
}
fun getDay(): Int {
val dateFormat = SimpleDateFormat("dd", Locale.ENGLISH)
return dateFormat.format(date).toInt()
}
// If expireDate is before NEVER_EXPIRE date less 1 month (to be sure)
// it is not expires
fun isNeverExpires(): Boolean {
return LocalDateTime(jDate)
.isBefore(
LocalDateTime.fromDateFields(NEVER_EXPIRES.date)
.minusMonths(1))
}
fun isCurrentlyExpire(): Boolean {
return when (type) {
Type.DATE -> LocalDate.fromDateFields(jDate).isBefore(LocalDate.now())
Type.TIME -> LocalTime.fromDateFields(jDate).isBefore(LocalTime.now())
else -> LocalDateTime.fromDateFields(jDate).isBefore(LocalDateTime.now())
}
}
override fun toString(): String {
return when (type) {
Type.DATE -> dateFormat.format(jDate)
Type.TIME -> timeFormat.format(jDate)
else -> dateTimeFormat.format(jDate)
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is DateInstant) return false
if (jDate != other.jDate) return false
if (mType != other.mType) return false
return true
}
override fun hashCode(): Int {
var result = jDate.hashCode()
result = 31 * result + mType.hashCode()
return result
}
enum class Type {
DATE_TIME, DATE, TIME
}
companion object {
val NEVER_EXPIRES = DateInstant(Calendar.getInstance().apply {
set(Calendar.YEAR, 2999)
set(Calendar.MONTH, 11)
set(Calendar.DAY_OF_MONTH, 28)
set(Calendar.HOUR, 23)
set(Calendar.MINUTE, 59)
set(Calendar.SECOND, 59)
}.time)
val IN_ONE_MONTH_DATE_TIME = DateInstant(
Instant.now().plus(Duration.standardDays(30)).toDate(), Type.DATE_TIME)
val IN_ONE_MONTH_DATE = DateInstant(
Instant.now().plus(Duration.standardDays(30)).toDate(), Type.DATE)
val IN_ONE_HOUR_TIME = DateInstant(
Instant.now().plus(Duration.standardHours(1)).toDate(), Type.TIME)
private val dateTimeFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'", Locale.ROOT).apply {
timeZone = TimeZone.getTimeZone("UTC")
}
private val dateFormat = SimpleDateFormat("yyyy-MM-dd'Z'", Locale.ROOT).apply {
timeZone = TimeZone.getTimeZone("UTC")
}
private val timeFormat = SimpleDateFormat("HH:mm'Z'", Locale.ROOT).apply {
timeZone = TimeZone.getTimeZone("UTC")
}
@JvmField
val CREATOR: Parcelable.Creator<DateInstant> = object : Parcelable.Creator<DateInstant> {
override fun createFromParcel(parcel: Parcel): DateInstant {
return DateInstant(parcel)
}
override fun newArray(size: Int): Array<DateInstant?> {
return arrayOfNulls(size)
}
}
}
}

View file

@ -0,0 +1,77 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element
import android.os.Parcel
import android.os.ParcelUuid
import android.os.Parcelable
import com.kunzisoft.keepass.database.element.database.DatabaseVersioned
import java.util.*
class DeletedObject : Parcelable {
var uuid: UUID = DatabaseVersioned.UUID_ZERO
var deletionTime: DateInstant = DateInstant()
constructor()
constructor(uuid: UUID, deletionTime: DateInstant = DateInstant()) {
this.uuid = uuid
this.deletionTime = deletionTime
}
constructor(parcel: Parcel) {
uuid = parcel.readParcelable<ParcelUuid>(ParcelUuid::class.java.classLoader)?.uuid ?: DatabaseVersioned.UUID_ZERO
deletionTime = parcel.readParcelable(DateInstant::class.java.classLoader) ?: deletionTime
}
override fun equals(other: Any?): Boolean {
if (this === other)
return true
if (other == null)
return false
if (other !is DeletedObject)
return false
return uuid == other.uuid
}
override fun hashCode(): Int {
return uuid.hashCode()
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeParcelable(ParcelUuid(uuid), flags)
parcel.writeParcelable(deletionTime, flags)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<DeletedObject> {
override fun createFromParcel(parcel: Parcel): DeletedObject {
return DeletedObject(parcel)
}
override fun newArray(size: Int): Array<DeletedObject?> {
return arrayOfNulls(size)
}
}
}

View file

@ -0,0 +1,566 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element
import android.graphics.Color
import android.os.Parcel
import android.os.Parcelable
import com.kunzisoft.androidclearchroma.ChromaUtil
import com.kunzisoft.keepass.database.element.binary.AttachmentPool
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
import com.kunzisoft.keepass.database.element.database.DatabaseVersioned
import com.kunzisoft.keepass.database.element.entry.AutoType
import com.kunzisoft.keepass.database.element.entry.EntryKDB
import com.kunzisoft.keepass.database.element.entry.EntryKDBX
import com.kunzisoft.keepass.database.element.entry.EntryVersionedInterface
import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.model.EntryInfo
import com.kunzisoft.keepass.otp.OtpElement
import com.kunzisoft.keepass.otp.OtpEntryFields
import java.util.*
import kotlin.collections.ArrayList
class Entry : Node, EntryVersionedInterface<Group> {
var entryKDB: EntryKDB? = null
private set
var entryKDBX: EntryKDBX? = null
private set
/**
* Use this constructor to copy an Entry with exact same values
*/
constructor(entry: Entry, copyHistory: Boolean = true) {
if (entry.entryKDB != null) {
this.entryKDB = EntryKDB()
}
if (entry.entryKDBX != null) {
this.entryKDBX = EntryKDBX()
}
entry.entryKDB?.let {
this.entryKDB?.updateWith(it)
}
entry.entryKDBX?.let {
this.entryKDBX?.updateWith(it, copyHistory)
}
}
constructor(entry: EntryKDB) {
this.entryKDBX = null
this.entryKDB = entry
}
constructor(entry: EntryKDBX) {
this.entryKDB = null
this.entryKDBX = entry
}
constructor(parcel: Parcel) {
entryKDB = parcel.readParcelable(EntryKDB::class.java.classLoader)
entryKDBX = parcel.readParcelable(EntryKDBX::class.java.classLoader)
}
override fun describeContents(): Int {
return 0
}
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeParcelable(entryKDB, flags)
dest.writeParcelable(entryKDBX, flags)
}
override var nodeId: NodeId<UUID>
get() = entryKDBX?.nodeId ?: entryKDB?.nodeId ?: NodeIdUUID()
set(value) {
entryKDB?.nodeId = value
entryKDBX?.nodeId = value
}
override var title: String
get() = entryKDB?.title ?: entryKDBX?.title ?: ""
set(value) {
entryKDB?.title = value
entryKDBX?.title = value
}
override var icon: IconImage
get() {
return entryKDB?.icon ?: entryKDBX?.icon ?: IconImage()
}
set(value) {
entryKDB?.icon = value
entryKDBX?.icon = value
}
var tags: Tags
get() = entryKDBX?.tags ?: Tags()
set(value) {
entryKDBX?.tags = value
}
var previousParentGroup: UUID = DatabaseVersioned.UUID_ZERO
get() = entryKDBX?.previousParentGroup ?: DatabaseVersioned.UUID_ZERO
private set
fun setPreviousParentGroup(previousParent: Group?) {
entryKDBX?.previousParentGroup = previousParent?.groupKDBX?.id ?: DatabaseVersioned.UUID_ZERO
}
override val type: Type
get() = Type.ENTRY
override var parent: Group?
get() {
entryKDB?.parent?.let {
return Group(it)
}
entryKDBX?.parent?.let {
return Group(it)
}
return null
}
set(value) {
entryKDB?.parent = value?.groupKDB
entryKDBX?.parent = value?.groupKDBX
}
override fun containsParent(): Boolean {
return entryKDB?.containsParent() ?: entryKDBX?.containsParent() ?: false
}
override fun afterAssignNewParent() {
entryKDBX?.afterChangeParent()
}
override fun touch(modified: Boolean, touchParents: Boolean) {
entryKDB?.touch(modified, touchParents)
entryKDBX?.touch(modified, touchParents)
}
override fun isContainedIn(container: Group): Boolean {
var contained: Boolean? = false
container.groupKDB?.let {
contained = entryKDB?.isContainedIn(it)
}
container.groupKDBX?.let {
contained = entryKDBX?.isContainedIn(it)
}
return contained ?: false
}
override fun nodeIndexInParentForNaturalOrder(): Int {
return entryKDB?.nodeIndexInParentForNaturalOrder()
?: entryKDBX?.nodeIndexInParentForNaturalOrder()
?: -1
}
override var creationTime: DateInstant
get() = entryKDB?.creationTime ?: entryKDBX?.creationTime ?: DateInstant()
set(value) {
entryKDB?.creationTime = value
entryKDBX?.creationTime = value
}
override var lastModificationTime: DateInstant
get() = entryKDB?.lastModificationTime ?: entryKDBX?.lastModificationTime ?: DateInstant()
set(value) {
entryKDB?.lastModificationTime = value
entryKDBX?.lastModificationTime = value
}
override var lastAccessTime: DateInstant
get() = entryKDB?.lastAccessTime ?: entryKDBX?.lastAccessTime ?: DateInstant()
set(value) {
entryKDB?.lastAccessTime = value
entryKDBX?.lastAccessTime = value
}
override var expiryTime: DateInstant
get() = entryKDB?.expiryTime ?: entryKDBX?.expiryTime ?: DateInstant()
set(value) {
entryKDB?.expiryTime = value
entryKDBX?.expiryTime = value
}
override var expires: Boolean
get() = entryKDB?.expires ?: entryKDBX?.expires ?: false
set(value) {
entryKDB?.expires = value
entryKDBX?.expires = value
}
override val isCurrentlyExpires: Boolean
get() = entryKDB?.isCurrentlyExpires ?: entryKDBX?.isCurrentlyExpires ?: false
override var username: String
get() = entryKDB?.username ?: entryKDBX?.username ?: ""
set(value) {
entryKDB?.username = value
entryKDBX?.username = value
}
override var password: String
get() = entryKDB?.password ?: entryKDBX?.password ?: ""
set(value) {
entryKDB?.password = value
entryKDBX?.password = value
}
override var url: String
get() = entryKDB?.url ?: entryKDBX?.url ?: ""
set(value) {
entryKDB?.url = value
entryKDBX?.url = value
}
override var notes: String
get() = entryKDB?.notes ?: entryKDBX?.notes ?: ""
set(value) {
entryKDB?.notes = value
entryKDBX?.notes = value
}
var backgroundColor: Int?
get() {
var colorInt: Int? = null
entryKDBX?.backgroundColor?.let {
try {
colorInt = Color.parseColor(it)
} catch (e: Exception) {}
}
return colorInt
}
set(value) {
entryKDBX?.backgroundColor = if (value == null) {
""
} else {
ChromaUtil.getFormattedColorString(value, false)
}
}
var foregroundColor: Int?
get() {
var colorInt: Int? = null
entryKDBX?.foregroundColor?.let {
try {
colorInt = Color.parseColor(it)
} catch (e: Exception) {}
}
return colorInt
}
set(value) {
entryKDBX?.foregroundColor = if (value == null) {
""
} else {
ChromaUtil.getFormattedColorString(value, false)
}
}
var customData: CustomData
get() = entryKDBX?.customData ?: CustomData()
set(value) {
entryKDBX?.customData = value
}
var autoType: AutoType
get() = entryKDBX?.autoType ?: AutoType()
set(value) {
entryKDBX?.autoType = value
}
private fun isTan(): Boolean {
return title == PMS_TAN_ENTRY && username.isNotEmpty()
}
/**
* {@inheritDoc}
* Get the display title from an entry, <br></br>
* [.startManageEntry] and [.stopManageEntry] must be called
* before and after [.getVisualTitle]
*/
fun getVisualTitle(): String {
return if (isTan()) {
"$PMS_TAN_ENTRY $username"
} else {
if (title.isEmpty())
if (url.isEmpty())
if (username.isEmpty())
nodeId.toString()
else
username
else
url
else
title
}
}
/*
------------
KDBX Methods
------------
*/
/**
* Retrieve extra fields to show, key is the label, value is the value of field (protected or not)
* @return Map of label/value
*/
fun getExtraFields(): List<Field> {
val extraFields = ArrayList<Field>()
entryKDBX?.let {
it.doForEachDecodedCustomField { field ->
extraFields.add(field)
}
}
return extraFields
}
/**
* Update or add an extra field to the list (standard or custom)
*/
fun putExtraField(field: Field) {
entryKDBX?.putField(field)
}
private fun addExtraFields(fields: List<Field>) {
fields.forEach {
putExtraField(it)
}
}
private fun removeAllFields() {
entryKDBX?.removeAllFields()
}
fun getOtpElement(): OtpElement? {
entryKDBX?.let {
return OtpEntryFields.parseFields { key ->
it.getFieldValue(key)?.toString()
}
}
return null
}
fun startToManageFieldReferences(database: DatabaseKDBX) {
entryKDBX?.startToManageFieldReferences(database)
}
fun stopToManageFieldReferences() {
entryKDBX?.stopToManageFieldReferences()
}
fun getAttachments(attachmentPool: AttachmentPool, inHistory: Boolean = false): List<Attachment> {
val attachments = ArrayList<Attachment>()
entryKDB?.getAttachment(attachmentPool)?.let {
attachments.add(it)
}
entryKDBX?.getAttachments(attachmentPool, inHistory)?.let {
attachments.addAll(it)
}
return attachments
}
fun containsAttachment(): Boolean {
return entryKDB?.containsAttachment() == true
|| entryKDBX?.containsAttachment() == true
}
private fun removeAttachment(attachment: Attachment) {
entryKDB?.removeAttachment(attachment)
entryKDBX?.removeAttachment(attachment)
}
private fun removeAllAttachments() {
entryKDB?.removeAttachment()
entryKDBX?.removeAttachments()
}
private fun putAttachment(attachment: Attachment, attachmentPool: AttachmentPool) {
entryKDB?.putAttachment(attachment, attachmentPool)
entryKDBX?.putAttachment(attachment, attachmentPool)
}
fun getHistory(): ArrayList<Entry> {
val history = ArrayList<Entry>()
val entryKDBXHistory = entryKDBX?.history ?: ArrayList()
for (entryHistory in entryKDBXHistory) {
history.add(Entry(entryHistory))
}
return history
}
fun addEntryToHistory(entry: Entry) {
entry.entryKDBX?.let {
entryKDBX?.addEntryToHistory(it)
}
}
fun removeEntryFromHistory(position: Int): Entry? {
entryKDBX?.removeEntryFromHistory(position)?.let {
return Entry(it)
}
return null
}
fun removeOldestEntryFromHistory(): Entry? {
entryKDBX?.removeOldestEntryFromHistory()?.let {
return Entry(it)
}
return null
}
fun getSize(attachmentPool: AttachmentPool): Long {
return entryKDBX?.getSize(attachmentPool) ?: 0L
}
/*
------------
Converter
------------
*/
/**
* Retrieve generated entry info.
* If are not [raw] data, remove parameter fields and add auto generated elements in auto custom fields
*/
fun getEntryInfo(database: Database?,
raw: Boolean = false,
removeTemplateConfiguration: Boolean = true): EntryInfo {
val entryInfo = EntryInfo()
// Remove unwanted template fields
val baseInfo = if (removeTemplateConfiguration)
database?.removeTemplateConfiguration(this) ?: this
else
this
baseInfo.apply {
if (raw)
database?.stopManageEntry(this)
else
database?.startManageEntry(this)
entryInfo.id = nodeId.id
entryInfo.title = title
entryInfo.icon = icon
entryInfo.username = username
entryInfo.password = password
entryInfo.creationTime = creationTime
entryInfo.lastModificationTime = lastModificationTime
entryInfo.expires = expires
entryInfo.expiryTime = expiryTime
entryInfo.url = url
entryInfo.notes = notes
entryInfo.tags = tags
entryInfo.backgroundColor = backgroundColor
entryInfo.foregroundColor = foregroundColor
entryInfo.customData = customData
entryInfo.autoType = autoType
entryInfo.customFields = getExtraFields().toMutableList()
// Add otpElement to generate token
entryInfo.otpModel = getOtpElement()?.otpModel
if (!raw) {
// Replace parameter fields by generated OTP fields
entryInfo.customFields = OtpEntryFields.generateAutoFields(entryInfo.customFields)
}
database?.attachmentPool?.let { binaryPool ->
entryInfo.attachments = getAttachments(binaryPool).toMutableList()
}
if (!raw)
database?.stopManageEntry(this)
}
return entryInfo
}
fun setEntryInfo(database: Database?, newEntryInfo: EntryInfo) {
database?.startManageEntry(this)
removeAllFields()
removeAllAttachments()
// NodeId stay as is
title = newEntryInfo.title
icon = newEntryInfo.icon
username = newEntryInfo.username
password = newEntryInfo.password
// Update date time, creation time stay as is
lastModificationTime = DateInstant()
lastAccessTime = DateInstant()
expires = newEntryInfo.expires
expiryTime = newEntryInfo.expiryTime
url = newEntryInfo.url
notes = newEntryInfo.notes
tags = newEntryInfo.tags
backgroundColor = newEntryInfo.backgroundColor
foregroundColor = newEntryInfo.foregroundColor
customData = newEntryInfo.customData
autoType = newEntryInfo.autoType
addExtraFields(newEntryInfo.customFields)
database?.attachmentPool?.let { binaryPool ->
newEntryInfo.attachments.forEach { attachment ->
putAttachment(attachment, binaryPool)
}
}
database?.stopManageEntry(this)
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Entry
if (entryKDB != other.entryKDB) return false
if (entryKDBX != other.entryKDBX) return false
return true
}
override fun hashCode(): Int {
var result = entryKDB?.hashCode() ?: 0
result = 31 * result + (entryKDBX?.hashCode() ?: 0)
return result
}
companion object {
const val PMS_TAN_ENTRY = "<TAN>"
/**
* True if [field] name is not a standard field name
*/
fun newExtraFieldNameAllowed(field: Field): Boolean {
return EntryKDBX.newCustomNameAllowed(field.name)
}
@JvmField
val CREATOR: Parcelable.Creator<Entry> = object : Parcelable.Creator<Entry> {
override fun createFromParcel(parcel: Parcel): Entry {
return Entry(parcel)
}
override fun newArray(size: Int): Array<Entry?> {
return arrayOfNulls(size)
}
}
}
}

View file

@ -0,0 +1,81 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element
import android.os.Parcel
import android.os.Parcelable
import com.kunzisoft.keepass.database.element.security.ProtectedString
class Field : Parcelable {
var name: String = ""
var protectedValue: ProtectedString = ProtectedString()
constructor(name: String, value: ProtectedString = ProtectedString()) {
this.name = name
this.protectedValue = value
}
constructor(fieldToCopy: Field) {
this.name = fieldToCopy.name
this.protectedValue = fieldToCopy.protectedValue
}
constructor(parcel: Parcel) {
this.name = parcel.readString() ?: name
this.protectedValue = parcel.readParcelable(ProtectedString::class.java.classLoader) ?: protectedValue
}
override fun describeContents(): Int {
return 0
}
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeString(name)
dest.writeParcelable(protectedValue, flags)
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is Field) return false
if (name != other.name) return false
return true
}
override fun hashCode(): Int {
return name.hashCode()
}
companion object {
@JvmField
val CREATOR: Parcelable.Creator<Field> = object : Parcelable.Creator<Field> {
override fun createFromParcel(parcel: Parcel): Field {
return Field(parcel)
}
override fun newArray(size: Int): Array<Field?> {
return arrayOfNulls(size)
}
}
}
}

View file

@ -0,0 +1,541 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element
import android.content.Context
import android.os.Parcel
import android.os.Parcelable
import com.kunzisoft.keepass.database.element.database.DatabaseVersioned
import com.kunzisoft.keepass.database.element.group.GroupKDB
import com.kunzisoft.keepass.database.element.group.GroupKDBX
import com.kunzisoft.keepass.database.element.group.GroupVersionedInterface
import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.database.element.node.*
import com.kunzisoft.keepass.model.EntryInfo
import com.kunzisoft.keepass.model.GroupInfo
import com.kunzisoft.keepass.settings.DatabasePreferencesUtil
import java.util.*
import kotlin.collections.ArrayList
class Group : Node, GroupVersionedInterface<Group, Entry> {
var groupKDB: GroupKDB? = null
private set
var groupKDBX: GroupKDBX? = null
private set
// Virtual group is used to defined a detached database group
var isVirtual = false
var numberOfChildEntries: Int = 0
/**
* Use this constructor to copy a Group
*/
constructor(group: Group) {
if (group.groupKDB != null) {
if (this.groupKDB == null)
this.groupKDB = GroupKDB()
}
if (group.groupKDBX != null) {
if (this.groupKDBX == null)
this.groupKDBX = GroupKDBX()
}
group.groupKDB?.let {
this.groupKDB?.updateWith(it)
}
group.groupKDBX?.let {
this.groupKDBX?.updateWith(it)
}
}
constructor(group: GroupKDB) {
this.groupKDBX = null
this.groupKDB = group
}
constructor(group: GroupKDBX) {
this.groupKDB = null
this.groupKDBX = group
}
constructor(parcel: Parcel) {
groupKDB = parcel.readParcelable(GroupKDB::class.java.classLoader)
groupKDBX = parcel.readParcelable(GroupKDBX::class.java.classLoader)
isVirtual = parcel.readByte().toInt() != 0
}
enum class ChildFilter {
META_STREAM, EXPIRED;
companion object {
fun getDefaults(context: Context): Array<ChildFilter> {
return if (DatabasePreferencesUtil.showExpiredEntries(context)) {
arrayOf(META_STREAM)
} else {
arrayOf(META_STREAM, EXPIRED)
}
}
}
}
companion object CREATOR : Parcelable.Creator<Group> {
override fun createFromParcel(parcel: Parcel): Group {
return Group(parcel)
}
override fun newArray(size: Int): Array<Group?> {
return arrayOfNulls(size)
}
}
override fun describeContents(): Int {
return 0
}
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeParcelable(groupKDB, flags)
dest.writeParcelable(groupKDBX, flags)
dest.writeByte((if (isVirtual) 1 else 0).toByte())
}
override val nodeId: NodeId<*>
get() = groupKDBX?.nodeId ?: groupKDB?.nodeId ?: NodeIdUUID()
override var title: String
get() = groupKDB?.title ?: groupKDBX?.title ?: ""
set(value) {
groupKDB?.title = value
groupKDBX?.title = value
}
override var icon: IconImage
get() = groupKDB?.icon ?: groupKDBX?.icon ?: IconImage()
set(value) {
groupKDB?.icon = value
groupKDBX?.icon = value
}
var tags: Tags
get() = groupKDBX?.tags ?: Tags()
set(value) {
groupKDBX?.tags = value
}
var previousParentGroup: UUID = DatabaseVersioned.UUID_ZERO
get() = groupKDBX?.previousParentGroup ?: DatabaseVersioned.UUID_ZERO
private set
fun setPreviousParentGroup(previousParent: Group?) {
groupKDBX?.previousParentGroup = previousParent?.groupKDBX?.id ?: DatabaseVersioned.UUID_ZERO
}
override val type: Type
get() = Type.GROUP
override var parent: Group?
get() {
groupKDB?.parent?.let {
return Group(it)
}
groupKDBX?.parent?.let {
return Group(it)
}
return null
}
set(value) {
groupKDB?.parent = value?.groupKDB
groupKDBX?.parent = value?.groupKDBX
}
override fun containsParent(): Boolean {
return groupKDB?.containsParent() ?: groupKDBX?.containsParent() ?: false
}
override fun afterAssignNewParent() {
groupKDB?.afterAssignNewParent()
groupKDBX?.afterAssignNewParent()
}
fun addChildrenFrom(group: Group) {
group.groupKDB?.getChildEntries()?.forEach { entryToAdd ->
groupKDB?.addChildEntry(entryToAdd)
entryToAdd.parent = groupKDB
}
group.groupKDB?.getChildGroups()?.forEach { groupToAdd ->
groupKDB?.addChildGroup(groupToAdd)
groupToAdd.parent = groupKDB
}
group.groupKDBX?.getChildEntries()?.forEach { entryToAdd ->
groupKDBX?.addChildEntry(entryToAdd)
entryToAdd.parent = groupKDBX
}
group.groupKDBX?.getChildGroups()?.forEach { groupToAdd ->
groupKDBX?.addChildGroup(groupToAdd)
groupToAdd.parent = groupKDBX
}
}
override fun touch(modified: Boolean, touchParents: Boolean) {
groupKDB?.touch(modified, touchParents)
groupKDBX?.touch(modified, touchParents)
}
override fun isContainedIn(container: Group): Boolean {
var contained: Boolean? = null
container.groupKDB?.let {
contained = groupKDB?.isContainedIn(it)
}
container.groupKDBX?.let {
contained = groupKDBX?.isContainedIn(it)
}
return contained ?: false
}
override fun nodeIndexInParentForNaturalOrder(): Int {
return groupKDB?.nodeIndexInParentForNaturalOrder()
?: groupKDBX?.nodeIndexInParentForNaturalOrder()
?: -1
}
override var creationTime: DateInstant
get() = groupKDB?.creationTime ?: groupKDBX?.creationTime ?: DateInstant()
set(value) {
groupKDB?.creationTime = value
groupKDBX?.creationTime = value
}
override var lastModificationTime: DateInstant
get() = groupKDB?.lastModificationTime ?: groupKDBX?.lastModificationTime ?: DateInstant()
set(value) {
groupKDB?.lastModificationTime = value
groupKDBX?.lastModificationTime = value
}
override var lastAccessTime: DateInstant
get() = groupKDB?.lastAccessTime ?: groupKDBX?.lastAccessTime ?: DateInstant()
set(value) {
groupKDB?.lastAccessTime = value
groupKDBX?.lastAccessTime = value
}
override var expiryTime: DateInstant
get() = groupKDB?.expiryTime ?: groupKDBX?.expiryTime ?: DateInstant()
set(value) {
groupKDB?.expiryTime = value
groupKDBX?.expiryTime = value
}
override var expires: Boolean
get() = groupKDB?.expires ?: groupKDBX?.expires ?: false
set(value) {
groupKDB?.expires = value
groupKDBX?.expires = value
}
override val isCurrentlyExpires: Boolean
get() = groupKDB?.isCurrentlyExpires ?: groupKDBX?.isCurrentlyExpires ?: false
var notes: String?
get() = groupKDBX?.notes
set(value) {
value?.let {
groupKDBX?.notes = it
}
}
var customData: CustomData
get() = groupKDBX?.customData ?: CustomData()
set(value) {
groupKDBX?.customData = value
}
override fun getChildGroups(): List<Group> {
return groupKDB?.getChildGroups()?.map {
Group(it)
} ?:
groupKDBX?.getChildGroups()?.map {
Group(it)
} ?:
ArrayList()
}
fun getFilteredChildGroups(filters: Array<ChildFilter>): List<Group> {
return groupKDB?.getChildGroups()?.map {
Group(it).apply {
this.refreshNumberOfChildEntries(filters)
}
} ?:
groupKDBX?.getChildGroups()?.map {
Group(it).apply {
this.refreshNumberOfChildEntries(filters)
}
} ?:
ArrayList()
}
override fun getChildEntries(): List<Entry> {
return groupKDB?.getChildEntries()?.map {
Entry(it)
} ?:
groupKDBX?.getChildEntries()?.map {
Entry(it)
} ?:
ArrayList()
}
fun getChildEntriesInfo(database: Database): List<EntryInfo> {
val entriesInfo = ArrayList<EntryInfo>()
getChildEntries().forEach { entry ->
entriesInfo.add(entry.getEntryInfo(database))
}
return entriesInfo
}
fun getFilteredChildEntries(filters: Array<ChildFilter>): List<Entry> {
val withoutMetaStream = filters.contains(ChildFilter.META_STREAM)
val showExpiredEntries = !filters.contains(ChildFilter.EXPIRED)
// TODO Change KDB parser to remove meta entries
return groupKDB?.getChildEntries()?.filter {
(!withoutMetaStream || (withoutMetaStream && !it.isMetaStream()))
&& (!it.isCurrentlyExpires or showExpiredEntries)
}?.map {
Entry(it)
} ?:
groupKDBX?.getChildEntries()?.filter {
!it.isCurrentlyExpires or showExpiredEntries
}?.map {
Entry(it)
} ?:
ArrayList()
}
fun refreshNumberOfChildEntries(filters: Array<ChildFilter> = emptyArray()) {
this.numberOfChildEntries = getFilteredChildEntries(filters).size
}
/**
* Filter entries and return children
* @return List of direct children (one level below) as NodeVersioned
*/
fun getChildren(): List<Node> {
return getChildGroups() + getChildEntries()
}
fun getFilteredChildren(filters: Array<ChildFilter>): List<Node> {
val nodes = getFilteredChildGroups(filters) + getFilteredChildEntries(filters)
refreshNumberOfChildEntries(filters)
return nodes
}
override fun addChildGroup(group: Group) {
group.groupKDB?.let {
groupKDB?.addChildGroup(it)
}
group.groupKDBX?.let {
groupKDBX?.addChildGroup(it)
}
}
override fun addChildEntry(entry: Entry) {
entry.entryKDB?.let {
groupKDB?.addChildEntry(it)
}
entry.entryKDBX?.let {
groupKDBX?.addChildEntry(it)
}
}
override fun updateChildGroup(group: Group) {
group.groupKDB?.let {
groupKDB?.updateChildGroup(it)
}
group.groupKDBX?.let {
groupKDBX?.updateChildGroup(it)
}
}
override fun updateChildEntry(entry: Entry) {
entry.entryKDB?.let {
groupKDB?.updateChildEntry(it)
}
entry.entryKDBX?.let {
groupKDBX?.updateChildEntry(it)
}
}
override fun removeChildGroup(group: Group) {
group.groupKDB?.let {
groupKDB?.removeChildGroup(it)
}
group.groupKDBX?.let {
groupKDBX?.removeChildGroup(it)
}
}
override fun removeChildEntry(entry: Entry) {
entry.entryKDB?.let {
groupKDB?.removeChildEntry(it)
}
entry.entryKDBX?.let {
groupKDBX?.removeChildEntry(it)
}
}
override fun removeChildren() {
groupKDB?.removeChildren()
groupKDBX?.removeChildren()
}
val allowAddEntryIfIsRoot: Boolean
get() = groupKDBX != null
val allowAddNoteInGroup: Boolean
get() = groupKDBX != null
/*
------------
KDB Methods
------------
*/
var nodeIdKDB: NodeId<Int>
get() = groupKDB?.nodeId ?: NodeIdInt()
set(value) { groupKDB?.nodeId = value }
fun setNodeId(id: NodeIdInt) {
groupKDB?.nodeId = id
}
/*
------------
KDBX Methods
------------
*/
var nodeIdKDBX: NodeId<UUID>
get() = groupKDBX?.nodeId ?: NodeIdUUID()
set(value) { groupKDBX?.nodeId = value }
fun setNodeId(id: NodeIdUUID) {
groupKDBX?.nodeId = id
}
var searchable: Boolean?
get() = groupKDBX?.enableSearching
set(value) {
groupKDBX?.enableSearching = value
}
fun isSearchable(): Boolean {
val searchableGroup = searchable
if (searchableGroup == null) {
val parenGroup = parent
if (parenGroup == null)
return true
else
return parenGroup.isSearchable()
} else {
return searchableGroup
}
}
var enableAutoType: Boolean?
get() = groupKDBX?.enableAutoType
set(value) {
groupKDBX?.enableAutoType = value
}
var defaultAutoTypeSequence: String
get() = groupKDBX?.defaultAutoTypeSequence ?: ""
set(value) {
groupKDBX?.defaultAutoTypeSequence = value
}
fun setExpanded(expanded: Boolean) {
groupKDBX?.isExpanded = expanded
}
/*
------------
Converter
------------
*/
fun getGroupInfo(): GroupInfo {
val groupInfo = GroupInfo()
groupInfo.id = groupKDBX?.nodeId?.id
groupInfo.title = title
groupInfo.icon = icon
groupInfo.creationTime = creationTime
groupInfo.lastModificationTime = lastModificationTime
groupInfo.expires = expires
groupInfo.expiryTime = expiryTime
groupInfo.notes = notes
groupInfo.searchable = searchable
groupInfo.enableAutoType = enableAutoType
groupInfo.defaultAutoTypeSequence = defaultAutoTypeSequence
groupInfo.tags = tags
groupInfo.customData = customData
return groupInfo
}
fun setGroupInfo(groupInfo: GroupInfo) {
title = groupInfo.title
icon = groupInfo.icon
// Update date time, creation time stay as is
lastModificationTime = DateInstant()
lastAccessTime = DateInstant()
expires = groupInfo.expires
expiryTime = groupInfo.expiryTime
notes = groupInfo.notes
searchable = groupInfo.searchable
enableAutoType = groupInfo.enableAutoType
defaultAutoTypeSequence = groupInfo.defaultAutoTypeSequence
tags = groupInfo.tags
customData = groupInfo.customData
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Group
if (groupKDB != other.groupKDB) return false
if (groupKDBX != other.groupKDBX) return false
return true
}
override fun hashCode(): Int {
var result = groupKDB?.hashCode() ?: 0
result = 31 * result + (groupKDBX?.hashCode() ?: 0)
return result
}
override fun toString(): String {
return groupKDB?.toString() ?: groupKDBX?.toString() ?: "Undefined"
}
}

View file

@ -0,0 +1,276 @@
/*
* Copyright 2022 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*/
package com.kunzisoft.keepass.database.element
import android.content.ContentResolver
import android.net.Uri
import android.os.Parcel
import android.os.Parcelable
import android.util.Base64
import android.util.Log
import com.kunzisoft.encrypt.HashManager
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
import com.kunzisoft.keepass.hardware.HardwareKey
import com.kunzisoft.keepass.utils.StringUtil.removeSpaceChars
import com.kunzisoft.keepass.utils.StringUtil.toHexString
import com.kunzisoft.keepass.utils.UriUtilDatabase
import com.kunzisoft.keepass.utils.readEnum
import com.kunzisoft.keepass.utils.writeEnum
import org.apache.commons.codec.binary.Hex
import org.w3c.dom.Node
import java.io.ByteArrayInputStream
import java.io.IOException
import java.io.InputStream
import java.io.UnsupportedEncodingException
import java.nio.charset.Charset
import javax.xml.XMLConstants
import javax.xml.parsers.DocumentBuilderFactory
import javax.xml.parsers.ParserConfigurationException
data class MainCredential(var password: String? = null,
var keyFileUri: Uri? = null,
var hardwareKey: HardwareKey? = null): Parcelable {
constructor(parcel: Parcel) : this() {
password = parcel.readString()
keyFileUri = parcel.readParcelable(Uri::class.java.classLoader)
hardwareKey = parcel.readEnum<HardwareKey>()
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(password)
parcel.writeParcelable(keyFileUri, flags)
parcel.writeEnum(hardwareKey)
}
override fun describeContents(): Int {
return 0
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as MainCredential
if (password != other.password) return false
if (keyFileUri != other.keyFileUri) return false
if (hardwareKey != other.hardwareKey) return false
return true
}
override fun hashCode(): Int {
var result = password?.hashCode() ?: 0
result = 31 * result + (keyFileUri?.hashCode() ?: 0)
result = 31 * result + (hardwareKey?.hashCode() ?: 0)
return result
}
companion object CREATOR : Parcelable.Creator<MainCredential> {
override fun createFromParcel(parcel: Parcel): MainCredential {
return MainCredential(parcel)
}
override fun newArray(size: Int): Array<MainCredential?> {
return arrayOfNulls(size)
}
private val TAG = MainCredential::class.java.simpleName
@Throws(IOException::class)
fun retrievePasswordKey(key: String,
encoding: Charset
): ByteArray {
val bKey: ByteArray = try {
key.toByteArray(encoding)
} catch (e: UnsupportedEncodingException) {
key.toByteArray()
}
return HashManager.hashSha256(bKey)
}
@Throws(IOException::class)
fun retrieveFileKey(contentResolver: ContentResolver,
keyFileUri: Uri?,
allowXML: Boolean): ByteArray {
if (keyFileUri == null)
throw IOException("Keyfile URI is null")
val keyData = getKeyFileData(contentResolver, keyFileUri)
?: throw IOException("No data retrieved")
try {
// Check XML key file
val xmlKeyByteArray = if (allowXML)
loadXmlKeyFile(ByteArrayInputStream(keyData))
else
null
if (xmlKeyByteArray != null) {
return xmlKeyByteArray
}
// Check 32 bytes key file
when (keyData.size) {
32 -> return keyData
64 -> try {
return Hex.decodeHex(String(keyData).toCharArray())
} catch (ignoredException: Exception) {
// Key is not base 64, treat it as binary data
}
}
// Hash file as binary data
return HashManager.hashSha256(keyData)
} catch (e: Exception) {
throw IOException("Unable to load the keyfile.", e)
}
}
@Throws(IOException::class)
fun retrieveHardwareKey(keyData: ByteArray): ByteArray {
return HashManager.hashSha256(keyData)
}
@Throws(Exception::class)
private fun getKeyFileData(contentResolver: ContentResolver,
keyFileUri: Uri): ByteArray? {
UriUtilDatabase.getUriInputStream(contentResolver, keyFileUri)?.use { keyFileInputStream ->
return keyFileInputStream.readBytes()
}
return null
}
private fun loadXmlKeyFile(keyInputStream: InputStream): ByteArray? {
try {
val documentBuilderFactory = DocumentBuilderFactory.newInstance()
// Disable certain unsecure XML-Parsing DocumentBuilderFactory features
try {
documentBuilderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true)
} catch (e : ParserConfigurationException) {
Log.w(TAG, "Unable to add FEATURE_SECURE_PROCESSING to prevent XML eXternal Entity injection (XXE)")
}
val documentBuilder = documentBuilderFactory.newDocumentBuilder()
val doc = documentBuilder.parse(keyInputStream)
var xmlKeyFileVersion = 1F
val docElement = doc.documentElement
val keyFileChildNodes = docElement.childNodes
// <KeyFile> Root node
if (docElement == null
|| !docElement.nodeName.equals(XML_NODE_ROOT_NAME, ignoreCase = true)) {
return null
}
if (keyFileChildNodes.length < 2)
return null
for (keyFileChildPosition in 0 until keyFileChildNodes.length) {
val keyFileChildNode = keyFileChildNodes.item(keyFileChildPosition)
// <Meta>
if (keyFileChildNode.nodeName.equals(XML_NODE_META_NAME, ignoreCase = true)) {
val metaChildNodes = keyFileChildNode.childNodes
for (metaChildPosition in 0 until metaChildNodes.length) {
val metaChildNode = metaChildNodes.item(metaChildPosition)
// <Version>
if (metaChildNode.nodeName.equals(XML_NODE_VERSION_NAME, ignoreCase = true)) {
val versionChildNodes = metaChildNode.childNodes
for (versionChildPosition in 0 until versionChildNodes.length) {
val versionChildNode = versionChildNodes.item(versionChildPosition)
if (versionChildNode.nodeType == Node.TEXT_NODE) {
val versionText = versionChildNode.textContent.removeSpaceChars()
try {
xmlKeyFileVersion = versionText.toFloat()
Log.i(TAG, "Reading XML KeyFile version : $xmlKeyFileVersion")
} catch (e: Exception) {
Log.e(TAG, "XML Keyfile version cannot be read : $versionText")
}
}
}
}
}
}
// <Key>
if (keyFileChildNode.nodeName.equals(XML_NODE_KEY_NAME, ignoreCase = true)) {
val keyChildNodes = keyFileChildNode.childNodes
for (keyChildPosition in 0 until keyChildNodes.length) {
val keyChildNode = keyChildNodes.item(keyChildPosition)
// <Data>
if (keyChildNode.nodeName.equals(XML_NODE_DATA_NAME, ignoreCase = true)) {
var hashString : String? = null
if (keyChildNode.hasAttributes()) {
val dataNodeAttributes = keyChildNode.attributes
hashString = dataNodeAttributes
.getNamedItem(XML_ATTRIBUTE_DATA_HASH).nodeValue
}
val dataChildNodes = keyChildNode.childNodes
for (dataChildPosition in 0 until dataChildNodes.length) {
val dataChildNode = dataChildNodes.item(dataChildPosition)
if (dataChildNode.nodeType == Node.TEXT_NODE) {
val dataString = dataChildNode.textContent.removeSpaceChars()
when (xmlKeyFileVersion) {
1F -> {
// No hash in KeyFile XML version 1
return Base64.decode(dataString,
DatabaseKDBX.BASE_64_FLAG
)
}
2F -> {
return if (hashString != null
&& checkKeyFileHash(dataString, hashString)
) {
Log.i(TAG, "Successful key file hash check.")
Hex.decodeHex(dataString.toCharArray())
} else {
Log.e(TAG, "Unable to check the hash of the key file.")
null
}
}
}
}
}
}
}
}
}
} catch (e: Exception) {
return null
}
return null
}
private fun checkKeyFileHash(data: String, hash: String): Boolean {
var success = false
try {
// hexadecimal encoding of the first 4 bytes of the SHA-256 hash of the key.
val dataDigest = HashManager.hashSha256(Hex.decodeHex(data.toCharArray()))
.copyOfRange(0, 4).toHexString()
success = dataDigest == hash
} catch (e: Exception) {
e.printStackTrace()
}
return success
}
private const val XML_NODE_ROOT_NAME = "KeyFile"
private const val XML_NODE_META_NAME = "Meta"
private const val XML_NODE_VERSION_NAME = "Version"
private const val XML_NODE_KEY_NAME = "Key"
private const val XML_NODE_DATA_NAME = "Data"
private const val XML_ATTRIBUTE_DATA_HASH = "Hash"
}
}

View file

@ -0,0 +1,223 @@
/*
* Copyright 2018 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element
import com.kunzisoft.keepass.database.element.group.GroupVersionedInterface
import com.kunzisoft.keepass.database.element.node.NodeVersionedInterface
import com.kunzisoft.keepass.database.element.node.Type
import java.util.*
enum class SortNodeEnum {
DB, TITLE, USERNAME, CREATION_TIME, LAST_MODIFY_TIME, LAST_ACCESS_TIME;
fun <G: GroupVersionedInterface<G, *>> getNodeComparator(
database: Database,
sortNodeParameters: SortNodeParameters)
: Comparator<NodeVersionedInterface<G>> {
return when (this) {
DB -> NodeNaturalComparator(database, sortNodeParameters) // Force false because natural order contains recycle bin
TITLE -> NodeTitleComparator(database, sortNodeParameters)
USERNAME -> NodeUsernameComparator(database, sortNodeParameters)
CREATION_TIME -> NodeCreationComparator(database, sortNodeParameters)
LAST_MODIFY_TIME -> NodeLastModificationComparator(database, sortNodeParameters)
LAST_ACCESS_TIME -> NodeLastAccessComparator(database, sortNodeParameters)
}
}
data class SortNodeParameters(var ascending: Boolean = true,
var groupsBefore: Boolean = true,
var recycleBinBottom: Boolean = true)
abstract class NodeComparator
<
G: GroupVersionedInterface<*, *>,
T: NodeVersionedInterface<G>
>(var database: Database, var sortNodeParameters: SortNodeParameters)
: Comparator<T> {
abstract fun compareBySpecificOrder(object1: T, object2: T): Int
private fun specificOrderOrHashIfEquals(object1: T, object2: T): Int {
val specificOrderComp = compareBySpecificOrder(object1, object2)
return when {
specificOrderComp == 0 -> object1.hashCode() - object2.hashCode()
sortNodeParameters.ascending -> specificOrderComp
else -> -specificOrderComp
}
}
override fun compare(object1: T, object2: T): Int {
if (object1 == object2)
return 0
when (object1.type) {
Type.GROUP -> {
when (object2.type) {
Type.GROUP -> {
// RecycleBin at end of groups
if (database.isRecycleBinEnabled && sortNodeParameters.recycleBinBottom) {
if (database.recycleBin == object1)
return 1
if (database.recycleBin == object2)
return -1
}
return specificOrderOrHashIfEquals(object1, object2)
}
Type.ENTRY -> {
return if (sortNodeParameters.groupsBefore)
-1
else
1
}
}
}
Type.ENTRY -> {
return when (object2.type) {
Type.GROUP -> {
if (sortNodeParameters.groupsBefore)
1
else
-1
}
Type.ENTRY -> {
specificOrderOrHashIfEquals(object1, object2)
}
}
}
}
}
}
/**
* Comparator of node by natural database placement
*/
class NodeNaturalComparator<G: GroupVersionedInterface<*, *>, T: NodeVersionedInterface<G>>(
database: Database,
sortNodeParameters: SortNodeParameters)
: NodeComparator<G, T>(database, sortNodeParameters) {
override fun compareBySpecificOrder(object1: T, object2: T): Int {
return object1.nodeIndexInParentForNaturalOrder()
.compareTo(object2.nodeIndexInParentForNaturalOrder())
}
}
/**
* Comparator of Node by Title
*/
class NodeTitleComparator<G: GroupVersionedInterface<*, *>, T: NodeVersionedInterface<G>>(
database: Database,
sortNodeParameters: SortNodeParameters)
: NodeComparator<G, T>(database, sortNodeParameters) {
override fun compareBySpecificOrder(object1: T, object2: T): Int {
val titleCompare = object1.title.compareTo(object2.title, ignoreCase = true)
return if (titleCompare == 0)
NodeNaturalComparator<G, T>(database, sortNodeParameters)
.compare(object1, object2)
else
titleCompare
}
}
/**
* Comparator of Node by Username, Groups by title
*/
class NodeUsernameComparator<G: GroupVersionedInterface<*, *>, T: NodeVersionedInterface<G>>(
database: Database,
sortNodeParameters: SortNodeParameters)
: NodeComparator<G, T>(database, sortNodeParameters) {
override fun compareBySpecificOrder(object1: T, object2: T): Int {
return if (object1.type == Type.ENTRY && object2.type == Type.ENTRY) {
// To get username if it's a ref
val usernameCompare = (object1 as Entry).getEntryInfo(database).username
.compareTo((object2 as Entry).getEntryInfo(database).username,
ignoreCase = true)
if (usernameCompare == 0)
NodeTitleComparator<G, T>(database, sortNodeParameters)
.compare(object1, object2)
else
usernameCompare
} else {
NodeTitleComparator<G, T>(database, sortNodeParameters)
.compare(object1, object2)
}
}
}
/**
* Comparator of node by creation
*/
class NodeCreationComparator<G: GroupVersionedInterface<*, *>, T: NodeVersionedInterface<G>>(
database: Database,
sortNodeParameters: SortNodeParameters)
: NodeComparator<G, T>(database, sortNodeParameters) {
override fun compareBySpecificOrder(object1: T, object2: T): Int {
val creationCompare = object1.creationTime.date
.compareTo(object2.creationTime.date)
return if (creationCompare == 0)
NodeNaturalComparator<G, T>(database, sortNodeParameters)
.compare(object1, object2)
else
creationCompare
}
}
/**
* Comparator of node by last modification
*/
class NodeLastModificationComparator<G: GroupVersionedInterface<*, *>, T: NodeVersionedInterface<G>>(
database: Database,
sortNodeParameters: SortNodeParameters)
: NodeComparator<G, T>(database, sortNodeParameters) {
override fun compareBySpecificOrder(object1: T, object2: T): Int {
val lastModificationCompare = object1.lastModificationTime.date
.compareTo(object2.lastModificationTime.date)
return if (lastModificationCompare == 0)
NodeNaturalComparator<G, T>(database, sortNodeParameters)
.compare(object1, object2)
else
lastModificationCompare
}
}
/**
* Comparator of node by last access
*/
class NodeLastAccessComparator<G: GroupVersionedInterface<*, *>, T: NodeVersionedInterface<G>>(
database: Database,
sortNodeParameters: SortNodeParameters)
: NodeComparator<G, T>(database, sortNodeParameters) {
override fun compareBySpecificOrder(object1: T, object2: T): Int {
val lastAccessCompare = object1.lastAccessTime.date
.compareTo(object2.lastAccessTime.date)
return if (lastAccessCompare == 0)
NodeNaturalComparator<G, T>(database, sortNodeParameters)
.compare(object1, object2)
else
lastAccessCompare
}
}
}

View file

@ -0,0 +1,89 @@
package com.kunzisoft.keepass.database.element
import android.os.Parcel
import android.os.Parcelable
import com.kunzisoft.keepass.utils.StringUtil.removeSpaceChars
class Tags: Parcelable {
private val mTags = mutableListOf<String>()
constructor()
constructor(values: String): this() {
mTags.addAll(values
.split(DELIMITER, DELIMITER1)
.filter { it.removeSpaceChars().isNotEmpty() }
)
}
constructor(parcel: Parcel) : this() {
parcel.readStringList(mTags)
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeStringList(mTags)
}
override fun describeContents(): Int {
return 0
}
fun setTags(tags: Tags) {
mTags.clear()
mTags.addAll(tags.mTags)
}
fun get(position: Int): String {
return mTags[position]
}
fun put(tag: String) {
if (tag.removeSpaceChars().isNotEmpty() && !mTags.contains(tag))
mTags.add(tag)
}
fun put(tags: Tags) {
tags.mTags.forEach {
put(it)
}
}
fun isEmpty(): Boolean {
return mTags.isEmpty()
}
fun isNotEmpty(): Boolean {
return !isEmpty()
}
fun size(): Int {
return mTags.size
}
fun clear() {
mTags.clear()
}
fun toList(): List<String> {
return mTags
}
override fun toString(): String {
return mTags.joinToString(DELIMITER.toString())
}
companion object CREATOR : Parcelable.Creator<Tags> {
const val DELIMITER= ','
const val DELIMITER1= ';'
val DELIMITERS = listOf(',', ';')
override fun createFromParcel(parcel: Parcel): Tags {
return Tags(parcel)
}
override fun newArray(size: Int): Array<Tags?> {
return arrayOfNulls(size)
}
}
}

View file

@ -0,0 +1,44 @@
/*
* Copyright 2021 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.binary
class AttachmentPool : BinaryPool<Int>() {
/**
* Utility method to find an unused key in the pool
*/
override fun findUnusedKey(): Int {
var unusedKey = 0
while (pool[unusedKey] != null)
unusedKey++
return unusedKey
}
/**
* To register a binary with a ref corresponding to an ordered index
*/
fun getBinaryIndexFromKey(key: Int): Int? {
val index = orderedBinariesWithoutDuplication().indexOfFirst { it.keys.contains(key) }
return if (index < 0)
null
else
index
}
}

View file

@ -0,0 +1,145 @@
/*
* Copyright 2018 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.binary
import android.os.Parcel
import android.os.Parcelable
import android.util.Base64
import android.util.Base64InputStream
import android.util.Base64OutputStream
import com.kunzisoft.keepass.utils.readAllBytes
import com.kunzisoft.keepass.database.element.binary.BinaryCache.Companion.UNKNOWN
import java.io.*
import java.util.zip.GZIPOutputStream
class BinaryByte : BinaryData {
private var mDataByteId: String
private fun getByteArray(binaryCache: BinaryCache): ByteArray {
val keyData = binaryCache.getByteArray(mDataByteId)
mDataByteId = keyData.key
return keyData.data
}
constructor() : super() {
mDataByteId = UNKNOWN
}
constructor(id: String,
compressed: Boolean = false,
protected: Boolean = false) : super(compressed, protected) {
mDataByteId = id
}
constructor(parcel: Parcel) : super(parcel) {
mDataByteId = parcel.readString() ?: UNKNOWN
}
override fun writeToParcel(dest: Parcel, flags: Int) {
super.writeToParcel(dest, flags)
dest.writeString(mDataByteId)
}
@Throws(IOException::class)
override fun getInputDataStream(binaryCache: BinaryCache): InputStream {
return Base64InputStream(ByteArrayInputStream(getByteArray(binaryCache)), Base64.NO_WRAP)
}
@Throws(IOException::class)
override fun getOutputDataStream(binaryCache: BinaryCache): OutputStream {
return BinaryCountingOutputStream(Base64OutputStream(ByteOutputStream(binaryCache), Base64.NO_WRAP))
}
@Throws(IOException::class)
override fun compress(binaryCache: BinaryCache) {
if (!isCompressed) {
GZIPOutputStream(getOutputDataStream(binaryCache)).use { outputStream ->
getInputDataStream(binaryCache).use { inputStream ->
inputStream.readAllBytes { buffer ->
outputStream.write(buffer)
}
}
isCompressed = true
}
}
}
@Throws(IOException::class)
override fun decompress(binaryCache: BinaryCache) {
if (isCompressed) {
getUnGzipInputDataStream(binaryCache).use { inputStream ->
getOutputDataStream(binaryCache).use { outputStream ->
inputStream.readAllBytes { buffer ->
outputStream.write(buffer)
}
}
isCompressed = false
}
}
}
@Throws(IOException::class)
override fun clear(binaryCache: BinaryCache) {
binaryCache.removeByteArray(mDataByteId)
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is BinaryByte) return false
if (!super.equals(other)) return false
if (mDataByteId != other.mDataByteId) return false
return true
}
override fun hashCode(): Int {
var result = super.hashCode()
result = 31 * result + mDataByteId.hashCode()
return result
}
/**
* Custom OutputStream to calculate the size and hash of binary file
*/
private inner class ByteOutputStream(private val binaryCache: BinaryCache) : ByteArrayOutputStream() {
override fun close() {
binaryCache.setByteArray(mDataByteId, this.toByteArray())
super.close()
}
}
companion object {
private val TAG = BinaryByte::class.java.name
@JvmField
val CREATOR: Parcelable.Creator<BinaryByte> = object : Parcelable.Creator<BinaryByte> {
override fun createFromParcel(parcel: Parcel): BinaryByte {
return BinaryByte(parcel)
}
override fun newArray(size: Int): Array<BinaryByte?> {
return arrayOfNulls(size)
}
}
}
}

View file

@ -0,0 +1,82 @@
package com.kunzisoft.keepass.database.element.binary
import java.io.File
import java.util.*
class BinaryCache {
/**
* Cipher key generated when the database is loaded, and destroyed when the database is closed
* Can be used to temporarily store database elements
*/
var loadedCipherKey: LoadedKey = LoadedKey.generateNewCipherKey()
var cacheDirectory: File? = null
private val voidBinary = KeyByteArray(UNKNOWN, ByteArray(0))
fun getBinaryData(binaryId: String,
smallSize: Boolean = false,
compression: Boolean = false,
protection: Boolean = false): BinaryData {
val cacheDir = cacheDirectory
return if (smallSize || cacheDir == null) {
BinaryByte(binaryId, compression, protection)
} else {
val fileInCache = File(cacheDir, binaryId)
BinaryFile(fileInCache, compression, protection)
}
}
// Similar to file storage but much faster TODO SparseArray
private val byteArrayList = HashMap<String, ByteArray>()
fun getByteArray(key: String): KeyByteArray {
if (key == UNKNOWN) {
return voidBinary
}
if (!byteArrayList.containsKey(key)) {
val newItem = KeyByteArray(key, ByteArray(0))
byteArrayList[newItem.key] = newItem.data
return newItem
}
return KeyByteArray(key, byteArrayList[key]!!)
}
fun setByteArray(key: String, data: ByteArray): KeyByteArray {
if (key == UNKNOWN) {
return voidBinary
}
byteArrayList[key] = data
return KeyByteArray(key, data)
}
fun removeByteArray(key: String?) {
key?.let {
byteArrayList.remove(it)
}
}
fun clear() {
byteArrayList.clear()
}
companion object {
const val UNKNOWN = "UNKNOWN"
}
data class KeyByteArray(val key: String, val data: ByteArray) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is KeyByteArray) return false
if (key != other.key) return false
return true
}
override fun hashCode(): Int {
return key.hashCode()
}
}
}

View file

@ -0,0 +1,196 @@
/*
* Copyright 2018 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.binary
import android.app.ActivityManager
import android.content.Context
import android.os.Parcel
import android.os.Parcelable
import org.apache.commons.io.output.CountingOutputStream
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import java.nio.ByteBuffer
import java.security.MessageDigest
import java.util.zip.GZIPInputStream
import java.util.zip.GZIPOutputStream
abstract class BinaryData : Parcelable {
var isCompressed: Boolean = false
protected set
var isProtected: Boolean = false
protected set
var isCorrupted: Boolean = false
private var mLength: Long = 0
private var mBinaryHash = 0
protected constructor(compressed: Boolean = false, protected: Boolean = false) {
this.isCompressed = compressed
this.isProtected = protected
this.mLength = 0
this.mBinaryHash = 0
}
protected constructor(parcel: Parcel) {
isCompressed = parcel.readByte().toInt() != 0
isProtected = parcel.readByte().toInt() != 0
isCorrupted = parcel.readByte().toInt() != 0
mLength = parcel.readLong()
mBinaryHash = parcel.readInt()
}
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeByte((if (isCompressed) 1 else 0).toByte())
dest.writeByte((if (isProtected) 1 else 0).toByte())
dest.writeByte((if (isCorrupted) 1 else 0).toByte())
dest.writeLong(mLength)
dest.writeInt(mBinaryHash)
}
@Throws(IOException::class)
abstract fun getInputDataStream(binaryCache: BinaryCache): InputStream
@Throws(IOException::class)
abstract fun getOutputDataStream(binaryCache: BinaryCache): OutputStream
@Throws(IOException::class)
fun getUnGzipInputDataStream(binaryCache: BinaryCache): InputStream {
return if (isCompressed) {
GZIPInputStream(getInputDataStream(binaryCache))
} else {
getInputDataStream(binaryCache)
}
}
@Throws(IOException::class)
fun getGzipOutputDataStream(binaryCache: BinaryCache): OutputStream {
return if (isCompressed) {
GZIPOutputStream(getOutputDataStream(binaryCache))
} else {
getOutputDataStream(binaryCache)
}
}
@Throws(IOException::class)
abstract fun compress(binaryCache: BinaryCache)
@Throws(IOException::class)
abstract fun decompress(binaryCache: BinaryCache)
@Throws(IOException::class)
fun dataExists(): Boolean {
return mLength > 0
}
@Throws(IOException::class)
fun getSize(): Long {
return mLength
}
@Throws(IOException::class)
fun binaryHash(): Int {
return mBinaryHash
}
@Throws(IOException::class)
abstract fun clear(binaryCache: BinaryCache)
override fun describeContents(): Int {
return 0
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is BinaryData) return false
if (isCompressed != other.isCompressed) return false
if (isProtected != other.isProtected) return false
if (isCorrupted != other.isCorrupted) return false
return true
}
override fun hashCode(): Int {
var result = isCompressed.hashCode()
result = 31 * result + isProtected.hashCode()
result = 31 * result + isCorrupted.hashCode()
result = 31 * result + mLength.hashCode()
result = 31 * result + mBinaryHash
return result
}
/**
* Custom OutputStream to calculate the size and hash of binary file
*/
protected inner class BinaryCountingOutputStream(out: OutputStream): CountingOutputStream(out) {
private val mMessageDigest: MessageDigest
init {
mLength = 0
mMessageDigest = MessageDigest.getInstance("MD5")
mBinaryHash = 0
}
override fun beforeWrite(n: Int) {
super.beforeWrite(n)
mLength = byteCount
}
override fun write(idx: Int) {
super.write(idx)
mMessageDigest.update(idx.toByte())
}
override fun write(bts: ByteArray) {
super.write(bts)
mMessageDigest.update(bts)
}
override fun write(bts: ByteArray, st: Int, end: Int) {
super.write(bts, st, end)
mMessageDigest.update(bts, st, end)
}
override fun close() {
super.close()
mLength = byteCount
val bytes = mMessageDigest.digest()
mBinaryHash = ByteBuffer.wrap(bytes).int
}
}
companion object {
private val TAG = BinaryData::class.java.name
private const val MAX_BINARY_BYTE = 10485760 // 10 MB
fun canMemoryBeAllocatedInRAM(context: Context, memoryWanted: Long): Boolean {
if (memoryWanted > MAX_BINARY_BYTE)
return false
val memoryInfo = ActivityManager.MemoryInfo()
(context.getSystemService(Context.ACTIVITY_SERVICE)
as? ActivityManager?)?.getMemoryInfo(memoryInfo)
val availableMemory = memoryInfo.availMem
return availableMemory > (memoryWanted * 5)
}
}
}

View file

@ -0,0 +1,183 @@
/*
* Copyright 2018 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.binary
import android.os.Parcel
import android.os.Parcelable
import android.util.Base64
import android.util.Base64InputStream
import android.util.Base64OutputStream
import com.kunzisoft.keepass.utils.readAllBytes
import java.io.*
import java.util.zip.GZIPOutputStream
import javax.crypto.Cipher
import javax.crypto.CipherInputStream
import javax.crypto.CipherOutputStream
import javax.crypto.spec.IvParameterSpec
class BinaryFile : BinaryData {
private var mDataFile: File? = null
// Cipher to encrypt temp file
@Transient
private var cipherEncryption: Cipher = Cipher.getInstance(LoadedKey.BINARY_CIPHER)
@Transient
private var cipherDecryption: Cipher = Cipher.getInstance(LoadedKey.BINARY_CIPHER)
constructor(dataFile: File,
compressed: Boolean = false,
protected: Boolean = false) : super(compressed, protected) {
this.mDataFile = dataFile
}
constructor(parcel: Parcel) : super(parcel) {
parcel.readString()?.let {
mDataFile = File(it)
}
}
override fun writeToParcel(dest: Parcel, flags: Int) {
super.writeToParcel(dest, flags)
dest.writeString(mDataFile?.absolutePath)
}
@Throws(IOException::class)
override fun getInputDataStream(binaryCache: BinaryCache): InputStream {
return buildInputStream(mDataFile, binaryCache)
}
@Throws(IOException::class)
override fun getOutputDataStream(binaryCache: BinaryCache): OutputStream {
return buildOutputStream(mDataFile, binaryCache)
}
@Throws(IOException::class)
private fun buildInputStream(file: File?, binaryCache: BinaryCache): InputStream {
val cipherKey = binaryCache.loadedCipherKey
return when {
file != null && file.length() > 0 -> {
cipherDecryption.init(Cipher.DECRYPT_MODE, cipherKey.key, IvParameterSpec(cipherKey.iv))
Base64InputStream(CipherInputStream(FileInputStream(file), cipherDecryption), Base64.NO_WRAP)
}
else -> ByteArrayInputStream(ByteArray(0))
}
}
@Throws(IOException::class)
private fun buildOutputStream(file: File?, binaryCache: BinaryCache): OutputStream {
val cipherKey = binaryCache.loadedCipherKey
return when {
file != null -> {
cipherEncryption.init(Cipher.ENCRYPT_MODE, cipherKey.key, IvParameterSpec(cipherKey.iv))
BinaryCountingOutputStream(Base64OutputStream(CipherOutputStream(FileOutputStream(file), cipherEncryption), Base64.NO_WRAP))
}
else -> throw IOException("Unable to write in an unknown file")
}
}
@Throws(IOException::class)
override fun compress(binaryCache: BinaryCache) {
mDataFile?.let { concreteDataFile ->
// To compress, create a new binary with file
if (!isCompressed) {
// Encrypt the new gzipped temp file
val fileBinaryCompress = File(concreteDataFile.parent, concreteDataFile.name + "_temp")
getInputDataStream(binaryCache).use { inputStream ->
GZIPOutputStream(buildOutputStream(fileBinaryCompress, binaryCache)).use { outputStream ->
inputStream.readAllBytes { buffer ->
outputStream.write(buffer)
}
}
}
// Remove ungzip file
if (concreteDataFile.delete()) {
if (fileBinaryCompress.renameTo(concreteDataFile)) {
// Harmonize with database compression
isCompressed = true
}
}
}
}
}
@Throws(IOException::class)
override fun decompress(binaryCache: BinaryCache) {
mDataFile?.let { concreteDataFile ->
if (isCompressed) {
// Encrypt the new ungzipped temp file
val fileBinaryDecompress = File(concreteDataFile.parent, concreteDataFile.name + "_temp")
getUnGzipInputDataStream(binaryCache).use { inputStream ->
buildOutputStream(fileBinaryDecompress, binaryCache).use { outputStream ->
inputStream.readAllBytes { buffer ->
outputStream.write(buffer)
}
}
}
// Remove gzip file
if (concreteDataFile.delete()) {
if (fileBinaryDecompress.renameTo(concreteDataFile)) {
// Harmonize with database compression
isCompressed = false
}
}
}
}
}
override fun clear(binaryCache: BinaryCache) {
if (mDataFile != null && !mDataFile!!.delete())
throw IOException("Unable to delete temp file " + mDataFile!!.absolutePath)
}
override fun toString(): String {
return mDataFile.toString()
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is BinaryFile) return false
if (!super.equals(other)) return false
return mDataFile != null && mDataFile == other.mDataFile
}
override fun hashCode(): Int {
var result = super.hashCode()
result = 31 * result + (mDataFile?.hashCode() ?: 0)
return result
}
companion object {
private val TAG = BinaryFile::class.java.name
@JvmField
val CREATOR: Parcelable.Creator<BinaryFile> = object : Parcelable.Creator<BinaryFile> {
override fun createFromParcel(parcel: Parcel): BinaryFile {
return BinaryFile(parcel)
}
override fun newArray(size: Int): Array<BinaryFile?> {
return arrayOfNulls(size)
}
}
}
}

View file

@ -0,0 +1,259 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.binary
import android.util.Log
import java.io.IOException
import kotlin.math.abs
abstract class BinaryPool<T> {
protected val pool = LinkedHashMap<T, BinaryData>()
// To build unique file id
private var creationId: Long = System.currentTimeMillis()
private var poolId: Int = abs(javaClass.simpleName.hashCode())
private var binaryFileIncrement = 0L
/**
* To get a binary by the pool key (ref attribute in entry)
*/
operator fun get(key: T): BinaryData? {
return pool[key]
}
/**
* Create and return a new binary file not yet linked to a binary
*/
fun put(key: T? = null,
builder: (uniqueBinaryId: String) -> BinaryData): KeyBinary<T> {
binaryFileIncrement++
val newBinaryFile: BinaryData = builder("$poolId$creationId$binaryFileIncrement")
val newKey = put(key, newBinaryFile)
return KeyBinary(newBinaryFile, newKey)
}
/**
* To linked a binary with a pool key, if the pool key doesn't exists, create an unused one
*/
fun put(key: T?, value: BinaryData): T {
if (key == null)
return put(value)
else
pool[key] = value
return key
}
/**
* To put a [binaryData] in the pool,
* if already exists, replace the current one,
* else add it with a new key
*/
fun put(binaryData: BinaryData): T {
var key: T? = findKey(binaryData)
if (key == null) {
key = findUnusedKey()
}
pool[key!!] = binaryData
return key
}
/**
* Remove a binary from the pool with its [key], the file is not deleted
*/
@Throws(IOException::class)
fun remove(key: T) {
pool.remove(key)
// Don't clear attachment here because a file can be used in many BinaryAttachment
}
/**
* Remove a binary from the pool, the file is not deleted
*/
@Throws(IOException::class)
fun remove(binaryData: BinaryData) {
findKey(binaryData)?.let {
pool.remove(it)
}
// Don't clear attachment here because a file can be used in many BinaryAttachment
}
/**
* Utility method to find an unused key in the pool
*/
abstract fun findUnusedKey(): T
/**
* Return key of [binaryDataToRetrieve] or null if not found
*/
private fun findKey(binaryDataToRetrieve: BinaryData): T? {
val contains = pool.containsValue(binaryDataToRetrieve)
return if (!contains)
null
else {
for ((key, binary) in pool) {
if (binary == binaryDataToRetrieve) {
return key
}
}
return null
}
}
fun isBinaryDuplicate(binaryData: BinaryData?): Boolean {
try {
binaryData?.let {
if (it.getSize() > 0) {
val searchBinaryMD5 = it.binaryHash()
var i = 0
for ((_, binary) in pool) {
if (binary.binaryHash() == searchBinaryMD5) {
i++
if (i > 1)
return true
}
}
}
}
} catch (e: Exception) {
Log.e(TAG, "Unable to check binary duplication", e)
}
return false
}
/**
* To do an action on each binary in the pool (order is not important)
*/
private fun doForEachBinary(action: (key: T, binary: BinaryData) -> Unit,
condition: (key: T, binary: BinaryData) -> Boolean) {
for ((key, value) in pool) {
if (condition.invoke(key, value)) {
action.invoke(key, value)
}
}
}
fun doForEachBinary(action: (key: T, binary: BinaryData) -> Unit) {
doForEachBinary(action) { _, _ -> true }
}
/**
* Utility method to order binaries and solve index problem in database v4
*/
protected fun orderedBinariesWithoutDuplication(condition: ((binary: BinaryData) -> Boolean) = { true })
: List<KeyBinary<T>> {
val keyBinaryList = ArrayList<KeyBinary<T>>()
for ((key, binary) in pool) {
// Don't deduplicate
val existentBinary =
try {
if (binary.getSize() > 0) {
keyBinaryList.find {
val hash0 = it.binary.binaryHash()
val hash1 = binary.binaryHash()
hash0 != 0 && hash1 != 0 && hash0 == hash1
}
} else {
null
}
} catch (e: Exception) {
Log.e(TAG, "Unable to check binary hash", e)
null
}
if (existentBinary == null) {
val newKeyBinary = KeyBinary(binary, key)
if (condition.invoke(newKeyBinary.binary)) {
keyBinaryList.add(newKeyBinary)
}
} else {
if (condition.invoke(existentBinary.binary)) {
existentBinary.addKey(key)
}
}
}
return keyBinaryList
}
/**
* Different from doForEach, provide an ordered index to each binary
*/
private fun doForEachBinaryWithoutDuplication(action: (keyBinary: KeyBinary<T>) -> Unit,
conditionToAdd: (binary: BinaryData) -> Boolean) {
orderedBinariesWithoutDuplication(conditionToAdd).forEach { keyBinary ->
action.invoke(keyBinary)
}
}
fun doForEachBinaryWithoutDuplication(action: (keyBinary: KeyBinary<T>) -> Unit) {
doForEachBinaryWithoutDuplication(action, { true })
}
/**
* Different from doForEach, provide an ordered index to each binary
*/
private fun doForEachOrderedBinaryWithoutDuplication(action: (index: Int, binary: BinaryData) -> Unit,
conditionToAdd: (binary: BinaryData) -> Boolean) {
orderedBinariesWithoutDuplication(conditionToAdd).forEachIndexed { index, keyBinary ->
action.invoke(index, keyBinary.binary)
}
}
fun doForEachOrderedBinaryWithoutDuplication(action: (index: Int, binary: BinaryData) -> Unit) {
doForEachOrderedBinaryWithoutDuplication(action, { true })
}
fun isEmpty(): Boolean {
return pool.isEmpty()
}
@Throws(IOException::class)
fun clear() {
pool.clear()
}
override fun toString(): String {
val stringBuffer = StringBuffer()
for ((key, value) in pool) {
if (stringBuffer.isNotEmpty())
stringBuffer.append(", {$key:$value}")
else
stringBuffer.append("{$key:$value}")
}
return stringBuffer.toString()
}
/**
* Utility class to order binaries
*/
class KeyBinary<T>(val binary: BinaryData, key: T) {
val keys = HashSet<T>()
init {
addKey(key)
}
fun addKey(key: T) {
keys.add(key)
}
}
companion object {
private val TAG = BinaryPool::class.java.name
}
}

View file

@ -0,0 +1,44 @@
package com.kunzisoft.keepass.database.element.binary
import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.icon.IconImageCustom
import java.util.*
class CustomIconPool : BinaryPool<UUID>() {
private val customIcons = HashMap<UUID, IconImageCustom>()
fun put(key: UUID? = null,
name: String,
lastModificationTime: DateInstant?,
builder: (uniqueBinaryId: String) -> BinaryData,
result: (IconImageCustom, BinaryData?) -> Unit) {
val keyBinary = super.put(key, builder)
val uuid = keyBinary.keys.first()
val customIcon = IconImageCustom(uuid, name, lastModificationTime)
customIcons[uuid] = customIcon
result.invoke(customIcon, keyBinary.binary)
}
override fun findUnusedKey(): UUID {
var newUUID = UUID.randomUUID()
while (pool.containsKey(newUUID)) {
newUUID = UUID.randomUUID()
}
return newUUID
}
fun getCustomIcon(key: UUID): IconImageCustom? {
return customIcons[key]
}
fun any(predicate: (IconImageCustom)-> Boolean): Boolean {
return customIcons.any { predicate(it.value) }
}
fun doForEachCustomIcon(action: (customIcon: IconImageCustom, binary: BinaryData) -> Unit) {
doForEachBinary { key, binary ->
action.invoke(customIcons[key] ?: IconImageCustom(key), binary)
}
}
}

View file

@ -0,0 +1,18 @@
package com.kunzisoft.keepass.database.element.binary
import java.io.Serializable
import java.security.Key
import java.security.SecureRandom
import javax.crypto.KeyGenerator
class LoadedKey(val key: Key, val iv: ByteArray): Serializable {
companion object {
const val BINARY_CIPHER = "Blowfish/CBC/PKCS5Padding"
fun generateNewCipherKey(): LoadedKey {
val iv = ByteArray(8)
SecureRandom().nextBytes(iv)
return LoadedKey(KeyGenerator.getInstance("Blowfish").generateKey(), iv)
}
}
}

View file

@ -0,0 +1,25 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.database
enum class CompressionAlgorithm {
None,
GZip;
}

View file

@ -0,0 +1,252 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*/
package com.kunzisoft.keepass.database.element.database
import android.content.ContentResolver
import com.kunzisoft.encrypt.HashManager
import com.kunzisoft.encrypt.aes.AESTransformer
import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm
import com.kunzisoft.keepass.database.crypto.kdf.KdfEngine
import com.kunzisoft.keepass.database.crypto.kdf.KdfFactory
import com.kunzisoft.keepass.database.element.MainCredential
import com.kunzisoft.keepass.database.element.binary.BinaryData
import com.kunzisoft.keepass.database.element.entry.EntryKDB
import com.kunzisoft.keepass.database.element.group.GroupKDB
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
import com.kunzisoft.keepass.database.element.node.NodeIdInt
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.database.element.node.NodeVersioned
import com.kunzisoft.keepass.database.exception.EmptyKeyDatabaseException
import com.kunzisoft.keepass.database.exception.HardwareKeyDatabaseException
import java.io.IOException
import java.nio.charset.Charset
import java.util.*
class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
override var encryptionAlgorithm: EncryptionAlgorithm = EncryptionAlgorithm.AESRijndael
override val availableEncryptionAlgorithms: List<EncryptionAlgorithm> = listOf(
EncryptionAlgorithm.AESRijndael,
EncryptionAlgorithm.Twofish
)
override var kdfEngine: KdfEngine?
get() = kdfAvailableList[0]
set(value) {
value?.let {
numberKeyEncryptionRounds = value.defaultKeyRounds
}
}
override val kdfAvailableList: List<KdfEngine> = listOf(
KdfFactory.aesKdf
)
override val passwordEncoding: Charset
get() = Charsets.ISO_8859_1
override var numberKeyEncryptionRounds = 300L
override val version: String
get() = "V1"
override val defaultFileExtension: String
get() = ".kdb"
init {
// New manual root because KDB contains multiple root groups (here available with getRootGroups())
rootGroup = createGroup().apply {
icon.standard = getStandardIcon(IconImageStandard.DATABASE_ID)
}
}
val backupGroup: GroupKDB?
get() {
return retrieveBackup()
}
val groupNamesNotAllowed: List<String>
get() {
return listOf(BACKUP_FOLDER_TITLE)
}
var defaultUserName: String = ""
var color: Int? = null
/**
* Generates an unused random tree id
*
* @return new tree id
*/
override fun newGroupId(): NodeIdInt {
var newId: NodeIdInt
do {
newId = NodeIdInt()
} while (isGroupIdUsed(newId))
return newId
}
/**
* Generates an unused random tree id
*
* @return new tree id
*/
override fun newEntryId(): NodeIdUUID {
var newId: NodeIdUUID
do {
newId = NodeIdUUID()
} while (isEntryIdUsed(newId))
return newId
}
@Throws(IOException::class)
fun makeFinalKey(masterSeed: ByteArray, transformSeed: ByteArray, numRounds: Long) {
// Encrypt the master key a few times to make brute-force key-search harder
val transformedKey = AESTransformer.transformKey(transformSeed, masterKey, numRounds) ?: ByteArray(0)
// Write checksum Checksum
finalKey = HashManager.hashSha256(masterSeed, transformedKey)
}
fun deriveMasterKey(
contentResolver: ContentResolver,
mainCredential: MainCredential
) {
// Exception when no password
if (mainCredential.hardwareKey != null)
throw HardwareKeyDatabaseException()
if (mainCredential.password == null && mainCredential.keyFileUri == null)
throw EmptyKeyDatabaseException()
// Retrieve plain data
val password = mainCredential.password
val keyFileUri = mainCredential.keyFileUri
val passwordBytes = if (password != null) MainCredential.retrievePasswordKey(
password,
passwordEncoding
) else null
val keyFileBytes = if (keyFileUri != null) MainCredential.retrieveFileKey(
contentResolver,
keyFileUri,
false
) else null
// Build master key
if (passwordBytes != null
&& keyFileBytes != null) {
this.masterKey = HashManager.hashSha256(
passwordBytes,
keyFileBytes
)
} else {
this.masterKey = passwordBytes ?: keyFileBytes ?: byteArrayOf(0)
}
}
override fun createGroup(): GroupKDB {
return GroupKDB()
}
override fun createEntry(): EntryKDB {
return EntryKDB()
}
override fun rootCanContainsEntry(): Boolean {
return false
}
override fun getStandardIcon(iconId: Int): IconImageStandard {
return this.iconsManager.getIcon(iconId)
}
override fun isInRecycleBin(group: GroupKDB): Boolean {
var currentGroup: GroupKDB? = group
val currentBackupGroup = backupGroup ?: return false
if (currentGroup == currentBackupGroup)
return true
val backupGroupId = currentBackupGroup.id
while (currentGroup != null) {
if (backupGroupId == currentGroup.id) {
return true
}
currentGroup = currentGroup.parent
}
return false
}
/**
* Retrieve backup group with his name
*/
private fun retrieveBackup(): GroupKDB? {
return rootGroup?.searchChildGroup {
it.title.equals(BACKUP_FOLDER_TITLE, ignoreCase = true)
}
}
/**
* Ensure that the backup tree exists if enabled, and create it
* if it doesn't exist
*/
fun ensureBackupExists() {
if (backupGroup == null) {
// Create recycle bin
val recycleBinGroup = createGroup().apply {
title = BACKUP_FOLDER_TITLE
icon.standard = getStandardIcon(IconImageStandard.TRASH_ID)
}
addGroupTo(recycleBinGroup, rootGroup)
}
}
/**
* Define if a Node must be delete or recycle when remove action is called
* @param node Node to remove
* @return true if node can be recycle, false elsewhere
*/
fun canRecycle(node: NodeVersioned<*, GroupKDB, EntryKDB>): Boolean {
if (backupGroup == null)
ensureBackupExists()
if (node == backupGroup)
return false
backupGroup?.let {
if (node.isContainedIn(it))
return false
}
return true
}
fun buildNewBinaryAttachment(): BinaryData {
// Generate an unique new file
return attachmentPool.put { uniqueBinaryId ->
binaryCache.getBinaryData(uniqueBinaryId, false)
}.binary
}
companion object {
val TYPE = DatabaseKDB::class.java
const val BACKUP_FOLDER_TITLE = "Backup"
}
}

View file

@ -0,0 +1,912 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.database
import android.content.ContentResolver
import android.content.res.Resources
import android.util.Base64
import android.util.Log
import com.kunzisoft.encrypt.HashManager
import com.kunzisoft.keepass.database.R
import com.kunzisoft.keepass.database.action.node.NodeHandler
import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm
import com.kunzisoft.keepass.database.crypto.VariantDictionary
import com.kunzisoft.keepass.database.crypto.kdf.AesKdf
import com.kunzisoft.keepass.database.crypto.kdf.KdfEngine
import com.kunzisoft.keepass.database.crypto.kdf.KdfFactory
import com.kunzisoft.keepass.database.crypto.kdf.KdfParameters
import com.kunzisoft.keepass.database.element.CompositeKey
import com.kunzisoft.keepass.database.element.CustomData
import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.DeletedObject
import com.kunzisoft.keepass.database.element.MainCredential
import com.kunzisoft.keepass.database.element.Tags
import com.kunzisoft.keepass.database.element.binary.BinaryData
import com.kunzisoft.keepass.database.element.database.DatabaseKDB.Companion.BACKUP_FOLDER_TITLE
import com.kunzisoft.keepass.database.element.entry.EntryKDBX
import com.kunzisoft.keepass.database.element.entry.FieldReferencesEngine
import com.kunzisoft.keepass.database.element.group.GroupKDBX
import com.kunzisoft.keepass.database.element.icon.IconImageCustom
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface
import com.kunzisoft.keepass.database.element.node.NodeVersioned
import com.kunzisoft.keepass.database.element.security.MemoryProtectionConfig
import com.kunzisoft.keepass.database.element.template.Template
import com.kunzisoft.keepass.database.element.template.TemplateEngineCompatible
import com.kunzisoft.keepass.database.exception.DatabaseOutputException
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_31
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_40
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_41
import com.kunzisoft.keepass.hardware.HardwareKey
import com.kunzisoft.keepass.utils.UnsignedInt
import com.kunzisoft.keepass.utils.longTo8Bytes
import java.io.IOException
import java.nio.charset.Charset
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
import java.util.Arrays
import java.util.UUID
import javax.crypto.Mac
import kotlin.math.min
class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
// To resave the database with same credential when already loaded
private var mCompositeKey = CompositeKey()
var hmacKey: ByteArray? = null
private set
override var encryptionAlgorithm: EncryptionAlgorithm = EncryptionAlgorithm.AESRijndael
fun setEncryptionAlgorithmFromUUID(uuid: UUID) {
encryptionAlgorithm = EncryptionAlgorithm.getFrom(uuid)
}
override val availableEncryptionAlgorithms: List<EncryptionAlgorithm> = listOf(
EncryptionAlgorithm.AESRijndael,
EncryptionAlgorithm.Twofish,
EncryptionAlgorithm.ChaCha20
)
var kdfParameters: KdfParameters? = null
override var kdfEngine: KdfEngine?
get() = getKdfEngineFromParameters(kdfParameters)
set(value) {
value?.let {
if (kdfParameters?.uuid != value.defaultParameters.uuid)
kdfParameters = value.defaultParameters
numberKeyEncryptionRounds = value.defaultKeyRounds
memoryUsage = value.defaultMemoryUsage
parallelism = value.defaultParallelism
}
}
private fun getKdfEngineFromParameters(kdfParameters: KdfParameters?): KdfEngine? {
if (kdfParameters == null) {
return null
}
for (engine in kdfAvailableList) {
if (engine.uuid == kdfParameters.uuid) {
return engine
}
}
return null
}
fun randomizeKdfParameters() {
kdfParameters?.let {
kdfEngine?.randomize(it)
}
}
override val kdfAvailableList: List<KdfEngine> = listOf(
KdfFactory.aesKdf,
KdfFactory.argon2dKdf,
KdfFactory.argon2idKdf
)
var compressionAlgorithm = CompressionAlgorithm.GZip
private val mFieldReferenceEngine = FieldReferencesEngine(this)
private val mTemplateEngine = TemplateEngineCompatible(this)
var kdbxVersion = UnsignedInt(0)
var name = ""
var nameChanged = DateInstant()
var description = ""
var descriptionChanged = DateInstant()
var defaultUserName = ""
var defaultUserNameChanged = DateInstant()
var settingsChanged = DateInstant()
var keyLastChanged = DateInstant()
var keyChangeRecDays: Long = -1
var keyChangeForceDays: Long = 1
var isKeyChangeForceOnce = false
var maintenanceHistoryDays = UnsignedInt(365)
var color = ""
/**
* Determine if RecycleBin is enable or not
* @return true if RecycleBin enable, false if is not available or not enable
*/
var isRecycleBinEnabled = true
var recycleBinUUID: UUID = UUID_ZERO
var recycleBinChanged = DateInstant()
var entryTemplatesGroup = UUID_ZERO
var entryTemplatesGroupChanged = DateInstant()
var historyMaxItems = DEFAULT_HISTORY_MAX_ITEMS
var historyMaxSize = DEFAULT_HISTORY_MAX_SIZE
var lastSelectedGroupUUID = UUID_ZERO
var lastTopVisibleGroupUUID = UUID_ZERO
var memoryProtection = MemoryProtectionConfig()
val deletedObjects = HashSet<DeletedObject>()
var publicCustomData = VariantDictionary()
val customData = CustomData()
val tagPool = Tags()
var localizedAppName = "KeePassDX"
constructor()
/**
* Create a new database with a root group
*/
constructor(
databaseName: String,
rootName: String,
templatesGroupName: String? = null,
) {
name = databaseName
kdbxVersion = FILE_VERSION_31
val group = createGroup().apply {
title = rootName
icon.standard = getStandardIcon(IconImageStandard.FOLDER_ID)
}
rootGroup = group
if (templatesGroupName != null) {
val templatesGroup = mTemplateEngine.createNewTemplatesGroup(templatesGroupName)
entryTemplatesGroup = templatesGroup.id
entryTemplatesGroupChanged = templatesGroup.lastModificationTime
}
}
override val version: String
get() {
val kdbxStringVersion = when (kdbxVersion) {
FILE_VERSION_31 -> "3.1"
FILE_VERSION_40 -> "4.0"
FILE_VERSION_41 -> "4.1"
else -> "UNKNOWN"
}
return "V2 - KDBX$kdbxStringVersion"
}
override val defaultFileExtension: String
get() = ".kdbx"
private open class NodeOperationHandler<T : NodeKDBXInterface> : NodeHandler<T>() {
var containsCustomData = false
override fun operate(node: T): Boolean {
if (node.customData.isNotEmpty()) {
containsCustomData = true
}
return true
}
}
private inner class EntryOperationHandler : NodeOperationHandler<EntryKDBX>() {
var passwordQualityEstimationDisabled = false
override fun operate(node: EntryKDBX): Boolean {
if (!node.qualityCheck) {
passwordQualityEstimationDisabled = true
}
return super.operate(node)
}
}
private inner class GroupOperationHandler : NodeOperationHandler<GroupKDBX>() {
var containsTags = false
override fun operate(node: GroupKDBX): Boolean {
if (node.tags.isNotEmpty())
containsTags = true
return super.operate(node)
}
}
fun deriveMasterKey(
contentResolver: ContentResolver,
mainCredential: MainCredential,
challengeResponseRetriever: (HardwareKey, ByteArray?) -> ByteArray,
) {
// Retrieve each plain credential
val password = mainCredential.password
val keyFileUri = mainCredential.keyFileUri
val hardwareKey = mainCredential.hardwareKey
val passwordBytes = if (password != null) MainCredential.retrievePasswordKey(
password,
passwordEncoding
) else null
val keyFileBytes = if (keyFileUri != null) MainCredential.retrieveFileKey(
contentResolver,
keyFileUri,
true
) else null
val hardwareKeyBytes = if (hardwareKey != null) MainCredential.retrieveHardwareKey(
challengeResponseRetriever.invoke(hardwareKey, transformSeed)
) else null
// Save to rebuild master password with new seed later
mCompositeKey = CompositeKey(passwordBytes, keyFileBytes, hardwareKey)
// Build the master key
this.masterKey = composedKeyToMasterKey(
passwordBytes,
keyFileBytes,
hardwareKeyBytes
)
}
@Throws(DatabaseOutputException::class)
fun deriveCompositeKey(
challengeResponseRetriever: (HardwareKey, ByteArray?) -> ByteArray,
) {
val passwordBytes = mCompositeKey.passwordData
val keyFileBytes = mCompositeKey.keyFileData
val hardwareKey = mCompositeKey.hardwareKey
if (hardwareKey == null) {
// If no hardware key, simply rebuild from composed keys
this.masterKey = composedKeyToMasterKey(
passwordBytes,
keyFileBytes
)
} else {
val hardwareKeyBytes = MainCredential.retrieveHardwareKey(
challengeResponseRetriever.invoke(hardwareKey, transformSeed)
)
this.masterKey = composedKeyToMasterKey(
passwordBytes,
keyFileBytes,
hardwareKeyBytes
)
}
}
private fun composedKeyToMasterKey(
passwordData: ByteArray?,
keyFileData: ByteArray?,
hardwareKeyData: ByteArray? = null,
): ByteArray {
return HashManager.hashSha256(
passwordData,
keyFileData,
hardwareKeyData
)
}
fun copyMasterKeyFrom(databaseVersioned: DatabaseKDBX) {
super.copyMasterKeyFrom(databaseVersioned)
this.mCompositeKey = databaseVersioned.mCompositeKey
}
fun getMinKdbxVersion(): UnsignedInt {
val entryHandler = EntryOperationHandler()
val groupHandler = GroupOperationHandler()
rootGroup?.doForEachChildAndForIt(entryHandler, groupHandler)
// https://keepass.info/help/kb/kdbx_4.1.html
val containsGroupWithTag = groupHandler.containsTags
val containsEntryWithPasswordQualityEstimationDisabled =
entryHandler.passwordQualityEstimationDisabled
val containsCustomIconWithNameOrLastModificationTime =
iconsManager.containsCustomIconWithNameOrLastModificationTime()
val containsHeaderCustomDataWithLastModificationTime =
customData.containsItemWithLastModificationTime()
// https://keepass.info/help/kb/kdbx_4.html
// If AES is not use, it's at least 4.0
val keyDerivationFunction = kdfEngine
val kdfIsNotAes =
keyDerivationFunction != null && keyDerivationFunction.uuid != AesKdf.CIPHER_UUID
val containsHeaderCustomData = customData.isNotEmpty()
val containsNodeCustomData =
entryHandler.containsCustomData || groupHandler.containsCustomData
// Check each condition to determine version
return if (containsGroupWithTag
|| containsEntryWithPasswordQualityEstimationDisabled
|| containsCustomIconWithNameOrLastModificationTime
|| containsHeaderCustomDataWithLastModificationTime
) {
FILE_VERSION_41
} else if (kdfIsNotAes
|| containsHeaderCustomData
|| containsNodeCustomData
) {
FILE_VERSION_40
} else {
FILE_VERSION_31
}
}
val availableCompressionAlgorithms: List<CompressionAlgorithm> = listOf(
CompressionAlgorithm.None,
CompressionAlgorithm.GZip
)
fun changeBinaryCompression(
oldCompression: CompressionAlgorithm,
newCompression: CompressionAlgorithm,
) {
when (oldCompression) {
CompressionAlgorithm.None -> {
when (newCompression) {
CompressionAlgorithm.None -> {
}
CompressionAlgorithm.GZip -> {
// Only in databaseV3.1, in databaseV4 the header is zipped during the save
if (kdbxVersion.isBefore(FILE_VERSION_40)) {
compressAllBinaries()
}
}
}
}
CompressionAlgorithm.GZip -> {
// In databaseV4 the header is zipped during the save, so not necessary here
if (kdbxVersion.isBefore(FILE_VERSION_40)) {
when (newCompression) {
CompressionAlgorithm.None -> {
decompressAllBinaries()
}
CompressionAlgorithm.GZip -> {
}
}
} else {
decompressAllBinaries()
}
}
}
}
private fun compressAllBinaries() {
attachmentPool.doForEachBinary { _, binary ->
try {
// To compress, create a new binary with file
binary.compress(binaryCache)
} catch (e: Exception) {
Log.e(TAG, "Unable to compress $binary", e)
}
}
}
private fun decompressAllBinaries() {
attachmentPool.doForEachBinary { _, binary ->
try {
binary.decompress(binaryCache)
} catch (e: Exception) {
Log.e(TAG, "Unable to decompress $binary", e)
}
}
}
override var numberKeyEncryptionRounds: Long
get() {
val kdfEngine = kdfEngine
var numKeyEncRounds: Long = 0
if (kdfEngine != null && kdfParameters != null)
numKeyEncRounds = kdfEngine.getKeyRounds(kdfParameters!!)
return numKeyEncRounds
}
set(rounds) {
val kdfEngine = kdfEngine
if (kdfEngine != null && kdfParameters != null)
kdfEngine.setKeyRounds(kdfParameters!!, rounds)
}
var memoryUsage: Long
get() {
val kdfEngine = kdfEngine
return if (kdfEngine != null && kdfParameters != null) {
kdfEngine.getMemoryUsage(kdfParameters!!)
} else KdfEngine.UNKNOWN_VALUE
}
set(memory) {
val kdfEngine = kdfEngine
if (kdfEngine != null && kdfParameters != null)
kdfEngine.setMemoryUsage(kdfParameters!!, memory)
}
var parallelism: Long
get() {
val kdfEngine = kdfEngine
return if (kdfEngine != null && kdfParameters != null) {
kdfEngine.getParallelism(kdfParameters!!)
} else KdfEngine.UNKNOWN_VALUE
}
set(parallelism) {
val kdfEngine = kdfEngine
if (kdfEngine != null && kdfParameters != null)
kdfEngine.setParallelism(kdfParameters!!, parallelism)
}
override val passwordEncoding: Charset
get() = Charsets.UTF_8
private fun getGroupByUUID(groupUUID: UUID): GroupKDBX? {
if (groupUUID == UUID_ZERO)
return null
return getGroupById(NodeIdUUID(groupUUID))
}
// Retrieve recycle bin in index
val recycleBin: GroupKDBX?
get() = getGroupByUUID(recycleBinUUID)
val lastSelectedGroup: GroupKDBX?
get() = getGroupByUUID(lastSelectedGroupUUID)
val lastTopVisibleGroup: GroupKDBX?
get() = getGroupByUUID(lastTopVisibleGroupUUID)
override fun getStandardIcon(iconId: Int): IconImageStandard {
return this.iconsManager.getIcon(iconId)
}
fun buildNewCustomIcon(
customIconId: UUID? = null,
result: (IconImageCustom, BinaryData?) -> Unit,
) {
// Create a binary file for a brand new custom icon
addCustomIcon(customIconId, "", null, false, result)
}
fun addCustomIcon(
customIconId: UUID? = null,
name: String,
lastModificationTime: DateInstant?,
smallSize: Boolean,
result: (IconImageCustom, BinaryData?) -> Unit,
) {
iconsManager.addCustomIcon(customIconId, name, lastModificationTime, { uniqueBinaryId ->
// Create a byte array for better performance with small data
binaryCache.getBinaryData(uniqueBinaryId, smallSize)
}, result)
}
fun removeCustomIcon(iconUuid: UUID) {
iconsManager.removeCustomIcon(iconUuid, binaryCache)
}
fun isCustomIconBinaryDuplicate(binary: BinaryData): Boolean {
return iconsManager.isCustomIconBinaryDuplicate(binary)
}
fun getCustomIcon(iconUuid: UUID): IconImageCustom? {
return this.iconsManager.getIcon(iconUuid)
}
fun isTemplatesGroupEnabled(): Boolean {
return entryTemplatesGroup != UUID_ZERO
}
fun enableTemplatesGroup(enable: Boolean, templatesGroupName: String) {
// Create templates group only if a group with a valid name don't already exists
val firstGroupWithValidName = getGroupIndexes().firstOrNull {
it.title == templatesGroupName
}
if (enable) {
val templatesGroup = firstGroupWithValidName
?: mTemplateEngine.createNewTemplatesGroup(templatesGroupName)
entryTemplatesGroup = templatesGroup.id
} else {
removeTemplatesGroup()
}
}
fun removeTemplatesGroup() {
entryTemplatesGroup = UUID_ZERO
mTemplateEngine.clearCache()
}
fun getTemplatesGroup(): GroupKDBX? {
if (isTemplatesGroupEnabled()) {
return getGroupById(entryTemplatesGroup)
}
return null
}
fun getTemplates(templateCreation: Boolean): List<Template> {
return if (templateCreation)
listOf(mTemplateEngine.getTemplateCreation())
else
mTemplateEngine.getTemplates()
}
fun getTemplate(entry: EntryKDBX): Template? {
return mTemplateEngine.getTemplate(entry)
}
fun decodeEntryWithTemplateConfiguration(
entryKDBX: EntryKDBX,
entryIsTemplate: Boolean,
): EntryKDBX {
return if (entryIsTemplate) {
mTemplateEngine.decodeTemplateEntry(entryKDBX)
} else {
mTemplateEngine.removeMetaTemplateRecognitionFromEntry(entryKDBX)
}
}
fun encodeEntryWithTemplateConfiguration(
entryKDBX: EntryKDBX,
entryIsTemplate: Boolean,
template: Template,
): EntryKDBX {
return if (entryIsTemplate) {
mTemplateEngine.encodeTemplateEntry(entryKDBX)
} else {
mTemplateEngine.addMetaTemplateRecognitionToEntry(template, entryKDBX)
}
}
/*
* Search methods
*/
fun getGroupById(id: UUID): GroupKDBX? {
return this.getGroupById(NodeIdUUID(id))
}
fun getEntryById(id: UUID): EntryKDBX? {
return this.getEntryById(NodeIdUUID(id))
}
fun getEntryByTitle(title: String, recursionLevel: Int): EntryKDBX? {
return findEntry { entry ->
entry.decodeTitleKey(recursionLevel).equals(title, true)
}
}
fun getEntryByUsername(username: String, recursionLevel: Int): EntryKDBX? {
return findEntry { entry ->
entry.decodeUsernameKey(recursionLevel).equals(username, true)
}
}
fun getEntryByURL(url: String, recursionLevel: Int): EntryKDBX? {
return findEntry { entry ->
entry.decodeUrlKey(recursionLevel).equals(url, true)
}
}
fun getEntryByPassword(password: String, recursionLevel: Int): EntryKDBX? {
return findEntry { entry ->
entry.decodePasswordKey(recursionLevel).equals(password, true)
}
}
fun getEntryByNotes(notes: String, recursionLevel: Int): EntryKDBX? {
return findEntry { entry ->
entry.decodeNotesKey(recursionLevel).equals(notes, true)
}
}
fun getEntryByCustomData(customDataValue: String): EntryKDBX? {
return findEntry { entry ->
entry.customData.containsItemWithValue(customDataValue)
}
}
/**
* Retrieve the value of a field reference
*/
fun getFieldReferenceValue(textReference: String, recursionLevel: Int): String {
return mFieldReferenceEngine.compile(textReference, recursionLevel)
}
@Throws(IOException::class)
fun makeFinalKey(masterSeed: ByteArray) {
kdfParameters?.let { keyDerivationFunctionParameters ->
val kdfEngine = getKdfEngineFromParameters(keyDerivationFunctionParameters)
?: throw IOException("Unknown key derivation function")
var transformedMasterKey =
kdfEngine.transform(masterKey, keyDerivationFunctionParameters)
if (transformedMasterKey.size != 32) {
transformedMasterKey = HashManager.hashSha256(transformedMasterKey)
}
val cmpKey = ByteArray(65)
System.arraycopy(masterSeed, 0, cmpKey, 0, 32)
System.arraycopy(transformedMasterKey, 0, cmpKey, 32, 32)
finalKey = resizeKey(cmpKey, encryptionAlgorithm.cipherEngine.keyLength())
val messageDigest: MessageDigest
try {
messageDigest = MessageDigest.getInstance("SHA-512")
cmpKey[64] = 1
hmacKey = messageDigest.digest(cmpKey)
} catch (e: NoSuchAlgorithmException) {
throw IOException("No SHA-512 implementation")
} finally {
Arrays.fill(cmpKey, 0.toByte())
}
}
}
private fun resizeKey(inBytes: ByteArray, cbOut: Int): ByteArray {
if (cbOut == 0) return ByteArray(0)
val messageDigest = if (cbOut <= 32) HashManager.getHash256() else HashManager.getHash512()
messageDigest.update(inBytes, 0, 64)
val hash: ByteArray = messageDigest.digest()
if (cbOut == hash.size) {
return hash
}
val ret = ByteArray(cbOut)
if (cbOut < hash.size) {
System.arraycopy(hash, 0, ret, 0, cbOut)
} else {
var pos = 0
var r: Long = 0
while (pos < cbOut) {
val hmac: Mac
try {
hmac = Mac.getInstance("HmacSHA256")
} catch (e: NoSuchAlgorithmException) {
throw RuntimeException(e)
}
val pbR = longTo8Bytes(r)
val part = hmac.doFinal(pbR)
val copy = min(cbOut - pos, part.size)
System.arraycopy(part, 0, ret, pos, copy)
pos += copy
r++
Arrays.fill(part, 0.toByte())
}
}
Arrays.fill(hash, 0.toByte())
return ret
}
override fun newGroupId(): NodeIdUUID {
var newId: NodeIdUUID
do {
newId = NodeIdUUID()
} while (isGroupIdUsed(newId))
return newId
}
override fun newEntryId(): NodeIdUUID {
var newId: NodeIdUUID
do {
newId = NodeIdUUID()
} while (isEntryIdUsed(newId))
return newId
}
override fun createGroup(): GroupKDBX {
return GroupKDBX()
}
override fun createEntry(): EntryKDBX {
return EntryKDBX()
}
override fun rootCanContainsEntry(): Boolean {
return true
}
override fun isInRecycleBin(group: GroupKDBX): Boolean {
// To keep compatibility with old V1 databases
var currentGroup: GroupKDBX? = group
while (currentGroup != null) {
if (currentGroup.parent == rootGroup
&& currentGroup.title.equals(BACKUP_FOLDER_TITLE, ignoreCase = true)
) {
return true
}
currentGroup = currentGroup.parent
}
return if (recycleBin == null)
false
else if (!isRecycleBinEnabled)
false
else
group.isContainedIn(recycleBin!!)
}
/**
* Ensure that the recycle bin tree exists, if enabled and create it
* if it doesn't exist
*/
fun ensureRecycleBinExists(recyclerBinTitle: String) {
if (recycleBin == null) {
// Create recycle bin only if a group with a valid name don't already exists
val firstGroupWithValidName = getGroupIndexes().firstOrNull {
it.title == recyclerBinTitle
}
val recycleBinGroup = if (firstGroupWithValidName == null) {
val newRecycleBinGroup = createGroup().apply {
title = recyclerBinTitle
icon.standard = getStandardIcon(IconImageStandard.TRASH_ID)
enableAutoType = false
enableSearching = false
isExpanded = false
}
addGroupTo(newRecycleBinGroup, rootGroup)
newRecycleBinGroup
} else {
firstGroupWithValidName
}
recycleBinUUID = recycleBinGroup.id
recycleBinChanged = DateInstant()
}
}
fun removeRecycleBin() {
if (recycleBin != null) {
recycleBinUUID = UUID_ZERO
}
}
/**
* Define if a Node must be delete or recycle when remove action is called
* @param node Node to remove
* @return true if node can be recycle, false elsewhere
*/
fun canRecycle(node: NodeVersioned<*, GroupKDBX, EntryKDBX>): Boolean {
if (!isRecycleBinEnabled)
return false
if (recycleBin == null)
return false
if (node is GroupKDBX
&& recycleBin!!.isContainedIn(node)
)
return false
if (!node.isContainedIn(recycleBin!!))
return true
return false
}
fun getDeletedObject(nodeId: NodeId<UUID>): DeletedObject? {
return deletedObjects.find { it.uuid == nodeId.id }
}
fun addDeletedObject(deletedObject: DeletedObject) {
this.deletedObjects.add(deletedObject)
}
fun addDeletedObject(objectId: UUID) {
addDeletedObject(DeletedObject(objectId))
}
override fun addEntryTo(newEntry: EntryKDBX, parent: GroupKDBX?) {
super.addEntryTo(newEntry, parent)
tagPool.put(newEntry.tags)
mFieldReferenceEngine.clear()
}
override fun updateEntry(entry: EntryKDBX) {
super.updateEntry(entry)
tagPool.put(entry.tags)
mFieldReferenceEngine.clear()
}
override fun removeEntryFrom(entryToRemove: EntryKDBX, parent: GroupKDBX?) {
super.removeEntryFrom(entryToRemove, parent)
// Do not remove tags from pool, it's only in temp memory
mFieldReferenceEngine.clear()
}
fun buildNewBinaryAttachment(
smallSize: Boolean,
compression: Boolean,
protection: Boolean,
binaryPoolId: Int? = null,
): BinaryData {
return attachmentPool.put(binaryPoolId) { uniqueBinaryId ->
binaryCache.getBinaryData(uniqueBinaryId, smallSize, compression, protection)
}.binary
}
fun removeUnlinkedAttachment(binary: BinaryData, clear: Boolean) {
val listBinaries = ArrayList<BinaryData>()
listBinaries.add(binary)
removeUnlinkedAttachments(listBinaries, clear)
}
fun removeUnlinkedAttachments(clear: Boolean) {
removeUnlinkedAttachments(emptyList(), clear)
}
private fun removeUnlinkedAttachments(binaries: List<BinaryData>, clear: Boolean) {
// TODO check in icon pool
// Build binaries to remove with all binaries known
val binariesToRemove = ArrayList<BinaryData>()
if (binaries.isEmpty()) {
attachmentPool.doForEachBinary { _, binary ->
binariesToRemove.add(binary)
}
} else {
binariesToRemove.addAll(binaries)
}
// Remove binaries from the list
rootGroup?.doForEachChild(object : NodeHandler<EntryKDBX>() {
override fun operate(node: EntryKDBX): Boolean {
node.getAttachments(attachmentPool, true).forEach {
binariesToRemove.remove(it.binaryData)
}
return binariesToRemove.isNotEmpty()
}
}, null)
// Effective removing
binariesToRemove.forEach {
try {
attachmentPool.remove(it)
if (clear)
it.clear(binaryCache)
} catch (e: Exception) {
Log.w(TAG, "Unable to clean binaries", e)
}
}
}
override fun validatePasswordEncoding(password: String?, containsKeyFile: Boolean): Boolean {
if (password == null)
return true
return super.validatePasswordEncoding(password, containsKeyFile)
}
override fun clearIndexes() {
try {
super.clearIndexes()
mFieldReferenceEngine.clear()
} catch (e: Exception) {
Log.e(TAG, "Unable to clear cache", e)
}
}
companion object {
val TYPE = DatabaseKDBX::class.java
private val TAG = DatabaseKDBX::class.java.name
private const val DEFAULT_HISTORY_MAX_ITEMS = 10 // -1 unlimited
private const val DEFAULT_HISTORY_MAX_SIZE = (6 * 1024 * 1024).toLong() // -1 unlimited
const val BASE_64_FLAG = Base64.NO_WRAP
}
}

View file

@ -0,0 +1,318 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.database
import android.util.Log
import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm
import com.kunzisoft.keepass.database.crypto.kdf.KdfEngine
import com.kunzisoft.keepass.database.element.binary.AttachmentPool
import com.kunzisoft.keepass.database.element.binary.BinaryCache
import com.kunzisoft.keepass.database.element.entry.EntryVersioned
import com.kunzisoft.keepass.database.element.group.GroupVersioned
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
import com.kunzisoft.keepass.database.element.icon.IconsManager
import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.database.exception.DuplicateUuidDatabaseException
import java.io.InputStream
import java.io.UnsupportedEncodingException
import java.nio.charset.Charset
import java.util.*
abstract class DatabaseVersioned<
GroupId,
EntryId,
Group : GroupVersioned<GroupId, EntryId, Group, Entry>,
Entry : EntryVersioned<GroupId, EntryId, Group, Entry>
> {
// Algorithm used to encrypt the database
abstract var encryptionAlgorithm: EncryptionAlgorithm
abstract val availableEncryptionAlgorithms: List<EncryptionAlgorithm>
abstract var kdfEngine: KdfEngine?
abstract val kdfAvailableList: List<KdfEngine>
abstract var numberKeyEncryptionRounds: Long
abstract val passwordEncoding: Charset
var masterKey = ByteArray(32)
var finalKey: ByteArray? = null
protected set
var transformSeed: ByteArray? = null
abstract val version: String
abstract val defaultFileExtension: String
/**
* To manage binaries in faster way
* Cipher key generated when the database is loaded, and destroyed when the database is closed
* Can be used to temporarily store database elements
*/
var binaryCache = BinaryCache()
var iconsManager = IconsManager()
var attachmentPool = AttachmentPool()
var changeDuplicateId = false
private var groupIndexes = LinkedHashMap<NodeId<GroupId>, Group>()
private var entryIndexes = LinkedHashMap<NodeId<EntryId>, Entry>()
var rootGroup: Group? = null
set(value) {
field = value
value?.let {
removeGroupIndex(it)
addGroupIndex(it)
}
}
fun getAllGroupsWithoutRoot(): List<Group> {
return getGroupIndexes().filter { it != rootGroup }
}
protected open fun loadXmlKeyFile(keyInputStream: InputStream): ByteArray? {
return null
}
open fun validatePasswordEncoding(password: String?, containsKeyFile: Boolean): Boolean {
if (password == null && !containsKeyFile)
return false
if (password == null)
return true
val encoding = passwordEncoding
val bKey: ByteArray
try {
bKey = password.toByteArray(encoding)
} catch (e: UnsupportedEncodingException) {
return false
}
val reEncoded: String
try {
reEncoded = String(bKey, encoding)
} catch (e: UnsupportedEncodingException) {
return false
}
return password == reEncoded
}
fun copyMasterKeyFrom(databaseVersioned: DatabaseVersioned<GroupId, EntryId, Group, Entry>) {
this.masterKey = databaseVersioned.masterKey
this.transformSeed = databaseVersioned.transformSeed
}
/*
* -------------------------------------
* Node Creation
* -------------------------------------
*/
abstract fun newGroupId(): NodeId<GroupId>
abstract fun newEntryId(): NodeId<EntryId>
abstract fun createGroup(): Group
abstract fun createEntry(): Entry
/*
* -------------------------------------
* Index Manipulation
* -------------------------------------
*/
/**
* Determine if an id number is already in use
*
* @param id
* ID number to check for
* @return True if the ID is used, false otherwise
*/
fun isGroupIdUsed(id: NodeId<GroupId>): Boolean {
return groupIndexes.containsKey(id)
}
fun getGroupIndexes(): Collection<Group> {
return groupIndexes.values
}
open fun getGroupById(id: NodeId<GroupId>): Group? {
return this.groupIndexes[id]
}
fun addGroupIndex(group: Group) {
val groupId = group.nodeId
if (groupIndexes.containsKey(groupId)) {
if (changeDuplicateId) {
val newGroupId = newGroupId()
group.nodeId = newGroupId
group.parent?.addChildGroup(group)
this.groupIndexes[newGroupId] = group
} else {
throw DuplicateUuidDatabaseException(Type.GROUP, groupId)
}
} else {
this.groupIndexes[groupId] = group
}
}
fun removeGroupIndex(group: Group) {
this.groupIndexes.remove(group.nodeId)
}
fun isEntryIdUsed(id: NodeId<EntryId>): Boolean {
return entryIndexes.containsKey(id)
}
fun getEntryIndexes(): Collection<Entry> {
return entryIndexes.values
}
fun getEntryById(id: NodeId<EntryId>): Entry? {
return this.entryIndexes[id]
}
fun findEntry(predicate: (Entry) -> Boolean): Entry? {
return this.entryIndexes.values.find(predicate)
}
fun addEntryIndex(entry: Entry) {
val entryId = entry.nodeId
if (entryIndexes.containsKey(entryId)) {
if (changeDuplicateId) {
val newEntryId = newEntryId()
entry.nodeId = newEntryId
entry.parent?.addChildEntry(entry)
this.entryIndexes[newEntryId] = entry
} else {
throw DuplicateUuidDatabaseException(Type.ENTRY, entryId)
}
} else {
this.entryIndexes[entryId] = entry
}
}
fun removeEntryIndex(entry: Entry) {
this.entryIndexes.remove(entry.nodeId)
}
open fun clearIndexes() {
this.groupIndexes.clear()
this.entryIndexes.clear()
}
/*
* -------------------------------------
* Node Manipulation
* -------------------------------------
*/
abstract fun rootCanContainsEntry(): Boolean
abstract fun getStandardIcon(iconId: Int): IconImageStandard
fun addGroupTo(newGroup: Group, parent: Group?) {
// Add tree to parent tree
parent?.addChildGroup(newGroup)
newGroup.parent = parent
addGroupIndex(newGroup)
}
fun updateGroup(group: Group) {
group.parent?.updateChildGroup(group)
val groupId = group.nodeId
if (groupIndexes.containsKey(groupId)) {
groupIndexes[groupId] = group
}
}
open fun removeGroupFrom(groupToRemove: Group, parent: Group?) {
// Remove tree from parent tree
parent?.removeChildGroup(groupToRemove)
removeGroupIndex(groupToRemove)
}
open fun addEntryTo(newEntry: Entry, parent: Group?) {
// Add entry to parent
parent?.addChildEntry(newEntry)
newEntry.parent = parent
addEntryIndex(newEntry)
}
open fun updateEntry(entry: Entry) {
entry.parent?.updateChildEntry(entry)
val entryId = entry.nodeId
if (entryIndexes.containsKey(entryId)) {
entryIndexes[entryId] = entry
}
}
open fun removeEntryFrom(entryToRemove: Entry, parent: Group?) {
// Remove entry from parent
parent?.removeChildEntry(entryToRemove)
removeEntryIndex(entryToRemove)
}
abstract fun isInRecycleBin(group: Group): Boolean
fun clearIconsCache() {
iconsManager.doForEachCustomIcon { _, binary ->
try {
binary.clear(binaryCache)
} catch (e: Exception) {
Log.e(TAG, "Unable to clear icon binary cache", e)
}
}
iconsManager.clear()
}
fun clearAttachmentsCache() {
attachmentPool.doForEachBinary { _, binary ->
try {
binary.clear(binaryCache)
} catch (e: Exception) {
Log.e(TAG, "Unable to clear attachment binary cache", e)
}
}
attachmentPool.clear()
}
fun clearBinaries() {
binaryCache.clear()
}
fun clearAll() {
clearIndexes()
clearIconsCache()
clearAttachmentsCache()
clearBinaries()
}
companion object {
private const val TAG = "DatabaseVersioned"
val UUID_ZERO = UUID(0, 0)
}
}

View file

@ -0,0 +1,111 @@
/*
* Copyright 2018 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.entry
import android.os.Parcel
import android.os.Parcelable
import com.kunzisoft.keepass.utils.UnsignedInt
class AutoType : Parcelable {
var enabled = true
var obfuscationOptions = OBF_OPT_NONE
var defaultSequence = ""
private var windowSeqPairs = ArrayList<AutoTypeItem>()
constructor()
constructor(autoType: AutoType) {
this.enabled = autoType.enabled
this.obfuscationOptions = autoType.obfuscationOptions
this.defaultSequence = autoType.defaultSequence
this.windowSeqPairs.clear()
this.windowSeqPairs.addAll(autoType.windowSeqPairs)
}
constructor(parcel: Parcel) {
this.enabled = parcel.readByte().toInt() != 0
this.obfuscationOptions = UnsignedInt(parcel.readInt())
this.defaultSequence = parcel.readString() ?: defaultSequence
parcel.readTypedList(this.windowSeqPairs, AutoTypeItem.CREATOR)
}
override fun describeContents(): Int {
return 0
}
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeByte((if (enabled) 1 else 0).toByte())
dest.writeInt(obfuscationOptions.toKotlinInt())
dest.writeString(defaultSequence)
dest.writeTypedList(windowSeqPairs)
}
fun add(key: String, value: String) {
windowSeqPairs.add(AutoTypeItem(key, value))
}
fun doForEachAutoTypeItem(action: (key: String, value: String) -> Unit) {
windowSeqPairs.forEach {
action.invoke(it.key, it.value)
}
}
private data class AutoTypeItem(var key: String, var value: String): Parcelable {
constructor(parcel: Parcel) : this(
parcel.readString() ?: "",
parcel.readString() ?: "") {
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(key)
parcel.writeString(value)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<AutoTypeItem> {
override fun createFromParcel(parcel: Parcel): AutoTypeItem {
return AutoTypeItem(parcel)
}
override fun newArray(size: Int): Array<AutoTypeItem?> {
return arrayOfNulls(size)
}
}
}
companion object {
private val OBF_OPT_NONE = UnsignedInt(0)
@JvmField
val CREATOR: Parcelable.Creator<AutoType> = object : Parcelable.Creator<AutoType> {
override fun createFromParcel(parcel: Parcel): AutoType {
return AutoType(parcel)
}
override fun newArray(size: Int): Array<AutoType?> {
return arrayOfNulls(size)
}
}
}
}

View file

@ -0,0 +1,232 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*/
package com.kunzisoft.keepass.database.element.entry
import android.os.Parcel
import android.os.Parcelable
import com.kunzisoft.keepass.database.element.Attachment
import com.kunzisoft.keepass.database.element.binary.AttachmentPool
import com.kunzisoft.keepass.database.element.binary.BinaryData
import com.kunzisoft.keepass.database.element.group.GroupKDB
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
import com.kunzisoft.keepass.database.element.icon.IconImageStandard.Companion.KEY_ID
import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.database.element.node.NodeKDBInterface
import com.kunzisoft.keepass.database.element.node.Type
import java.util.*
/**
* Structure containing information about one entry.
*
* <PRE>
* One entry: [FIELDTYPE(FT)][FIELDSIZE(FS)][FIELDDATA(FD)]
* [FT+FS+(FD)][FT+FS+(FD)][FT+FS+(FD)][FT+FS+(FD)][FT+FS+(FD)]...
*
* [ 2 bytes] FIELDTYPE
* [ 4 bytes] FIELDSIZE, size of FIELDDATA in bytes
* [ n bytes] FIELDDATA, n = FIELDSIZE
*
* Notes:
* - Strings are stored in UTF-8 encoded form and are null-terminated.
* - FIELDTYPE can be one of the FT_ constants.
</PRE> *
*
* @author Naomaru Itoi <nao></nao>@phoneid.org>
* @author Bill Zwicky <wrzwicky></wrzwicky>@pobox.com>
* @author Dominik Reichl <dominik.reichl></dominik.reichl>@t-online.de>
* @author Jeremy Jamet <jeremy.jamet></jeremy.jamet>@kunzisoft.com>
*/
class EntryKDB : EntryVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface {
/** A string describing what is in binaryData */
var binaryDescription = ""
private var binaryDataId: Int? = null
// Determine if this is a MetaStream entry
fun isMetaStream(): Boolean {
if (notes.isEmpty()) return false
if (binaryDescription != PMS_ID_BINDESC) return false
if (title.isEmpty()) return false
if (title != PMS_ID_TITLE) return false
if (username.isEmpty()) return false
if (username != PMS_ID_USER) return false
if (url.isEmpty()) return false
if (url != PMS_ID_URL) return false
return icon.standard.id == KEY_ID
}
fun isMetaStreamDefaultUsername(): Boolean {
return isMetaStream() && notes == PMS_STREAM_DEFAULTUSER
}
private fun setMetaStream() {
binaryDescription = PMS_ID_BINDESC
title = PMS_ID_TITLE
username = PMS_ID_USER
url = PMS_ID_URL
icon.standard = IconImageStandard(KEY_ID)
}
fun setMetaStreamDefaultUsername() {
notes = PMS_STREAM_DEFAULTUSER
setMetaStream()
}
fun isMetaStreamDatabaseColor(): Boolean {
return isMetaStream() && notes == PMS_STREAM_DBCOLOR
}
fun setMetaStreamDatabaseColor() {
notes = PMS_STREAM_DBCOLOR
setMetaStream()
}
override fun initNodeId(): NodeId<UUID> {
return NodeIdUUID()
}
override fun copyNodeId(nodeId: NodeId<UUID>): NodeId<UUID> {
return NodeIdUUID(nodeId.id)
}
constructor() : super()
constructor(parcel: Parcel) : super(parcel) {
title = parcel.readString() ?: title
username = parcel.readString() ?: username
password = parcel.readString() ?: password
url = parcel.readString() ?: url
notes = parcel.readString() ?: notes
binaryDescription = parcel.readString() ?: binaryDescription
val rawBinaryDataId = parcel.readInt()
binaryDataId = if (rawBinaryDataId == -1) null else rawBinaryDataId
}
override fun readParentParcelable(parcel: Parcel): GroupKDB? {
return parcel.readParcelable(GroupKDB::class.java.classLoader)
}
override fun writeParentParcelable(parent: GroupKDB?, parcel: Parcel, flags: Int) {
parcel.writeParcelable(parent, flags)
}
override fun writeToParcel(dest: Parcel, flags: Int) {
super.writeToParcel(dest, flags)
dest.writeString(title)
dest.writeString(username)
dest.writeString(password)
dest.writeString(url)
dest.writeString(notes)
dest.writeString(binaryDescription)
dest.writeInt(binaryDataId ?: -1)
}
fun updateWith(source: EntryKDB,
updateParents: Boolean = true) {
super.updateWith(source, updateParents)
title = source.title
username = source.username
password = source.password
url = source.url
notes = source.notes
binaryDescription = source.binaryDescription
binaryDataId = source.binaryDataId
}
override var username = ""
/**
* @return the actual password byte array.
*/
override var password = ""
override var url = ""
override var notes = ""
override var title = ""
override val type: Type
get() = Type.ENTRY
fun getAttachment(attachmentPool: AttachmentPool): Attachment? {
binaryDataId?.let { poolId ->
attachmentPool[poolId]?.let { binary ->
return Attachment(binaryDescription, binary)
}
}
return null
}
fun containsAttachment(): Boolean {
return binaryDataId != null
}
fun getBinary(attachmentPool: AttachmentPool): BinaryData? {
this.binaryDataId?.let {
return attachmentPool[it]
}
return null
}
fun putBinary(binaryData: BinaryData, attachmentPool: AttachmentPool) {
this.binaryDataId = attachmentPool.put(binaryData)
}
fun putAttachment(attachment: Attachment, attachmentPool: AttachmentPool) {
this.binaryDescription = attachment.name
this.binaryDataId = attachmentPool.put(attachment.binaryData)
}
fun removeAttachment(attachment: Attachment? = null) {
if (attachment == null || this.binaryDescription == attachment.name) {
this.binaryDescription = ""
this.binaryDataId = null
}
}
companion object {
/** Size of byte buffer needed to hold this struct. */
private const val PMS_ID_BINDESC = "bin-stream"
private const val PMS_ID_TITLE = "Meta-Info"
private const val PMS_ID_USER = "SYSTEM"
private const val PMS_ID_URL = "$"
const val PMS_STREAM_SIMPLESTATE = "Simple UI State"
const val PMS_STREAM_DEFAULTUSER = "Default User Name"
const val PMS_STREAM_SEARCHHISTORYITEM = "Search History Item"
const val PMS_STREAM_CUSTOMKVP = "Custom KVP"
const val PMS_STREAM_DBCOLOR = "Database Color"
const val PMS_STREAM_KPXICON2 = "KPX_CUSTOM_ICONS_2"
@JvmField
val CREATOR: Parcelable.Creator<EntryKDB> = object : Parcelable.Creator<EntryKDB> {
override fun createFromParcel(parcel: Parcel): EntryKDB {
return EntryKDB(parcel)
}
override fun newArray(size: Int): Array<EntryKDB?> {
return arrayOfNulls(size)
}
}
}
}

View file

@ -0,0 +1,410 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.entry
import android.os.Parcel
import android.os.ParcelUuid
import android.os.Parcelable
import com.kunzisoft.keepass.database.element.*
import com.kunzisoft.keepass.database.element.binary.AttachmentPool
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
import com.kunzisoft.keepass.database.element.database.DatabaseVersioned
import com.kunzisoft.keepass.database.element.group.GroupKDBX
import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface
import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.database.element.security.ProtectedString
import com.kunzisoft.keepass.utils.ParcelableUtil
import com.kunzisoft.keepass.utils.UnsignedLong
import java.util.*
import kotlin.collections.ArrayList
import kotlin.collections.LinkedHashMap
class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInterface {
// To decode each field not parcelable
@Transient
private var mDatabase: DatabaseKDBX? = null
@Transient
private var mDecodeRef = false
override var usageCount = UnsignedLong(0)
override var locationChanged = DateInstant()
override var customData = CustomData()
private var fields = LinkedHashMap<String, ProtectedString>()
var binaries = LinkedHashMap<String, Int>() // Map<Label, PoolId>
var foregroundColor = ""
var backgroundColor = ""
var overrideURL = ""
override var tags = Tags()
override var previousParentGroup: UUID = DatabaseVersioned.UUID_ZERO
var qualityCheck = true
var autoType = AutoType()
var history = ArrayList<EntryKDBX>()
var additional = ""
override var expires: Boolean = false
constructor() : super()
constructor(parcel: Parcel) : super(parcel) {
usageCount = UnsignedLong(parcel.readLong())
locationChanged = parcel.readParcelable(DateInstant::class.java.classLoader) ?: locationChanged
customData = parcel.readParcelable(CustomData::class.java.classLoader) ?: CustomData()
fields = ParcelableUtil.readStringParcelableMap(parcel, ProtectedString::class.java)
binaries = ParcelableUtil.readStringIntMap(parcel)
foregroundColor = parcel.readString() ?: foregroundColor
backgroundColor = parcel.readString() ?: backgroundColor
overrideURL = parcel.readString() ?: overrideURL
tags = parcel.readParcelable(Tags::class.java.classLoader) ?: tags
previousParentGroup = parcel.readParcelable<ParcelUuid>(ParcelUuid::class.java.classLoader)?.uuid ?: DatabaseVersioned.UUID_ZERO
autoType = parcel.readParcelable(AutoType::class.java.classLoader) ?: autoType
parcel.readTypedList(history, CREATOR)
additional = parcel.readString() ?: additional
}
override fun readParentParcelable(parcel: Parcel): GroupKDBX? {
return parcel.readParcelable(GroupKDBX::class.java.classLoader)
}
override fun writeParentParcelable(parent: GroupKDBX?, parcel: Parcel, flags: Int) {
parcel.writeParcelable(parent, flags)
}
override fun writeToParcel(dest: Parcel, flags: Int) {
super.writeToParcel(dest, flags)
dest.writeLong(usageCount.toKotlinLong())
dest.writeParcelable(locationChanged, flags)
dest.writeParcelable(customData, flags)
ParcelableUtil.writeStringParcelableMap(dest, flags, fields)
ParcelableUtil.writeStringIntMap(dest, binaries)
dest.writeString(foregroundColor)
dest.writeString(backgroundColor)
dest.writeString(overrideURL)
dest.writeParcelable(tags, flags)
dest.writeParcelable(ParcelUuid(previousParentGroup), flags)
dest.writeParcelable(autoType, flags)
dest.writeTypedList(history)
dest.writeString(additional)
}
/**
* Update with deep copy of each entry element
* @param source
*/
fun updateWith(source: EntryKDBX,
copyHistory: Boolean = true,
updateParents: Boolean = true) {
super.updateWith(source, updateParents)
usageCount = source.usageCount
locationChanged = DateInstant(source.locationChanged)
customData = CustomData(source.customData)
fields.clear()
fields.putAll(source.fields)
binaries.clear()
binaries.putAll(source.binaries)
foregroundColor = source.foregroundColor
backgroundColor = source.backgroundColor
overrideURL = source.overrideURL
tags = source.tags
previousParentGroup = source.previousParentGroup
autoType = AutoType(source.autoType)
history.clear()
if (copyHistory)
history.addAll(source.history)
additional = source.additional
}
fun startToManageFieldReferences(database: DatabaseKDBX) {
this.mDatabase = database
this.mDecodeRef = true
}
fun stopToManageFieldReferences() {
this.mDatabase = null
this.mDecodeRef = false
}
override fun initNodeId(): NodeId<UUID> {
return NodeIdUUID()
}
override fun copyNodeId(nodeId: NodeId<UUID>): NodeId<UUID> {
return NodeIdUUID(nodeId.id)
}
override val type: Type
get() = Type.ENTRY
/**
* Decode a reference key with the FieldReferencesEngine
* @param decodeRef
* @param key
* @return
*/
private fun decodeRefKey(decodeRef: Boolean, key: String, recursionLevel: Int): String {
return fields[key]?.toString()?.let { text ->
return if (decodeRef) {
mDatabase?.getFieldReferenceValue(text, recursionLevel) ?: text
} else text
} ?: ""
}
fun decodeTitleKey(recursionLevel: Int): String {
return decodeRefKey(mDecodeRef, STR_TITLE, recursionLevel)
}
override var title: String
get() = decodeTitleKey(0)
set(value) {
val protect = mDatabase != null && mDatabase!!.memoryProtection.protectTitle
fields[STR_TITLE] = ProtectedString(protect, value)
}
fun decodeUsernameKey(recursionLevel: Int): String {
return decodeRefKey(mDecodeRef, STR_USERNAME, recursionLevel)
}
override var username: String
get() = decodeUsernameKey(0)
set(value) {
val protect = mDatabase != null && mDatabase!!.memoryProtection.protectUserName
fields[STR_USERNAME] = ProtectedString(protect, value)
}
fun decodePasswordKey(recursionLevel: Int): String {
return decodeRefKey(mDecodeRef, STR_PASSWORD, recursionLevel)
}
override var password: String
get() = decodePasswordKey(0)
set(value) {
val protect = mDatabase != null && mDatabase!!.memoryProtection.protectPassword
fields[STR_PASSWORD] = ProtectedString(protect, value)
}
fun decodeUrlKey(recursionLevel: Int): String {
return decodeRefKey(mDecodeRef, STR_URL, recursionLevel)
}
override var url
get() = decodeUrlKey(0)
set(value) {
val protect = mDatabase != null && mDatabase!!.memoryProtection.protectUrl
fields[STR_URL] = ProtectedString(protect, value)
}
fun decodeNotesKey(recursionLevel: Int): String {
return decodeRefKey(mDecodeRef, STR_NOTES, recursionLevel)
}
override var notes: String
get() = decodeNotesKey(0)
set(value) {
val protect = mDatabase != null && mDatabase!!.memoryProtection.protectNotes
fields[STR_NOTES] = ProtectedString(protect, value)
}
fun getCustomFieldValue(label: String): String {
return decodeRefKey(mDecodeRef, label, 0)
}
fun getSize(attachmentPool: AttachmentPool): Long {
var size = FIXED_LENGTH_SIZE
for (entry in fields.entries) {
size += entry.key.length.toLong()
size += entry.value.length().toLong()
}
size += getAttachmentsSize(attachmentPool)
size += autoType.defaultSequence.length.toLong()
autoType.doForEachAutoTypeItem { key, value ->
size += key.length.toLong()
size += value.length.toLong()
}
for (entry in history) {
size += entry.getSize(attachmentPool)
}
size += overrideURL.length.toLong()
size += tags.toString().length
return size
}
fun afterChangeParent() {
locationChanged = DateInstant()
}
private fun isStandardField(key: String): Boolean {
return (key == STR_TITLE
|| key == STR_USERNAME
|| key == STR_PASSWORD
|| key == STR_URL
|| key == STR_NOTES)
}
fun doForEachDecodedCustomField(action: (field: Field) -> Unit) {
val iterator = fields.entries.iterator()
while (iterator.hasNext()) {
val mapEntry = iterator.next()
if (!isStandardField(mapEntry.key)) {
action.invoke(Field(mapEntry.key,
ProtectedString(mapEntry.value.isProtected,
decodeRefKey(mDecodeRef, mapEntry.key, 0)
)
)
)
}
}
}
fun getFieldValue(label: String): ProtectedString? {
return fields[label]
}
fun getFields(): List<Field> {
return fields.map { Field(it.key, it.value) }
}
fun putField(field: Field) {
putField(field.name, field.protectedValue)
}
fun putField(label: String, value: ProtectedString) {
fields[label] = value
}
fun removeField(name: String) {
fields.remove(name)
}
fun removeAllFields() {
fields.clear()
}
/**
* It's a list because history labels can be defined multiple times
*/
fun getAttachments(attachmentPool: AttachmentPool, inHistory: Boolean = false): List<Attachment> {
val entryAttachmentList = ArrayList<Attachment>()
for ((label, poolId) in binaries) {
attachmentPool[poolId]?.let { binary ->
entryAttachmentList.add(Attachment(label, binary))
}
}
if (inHistory) {
history.forEach {
entryAttachmentList.addAll(it.getAttachments(attachmentPool, false))
}
}
return entryAttachmentList
}
fun containsAttachment(): Boolean {
return binaries.isNotEmpty()
}
fun putAttachment(attachment: Attachment, attachmentPool: AttachmentPool) {
binaries[attachment.name] = attachmentPool.put(attachment.binaryData)
}
fun removeAttachment(attachment: Attachment) {
binaries.remove(attachment.name)
}
fun removeAttachments() {
binaries.clear()
}
private fun getAttachmentsSize(attachmentPool: AttachmentPool): Long {
var size = 0L
for ((label, poolId) in binaries) {
size += label.length.toLong()
size += attachmentPool[poolId]?.getSize() ?: 0
}
return size
}
fun addEntryToHistory(entry: EntryKDBX) {
history.add(entry)
}
fun removeEntryFromHistory(position: Int): EntryKDBX {
return history.removeAt(position)
}
fun removeOldestEntryFromHistory(): EntryKDBX? {
var min: Date? = null
var index = -1
for (i in history.indices) {
val entry = history[i]
val lastMod = entry.lastModificationTime.date
if (min == null || lastMod.before(min)) {
index = i
min = lastMod
}
}
return if (index != -1) {
history.removeAt(index)
} else null
}
override fun touch(modified: Boolean, touchParents: Boolean) {
super.touch(modified, touchParents)
usageCount.plusOne()
}
companion object {
const val STR_TITLE = "Title"
const val STR_USERNAME = "UserName"
const val STR_PASSWORD = "Password"
const val STR_URL = "URL"
const val STR_NOTES = "Notes"
private const val FIXED_LENGTH_SIZE: Long = 128 // Approximate fixed length size
fun newCustomNameAllowed(name: String): Boolean {
return !(name.equals(STR_TITLE, true)
|| name.equals(STR_USERNAME, true)
|| name.equals(STR_PASSWORD, true)
|| name.equals(STR_URL, true)
|| name.equals(STR_NOTES, true))
}
@JvmField
val CREATOR: Parcelable.Creator<EntryKDBX> = object : Parcelable.Creator<EntryKDBX> {
override fun createFromParcel(parcel: Parcel): EntryKDBX {
return EntryKDBX(parcel)
}
override fun newArray(size: Int): Array<EntryKDBX?> {
return arrayOfNulls(size)
}
}
}
}

View file

@ -0,0 +1,53 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.entry
import android.os.Parcel
import com.kunzisoft.keepass.database.element.group.GroupVersioned
import com.kunzisoft.keepass.database.element.node.NodeVersioned
abstract class EntryVersioned
<
GroupId,
EntryId,
ParentGroup: GroupVersioned<GroupId, EntryId, ParentGroup, Entry>,
Entry: EntryVersioned<GroupId, EntryId, ParentGroup, Entry>
>
: NodeVersioned<EntryId, ParentGroup, Entry>, EntryVersionedInterface<ParentGroup> {
constructor() : super()
constructor(parcel: Parcel) : super(parcel)
override fun writeToParcel(dest: Parcel, flags: Int) {
super.writeToParcel(dest, flags)
}
override fun nodeIndexInParentForNaturalOrder(): Int {
if (nodeIndexInParentForNaturalOrder == -1) {
val numberOfGroups = parent?.getChildGroups()?.size
val indexInEntries = parent?.getChildEntries()?.indexOf(this)
if (numberOfGroups != null && indexInEntries != null)
return numberOfGroups + indexInEntries
}
return nodeIndexInParentForNaturalOrder
}
}

View file

@ -0,0 +1,33 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.entry
import com.kunzisoft.keepass.database.element.node.NodeVersionedInterface
interface EntryVersionedInterface<ParentGroup> : NodeVersionedInterface<ParentGroup> {
var username: String
var password: String
var url: String
var notes: String
}

View file

@ -0,0 +1,150 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.entry
import android.util.Log
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.utils.UuidUtil
import java.util.concurrent.ConcurrentHashMap
class FieldReferencesEngine(private val mDatabase: DatabaseKDBX) {
// Key : <WantedField>@<SearchIn>:<Text>
// Value : content
private var refsCache = ConcurrentHashMap<String, String?>()
fun clear() {
refsCache.clear()
}
fun compile(textReference: String, recursionLevel: Int): String {
return if (recursionLevel >= MAX_RECURSION_DEPTH) {
""
} else
fillReferencesPlaceholders(textReference, recursionLevel)
}
/**
* Manage placeholders with {REF:<WantedField>@<SearchIn>:<Text>}
*/
private fun fillReferencesPlaceholders(textReference: String, recursionLevel: Int): String {
var textValue = textReference
var offset = 0
var numberInlineRef = 0
while (textValue.contains(STR_REF_START)
&& numberInlineRef <= MAX_INLINE_REF) {
numberInlineRef++
try {
textValue = fillReferencesUsingCache(textValue)
val start = textValue.indexOf(STR_REF_START, offset, true)
if (start < 0) {
break
}
val end = textValue.indexOf(STR_REF_END, offset, true)
if (end <= start) {
break
}
val reference = textValue.substring(start + STR_REF_START.length, end)
val fullReference = "$STR_REF_START$reference$STR_REF_END"
if (!refsCache.containsKey(fullReference)) {
val newRecursionLevel = recursionLevel + 1
val result = findReferenceTarget(reference, newRecursionLevel)
val entryFound = result.entry
val data: String? = when (result.wanted) {
'T' -> entryFound?.decodeTitleKey(newRecursionLevel)
'U' -> entryFound?.decodeUsernameKey(newRecursionLevel)
'A' -> entryFound?.decodeUrlKey(newRecursionLevel)
'P' -> entryFound?.decodePasswordKey(newRecursionLevel)
'N' -> entryFound?.decodeNotesKey(newRecursionLevel)
'I' -> UuidUtil.toHexString(entryFound?.nodeId?.id)
else -> null
}
refsCache[fullReference] = data
textValue = fillReferencesUsingCache(textValue)
}
offset = end
} catch (e: Exception) {
Log.e(TAG, "Error when fill placeholders by reference", e)
}
}
return textValue
}
private fun fillReferencesUsingCache(text: String): String {
var newText = text
refsCache.keys.forEach { key ->
// Replace by key if value not found
newText = newText.replace(key, refsCache[key] ?: key, true)
}
return newText
}
private fun findReferenceTarget(reference: String, recursionLevel: Int): TargetResult {
val targetResult = TargetResult(null, 'J')
if (reference.length <= 4) {
return targetResult
}
if (reference[1] != '@') {
return targetResult
}
if (reference[3] != ':') {
return targetResult
}
targetResult.wanted = Character.toUpperCase(reference[0])
val searchIn = Character.toUpperCase(reference[2])
val searchQuery = reference.substring(4)
targetResult.entry = when (searchIn) {
'T' -> mDatabase.getEntryByTitle(searchQuery, recursionLevel)
'U' -> mDatabase.getEntryByUsername(searchQuery, recursionLevel)
'A' -> mDatabase.getEntryByURL(searchQuery, recursionLevel)
'P' -> mDatabase.getEntryByPassword(searchQuery, recursionLevel)
'N' -> mDatabase.getEntryByNotes(searchQuery, recursionLevel)
'I' -> {
UuidUtil.fromHexString(searchQuery)?.let { uuid ->
mDatabase.getEntryById(NodeIdUUID(uuid))
}
}
'O' -> mDatabase.getEntryByCustomData(searchQuery)
else -> null
}
return targetResult
}
private data class TargetResult(var entry: EntryKDBX?, var wanted: Char)
companion object {
private const val MAX_RECURSION_DEPTH = 10
private const val MAX_INLINE_REF = 10
private const val STR_REF_START = "{REF:"
private const val STR_REF_END = "}"
private val TAG = FieldReferencesEngine::class.java.name
}
}

View file

@ -0,0 +1,92 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.group
import android.os.Parcel
import android.os.Parcelable
import com.kunzisoft.keepass.database.element.entry.EntryKDB
import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.NodeIdInt
import com.kunzisoft.keepass.database.element.node.NodeKDBInterface
import com.kunzisoft.keepass.database.element.node.Type
import java.util.*
class GroupKDB : GroupVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface {
// Used by KeePass internally, don't use
var groupFlags = 0
constructor() : super()
constructor(parcel: Parcel) : super(parcel) {
groupFlags = parcel.readInt()
}
override fun readParentParcelable(parcel: Parcel): GroupKDB? {
return parcel.readParcelable(GroupKDB::class.java.classLoader)
}
override fun writeParentParcelable(parent: GroupKDB?, parcel: Parcel, flags: Int) {
parcel.writeParcelable(parent, flags)
}
override fun writeToParcel(dest: Parcel, flags: Int) {
super.writeToParcel(dest, flags)
dest.writeInt(groupFlags)
}
fun updateWith(source: GroupKDB,
updateParents: Boolean = true) {
super.updateWith(source, updateParents)
groupFlags = source.groupFlags
}
override val type: Type
get() = Type.GROUP
override fun initNodeId(): NodeId<Int> {
return NodeIdInt()
}
override fun copyNodeId(nodeId: NodeId<Int>): NodeId<Int> {
return NodeIdInt(nodeId.id)
}
fun setGroupId(groupId: Int) {
this.nodeId = NodeIdInt(groupId)
}
override fun afterAssignNewParent() {}
companion object {
@JvmField
val CREATOR: Parcelable.Creator<GroupKDB> = object : Parcelable.Creator<GroupKDB> {
override fun createFromParcel(parcel: Parcel): GroupKDB {
return GroupKDB(parcel)
}
override fun newArray(size: Int): Array<GroupKDB?> {
return arrayOfNulls(size)
}
}
}
}

View file

@ -0,0 +1,139 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.group
import android.os.Parcel
import android.os.ParcelUuid
import android.os.Parcelable
import com.kunzisoft.keepass.database.element.CustomData
import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.Tags
import com.kunzisoft.keepass.database.element.database.DatabaseVersioned
import com.kunzisoft.keepass.database.element.entry.EntryKDBX
import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface
import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.utils.UnsignedLong
import java.util.*
class GroupKDBX : GroupVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInterface {
override var usageCount = UnsignedLong(0)
override var locationChanged = DateInstant()
override var customData = CustomData()
var notes = ""
var isExpanded = true
var enableSearching: Boolean? = null
var enableAutoType: Boolean? = null
var defaultAutoTypeSequence: String = ""
var lastTopVisibleEntry: UUID = DatabaseVersioned.UUID_ZERO
override var tags = Tags()
override var previousParentGroup: UUID = DatabaseVersioned.UUID_ZERO
override var expires: Boolean = false
override val type: Type
get() = Type.GROUP
override fun initNodeId(): NodeId<UUID> {
return NodeIdUUID()
}
override fun copyNodeId(nodeId: NodeId<UUID>): NodeId<UUID> {
return NodeIdUUID(nodeId.id)
}
constructor() : super()
constructor(parcel: Parcel) : super(parcel) {
usageCount = UnsignedLong(parcel.readLong())
locationChanged = parcel.readParcelable(DateInstant::class.java.classLoader) ?: locationChanged
customData = parcel.readParcelable(CustomData::class.java.classLoader) ?: CustomData()
notes = parcel.readString() ?: notes
isExpanded = parcel.readByte().toInt() != 0
val isSearchingEnabled = parcel.readInt()
enableSearching = if (isSearchingEnabled == -1) null else isSearchingEnabled == 1
val isAutoTypeEnabled = parcel.readInt()
enableAutoType = if (isAutoTypeEnabled == -1) null else isAutoTypeEnabled == 1
defaultAutoTypeSequence = parcel.readString() ?: defaultAutoTypeSequence
lastTopVisibleEntry = parcel.readSerializable() as UUID
tags = parcel.readParcelable(Tags::class.java.classLoader) ?: tags
previousParentGroup = parcel.readParcelable<ParcelUuid>(ParcelUuid::class.java.classLoader)?.uuid ?: DatabaseVersioned.UUID_ZERO
}
override fun readParentParcelable(parcel: Parcel): GroupKDBX? {
return parcel.readParcelable(GroupKDBX::class.java.classLoader)
}
override fun writeParentParcelable(parent: GroupKDBX?, parcel: Parcel, flags: Int) {
parcel.writeParcelable(parent, flags)
}
override fun writeToParcel(dest: Parcel, flags: Int) {
super.writeToParcel(dest, flags)
dest.writeLong(usageCount.toKotlinLong())
dest.writeParcelable(locationChanged, flags)
dest.writeParcelable(customData, flags)
dest.writeString(notes)
dest.writeByte((if (isExpanded) 1 else 0).toByte())
dest.writeInt(if (enableSearching == null) -1 else if (enableSearching!!) 1 else 0)
dest.writeInt(if (enableAutoType == null) -1 else if (enableAutoType!!) 1 else 0)
dest.writeString(defaultAutoTypeSequence)
dest.writeSerializable(lastTopVisibleEntry)
dest.writeParcelable(tags, flags)
dest.writeParcelable(ParcelUuid(previousParentGroup), flags)
}
fun updateWith(source: GroupKDBX,
updateParents: Boolean = true) {
super.updateWith(source, updateParents)
usageCount = source.usageCount
locationChanged = DateInstant(source.locationChanged)
// Add all custom elements in map
customData = CustomData(source.customData)
notes = source.notes
isExpanded = source.isExpanded
enableSearching = source.enableSearching
enableAutoType = source.enableAutoType
defaultAutoTypeSequence = source.defaultAutoTypeSequence
lastTopVisibleEntry = source.lastTopVisibleEntry
tags = source.tags
previousParentGroup = source.previousParentGroup
}
override fun afterAssignNewParent() {
locationChanged = DateInstant()
}
companion object {
@JvmField
val CREATOR: Parcelable.Creator<GroupKDBX> = object : Parcelable.Creator<GroupKDBX> {
override fun createFromParcel(parcel: Parcel): GroupKDBX {
return GroupKDBX(parcel)
}
override fun newArray(size: Int): Array<GroupKDBX?> {
return arrayOfNulls(size)
}
}
}
}

View file

@ -0,0 +1,141 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.group
import android.os.Parcel
import com.kunzisoft.keepass.database.element.entry.EntryVersioned
import com.kunzisoft.keepass.database.element.node.NodeVersioned
import java.util.*
abstract class GroupVersioned
<
GroupId,
EntryId,
Group: GroupVersioned<GroupId, EntryId, Group, Entry>,
Entry: EntryVersioned<GroupId, EntryId, Group, Entry>
>
: NodeVersioned<GroupId, Group, Entry>, GroupVersionedInterface<Group, Entry> {
private var titleGroup = ""
@Transient
private val childGroups = LinkedList<Group>()
@Transient
private val childEntries = LinkedList<Entry>()
private var positionIndexChildren = 0
constructor() : super()
constructor(parcel: Parcel) : super(parcel) {
titleGroup = parcel.readString() ?: titleGroup
}
override fun writeToParcel(dest: Parcel, flags: Int) {
super.writeToParcel(dest, flags)
dest.writeString(titleGroup)
}
protected fun updateWith(source: GroupVersioned<GroupId, EntryId, Group, Entry>,
updateParents: Boolean = true) {
super.updateWith(source, updateParents)
titleGroup = source.titleGroup
if (updateParents) {
removeChildren()
childGroups.addAll(source.childGroups)
childEntries.addAll(source.childEntries)
}
}
override var title: String
get() = titleGroup
set(value) { titleGroup = value }
/**
* To determine the level from the root group (root group level is -1)
*/
fun getLevel(): Int {
var level = -1
parent?.let { parent ->
level = parent.getLevel() + 1
}
return level
}
override fun getChildGroups(): List<Group> {
return childGroups
}
override fun getChildEntries(): List<Entry> {
return childEntries
}
override fun addChildGroup(group: Group) {
if (childGroups.contains(group))
removeChildGroup(group)
positionIndexChildren++
group.nodeIndexInParentForNaturalOrder = positionIndexChildren
this.childGroups.add(group)
}
override fun addChildEntry(entry: Entry) {
if (childEntries.contains(entry))
removeChildEntry(entry)
positionIndexChildren++
entry.nodeIndexInParentForNaturalOrder = positionIndexChildren
this.childEntries.add(entry)
}
override fun updateChildGroup(group: Group) {
val index = this.childGroups.indexOfFirst { it.nodeId == group.nodeId }
if (index >= 0) {
val oldGroup = this.childGroups.removeAt(index)
group.nodeIndexInParentForNaturalOrder = oldGroup.nodeIndexInParentForNaturalOrder
this.childGroups.add(index, group)
}
}
override fun updateChildEntry(entry: Entry) {
val index = this.childEntries.indexOfFirst { it.nodeId == entry.nodeId }
if (index >= 0) {
val oldEntry = this.childEntries.removeAt(index)
entry.nodeIndexInParentForNaturalOrder = oldEntry.nodeIndexInParentForNaturalOrder
this.childEntries.add(index, entry)
}
}
override fun removeChildGroup(group: Group) {
this.childGroups.remove(group)
}
override fun removeChildEntry(entry: Entry) {
this.childEntries.remove(entry)
}
override fun removeChildren() {
this.childGroups.clear()
this.childEntries.clear()
}
override fun nodeIndexInParentForNaturalOrder(): Int {
return if (nodeIndexInParentForNaturalOrder == -1)
childGroups.indexOf(this)
else
nodeIndexInParentForNaturalOrder
}
}

View file

@ -0,0 +1,112 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.group
import com.kunzisoft.keepass.database.action.node.NodeHandler
import com.kunzisoft.keepass.database.element.node.NodeVersionedInterface
interface GroupVersionedInterface<Group: GroupVersionedInterface<Group, Entry>, Entry> : NodeVersionedInterface<Group> {
fun getChildGroups(): List<Group>
fun getChildEntries(): List<Entry>
fun addChildGroup(group: Group)
fun addChildEntry(entry: Entry)
fun updateChildGroup(group: Group)
fun updateChildEntry(entry: Entry)
fun removeChildGroup(group: Group)
fun removeChildEntry(entry: Entry)
fun removeChildren()
@Suppress("UNCHECKED_CAST")
fun doForEachChildAndForIt(entryHandler: NodeHandler<Entry>,
groupHandler: NodeHandler<Group>) {
doForEachChild(entryHandler, groupHandler)
groupHandler.operate(this as Group)
}
fun doForEachChild(entryHandler: NodeHandler<Entry>?,
groupHandler: NodeHandler<Group>?,
stopIterationWhenGroupHandlerOperateFalse: Boolean = true): Boolean {
if (entryHandler != null) {
for (entry in this.getChildEntries()) {
if (!entryHandler.operate(entry))
return false
}
}
for (group in this.getChildGroups()) {
var doActionForChild = true
if (groupHandler != null && !groupHandler.operate(group)) {
doActionForChild = false
if (stopIterationWhenGroupHandlerOperateFalse)
return false
}
if (doActionForChild)
group.doForEachChild(entryHandler, groupHandler, stopIterationWhenGroupHandlerOperateFalse)
}
return true
}
fun searchChildEntry(criteria: (entry: Entry) -> Boolean): Entry? {
return searchChildEntry(this, criteria)
}
private fun searchChildEntry(rootGroup: GroupVersionedInterface<Group, Entry>,
criteria: (entry: Entry) -> Boolean): Entry? {
for (childEntry in rootGroup.getChildEntries()) {
if (criteria.invoke(childEntry)) {
return childEntry
}
}
for (group in rootGroup.getChildGroups()) {
val searchChildEntry = searchChildEntry(group, criteria)
if (searchChildEntry != null) {
return searchChildEntry
}
}
return null
}
fun searchChildGroup(criteria: (group: Group) -> Boolean): Group? {
return searchChildGroup(this, criteria)
}
private fun searchChildGroup(rootGroup: GroupVersionedInterface<Group, Entry>,
criteria: (group: Group) -> Boolean): Group? {
for (childGroup in rootGroup.getChildGroups()) {
if (criteria.invoke(childGroup)) {
return childGroup
} else {
val subGroup = searchChildGroup(childGroup, criteria)
if (subGroup != null) {
return subGroup
}
}
}
return null
}
}

View file

@ -0,0 +1,87 @@
/*
* Copyright 2021 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.icon
import android.os.Parcel
import android.os.Parcelable
class IconImage() : IconImageDraw() {
var standard: IconImageStandard = IconImageStandard()
var custom: IconImageCustom = IconImageCustom()
constructor(iconImageStandard: IconImageStandard) : this() {
this.standard = iconImageStandard
}
constructor(iconImageCustom: IconImageCustom) : this() {
this.custom = iconImageCustom
}
constructor(iconImageStandard: IconImageStandard,
iconImageCustom: IconImageCustom) : this() {
this.standard = iconImageStandard
this.custom = iconImageCustom
}
constructor(parcel: Parcel) : this() {
standard = parcel.readParcelable(IconImageStandard::class.java.classLoader) ?: standard
custom = parcel.readParcelable(IconImageCustom::class.java.classLoader) ?: custom
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeParcelable(standard, flags)
parcel.writeParcelable(custom, flags)
}
override fun describeContents(): Int {
return 0
}
override fun getIconImageToDraw(): IconImage {
return this
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is IconImage) return false
if (standard != other.standard) return false
if (custom != other.custom) return false
return true
}
override fun hashCode(): Int {
var result = standard.hashCode()
result = 31 * result + custom.hashCode()
return result
}
companion object CREATOR : Parcelable.Creator<IconImage> {
override fun createFromParcel(parcel: Parcel): IconImage {
return IconImage(parcel)
}
override fun newArray(size: Int): Array<IconImage?> {
return arrayOfNulls(size)
}
}
}

View file

@ -0,0 +1,110 @@
/*
* Copyright 2021 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.icon
import android.os.Parcel
import android.os.ParcelUuid
import android.os.Parcelable
import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.database.DatabaseVersioned
import java.util.*
class IconImageCustom : IconImageDraw {
val uuid: UUID
var name: String = ""
var lastModificationTime: DateInstant? = null
fun updateWith(icon: IconImageCustom) {
this.name = icon.name
this.lastModificationTime = icon.lastModificationTime
}
constructor(copy: IconImageCustom) {
this.uuid = copy.uuid
updateWith(copy)
}
constructor(name: String = "", lastModificationTime: DateInstant? = null) {
this.uuid = DatabaseVersioned.UUID_ZERO
this.name = name
this.lastModificationTime = lastModificationTime
}
constructor(uuid: UUID, name: String = "", lastModificationTime: DateInstant? = null) {
this.uuid = uuid
this.name = name
this.lastModificationTime = lastModificationTime
}
constructor(parcel: Parcel) {
uuid = parcel.readParcelable<ParcelUuid>(ParcelUuid::class.java.classLoader)?.uuid ?: DatabaseVersioned.UUID_ZERO
name = parcel.readString() ?: name
lastModificationTime = parcel.readParcelable(DateInstant::class.java.classLoader)
}
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeParcelable(ParcelUuid(uuid), flags)
dest.writeString(name)
dest.writeParcelable(lastModificationTime, flags)
}
override fun describeContents(): Int {
return 0
}
override fun hashCode(): Int {
val prime = 31
var result = 1
result = prime * result + uuid.hashCode()
return result
}
override fun getIconImageToDraw(): IconImage {
return IconImage(this)
}
override fun equals(other: Any?): Boolean {
if (this === other)
return true
if (other == null)
return false
if (other !is IconImageCustom)
return false
return uuid == other.uuid
}
val isUnknown: Boolean
get() = uuid == DatabaseVersioned.UUID_ZERO
companion object {
@JvmField
val CREATOR: Parcelable.Creator<IconImageCustom> = object : Parcelable.Creator<IconImageCustom> {
override fun createFromParcel(parcel: Parcel): IconImageCustom {
return IconImageCustom(parcel)
}
override fun newArray(size: Int): Array<IconImageCustom?> {
return arrayOfNulls(size)
}
}
}
}

View file

@ -0,0 +1,31 @@
/*
* Copyright 2021 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.icon
import android.os.Parcelable
abstract class IconImageDraw : Parcelable {
var selected = false
/**
* Only to retrieve an icon image to Draw, to not use as object to manipulate
*/
abstract fun getIconImageToDraw(): IconImage
}

View file

@ -0,0 +1,105 @@
/*
* Copyright 2019 Brian Pellin, Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.icon
import android.os.Parcel
import android.os.Parcelable
import com.kunzisoft.keepass.icons.IconPack.Companion.NB_ICONS
class IconImageStandard : IconImageDraw {
val id: Int
constructor() {
this.id = KEY_ID
}
constructor(iconId: Int) {
if (!isCorrectIconId(iconId))
this.id = KEY_ID
else
this.id = iconId
}
constructor(parcel: Parcel) {
id = parcel.readInt()
}
override fun describeContents(): Int {
return 0
}
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeInt(id)
}
override fun hashCode(): Int {
val prime = 31
var result = 1
result = prime * result + id
return result
}
override fun getIconImageToDraw(): IconImage {
return IconImage(this)
}
override fun equals(other: Any?): Boolean {
if (this === other)
return true
if (other == null)
return false
if (other !is IconImageStandard) {
return false
}
return id == other.id
}
companion object {
const val KEY_ID = 0
const val ID_CARD_ID = 9
const val WIRELESS_ID = 12
const val EMAIL_ID = 19
const val CREDIT_CARD_ID = 37
const val TRASH_ID = 43
const val FOLDER_ID = 48
const val DATABASE_ID = 50
const val LIST_ID = 57
const val BUILD_ID = 59
const val STAR_ID = 61
const val DOLLAR_ID = 66
fun isCorrectIconId(iconId: Int): Boolean {
return iconId in 0 until NB_ICONS
}
@JvmField
val CREATOR: Parcelable.Creator<IconImageStandard> = object : Parcelable.Creator<IconImageStandard> {
override fun createFromParcel(parcel: Parcel): IconImageStandard {
return IconImageStandard(parcel)
}
override fun newArray(size: Int): Array<IconImageStandard?> {
return arrayOfNulls(size)
}
}
}
}

View file

@ -0,0 +1,103 @@
/*
* Copyright 2021 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.icon
import android.util.Log
import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.binary.BinaryCache
import com.kunzisoft.keepass.database.element.binary.BinaryData
import com.kunzisoft.keepass.database.element.binary.CustomIconPool
import com.kunzisoft.keepass.database.element.icon.IconImageStandard.Companion.KEY_ID
import com.kunzisoft.keepass.icons.IconPack.Companion.NB_ICONS
import java.util.*
class IconsManager {
private val standardCache = List(NB_ICONS) {
IconImageStandard(it)
}
private val customCache = CustomIconPool()
fun getIcon(iconId: Int): IconImageStandard {
val searchIconId = if (IconImageStandard.isCorrectIconId(iconId)) iconId else KEY_ID
return standardCache[searchIconId]
}
fun doForEachStandardIcon(action: (IconImageStandard) -> Unit) {
standardCache.forEach { icon ->
action.invoke(icon)
}
}
/*
* Custom
*/
fun addCustomIcon(key: UUID? = null,
name: String,
lastModificationTime: DateInstant?,
builder: (uniqueBinaryId: String) -> BinaryData,
result: (IconImageCustom, BinaryData?) -> Unit) {
customCache.put(key, name, lastModificationTime, builder, result)
}
fun getIcon(iconUuid: UUID): IconImageCustom? {
return customCache.getCustomIcon(iconUuid)
}
fun isCustomIconBinaryDuplicate(binaryData: BinaryData): Boolean {
return customCache.isBinaryDuplicate(binaryData)
}
fun removeCustomIcon(iconUuid: UUID, binaryCache: BinaryCache) {
val binary = customCache[iconUuid]
customCache.remove(iconUuid)
try {
binary?.clear(binaryCache)
} catch (e: Exception) {
Log.w(TAG, "Unable to remove custom icon binary", e)
}
}
fun getBinaryForCustomIcon(iconUuid: UUID): BinaryData? {
return customCache[iconUuid]
}
fun doForEachCustomIcon(action: (IconImageCustom, BinaryData) -> Unit) {
customCache.doForEachCustomIcon(action)
}
fun containsCustomIconWithNameOrLastModificationTime(): Boolean {
return customCache.any { customIcon ->
customIcon.name.isNotEmpty() || customIcon.lastModificationTime != null
}
}
/**
* Clear the cache of icons
*/
fun clear() {
customCache.clear()
}
companion object {
private val TAG = IconsManager::class.java.name
}
}

View file

@ -0,0 +1,57 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.node
import com.kunzisoft.keepass.database.element.Group
interface Node: NodeVersionedInterface<Group> {
val nodeId: NodeId<*>?
fun addParentFrom(node: Node) {
parent = node.parent
}
fun removeParent() {
parent = null
}
fun getPathString(): String {
val pathNodes = mutableListOf<Node>()
var currentNode = this
pathNodes.add(0, currentNode)
while (currentNode.containsParent()) {
currentNode.parent?.let { parent ->
currentNode = parent
pathNodes.add(0, currentNode)
}
}
return pathNodes.joinToString("/") { it.title }
}
}
/**
* Type of available Nodes
*/
enum class Type {
GROUP, ENTRY
}

View file

@ -0,0 +1,49 @@
/*
* Copyright 2018 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.node
import android.os.Parcel
import android.os.Parcelable
abstract class NodeId<Id> : Parcelable {
abstract val id: Id
override fun writeToParcel(dest: Parcel, flags: Int) {}
override fun describeContents(): Int {
return 0
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is NodeId<*>) return false
if (id != other.id) return false
return true
}
override fun hashCode(): Int {
return id?.hashCode() ?: 0
}
abstract fun toVisualString(): String?
}

View file

@ -0,0 +1,82 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.node
import android.os.Parcel
import android.os.Parcelable
import java.util.Random
class NodeIdInt : NodeId<Int> {
override var id: Int = -1
private set
constructor(source: NodeIdInt) : this(source.id)
constructor(groupId: Int = Random().nextInt()) : super() {
this.id = groupId
}
constructor(parcel: Parcel) {
id = parcel.readInt()
}
override fun writeToParcel(dest: Parcel, flags: Int) {
super.writeToParcel(dest, flags)
dest.writeInt(id)
}
override fun equals(other: Any?): Boolean {
if (this === other)
return true
if (other == null)
return false
if (other !is NodeIdInt) {
return false
}
return id == other.id
}
override fun hashCode(): Int {
return id.hashCode()
}
override fun toString(): String {
return id.toString()
}
override fun toVisualString(): String? {
return null
}
companion object {
@JvmField
val CREATOR: Parcelable.Creator<NodeIdInt> = object : Parcelable.Creator<NodeIdInt> {
override fun createFromParcel(parcel: Parcel): NodeIdInt {
return NodeIdInt(parcel)
}
override fun newArray(size: Int): Array<NodeIdInt?> {
return arrayOfNulls(size)
}
}
}
}

View file

@ -0,0 +1,83 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.node
import android.os.Parcel
import android.os.ParcelUuid
import android.os.Parcelable
import com.kunzisoft.keepass.utils.UuidUtil
import java.util.*
class NodeIdUUID : NodeId<UUID> {
override var id: UUID = UUID.randomUUID()
private set
constructor(source: NodeIdUUID) : this(source.id)
constructor(uuid: UUID = UUID.randomUUID()) : super() {
this.id = uuid
}
constructor(parcel: Parcel) {
id = parcel.readParcelable<ParcelUuid>(ParcelUuid::class.java.classLoader)?.uuid ?: id
}
override fun writeToParcel(dest: Parcel, flags: Int) {
super.writeToParcel(dest, flags)
dest.writeParcelable(ParcelUuid(id), flags)
}
override fun equals(other: Any?): Boolean {
if (this === other)
return true
if (other == null)
return false
if (other !is NodeIdUUID) {
return false
}
return this.id == other.id
}
override fun hashCode(): Int {
return id.hashCode()
}
override fun toString(): String {
return UuidUtil.toHexString(id) ?: id.toString()
}
override fun toVisualString(): String {
return toString()
}
companion object {
@JvmField
val CREATOR: Parcelable.Creator<NodeIdUUID> = object : Parcelable.Creator<NodeIdUUID> {
override fun createFromParcel(parcel: Parcel): NodeIdUUID {
return NodeIdUUID(parcel)
}
override fun newArray(size: Int): Array<NodeIdUUID?> {
return arrayOfNulls(size)
}
}
}
}

View file

@ -0,0 +1,35 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.node
import com.kunzisoft.keepass.database.element.DateInstant
interface NodeKDBInterface : NodeTimeInterface {
override var expires: Boolean
get() = expiryTime.isNeverExpires()
set(value) {
if (!value)
expiryTime = DateInstant.NEVER_EXPIRES
}
}

View file

@ -0,0 +1,35 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.node
import com.kunzisoft.keepass.database.element.CustomData
import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.Tags
import com.kunzisoft.keepass.utils.UnsignedLong
import java.util.*
interface NodeKDBXInterface : NodeTimeInterface {
var usageCount: UnsignedLong
var locationChanged: DateInstant
var customData: CustomData
var tags: Tags
var previousParentGroup: UUID
}

View file

@ -0,0 +1,37 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.node
import com.kunzisoft.keepass.database.element.DateInstant
interface NodeTimeInterface {
var creationTime: DateInstant
var lastModificationTime: DateInstant
var lastAccessTime: DateInstant
var expiryTime: DateInstant
var expires: Boolean
val isCurrentlyExpires: Boolean
}

View file

@ -0,0 +1,158 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*
*/
package com.kunzisoft.keepass.database.element.node
import android.os.Parcel
import android.os.Parcelable
import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.entry.EntryVersionedInterface
import com.kunzisoft.keepass.database.element.group.GroupVersionedInterface
import com.kunzisoft.keepass.database.element.icon.IconImage
/**
* Abstract class who manage Groups and Entries
*/
abstract class NodeVersioned<IdType, Parent : GroupVersionedInterface<Parent, Entry>, Entry : EntryVersionedInterface<Parent>>
: NodeVersionedInterface<Parent>, NodeTimeInterface, Parcelable {
var nodeId: NodeId<IdType> = this.initNodeId()
val id: IdType
get() = nodeId.id
var nodeIndexInParentForNaturalOrder = -1
protected constructor()
protected constructor(parcel: Parcel) {
this.nodeId = parcel.readParcelable(NodeId::class.java.classLoader) ?: nodeId
this.parent = this.readParentParcelable(parcel)
this.icon = parcel.readParcelable(IconImage::class.java.classLoader) ?: icon
this.creationTime = parcel.readParcelable(DateInstant::class.java.classLoader) ?: creationTime
this.lastModificationTime = parcel.readParcelable(DateInstant::class.java.classLoader) ?: lastModificationTime
this.lastAccessTime = parcel.readParcelable(DateInstant::class.java.classLoader) ?: lastAccessTime
this.expiryTime = parcel.readParcelable(DateInstant::class.java.classLoader) ?: expiryTime
this.expires = parcel.readByte().toInt() != 0
}
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeParcelable(nodeId, flags)
writeParentParcelable(parent, dest, flags)
dest.writeParcelable(icon, flags)
dest.writeParcelable(creationTime, flags)
dest.writeParcelable(lastModificationTime, flags)
dest.writeParcelable(lastAccessTime, flags)
dest.writeParcelable(expiryTime, flags)
dest.writeByte((if (expires) 1 else 0).toByte())
}
override fun describeContents(): Int {
return 0
}
protected fun updateWith(source: NodeVersioned<IdType, Parent, Entry>,
updateParents: Boolean = true) {
this.nodeId = copyNodeId(source.nodeId)
if (updateParents) {
this.parent = source.parent
}
this.icon = source.icon
this.creationTime = DateInstant(source.creationTime)
this.lastModificationTime = DateInstant(source.lastModificationTime)
this.lastAccessTime = DateInstant(source.lastAccessTime)
this.expiryTime = DateInstant(source.expiryTime)
this.expires = source.expires
}
protected abstract fun initNodeId(): NodeId<IdType>
protected abstract fun copyNodeId(nodeId: NodeId<IdType>): NodeId<IdType>
protected abstract fun readParentParcelable(parcel: Parcel): Parent?
protected abstract fun writeParentParcelable(parent: Parent?, parcel: Parcel, flags: Int)
final override var parent: Parent? = null
final override var icon: IconImage = IconImage()
final override var creationTime: DateInstant = DateInstant()
final override var lastModificationTime: DateInstant = DateInstant()
final override var lastAccessTime: DateInstant = DateInstant()
final override var expiryTime: DateInstant = DateInstant.NEVER_EXPIRES
final override val isCurrentlyExpires: Boolean
get() = expires && expiryTime.isCurrentlyExpire()
/**
* @return true if parent is present (false if not present, can be a root or a detach element)
*/
override fun containsParent(): Boolean {
return parent != null
}
override fun afterAssignNewParent() {}
override fun isContainedIn(container: Parent): Boolean {
if (this == container)
return true
var cur = this.parent
while (cur != null) {
if (cur == container) {
return true
}
cur = cur.parent
}
return false
}
override fun touch(modified: Boolean, touchParents: Boolean) {
val now = DateInstant()
lastAccessTime = now
if (modified) {
lastModificationTime = now
}
if (touchParents) {
parent?.touch(modified, true)
}
}
override fun equals(other: Any?): Boolean {
if (this === other)
return true
if (other == null)
return false
if (other !is NodeVersioned<*, *, *>) {
return false
}
return type == other.type && nodeId == other.nodeId
}
override fun hashCode(): Int {
return nodeId.hashCode()
}
override fun toString(): String {
return "$title ($nodeId)"
}
}

View file

@ -0,0 +1,48 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.node
import android.os.Parcelable
import com.kunzisoft.keepass.database.element.icon.IconImage
interface NodeVersionedInterface<ParentGroup> : NodeTimeInterface, Parcelable {
var title: String
var icon: IconImage
val type: Type
/**
* Retrieve the parent node
*/
var parent: ParentGroup?
fun containsParent(): Boolean
fun afterAssignNewParent()
fun isContainedIn(container: ParentGroup): Boolean
/**
* Groups are always before in natural order (DB order)
*/
fun nodeIndexInParentForNaturalOrder(): Int
fun touch(modified: Boolean, touchParents: Boolean)
}

View file

@ -0,0 +1,55 @@
/*
* Copyright 2018 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.security
class MemoryProtectionConfig {
var protectTitle = DEFAULT_PROTECT_TITLE
var protectUserName = DEFAULT_PROTECT_USERNAME
var protectPassword = DEFAULT_PROTECT_PASSWORD
var protectUrl = DEFAULT_PROTECT_URL
var protectNotes = DEFAULT_PROTECT_NOTES
var autoEnableVisualHiding = DEFAULT_AUTO_ENABLE_VISUAL_HIDING
fun isProtected(field: String): Boolean {
if (field.equals(TITLE_FIELD, ignoreCase = true)) return protectTitle
if (field.equals(USERNAME_FIELD, ignoreCase = true)) return protectUserName
if (field.equals(PASSWORD_FIELD, ignoreCase = true)) return protectPassword
if (field.equals(URL_FIELD, ignoreCase = true)) return protectUrl
if (field.equals(NOTES_FIELD, ignoreCase = true)) return protectNotes
return false
}
companion object ProtectDefinition {
const val TITLE_FIELD = "Title"
const val USERNAME_FIELD = "UserName"
const val PASSWORD_FIELD = "Password"
const val URL_FIELD = "URL"
const val NOTES_FIELD = "Notes"
const val DEFAULT_PROTECT_TITLE = false
const val DEFAULT_PROTECT_USERNAME = false
const val DEFAULT_PROTECT_PASSWORD = true
const val DEFAULT_PROTECT_URL = false
const val DEFAULT_PROTECT_NOTES = true
const val DEFAULT_AUTO_ENABLE_VISUAL_HIDING = false
}
}

View file

@ -0,0 +1,77 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.security
import android.os.Parcel
import android.os.Parcelable
class ProtectedString : Parcelable {
var isProtected: Boolean = false
private set
var stringValue: String = ""
constructor(toCopy: ProtectedString) {
this.isProtected = toCopy.isProtected
this.stringValue = toCopy.stringValue
}
constructor(enableProtection: Boolean = false, string: String = "") {
this.isProtected = enableProtection
this.stringValue = string
}
constructor(parcel: Parcel) {
isProtected = parcel.readByte().toInt() != 0
stringValue = parcel.readString() ?: stringValue
}
override fun describeContents(): Int {
return 0
}
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeByte((if (isProtected) 1 else 0).toByte())
dest.writeString(stringValue)
}
fun length(): Int {
return stringValue.length
}
override fun toString(): String {
return stringValue
}
companion object {
@JvmField
val CREATOR: Parcelable.Creator<ProtectedString> = object : Parcelable.Creator<ProtectedString> {
override fun createFromParcel(parcel: Parcel): ProtectedString {
return ProtectedString(parcel)
}
override fun newArray(size: Int): Array<ProtectedString?> {
return arrayOfNulls(size)
}
}
}
}

View file

@ -0,0 +1,182 @@
/*
* Copyright 2021 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*/
package com.kunzisoft.keepass.database.element.template
import android.os.Parcel
import android.os.ParcelUuid
import android.os.Parcelable
import com.kunzisoft.keepass.database.element.database.DatabaseVersioned
import com.kunzisoft.keepass.database.element.icon.IconImage
import java.util.*
import kotlin.collections.ArrayList
class Template : Parcelable {
var version = 1
var uuid: UUID = DatabaseVersioned.UUID_ZERO
var title = ""
var icon = IconImage()
var backgroundColor: Int? = null
var foregroundColor: Int? = null
var sections: MutableList<TemplateSection> = ArrayList()
private set
constructor(uuid: UUID,
title: String,
icon: IconImage,
section: TemplateSection,
version: Int = 1)
: this(uuid, title, icon, ArrayList<TemplateSection>().apply {
add(section)
}, version)
constructor(uuid: UUID,
title: String,
icon: IconImage,
sections: List<TemplateSection>,
version: Int = 1)
: this(uuid, title, icon, null, null, sections, version)
constructor(uuid: UUID,
title: String,
icon: IconImage,
backgroundColor: Int?,
foregroundColor: Int?,
sections: List<TemplateSection>,
version: Int = 1) {
this.version = version
this.uuid = uuid
this.title = title
this.icon = icon
this.backgroundColor = backgroundColor
this.foregroundColor = foregroundColor
this.sections.clear()
this.sections.addAll(sections)
}
constructor(template: Template) {
this.version = template.version
this.uuid = template.uuid
this.title = template.title
this.icon = template.icon
this.backgroundColor = template.backgroundColor
this.foregroundColor = template.foregroundColor
this.sections.clear()
this.sections.addAll(template.sections)
}
constructor(parcel: Parcel) {
version = parcel.readInt()
uuid = parcel.readParcelable<ParcelUuid>(ParcelUuid::class.java.classLoader)?.uuid ?: uuid
title = parcel.readString() ?: title
icon = parcel.readParcelable(IconImage::class.java.classLoader) ?: icon
backgroundColor = parcel.readInt()
foregroundColor = parcel.readInt()
parcel.readList(sections, TemplateSection::class.java.classLoader)
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeInt(version)
parcel.writeParcelable(ParcelUuid(uuid), flags)
parcel.writeString(title)
parcel.writeParcelable(icon, flags)
parcel.writeInt(backgroundColor ?: -1)
parcel.writeInt(foregroundColor ?: -1)
parcel.writeList(sections)
}
override fun describeContents(): Int {
return 0
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is Template) return false
if (uuid != other.uuid) return false
return true
}
override fun hashCode(): Int {
return uuid.hashCode()
}
companion object CREATOR : Parcelable.Creator<Template> {
override fun createFromParcel(parcel: Parcel): Template {
return Template(parcel)
}
override fun newArray(size: Int): Array<Template?> {
return arrayOfNulls(size)
}
val TITLE_ATTRIBUTE = TemplateAttribute(
TemplateField.LABEL_TITLE,
TemplateAttributeType.TEXT)
val USERNAME_ATTRIBUTE = TemplateAttribute(
TemplateField.LABEL_USERNAME,
TemplateAttributeType.TEXT)
val PASSWORD_ATTRIBUTE = TemplateAttribute(
TemplateField.LABEL_PASSWORD,
TemplateAttributeType.TEXT,
true,
TemplateAttributeOption().apply {
setNumberLines(3)
associatePasswordGenerator()
}
)
val URL_ATTRIBUTE = TemplateAttribute(
TemplateField.LABEL_URL,
TemplateAttributeType.TEXT,
false,
TemplateAttributeOption().apply {
setLink(true)
})
val EXPIRATION_ATTRIBUTE = TemplateAttribute(
TemplateField.LABEL_EXPIRATION,
TemplateAttributeType.DATETIME,
false)
val NOTES_ATTRIBUTE = TemplateAttribute(
TemplateField.LABEL_NOTES,
TemplateAttributeType.TEXT,
false,
TemplateAttributeOption().apply {
setNumberLinesToMany()
})
val STANDARD: Template
get() {
val sections = mutableListOf<TemplateSection>()
val mainSection = TemplateSection(mutableListOf<TemplateAttribute>().apply {
add(USERNAME_ATTRIBUTE)
add(PASSWORD_ATTRIBUTE)
add(URL_ATTRIBUTE)
add(EXPIRATION_ATTRIBUTE)
add(NOTES_ATTRIBUTE)
})
sections.add(mainSection)
return Template(
DatabaseVersioned.UUID_ZERO,
TemplateField.LABEL_STANDARD,
IconImage(),
sections)
}
}
}

View file

@ -0,0 +1,77 @@
/*
* Copyright 2021 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*/
package com.kunzisoft.keepass.database.element.template
import android.os.Parcel
import android.os.Parcelable
import com.kunzisoft.keepass.utils.ParcelableUtil
import com.kunzisoft.keepass.utils.readEnum
import com.kunzisoft.keepass.utils.writeEnum
data class TemplateAttribute(var label: String,
var type: TemplateAttributeType,
var protected: Boolean = false,
var options: TemplateAttributeOption = TemplateAttributeOption(),
var action: TemplateAttributeAction = TemplateAttributeAction.NONE): Parcelable {
constructor(parcel: Parcel) : this(
parcel.readString() ?: "",
parcel.readEnum<TemplateAttributeType>() ?: TemplateAttributeType.TEXT,
parcel.readByte() != 0.toByte(),
parcel.readParcelable(TemplateAttributeOption::class.java.classLoader) ?: TemplateAttributeOption(),
parcel.readEnum<TemplateAttributeAction>() ?: TemplateAttributeAction.NONE)
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(label)
parcel.writeEnum(type)
parcel.writeByte(if (protected) 1 else 0)
parcel.writeParcelable(options, flags)
parcel.writeEnum(action)
}
override fun describeContents(): Int {
return 0
}
var alias: String?
get() {
return options.alias
}
set(value) {
options.alias = value
}
var default: String
get() {
return this.options.default
}
set(value) {
this.options.default = value
}
companion object CREATOR : Parcelable.Creator<TemplateAttribute> {
override fun createFromParcel(parcel: Parcel): TemplateAttribute {
return TemplateAttribute(parcel)
}
override fun newArray(size: Int): Array<TemplateAttribute?> {
return arrayOfNulls(size)
}
}
}

View file

@ -0,0 +1,23 @@
/*
* Copyright 2021 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*/
package com.kunzisoft.keepass.database.element.template
enum class TemplateAttributeAction {
NONE, CUSTOM_EDITION
}

View file

@ -0,0 +1,288 @@
/*
* Copyright 2021 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*/
package com.kunzisoft.keepass.database.element.template
import android.os.Parcel
import android.os.Parcelable
import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.utils.ParcelableUtil
class TemplateAttributeOption() : Parcelable {
private val mOptions: MutableMap<String, String> = mutableMapOf()
constructor(parcel: Parcel) : this() {
ParcelableUtil.readStringParcelableMap(parcel)
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
ParcelableUtil.writeStringParcelableMap(parcel, LinkedHashMap(mOptions))
}
override fun describeContents(): Int {
return 0
}
var alias: String?
get() {
val tempAlias = mOptions[ALIAS_ATTR]
if (tempAlias.isNullOrEmpty())
return null
return tempAlias
}
set(value) {
if (value == null)
mOptions.remove(ALIAS_ATTR)
else
mOptions[ALIAS_ATTR] = value
}
var default: String
get() {
return mOptions[DEFAULT_ATTR] ?: DEFAULT_VALUE
}
set(value) {
mOptions[DEFAULT_ATTR] = value
}
fun getNumberChars(): Int {
return try {
if (mOptions[TEXT_NUMBER_CHARS_ATTR].equals(TEXT_NUMBER_CHARS_VALUE_MANY_STRING, true))
TEXT_NUMBER_CHARS_VALUE_MANY
else
mOptions[TEXT_NUMBER_CHARS_ATTR]?.toInt() ?: TEXT_NUMBER_CHARS_VALUE_DEFAULT
} catch (e: Exception) {
TEXT_NUMBER_CHARS_VALUE_DEFAULT
}
}
fun setNumberChars(numberChars: Int) {
mOptions[TEXT_NUMBER_CHARS_ATTR] = numberChars.toString()
}
fun setNumberCharsToMany() {
mOptions[TEXT_NUMBER_CHARS_ATTR] = TEXT_NUMBER_CHARS_VALUE_MANY_STRING
}
fun getNumberLines(): Int {
return try {
if (mOptions[TEXT_NUMBER_LINES_ATTR].equals(TEXT_NUMBER_LINES_VALUE_MANY_STRING, true))
TEXT_NUMBER_LINES_VALUE_MANY
else
mOptions[TEXT_NUMBER_LINES_ATTR]?.toInt() ?: TEXT_NUMBER_LINES_VALUE_DEFAULT
} catch (e: Exception) {
TEXT_NUMBER_LINES_VALUE_DEFAULT
}
}
fun setNumberLines(numberLines: Int) {
val lines = if (numberLines == 0) 1 else numberLines
mOptions[TEXT_NUMBER_LINES_ATTR] = lines.toString()
}
fun setNumberLinesToMany() {
mOptions[TEXT_NUMBER_LINES_ATTR] = TEXT_NUMBER_LINES_VALUE_MANY_STRING
}
fun isLink(): Boolean {
return try {
mOptions[TEXT_LINK_ATTR]?.toBoolean() ?: TEXT_LINK_VALUE_DEFAULT
} catch (e: Exception) {
TEXT_LINK_VALUE_DEFAULT
}
}
fun setLink(isLink: Boolean) {
mOptions[TEXT_LINK_ATTR] = isLink.toString()
}
fun isAssociatedWithPasswordGenerator(): Boolean {
return try {
mOptions[PASSWORD_GENERATOR_ATTR]?.toBoolean() ?: PASSWORD_GENERATOR_VALUE_DEFAULT
} catch (e: Exception) {
PASSWORD_GENERATOR_VALUE_DEFAULT
}
}
fun associatePasswordGenerator() {
mOptions[PASSWORD_GENERATOR_ATTR] = true.toString()
}
fun getListItems(): List<String> {
return mOptions[LIST_ITEMS]?.split(LIST_ITEMS_SEPARATOR) ?: listOf()
}
fun setListItems(vararg items: String) {
mOptions[LIST_ITEMS] = items.joinToString(LIST_ITEMS_SEPARATOR)
}
fun getDateFormat(): DateInstant.Type {
return when (mOptions[DATETIME_FORMAT_ATTR]) {
DATETIME_FORMAT_VALUE_DATE -> DateInstant.Type.DATE
DATETIME_FORMAT_VALUE_TIME -> DateInstant.Type.TIME
else -> DateInstant.Type.DATE_TIME
}
}
fun setDateFormatToDate() {
mOptions[DATETIME_FORMAT_ATTR] = DATETIME_FORMAT_VALUE_DATE
}
fun setDateFormatToTime() {
mOptions[DATETIME_FORMAT_ATTR] = DATETIME_FORMAT_VALUE_TIME
}
fun get(label: String): String? {
return mOptions[label]
}
fun put(label: String, value: String) {
mOptions[label] = value
}
fun remove(label: String) {
mOptions.remove(label)
}
companion object CREATOR : Parcelable.Creator<TemplateAttributeOption> {
override fun createFromParcel(parcel: Parcel): TemplateAttributeOption {
return TemplateAttributeOption(parcel)
}
override fun newArray(size: Int): Array<TemplateAttributeOption?> {
return arrayOfNulls(size)
}
/**
* Applicable to each type
* Define a text replacement for a label,
* Useful to keep compatibility with old keepass apps by replacing standard field label
*/
private const val ALIAS_ATTR = "alias"
/**
* Applicable to each type
* Define a default string element representation
* For a type LIST, represents a single string element representation
*/
private const val DEFAULT_ATTR = "default"
private const val DEFAULT_VALUE = ""
/**
* Applicable to type TEXT
* Define a number of chars
* Integer, can be "many" or "-1" to infinite value
* "1" if not defined
*/
private const val TEXT_NUMBER_CHARS_ATTR = "chars"
private const val TEXT_NUMBER_CHARS_VALUE_MANY = -1
private const val TEXT_NUMBER_CHARS_VALUE_MANY_STRING = "many"
private const val TEXT_NUMBER_CHARS_VALUE_DEFAULT = -1
/**
* Applicable to type TEXT
* Define a number of lines
* Integer, can be "-1" to infinite value
* "1" if not defined
*/
private const val TEXT_NUMBER_LINES_ATTR = "lines"
private const val TEXT_NUMBER_LINES_VALUE_MANY = -1
private const val TEXT_NUMBER_LINES_VALUE_MANY_STRING = "many"
private const val TEXT_NUMBER_LINES_VALUE_DEFAULT = 1
/**
* Applicable to type TEXT
* Define if a text is a link
* Boolean ("true" or "false")
* "true" if not defined
*/
private const val TEXT_LINK_ATTR = "link"
private const val TEXT_LINK_VALUE_DEFAULT = false
/**
* Applicable to type TEXT
* Define if a password generator is associated with the text
* Boolean ("true" or "false")
* "false" if not defined
*/
private const val PASSWORD_GENERATOR_ATTR = "generator"
private const val PASSWORD_GENERATOR_VALUE_DEFAULT = false
/**
* Applicable to type LIST
* Define items of a list
* List of items, separator is '|'
*/
private const val LIST_ITEMS = "items"
private const val LIST_ITEMS_SEPARATOR = "|"
/**
* Applicable to type DATETIME
* Define the type of date
* String ("date" or "time" or "datetime" or based on https://docs.oracle.com/javase/7/docs/api/java/text/SimpleDateFormat.html)
* "datetime" if not defined
*/
private const val DATETIME_FORMAT_ATTR = "format"
private const val DATETIME_FORMAT_VALUE_DATE = "date"
private const val DATETIME_FORMAT_VALUE_TIME = "time"
private fun removeSpecialChars(string: String): String {
return string.filterNot { "{,:}".indexOf(it) > -1 }
}
fun getOptionsFromString(label: String): TemplateAttributeOption {
val options = TemplateAttributeOption()
val optionsMap = if (label.contains("{") || label.contains("}")) {
try {
label.trim().substringAfter("{").substringBefore("}")
.split(",").associate {
val keyValue = it.trim()
val (left, right) = keyValue.split(":")
left to right
}.toMutableMap()
} catch (e: Exception) {
mutableMapOf()
}
} else {
mutableMapOf()
}
options.mOptions.apply {
clear()
putAll(optionsMap)
}
return options
}
fun getStringFromOptions(options: TemplateAttributeOption): String {
var optionsString = ""
if (options.mOptions.isNotEmpty()) {
optionsString += " {"
var first = true
for ((key, value) in options.mOptions) {
if (!first)
optionsString += ", "
first = false
optionsString += "${removeSpecialChars(key)}:${removeSpecialChars(value)}"
}
optionsString += "}"
}
return optionsString
}
}
}

View file

@ -0,0 +1,38 @@
/*
* Copyright 2021 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*/
package com.kunzisoft.keepass.database.element.template
enum class TemplateAttributeType(val typeString: String) {
TEXT("text"),
LIST("list"),
DATETIME("datetime"),
DIVIDER("divider");
companion object {
fun getFromString(label: String): TemplateAttributeType {
return when {
label.contains(TEXT.typeString, true) -> TEXT
label.contains(LIST.typeString, true) -> LIST
label.contains(DATETIME.typeString, true) -> DATETIME
label.contains(DIVIDER.typeString, true) -> DIVIDER
else -> TEXT
}
}
}
}

View file

@ -0,0 +1,195 @@
/*
* Copyright 2021 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX 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 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*/
package com.kunzisoft.keepass.database.element.template
import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
import java.util.*
class TemplateBuilder {
private val urlAttribute = TemplateAttribute(TemplateField.LABEL_URL, TemplateAttributeType.TEXT)
private val usernameAttribute = TemplateAttribute(TemplateField.LABEL_USERNAME, TemplateAttributeType.TEXT)
private val notesAttribute = TemplateAttribute(
TemplateField.LABEL_NOTES,
TemplateAttributeType.TEXT,
false,
TemplateAttributeOption().apply {
setNumberLinesToMany()
})
private val holderAttribute = TemplateAttribute(TemplateField.LABEL_HOLDER, TemplateAttributeType.TEXT)
private val numberAttribute = TemplateAttribute(TemplateField.LABEL_NUMBER, TemplateAttributeType.TEXT)
private val cvvAttribute = TemplateAttribute(TemplateField.LABEL_CVV, TemplateAttributeType.TEXT, true)
private val pinAttribute = TemplateAttribute(TemplateField.LABEL_PIN, TemplateAttributeType.TEXT, true)
private val nameAttribute = TemplateAttribute(TemplateField.LABEL_NAME, TemplateAttributeType.TEXT)
private val placeOfIssueAttribute = TemplateAttribute(TemplateField.LABEL_PLACE_OF_ISSUE, TemplateAttributeType.TEXT)
private val dateOfIssueAttribute = TemplateAttribute(
TemplateField.LABEL_DATE_OF_ISSUE,
TemplateAttributeType.DATETIME,
false,
TemplateAttributeOption().apply {
setDateFormatToDate()
})
private val expirationDateAttribute = TemplateAttribute(
TemplateField.LABEL_EXPIRATION,
TemplateAttributeType.DATETIME,
false,
TemplateAttributeOption().apply {
setDateFormatToDate()
})
private val emailAddressAttribute = TemplateAttribute(TemplateField.LABEL_EMAIL_ADDRESS, TemplateAttributeType.TEXT)
private val passwordAttribute = TemplateAttribute(TemplateField.LABEL_PASSWORD, TemplateAttributeType.TEXT, true)
private val ssidAttribute = TemplateAttribute(TemplateField.LABEL_SSID, TemplateAttributeType.TEXT)
private val wirelessTypeAttribute = TemplateAttribute(
TemplateField.LABEL_TYPE,
TemplateAttributeType.LIST,
false,
TemplateAttributeOption().apply{
setListItems("WPA3", "WPA2", "WPA", "WEP")
default = "WPA2"
})
private val tokenAttribute = TemplateAttribute(TemplateField.LABEL_TOKEN, TemplateAttributeType.TEXT)
private val publicKeyAttribute = TemplateAttribute(TemplateField.LABEL_PUBLIC_KEY, TemplateAttributeType.TEXT)
private val privateKeyAttribute = TemplateAttribute(TemplateField.LABEL_PRIVATE_KEY, TemplateAttributeType.TEXT, true)
private val seedAttribute = TemplateAttribute(TemplateField.LABEL_SEED, TemplateAttributeType.TEXT, true)
private val bicAttribute = TemplateAttribute(TemplateField.LABEL_BIC, TemplateAttributeType.TEXT)
private val ibanAttribute = TemplateAttribute(TemplateField.LABEL_IBAN, TemplateAttributeType.TEXT)
val email: Template
get() {
val sections = mutableListOf<TemplateSection>()
val mainSection = TemplateSection(mutableListOf<TemplateAttribute>().apply {
add(emailAddressAttribute)
add(urlAttribute)
add(passwordAttribute)
})
sections.add(mainSection)
return Template(
UUID.randomUUID(),
TemplateField.LABEL_EMAIL,
IconImage(IconImageStandard(IconImageStandard.EMAIL_ID)),
sections)
}
val wifi: Template
get() {
val sections = mutableListOf<TemplateSection>()
val mainSection = TemplateSection(mutableListOf<TemplateAttribute>().apply {
add(ssidAttribute)
add(passwordAttribute)
add(wirelessTypeAttribute)
})
sections.add(mainSection)
return Template(
UUID.randomUUID(),
TemplateField.LABEL_WIRELESS,
IconImage(IconImageStandard(IconImageStandard.WIRELESS_ID)),
sections)
}
val notes: Template
get() {
val sections = mutableListOf<TemplateSection>()
val mainSection = TemplateSection(mutableListOf<TemplateAttribute>().apply {
add(notesAttribute)
})
sections.add(mainSection)
return Template(
UUID.randomUUID(),
TemplateField.LABEL_NOTES,
IconImage(IconImageStandard(IconImageStandard.LIST_ID)),
sections)
}
val idCard: Template
get() {
val sections = mutableListOf<TemplateSection>()
val mainSection = TemplateSection(mutableListOf<TemplateAttribute>().apply {
add(numberAttribute)
add(nameAttribute)
add(placeOfIssueAttribute)
add(dateOfIssueAttribute)
add(expirationDateAttribute)
})
sections.add(mainSection)
return Template(
UUID.randomUUID(),
TemplateField.LABEL_ID_CARD,
IconImage(IconImageStandard(IconImageStandard.ID_CARD_ID)),
sections)
}
val creditCard: Template
get() {
val sections = mutableListOf<TemplateSection>()
val mainSection = TemplateSection(mutableListOf<TemplateAttribute>().apply {
add(numberAttribute)
add(cvvAttribute)
add(pinAttribute)
add(holderAttribute)
add(expirationDateAttribute)
})
sections.add(mainSection)
return Template(
UUID.randomUUID(),
TemplateField.LABEL_DEBIT_CREDIT_CARD,
IconImage(IconImageStandard(IconImageStandard.CREDIT_CARD_ID)),
sections)
}
val bank: Template
get() {
val sections = mutableListOf<TemplateSection>()
val mainSection = TemplateSection(mutableListOf<TemplateAttribute>().apply {
add(nameAttribute)
add(urlAttribute)
add(usernameAttribute)
add(passwordAttribute)
})
val ibanSection = TemplateSection(mutableListOf<TemplateAttribute>().apply {
add(holderAttribute)
add(bicAttribute)
add(ibanAttribute)
}, TemplateField.LABEL_ACCOUNT)
sections.add(mainSection)
sections.add(ibanSection)
return Template(
UUID.randomUUID(),
TemplateField.LABEL_BANK,
IconImage(IconImageStandard(IconImageStandard.DOLLAR_ID)),
sections)
}
val cryptocurrency: Template
get() {
val sections = mutableListOf<TemplateSection>()
val mainSection = TemplateSection(mutableListOf<TemplateAttribute>().apply {
add(tokenAttribute)
add(publicKeyAttribute)
add(privateKeyAttribute)
add(seedAttribute)
})
sections.add(mainSection)
return Template(
UUID.randomUUID(),
TemplateField.LABEL_CRYPTOCURRENCY,
IconImage(IconImageStandard(IconImageStandard.STAR_ID)),
sections)
}
}

Some files were not shown because too many files have changed in this diff Show more