diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 8d689678e..c728cd337 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -105,6 +105,12 @@ -keep,allowobfuscation,allowshrinking class com.google.gson.** { *; } -keep,allowobfuscation,allowshrinking class * extends com.google.gson.** { *; } +# Keep ConfigurationProvider's List cert fields (tslCerts / certBundle), +-keepclassmembers,allowobfuscation class ee.ria.DigiDoc.** { + @com.google.gson.annotations.SerializedName ; + @com.google.gson.annotations.JsonAdapter ; +} + # BouncyCastle -keep class org.bouncycastle.** { *; } diff --git a/app/src/androidTest/kotlin/ee/ria/DigiDoc/viewmodel/NFCViewModelTest.kt b/app/src/androidTest/kotlin/ee/ria/DigiDoc/viewmodel/NFCViewModelTest.kt index c0eae8785..a76ac8682 100644 --- a/app/src/androidTest/kotlin/ee/ria/DigiDoc/viewmodel/NFCViewModelTest.kt +++ b/app/src/androidTest/kotlin/ee/ria/DigiDoc/viewmodel/NFCViewModelTest.kt @@ -429,8 +429,10 @@ class NFCViewModelTest { null, ) + advanceUntilIdle() job.cancel() + assertTrue(values.isNotEmpty()) assertNull(values.last()) val signStatusObserver: Observer = mock() diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/fragment/screen/AdvancedSettingsScreen.kt b/app/src/main/kotlin/ee/ria/DigiDoc/fragment/screen/AdvancedSettingsScreen.kt index 3c808fda4..3f56a435f 100644 --- a/app/src/main/kotlin/ee/ria/DigiDoc/fragment/screen/AdvancedSettingsScreen.kt +++ b/app/src/main/kotlin/ee/ria/DigiDoc/fragment/screen/AdvancedSettingsScreen.kt @@ -42,6 +42,7 @@ import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember diff --git a/config-lib/src/main/kotlin/ee/ria/DigiDoc/configuration/utils/TSLUtil.kt b/config-lib/src/main/kotlin/ee/ria/DigiDoc/configuration/utils/TSLUtil.kt index 0556d9aa2..567e80467 100644 --- a/config-lib/src/main/kotlin/ee/ria/DigiDoc/configuration/utils/TSLUtil.kt +++ b/config-lib/src/main/kotlin/ee/ria/DigiDoc/configuration/utils/TSLUtil.kt @@ -61,6 +61,7 @@ object TSLUtil { } createDirectoryIfNotExist(destination) + debugLog(logTag, "Setting up TSL files in cache; bundled in assets: ${tslFiles?.joinToString() ?: "none"}") if (!tslFiles.isNullOrEmpty()) { for (fileName in tslFiles) { if (isXMLFile(fileName) && shouldCopyTSL(context, assetsPath, fileName, destination)) { @@ -68,6 +69,7 @@ object TSLUtil { val tslFile = File(destination, fileName) setFileDateAttributes(tslFile) removeExistingETag(tslFile.path) + debugLog(logTag, "Copied TSL '$fileName' from assets into cache (${tslFile.length()} bytes)") } } } @@ -82,20 +84,28 @@ object TSLUtil { fileName: String, destinationDir: String, ): Boolean { - if (!FileUtil.fileExists(File(destinationDir, fileName).path)) { + val cachedFile = File(destinationDir, fileName) + if (!FileUtil.fileExists(cachedFile.path)) { + debugLog(logTag, "TSL '$fileName' is not in the cache yet; copying it from assets") return true } else { try { context.assets .open(File(sourcePath, fileName).path) .use { assetsTSLInputStream -> - FileInputStream(File(destinationDir, fileName)) + FileInputStream(cachedFile) .use { cachedTSLInputStream -> val assetsTslVersion: Int = readSequenceNumber(assetsTSLInputStream) val cachedTslVersion: Int = readSequenceNumber(cachedTSLInputStream) - return assetsTslVersion > cachedTslVersion + val isAssetNewer = assetsTslVersion > cachedTslVersion + debugLog( + logTag, + "TSL '$fileName': assets version $assetsTslVersion, cached version " + + "$cachedTslVersion — ${if (isAssetNewer) "updating cache" else "cache is up to date"}", + ) + return isAssetNewer } } } catch (e: Exception) { diff --git a/libdigidoc-lib/src/main/kotlin/ee/ria/DigiDoc/libdigidoclib/domain/model/ContainerWrapper.kt b/libdigidoc-lib/src/main/kotlin/ee/ria/DigiDoc/libdigidoclib/domain/model/ContainerWrapper.kt index e4bc5c450..9d2a63104 100644 --- a/libdigidoc-lib/src/main/kotlin/ee/ria/DigiDoc/libdigidoclib/domain/model/ContainerWrapper.kt +++ b/libdigidoc-lib/src/main/kotlin/ee/ria/DigiDoc/libdigidoclib/domain/model/ContainerWrapper.kt @@ -22,6 +22,8 @@ package ee.ria.DigiDoc.libdigidoclib.domain.model import ee.ria.DigiDoc.libdigidoclib.SignedContainer +import ee.ria.DigiDoc.utilsLib.logging.LoggingUtil.Companion.debugLog +import ee.ria.DigiDoc.utilsLib.logging.LoggingUtil.Companion.errorLog import ee.ria.DigiDoc.utilsLib.text.TextUtil.removeEmptyStrings import ee.ria.libdigidocpp.ExternalSigner import ee.ria.libdigidocpp.Signature @@ -46,6 +48,7 @@ interface ContainerWrapper { class ContainerWrapperImpl : ContainerWrapper { private lateinit var signature: Signature + private val logTag = "Libdigidoc-ContainerWrapper" @Throws(CertificateException::class) override fun prepareSignature( @@ -54,6 +57,7 @@ class ContainerWrapperImpl : ContainerWrapper { cert: ByteArray?, roleData: RoleData?, ): ByteArray { + debugLog(logTag, "Preparing signature (with role data: ${roleData != null})") signature = when { roleData != null && signedContainer != null -> { @@ -75,7 +79,9 @@ class ContainerWrapperImpl : ContainerWrapper { } else -> throw IllegalStateException("Unable to get container") } - return signature.dataToSign() + val dataToSign = signature.dataToSign() + debugLog(logTag, "Signature prepared (${dataToSign.size} bytes to sign)") + return dataToSign } override fun finalizeSignature( @@ -84,7 +90,14 @@ class ContainerWrapperImpl : ContainerWrapper { signatureArray: ByteArray, ) { signature.setSignatureValue(signatureArray) - signature.extendSignatureProfile(signer) + debugLog(logTag, "Extending signature profile (fetches OCSP confirmation and timestamp)") + try { + signature.extendSignatureProfile(signer) + } catch (e: Exception) { + errorLog(logTag, "Unable to extend signature profile: ${e.message}", e) + throw e + } signedContainer?.rawContainer()?.save() + debugLog(logTag, "Signature finalized and container saved") } } diff --git a/libdigidoc-lib/src/main/kotlin/ee/ria/DigiDoc/libdigidoclib/init/Initialization.kt b/libdigidoc-lib/src/main/kotlin/ee/ria/DigiDoc/libdigidoclib/init/Initialization.kt index f8e59dd0d..f1bda3dde 100644 --- a/libdigidoc-lib/src/main/kotlin/ee/ria/DigiDoc/libdigidoclib/init/Initialization.kt +++ b/libdigidoc-lib/src/main/kotlin/ee/ria/DigiDoc/libdigidoclib/init/Initialization.kt @@ -93,6 +93,7 @@ class Initialization isLoggingEnabled: Boolean = false, ) { if (isInitialized) { + debugLog(libdigidocInitLogTag, "libdigidocpp is already initialized; only refreshing the log level") setLibdigidocppLogLevel(isLoggingEnabled) throw AlreadyInitializedException("Libdigidocpp is already initialized") } @@ -111,6 +112,10 @@ class Initialization throw erre } + debugLog( + libdigidocInitLogTag, + "TSL cache directory contains: ${getSchemaDir(context).list()?.joinToString() ?: "(empty)"}", + ) initLibDigiDocpp( context, getSchemaPath(context), @@ -130,6 +135,7 @@ class Initialization ) digidoc.initializeLib(UserAgentUtil.getAppInfo(context), path) UserAgentUtil.setLibdigidocppVersion(digidoc.version()) + debugLog(libdigidocInitLogTag, "Initialized libdigidocpp ${digidoc.version()} (TSL cache: $path)") isInitialized = true } @@ -189,6 +195,16 @@ class Initialization context: Context, configurationProvider: ConfigurationProvider, ) { + debugLog( + libdigidocInitLogTag, + "Applying configuration to libdigidocpp — " + + "TSL URL: ${configurationProvider.tslUrl}, TSA URL: ${configurationProvider.tsaUrl}, " + + "SiVa URL: ${configurationProvider.sivaUrl}, " + + "TSL signer certs: ${configurationProvider.tslCerts.size}, " + + "trust bundle certs: ${configurationProvider.certBundle.size}, " + + "config serial: ${configurationProvider.metaInf.serial}", + ) + overrideTSLUrl(configurationProvider.tslUrl) overrideTSLCert(configurationProvider.tslCerts) overrideSivaUrl(configurationProvider.sivaUrl) @@ -418,9 +434,19 @@ class Initialization } private fun loadConfiguration(context: Context) { - configurationRepository.getConfiguration()?.let { overrideConfiguration(context, it) } + val current = configurationRepository.getConfiguration() + debugLog( + libdigidocInitLogTag, + if (current == null) { + "No cached configuration yet; will apply it once it is loaded" + } else { + "Applying cached configuration" + }, + ) + current?.let { overrideConfiguration(context, it) } CoroutineScope(Main).launch { configurationRepository.observeConfigurationUpdates { newConfig -> + debugLog(libdigidocInitLogTag, "Configuration updated; reapplying it to libdigidocpp") overrideConfiguration(context, newConfig) } } diff --git a/libdigidoc-lib/src/main/kotlin/ee/ria/DigiDoc/libdigidoclib/utils/FileUtils.kt b/libdigidoc-lib/src/main/kotlin/ee/ria/DigiDoc/libdigidoclib/utils/FileUtils.kt index 3560fb155..5234bf987 100644 --- a/libdigidoc-lib/src/main/kotlin/ee/ria/DigiDoc/libdigidoclib/utils/FileUtils.kt +++ b/libdigidoc-lib/src/main/kotlin/ee/ria/DigiDoc/libdigidoclib/utils/FileUtils.kt @@ -68,6 +68,8 @@ object FileUtils { errorLog(LIBDIGIDOC_FILEUTILS_LOG_TAG, "Unable to get 'schema' resource", nfe) throw nfe } + debugLog(LIBDIGIDOC_FILEUTILS_LOG_TAG, "Extracting XML schema into ${schemaDir.absolutePath}") + val extractedFiles = mutableListOf() schemaResourceInputStream.use { inputStream -> ZipInputStream(inputStream).use { zipInputStream -> var entry: ZipEntry? @@ -78,9 +80,11 @@ object FileUtils { throw ZipException("Bad zip entry: $entryName") } Files.copy(zipInputStream, Paths.get(entryFile.toURI()), StandardCopyOption.REPLACE_EXISTING) + extractedFiles.add(entryName) } } } + debugLog(LIBDIGIDOC_FILEUTILS_LOG_TAG, "Extracted schema files: ${extractedFiles.joinToString()}") } private fun isChild(