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 90c5a6947..f13220304 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/EntryActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/EntryActivity.kt @@ -50,15 +50,15 @@ import com.google.android.material.tabs.TabLayout import com.kunzisoft.keepass.R import com.kunzisoft.keepass.activities.fragments.EntryFragment import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper -import com.kunzisoft.keepass.credentialprovider.SpecialMode import com.kunzisoft.keepass.activities.legacy.DatabaseLockActivity import com.kunzisoft.keepass.adapters.TagsAdapter +import com.kunzisoft.keepass.credentialprovider.SpecialMode +import com.kunzisoft.keepass.credentialprovider.magikeyboard.MagikeyboardService import com.kunzisoft.keepass.database.ContextualDatabase import com.kunzisoft.keepass.database.element.Attachment import com.kunzisoft.keepass.database.element.icon.IconImage import com.kunzisoft.keepass.database.element.node.NodeId import com.kunzisoft.keepass.education.EntryActivityEducation -import com.kunzisoft.keepass.credentialprovider.magikeyboard.MagikeyboardService import com.kunzisoft.keepass.model.EntryAttachmentState import com.kunzisoft.keepass.otp.OtpType import com.kunzisoft.keepass.services.AttachmentFileNotificationService @@ -69,7 +69,7 @@ import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.tasks.ActionRunnable import com.kunzisoft.keepass.tasks.AttachmentFileBinderManager import com.kunzisoft.keepass.timeout.TimeoutHelper -import com.kunzisoft.keepass.utils.UuidUtil +import com.kunzisoft.keepass.utils.UUIDUtils.asHexString import com.kunzisoft.keepass.utils.getParcelableExtraCompat import com.kunzisoft.keepass.view.WindowInsetPosition import com.kunzisoft.keepass.view.applyWindowInsets @@ -257,7 +257,7 @@ class EntryActivity : DatabaseLockActivity() { mIcon = entryInfo.icon // Assign title text val entryTitle = - if (entryInfo.title.isNotEmpty()) entryInfo.title else UuidUtil.toHexString(entryInfo.id) + entryInfo.title.ifEmpty { entryInfo.id.asHexString() } collapsingToolbarLayout?.title = entryTitle toolbar?.title = entryTitle // Assign tags 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 6a41fb3bb..bf3389633 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 @@ -36,7 +36,7 @@ import com.kunzisoft.keepass.database.element.icon.IconImage import com.kunzisoft.keepass.model.GroupInfo import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.utils.TimeUtil.getDateTimeString -import com.kunzisoft.keepass.utils.UuidUtil +import com.kunzisoft.keepass.utils.UUIDUtils.asHexString import com.kunzisoft.keepass.utils.getParcelableCompat import com.kunzisoft.keepass.view.DateTimeFieldView @@ -155,7 +155,7 @@ class GroupDialogFragment : DatabaseDialogFragment() { searchableView.text = stringFromInheritableBoolean(mGroupInfo.searchable) autoTypeView.text = stringFromInheritableBoolean(mGroupInfo.enableAutoType, mGroupInfo.defaultAutoTypeSequence) - val uuid = UuidUtil.toHexString(mGroupInfo.id) + val uuid = mGroupInfo.id?.asHexString() if (uuid == null || uuid.isEmpty()) { uuidContainerView.visibility = View.GONE } else { diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/fragments/EntryFragment.kt b/app/src/main/java/com/kunzisoft/keepass/activities/fragments/EntryFragment.kt index a9de1526c..63b8c4601 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/fragments/EntryFragment.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/fragments/EntryFragment.kt @@ -24,7 +24,7 @@ import com.kunzisoft.keepass.model.StreamDirection import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.timeout.ClipboardHelper import com.kunzisoft.keepass.utils.TimeUtil.getDateTimeString -import com.kunzisoft.keepass.utils.UuidUtil +import com.kunzisoft.keepass.utils.UUIDUtils.asHexString import com.kunzisoft.keepass.view.TemplateView import com.kunzisoft.keepass.view.hideByFading import com.kunzisoft.keepass.view.showByFading @@ -184,7 +184,7 @@ class EntryFragment: DatabaseFragment() { // customDataView.text = entryInfo?.customData?.toString() // Assign special data - uuidReferenceView.text = UuidUtil.toHexString(entryInfo?.id) + uuidReferenceView.text = entryInfo?.id?.asHexString() } private fun showClipboardDialog() { diff --git a/app/src/main/java/com/kunzisoft/keepass/credentialprovider/passkey/data/AuthenticatorAttestationResponse.kt b/app/src/main/java/com/kunzisoft/keepass/credentialprovider/passkey/data/AuthenticatorAttestationResponse.kt index d44a0c116..231d8b5a1 100644 --- a/app/src/main/java/com/kunzisoft/keepass/credentialprovider/passkey/data/AuthenticatorAttestationResponse.kt +++ b/app/src/main/java/com/kunzisoft/keepass/credentialprovider/passkey/data/AuthenticatorAttestationResponse.kt @@ -20,6 +20,7 @@ package com.kunzisoft.keepass.credentialprovider.passkey.data import com.kunzisoft.keepass.credentialprovider.passkey.util.Base64Helper.Companion.b64Encode +import com.kunzisoft.keepass.utils.UUIDUtils.asUUIDBytes import org.json.JSONArray import org.json.JSONObject @@ -59,6 +60,7 @@ class AuthenticatorAttestationResponse( } internal fun defaultAttestationObject(): ByteArray { + // https://www.w3.org/TR/webauthn-3/#attestation-object val ao = mutableMapOf() ao.put("fmt", "none") ao.put("attStmt", emptyMap()) @@ -80,7 +82,7 @@ class AuthenticatorAttestationResponse( } companion object { - // TODO Authenticator Attestation Global Unique Identifier - private val AAGUID = ByteArray(16) { 0 } + // Authenticator Attestation Global Unique Identifier + private val AAGUID: ByteArray = "eaecdef2-1c31-5634-8639-f1cbd9c00a08".asUUIDBytes() } } \ No newline at end of file diff --git a/database/src/main/java/com/kunzisoft/keepass/database/element/entry/FieldReferencesEngine.kt b/database/src/main/java/com/kunzisoft/keepass/database/element/entry/FieldReferencesEngine.kt index 81ce52dee..5da32dc09 100644 --- a/database/src/main/java/com/kunzisoft/keepass/database/element/entry/FieldReferencesEngine.kt +++ b/database/src/main/java/com/kunzisoft/keepass/database/element/entry/FieldReferencesEngine.kt @@ -22,7 +22,8 @@ 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 com.kunzisoft.keepass.utils.UUIDUtils.asHexString +import com.kunzisoft.keepass.utils.UUIDUtils.asUUID import java.util.concurrent.ConcurrentHashMap class FieldReferencesEngine(private val mDatabase: DatabaseKDBX) { @@ -79,7 +80,7 @@ class FieldReferencesEngine(private val mDatabase: DatabaseKDBX) { 'A' -> entryFound?.decodeUrlKey(newRecursionLevel) 'P' -> entryFound?.decodePasswordKey(newRecursionLevel) 'N' -> entryFound?.decodeNotesKey(newRecursionLevel) - 'I' -> UuidUtil.toHexString(entryFound?.nodeId?.id) + 'I' -> entryFound?.nodeId?.id?.asHexString() else -> null } refsCache[fullReference] = data @@ -127,7 +128,7 @@ class FieldReferencesEngine(private val mDatabase: DatabaseKDBX) { 'P' -> mDatabase.getEntryByPassword(searchQuery, recursionLevel) 'N' -> mDatabase.getEntryByNotes(searchQuery, recursionLevel) 'I' -> { - UuidUtil.fromHexString(searchQuery)?.let { uuid -> + searchQuery.asUUID()?.let { uuid -> mDatabase.getEntryById(NodeIdUUID(uuid)) } } diff --git a/database/src/main/java/com/kunzisoft/keepass/database/element/node/NodeIdUUID.kt b/database/src/main/java/com/kunzisoft/keepass/database/element/node/NodeIdUUID.kt index f883851e1..466ccc3fa 100644 --- a/database/src/main/java/com/kunzisoft/keepass/database/element/node/NodeIdUUID.kt +++ b/database/src/main/java/com/kunzisoft/keepass/database/element/node/NodeIdUUID.kt @@ -22,9 +22,9 @@ package com.kunzisoft.keepass.database.element.node import android.os.Parcel import android.os.ParcelUuid import android.os.Parcelable +import com.kunzisoft.keepass.utils.UUIDUtils.asHexString import com.kunzisoft.keepass.utils.readParcelableCompat -import com.kunzisoft.keepass.utils.UuidUtil -import java.util.* +import java.util.UUID class NodeIdUUID : NodeId { @@ -62,7 +62,7 @@ class NodeIdUUID : NodeId { } override fun toString(): String { - return UuidUtil.toHexString(id) ?: id.toString() + return id.asHexString() ?: id.toString() } override fun toVisualString(): String { diff --git a/database/src/main/java/com/kunzisoft/keepass/database/element/template/TemplateEngineCompatible.kt b/database/src/main/java/com/kunzisoft/keepass/database/element/template/TemplateEngineCompatible.kt index 85e4d5f32..84c5dd113 100644 --- a/database/src/main/java/com/kunzisoft/keepass/database/element/template/TemplateEngineCompatible.kt +++ b/database/src/main/java/com/kunzisoft/keepass/database/element/template/TemplateEngineCompatible.kt @@ -24,7 +24,8 @@ import com.kunzisoft.keepass.database.element.Field import com.kunzisoft.keepass.database.element.database.DatabaseKDBX import com.kunzisoft.keepass.database.element.entry.EntryKDBX import com.kunzisoft.keepass.database.element.security.ProtectedString -import com.kunzisoft.keepass.utils.UuidUtil +import com.kunzisoft.keepass.utils.UUIDUtils.asHexString +import com.kunzisoft.keepass.utils.UUIDUtils.asUUID class TemplateEngineCompatible(database: DatabaseKDBX): TemplateEngine(database) { @@ -33,7 +34,7 @@ class TemplateEngineCompatible(database: DatabaseKDBX): TemplateEngine(database) } override fun getTemplate(entryKDBX: EntryKDBX): Template? { - UuidUtil.fromHexString(entryKDBX.getCustomFieldValue(TEMPLATE_ENTRY_UUID))?.let { templateUUID -> + entryKDBX.getCustomFieldValue(TEMPLATE_ENTRY_UUID).asUUID()?.let { templateUUID -> return getTemplateByCache(templateUUID) } return null @@ -48,7 +49,7 @@ class TemplateEngineCompatible(database: DatabaseKDBX): TemplateEngine(database) } private fun getTemplateUUIDField(template: Template): Field? { - UuidUtil.toHexString(template.uuid)?.let { uuidString -> + template.uuid.asHexString()?.let { uuidString -> return Field(TEMPLATE_ENTRY_UUID, ProtectedString(false, uuidString)) } diff --git a/database/src/main/java/com/kunzisoft/keepass/database/search/SearchHelper.kt b/database/src/main/java/com/kunzisoft/keepass/database/search/SearchHelper.kt index b616708ec..1187c38ab 100644 --- a/database/src/main/java/com/kunzisoft/keepass/database/search/SearchHelper.kt +++ b/database/src/main/java/com/kunzisoft/keepass/database/search/SearchHelper.kt @@ -28,7 +28,7 @@ import com.kunzisoft.keepass.model.PasskeyEntryFields.FIELD_RELYING_PARTY import com.kunzisoft.keepass.model.PasskeyEntryFields.isPasskeyExclusion import com.kunzisoft.keepass.otp.OtpEntryFields.OTP_FIELD import com.kunzisoft.keepass.otp.OtpEntryFields.isOtpExclusion -import com.kunzisoft.keepass.utils.UuidUtil +import com.kunzisoft.keepass.utils.UUIDUtils.asHexString import com.kunzisoft.keepass.utils.inTheSameDomainAs class SearchHelper { @@ -166,7 +166,7 @@ class SearchHelper { return true } if (searchParameters.searchInUUIDs) { - val hexString = UuidUtil.toHexString(entry.nodeId.id) ?: "" + val hexString = entry.nodeId.id.asHexString() ?: "" if (checkSearchQuery(hexString, searchParameters)) return true } diff --git a/database/src/main/java/com/kunzisoft/keepass/utils/UUIDUtils.kt b/database/src/main/java/com/kunzisoft/keepass/utils/UUIDUtils.kt new file mode 100644 index 000000000..dc02e0fb7 --- /dev/null +++ b/database/src/main/java/com/kunzisoft/keepass/utils/UUIDUtils.kt @@ -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 . + * + */ +package com.kunzisoft.keepass.utils + +import java.nio.ByteBuffer +import java.util.Locale +import java.util.UUID + +object UUIDUtils { + fun UUID.asHexString(): String? { + try { + val buf = uuidTo16Bytes(this) + + val len = buf.size + if (len == 0) { + return "" + } + + val sb = StringBuilder() + + var bt: Short + var high: Char + var low: Char + for (b in buf) { + bt = (b.toInt() and 0xFF).toShort() + high = (bt.toInt() ushr 4).toChar() + low = (bt.toInt() and 0x0F).toChar() + sb.append(byteToChar(high)) + sb.append(byteToChar(low)) + } + + return sb.toString() + } catch (e: Exception) { + return null + } + } + + fun String.asUUID(): UUID? { + if (this.length != 32) return null + + val charArray = this.lowercase(Locale.getDefault()).toCharArray() + val leastSignificantChars = CharArray(16) + val mostSignificantChars = CharArray(16) + + var i = 31 + while (i >= 0) { + if (i >= 16) { + mostSignificantChars[32 - i] = charArray[i] + mostSignificantChars[31 - i] = charArray[i - 1] + } else { + leastSignificantChars[16 - i] = charArray[i] + leastSignificantChars[15 - i] = charArray[i - 1] + } + i = i - 2 + } + val standardUUIDString = StringBuilder() + standardUUIDString.append(leastSignificantChars) + standardUUIDString.append(mostSignificantChars) + standardUUIDString.insert(8, '-') + standardUUIDString.insert(13, '-') + standardUUIDString.insert(18, '-') + standardUUIDString.insert(23, '-') + return try { + UUID.fromString(standardUUIDString.toString()) + } catch (e: Exception) { + null + } + } + + fun ByteArray.asUUID(): UUID { + val bb = ByteBuffer.wrap(this) + val firstLong = bb.getLong() + val secondLong = bb.getLong() + return UUID(firstLong, secondLong) + } + + fun UUID.asBytes(): ByteArray { + return ByteBuffer.allocate(16).apply { + putLong(mostSignificantBits) + putLong(leastSignificantBits) + }.array() + } + + fun String.asUUIDBytes(): ByteArray { + return this.asUUID()?.asBytes() ?: ByteArray(16) + } + + // Use short to represent unsigned byte + private fun byteToChar(bt: Char): Char { + return if (bt.code >= 10) { + ('A'.code + bt.code - 10).toChar() + } else { + ('0'.code + bt.code).toChar() + } + } +} diff --git a/database/src/main/java/com/kunzisoft/keepass/utils/UuidUtil.java b/database/src/main/java/com/kunzisoft/keepass/utils/UuidUtil.java deleted file mode 100644 index 2b559e539..000000000 --- a/database/src/main/java/com/kunzisoft/keepass/utils/UuidUtil.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * 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 . - * - */ -package com.kunzisoft.keepass.utils; - -import java.util.UUID; - -import static com.kunzisoft.keepass.utils.StreamBytesUtilsKt.uuidTo16Bytes; - -import org.jetbrains.annotations.Nullable; - -public class UuidUtil { - - public static @Nullable String toHexString(@Nullable UUID uuid) { - if (uuid == null) { return null; } - try { - byte[] buf = uuidTo16Bytes(uuid); - - int len = buf.length; - if (len == 0) { - return ""; - } - - StringBuilder sb = new StringBuilder(); - - short bt; - char high, low; - for (byte b : buf) { - bt = (short) (b & 0xFF); - high = (char) (bt >>> 4); - low = (char) (bt & 0x0F); - sb.append(byteToChar(high)); - sb.append(byteToChar(low)); - } - - return sb.toString(); - } catch (Exception e) { - return null; - } - } - - public static @Nullable UUID fromHexString(@Nullable String hexString) { - if (hexString == null) - return null; - - if (hexString.length() != 32) - return null; - - char[] charArray = hexString.toLowerCase().toCharArray(); - char[] leastSignificantChars = new char[16]; - char[] mostSignificantChars = new char[16]; - - for (int i = 31; i >= 0; i = i-2) { - if (i >= 16) { - mostSignificantChars[32-i] = charArray[i]; - mostSignificantChars[31-i] = charArray[i-1]; - } else { - leastSignificantChars[16-i] = charArray[i]; - leastSignificantChars[15-i] = charArray[i-1]; - } - } - StringBuilder standardUUIDString = new StringBuilder(); - standardUUIDString.append(leastSignificantChars); - standardUUIDString.append(mostSignificantChars); - standardUUIDString.insert(8, '-'); - standardUUIDString.insert(13, '-'); - standardUUIDString.insert(18, '-'); - standardUUIDString.insert(23, '-'); - try { - return UUID.fromString(standardUUIDString.toString()); - } catch (Exception e) { - return null; - } - } - - // Use short to represent unsigned byte - private static char byteToChar(char bt) { - if (bt >= 10) { - return (char)('A' + bt - 10); - } - else { - return (char)('0' + bt); - } - } -} diff --git a/database/src/test/java/com/kunzisoft/keepass/tests/utils/UUIDTest.kt b/database/src/test/java/com/kunzisoft/keepass/tests/utils/UUIDTest.kt index 18ee0f6ce..1f84a06f7 100644 --- a/database/src/test/java/com/kunzisoft/keepass/tests/utils/UUIDTest.kt +++ b/database/src/test/java/com/kunzisoft/keepass/tests/utils/UUIDTest.kt @@ -1,15 +1,43 @@ +/* + * Copyright 2025 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 . + * + */ package com.kunzisoft.keepass.tests.utils -import com.kunzisoft.keepass.utils.UuidUtil +import com.kunzisoft.keepass.utils.UUIDUtils.asBytes +import com.kunzisoft.keepass.utils.UUIDUtils.asHexString +import com.kunzisoft.keepass.utils.UUIDUtils.asUUID import junit.framework.TestCase -import java.util.* +import java.util.UUID class UUIDTest: TestCase() { - fun testUUID() { + fun testUUIDString() { val randomUUID = UUID.randomUUID() - val hexStringUUID = UuidUtil.toHexString(randomUUID) - val retrievedUUID = UuidUtil.fromHexString(hexStringUUID) + val hexStringUUID = randomUUID.asHexString() + val retrievedUUID = hexStringUUID?.asUUID() + assertEquals(randomUUID, retrievedUUID) + } + + fun testUUIDBytes() { + val randomUUID = UUID.randomUUID() + val byteArrayUUID = randomUUID.asBytes() + val retrievedUUID = byteArrayUUID.asUUID() assertEquals(randomUUID, retrievedUUID) } } \ No newline at end of file