Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions client/drift_schemas/defguard/drift_schema_v5.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"_meta":{"description":"This file contains a serialized version of schema entities for drift.","version":"1.2.0"},"options":{"store_date_time_values_as_text":false},"entities":[{"id":0,"references":[],"type":"table","data":{"name":"defguard_instances","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"uuid","getter_name":"uuid","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"url","getter_name":"url","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"device_id","getter_name":"deviceId","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"proxy_url","getter_name":"proxyUrl","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"username","getter_name":"username","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"client_traffic_policy","getter_name":"clientTrafficPolicy","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const ClientTrafficPolicyConverter()","dart_type_name":"ClientTrafficPolicy"}},{"name":"enterprise_enabled","getter_name":"enterpriseEnabled","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"enterprise_enabled\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"enterprise_enabled\" IN (0, 1))"},"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"pub_key","getter_name":"pubKey","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"mfa_keys_stored","getter_name":"mfaKeysStored","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"mfa_keys_stored\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"mfa_keys_stored\" IN (0, 1))"},"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"openid_display_name","getter_name":"openidDisplayName","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":1,"references":[0],"type":"table","data":{"name":"locations","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"instance","getter_name":"instance","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES defguard_instances (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES defguard_instances (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":[{"foreign_key":{"to":{"table":"defguard_instances","column":"id"},"initially_deferred":false,"on_update":null,"on_delete":"cascade"}}]},{"name":"network_id","getter_name":"networkId","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"address","getter_name":"address","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"pub_key","getter_name":"pubKey","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"endpoint","getter_name":"endpoint","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"allowed_ips","getter_name":"allowedIps","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"dns","getter_name":"dns","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"mfa_enabled","getter_name":"mfaEnabled","moor_type":"bool","nullable":true,"customConstraints":null,"defaultConstraints":"CHECK (\"mfa_enabled\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"mfa_enabled\" IN (0, 1))"},"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"traffic_method","getter_name":"trafficMethod","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter<RoutingMethod>(RoutingMethod.values)","dart_type_name":"RoutingMethod"}},{"name":"mfa_method","getter_name":"mfaMethod","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const MfaMethodConverter()","dart_type_name":"MfaMethod"}},{"name":"keep_alive_interval","getter_name":"keepAliveInterval","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"location_mfa_mode","getter_name":"locationMfaMode","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const LocationMfaModeConverter()","dart_type_name":"LocationMfaMode"}},{"name":"posture_check_required","getter_name":"postureCheckRequired","moor_type":"bool","nullable":true,"customConstraints":null,"defaultConstraints":"CHECK (\"posture_check_required\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"posture_check_required\" IN (0, 1))"},"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}}]}
9 changes: 8 additions & 1 deletion client/ios/Podfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Uncomment this line to define a global platform for your project
platform :ios, '15.6'
platform :ios, '15.0'

# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
Expand Down Expand Up @@ -39,5 +39,12 @@ end
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
# Pods declaring their own, older minimum keep it (podhelper only clears
# targets below 13), and Xcode 27 rejects anything below 15.0.
target.build_configurations.each do |config|
if config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'].to_f < 15.0
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '15.0'
end
end
end
end
2 changes: 1 addition & 1 deletion client/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,6 @@ SPEC CHECKSUMS:
url_launcher_ios: bb13df5870e8c4234ca12609d04010a21be43dfa
wireguard_plugin: 4d2720563b180d23101f7162ecdbf57203e82e8e

PODFILE CHECKSUM: ae9e65fc23486119b8e977fd41d9213f251e537a
PODFILE CHECKSUM: 274710f88d7bc73352a0d86724aba1a04360601c

COCOAPODS: 1.16.2
18 changes: 9 additions & 9 deletions client/ios/Runner.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -576,7 +576,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MTL_ENABLE_DEBUG_INFO = NO;
REGISTER_APP_GROUPS = YES;
SUPPORTED_PLATFORMS = iphoneos;
Expand All @@ -600,7 +600,7 @@
INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = Defguard;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
Expand Down Expand Up @@ -791,7 +791,7 @@
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = net.defguard.mobile.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
Expand All @@ -811,7 +811,7 @@
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = net.defguard.mobile.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
Expand All @@ -829,7 +829,7 @@
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = net.defguard.mobile.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
Expand Down Expand Up @@ -890,7 +890,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
REGISTER_APP_GROUPS = YES;
Expand Down Expand Up @@ -944,7 +944,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MTL_ENABLE_DEBUG_INFO = NO;
REGISTER_APP_GROUPS = YES;
SUPPORTED_PLATFORMS = iphoneos;
Expand All @@ -970,7 +970,7 @@
INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = Defguard;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
Expand Down Expand Up @@ -1009,7 +1009,7 @@
INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = Defguard;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
Expand Down
61 changes: 53 additions & 8 deletions client/lib/data/db/database.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import "package:drift/drift.dart";
import "package:drift_flutter/drift_flutter.dart";
import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:mobile/data/db/database.steps.dart";
import "package:mobile/data/db/db_file.dart";
import "package:mobile/data/db/enums.dart";
import "package:mobile/utils/instance_secrets.dart";
import "package:mobile/utils/keychain.dart";
import "package:path_provider/path_provider.dart";
import "package:riverpod_annotation/riverpod_annotation.dart";

