mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
fix: Move application signature function
This commit is contained in:
@@ -19,20 +19,15 @@
|
||||
*/
|
||||
package com.kunzisoft.keepass.credentialprovider.passkey.util
|
||||
|
||||
import android.content.pm.Signature
|
||||
import android.content.pm.SigningInfo
|
||||
import android.content.res.AssetManager
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.credentials.provider.CallingAppInfo
|
||||
import com.kunzisoft.encrypt.HashManager.getApplicationSignatures
|
||||
import com.kunzisoft.keepass.model.OriginApp
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.security.MessageDigest
|
||||
import java.security.cert.CertificateFactory
|
||||
import java.security.cert.X509Certificate
|
||||
import java.util.Locale
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.P)
|
||||
class OriginManager(
|
||||
@@ -102,7 +97,7 @@ class OriginManager(
|
||||
onOriginNotRetrieved(
|
||||
OriginApp(
|
||||
appId = callingAppInfo.packageName,
|
||||
appSignature = getApplicationSignatures(callingAppInfo.signingInfo)
|
||||
appSignature = callingAppInfo.signingInfo.getApplicationSignatures()
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -110,77 +105,6 @@ class OriginManager(
|
||||
}
|
||||
}
|
||||
|
||||
// TODO Move in Crypto package and make unit tests
|
||||
/**
|
||||
* Converts a Signature object into its SHA-256 fingerprint string.
|
||||
* The fingerprint is typically represented as uppercase hex characters separated by colons.
|
||||
*/
|
||||
private fun signatureToSha256Fingerprint(signature: Signature): String? {
|
||||
return try {
|
||||
val certificateFactory = CertificateFactory.getInstance("X.509")
|
||||
val x509Certificate = certificateFactory.generateCertificate(
|
||||
signature.toByteArray().inputStream()
|
||||
) as X509Certificate
|
||||
|
||||
val messageDigest = MessageDigest.getInstance("SHA-256")
|
||||
val digest = messageDigest.digest(x509Certificate.encoded)
|
||||
|
||||
// Format as colon-separated HEX uppercase string
|
||||
digest.joinToString(separator = ":") { byte -> "%02X".format(byte) }
|
||||
.uppercase(Locale.US)
|
||||
} catch (e: Exception) {
|
||||
Log.e("SigningInfoUtil", "Error converting signature to SHA-256 fingerprint", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all relevant SHA-256 signature fingerprints for a given package.
|
||||
*
|
||||
* @param signingInfo The SigningInfo object to retrieve the strings signatures
|
||||
* @return A List of SHA-256 fingerprint strings, or null if an error occurs or no signatures are found.
|
||||
*/
|
||||
fun getAllSignatures(signingInfo: SigningInfo?): List<String>? {
|
||||
try {
|
||||
val signatures = mutableSetOf<String>()
|
||||
if (signingInfo != null) {
|
||||
// Includes past and current keys if rotation occurred. This is generally preferred.
|
||||
signingInfo.signingCertificateHistory?.forEach { signature ->
|
||||
signatureToSha256Fingerprint(signature)?.let { signatures.add(it) }
|
||||
}
|
||||
// If only one signer and history is empty (e.g. new app), this might be needed.
|
||||
// Or if multiple signers are explicitly used for the APK content.
|
||||
if (signingInfo.hasMultipleSigners()) {
|
||||
signingInfo.apkContentsSigners?.forEach { signature ->
|
||||
signatureToSha256Fingerprint(signature)?.let { signatures.add(it) }
|
||||
}
|
||||
} else { // Fallback for single signer if history was somehow null/empty
|
||||
signingInfo.signingCertificateHistory?.firstOrNull()?.let {
|
||||
signatureToSha256Fingerprint(it)?.let { fp -> signatures.add(fp) }
|
||||
}
|
||||
}
|
||||
}
|
||||
return if (signatures.isEmpty()) null else signatures.toList()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error getting signatures", e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Combines a list of signature into a single string for database storage.
|
||||
*
|
||||
* @return A single string with fingerprints joined by a delimiter, or null if the input list is null or empty.
|
||||
*/
|
||||
private fun getApplicationSignatures(signingInfo: SigningInfo?): String? {
|
||||
val fingerprints = getAllSignatures(signingInfo)
|
||||
if (fingerprints.isNullOrEmpty()) {
|
||||
return null
|
||||
}
|
||||
return fingerprints.joinToString(SIGNATURE_DELIMITER)
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds an Android Origin from a package name.
|
||||
*/
|
||||
@@ -194,7 +118,5 @@ class OriginManager(
|
||||
|
||||
companion object {
|
||||
private val TAG = OriginManager::class.simpleName
|
||||
|
||||
private const val SIGNATURE_DELIMITER = "##SIG##"
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,12 @@
|
||||
package com.kunzisoft.asymmetric
|
||||
|
||||
import org.bouncycastle.jcajce.provider.asymmetric.edec.BCEdDSAPrivateKey
|
||||
import org.bouncycastle.jcajce.provider.asymmetric.edec.BCXDHPrivateKey
|
||||
import org.junit.Test
|
||||
import java.io.File
|
||||
import java.io.FileWriter
|
||||
import kotlin.io.path.Path
|
||||
|
||||
class SignatureTest {
|
||||
|
||||
// All private keys are for testing only.
|
||||
// DO NOT USE THEM
|
||||
|
||||
|
||||
// region ES256
|
||||
private val es256PemInKeePassXC =
|
||||
"""
|
||||
@@ -159,9 +153,5 @@ class SignatureTest {
|
||||
assert(privateKeyPem.contains("-----BEGIN EC PRIVATE KEY-----", true).not())
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// endregion
|
||||
|
||||
}
|
||||
@@ -19,6 +19,11 @@
|
||||
*/
|
||||
package com.kunzisoft.encrypt
|
||||
|
||||
import android.content.pm.Signature
|
||||
import android.content.pm.SigningInfo
|
||||
import android.os.Build
|
||||
import android.util.AndroidException
|
||||
import android.util.Log
|
||||
import org.bouncycastle.crypto.engines.ChaCha7539Engine
|
||||
import org.bouncycastle.crypto.engines.Salsa20Engine
|
||||
import org.bouncycastle.crypto.params.KeyParameter
|
||||
@@ -26,9 +31,14 @@ import org.bouncycastle.crypto.params.ParametersWithIV
|
||||
import java.io.IOException
|
||||
import java.security.MessageDigest
|
||||
import java.security.NoSuchAlgorithmException
|
||||
import java.security.cert.CertificateFactory
|
||||
import java.security.cert.X509Certificate
|
||||
import java.util.Locale
|
||||
|
||||
object HashManager {
|
||||
|
||||
private val TAG = HashManager::class.simpleName
|
||||
|
||||
fun getHash256(): MessageDigest {
|
||||
val messageDigest: MessageDigest
|
||||
try {
|
||||
@@ -107,4 +117,77 @@ object HashManager {
|
||||
|
||||
return StreamCipher(cipher)
|
||||
}
|
||||
|
||||
private const val SIGNATURE_DELIMITER = "##SIG##"
|
||||
|
||||
/**
|
||||
* Converts a Signature object into its SHA-256 fingerprint string.
|
||||
* The fingerprint is typically represented as uppercase hex characters separated by colons.
|
||||
*/
|
||||
private fun signatureToSha256Fingerprint(signature: Signature): String? {
|
||||
return try {
|
||||
val certificateFactory = CertificateFactory.getInstance("X.509")
|
||||
val x509Certificate = certificateFactory.generateCertificate(
|
||||
signature.toByteArray().inputStream()
|
||||
) as X509Certificate
|
||||
|
||||
val messageDigest = MessageDigest.getInstance("SHA-256")
|
||||
val digest = messageDigest.digest(x509Certificate.encoded)
|
||||
|
||||
// Format as colon-separated HEX uppercase string
|
||||
digest.joinToString(separator = ":") { byte -> "%02X".format(byte) }
|
||||
.uppercase(Locale.US)
|
||||
} catch (e: Exception) {
|
||||
Log.e("SigningInfoUtil", "Error converting signature to SHA-256 fingerprint", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all relevant SHA-256 signature fingerprints for a given package.
|
||||
*
|
||||
* @param signingInfo The SigningInfo object to retrieve the strings signatures
|
||||
* @return A List of SHA-256 fingerprint strings, or null if an error occurs or no signatures are found.
|
||||
*/
|
||||
fun getAllSignatures(signingInfo: SigningInfo?): List<String>? {
|
||||
try {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P)
|
||||
throw AndroidException("API level ${Build.VERSION.SDK_INT} not supported")
|
||||
val signatures = mutableSetOf<String>()
|
||||
if (signingInfo != null) {
|
||||
// Includes past and current keys if rotation occurred. This is generally preferred.
|
||||
signingInfo.signingCertificateHistory?.forEach { signature ->
|
||||
signatureToSha256Fingerprint(signature)?.let { signatures.add(it) }
|
||||
}
|
||||
// If only one signer and history is empty (e.g. new app), this might be needed.
|
||||
// Or if multiple signers are explicitly used for the APK content.
|
||||
if (signingInfo.hasMultipleSigners()) {
|
||||
signingInfo.apkContentsSigners?.forEach { signature ->
|
||||
signatureToSha256Fingerprint(signature)?.let { signatures.add(it) }
|
||||
}
|
||||
} else { // Fallback for single signer if history was somehow null/empty
|
||||
signingInfo.signingCertificateHistory?.firstOrNull()?.let {
|
||||
signatureToSha256Fingerprint(it)?.let { fp -> signatures.add(fp) }
|
||||
}
|
||||
}
|
||||
}
|
||||
return if (signatures.isEmpty()) null else signatures.toList()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error getting signatures", e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Combines a list of signature into a single string for database storage.
|
||||
*
|
||||
* @return A single string with fingerprints joined by a delimiter, or null if the input list is null or empty.
|
||||
*/
|
||||
fun SigningInfo.getApplicationSignatures(): String? {
|
||||
val fingerprints = getAllSignatures(this)
|
||||
if (fingerprints.isNullOrEmpty()) {
|
||||
return null
|
||||
}
|
||||
return fingerprints.joinToString(SIGNATURE_DELIMITER)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user