From 4160b2fb6bc58a2794c33872a47904efb916abba Mon Sep 17 00:00:00 2001 From: Czarek Nakamoto Date: Wed, 5 Nov 2025 13:38:59 +0100 Subject: [PATCH] feat: encrypt log files --- .../workflows/automated_integration_test.yml | 325 +++++++++--------- .github/workflows/pr_test_build_android.yml | 10 +- .github/workflows/pr_test_build_linux.yml | 16 +- cw_bitcoin/pubspec.lock | 8 + cw_core/lib/encryption_log_utils.dart | 86 +++++ cw_core/lib/utils/print_verbose.dart | 7 +- cw_core/pubspec.lock | 8 + cw_core/pubspec.yaml | 3 +- cw_decred/pubspec.lock | 8 + cw_nano/pubspec.lock | 8 + cw_zano/pubspec.lock | 8 + lib/src/screens/settings/mweb_logs_page.dart | 36 +- .../settings/silent_payments_logs_page.dart | 36 +- lib/utils/exception_handler.dart | 17 + .../silent_payments_settings_view_model.dart | 7 +- tool/generate_secrets_config.dart | 3 + tool/import_secrets_config.dart | 30 +- tool/utils/secret_key.dart | 7 + 18 files changed, 409 insertions(+), 214 deletions(-) create mode 100644 cw_core/lib/encryption_log_utils.dart diff --git a/.github/workflows/automated_integration_test.yml b/.github/workflows/automated_integration_test.yml index 8ff5c137a2..677333dcb5 100644 --- a/.github/workflows/automated_integration_test.yml +++ b/.github/workflows/automated_integration_test.yml @@ -1,12 +1,11 @@ - name: Automated Integration Tests # Temporarily disabled - uncomment to re-enable -on: +on: # push: # pull_request: # branches: [main] - workflow_dispatch: # This is to manually trigger if needed + workflow_dispatch: # This is to manually trigger if needed defaults: run: shell: bash @@ -38,7 +37,7 @@ jobs: steps: - name: Fix github actions messing up $HOME... - run: 'echo HOME=/root | sudo tee -a $GITHUB_ENV' + run: "echo HOME=/root | sudo tee -a $GITHUB_ENV" - uses: actions/checkout@v4 with: ref: ${{ github.event.pull_request.head.sha || github.sha }} @@ -140,6 +139,8 @@ jobs: echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart echo "const chainStackApiKey = '${{ secrets.CHAIN_STACK_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart + echo "const logPassword = '${{ secrets.LOG_PASSWORD }}';" >> cw_core/lib/.secrets.g.dart + echo "const logSalt = '${{ secrets.LOG_SALT }}';" >> cw_core/lib/.secrets.g.dart echo "const testCakePayApiKey = '${{ secrets.TEST_CAKE_PAY_API_KEY }}';" >> lib/.secrets.g.dart echo "const cakePayApiKey = '${{ secrets.CAKE_PAY_API_KEY }}';" >> lib/.secrets.g.dart echo "const authorization = '${{ secrets.CAKE_PAY_AUTHORIZATION }}';" >> lib/.secrets.g.dart @@ -294,7 +295,7 @@ jobs: sanitized_branch_name=$(echo "$sanitized_branch_name" | sed 's/[^a-z0-9]//g') # Remove all special characters echo -e "id=com.cakewallet.test_${sanitized_branch_name}\nname=${BRANCH_NAME}" > android/app.properties - + - name: Build run: | flutter build apk --dart-define=hasDevOptions=true --release --split-per-abi @@ -304,7 +305,7 @@ jobs: sanitized_branch_name=$(grep '^id=' android/app.properties | cut -d'=' -f2 | sed 's/com\.cakewallet\.test_//') cd build/app/outputs/flutter-apk mkdir test-apk - + cp app-arm64-v8a-release.apk test-apk/${sanitized_branch_name}.apk cp app-x86_64-release.apk test-apk/${sanitized_branch_name}_x86.apk echo "APK files created: test-apk/${sanitized_branch_name}.apk and test-apk/${sanitized_branch_name}_x86.apk" @@ -315,7 +316,7 @@ jobs: set -x # Read the sanitized branch name from the app.properties file sanitized_branch_name=$(grep '^id=' android/app.properties | cut -d'=' -f2 | sed 's/com\.cakewallet\.test_//') - + echo "Looking for APK file: build/app/outputs/flutter-apk/test-apk/${sanitized_branch_name}.apk" ls -la build/app/outputs/flutter-apk/test-apk/ || echo "test-apk directory not found" apk_file=$(ls build/app/outputs/flutter-apk/test-apk/${sanitized_branch_name}.apk || exit 1) @@ -358,91 +359,91 @@ jobs: - name: 🔍 Verify AVD Configuration run: | echo "=== AVD Configuration Check ===" - + # Check if AVD directory exists echo "Checking AVD directory..." ls -la ~/.android/avd/ || echo "AVD directory not found" - + # List available AVDs echo "Available AVDs:" emulator -list-avds || echo "Failed to list AVDs" - + # Check Android SDK location echo "Android SDK location:" echo $ANDROID_HOME echo $ANDROID_SDK_ROOT - + # Check emulator binary echo "Emulator binary:" which emulator || echo "Emulator not found in PATH" emulator -version || echo "Failed to get emulator version" - + echo "=== AVD Check Complete ===" - name: đŸĻž Enable KVM run: | - echo "=== KVM Setup and Verification ===" - - # Check if KVM device exists - if [ -e /dev/kvm ]; then - echo "✅ KVM device found at /dev/kvm" - - # Check current permissions - echo "Current KVM permissions:" - ls -la /dev/kvm - - # Set proper permissions - sudo chmod 666 /dev/kvm || echo "âš ī¸ Failed to set KVM permissions" - - # Verify permissions were set - if [ -r /dev/kvm ] && [ -w /dev/kvm ]; then - echo "✅ KVM is readable and writable" - else - echo "âš ī¸ KVM permissions may not be optimal" - fi - - # Test KVM functionality - echo "Testing KVM functionality..." - if command -v kvm-ok >/dev/null 2>&1; then - if kvm-ok 2>/dev/null; then - echo "✅ KVM is working properly" - else - echo "âš ī¸ KVM check failed, but continuing..." - fi - else - echo "â„šī¸ kvm-ok command not available, testing manually..." - # Manual KVM test - if [ -r /dev/kvm ] && [ -w /dev/kvm ]; then - echo "✅ KVM device is accessible" - fi - fi - - # Try to create udev rules (may fail in container, that's OK) - echo "Setting up udev rules..." - sudo mkdir -p /etc/udev/rules.d || echo "âš ī¸ Could not create udev directory" - echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules || echo "âš ī¸ Could not create udev rule" - - # Try to reload udev rules (will likely fail in container, that's OK) - sudo udevadm control --reload-rules || echo "â„šī¸ udevadm control failed (expected in container)" - sudo udevadm trigger --name-match=kvm || echo "â„šī¸ udevadm trigger failed (expected in container)" - - else - echo "❌ KVM device not found at /dev/kvm" - echo "This will cause the emulator to run in software mode (slower)" - fi - - # Check system resources - echo "=== System Resources ===" - echo "Memory:" - free -h - echo "Disk space:" - df -h - echo "CPU cores:" - nproc - echo "KVM module loaded:" - lsmod | grep kvm || echo "KVM module not loaded (may be built-in)" - - echo "=== KVM Setup Complete ===" + echo "=== KVM Setup and Verification ===" + + # Check if KVM device exists + if [ -e /dev/kvm ]; then + echo "✅ KVM device found at /dev/kvm" + + # Check current permissions + echo "Current KVM permissions:" + ls -la /dev/kvm + + # Set proper permissions + sudo chmod 666 /dev/kvm || echo "âš ī¸ Failed to set KVM permissions" + + # Verify permissions were set + if [ -r /dev/kvm ] && [ -w /dev/kvm ]; then + echo "✅ KVM is readable and writable" + else + echo "âš ī¸ KVM permissions may not be optimal" + fi + + # Test KVM functionality + echo "Testing KVM functionality..." + if command -v kvm-ok >/dev/null 2>&1; then + if kvm-ok 2>/dev/null; then + echo "✅ KVM is working properly" + else + echo "âš ī¸ KVM check failed, but continuing..." + fi + else + echo "â„šī¸ kvm-ok command not available, testing manually..." + # Manual KVM test + if [ -r /dev/kvm ] && [ -w /dev/kvm ]; then + echo "✅ KVM device is accessible" + fi + fi + + # Try to create udev rules (may fail in container, that's OK) + echo "Setting up udev rules..." + sudo mkdir -p /etc/udev/rules.d || echo "âš ī¸ Could not create udev directory" + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules || echo "âš ī¸ Could not create udev rule" + + # Try to reload udev rules (will likely fail in container, that's OK) + sudo udevadm control --reload-rules || echo "â„šī¸ udevadm control failed (expected in container)" + sudo udevadm trigger --name-match=kvm || echo "â„šī¸ udevadm trigger failed (expected in container)" + + else + echo "❌ KVM device not found at /dev/kvm" + echo "This will cause the emulator to run in software mode (slower)" + fi + + # Check system resources + echo "=== System Resources ===" + echo "Memory:" + free -h + echo "Disk space:" + df -h + echo "CPU cores:" + nproc + echo "KVM module loaded:" + lsmod | grep kvm || echo "KVM module not loaded (may be built-in)" + + echo "=== KVM Setup Complete ===" - name: đŸĻž Cache gradle uses: gradle/actions/setup-gradle@v3 @@ -451,19 +452,19 @@ jobs: uses: actions/cache@v4 id: avd-cache with: - path: | - ~/.android/avd/* - ~/.android/adb* - key: avd-${{ matrix.api-level }} + path: | + ~/.android/avd/* + ~/.android/adb* + key: avd-${{ matrix.api-level }} - name: 🔧 Prepare emulator utilities run: | # Create a helper script for emulator readiness checks cat > /tmp/emulator_ready.sh << 'EOF' #!/bin/bash - + echo "=== Emulator Readiness Check ===" - + # Wait for boot completion echo "1. Checking boot completion..." timeout 300 bash -c 'until adb shell getprop sys.boot_completed 2>/dev/null | grep -q "1"; do sleep 5; echo " Waiting for boot completion..."; done' @@ -473,7 +474,7 @@ jobs: echo "❌ Boot completion timeout" echo "âš ī¸ Continuing anyway..." fi - + # Wait for input service echo "2. Checking input service..." timeout 60 bash -c 'until adb shell service list 2>/dev/null | grep -q "input"; do sleep 2; echo " Waiting for input service..."; done' @@ -483,7 +484,7 @@ jobs: echo "❌ Input service timeout" echo "âš ī¸ Continuing anyway..." fi - + # Wait for package manager echo "3. Checking package manager..." timeout 60 bash -c 'until adb shell pm list packages 2>/dev/null >/dev/null; do sleep 2; echo " Waiting for package manager..."; done' @@ -493,7 +494,7 @@ jobs: echo "❌ Package manager timeout" echo "âš ī¸ Continuing anyway..." fi - + # Wait for settings service echo "4. Checking settings service..." timeout 60 bash -c 'until adb shell settings get global window_animation_scale 2>/dev/null >/dev/null; do sleep 2; echo " Waiting for settings service..."; done' @@ -503,101 +504,101 @@ jobs: echo "❌ Settings service timeout" echo "âš ī¸ Continuing anyway..." fi - + echo "✅ Emulator readiness check completed" EOF - + chmod +x /tmp/emulator_ready.sh - name: đŸĻž Create AVD and generate snapshot for caching if: steps.avd-cache.outputs.cache-hit != 'true' uses: reactivecircus/android-emulator-runner@v2 with: - api-level: ${{ matrix.api-level }} - force-avd-creation: false - # arch: ${{ matrix.arch }} - emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none -read-only -memory 1024 -no-snapshot-save - working-directory: ${{ github.workspace }} - disable-animations: false - script: | - echo "=== AVD Snapshot Generation ===" - - # Use the helper script to ensure emulator is fully ready - /tmp/emulator_ready.sh - - # Manually disable animations with error handling - echo "Disabling animations manually..." - adb shell settings put global window_animation_scale 0.0 || echo "Failed to disable window animations" - adb shell settings put global transition_animation_scale 0.0 || echo "Failed to disable transition animations" - adb shell settings put global animator_duration_scale 0.0 || echo "Failed to disable animator duration" - - echo "=== Checking emulator status before termination ===" - adb devices || echo "ADB devices check failed" - echo "=== AVD snapshot generation complete ===" + api-level: ${{ matrix.api-level }} + force-avd-creation: false + # arch: ${{ matrix.arch }} + emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none -read-only -memory 1024 -no-snapshot-save + working-directory: ${{ github.workspace }} + disable-animations: false + script: | + echo "=== AVD Snapshot Generation ===" + + # Use the helper script to ensure emulator is fully ready + /tmp/emulator_ready.sh + + # Manually disable animations with error handling + echo "Disabling animations manually..." + adb shell settings put global window_animation_scale 0.0 || echo "Failed to disable window animations" + adb shell settings put global transition_animation_scale 0.0 || echo "Failed to disable transition animations" + adb shell settings put global animator_duration_scale 0.0 || echo "Failed to disable animator duration" + + echo "=== Checking emulator status before termination ===" + adb devices || echo "ADB devices check failed" + echo "=== AVD snapshot generation complete ===" - name: 🚀 Integration tests on Android Emulator timeout-minutes: 60 uses: reactivecircus/android-emulator-runner@v2 with: - api-level: ${{ matrix.api-level }} - force-avd-creation: false - emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none -read-only -memory 1024 -no-snapshot-save - disable-animations: false - working-directory: ${{ github.workspace }} - script: | - echo "=== Pre-test Environment Check ===" - pwd - ls -la integration_test_runner.sh || echo "integration_test_runner.sh not found" - - echo "=== Emulator Status Check ===" - adb devices - - echo "=== Emulator Info ===" - adb shell getprop ro.build.version.release || echo "Failed to get Android version" - adb shell getprop ro.product.model || echo "Failed to get device model" - - echo "=== Checking Emulator Performance ===" - adb shell cat /proc/cpuinfo | grep -i "model name" | head -1 || echo "Could not check CPU info" - adb shell cat /proc/meminfo | grep -i "memtotal" || echo "Could not check memory info" - - echo "=== Checking Emulator Process ===" - ps aux | grep emulator | grep -v grep || echo "Emulator process not found in ps output" - - echo "=== Making script executable ===" - chmod a+rx integration_test_runner.sh - - echo "=== Running integration tests ===" - - echo "Final emulator readiness check..." - /tmp/emulator_ready.sh || echo "Emulator readiness check failed, but continuing..." - - echo "Disabling animations for test performance..." - adb shell settings put global window_animation_scale 0.0 || echo "Failed to disable window animations" - adb shell settings put global transition_animation_scale 0.0 || echo "Failed to disable transition animations" - adb shell settings put global animator_duration_scale 0.0 || echo "Failed to disable animator duration" - - echo "Starting integration test runner..." - ./integration_test_runner.sh - TEST_EXIT_CODE=$? - - if [ $TEST_EXIT_CODE -eq 0 ]; then - echo "Integration tests completed successfully" - else - echo "Integration tests failed with exit code $TEST_EXIT_CODE" - fi + api-level: ${{ matrix.api-level }} + force-avd-creation: false + emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none -read-only -memory 1024 -no-snapshot-save + disable-animations: false + working-directory: ${{ github.workspace }} + script: | + echo "=== Pre-test Environment Check ===" + pwd + ls -la integration_test_runner.sh || echo "integration_test_runner.sh not found" + + echo "=== Emulator Status Check ===" + adb devices + + echo "=== Emulator Info ===" + adb shell getprop ro.build.version.release || echo "Failed to get Android version" + adb shell getprop ro.product.model || echo "Failed to get device model" + + echo "=== Checking Emulator Performance ===" + adb shell cat /proc/cpuinfo | grep -i "model name" | head -1 || echo "Could not check CPU info" + adb shell cat /proc/meminfo | grep -i "memtotal" || echo "Could not check memory info" + + echo "=== Checking Emulator Process ===" + ps aux | grep emulator | grep -v grep || echo "Emulator process not found in ps output" + + echo "=== Making script executable ===" + chmod a+rx integration_test_runner.sh + + echo "=== Running integration tests ===" + + echo "Final emulator readiness check..." + /tmp/emulator_ready.sh || echo "Emulator readiness check failed, but continuing..." + + echo "Disabling animations for test performance..." + adb shell settings put global window_animation_scale 0.0 || echo "Failed to disable window animations" + adb shell settings put global transition_animation_scale 0.0 || echo "Failed to disable transition animations" + adb shell settings put global animator_duration_scale 0.0 || echo "Failed to disable animator duration" + + echo "Starting integration test runner..." + ./integration_test_runner.sh + TEST_EXIT_CODE=$? + + if [ $TEST_EXIT_CODE -eq 0 ]; then + echo "Integration tests completed successfully" + else + echo "Integration tests failed with exit code $TEST_EXIT_CODE" + fi - name: 🧹 Post-test cleanup if: always() run: | - echo "=== Post-test cleanup ===" - # Kill any remaining emulator processes - pkill -f emulator || echo "No emulator processes to kill" - # Kill any remaining adb processes - pkill -f adb || echo "No adb processes to kill" - # Wait for processes to terminate - sleep 2 - # Check for any remaining processes - ps aux | grep -E "(emulator|adb)" | grep -v grep || echo "No remaining emulator/adb processes" - echo "=== Cleanup complete ===" + echo "=== Post-test cleanup ===" + # Kill any remaining emulator processes + pkill -f emulator || echo "No emulator processes to kill" + # Kill any remaining adb processes + pkill -f adb || echo "No adb processes to kill" + # Wait for processes to terminate + sleep 2 + # Check for any remaining processes + ps aux | grep -E "(emulator|adb)" | grep -v grep || echo "No remaining emulator/adb processes" + echo "=== Cleanup complete ===" - name: cleanup run: rm -rf build/app/outputs/flutter-apk/test-apk/ diff --git a/.github/workflows/pr_test_build_android.yml b/.github/workflows/pr_test_build_android.yml index 02f6e1b4c4..ab86c4ba0a 100644 --- a/.github/workflows/pr_test_build_android.yml +++ b/.github/workflows/pr_test_build_android.yml @@ -32,7 +32,7 @@ jobs: steps: - name: Fix github actions messing up $HOME... - run: 'echo HOME=/root | sudo tee -a $GITHUB_ENV' + run: "echo HOME=/root | sudo tee -a $GITHUB_ENV" - uses: actions/checkout@v4 with: ref: ${{ github.event.pull_request.head.sha }} @@ -134,6 +134,8 @@ jobs: echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart echo "const chainStackApiKey = '${{ secrets.CHAIN_STACK_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart + echo "const logPassword = '${{ secrets.LOG_PASSWORD }}';" >> cw_core/lib/.secrets.g.dart + echo "const logSalt = '${{ secrets.LOG_SALT }}';" >> cw_core/lib/.secrets.g.dart echo "const testCakePayApiKey = '${{ secrets.TEST_CAKE_PAY_API_KEY }}';" >> lib/.secrets.g.dart echo "const cakePayApiKey = '${{ secrets.CAKE_PAY_API_KEY }}';" >> lib/.secrets.g.dart echo "const authorization = '${{ secrets.CAKE_PAY_AUTHORIZATION }}';" >> lib/.secrets.g.dart @@ -234,7 +236,7 @@ jobs: wget https://github.com/MrCyjaneK/monero_c/releases/download/v0.18.4.0-RC9/release-bundle.zip unzip release-bundle.zip rm release-bundle.zip - unxz -fv release/*/*.xz + unxz -fv release/*/*.xz popd - name: Build Bitbox Flutter @@ -326,10 +328,10 @@ jobs: with: path: ${{ github.workspace }}/build/app/outputs/flutter-apk name: "android apk" - + - name: 16KB align run: | cd build/app/outputs/flutter-apk for i in arm64-v8a x86_64; do ../../../../scripts/android/check_16kb_align.sh app-$i-release.apk - done \ No newline at end of file + done diff --git a/.github/workflows/pr_test_build_linux.yml b/.github/workflows/pr_test_build_linux.yml index 903f77a754..c67dc6136f 100644 --- a/.github/workflows/pr_test_build_linux.yml +++ b/.github/workflows/pr_test_build_linux.yml @@ -25,7 +25,7 @@ jobs: steps: - name: Fix github actions messing up $HOME... - run: 'echo HOME=/root | sudo tee -a $GITHUB_ENV' + run: "echo HOME=/root | sudo tee -a $GITHUB_ENV" - uses: actions/checkout@v4 with: ref: ${{ github.event.pull_request.head.sha }} @@ -127,6 +127,8 @@ jobs: echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart echo "const chainStackApiKey = '${{ secrets.CHAIN_STACK_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart + echo "const logPassword = '${{ secrets.LOG_PASSWORD }}';" >> cw_core/lib/.secrets.g.dart + echo "const logSalt = '${{ secrets.LOG_SALT }}';" >> cw_core/lib/.secrets.g.dart echo "const testCakePayApiKey = '${{ secrets.TEST_CAKE_PAY_API_KEY }}';" >> lib/.secrets.g.dart echo "const cakePayApiKey = '${{ secrets.CAKE_PAY_API_KEY }}';" >> lib/.secrets.g.dart echo "const authorization = '${{ secrets.CAKE_PAY_AUTHORIZATION }}';" >> lib/.secrets.g.dart @@ -218,7 +220,7 @@ jobs: wget https://github.com/MrCyjaneK/monero_c/releases/download/v0.18.4.0-RC9/release-bundle.zip unzip release-bundle.zip rm release-bundle.zip - unxz -fv release/*/*.xz + unxz -fv release/*/*.xz popd - name: Build Bitbox Flutter @@ -268,11 +270,11 @@ jobs: - name: Prepare virtual desktop if: ${{ contains(env.message, 'run tests') }} run: | - nohup Xvfb :99 -screen 0 720x1280x16 & - echo DISPLAY=:99 | sudo tee -a $GITHUB_ENV - dbus-daemon --system --fork - nohup NetworkManager & - nohup ffmpeg -framerate 60 -video_size 720x1280 -f x11grab -i :99 -c:v libx264 -c:a aac /opt/screen_grab.mkv & + nohup Xvfb :99 -screen 0 720x1280x16 & + echo DISPLAY=:99 | sudo tee -a $GITHUB_ENV + dbus-daemon --system --fork + nohup NetworkManager & + nohup ffmpeg -framerate 60 -video_size 720x1280 -f x11grab -i :99 -c:v libx264 -c:a aac /opt/screen_grab.mkv & # Note for people adding tests: # - Tests are ran on Linux, with some things being mocked out. diff --git a/cw_bitcoin/pubspec.lock b/cw_bitcoin/pubspec.lock index 681e68d478..f39b315784 100644 --- a/cw_bitcoin/pubspec.lock +++ b/cw_bitcoin/pubspec.lock @@ -706,6 +706,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.4.5" + mutex: + dependency: transitive + description: + name: mutex + sha256: "8827da25de792088eb33e572115a5eb0d61d61a3c01acbc8bcbe76ed78f1a1f2" + url: "https://pub.dev" + source: hosted + version: "3.1.0" nested: dependency: transitive description: diff --git a/cw_core/lib/encryption_log_utils.dart b/cw_core/lib/encryption_log_utils.dart new file mode 100644 index 0000000000..d5b3c429f4 --- /dev/null +++ b/cw_core/lib/encryption_log_utils.dart @@ -0,0 +1,86 @@ +import 'dart:convert'; +import 'dart:io'; +import 'package:cryptography/cryptography.dart'; +import 'package:mutex/mutex.dart'; +import 'package:cw_core/.secrets.g.dart' as secrets; + +final logMutex = Mutex(); +final password = secrets.logPassword.isEmpty ? ':)' : secrets.logPassword; +final salt = secrets.logSalt.isEmpty ? '(:' : secrets.logSalt; + +class EncryptionLogUtil { + static final _algorithm = AesGcm.with256bits(); + static SecretKey? cachedKey = null; + static Future _deriveKey() async { + if (cachedKey != null) { + return cachedKey!; + } + final pbkdf2 = Pbkdf2( + macAlgorithm: Hmac.sha256(), + iterations: 120000, // OWASP recommendation: https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#pbkdf2 + bits: 256, + ); + final key = await pbkdf2.deriveKey( + secretKey: SecretKey(utf8.encode(password)), + nonce: utf8.encode(salt), + ); + cachedKey = key; + return key; + } + + static Future write({required String path, required String data}) async { + await logMutex.acquire(); + try { + final key = await _deriveKey(); + final secretKey = await _algorithm.newSecretKey(); + final iv = await secretKey.extractBytes(); + + final nonce = iv.sublist(0, 12); + + final secretBox = await _algorithm.encrypt( + utf8.encode(data), + secretKey: key, + nonce: nonce, + ); + + final line = base64.encode([...nonce, ...secretBox.cipherText, ...secretBox.mac.bytes]); + File(path).writeAsStringSync("$line\n", mode: FileMode.append); + } finally { + logMutex.release(); + } + } + + static Future read({required String path}) async { + await logMutex.acquire(); + try { + final key = await _deriveKey(); + final file = File(path); + final lines = file.readAsLinesSync(); + final sb = StringBuffer(); + + for (final line in lines) { + try { + final bytes = base64.decode(line); + final nonce = bytes.sublist(0, 12); + final cipherText = bytes.sublist(12, bytes.length - 16); + final macBytes = bytes.sublist(bytes.length - 16); + + final secretBox = SecretBox( + cipherText, + nonce: nonce, + mac: Mac(macBytes), + ); + + final decrypted = await _algorithm.decrypt(secretBox, secretKey: key); + sb.write(utf8.decode(decrypted)); + } catch (_) { + sb.writeln(line); + } + } + + return sb.toString(); + } finally { + logMutex.release(); + } + } +} diff --git a/cw_core/lib/utils/print_verbose.dart b/cw_core/lib/utils/print_verbose.dart index 69d4832c0c..0bc4a82621 100644 --- a/cw_core/lib/utils/print_verbose.dart +++ b/cw_core/lib/utils/print_verbose.dart @@ -1,5 +1,7 @@ +import 'dart:async'; import 'dart:io'; import 'dart:math'; +import 'package:cw_core/encryption_log_utils.dart'; import 'package:flutter/foundation.dart'; enum LogLevel { info, debug, warn, error } @@ -21,7 +23,10 @@ void printV( if (!logFile.existsSync()) { logFile.createSync(recursive: true); } - logFile.writeAsStringSync("$logLine\n", mode: FileMode.append, flush: true); + unawaited(EncryptionLogUtil.write( + path: logFile.path, + data: "$logLine\n", + )); } } diff --git a/cw_core/pubspec.lock b/cw_core/pubspec.lock index 918a81a0b5..2b13650a61 100644 --- a/cw_core/pubspec.lock +++ b/cw_core/pubspec.lock @@ -466,6 +466,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.7.1" + mutex: + dependency: "direct main" + description: + name: mutex + sha256: "8827da25de792088eb33e572115a5eb0d61d61a3c01acbc8bcbe76ed78f1a1f2" + url: "https://pub.dev" + source: hosted + version: "3.1.0" nested: dependency: transitive description: diff --git a/cw_core/pubspec.yaml b/cw_core/pubspec.yaml index 7c963f246e..4f086f5786 100644 --- a/cw_core/pubspec.yaml +++ b/cw_core/pubspec.yaml @@ -46,6 +46,7 @@ dependencies: ref: cake-update-v2 sqflite: ^2.4.1 sqflite_common_ffi: ^2.3.4+4 + mutex: ^3.1.0 dev_dependencies: flutter_test: @@ -63,7 +64,7 @@ dependency_overrides: # The following section is specific to Flutter. flutter: - uses-material-design: true + uses-material-design: true # To add assets to your package, add an assets section, like this: # assets: diff --git a/cw_decred/pubspec.lock b/cw_decred/pubspec.lock index 8558572154..33437031f3 100644 --- a/cw_decred/pubspec.lock +++ b/cw_decred/pubspec.lock @@ -489,6 +489,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.7.0" + mutex: + dependency: transitive + description: + name: mutex + sha256: "8827da25de792088eb33e572115a5eb0d61d61a3c01acbc8bcbe76ed78f1a1f2" + url: "https://pub.dev" + source: hosted + version: "3.1.0" nested: dependency: transitive description: diff --git a/cw_nano/pubspec.lock b/cw_nano/pubspec.lock index c009b65024..0922f9487a 100644 --- a/cw_nano/pubspec.lock +++ b/cw_nano/pubspec.lock @@ -534,6 +534,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.7.1" + mutex: + dependency: transitive + description: + name: mutex + sha256: "8827da25de792088eb33e572115a5eb0d61d61a3c01acbc8bcbe76ed78f1a1f2" + url: "https://pub.dev" + source: hosted + version: "3.1.0" nanodart: dependency: transitive description: diff --git a/cw_zano/pubspec.lock b/cw_zano/pubspec.lock index 0c4a57658d..79554000e9 100644 --- a/cw_zano/pubspec.lock +++ b/cw_zano/pubspec.lock @@ -503,6 +503,14 @@ packages: url: "https://github.com/mrcyjanek/monero_c" source: git version: "0.0.0" + mutex: + dependency: transitive + description: + name: mutex + sha256: "8827da25de792088eb33e572115a5eb0d61d61a3c01acbc8bcbe76ed78f1a1f2" + url: "https://pub.dev" + source: hosted + version: "3.1.0" nested: dependency: transitive description: diff --git a/lib/src/screens/settings/mweb_logs_page.dart b/lib/src/screens/settings/mweb_logs_page.dart index 8310673c5c..2f9108f5d8 100644 --- a/lib/src/screens/settings/mweb_logs_page.dart +++ b/lib/src/screens/settings/mweb_logs_page.dart @@ -7,7 +7,8 @@ import 'package:cake_wallet/utils/exception_handler.dart'; import 'package:cake_wallet/utils/share_util.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/view_model/settings/mweb_settings_view_model.dart'; -import 'package:cw_core/root_dir.dart'; +import 'package:cw_core/encryption_log_utils.dart'; +import 'package:path/path.dart' as p; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:path_provider/path_provider.dart'; @@ -30,7 +31,9 @@ class MwebLogsPage extends BasePage { builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return Center(child: CircularProgressIndicator()); - } else if (snapshot.hasError || !snapshot.hasData || snapshot.data!.isEmpty) { + } else if (snapshot.hasError || + !snapshot.hasData || + snapshot.data!.isEmpty) { return Center(child: Text('No logs found')); } else { return SingleChildScrollView( @@ -38,7 +41,10 @@ class MwebLogsPage extends BasePage { padding: EdgeInsets.all(16.0), child: Text( snapshot.data!, - style: Theme.of(context).textTheme.bodyMedium!.copyWith(fontFamily: 'Monospace'), + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith(fontFamily: 'Monospace'), ), ), ); @@ -98,16 +104,30 @@ class MwebLogsPage extends BasePage { } Future share(BuildContext context) async { - final inAppPath = "${(await getApplicationSupportDirectory()).path}/logs/debug.log"; - await ShareUtil.shareFile(filePath: inAppPath, fileName: "debug.log", context: context); + final inAppPath = + "${(await getApplicationSupportDirectory()).path}/logs/debug.log"; + final tmp = await getTemporaryDirectory(); + final tmpPath = p.join(tmp.path, "plain_logs"); + final tmpDir = Directory(tmpPath); + if (!tmpDir.existsSync()) { + tmpDir.createSync(recursive: true); + } + final decryptedFile = File(p.join(tmpPath, p.basename(inAppPath))); + final str = await EncryptionLogUtil.read(path: inAppPath); + decryptedFile.writeAsStringSync(str); + await ShareUtil.shareFile( + filePath: decryptedFile.path, fileName: "debug.log", context: context); + decryptedFile.writeAsStringSync(""); } Future _saveFile() async { - String? outputFile = await FilePicker.platform - .saveFile(dialogTitle: 'Save Your File to desired location', fileName: "debug.log"); + String? outputFile = await FilePicker.platform.saveFile( + dialogTitle: 'Save Your File to desired location', + fileName: "debug.log"); try { - final filePath = (await getApplicationSupportDirectory()).path + "/debug.log"; + final filePath = + (await getApplicationSupportDirectory()).path + "/debug.log"; File debugLogFile = File(filePath); await debugLogFile.copy(outputFile!); } catch (exception, stackTrace) { diff --git a/lib/src/screens/settings/silent_payments_logs_page.dart b/lib/src/screens/settings/silent_payments_logs_page.dart index 590f2fb40b..8663fc6731 100644 --- a/lib/src/screens/settings/silent_payments_logs_page.dart +++ b/lib/src/screens/settings/silent_payments_logs_page.dart @@ -7,9 +7,11 @@ import 'package:cake_wallet/utils/exception_handler.dart'; import 'package:cake_wallet/utils/share_util.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/view_model/settings/silent_payments_settings_view_model.dart'; +import 'package:cw_core/encryption_log_utils.dart'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:path_provider/path_provider.dart'; +import 'package:path/path.dart' as p; class SilentPaymentsLogPage extends BasePage { SilentPaymentsLogPage(this.silentPaymentsSettingsViewModelBase); @@ -29,7 +31,9 @@ class SilentPaymentsLogPage extends BasePage { builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return Center(child: CircularProgressIndicator()); - } else if (snapshot.hasError || !snapshot.hasData || snapshot.data!.isEmpty) { + } else if (snapshot.hasError || + !snapshot.hasData || + snapshot.data!.isEmpty) { return Center(child: Text('No logs found')); } else { return SingleChildScrollView( @@ -37,8 +41,10 @@ class SilentPaymentsLogPage extends BasePage { padding: EdgeInsets.all(16.0), child: Text( snapshot.data!, - style: - Theme.of(context).textTheme.bodyMedium!.copyWith(fontFamily: 'Monospace'), + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith(fontFamily: 'Monospace'), ), ), ); @@ -98,16 +104,30 @@ class SilentPaymentsLogPage extends BasePage { } Future share(BuildContext context) async { - final inAppPath = "${(await getApplicationSupportDirectory()).path}/logs/debug.log"; - await ShareUtil.shareFile(filePath: inAppPath, fileName: "debug.log", context: context); + final inAppPath = + "${(await getApplicationSupportDirectory()).path}/logs/debug.log"; + final tmp = await getTemporaryDirectory(); + final tmpPath = p.join(tmp.path, "plain_logs"); + final tmpDir = Directory(tmpPath); + if (!tmpDir.existsSync()) { + tmpDir.createSync(recursive: true); + } + final decryptedFile = File(p.join(tmpPath, p.basename(inAppPath))); + final str = await EncryptionLogUtil.read(path: inAppPath); + decryptedFile.writeAsStringSync(str); + await ShareUtil.shareFile( + filePath: decryptedFile.path, fileName: "debug.log", context: context); + decryptedFile.writeAsStringSync(""); } Future _saveFile() async { - String? outputFile = await FilePicker.platform - .saveFile(dialogTitle: 'Save Your File to desired location', fileName: "debug.log"); + String? outputFile = await FilePicker.platform.saveFile( + dialogTitle: 'Save Your File to desired location', + fileName: "debug.log"); try { - final filePath = (await getApplicationSupportDirectory()).path + "/debug.log"; + final filePath = + (await getApplicationSupportDirectory()).path + "/debug.log"; File debugLogFile = File(filePath); await debugLogFile.copy(outputFile!); } catch (exception, stackTrace) { diff --git a/lib/utils/exception_handler.dart b/lib/utils/exception_handler.dart index 5733f648ad..1bd2b5e8a2 100644 --- a/lib/utils/exception_handler.dart +++ b/lib/utils/exception_handler.dart @@ -10,6 +10,7 @@ import 'package:cake_wallet/store/app_store.dart'; import 'package:cake_wallet/utils/package_info.dart'; import 'package:cake_wallet/utils/show_bar.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; +import 'package:cw_core/encryption_log_utils.dart'; import 'package:cw_core/root_dir.dart'; import 'package:cw_core/utils/print_verbose.dart'; import 'package:device_info_plus/device_info_plus.dart'; @@ -17,6 +18,8 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_mailer/flutter_mailer.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:path/path.dart' as p; import 'package:shared_preferences/shared_preferences.dart'; class ExceptionHandler { @@ -88,6 +91,19 @@ class ExceptionHandler { return; } + final tmp = await getTemporaryDirectory(); + final tmpPath = p.join(tmp.path, "plain_logs"); + final tmpDir = Directory(tmpPath); + if (!tmpDir.existsSync()) { + tmpDir.createSync(recursive: true); + } + final decryptedFile = File(p.join(tmpPath, p.basename(_file!.path))); + final str = await EncryptionLogUtil.read( + path: _file!.path + ); + + decryptedFile.writeAsStringSync(str); + final MailOptions mailOptions = MailOptions( subject: 'Mobile App Issue', recipients: ['support@cakewallet.com'], @@ -102,6 +118,7 @@ class ExceptionHandler { result.name == MailerResponse.saved.name || result.name == MailerResponse.android.name) { _file!.writeAsString("", mode: FileMode.write); + decryptedFile.delete(); } } catch (e, s) { _saveException(e.toString(), s); diff --git a/lib/view_model/settings/silent_payments_settings_view_model.dart b/lib/view_model/settings/silent_payments_settings_view_model.dart index 9f0b4a5348..0246185ec3 100644 --- a/lib/view_model/settings/silent_payments_settings_view_model.dart +++ b/lib/view_model/settings/silent_payments_settings_view_model.dart @@ -2,6 +2,7 @@ import 'dart:io'; import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/store/settings_store.dart'; +import 'package:cw_core/encryption_log_utils.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:mobx/mobx.dart'; import 'package:path_provider/path_provider.dart'; @@ -36,12 +37,12 @@ abstract class SilentPaymentsSettingsViewModelBase with Store { Future getAbbreviatedLogs() async { final appSupportPath = (await getApplicationSupportDirectory()).path; - final logsFile = File("$appSupportPath/logs/debug.log"); + final fpath = "$appSupportPath/logs/debug.log"; + final logsFile = File(fpath); if (!logsFile.existsSync()) { return ""; } - final logs = logsFile.readAsStringSync(); - + final logs = await EncryptionLogUtil.read(path: fpath); // return last 10000 characters: return logs.substring(logs.length > 10000 ? logs.length - 10000 : 0); } diff --git a/tool/generate_secrets_config.dart b/tool/generate_secrets_config.dart index 8e9762b7a0..8ae4ed3030 100644 --- a/tool/generate_secrets_config.dart +++ b/tool/generate_secrets_config.dart @@ -6,6 +6,7 @@ import 'utils/utils.dart'; const baseConfigPath = 'tool/.secrets-config.json'; const evmChainsConfigPath = 'tool/.evm-secrets-config.json'; const solanaConfigPath = 'tool/.solana-secrets-config.json'; +const coreConfigPath = 'tool/.core-secrets-config.json'; const nanoConfigPath = 'tool/.nano-secrets-config.json'; const tronConfigPath = 'tool/.tron-secrets-config.json'; @@ -39,6 +40,7 @@ Future generateSecretsConfig(List args) async { final baseConfigFile = File(baseConfigPath); final evmChainsConfigFile = File(evmChainsConfigPath); final solanaConfigFile = File(solanaConfigPath); + final coreConfigFile = File(coreConfigPath); final nanoConfigFile = File(nanoConfigPath); final tronConfigFile = File(tronConfigPath); @@ -64,6 +66,7 @@ Future generateSecretsConfig(List args) async { await writeConfig(evmChainsConfigFile, SecretKey.evmChainsSecrets); await writeConfig(solanaConfigFile, SecretKey.solanaSecrets); + await writeConfig(coreConfigFile, SecretKey.coreSecrets); await writeConfig(nanoConfigFile, SecretKey.nanoSecrets); await writeConfig(tronConfigFile, SecretKey.tronSecrets); } diff --git a/tool/import_secrets_config.dart b/tool/import_secrets_config.dart index 42379021f5..14bd1068d4 100644 --- a/tool/import_secrets_config.dart +++ b/tool/import_secrets_config.dart @@ -5,6 +5,9 @@ import 'utils/utils.dart'; const configPath = 'tool/.secrets-config.json'; const outputPath = 'lib/.secrets.g.dart'; +const coreConfigPath = 'tool/.core-secrets-config.json'; +const coreOutputPath = 'cw_core/lib/.secrets.g.dart'; + const evmChainsConfigPath = 'tool/.evm-secrets-config.json'; const evmChainsOutputPath = 'cw_evm/lib/.secrets.g.dart'; @@ -24,6 +27,12 @@ Future importSecretsConfig() async { final input = json.decode(File(configPath).readAsStringSync()) as Map; final output = input.keys.fold('', (String acc, String val) => acc + generateConst(val, input)); + final coreOutputFile = File(coreOutputPath); + final coreInput = + json.decode(File(coreConfigPath).readAsStringSync()) as Map; + final coreOutput = + coreInput.keys.fold('', (String acc, String val) => acc + generateConst(val, coreInput)); + final evmChainsOutputFile = File(evmChainsOutputPath); final evmChainsInput = json.decode(File(evmChainsConfigPath).readAsStringSync()) as Map; @@ -51,28 +60,9 @@ Future importSecretsConfig() async { } await outputFile.writeAsString(output); - - if (evmChainsOutputFile.existsSync()) { - await evmChainsOutputFile.delete(); - } - + await coreOutputFile.writeAsString(coreOutput); await evmChainsOutputFile.writeAsString(evmChainsOutput); - - if (solanaOutputFile.existsSync()) { - await solanaOutputFile.delete(); - } - await solanaOutputFile.writeAsString(solanaOutput); - - if (tronOutputFile.existsSync()) { - await tronOutputFile.delete(); - } - await tronOutputFile.writeAsString(tronOutput); - - if (nanoOutputFile.existsSync()) { - await nanoOutputFile.delete(); - } - await nanoOutputFile.writeAsString(nanoOutput); } diff --git a/tool/utils/secret_key.dart b/tool/utils/secret_key.dart index c02c591765..64f3fe05ef 100644 --- a/tool/utils/secret_key.dart +++ b/tool/utils/secret_key.dart @@ -85,6 +85,8 @@ class SecretKey { SecretKey('kryptonimApiKey', () => ''), SecretKey('walletGroupSalt', () => hex.encode(encrypt.Key.fromSecureRandom(16).bytes)), SecretKey('swapsXyzApiKey', () => ''), + SecretKey('logPassword', () => ''), + SecretKey('logSalt', () => ''), ]; static final evmChainsSecrets = [ @@ -99,6 +101,11 @@ class SecretKey { SecretKey('nowNodesApiKey', () => ''), SecretKey('chainStackApiKey', () => ''), ]; + + static final coreSecrets = [ + SecretKey('logPassword', () => ''), + SecretKey('logSalt', () => ''), + ]; static final nanoSecrets = [ SecretKey('nano2ApiKey', () => ''),