Expand All @@ -27,8 +30,6 @@ class DefguardInstances extends Table with AutoIncrementingPrimaryKey {

TextColumn get username => text()();

TextColumn get poolingToken => text()();

@JsonKey('client_traffic_policy')
IntColumn get clientTrafficPolicy => integer()
.withDefault(const Constant(0))
Expand All @@ -40,9 +41,6 @@ class DefguardInstances extends Table with AutoIncrementingPrimaryKey {
// user public key
TextColumn get pubKey => text()();

// user private key
TextColumn get privateKey => text()();

// tells if the secure biometric storage exists for this instance
BoolColumn get mfaKeysStored => boolean()();

Expand Down Expand Up @@ -103,13 +101,22 @@ class AppDatabase extends _$AppDatabase {
AppDatabase([QueryExecutor? executor]) : super(executor ?? _openConnection());

@override
int get schemaVersion => 4;
int get schemaVersion => 5;

@override
MigrationStrategy get migration {
return MigrationStrategy(
beforeOpen: (details) async {
await customStatement('PRAGMA foreign_keys = ON');
// Overwrite deleted content instead of leaving it in freed pages.
await customStatement('PRAGMA secure_delete = ON');
if (details.hadUpgrade && details.versionBefore! < 5) {
// Rebuild the database file so the pages that held the secrets moved
// to the keychain in the 4 -> 5 migration are gone for good. Cannot
// run inside the migration itself, VACUUM is not allowed in a
// transaction.
await customStatement('VACUUM');
}
},
onUpgrade: stepByStep(
from1To2: (m, schema) async {
Expand Down Expand Up @@ -140,13 +147,34 @@ class AppDatabase extends _$AppDatabase {
schema.locations.postureCheckRequired,
);
},
from4To5: (m, schema) async {
// Move the WireGuard private keys and the proxy tokens out of the
// database and into the platform keychain. Any failure here aborts
// the migration so it is retried on the next launch with the columns
// still in place.
await customStatement('PRAGMA secure_delete = ON');
final instances = await customSelect(
'SELECT uuid, device_id, private_key, pooling_token '
'FROM defguard_instances',
).get();
for (final instance in instances) {
await storeInstanceSecrets(
uuid: instance.read<String>('uuid'),
deviceId: instance.read<int>('device_id'),
privateKey: instance.read<String>('private_key'),
poolingToken: instance.read<String>('pooling_token'),
);
}
await m.dropColumn(schema.defguardInstances, 'private_key');
await m.dropColumn(schema.defguardInstances, 'pooling_token');
},
),
);
}

static QueryExecutor _openConnection() {
return driftDatabase(
name: 'defguard',
name: databaseName,
native: const DriftNativeOptions(
databaseDirectory: getApplicationSupportDirectory,
),
Expand All @@ -166,7 +194,24 @@ extension DefguardInstanceLogName on DefguardInstance {
}

extension DefguardInstanceStorageKey on DefguardInstance {
String get secureStorageKey => 'mfa-$uuid-$deviceId';
String get secureStorageKey => mfaStorageKey(uuid, deviceId);
}

/// Secrets of an instance are kept in the platform keychain, not in the
/// database. They can be absent (see [reportMissingSecret]), callers have to
/// handle a `null`.
extension DefguardInstanceSecrets on DefguardInstance {
Future<String?> wireguardPrivateKey() =>
secureStorage.read(key: wireguardKeyStorageKey(uuid, deviceId));

Future<String?> poolingToken() =>
secureStorage.read(key: tokenStorageKey(uuid, deviceId));

Future<void> storeToken(String token) =>
secureStorage.write(key: tokenStorageKey(uuid, deviceId), value: token);

Future<void> removeSecrets() =>
removeInstanceSecrets(uuid: uuid, deviceId: deviceId);
}

extension LocationLogName on Location {
Expand Down
Loading