diff --git a/README.md b/README.md
index 745b444c5..364fd3b59 100644
--- a/README.md
+++ b/README.md
@@ -109,3 +109,24 @@ Other questions? You can read the [FAQ](https://github.com/Kunzisoft/KeePassDX/w
along with KeePassDX. If not, see .
*This project is a fork of [KeePassDroid](https://github.com/bpellin/keepassdroid) by bpellin.*
+
+
+## Credential Provider
+
+Use this version only for testing at your own risk.
+
+### requirements
+- Android 14 or up
+- enable 3rd party passkeys in chrome. For detail see https://1password.community/discussion/comment/711037/#Comment_711037
+- set KeepassDX in the Android setting Passwords & Accounts > Your Provider > Enable
+
+### working
+- sign in with ecdsa/rsa passkeys created by KeepassXC in Chrome. Tested with passkeys.io and webauthn.io.
+
+### maybe working
+- sign in with passkeys apps natively (without browser)
+
+### not working
+- create passkeys
+- user credential provider with username/password
+- open KeepassDX to unlock the database, if it is locked (currently a dummy entry with title unlock db is shown)
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
index 561308a84..b466e81e1 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -9,7 +9,7 @@ android {
defaultConfig {
applicationId "com.kunzisoft.keepass"
- minSdkVersion 15
+ minSdkVersion 19
targetSdkVersion 34
versionCode = 132
versionName = "4.1.0"
@@ -35,6 +35,10 @@ android {
}
}
+ buildFeatures {
+ buildConfig true
+ }
+
dependenciesInfo {
// Disables dependency metadata when building APKs.
includeInApk = false
@@ -98,6 +102,10 @@ android {
kotlinOptions {
jvmTarget = "1.8"
}
+
+ packaging {
+ resources.excludes.add("META-INF/versions/9/OSGI-INF/MANIFEST.MF") // necessary for bcpkix-jdk18on in crypto
+ }
}
def room_version = "2.5.1"
@@ -122,6 +130,7 @@ dependencies {
implementation "com.splitwise:tokenautocomplete:4.0.0-beta05"
// Database
implementation "androidx.room:room-runtime:$room_version"
+ implementation project(':crypto')
kapt "androidx.room:room-compiler:$room_version"
// Autofill
implementation "androidx.autofill:autofill:1.1.0"
@@ -136,6 +145,10 @@ dependencies {
implementation 'commons-codec:commons-codec:1.15'
// Password generator
implementation 'me.gosimple:nbvcxz:1.5.0'
+
+ // Credentials Provider
+ implementation "androidx.credentials:credentials:1.2.2"
+ implementation "androidx.credentials:credentials-play-services-auth:1.2.2"
// Modules import
implementation project(path: ':database')
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 070c5251c..5b881859b 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -44,7 +44,9 @@
android:largeHeap="true"
android:resizeableActivity="true"
android:theme="@style/KeepassDXStyle.Night"
- tools:targetApi="s">
+ tools:targetApi="s"
+ android:enableOnBackInvokedCallback="true">
+
@@ -199,6 +201,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/assets/trustedPackages.json b/app/src/main/assets/trustedPackages.json
new file mode 100644
index 000000000..263805e82
--- /dev/null
+++ b/app/src/main/assets/trustedPackages.json
@@ -0,0 +1,536 @@
+{
+ "apps": [
+ {
+ "type": "android",
+ "info": {
+ "package_name": "com.android.chrome",
+ "signatures": [
+ {
+ "build": "release",
+ "cert_fingerprint_sha256": "F0:FD:6C:5B:41:0F:25:CB:25:C3:B5:33:46:C8:97:2F:AE:30:F8:EE:74:11:DF:91:04:80:AD:6B:2D:60:DB:83"
+ },
+ {
+ "build": "userdebug",
+ "cert_fingerprint_sha256": "19:75:B2:F1:71:77:BC:89:A5:DF:F3:1F:9E:64:A6:CA:E2:81:A5:3D:C1:D1:D5:9B:1D:14:7F:E1:C8:2A:FA:00"
+ }
+ ]
+ }
+ },
+ {
+ "type": "android",
+ "info": {
+ "package_name": "com.chrome.beta",
+ "signatures": [
+ {
+ "build": "release",
+ "cert_fingerprint_sha256": "DA:63:3D:34:B6:9E:63:AE:21:03:B4:9D:53:CE:05:2F:C5:F7:F3:C5:3A:AB:94:FD:C2:A2:08:BD:FD:14:24:9C"
+ },
+ {
+ "build": "release",
+ "cert_fingerprint_sha256": "3D:7A:12:23:01:9A:A3:9D:9E:A0:E3:43:6A:B7:C0:89:6B:FB:4F:B6:79:F4:DE:5F:E7:C2:3F:32:6C:8F:99:4A"
+ }
+ ]
+ }
+ },
+ {
+ "type": "android",
+ "info": {
+ "package_name": "com.chrome.dev",
+ "signatures": [
+ {
+ "build": "release",
+ "cert_fingerprint_sha256": "90:44:EE:5F:EE:4B:BC:5E:21:DD:44:66:54:31:C4:EB:1F:1F:71:A3:27:16:A0:BC:92:7B:CB:B3:92:33:CA:BF"
+ },
+ {
+ "build": "release",
+ "cert_fingerprint_sha256": "3D:7A:12:23:01:9A:A3:9D:9E:A0:E3:43:6A:B7:C0:89:6B:FB:4F:B6:79:F4:DE:5F:E7:C2:3F:32:6C:8F:99:4A"
+ }
+ ]
+ }
+ },
+ {
+ "type": "android",
+ "info": {
+ "package_name": "com.chrome.canary",
+ "signatures": [
+ {
+ "build": "release",
+ "cert_fingerprint_sha256": "20:19:DF:A1:FB:23:EF:BF:70:C5:BC:D1:44:3C:5B:EA:B0:4F:3F:2F:F4:36:6E:9A:C1:E3:45:76:39:A2:4C:FC"
+ }
+ ]
+ }
+ },
+ {
+ "type": "android",
+ "info": {
+ "package_name": "org.chromium.chrome",
+ "signatures": [
+ {
+ "build": "release",
+ "cert_fingerprint_sha256": "C6:AD:B8:B8:3C:6D:4C:17:D2:92:AF:DE:56:FD:48:8A:51:D3:16:FF:8F:2C:11:C5:41:02:23:BF:F8:A7:DB:B3"
+ },
+ {
+ "build": "userdebug",
+ "cert_fingerprint_sha256": "19:75:B2:F1:71:77:BC:89:A5:DF:F3:1F:9E:64:A6:CA:E2:81:A5:3D:C1:D1:D5:9B:1D:14:7F:E1:C8:2A:FA:00"
+ }
+ ]
+ }
+ },
+ {
+ "type": "android",
+ "info": {
+ "package_name": "com.google.android.apps.chrome",
+ "signatures": [
+ {
+ "build": "userdebug",
+ "cert_fingerprint_sha256": "19:75:B2:F1:71:77:BC:89:A5:DF:F3:1F:9E:64:A6:CA:E2:81:A5:3D:C1:D1:D5:9B:1D:14:7F:E1:C8:2A:FA:00"
+ }
+ ]
+ }
+ },
+ {
+ "type": "android",
+ "info": {
+ "package_name": "org.mozilla.fennec_webauthndebug",
+ "signatures": [
+ {
+ "build": "userdebug",
+ "cert_fingerprint_sha256": "BD:AE:82:02:80:D2:AF:B7:74:94:EF:22:58:AA:78:A9:AE:A1:36:41:7E:8B:C2:3D:C9:87:75:2E:6F:48:E8:48"
+ }
+ ]
+ }
+ },
+ {
+ "type": "android",
+ "info": {
+ "package_name": "org.mozilla.firefox",
+ "signatures": [
+ {
+ "build": "release",
+ "cert_fingerprint_sha256": "A7:8B:62:A5:16:5B:44:94:B2:FE:AD:9E:76:A2:80:D2:2D:93:7F:EE:62:51:AE:CE:59:94:46:B2:EA:31:9B:04"
+ }
+ ]
+ }
+ },
+ {
+ "type": "android",
+ "info": {
+ "package_name": "org.mozilla.firefox_beta",
+ "signatures": [
+ {
+ "build": "release",
+ "cert_fingerprint_sha256": "A7:8B:62:A5:16:5B:44:94:B2:FE:AD:9E:76:A2:80:D2:2D:93:7F:EE:62:51:AE:CE:59:94:46:B2:EA:31:9B:04"
+ }
+ ]
+ }
+ },
+ {
+ "type": "android",
+ "info": {
+ "package_name": "org.mozilla.focus",
+ "signatures": [
+ {
+ "build": "release",
+ "cert_fingerprint_sha256": "62:03:A4:73:BE:36:D6:4E:E3:7F:87:FA:50:0E:DB:C7:9E:AB:93:06:10:AB:9B:9F:A4:CA:7D:5C:1F:1B:4F:FC"
+ }
+ ]
+ }
+ },
+ {
+ "type": "android",
+ "info": {
+ "package_name": "org.mozilla.fennec_aurora",
+ "signatures": [
+ {
+ "build": "release",
+ "cert_fingerprint_sha256": "BC:04:88:83:8D:06:F4:CA:6B:F3:23:86:DA:AB:0D:D8:EB:CF:3E:77:30:78:74:59:F6:2F:B3:CD:14:A1:BA:AA"
+ }
+ ]
+ }
+ },
+ {
+ "type": "android",
+ "info": {
+ "package_name": "org.mozilla.rocket",
+ "signatures": [
+ {
+ "build": "release",
+ "cert_fingerprint_sha256": "86:3A:46:F0:97:39:32:B7:D0:19:9B:54:91:12:74:1C:2D:27:31:AC:72:EA:11:B7:52:3A:A9:0A:11:BF:56:91"
+ }
+ ]
+ }
+ },
+ {
+ "type": "android",
+ "info": {
+ "package_name": "com.microsoft.emmx.canary",
+ "signatures": [
+ {
+ "build": "release",
+ "cert_fingerprint_sha256": "01:E1:99:97:10:A8:2C:27:49:B4:D5:0C:44:5D:C8:5D:67:0B:61:36:08:9D:0A:76:6A:73:82:7C:82:A1:EA:C9"
+ }
+ ]
+ }
+ },
+ {
+ "type": "android",
+ "info": {
+ "package_name": "com.microsoft.emmx.dev",
+ "signatures": [
+ {
+ "build": "release",
+ "cert_fingerprint_sha256": "01:E1:99:97:10:A8:2C:27:49:B4:D5:0C:44:5D:C8:5D:67:0B:61:36:08:9D:0A:76:6A:73:82:7C:82:A1:EA:C9"
+ }
+ ]
+ }
+ },
+ {
+ "type": "android",
+ "info": {
+ "package_name": "com.microsoft.emmx.beta",
+ "signatures": [
+ {
+ "build": "release",
+ "cert_fingerprint_sha256": "01:E1:99:97:10:A8:2C:27:49:B4:D5:0C:44:5D:C8:5D:67:0B:61:36:08:9D:0A:76:6A:73:82:7C:82:A1:EA:C9"
+ }
+ ]
+ }
+ },
+ {
+ "type": "android",
+ "info": {
+ "package_name": "com.microsoft.emmx",
+ "signatures": [
+ {
+ "build": "release",
+ "cert_fingerprint_sha256": "01:E1:99:97:10:A8:2C:27:49:B4:D5:0C:44:5D:C8:5D:67:0B:61:36:08:9D:0A:76:6A:73:82:7C:82:A1:EA:C9"
+ }
+ ]
+ }
+ },
+ {
+ "type": "android",
+ "info": {
+ "package_name": "com.microsoft.emmx.rolling",
+ "signatures": [
+ {
+ "build": "userdebug",
+ "cert_fingerprint_sha256": "32:A2:FC:74:D7:31:10:58:59:E5:A8:5D:F1:6D:95:F1:02:D8:5B:22:09:9B:80:64:C5:D8:91:5C:61:DA:D1:E0"
+ }
+ ]
+ }
+ },
+ {
+ "type": "android",
+ "info": {
+ "package_name": "com.microsoft.emmx.local",
+ "signatures": [
+ {
+ "build": "userdebug",
+ "cert_fingerprint_sha256": "32:A2:FC:74:D7:31:10:58:59:E5:A8:5D:F1:6D:95:F1:02:D8:5B:22:09:9B:80:64:C5:D8:91:5C:61:DA:D1:E0"
+ }
+ ]
+ }
+ },
+ {
+ "type": "android",
+ "info": {
+ "package_name": "com.brave.browser",
+ "signatures": [
+ {
+ "build": "release",
+ "cert_fingerprint_sha256": "9C:2D:B7:05:13:51:5F:DB:FB:BC:58:5B:3E:DF:3D:71:23:D4:DC:67:C9:4F:FD:30:63:61:C1:D7:9B:BF:18:AC"
+ }
+ ]
+ }
+ },
+ {
+ "type": "android",
+ "info": {
+ "package_name": "com.brave.browser_beta",
+ "signatures": [
+ {
+ "build": "release",
+ "cert_fingerprint_sha256": "9C:2D:B7:05:13:51:5F:DB:FB:BC:58:5B:3E:DF:3D:71:23:D4:DC:67:C9:4F:FD:30:63:61:C1:D7:9B:BF:18:AC"
+ }
+ ]
+ }
+ },
+ {
+ "type": "android",
+ "info": {
+ "package_name": "com.brave.browser_nightly",
+ "signatures": [
+ {
+ "build": "release",
+ "cert_fingerprint_sha256": "9C:2D:B7:05:13:51:5F:DB:FB:BC:58:5B:3E:DF:3D:71:23:D4:DC:67:C9:4F:FD:30:63:61:C1:D7:9B:BF:18:AC"
+ }
+ ]
+ }
+ },
+ {
+ "type": "android",
+ "info": {
+ "package_name": "app.vanadium.browser",
+ "signatures": [
+ {
+ "build": "release",
+ "cert_fingerprint_sha256": "C6:AD:B8:B8:3C:6D:4C:17:D2:92:AF:DE:56:FD:48:8A:51:D3:16:FF:8F:2C:11:C5:41:02:23:BF:F8:A7:DB:B3"
+ }
+ ]
+ }
+ },
+ {
+ "type": "android",
+ "info": {
+ "package_name": "com.vivaldi.browser",
+ "signatures": [
+ {
+ "build": "release",
+ "cert_fingerprint_sha256": "E8:A7:85:44:65:5B:A8:C0:98:17:F7:32:76:8F:56:89:B1:66:2E:C4:B2:BC:5A:0B:C0:EC:13:8D:33:CA:3D:1E"
+ }
+ ]
+ }
+ },
+ {
+ "type": "android",
+ "info": {
+ "package_name": "com.vivaldi.browser.snapshot",
+ "signatures": [
+ {
+ "build": "release",
+ "cert_fingerprint_sha256": "E8:A7:85:44:65:5B:A8:C0:98:17:F7:32:76:8F:56:89:B1:66:2E:C4:B2:BC:5A:0B:C0:EC:13:8D:33:CA:3D:1E"
+ }
+ ]
+ }
+ },
+ {
+ "type": "android",
+ "info": {
+ "package_name": "com.vivaldi.browser.sopranos",
+ "signatures": [
+ {
+ "build": "release",
+ "cert_fingerprint_sha256": "E8:A7:85:44:65:5B:A8:C0:98:17:F7:32:76:8F:56:89:B1:66:2E:C4:B2:BC:5A:0B:C0:EC:13:8D:33:CA:3D:1E"
+ }
+ ]
+ }
+ },
+ {
+ "type": "android",
+ "info": {
+ "package_name": "com.citrix.Receiver",
+ "signatures": [
+ {
+ "build": "release",
+ "cert_fingerprint_sha256": "3D:D1:12:67:10:69:AB:36:4E:F9:BE:73:9A:B7:B5:EE:15:E1:CD:E9:D8:75:7B:1B:F0:64:F5:0C:55:68:9A:49"
+ },
+ {
+ "build": "release",
+ "cert_fingerprint_sha256": "CE:B2:23:D7:77:09:F2:B6:BC:0B:3A:78:36:F5:A5:AF:4C:E1:D3:55:F4:A7:28:86:F7:9D:F8:0D:C9:D6:12:2E"
+ },
+ {
+ "build": "release",
+ "cert_fingerprint_sha256": "AA:D0:D4:57:E6:33:C3:78:25:77:30:5B:C1:B2:D9:E3:81:41:C7:21:DF:0D:AA:6E:29:07:2F:C4:1D:34:F0:AB"
+ }
+ ]
+ }
+ },
+ {
+ "type": "android",
+ "info": {
+ "package_name": "com.android.browser",
+ "signatures": [
+ {
+ "build": "release",
+ "cert_fingerprint_sha256": "C9:00:9D:01:EB:F9:F5:D0:30:2B:C7:1B:2F:E9:AA:9A:47:A4:32:BB:A1:73:08:A3:11:1B:75:D7:B2:14:90:25"
+ }
+ ]
+ }
+ },
+ {
+ "type": "android",
+ "info": {
+ "package_name": "com.sec.android.app.sbrowser",
+ "signatures": [
+ {
+ "build": "release",
+ "cert_fingerprint_sha256": "C8:A2:E9:BC:CF:59:7C:2F:B6:DC:66:BE:E2:93:FC:13:F2:FC:47:EC:77:BC:6B:2B:0D:52:C1:1F:51:19:2A:B8"
+ },
+ {
+ "build": "release",
+ "cert_fingerprint_sha256": "34:DF:0E:7A:9F:1C:F1:89:2E:45:C0:56:B4:97:3C:D8:1C:CF:14:8A:40:50:D1:1A:EA:4A:C5:A6:5F:90:0A:42"
+ }
+ ]
+ }
+ },
+ {
+ "type": "android",
+ "info": {
+ "package_name": "com.sec.android.app.sbrowser.beta",
+ "signatures": [
+ {
+ "build": "release",
+ "cert_fingerprint_sha256": "C8:A2:E9:BC:CF:59:7C:2F:B6:DC:66:BE:E2:93:FC:13:F2:FC:47:EC:77:BC:6B:2B:0D:52:C1:1F:51:19:2A:B8"
+ },
+ {
+ "build": "release",
+ "cert_fingerprint_sha256": "34:DF:0E:7A:9F:1C:F1:89:2E:45:C0:56:B4:97:3C:D8:1C:CF:14:8A:40:50:D1:1A:EA:4A:C5:A6:5F:90:0A:42"
+ }
+ ]
+ }
+ },
+ {
+ "type": "android",
+ "info": {
+ "package_name": "com.google.android.gms",
+ "signatures": [
+ {
+ "build": "release",
+ "cert_fingerprint_sha256": "7C:E8:3C:1B:71:F3:D5:72:FE:D0:4C:8D:40:C5:CB:10:FF:75:E6:D8:7D:9D:F6:FB:D5:3F:04:68:C2:90:50:53"
+ },
+ {
+ "build": "release",
+ "cert_fingerprint_sha256": "D2:2C:C5:00:29:9F:B2:28:73:A0:1A:01:0D:E1:C8:2F:BE:4D:06:11:19:B9:48:14:DD:30:1D:AB:50:CB:76:78"
+ },
+ {
+ "build": "release",
+ "cert_fingerprint_sha256": "F0:FD:6C:5B:41:0F:25:CB:25:C3:B5:33:46:C8:97:2F:AE:30:F8:EE:74:11:DF:91:04:80:AD:6B:2D:60:DB:83"
+ },
+ {
+ "build": "release",
+ "cert_fingerprint_sha256": "19:75:B2:F1:71:77:BC:89:A5:DF:F3:1F:9E:64:A6:CA:E2:81:A5:3D:C1:D1:D5:9B:1D:14:7F:E1:C8:2A:FA:00"
+ }
+ ]
+ }
+ },
+ {
+ "type": "android",
+ "info": {
+ "package_name": "com.yandex.browser",
+ "signatures": [
+ {
+ "build": "release",
+ "cert_fingerprint_sha256": "AC:A4:05:DE:D8:B2:5C:B2:E8:C6:DA:69:42:5D:2B:43:07:D0:87:C1:27:6F:C0:6A:D5:94:27:31:CC:C5:1D:BA"
+ }
+ ]
+ }
+ },
+ {
+ "type": "android",
+ "info": {
+ "package_name": "com.yandex.browser.beta",
+ "signatures": [
+ {
+ "build": "release",
+ "cert_fingerprint_sha256": "AC:A4:05:DE:D8:B2:5C:B2:E8:C6:DA:69:42:5D:2B:43:07:D0:87:C1:27:6F:C0:6A:D5:94:27:31:CC:C5:1D:BA"
+ }
+ ]
+ }
+ },
+ {
+ "type": "android",
+ "info": {
+ "package_name": "com.yandex.browser.alpha",
+ "signatures": [
+ {
+ "build": "release",
+ "cert_fingerprint_sha256": "AC:A4:05:DE:D8:B2:5C:B2:E8:C6:DA:69:42:5D:2B:43:07:D0:87:C1:27:6F:C0:6A:D5:94:27:31:CC:C5:1D:BA"
+ }
+ ]
+ }
+ },
+ {
+ "type": "android",
+ "info": {
+ "package_name": "com.yandex.browser.corp",
+ "signatures": [
+ {
+ "build": "release",
+ "cert_fingerprint_sha256": "AC:A4:05:DE:D8:B2:5C:B2:E8:C6:DA:69:42:5D:2B:43:07:D0:87:C1:27:6F:C0:6A:D5:94:27:31:CC:C5:1D:BA"
+ }
+ ]
+ }
+ },
+ {
+ "type": "android",
+ "info": {
+ "package_name": "com.yandex.browser.canary",
+ "signatures": [
+ {
+ "build": "release",
+ "cert_fingerprint_sha256": "1D:A9:CB:AE:2D:CC:C6:A5:8D:6C:94:7B:E9:4C:DB:B7:33:D6:5D:A4:D1:77:0F:A1:4A:53:64:CB:4A:28:EB:49"
+ }
+ ]
+ }
+ },
+ {
+ "type": "android",
+ "info": {
+ "package_name": "com.yandex.browser.broteam",
+ "signatures": [
+ {
+ "build": "release",
+ "cert_fingerprint_sha256": "1D:A9:CB:AE:2D:CC:C6:A5:8D:6C:94:7B:E9:4C:DB:B7:33:D6:5D:A4:D1:77:0F:A1:4A:53:64:CB:4A:28:EB:49"
+ }
+ ]
+ }
+ },
+ {
+ "type": "android",
+ "info": {
+ "package_name": "com.talonsec.talon",
+ "signatures": [
+ {
+ "build": "release",
+ "cert_fingerprint_sha256": "A3:66:03:44:A6:F6:AF:CA:81:8C:BF:43:96:A2:3C:CF:D5:ED:7A:78:1B:B4:A3:D1:85:03:01:E2:F4:6D:23:83"
+ },
+ {
+ "build": "release",
+ "cert_fingerprint_sha256": "E2:A5:64:74:EA:23:7B:06:67:B6:F5:2C:DC:E9:04:5E:24:88:3B:AE:D0:82:59:9A:A2:DF:0B:60:3A:CF:6A:3B"
+ }
+ ]
+ }
+ },
+ {
+ "type": "android",
+ "info": {
+ "package_name": "com.talonsec.talon_beta",
+ "signatures": [
+ {
+ "build": "release",
+ "cert_fingerprint_sha256": "F5:86:62:7A:32:C8:9F:E6:7E:00:6D:B1:8C:34:31:9E:01:7F:B3:B2:BE:D6:9D:01:01:B7:F9:43:E7:7C:48:AE"
+ },
+ {
+ "build": "release",
+ "cert_fingerprint_sha256": "9A:A1:25:D5:E5:5E:3F:B0:DE:96:72:D9:A9:5D:04:65:3F:49:4A:1E:C3:EE:76:1E:94:C4:4E:5D:2F:65:8E:2F"
+ }
+ ]
+ }
+ },
+ {
+ "type": "android",
+ "info": {
+ "package_name": "com.duckduckgo.mobile.android.debug",
+ "signatures": [
+ {
+ "build": "release",
+ "cert_fingerprint_sha256": "C4:F0:9E:2B:D7:25:AD:F5:AD:92:0B:A2:80:27:66:AC:16:4A:C1:53:B3:EA:9E:08:48:B0:57:98:37:F7:6A:29"
+ }
+ ]
+ }
+ },
+ {
+ "type": "android",
+ "info": {
+ "package_name": "com.duckduckgo.mobile.android",
+ "signatures": [
+ {
+ "build": "release",
+ "cert_fingerprint_sha256": "BB:7B:B3:1C:57:3C:46:A1:DA:7F:C5:C5:28:A6:AC:F4:32:10:84:56:FE:EC:50:81:0C:7F:33:69:4E:B3:D2:D4"
+ }
+ ]
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/EntryActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/EntryActivity.kt
index 5da1544b8..75bc8c5f0 100644
--- a/app/src/main/java/com/kunzisoft/keepass/activities/EntryActivity.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/activities/EntryActivity.kt
@@ -163,9 +163,9 @@ class EntryActivity : DatabaseLockActivity() {
toolbar?.title = " "
// Retrieve the textColor to tint the toolbar
- val taColorSecondary = theme.obtainStyledAttributes(intArrayOf(R.attr.colorSecondary))
- val taColorSurface = theme.obtainStyledAttributes(intArrayOf(R.attr.colorSurface))
- val taColorOnSurface = theme.obtainStyledAttributes(intArrayOf(R.attr.colorOnSurface))
+ val taColorSecondary = theme.obtainStyledAttributes(intArrayOf(com.google.android.material.R.attr.colorSecondary))
+ val taColorSurface = theme.obtainStyledAttributes(intArrayOf(com.google.android.material.R.attr.colorSurface))
+ val taColorOnSurface = theme.obtainStyledAttributes(intArrayOf(com.google.android.material.R.attr.colorOnSurface))
val taColorBackground = theme.obtainStyledAttributes(intArrayOf(android.R.attr.windowBackground))
mColorSecondary = taColorSecondary.getColor(0, Color.BLACK)
mColorSurface = taColorSurface.getColor(0, Color.BLACK)
diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/DatabaseDialogFragment.kt b/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/DatabaseDialogFragment.kt
index 3c83c7750..1050ba7b8 100644
--- a/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/DatabaseDialogFragment.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/DatabaseDialogFragment.kt
@@ -30,6 +30,7 @@ abstract class DatabaseDialogFragment : DialogFragment(), DatabaseRetrieval {
}
@Suppress("DEPRECATION")
+ @Deprecated(message = "")
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/GroupDialogFragment.kt b/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/GroupDialogFragment.kt
index 593a99d6f..6a41fb3bb 100644
--- a/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/GroupDialogFragment.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/GroupDialogFragment.kt
@@ -108,7 +108,7 @@ class GroupDialogFragment : DatabaseDialogFragment() {
uuidReferenceView = root.findViewById(R.id.group_UUID_reference)
// Retrieve the textColor to tint the icon
- val ta = activity.theme.obtainStyledAttributes(intArrayOf(R.attr.colorSecondary))
+ val ta = activity.theme.obtainStyledAttributes(intArrayOf(com.google.android.material.R.attr.colorSecondary))
mIconColor = ta.getColor(0, Color.WHITE)
ta.recycle()
diff --git a/app/src/main/java/com/kunzisoft/keepass/adapters/BreadcrumbAdapter.kt b/app/src/main/java/com/kunzisoft/keepass/adapters/BreadcrumbAdapter.kt
index 6b0dafb2b..1e073e8ae 100644
--- a/app/src/main/java/com/kunzisoft/keepass/adapters/BreadcrumbAdapter.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/adapters/BreadcrumbAdapter.kt
@@ -40,7 +40,7 @@ class BreadcrumbAdapter(val context: Context)
mShowUUID = PreferencesUtil.showUUID(context)
// Retrieve the color to tint the icon
- val taIconColor = context.theme.obtainStyledAttributes(intArrayOf(R.attr.colorOnSurface))
+ val taIconColor = context.theme.obtainStyledAttributes(intArrayOf(com.google.android.material.R.attr.colorOnSurface))
mIconColor = taIconColor.getColor(0, Color.WHITE)
taIconColor.recycle()
}
diff --git a/app/src/main/java/com/kunzisoft/keepass/adapters/NodesAdapter.kt b/app/src/main/java/com/kunzisoft/keepass/adapters/NodesAdapter.kt
index fc47ca14f..9961a8675 100644
--- a/app/src/main/java/com/kunzisoft/keepass/adapters/NodesAdapter.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/adapters/NodesAdapter.kt
@@ -118,7 +118,7 @@ class NodesAdapter (
this.mNodeSortedListCallback = NodeSortedListCallback()
this.mNodeSortedList = SortedList(Node::class.java, mNodeSortedListCallback)
- val taColorSurfaceContainer = context.obtainStyledAttributes(intArrayOf(R.attr.colorSurfaceContainer))
+ val taColorSurfaceContainer = context.obtainStyledAttributes(intArrayOf(com.google.android.material.R.attr.colorSurfaceContainer))
this.mColorSurfaceContainer = taColorSurfaceContainer.getColor(0, Color.BLACK)
taColorSurfaceContainer.recycle()
// Retrieve the color to tint the icon
@@ -134,11 +134,11 @@ class NodesAdapter (
this.mTextColorSecondary = taTextColorSecondary.getColor(0, Color.BLACK)
taTextColorSecondary.recycle()
// To get background color for selection
- val taColorSecondary = context.obtainStyledAttributes(intArrayOf(R.attr.colorSecondary))
+ val taColorSecondary = context.obtainStyledAttributes(intArrayOf(com.google.android.material.R.attr.colorSecondary))
this.mColorSecondary = taColorSecondary.getColor(0, Color.GRAY)
taColorSecondary.recycle()
// To get text color for selection
- val taColorOnSecondary = context.obtainStyledAttributes(intArrayOf(R.attr.colorOnSecondary))
+ val taColorOnSecondary = context.obtainStyledAttributes(intArrayOf(com.google.android.material.R.attr.colorOnSecondary))
this.mColorOnSecondary = taColorOnSecondary.getColor(0, Color.WHITE)
taColorOnSecondary.recycle()
}
diff --git a/app/src/main/java/com/kunzisoft/keepass/credentialprovider/CredentialProviderActivity.kt b/app/src/main/java/com/kunzisoft/keepass/credentialprovider/CredentialProviderActivity.kt
new file mode 100644
index 000000000..62d20ea7e
--- /dev/null
+++ b/app/src/main/java/com/kunzisoft/keepass/credentialprovider/CredentialProviderActivity.kt
@@ -0,0 +1,185 @@
+package com.kunzisoft.keepass.credentialprovider
+
+import android.annotation.SuppressLint
+import android.content.Intent
+import android.os.Build
+import android.os.Bundle
+import android.util.Log
+import androidx.annotation.RequiresApi
+import androidx.biometric.BiometricManager
+import androidx.credentials.GetCredentialResponse
+import androidx.credentials.GetPasswordOption
+import androidx.credentials.GetPublicKeyCredentialOption
+import androidx.credentials.PublicKeyCredential
+import androidx.credentials.provider.CallingAppInfo
+import androidx.credentials.provider.PendingIntentHandler
+import androidx.credentials.webauthn.MyAuthenticatorAssertionResponse
+import androidx.credentials.webauthn.FidoPublicKeyCredential
+import androidx.credentials.webauthn.PublicKeyCredentialRequestOptions
+import com.kunzisoft.signature.Signature
+import com.kunzisoft.keepass.activities.legacy.DatabaseActivity
+import com.kunzisoft.keepass.database.ContextualDatabase
+import org.apache.commons.codec.binary.Base64
+import androidx.biometric.BiometricPrompt;
+import com.kunzisoft.keepass.R
+
+@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+class CredentialProviderActivity : DatabaseActivity() {
+
+ @SuppressLint("RestrictedApi")
+ private fun validatePasskey(requestJson: String, origin: String, packageName: String, uid: ByteArray, username: Any, credId: ByteArray, privateKey: String) {
+
+ val request = PublicKeyCredentialRequestOptions(requestJson)
+
+ // https://www.w3.org/TR/webauthn-3/#authdata-flags
+ val userPresent = true
+ val userVerified = true
+ val backupEligibility = true
+ val backupState = true
+ val response = MyAuthenticatorAssertionResponse(
+ requestOptions = request,
+ credentialId = credId,
+ origin = origin,
+ up = userPresent,
+ uv = userVerified,
+ be = backupEligibility,
+ bs = backupState,
+ userHandle = uid
+ )
+
+ val messageToSign = response.dataToSign()
+
+ val sig = Signature.sign(privateKey, messageToSign)
+
+ response.signature = sig
+
+ val credential = FidoPublicKeyCredential(
+ rawId = credId, response = response, authenticatorAttachment = "platform"
+ )
+ val result = Intent()
+
+ val cJson = credential.json()
+ Log.w("", cJson)
+ val passkeyCredential = PublicKeyCredential(cJson)
+ PendingIntentHandler.setGetCredentialResponse(
+ result, GetCredentialResponse(passkeyCredential)
+ )
+ setResult(RESULT_OK, result)
+ finish()
+ }
+
+ private fun b64Decode(encodedString: String?): ByteArray {
+ return Base64.decodeBase64(encodedString)
+ }
+
+ private fun cleanUp() {
+ setResult(RESULT_CANCELED)
+ finish()
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ }
+
+ override fun onDatabaseRetrieved(database: ContextualDatabase?) {
+ Log.d(javaClass.simpleName,"onDatabaseRetrieved called: database = $database")
+ super.onDatabaseRetrieved(database)
+
+ val getRequest =
+ PendingIntentHandler.retrieveProviderGetCredentialRequest(intent)
+
+ if (getRequest?.credentialOptions?.size != 1) {
+ throw Exception("not exact 1 credentialOption")
+ }
+
+ when (val credOption = getRequest.credentialOptions[0]) {
+ is GetPublicKeyCredentialOption -> handlePublicKeyCredOption(credOption, getRequest.callingAppInfo)
+ is GetPasswordOption -> handlePasswordOption(credOption)
+ else -> throw Exception("unknown type of credentialOption")
+ }
+
+ Log.d(javaClass.simpleName, "onDatabaseRetrieved finished")
+
+ }
+ private fun handlePublicKeyCredOption(publicKeyRequest: GetPublicKeyCredentialOption, callingAppInfo: CallingAppInfo) {
+
+ val requestInfo = intent.getBundleExtra(KeePassDXCredentialProviderService.INTENT_EXTRA_KEY)
+ val nodeId = requestInfo?.getString(KeePassDXCredentialProviderService.NODE_ID_KEY)
+
+ Log.d(javaClass.simpleName, "nodeId = $nodeId")
+
+ if (mDatabase == null || nodeId == null) {
+ cleanUp()
+ return
+ }
+ val passkey = PasskeyUtil.searchPassKeyByNodeId(mDatabase!!, nodeId)
+
+
+ if (passkey == null) {
+ cleanUp()
+ return
+ }
+ Log.d(javaClass.simpleName, "passkey found")
+
+ val credId = b64Decode(passkey.credId)
+ val privateKey = passkey.privateKeyPem
+ val uid = b64Decode(passkey.userHandle)
+
+ val origin = appInfoToOrigin(callingAppInfo)
+
+ Log.d(javaClass.simpleName, "origin = $origin")
+ val packageName = callingAppInfo.packageName
+
+ val biometricPrompt = BiometricPrompt(
+ this,
+ object : BiometricPrompt.AuthenticationCallback() {
+ override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
+ super.onAuthenticationError(errorCode, errString)
+ cleanUp()
+ }
+
+ override fun onAuthenticationFailed() {
+ super.onAuthenticationFailed()
+ cleanUp()
+ }
+
+ override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
+ super.onAuthenticationSucceeded(result)
+ validatePasskey(
+ publicKeyRequest.requestJson,
+ origin!!,
+ packageName,
+ uid,
+ passkey.username,
+ credId,
+ privateKey
+ )
+ }
+ }
+ )
+
+ val title = getString(R.string.passkey_biometric_prompt_title)
+ val subtitle = getString(R.string.passkey_biometric_prompt_subtitle, origin)
+ val negativeButtonText = getString(R.string.passkey_biometric_prompt_negative_button_text)
+ val promptInfo = BiometricPrompt.PromptInfo.Builder()
+ .setTitle(title)
+ .setSubtitle(subtitle)
+ .setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG)
+ .setNegativeButtonText(negativeButtonText)
+ .build()
+ biometricPrompt.authenticate(promptInfo)
+ }
+
+ private fun handlePasswordOption(passwordOption: GetPasswordOption) {
+ // TODO
+ }
+
+ private fun appInfoToOrigin(callingAppInfo: CallingAppInfo): String? {
+ val privilegedAllowlist = assets.open("trustedPackages.json").bufferedReader().use {
+ it.readText()
+ }
+ return callingAppInfo.getOrigin(privilegedAllowlist)?.removeSuffix("/")
+ }
+
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/kunzisoft/keepass/credentialprovider/KeePassDXCredentialProviderService.kt b/app/src/main/java/com/kunzisoft/keepass/credentialprovider/KeePassDXCredentialProviderService.kt
new file mode 100644
index 000000000..eaaba0c35
--- /dev/null
+++ b/app/src/main/java/com/kunzisoft/keepass/credentialprovider/KeePassDXCredentialProviderService.kt
@@ -0,0 +1,179 @@
+package com.kunzisoft.keepass.credentialprovider;
+
+import android.app.PendingIntent
+import android.content.Intent
+import android.os.Bundle
+import android.os.CancellationSignal;
+import android.os.OutcomeReceiver;
+import android.provider.ContactsContract.Directory.PACKAGE_NAME
+
+import androidx.annotation.RequiresApi
+import androidx.credentials.exceptions.ClearCredentialException
+import androidx.credentials.exceptions.CreateCredentialException
+import androidx.credentials.exceptions.GetCredentialException;
+import androidx.credentials.exceptions.GetCredentialUnknownException
+import androidx.credentials.provider.BeginCreateCredentialRequest
+import androidx.credentials.provider.BeginCreateCredentialResponse
+import androidx.credentials.provider.BeginGetCredentialRequest
+import androidx.credentials.provider.BeginGetCredentialResponse;
+import androidx.credentials.provider.BeginGetPasswordOption
+import androidx.credentials.provider.BeginGetPublicKeyCredentialOption
+import androidx.credentials.provider.CallingAppInfo
+import androidx.credentials.provider.CredentialEntry
+import androidx.credentials.provider.CredentialProviderService
+import androidx.credentials.provider.ProviderClearCredentialStateRequest
+import androidx.credentials.provider.PublicKeyCredentialEntry
+import org.json.JSONObject
+
+import android.util.Log
+import com.kunzisoft.keepass.database.DatabaseTaskProvider
+import com.kunzisoft.keepass.database.element.Database
+import com.kunzisoft.keepass.database.search.SearchHelper
+import com.kunzisoft.keepass.database.search.SearchParameters
+
+@RequiresApi(value = 34)
+class KeePassDXCredentialProviderService : CredentialProviderService() {
+
+ private var mDatabaseTaskProvider: DatabaseTaskProvider? = null
+ private var mDatabase: Database? = null
+
+ override fun onCreate() {
+ super.onCreate()
+
+ mDatabaseTaskProvider = DatabaseTaskProvider(this)
+ mDatabaseTaskProvider?.registerProgressTask()
+ mDatabaseTaskProvider?.onDatabaseRetrieved = { database ->
+ this.mDatabase = database
+ }
+ }
+
+ override fun onDestroy() {
+ mDatabaseTaskProvider?.unregisterProgressTask()
+ super.onDestroy()
+ }
+
+ override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
+ return super.onStartCommand(intent, flags, startId)
+ }
+
+ override fun onBeginCreateCredentialRequest(request: BeginCreateCredentialRequest, cancellationSignal: CancellationSignal, callback: OutcomeReceiver) {
+ TODO("Not yet implemented")
+
+ }
+
+ override fun onBeginGetCredentialRequest(
+ request: BeginGetCredentialRequest,
+ cancellationSignal: CancellationSignal,
+ callback: OutcomeReceiver,
+ ) {
+ /*
+ val unlockEntryTitle = "Authenticate to continue"
+ if (isAppLocked()) {
+ callback.onResult(BeginGetCredentialResponse(
+ authenticationActions = mutableListOf(AuthenticationAction(
+ unlockEntryTitle, createUnlockPendingIntent())
+ )
+ )
+ )
+ return
+ }
+ */
+ try {
+ val response = processGetCredentialsRequest(request)
+ callback.onResult(response)
+ } catch (e: GetCredentialException) {
+ callback.onError(GetCredentialUnknownException())
+ }
+ }
+
+ companion object {
+ private const val GET_PASSKEY_INTENT_ACTION = "com.kunzisoft.keepass.credentialprovider.GET_PASSKEY"
+ private const val GET_PASSWORD_INTENT_ACTION = "com.kunzisoft.keepass.credentialprovider.GET_PASSWORD"
+
+ const val NODE_ID_KEY = "nodeId"
+ const val INTENT_EXTRA_KEY = "CREDENTIAL_DATA"
+ }
+
+ private fun processGetCredentialsRequest(
+ request: BeginGetCredentialRequest
+ ): BeginGetCredentialResponse {
+
+ val callingAppInfo = request.callingAppInfo ?: throw Exception("callingAppInfo is null")
+ val credentialEntries: MutableList = mutableListOf()
+
+ for (option in request.beginGetCredentialOptions) {
+ when (option) {
+ is BeginGetPasswordOption -> {
+ // TODO
+ }
+ is BeginGetPublicKeyCredentialOption -> {
+ credentialEntries.addAll(
+ populatePasskeyData(callingAppInfo, option)
+ )
+ } else -> {
+ Log.d(javaClass.simpleName,"Request not supported")
+ }
+ }
+ }
+ return BeginGetCredentialResponse(credentialEntries)
+ }
+
+ private fun populatePasskeyData(callingAppInfo: CallingAppInfo, option: BeginGetPublicKeyCredentialOption): List {
+
+ val json = JSONObject(option.requestJson)
+
+ val relyingPartyId = json.optString("rpId", "")
+
+ val passkeys = getCredentialsFromDb(relyingPartyId)
+
+ val passkeyEntries: MutableList = mutableListOf()
+ for (passkey in passkeys) {
+ val data = Bundle()
+ data.putString(NODE_ID_KEY, passkey.nodeId)
+ passkeyEntries.add(
+ PublicKeyCredentialEntry(
+ context = applicationContext,
+ username = passkey.username,
+ pendingIntent = createNewPendingIntent(
+ GET_PASSKEY_INTENT_ACTION,
+ data
+ ),
+ beginGetPublicKeyCredentialOption = option,
+ displayName = passkey.displayName,
+ lastUsedTime = passkey.lastUsedTime,
+ isAutoSelectAllowed = false
+ )
+ )
+ }
+ return passkeyEntries
+ }
+
+ private fun getCredentialsFromDb(relyingPartyId: String) : List {
+ if (mDatabase == null) {
+ // TODO make sure that the database is open
+ val dummyPassKey = PasskeyUtil.Passkey("", "unknown", "unlock db", "", "", "", "", null)
+ return listOf(dummyPassKey)
+ }
+ val passkeys = PasskeyUtil.searchPasskeys(mDatabase!!)
+ val passkeysMatching = passkeys.filter { p -> p.relyingParty == relyingPartyId }
+ return passkeysMatching
+ }
+
+
+ private fun createNewPendingIntent(action: String, extra: Bundle? = null): PendingIntent {
+ val intent = Intent(action).setPackage(PACKAGE_NAME).setClass(applicationContext, CredentialProviderActivity::class.java)
+ if (extra != null) {
+ intent.putExtra(INTENT_EXTRA_KEY, extra)
+ }
+ val requestCode = 42 // not used
+ return PendingIntent.getActivity(
+ applicationContext, requestCode, intent,
+ (PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)
+ )
+ }
+
+ override fun onClearCredentialStateRequest(request: ProviderClearCredentialStateRequest, cancellationSignal: CancellationSignal, callback: OutcomeReceiver) {
+ // nothing to do
+ }
+
+}
diff --git a/app/src/main/java/com/kunzisoft/keepass/credentialprovider/MyAuthenticatorAssertionResponse.kt b/app/src/main/java/com/kunzisoft/keepass/credentialprovider/MyAuthenticatorAssertionResponse.kt
new file mode 100644
index 000000000..d8536a26f
--- /dev/null
+++ b/app/src/main/java/com/kunzisoft/keepass/credentialprovider/MyAuthenticatorAssertionResponse.kt
@@ -0,0 +1,114 @@
+/*
+* Copyright 2023 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package androidx.credentials.webauthn
+
+import android.annotation.SuppressLint
+import android.util.Log
+import java.security.MessageDigest
+import org.json.JSONObject
+import org.apache.commons.codec.binary.Base64
+
+@SuppressLint("RestrictedApi")
+class MyAuthenticatorAssertionResponse(
+ private val requestOptions: PublicKeyCredentialRequestOptions,
+ private val credentialId: ByteArray,
+ private val origin: String,
+ private val up: Boolean,
+ private val uv: Boolean,
+ private val be: Boolean,
+ private val bs: Boolean,
+ private var userHandle: ByteArray,
+ private val packageName: String? = null,
+ private val clientDataHash: ByteArray? = null,
+) : AuthenticatorResponse {
+ override var clientJson = JSONObject()
+ var authenticatorData: ByteArray
+ var signature: ByteArray = byteArrayOf()
+
+ init {
+ clientJson.put("type", "webauthn.get")
+ clientJson.put("challenge", b64Encode(requestOptions.challenge))
+ clientJson.put("origin", origin)
+ clientJson.put("crossOrigin", false)
+ if (packageName != null) {
+ clientJson.put("androidPackageName", packageName)
+ }
+
+ authenticatorData = defaultAuthenticatorData()
+ }
+
+ fun defaultAuthenticatorData(): ByteArray {
+ val md = MessageDigest.getInstance("SHA-256")
+ val rpHash = md.digest(requestOptions.rpId.toByteArray())
+ var flags: Int = 0
+ if (up) {
+ flags = flags or 0x01
+ }
+ if (uv) {
+ flags = flags or 0x04
+ }
+ if (be) {
+ flags = flags or 0x08
+ }
+ if (bs) {
+ flags = flags or 0x10
+ }
+ val ret = rpHash + byteArrayOf(flags.toByte()) + byteArrayOf(0, 0, 0, 0)
+ return ret
+ }
+
+ fun dataToSign(): ByteArray {
+ val md = MessageDigest.getInstance("SHA-256")
+ var hash: ByteArray
+ if (clientDataHash != null) {
+ hash = clientDataHash
+ } else {
+ hash = md.digest(temp().toByteArray())
+ }
+
+ return authenticatorData + hash
+ }
+
+ override fun json(): JSONObject {
+
+ val clientJsonTemp = temp()
+ Log.w("", clientJsonTemp)
+ val clientData = clientJsonTemp.toByteArray()
+ val response = JSONObject()
+ if (clientDataHash == null) {
+ response.put("clientDataJSON", b64Encode(clientData))
+ }
+ response.put("authenticatorData", b64Encode(authenticatorData))
+ response.put("signature", b64Encode(signature))
+ response.put("userHandle", b64Encode(userHandle))
+ return response
+ }
+
+ fun temp(): String {
+ val clientJsonTemp = clientJson.toString()
+ val clientJsonGood = clientJsonTemp.replace("\\/", "/")
+ return clientJsonGood
+ }
+
+ private fun b64Decode(encodedString: String?): ByteArray {
+ return Base64.decodeBase64(encodedString)
+ }
+
+ private fun b64Encode(binData: ByteArray?): String {
+ return Base64.encodeBase64URLSafeString(binData)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/kunzisoft/keepass/credentialprovider/PasskeyUtil.kt b/app/src/main/java/com/kunzisoft/keepass/credentialprovider/PasskeyUtil.kt
new file mode 100644
index 000000000..c1b578c01
--- /dev/null
+++ b/app/src/main/java/com/kunzisoft/keepass/credentialprovider/PasskeyUtil.kt
@@ -0,0 +1,93 @@
+package com.kunzisoft.keepass.credentialprovider
+
+import android.os.Build
+import com.kunzisoft.keepass.database.element.Database
+import com.kunzisoft.keepass.database.element.Entry
+import com.kunzisoft.keepass.database.element.node.NodeIdUUID
+import com.kunzisoft.keepass.database.search.SearchHelper
+import com.kunzisoft.keepass.database.search.SearchParameters
+import com.kunzisoft.keepass.utils.UuidUtil
+import java.time.Instant
+
+class PasskeyUtil {
+
+ data class Passkey(val nodeId: String, val username: String, val displayName: String, val privateKeyPem: String, val credId: String, val userHandle: String, val relyingParty: String, val lastUsedTime: Instant?)
+
+ companion object {
+
+ const val PASSKEY_TAG = "Passkey"
+ fun convertEntryToPasskey(entry: Entry): Passkey? {
+ if (!entry.tags.toList().contains(PASSKEY_TAG)) {
+ return null
+ }
+
+ val nodeId = UuidUtil.toHexString(entry.nodeId.id)!!
+
+ val displayName = entry.getVisualTitle()
+ val lastUsedTime = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ entry.lastAccessTime.date.toInstant()
+ } else {
+ null
+ }
+ var username = ""
+ var privateKeyPem = ""
+ var credId = ""
+ var userHandle = ""
+ var relyingParty = ""
+
+ for (field in entry.getExtraFields()) {
+ val fieldName = field.name
+
+ // field names from KeypassXC are used
+ if (fieldName == "KPEX_PASSKEY_USERNAME") {
+ username = field.protectedValue.stringValue
+ } else if (field.name == "KPEX_PASSKEY_PRIVATE_KEY_PEM") {
+ privateKeyPem = field.protectedValue.stringValue
+ } else if (field.name == "KPEX_PASSKEY_CREDENTIAL_ID") {
+ credId = field.protectedValue.stringValue
+ } else if (field.name == "KPEX_PASSKEY_USER_HANDLE") {
+ userHandle = field.protectedValue.stringValue
+ } else if (field.name == "KPEX_PASSKEY_RELYING_PARTY") {
+ relyingParty = field.protectedValue.stringValue
+ }
+ // KPEX_PASSKEY_RELYING_PARTY
+ }
+ return Passkey(nodeId, username, displayName, privateKeyPem, credId, userHandle, relyingParty, lastUsedTime)
+ }
+
+ fun convertEntriesListToPasskeys(entries: List): List {
+ return entries.mapNotNull { e -> convertEntryToPasskey(e) }
+ }
+
+ fun searchPasskeys(database: Database): List {
+ val searchHelper = SearchHelper()
+ val searchParameters = SearchParameters().apply {
+ searchQuery = PASSKEY_TAG
+ searchInTitles = false
+ searchInUsernames = false
+ searchInPasswords = false
+ searchInUrls = false
+ searchInNotes = false
+ searchInOTP = false
+ searchInOther = false
+ searchInUUIDs = false
+ searchInTags = true
+ searchInCurrentGroup = false
+ searchInSearchableGroup = false
+ searchInRecycleBin = false
+ searchInTemplates = false
+ }
+ val searchResult = searchHelper.createVirtualGroupWithSearchResult(database, searchParameters, null, Int.MAX_VALUE)
+ ?: return emptyList()
+
+ return convertEntriesListToPasskeys(searchResult.getChildEntries())
+ }
+
+ fun searchPassKeyByNodeId(database: Database, nodeId: String): Passkey? {
+ val uuidToSearch = UuidUtil.fromHexString(nodeId)!!
+ val nodeIdUUIDToSearch = NodeIdUUID(uuidToSearch)
+ val entry = database.getEntryById(nodeIdUUIDToSearch)!!
+ return convertEntryToPasskey(entry)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/kunzisoft/keepass/education/Education.kt b/app/src/main/java/com/kunzisoft/keepass/education/Education.kt
index 1bd0f9a14..4cfaad746 100644
--- a/app/src/main/java/com/kunzisoft/keepass/education/Education.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/education/Education.kt
@@ -98,7 +98,7 @@ open class Education(val activity: Activity) {
}
protected fun getCircleColor(): Int {
- val typedArray = activity.obtainStyledAttributes(intArrayOf(R.attr.colorPrimaryContainer))
+ val typedArray = activity.obtainStyledAttributes(intArrayOf(com.google.android.material.R.attr.colorPrimaryContainer))
val colorControl = typedArray.getColor(0, Color.GREEN)
typedArray.recycle()
return colorControl
@@ -109,7 +109,7 @@ open class Education(val activity: Activity) {
}
protected fun getTextColor(): Int {
- val typedArray = activity.obtainStyledAttributes(intArrayOf(R.attr.colorOnPrimaryContainer))
+ val typedArray = activity.obtainStyledAttributes(intArrayOf(com.google.android.material.R.attr.colorOnPrimaryContainer))
val colorControl = typedArray.getColor(0, Color.WHITE)
typedArray.recycle()
return colorControl
diff --git a/app/src/main/java/com/kunzisoft/keepass/services/NotificationService.kt b/app/src/main/java/com/kunzisoft/keepass/services/NotificationService.kt
index a3eb2bf9b..774272583 100644
--- a/app/src/main/java/com/kunzisoft/keepass/services/NotificationService.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/services/NotificationService.kt
@@ -70,7 +70,7 @@ abstract class NotificationService : Service() {
setTheme(Stylish.getThemeId(this))
val typedValue = TypedValue()
val theme = theme
- theme.resolveAttribute(R.attr.colorPrimary, typedValue, true)
+ theme.resolveAttribute(com.google.android.material.R.attr.colorPrimary, typedValue, true)
colorNotificationAccent = typedValue.data
}
diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/preference/DialogColorPreference.kt b/app/src/main/java/com/kunzisoft/keepass/settings/preference/DialogColorPreference.kt
index 68ff04335..134848ef4 100644
--- a/app/src/main/java/com/kunzisoft/keepass/settings/preference/DialogColorPreference.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/settings/preference/DialogColorPreference.kt
@@ -29,7 +29,7 @@ import com.kunzisoft.keepass.R
class DialogColorPreference @JvmOverloads constructor(context: Context,
attrs: AttributeSet? = null,
- defStyleAttr: Int = R.attr.dialogPreferenceStyle,
+ defStyleAttr: Int = PreferenceConstant.R_ATTR_DIALOG_PREFERENCE_STYLE,
defStyleRes: Int = defStyleAttr)
: ChromaPreferenceCompat(context, attrs, defStyleAttr, defStyleRes) {
diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/preference/DialogListExplanationPreference.kt b/app/src/main/java/com/kunzisoft/keepass/settings/preference/DialogListExplanationPreference.kt
index c0a028bbe..83d2531b5 100644
--- a/app/src/main/java/com/kunzisoft/keepass/settings/preference/DialogListExplanationPreference.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/settings/preference/DialogListExplanationPreference.kt
@@ -27,7 +27,7 @@ import com.kunzisoft.keepass.R
class DialogListExplanationPreference @JvmOverloads constructor(context: Context,
attrs: AttributeSet? = null,
- defStyleAttr: Int = R.attr.dialogPreferenceStyle,
+ defStyleAttr: Int = PreferenceConstant.R_ATTR_DIALOG_PREFERENCE_STYLE,
defStyleRes: Int = defStyleAttr)
: DialogPreference(context, attrs, defStyleAttr, defStyleRes) {
diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/preference/DurationDialogPreference.kt b/app/src/main/java/com/kunzisoft/keepass/settings/preference/DurationDialogPreference.kt
index 46f7a4e92..6d1730e27 100644
--- a/app/src/main/java/com/kunzisoft/keepass/settings/preference/DurationDialogPreference.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/settings/preference/DurationDialogPreference.kt
@@ -27,7 +27,7 @@ import com.kunzisoft.keepass.R
class DurationDialogPreference @JvmOverloads constructor(context: Context,
attrs: AttributeSet? = null,
- defStyleAttr: Int = R.attr.dialogPreferenceStyle,
+ defStyleAttr: Int = PreferenceConstant.R_ATTR_DIALOG_PREFERENCE_STYLE,
defStyleRes: Int = defStyleAttr)
: DialogPreference(context, attrs, defStyleAttr, defStyleRes) {
@@ -52,6 +52,7 @@ class DurationDialogPreference @JvmOverloads constructor(context: Context,
notifyChanged()
}
+ @Deprecated(message = "")
override fun onSetInitialValue(restorePersistedValue: Boolean, defaultValue: Any?) {
if (restorePersistedValue) {
mDuration = getPersistedString(mDuration.toString()).toLongOrNull() ?: mDuration
diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/preference/IconPackListPreference.kt b/app/src/main/java/com/kunzisoft/keepass/settings/preference/IconPackListPreference.kt
index 3ed4a43ec..6d99c99f3 100644
--- a/app/src/main/java/com/kunzisoft/keepass/settings/preference/IconPackListPreference.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/settings/preference/IconPackListPreference.kt
@@ -28,7 +28,7 @@ import java.util.*
class IconPackListPreference @JvmOverloads constructor(context: Context,
attrs: AttributeSet? = null,
- defStyleAttr: Int = R.attr.dialogPreferenceStyle,
+ defStyleAttr: Int = PreferenceConstant.R_ATTR_DIALOG_PREFERENCE_STYLE,
defStyleRes: Int = defStyleAttr)
: ListPreference(context, attrs, defStyleAttr, defStyleRes) {
diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/preference/InputKdfNumberPreference.kt b/app/src/main/java/com/kunzisoft/keepass/settings/preference/InputKdfNumberPreference.kt
index 367f73489..0be703810 100644
--- a/app/src/main/java/com/kunzisoft/keepass/settings/preference/InputKdfNumberPreference.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/settings/preference/InputKdfNumberPreference.kt
@@ -27,7 +27,7 @@ import com.kunzisoft.keepass.database.crypto.kdf.KdfEngine
open class InputKdfNumberPreference @JvmOverloads constructor(context: Context,
attrs: AttributeSet? = null,
- defStyleAttr: Int = R.attr.dialogPreferenceStyle,
+ defStyleAttr: Int = PreferenceConstant.R_ATTR_DIALOG_PREFERENCE_STYLE,
defStyleRes: Int = defStyleAttr)
: DialogPreference(context, attrs, defStyleAttr, defStyleRes) {
diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/preference/InputKdfSizePreference.kt b/app/src/main/java/com/kunzisoft/keepass/settings/preference/InputKdfSizePreference.kt
index ff07d973d..cb691ae88 100644
--- a/app/src/main/java/com/kunzisoft/keepass/settings/preference/InputKdfSizePreference.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/settings/preference/InputKdfSizePreference.kt
@@ -26,7 +26,7 @@ import com.kunzisoft.keepass.utils.DataByte
class InputKdfSizePreference @JvmOverloads constructor(context: Context,
attrs: AttributeSet? = null,
- defStyleAttr: Int = R.attr.dialogPreferenceStyle,
+ defStyleAttr: Int = PreferenceConstant.R_ATTR_DIALOG_PREFERENCE_STYLE,
defStyleRes: Int = defStyleAttr)
: InputKdfNumberPreference(context, attrs, defStyleAttr, defStyleRes) {
diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/preference/InputListPreference.kt b/app/src/main/java/com/kunzisoft/keepass/settings/preference/InputListPreference.kt
index 708ba3fd1..eaaee1223 100644
--- a/app/src/main/java/com/kunzisoft/keepass/settings/preference/InputListPreference.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/settings/preference/InputListPreference.kt
@@ -26,7 +26,7 @@ import com.kunzisoft.keepass.R
open class InputListPreference @JvmOverloads constructor(context: Context,
attrs: AttributeSet? = null,
- defStyleAttr: Int = R.attr.dialogPreferenceStyle,
+ defStyleAttr: Int = PreferenceConstant.R_ATTR_DIALOG_PREFERENCE_STYLE,
defStyleRes: Int = defStyleAttr)
: DialogPreference(context, attrs, defStyleAttr, defStyleRes) {
diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/preference/InputNumberPreference.kt b/app/src/main/java/com/kunzisoft/keepass/settings/preference/InputNumberPreference.kt
index 116eaedbe..8a1f69879 100644
--- a/app/src/main/java/com/kunzisoft/keepass/settings/preference/InputNumberPreference.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/settings/preference/InputNumberPreference.kt
@@ -26,7 +26,7 @@ import com.kunzisoft.keepass.R
open class InputNumberPreference @JvmOverloads constructor(context: Context,
attrs: AttributeSet? = null,
- defStyleAttr: Int = R.attr.dialogPreferenceStyle,
+ defStyleAttr: Int = PreferenceConstant.R_ATTR_DIALOG_PREFERENCE_STYLE,
defStyleRes: Int = defStyleAttr)
: DialogPreference(context, attrs, defStyleAttr, defStyleRes) {
diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/preference/InputSizePreference.kt b/app/src/main/java/com/kunzisoft/keepass/settings/preference/InputSizePreference.kt
index 39dee92e6..2030fc7c2 100644
--- a/app/src/main/java/com/kunzisoft/keepass/settings/preference/InputSizePreference.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/settings/preference/InputSizePreference.kt
@@ -26,7 +26,7 @@ import com.kunzisoft.keepass.utils.DataByte
open class InputSizePreference @JvmOverloads constructor(context: Context,
attrs: AttributeSet? = null,
- defStyleAttr: Int = R.attr.dialogPreferenceStyle,
+ defStyleAttr: Int = PreferenceConstant.R_ATTR_DIALOG_PREFERENCE_STYLE,
defStyleRes: Int = defStyleAttr)
: InputNumberPreference(context, attrs, defStyleAttr, defStyleRes) {
diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/preference/InputTextPreference.kt b/app/src/main/java/com/kunzisoft/keepass/settings/preference/InputTextPreference.kt
index a8e18a750..b2bc8e3ce 100644
--- a/app/src/main/java/com/kunzisoft/keepass/settings/preference/InputTextPreference.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/settings/preference/InputTextPreference.kt
@@ -27,7 +27,7 @@ import com.kunzisoft.keepass.R
open class InputTextPreference @JvmOverloads constructor(context: Context,
attrs: AttributeSet? = null,
- defStyleAttr: Int = R.attr.dialogPreferenceStyle,
+ defStyleAttr: Int = PreferenceConstant.R_ATTR_DIALOG_PREFERENCE_STYLE,
defStyleRes: Int = defStyleAttr)
: DialogPreference(context, attrs, defStyleAttr, defStyleRes) {
diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/preference/PreferenceConstant.kt b/app/src/main/java/com/kunzisoft/keepass/settings/preference/PreferenceConstant.kt
new file mode 100644
index 000000000..0ac354129
--- /dev/null
+++ b/app/src/main/java/com/kunzisoft/keepass/settings/preference/PreferenceConstant.kt
@@ -0,0 +1,8 @@
+package com.kunzisoft.keepass.settings.preference
+
+class PreferenceConstant {
+ companion object {
+ const val R_ATTR_DIALOG_PREFERENCE_STYLE: Int = 16842897;
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/preference/TextPreference.kt b/app/src/main/java/com/kunzisoft/keepass/settings/preference/TextPreference.kt
index 8b204e0c0..b60e48d0a 100644
--- a/app/src/main/java/com/kunzisoft/keepass/settings/preference/TextPreference.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/settings/preference/TextPreference.kt
@@ -27,7 +27,7 @@ import com.kunzisoft.keepass.R
open class TextPreference @JvmOverloads constructor(context: Context,
attrs: AttributeSet? = null,
- defStyleAttr: Int = R.attr.dialogPreferenceStyle,
+ defStyleAttr: Int = PreferenceConstant.R_ATTR_DIALOG_PREFERENCE_STYLE,
defStyleRes: Int = defStyleAttr)
: DialogPreference(context, attrs, defStyleAttr, defStyleRes) {
diff --git a/app/src/main/java/com/kunzisoft/keepass/view/SectionView.kt b/app/src/main/java/com/kunzisoft/keepass/view/SectionView.kt
index 396b66835..26666ce6c 100644
--- a/app/src/main/java/com/kunzisoft/keepass/view/SectionView.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/view/SectionView.kt
@@ -30,7 +30,7 @@ import com.kunzisoft.keepass.R
class SectionView @JvmOverloads constructor(context: Context,
attrs: AttributeSet? = null,
- defStyle: Int = R.attr.cardViewStyle)
+ defStyle: Int = com.google.android.material.R.attr.cardViewStyle)
: CardView(context, attrs, defStyle) {
private var containerSectionView = LinearLayout(context).apply {
diff --git a/app/src/main/java/com/kunzisoft/keepass/view/TemplateAbstractView.kt b/app/src/main/java/com/kunzisoft/keepass/view/TemplateAbstractView.kt
index dd336c843..d6c7fb1f6 100644
--- a/app/src/main/java/com/kunzisoft/keepass/view/TemplateAbstractView.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/view/TemplateAbstractView.kt
@@ -148,7 +148,7 @@ abstract class TemplateAbstractView<
// Build each section
template.sections.forEach { templateSection ->
- val sectionView = SectionView(context, null, R.attr.cardViewStyle)
+ val sectionView = SectionView(context, null, com.google.android.material.R.attr.cardViewStyle)
// Build each attribute
templateSection.attributes.forEach { templateAttribute ->
diff --git a/app/src/main/java/com/kunzisoft/keepass/view/ToolbarAction.kt b/app/src/main/java/com/kunzisoft/keepass/view/ToolbarAction.kt
index 82f0fbfa1..975da93d3 100644
--- a/app/src/main/java/com/kunzisoft/keepass/view/ToolbarAction.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/view/ToolbarAction.kt
@@ -47,7 +47,7 @@ class ToolbarAction @JvmOverloads constructor(context: Context,
init {
ContextCompat.getDrawable(context, R.drawable.ic_close_white_24dp)?.let { closeDrawable ->
val typedValue = TypedValue()
- context.theme.resolveAttribute(R.attr.colorOnSurface, typedValue, true)
+ context.theme.resolveAttribute(com.google.android.material.R.attr.colorOnSurface, typedValue, true)
@ColorInt val colorControl = typedValue.data
closeDrawable.colorFilter = PorterDuffColorFilter(colorControl, PorterDuff.Mode.SRC_ATOP)
navigationIcon = closeDrawable
diff --git a/app/src/main/java/com/kunzisoft/keepass/view/ToolbarSpecial.kt b/app/src/main/java/com/kunzisoft/keepass/view/ToolbarSpecial.kt
index 8367fdea0..141490c47 100644
--- a/app/src/main/java/com/kunzisoft/keepass/view/ToolbarSpecial.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/view/ToolbarSpecial.kt
@@ -38,7 +38,7 @@ class ToolbarSpecial @JvmOverloads constructor(context: Context,
init {
ContextCompat.getDrawable(context, R.drawable.ic_arrow_back_white_24dp)?.let { closeDrawable ->
val typedValue = TypedValue()
- context.theme.resolveAttribute(R.attr.colorOnSurface, typedValue, true)
+ context.theme.resolveAttribute(com.google.android.material.R.attr.colorOnSurface, typedValue, true)
@ColorInt val colorOnSurface = typedValue.data
closeDrawable.colorFilter = PorterDuffColorFilter(colorOnSurface, PorterDuff.Mode.SRC_ATOP)
navigationIcon = closeDrawable
diff --git a/app/src/main/java/com/kunzisoft/keepass/view/ViewUtil.kt b/app/src/main/java/com/kunzisoft/keepass/view/ViewUtil.kt
index 90e9ff5b2..a329902f2 100644
--- a/app/src/main/java/com/kunzisoft/keepass/view/ViewUtil.kt
+++ b/app/src/main/java/com/kunzisoft/keepass/view/ViewUtil.kt
@@ -117,7 +117,7 @@ fun TextView.customLink(listener: (View) -> Unit) {
fun Snackbar.asError(): Snackbar {
this.view.apply {
setBackgroundColor(Color.RED)
- findViewById(R.id.snackbar_text).setTextColor(Color.WHITE)
+ findViewById(com.google.android.material.R.id.snackbar_text).setTextColor(Color.WHITE)
}
return this
}
@@ -308,7 +308,7 @@ fun Activity.setTransparentNavigationBar(applyToStatusBar: Boolean = false, appl
WindowCompat.setDecorFitsSystemWindows(window, false)
window.navigationBarColor = ContextCompat.getColor(this, R.color.surface_selector)
if (applyToStatusBar) {
- obtainStyledAttributes(intArrayOf(R.attr.colorSurface)).apply {
+ obtainStyledAttributes(intArrayOf(com.google.android.material.R.attr.colorSurface)).apply {
window.statusBarColor = getColor(0, Color.GRAY)
recycle()
}
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index f4befb9e5..01bff8c47 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -733,4 +733,8 @@
Displays foreground and background colors for an entry
Hide expired entries
Expired entries are not shown
+
+ Confirm passkey usage
+ for %1$s
+ Cancel
\ No newline at end of file
diff --git a/app/src/main/res/xml/provider.xml b/app/src/main/res/xml/provider.xml
new file mode 100644
index 000000000..5dc307a8c
--- /dev/null
+++ b/app/src/main/res/xml/provider.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/build.gradle b/build.gradle
index 80b4e9176..b736f33cb 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,6 +1,6 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
- ext.kotlin_version = '1.8.20'
+ ext.kotlin_version = '2.0.0'
ext.android_core_version = '1.10.1'
ext.android_appcompat_version = '1.6.1'
ext.android_material_version = '1.9.0'
@@ -10,7 +10,7 @@ buildscript {
google()
}
dependencies {
- classpath 'com.android.tools.build:gradle:7.4.2'
+ classpath 'com.android.tools.build:gradle:8.4.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
diff --git a/crypto/build.gradle b/crypto/build.gradle
index 20634e48f..ddab8d18a 100644
--- a/crypto/build.gradle
+++ b/crypto/build.gradle
@@ -9,7 +9,7 @@ android {
ndkVersion "21.4.7075529"
defaultConfig {
- minSdkVersion 15
+ minSdkVersion 19
targetSdkVersion 34
multiDexEnabled true
@@ -40,7 +40,7 @@ android {
dependencies {
// Crypto
- implementation 'org.bouncycastle:bcprov-jdk15on:1.70'
+ implementation 'org.bouncycastle:bcpkix-jdk18on:1.78.1'
testImplementation "androidx.test:runner:$android_test_version"
}
diff --git a/crypto/src/main/java/com/kunzisoft/signature/Signature.kt b/crypto/src/main/java/com/kunzisoft/signature/Signature.kt
new file mode 100644
index 000000000..ee94f44ca
--- /dev/null
+++ b/crypto/src/main/java/com/kunzisoft/signature/Signature.kt
@@ -0,0 +1,43 @@
+package com.kunzisoft.signature
+
+import android.util.Log
+import org.bouncycastle.asn1.pkcs.PrivateKeyInfo
+import org.bouncycastle.jce.provider.BouncyCastleProvider
+import java.io.StringReader
+import java.security.PrivateKey
+import java.security.Security
+import java.security.Signature
+
+import org.bouncycastle.openssl.PEMParser
+import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter
+
+object Signature {
+
+ init {
+ Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME)
+ Security.addProvider(BouncyCastleProvider())
+ }
+ fun sign(privateKeyPem: String, message: ByteArray): ByteArray {
+ val privateKey = createPrivateKey(privateKeyPem)
+ val algorithmKey = privateKey.algorithm
+ val algorithmSignature = when (algorithmKey) {
+ "EC" -> "SHA256withECDSA"
+ "ECDSA" -> "SHA256withECDSA"
+ "RSA" -> "SHA256withRSA"
+ else -> "no signature algorithms known"
+ }
+ val sig = Signature.getInstance(algorithmSignature, BouncyCastleProvider.PROVIDER_NAME)
+ sig.initSign(privateKey)
+ sig.update(message)
+ return sig.sign()
+ }
+
+ private fun createPrivateKey(privateKeyPem: String): PrivateKey {
+ val targetReader = StringReader(privateKeyPem);
+ val a = PEMParser(targetReader)
+ val privateKeyInfo = a.readObject() as PrivateKeyInfo
+ val privateKey = JcaPEMKeyConverter().getPrivateKey(privateKeyInfo)
+ return privateKey
+ }
+
+}
\ No newline at end of file
diff --git a/database/build.gradle b/database/build.gradle
index b11df6359..5fd603a8f 100644
--- a/database/build.gradle
+++ b/database/build.gradle
@@ -6,7 +6,7 @@ android {
compileSdkVersion 34
defaultConfig {
- minSdkVersion 15
+ minSdkVersion 19
targetSdk 34
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index ae04661ee..00def7772 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,6 @@
+#Sun Sep 08 17:39:21 CEST 2024
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/icon-pack/build.gradle b/icon-pack/build.gradle
index bceaec7f5..fe907a19f 100644
--- a/icon-pack/build.gradle
+++ b/icon-pack/build.gradle
@@ -6,7 +6,7 @@ android {
compileSdkVersion 34
defaultConfig {
- minSdkVersion 14
+ minSdkVersion 19
targetSdkVersion 34
}
diff --git a/icon-pack/classic/build.gradle b/icon-pack/classic/build.gradle
index c5262974d..6fcd807bc 100644
--- a/icon-pack/classic/build.gradle
+++ b/icon-pack/classic/build.gradle
@@ -5,7 +5,7 @@ android {
compileSdkVersion 34
defaultConfig {
- minSdkVersion 14
+ minSdkVersion 19
targetSdkVersion 34
}
diff --git a/icon-pack/material/build.gradle b/icon-pack/material/build.gradle
index b4dfe5d79..b17b7ebee 100644
--- a/icon-pack/material/build.gradle
+++ b/icon-pack/material/build.gradle
@@ -5,7 +5,7 @@ android {
compileSdkVersion 34
defaultConfig {
- minSdkVersion 14
+ minSdkVersion 19
targetSdkVersion 34
}