diff --git a/.env.secret.example b/.env.secret.example new file mode 100644 index 000000000..dbb2fdd15 --- /dev/null +++ b/.env.secret.example @@ -0,0 +1,11 @@ +FERAL_FILE_SECRET_KEY_TESTNET= +FERAL_FILE_SECRET_KEY_MAINNET= +CHAT_SERVER_HMAC_KEY= +AU_CLAIM_SECRET_KEY= +MIXPANEL_KEY= +METRIC_SECRET_KEY= +BRANCH_KEY= +SENTRY_DSN= +ONESIGNAL_APP_ID= +METRIC_ENDPOINT= +WEB3_RPC_MAINNET_URL= \ No newline at end of file diff --git a/.github/workflows/android-release-appcenter.yaml b/.github/workflows/android-release-appcenter.yaml index 5e6e4635f..2a1369e0a 100644 --- a/.github/workflows/android-release-appcenter.yaml +++ b/.github/workflows/android-release-appcenter.yaml @@ -48,10 +48,22 @@ jobs: ${{ inputs.testnet == true }} && echo -e "${{ secrets.DEV_ENV }}" | sed 's/\\n/\n/g' >> .env || echo -e "${{ secrets.STAGING_ENV }}" | sed 's/\\n/\n/g' >> .env cat .env + - name: Create env secret file + run: | + touch .env.secret + ${{ inputs.testnet == true }} && echo -e "${{ secrets.DEV_ENV_SECRET }}" | sed 's/\\n/\n/g' >> .env.secret || echo -e "${{ secrets.STAGING_ENV_SECRET }}" | sed 's/\\n/\n/g' >> .env.secret + cat .env.secret + - name: Submoudles update run: git -c submodule.auto-test.update=none submodule update --init --recursive - run: flutter pub cache repair - run: flutter pub get + + # Encrypt secrets before use + - name: Encrypt Secrets + run: | + ./script/encrypt_secrets.sh ${{ secrets.ENTROPY }} + - run: flutter build apk --flavor inhouse - run: mv build/app/outputs/flutter-apk/app-inhouse-release.apk build/app/outputs/flutter-apk/app-inhouse-release-${{ env.NAME_SUFFIX }}.apk - name: Distribute apk to App Center diff --git a/.github/workflows/bmvn_build_appcenter_android.yaml b/.github/workflows/bmvn_build_appcenter_android.yaml index 6311654cb..f4653440c 100644 --- a/.github/workflows/bmvn_build_appcenter_android.yaml +++ b/.github/workflows/bmvn_build_appcenter_android.yaml @@ -78,10 +78,22 @@ jobs: ${{ inputs.testnet == true }} && echo -e "${{ secrets.DEV_ENV }}" | sed 's/\\n/\n/g' >> .env || echo -e "${{ secrets.STAGING_ENV }}" | sed 's/\\n/\n/g' >> .env cat .env + - name: Create env secret file + run: | + touch .env.secret + ${{ inputs.testnet == true }} && echo -e "${{ secrets.DEV_ENV_SECRET }}" | sed 's/\\n/\n/g' >> .env.secret || echo -e "${{ secrets.STAGING_ENV_SECRET }}" | sed 's/\\n/\n/g' >> .env.secret + cat .env + - name: Submoudles update run: git -c submodule.auto-test.update=none submodule update --init --recursive - run: flutter pub cache repair - run: flutter pub get + + # Encrypt secrets before use + - name: Encrypt Secrets + run: | + ./script/encrypt_secrets.sh ${{ secrets.ENTROPY }} + - run: flutter build apk --flavor inhouse - run: mv build/app/outputs/flutter-apk/app-inhouse-release.apk build/app/outputs/flutter-apk/app-inhouse-release-${{ env.NAME_SUFFIX }}.apk - name: Distribute apk to App Center @@ -95,7 +107,7 @@ jobs: release_notes: ${{ inputs.message }} - name: Upload Sentry debug symbols - env: + env: SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH }} run: | - flutter packages pub run sentry_dart_plugin \ No newline at end of file + flutter packages pub run sentry_dart_plugin diff --git a/.github/workflows/bmvn_build_appcenter_ios.yaml b/.github/workflows/bmvn_build_appcenter_ios.yaml index 4e4906e1c..5fc2a4035 100644 --- a/.github/workflows/bmvn_build_appcenter_ios.yaml +++ b/.github/workflows/bmvn_build_appcenter_ios.yaml @@ -72,6 +72,12 @@ jobs: ${{ inputs.testnet == true }} && echo -e "${{ secrets.DEV_ENV }}" | sed 's/\\n/\n/g' >> .env || echo -e "${{ secrets.STAGING_ENV }}" | sed 's/\\n/\n/g' >> .env cat .env + - name: Create env secret file + run: | + touch .env.secret + ${{ inputs.testnet == true }} && echo -e "${{ secrets.DEV_ENV_SECRET }}" | sed 's/\\n/\n/g' >> .env.secret || echo -e "${{ secrets.STAGING_ENV_SECRET }}" | sed 's/\\n/\n/g' >> .env.secret + cat .env + - name: Flutter doctor run: flutter doctor - name: Submoudles update @@ -96,6 +102,11 @@ jobs: cd ios/ pod install + # Encrypt secrets before use + - name: Encrypt Secrets + run: | + ./script/encrypt_secrets.sh ${{ secrets.ENTROPY }} + - uses: mathieu-bour/setup-sentry-cli@main # Build and deploy with Fastlane (by default, to ciappcenter track) ๐Ÿš€. @@ -120,7 +131,7 @@ jobs: working-directory: ios - name: Upload Sentry debug symbols - env: + env: SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH }} run: | flutter packages pub run sentry_dart_plugin diff --git a/.github/workflows/ios-release-testflight.yaml b/.github/workflows/ios-release-testflight.yaml index df3c49a00..03f6eb56a 100644 --- a/.github/workflows/ios-release-testflight.yaml +++ b/.github/workflows/ios-release-testflight.yaml @@ -52,6 +52,12 @@ jobs: echo -e "${{ secrets.PRODUCTION_ENV }}" | sed 's/\\n/\n/g' >> .env cat .env + - name: Create env secret file + run: | + touch .env.secret + echo -e "${{ secrets.PRODUCTION_ENV_SECRET }}" | sed 's/\\n/\n/g' >> .env.secret + cat .env.secret + - name: Flutter doctor run: flutter doctor - name: Submoudles update @@ -76,6 +82,11 @@ jobs: cd ios/ pod install + # Encrypt secrets before use + - name: Encrypt Secrets + run: | + ./script/encrypt_secrets.sh ${{ secrets.ENTROPY }} + - uses: mathieu-bour/setup-sentry-cli@main # Build and deploy with Fastlane (by default, to ciappcenter track) ๐Ÿš€. diff --git a/.gitignore b/.gitignore index 0b550c296..75077fd95 100644 --- a/.gitignore +++ b/.gitignore @@ -53,4 +53,7 @@ app.*.map.json # app environment variables .env .env.development -.env.production \ No newline at end of file +.env.production +.env.secret + +lib/encrypt_env/secrets.g.dart \ No newline at end of file diff --git a/README.md b/README.md index 759ea44cc..049b66aec 100644 --- a/README.md +++ b/README.md @@ -23,8 +23,13 @@ Autonomy is the worldโ€™s first and only digital art wallet. It gives you one ea 3. Initialize submodule by running; `git submodule update --init --recursive` - If you don't want to clone the auto-test package, simply run: `git -c submodule.auto-test.update=none submodule update --init --recursive` 4. Initialize the config file. `cp .env.example .env` -- Contact with Feral File app development team for development env. -5. Run `flutter run --flavor inhouse` to run Feral File app development on the connected device. +- Contact with Feral File app development team for development env. +5. Initialize the secret config file. `cp .env.secret.example .env.secret` +- There are credentials information. You may need to provide your own credentials.Contact with Feral File app development team for consultation. +6. Run ./script/encrypt_secrets.sh <-entropy-> to generate the encrypted secrets file. +- <-entropy-> is a random string. You can type a random string like akhrdsgl4893tynk3iu4y8hf +- You only need to run this script again when you want to update .env.secret. +7. Run `flutter run --flavor inhouse` to run Feral File app development on the connected device. A few resources to get you started if this is your first Flutter project: diff --git a/lib/common/environment.dart b/lib/common/environment.dart index 4f6b1a206..3dcf62597 100644 --- a/lib/common/environment.dart +++ b/lib/common/environment.dart @@ -5,6 +5,7 @@ // that can be found in the LICENSE file. // +import 'package:autonomy_flutter/encrypt_env/secrets.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; class Environment { @@ -42,8 +43,6 @@ class Environment { ? connectWebsocketTestnetURL : connectWebsocketMainnetURL; - static String get auClaimSecretKey => dotenv.env['AU_CLAIM_SECRET_KEY'] ?? ''; - static String get tokenWebviewPrefix => dotenv.env['TOKEN_WEBVIEW_PREFIX'] ?? ''; @@ -53,9 +52,6 @@ class Environment { static String get indexerTestnetURL => dotenv.env['INDEXER_TESTNET_API_URL'] ?? ''; - static String get web3RpcMainnetURL => - dotenv.env['WEB3_RPC_MAINNET_URL'] ?? ''; - static int get web3MainnetChainId => int.tryParse(dotenv.env['WEB3_MAINNET_CHAIN_ID'] ?? '1') ?? 1; @@ -80,21 +76,15 @@ class Environment { static String get feralFileAPIMainnetURL => dotenv.env['FERAL_FILE_API_MAINNET_URL'] ?? ''; - static String get feralFileSecretKeyMainnet => - dotenv.env['FERAL_FILE_SECRET_KEY_MAINNET'] ?? ''; - static String get feralFileAPITestnetURL => dotenv.env['FERAL_FILE_API_TESTNET_URL'] ?? ''; - static String get feralFileSecretKeyTestnet => - dotenv.env['FERAL_FILE_SECRET_KEY_TESTNET'] ?? ''; + static String get feralFileAssetURLTestnet => + dotenv.env['FERAL_FILE_ASSET_URL_TESTNET'] ?? ''; static String get feralFileAssetURLMainnet => dotenv.env['FERAL_FILE_ASSET_URL_MAINNET'] ?? ''; - static String get feralFileAssetURLTestnet => - dotenv.env['FERAL_FILE_ASSET_URL_TESTNET'] ?? ''; - static String get extensionSupportMainnetURL => dotenv.env['EXTENSION_SUPPORT_MAINNET_URL'] ?? ''; @@ -119,10 +109,6 @@ class Environment { static String get pubdocURL => dotenv.env['AUTONOMY_PUBDOC_URL'] ?? ''; - static String get sentryDSN => dotenv.env['SENTRY_DSN'] ?? ''; - - static String get onesignalAppID => dotenv.env['ONESIGNAL_APP_ID'] ?? ''; - static String get awsIdentityPoolId => dotenv.env['AWS_IDENTITY_POOL_ID'] ?? ''; @@ -138,14 +124,6 @@ class Environment { static bool get appTestnetConfig => dotenv.env['APP_TESTNET_CONFIG']?.toUpperCase() == 'TRUE'; - static String get metricEndpoint => dotenv.env['METRIC_ENDPOINT'] ?? ''; - - static String get metricSecretKey => dotenv.env['METRIC_SECRET_KEY'] ?? ''; - - static String get branchKey => dotenv.env['BRANCH_KEY'] ?? ''; - - static String get mixpanelKey => dotenv.env['MIXPANEL_KEY'] ?? ''; - static String get auClaimAPIURL => dotenv.env['AU_CLAIM_API_URL'] ?? ''; static List get irlWhitelistUrls => @@ -166,9 +144,6 @@ class Environment { static String get payToMintBaseUrl => dotenv.env['PAY_TO_MINT_BASE_URL'] ?? ''; - static String get chatServerHmacKey => - dotenv.env['CHAT_SERVER_HMAC_KEY'] ?? ''; - static String get postcardChatServerUrl => dotenv.env['CHAT_SERVER_URL'] ?? ''; @@ -184,6 +159,34 @@ class Environment { static String get autonomyActivationURL => dotenv.env['AUTONOMY_ACTIVATION_URL'] ?? ''; + + static String get chatServerHmacKey => + cachedSecretEnv['CHAT_SERVER_HMAC_KEY'] ?? ''; + + static String get metricEndpoint => cachedSecretEnv['METRIC_ENDPOINT'] ?? ''; + + static String get metricSecretKey => + cachedSecretEnv['METRIC_SECRET_KEY'] ?? ''; + + static String get branchKey => cachedSecretEnv['BRANCH_KEY'] ?? ''; + + static String get mixpanelKey => cachedSecretEnv['MIXPANEL_KEY'] ?? ''; + + static String get auClaimSecretKey => + cachedSecretEnv['AU_CLAIM_SECRET_KEY'] ?? ''; + + static String get feralFileSecretKeyTestnet => + cachedSecretEnv['FERAL_FILE_SECRET_KEY_TESTNET'] ?? ''; + + static String get feralFileSecretKeyMainnet => + cachedSecretEnv['FERAL_FILE_SECRET_KEY_MAINNET'] ?? ''; + + static String get web3RpcMainnetURL => + cachedSecretEnv['WEB3_RPC_MAINNET_URL'] ?? ''; + + static String get sentryDSN => cachedSecretEnv['SENTRY_DSN'] ?? ''; + + static String get onesignalAppID => cachedSecretEnv['ONESIGNAL_APP_ID'] ?? ''; } class Secret { diff --git a/lib/encrypt_env/secrets.dart b/lib/encrypt_env/secrets.dart new file mode 100644 index 000000000..be03917dd --- /dev/null +++ b/lib/encrypt_env/secrets.dart @@ -0,0 +1 @@ +late dynamic cachedSecretEnv; diff --git a/lib/main.dart b/lib/main.dart index 8f20e3f45..cad275077 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -9,11 +9,14 @@ // ignore_for_file: avoid_annotating_with_dynamic import 'dart:async'; +import 'dart:convert'; import 'dart:isolate'; import 'dart:ui'; import 'package:autonomy_flutter/common/environment.dart'; import 'package:autonomy_flutter/common/injector.dart'; +import 'package:autonomy_flutter/encrypt_env/secrets.dart'; +import 'package:autonomy_flutter/encrypt_env/secrets.g.dart'; import 'package:autonomy_flutter/model/eth_pending_tx_amount.dart'; import 'package:autonomy_flutter/screen/app_router.dart'; import 'package:autonomy_flutter/service/configuration_service.dart'; @@ -47,6 +50,8 @@ import 'package:sentry_flutter/sentry_flutter.dart'; void main() async { unawaited(runZonedGuarded(() async { + final json = await getSecretEnv(); + cachedSecretEnv = jsonDecode(json); await dotenv.load(); await SentryFlutter.init( (options) { diff --git a/pubspec.lock b/pubspec.lock index 3d7d93f77..a122d07cf 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -377,6 +377,22 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.3" + cryptography: + dependency: "direct main" + description: + name: cryptography + sha256: d146b76d33d94548cf035233fbc2f4338c1242fa119013bead807d033fc4ae05 + url: "https://pub.dev" + source: hosted + version: "2.7.0" + cryptography_flutter: + dependency: "direct main" + description: + name: cryptography_flutter + sha256: a7fc3f0de42fb0947cbf213257aa3a69c89df561d104723ede8050658621f292 + url: "https://pub.dev" + source: hosted + version: "2.3.2" csslib: dependency: transitive description: @@ -568,10 +584,10 @@ packages: dependency: transitive description: name: ffi - sha256: ed5337a5660c506388a9f012be0288fb38b49020ce2b45fe1f8b8323fe429f99 + sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21" url: "https://pub.dev" source: hosted - version: "2.0.2" + version: "2.1.2" file: dependency: transitive description: @@ -2979,5 +2995,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.2.0-0 <4.0.0" - flutter: ">=3.10.0" + dart: ">=3.3.0-279.1.beta <4.0.0" + flutter: ">=3.13.0" diff --git a/pubspec.yaml b/pubspec.yaml index d658784f9..c2a5e448e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -163,6 +163,8 @@ dependencies: image_gallery_saver: ^2.0.3 hand_signature: ^3.0.1 flutter_pdfview: ^1.3.2 + cryptography: ^2.7.0 + cryptography_flutter: ^2.3.2 dependency_overrides: intl: 0.18.0 diff --git a/script/encrypt.dart b/script/encrypt.dart new file mode 100644 index 000000000..12fe0dba4 --- /dev/null +++ b/script/encrypt.dart @@ -0,0 +1,40 @@ +// ignore_for_file: avoid_print + +import 'dart:convert'; +import 'dart:math'; +import 'package:cryptography/cryptography.dart'; + +// Example function for encryption +Future> _encryptText(String text, String key) async { + final algorithm = Chacha20.poly1305Aead(); + final bytes = utf8.encode(key); + final secretKey = await algorithm.newSecretKeyFromBytes(bytes); + final secretBox = await algorithm.encryptString(text, secretKey: secretKey); + final encryptText = secretBox.concatenation(); + return [ + base64Encode(encryptText), + secretBox.nonce.length, + secretBox.mac.bytes.length + ]; +} + +String _generateKeyFromEntropy(String entropy, int length) { + final random = Random(entropy.hashCode); // Use entropy's hash code as seed + final entropyLength = entropy.length; + return String.fromCharCodes(Iterable.generate( + length, (_) => entropy.codeUnitAt(random.nextInt(entropyLength)))); +} + +void main(List arguments) async { + if (arguments.length < 2) { + print('Usage: dart encrypt.dart '); + return; + } + + String textToEncrypt = arguments[0]; + String entropy = arguments[1]; + + final encryptedText = + await _encryptText(textToEncrypt, _generateKeyFromEntropy(entropy, 32)); + print(encryptedText.join(' ')); +} diff --git a/script/encrypt_secrets.sh b/script/encrypt_secrets.sh new file mode 100755 index 000000000..74acf1596 --- /dev/null +++ b/script/encrypt_secrets.sh @@ -0,0 +1,84 @@ +#!/bin/bash + +# Check if Dart is installed +if ! command -v dart &> /dev/null; then + echo "Dart is not installed. Please install Dart and try again." + exit 1 +fi + +if [ "$#" -lt 1 ]; then + echo "Usage: ./run_random_string.sh " + exit 1 +fi + +echo "Running Encrypt script with the entropy" + +# Check if .env file exists +if [ ! -f .env.secret ]; then + echo ".env.secret file not found." + exit 1 +fi + +# Initialize an empty JSON object +json_object="{" + +# Read each line from the .env.secret file +while IFS= read -r line; do + # Extract key and value from the line + key=$(echo "$line" | cut -d '=' -f 1) + value=$(echo "$line" | cut -d '=' -f 2-) + + # Add key-value pair to the JSON object + json_object+="\"$key\":\"$value\", " + +done < .env.secret + +# Remove the trailing comma and space from the JSON object +json_object="${json_object%, *} }" + +# Run the Dart script with the provided argument and capture its output +encrypted_text=$(dart script/encrypt.dart "$json_object" "$1") + +# Extract elements from the array +IFS=' ' read -r text nonceLength macLength <<< "$encrypted_text" + +# Create the secrets.g.dart file +cat < lib/encrypt_env/secrets.g.dart +import 'dart:convert'; +import 'dart:math'; + +import 'package:cryptography/cryptography.dart'; + +///============================================================================= +const _encryptedMessage = '$text'; +const _nonceLength = $nonceLength; +const _macLength = $macLength; +///============================================================================= + +String _generateKeyFromEntropy(int length) { + ///=========================================================================== + var entropy = '$1'; + ///=========================================================================== + final random = Random(entropy.hashCode); // Use entropy's hash code as seed + final entropyLength = entropy.length; + final result = String.fromCharCodes(Iterable.generate( + length, (_) => entropy.codeUnitAt(random.nextInt(entropyLength)))); + entropy = ''; // Clear the entropy + return result; +} + +Future getSecretEnv() async { + final algorithm = Chacha20.poly1305Aead(); + final key = _generateKeyFromEntropy(32); + final bytes = utf8.encode(key); + final secretKey = await algorithm.newSecretKeyFromBytes(bytes); + final decryptText = await algorithm.decryptString( + SecretBox.fromConcatenation(base64Decode(_encryptedMessage), + nonceLength: _nonceLength, macLength: _macLength), + secretKey: secretKey, + ); + return decryptText; +} +EOF + +echo "lib/encrypt_env/secrets.g.dart created successfully." \ No newline at end of file