< Summary

Information
Class: LOCKnet.Data.SqlCipherRuntimeProbeResult
Assembly: LOCKnet.Data
File(s): /home/runner/work/LOCKnet/LOCKnet/src/LOCKnet.Data/SqlCipherEncryptedVaultMigrationExporter.cs
Line coverage
100%
Covered lines: 8
Uncovered lines: 0
Coverable lines: 8
Total lines: 489
Line coverage: 100%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
get_IsAvailable()100%11100%
get_CipherVersion()100%11100%
get_SqliteVersion()100%11100%
get_ProviderPath()100%11100%
get_IsProductionCrediblePath()100%11100%
get_FailureKind()100%11100%
get_Message()100%11100%

File(s)

/home/runner/work/LOCKnet/LOCKnet/src/LOCKnet.Data/SqlCipherEncryptedVaultMigrationExporter.cs

#LineLine coverage
 1using LOCKnet.Core.DataAbstractions;
 2using LOCKnet.Data.Repositories;
 3using Microsoft.Data.Sqlite;
 4
 5namespace LOCKnet.Data;
 6
 7internal sealed class SqlCipherEncryptedVaultMigrationExporter : IEncryptedVaultMigrationExporter
 8{
 9  private readonly string _password;
 10  private readonly Func<SqlCipherRuntimeProbeResult>? _runtimeProbeOverride;
 11
 12  internal SqlCipherEncryptedVaultMigrationExporter(string password)
 13    : this(password, null)
 14  {
 15  }
 16
 17  internal SqlCipherEncryptedVaultMigrationExporter(string password, Func<SqlCipherRuntimeProbeResult>? runtimeProbeOver
 18  {
 19    ArgumentException.ThrowIfNullOrWhiteSpace(password);
 20    _password = password;
 21    _runtimeProbeOverride = runtimeProbeOverride;
 22  }
 23
 24  public VaultStorageMigrationTargetMode TargetMode => VaultStorageMigrationTargetMode.EncryptedSqlite;
 25
 26  internal SqlCipherRuntimeProbeResult ProbeRuntime()
 27  {
 28    if (_runtimeProbeOverride is not null)
 29      return _runtimeProbeOverride();
 30
 31    var providerPath = GetConfiguredProviderPath();
 32    var productionCrediblePath = providerPath is SqlCipherProviderPackagingPath.OfficialZetetic;
 33
 34    try
 35    {
 36#if USE_OFFICIAL_SQLCIPHER_PATH
 37      if (providerPath is SqlCipherProviderPackagingPath.BundleZeteticWithProviderSqlcipher)
 38        SQLitePCL.raw.SetProvider(new SQLitePCL.SQLite3Provider_sqlcipher());
 39#endif
 40    }
 41    catch (Exception ex)
 42    {
 43      return new SqlCipherRuntimeProbeResult(
 44        false,
 45        null,
 46        null,
 47        providerPath,
 48        productionCrediblePath,
 49        SqlCipherExporterFailureKind.NativeProviderLoadFailure,
 50        $"SQLCipher provider activation failed: {GetInnermostMessage(ex)}");
 51    }
 52
 53    SQLitePCL.Batteries_V2.Init();
 54
 55    try
 56    {
 57      using var connection = new SqliteConnection("Data Source=:memory:");
 58      connection.Open();
 59      using var versionCommand = connection.CreateCommand();
 60      versionCommand.CommandText = "PRAGMA cipher_version;";
 61      var cipherVersion = Convert.ToString(versionCommand.ExecuteScalar()) ?? string.Empty;
 62      if (string.IsNullOrWhiteSpace(cipherVersion))
 63        return new SqlCipherRuntimeProbeResult(false, null, connection.ServerVersion, providerPath, productionCrediblePa
 64
 65      return new SqlCipherRuntimeProbeResult(true, cipherVersion, connection.ServerVersion, providerPath, productionCred
 66    }
 67    catch (Exception ex)
 68    {
 69      var message = GetInnermostMessage(ex);
 70      return new SqlCipherRuntimeProbeResult(false, null, null, providerPath, productionCrediblePath, SqlCipherExporterF
 71    }
 72  }
 73
 74  public void ExportPlaintextVault(string sourceConnectionString, string destinationPath)
 75  {
 76    ArgumentException.ThrowIfNullOrWhiteSpace(sourceConnectionString);
 77    ArgumentException.ThrowIfNullOrWhiteSpace(destinationPath);
 78
 79    EnsureRuntimeAvailable();
 80
 81    var sourcePath = StorageRewriteArtifacts.TryResolveDatabasePath(sourceConnectionString)
 82      ?? throw new SqlCipherEncryptedVaultMigrationException(SqlCipherExporterFailureKind.MigrationExportFailure, "SQLCi
 83
 84    if (File.Exists(destinationPath) && !StorageRewriteArtifacts.TryDeleteFile(destinationPath))
 85      throw new SqlCipherEncryptedVaultMigrationException(SqlCipherExporterFailureKind.MigrationExportFailure, "Vorhande
 86
 87    try
 88    {
 89      var destinationFactory = new SqlCipherConnectionFactory(destinationPath, _password);
 90      new Database(destinationFactory).Initialize();
 91
 92      using var sourceConnection = new SqliteConnection(sourceConnectionString);
 93      sourceConnection.Open();
 94      RepositoryBase.ConfigureConnection(sourceConnection);
 95
 96      using var destinationConnection = destinationFactory.OpenConnection();
 97      using var transaction = destinationConnection.BeginTransaction();
 98
 99      CopyMasterKey(sourceConnection, destinationConnection, transaction);
 100      CopyCredentials(sourceConnection, destinationConnection, transaction);
 101      CopySettings(sourceConnection, destinationConnection, transaction);
 102
 103      transaction.Commit();
 104    }
 105    catch (SqlCipherEncryptedVaultMigrationException)
 106    {
 107      throw;
 108    }
 109    catch (Exception ex) when (ex is SqliteException or IOException or InvalidOperationException)
 110    {
 111      throw new SqlCipherEncryptedVaultMigrationException(SqlCipherExporterFailureKind.MigrationExportFailure, GetInnerm
 112    }
 113  }
 114
 115  public void ValidateExportedVault(string destinationPath)
 116  {
 117    ArgumentException.ThrowIfNullOrWhiteSpace(destinationPath);
 118    EnsureRuntimeAvailable();
 119
 120    if (!File.Exists(destinationPath))
 121      throw new SqlCipherEncryptedVaultMigrationException(SqlCipherExporterFailureKind.InvalidTarget, "SQLCipher-Zielart
 122
 123    var openResult = TryOpenEncryptedVault(destinationPath, _password);
 124    if (!openResult.Success)
 125      throw new SqlCipherEncryptedVaultMigrationException(openResult.FailureKind, openResult.Message ?? "SQLCipher-Ziela
 126
 127    try
 128    {
 129      using var connection = OpenEncryptedConnection(destinationPath, _password, SqliteOpenMode.ReadOnly);
 130      RequireCipherVersion(connection);
 131      RequireQuickCheck(connection);
 132      RequireTable(connection, "MasterKey");
 133      RequireTable(connection, "Credentials");
 134      RequireTable(connection, "Settings");
 135
 136      using var masterKeyCount = connection.CreateCommand();
 137      masterKeyCount.CommandText = "SELECT COUNT(*) FROM MasterKey;";
 138      if (Convert.ToInt64(masterKeyCount.ExecuteScalar() ?? 0L) != 1)
 139        throw new SqlCipherEncryptedVaultMigrationException(SqlCipherExporterFailureKind.ValidationFailure, "SQLCipher-Z
 140    }
 141    catch (SqlCipherEncryptedVaultMigrationException)
 142    {
 143      throw;
 144    }
 145    catch (Exception ex) when (ex is SqliteException or IOException or InvalidOperationException)
 146    {
 147      throw new SqlCipherEncryptedVaultMigrationException(SqlCipherExporterFailureKind.OperationalFailure, GetInnermostM
 148    }
 149  }
 150
 151  public void PersistMigratedHeader(string databasePath, VaultHeader header)
 152  {
 153    ArgumentException.ThrowIfNullOrWhiteSpace(databasePath);
 154    ArgumentNullException.ThrowIfNull(header);
 155    EnsureRuntimeAvailable();
 156
 157    try
 158    {
 159      using var connection = OpenEncryptedConnection(databasePath, _password, SqliteOpenMode.ReadWrite);
 160      using var command = connection.CreateCommand();
 161      command.CommandText = @"
 162        UPDATE MasterKey
 163        SET PasswordHash = $hash,
 164          FormatVersion = $formatVersion,
 165          KdfIdentifier = $kdfIdentifier,
 166          KdfParameters = $kdfParameters,
 167          Salt = $salt,
 168          WrappedVaultKey = $wrappedVaultKey,
 169          UsesLegacyKeyMaterial = $usesLegacyKeyMaterial,
 170          RequiresStorageCompaction = $requiresStorageCompaction,
 171          LastStorageCompactionAttemptUtc = $lastStorageCompactionAttemptUtc,
 172          LastStorageCompactionFailureKind = $lastStorageCompactionFailureKind,
 173          LastStorageCompactionError = $lastStorageCompactionError,
 174          StorageMigrationState = $storageMigrationState,
 175          StorageMigrationTargetMode = $storageMigrationTargetMode,
 176          LastStorageMigrationAttemptUtc = $lastStorageMigrationAttemptUtc,
 177          LastStorageMigrationError = $lastStorageMigrationError,
 178          UpdatedAt = CURRENT_TIMESTAMP
 179        WHERE Id = 1;";
 180      command.Parameters.AddWithValue("$hash", header.LegacyPasswordHash);
 181      command.Parameters.AddWithValue("$formatVersion", header.FormatVersion);
 182      command.Parameters.AddWithValue("$kdfIdentifier", header.KdfIdentifier);
 183      command.Parameters.AddWithValue("$kdfParameters", header.KdfParameters.Serialize());
 184      command.Parameters.AddWithValue("$salt", header.Salt);
 185      command.Parameters.AddWithValue("$wrappedVaultKey", (object?)header.WrappedVaultKey ?? DBNull.Value);
 186      command.Parameters.AddWithValue("$usesLegacyKeyMaterial", header.UsesLegacyKeyMaterial ? 1 : 0);
 187      command.Parameters.AddWithValue("$requiresStorageCompaction", header.RequiresStorageCompaction ? 1 : 0);
 188      command.Parameters.AddWithValue("$lastStorageCompactionAttemptUtc", (object?)header.LastStorageCompactionAttemptUt
 189      command.Parameters.AddWithValue("$lastStorageCompactionFailureKind", (int)header.LastStorageCompactionFailureKind)
 190      command.Parameters.AddWithValue("$lastStorageCompactionError", (object?)header.LastStorageCompactionError ?? DBNul
 191      command.Parameters.AddWithValue("$storageMigrationState", (int)header.StorageMigrationState);
 192      command.Parameters.AddWithValue("$storageMigrationTargetMode", (int)header.StorageMigrationTargetMode);
 193      command.Parameters.AddWithValue("$lastStorageMigrationAttemptUtc", (object?)header.LastStorageMigrationAttemptUtc?
 194      command.Parameters.AddWithValue("$lastStorageMigrationError", (object?)header.LastStorageMigrationError ?? DBNull.
 195      command.ExecuteNonQuery();
 196    }
 197    catch (SqlCipherEncryptedVaultMigrationException)
 198    {
 199      throw;
 200    }
 201    catch (Exception ex) when (ex is SqliteException or IOException or InvalidOperationException)
 202    {
 203      throw new SqlCipherEncryptedVaultMigrationException(SqlCipherExporterFailureKind.OperationalFailure, GetInnermostM
 204    }
 205  }
 206
 207  internal SqlCipherVaultOpenResult TryOpenEncryptedVault(string destinationPath, string password)
 208  {
 209    ArgumentException.ThrowIfNullOrWhiteSpace(destinationPath);
 210    ArgumentException.ThrowIfNullOrWhiteSpace(password);
 211
 212    var runtime = ProbeRuntime();
 213    if (!runtime.IsAvailable)
 214      return new SqlCipherVaultOpenResult(false, runtime.CipherVersion, runtime.ProviderPath, runtime.IsProductionCredib
 215
 216    if (!File.Exists(destinationPath))
 217      return new SqlCipherVaultOpenResult(false, runtime.CipherVersion, runtime.ProviderPath, runtime.IsProductionCredib
 218
 219    try
 220    {
 221      using var connection = OpenEncryptedConnection(destinationPath, password, SqliteOpenMode.ReadOnly);
 222      var cipherVersion = RequireCipherVersion(connection);
 223      using var command = connection.CreateCommand();
 224      command.CommandText = "SELECT COUNT(*) FROM sqlite_master;";
 225      _ = Convert.ToInt64(command.ExecuteScalar() ?? 0L);
 226      return new SqlCipherVaultOpenResult(true, cipherVersion, runtime.ProviderPath, runtime.IsProductionCrediblePath, S
 227    }
 228    catch (Exception ex) when (ex is SqliteException or InvalidOperationException)
 229    {
 230      var kind = ClassifyOpenFailure(ex);
 231      return new SqlCipherVaultOpenResult(false, runtime.CipherVersion, runtime.ProviderPath, runtime.IsProductionCredib
 232    }
 233  }
 234
 235  private void EnsureRuntimeAvailable()
 236  {
 237    var runtime = ProbeRuntime();
 238    if (!runtime.IsAvailable)
 239      throw new SqlCipherEncryptedVaultMigrationException(runtime.FailureKind, runtime.Message ?? "SQLCipher-Laufzeit ko
 240  }
 241
 242  private static SqliteConnection OpenEncryptedConnection(string databasePath, string password, SqliteOpenMode mode)
 243  {
 244    var builder = new SqliteConnectionStringBuilder
 245    {
 246      DataSource = databasePath,
 247      Mode = mode,
 248      Password = password,
 249    };
 250
 251    var connection = new SqliteConnection(builder.ToString());
 252    connection.Open();
 253    if (mode != SqliteOpenMode.ReadOnly)
 254      RepositoryBase.ConfigureConnection(connection);
 255    return connection;
 256  }
 257
 258  private static string RequireCipherVersion(SqliteConnection connection)
 259  {
 260    using var command = connection.CreateCommand();
 261    command.CommandText = "PRAGMA cipher_version;";
 262    var cipherVersion = Convert.ToString(command.ExecuteScalar()) ?? string.Empty;
 263    if (string.IsNullOrWhiteSpace(cipherVersion))
 264      throw new SqlCipherEncryptedVaultMigrationException(SqlCipherExporterFailureKind.NativeProviderLoadFailure, "PRAGM
 265
 266    return cipherVersion;
 267  }
 268
 269  private static void RequireQuickCheck(SqliteConnection connection)
 270  {
 271    using var command = connection.CreateCommand();
 272    command.CommandText = "PRAGMA quick_check(1);";
 273    var result = Convert.ToString(command.ExecuteScalar()) ?? string.Empty;
 274    if (!string.Equals(result, "ok", StringComparison.OrdinalIgnoreCase))
 275      throw new SqlCipherEncryptedVaultMigrationException(SqlCipherExporterFailureKind.ValidationFailure, "SQLCipher qui
 276  }
 277
 278  private static void RequireTable(SqliteConnection connection, string tableName)
 279  {
 280    using var command = connection.CreateCommand();
 281    command.CommandText = "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name=$name;";
 282    command.Parameters.AddWithValue("$name", tableName);
 283    if (Convert.ToInt64(command.ExecuteScalar() ?? 0L) <= 0)
 284      throw new SqlCipherEncryptedVaultMigrationException(SqlCipherExporterFailureKind.ValidationFailure, $"SQLCipher-Zi
 285  }
 286
 287  private static void CopyMasterKey(SqliteConnection sourceConnection, SqliteConnection destinationConnection, SqliteTra
 288  {
 289    using var sourceCommand = sourceConnection.CreateCommand();
 290    sourceCommand.CommandText = @"
 291      SELECT Id, PasswordHash, FormatVersion, KdfIdentifier, KdfParameters, Salt, WrappedVaultKey, UsesLegacyKeyMaterial
 292             RequiresStorageCompaction, LastStorageCompactionAttemptUtc, LastStorageCompactionFailureKind, LastStorageCo
 293             StorageMigrationState, StorageMigrationTargetMode, LastStorageMigrationAttemptUtc, LastStorageMigrationErro
 294      FROM MasterKey;";
 295    using var reader = sourceCommand.ExecuteReader();
 296    while (reader.Read())
 297    {
 298      using var insert = destinationConnection.CreateCommand();
 299      insert.Transaction = transaction;
 300      insert.CommandText = @"
 301        INSERT INTO MasterKey (Id, PasswordHash, FormatVersion, KdfIdentifier, KdfParameters, Salt, WrappedVaultKey, Use
 302          RequiresStorageCompaction, LastStorageCompactionAttemptUtc, LastStorageCompactionFailureKind, LastStorageCompa
 303          StorageMigrationState, StorageMigrationTargetMode, LastStorageMigrationAttemptUtc, LastStorageMigrationError, 
 304        VALUES ($id, $passwordHash, $formatVersion, $kdfIdentifier, $kdfParameters, $salt, $wrappedVaultKey, $usesLegacy
 305          $requiresStorageCompaction, $lastStorageCompactionAttemptUtc, $lastStorageCompactionFailureKind, $lastStorageC
 306          $storageMigrationState, $storageMigrationTargetMode, $lastStorageMigrationAttemptUtc, $lastStorageMigrationErr
 307      insert.Parameters.AddWithValue("$id", reader.GetInt64(0));
 308      insert.Parameters.AddWithValue("$passwordHash", reader.IsDBNull(1) ? [] : (byte[])reader[1]);
 309      insert.Parameters.AddWithValue("$formatVersion", reader.GetInt32(2));
 310      insert.Parameters.AddWithValue("$kdfIdentifier", reader.GetString(3));
 311      insert.Parameters.AddWithValue("$kdfParameters", reader.GetString(4));
 312      insert.Parameters.AddWithValue("$salt", (byte[])reader[5]);
 313      insert.Parameters.AddWithValue("$wrappedVaultKey", reader.IsDBNull(6) ? DBNull.Value : reader[6]);
 314      insert.Parameters.AddWithValue("$usesLegacyKeyMaterial", reader.GetInt32(7));
 315      insert.Parameters.AddWithValue("$requiresStorageCompaction", reader.GetInt32(8));
 316      insert.Parameters.AddWithValue("$lastStorageCompactionAttemptUtc", reader.IsDBNull(9) ? DBNull.Value : reader.GetS
 317      insert.Parameters.AddWithValue("$lastStorageCompactionFailureKind", reader.GetInt32(10));
 318      insert.Parameters.AddWithValue("$lastStorageCompactionError", reader.IsDBNull(11) ? DBNull.Value : reader.GetStrin
 319      insert.Parameters.AddWithValue("$storageMigrationState", reader.GetInt32(12));
 320      insert.Parameters.AddWithValue("$storageMigrationTargetMode", reader.GetInt32(13));
 321      insert.Parameters.AddWithValue("$lastStorageMigrationAttemptUtc", reader.IsDBNull(14) ? DBNull.Value : reader.GetS
 322      insert.Parameters.AddWithValue("$lastStorageMigrationError", reader.IsDBNull(15) ? DBNull.Value : reader.GetString
 323      insert.Parameters.AddWithValue("$createdAt", reader.GetDateTime(16));
 324      insert.Parameters.AddWithValue("$updatedAt", reader.GetDateTime(17));
 325      insert.ExecuteNonQuery();
 326    }
 327  }
 328
 329  private static void CopyCredentials(SqliteConnection sourceConnection, SqliteConnection destinationConnection, SqliteT
 330  {
 331    using var sourceCommand = sourceConnection.CreateCommand();
 332    sourceCommand.CommandText = @"
 333      SELECT Id, Title, Username, EncryptedPassword, EncryptedMetadata, CredentialUuid, SecretFormatVersion, MetadataFor
 334             URL, Notes, CreatedAt, UpdatedAt, IconKey, CredentialType
 335      FROM Credentials;";
 336    using var reader = sourceCommand.ExecuteReader();
 337    while (reader.Read())
 338    {
 339      using var insert = destinationConnection.CreateCommand();
 340      insert.Transaction = transaction;
 341      insert.CommandText = @"
 342        INSERT INTO Credentials (Id, Title, Username, EncryptedPassword, EncryptedMetadata, CredentialUuid, SecretFormat
 343          URL, Notes, CreatedAt, UpdatedAt, IconKey, CredentialType)
 344        VALUES ($id, $title, $username, $encryptedPassword, $encryptedMetadata, $credentialUuid, $secretFormatVersion, $
 345          $url, $notes, $createdAt, $updatedAt, $iconKey, $credentialType);";
 346      insert.Parameters.AddWithValue("$id", reader.GetInt64(0));
 347      insert.Parameters.AddWithValue("$title", reader.GetString(1));
 348      insert.Parameters.AddWithValue("$username", reader.IsDBNull(2) ? DBNull.Value : reader.GetString(2));
 349      insert.Parameters.AddWithValue("$encryptedPassword", (byte[])reader[3]);
 350      insert.Parameters.AddWithValue("$encryptedMetadata", reader.IsDBNull(4) ? DBNull.Value : reader[4]);
 351      insert.Parameters.AddWithValue("$credentialUuid", reader.GetString(5));
 352      insert.Parameters.AddWithValue("$secretFormatVersion", reader.GetInt32(6));
 353      insert.Parameters.AddWithValue("$metadataFormatVersion", reader.GetInt32(7));
 354      insert.Parameters.AddWithValue("$url", reader.IsDBNull(8) ? DBNull.Value : reader.GetString(8));
 355      insert.Parameters.AddWithValue("$notes", reader.IsDBNull(9) ? DBNull.Value : reader.GetString(9));
 356      insert.Parameters.AddWithValue("$createdAt", reader.GetDateTime(10));
 357      insert.Parameters.AddWithValue("$updatedAt", reader.GetDateTime(11));
 358      insert.Parameters.AddWithValue("$iconKey", reader.IsDBNull(12) ? DBNull.Value : reader.GetString(12));
 359      insert.Parameters.AddWithValue("$credentialType", reader.GetInt32(13));
 360      insert.ExecuteNonQuery();
 361    }
 362  }
 363
 364  private static void CopySettings(SqliteConnection sourceConnection, SqliteConnection destinationConnection, SqliteTran
 365  {
 366    using var sourceCommand = sourceConnection.CreateCommand();
 367    sourceCommand.CommandText = "SELECT Id, Key, Value, CreatedAt, UpdatedAt FROM Settings;";
 368    using var reader = sourceCommand.ExecuteReader();
 369    while (reader.Read())
 370    {
 371      using var insert = destinationConnection.CreateCommand();
 372      insert.Transaction = transaction;
 373      insert.CommandText = @"
 374        INSERT INTO Settings (Id, Key, Value, CreatedAt, UpdatedAt)
 375        VALUES ($id, $key, $value, $createdAt, $updatedAt);";
 376      insert.Parameters.AddWithValue("$id", reader.GetInt64(0));
 377      insert.Parameters.AddWithValue("$key", reader.GetString(1));
 378      insert.Parameters.AddWithValue("$value", reader.GetString(2));
 379      insert.Parameters.AddWithValue("$createdAt", reader.GetDateTime(3));
 380      insert.Parameters.AddWithValue("$updatedAt", reader.GetDateTime(4));
 381      insert.ExecuteNonQuery();
 382    }
 383  }
 384
 385  private static SqlCipherExporterFailureKind ClassifyOpenFailure(Exception exception)
 386  {
 387    if (exception is SqliteException sqliteException)
 388    {
 389      if (sqliteException.SqliteErrorCode == 26)
 390        return SqlCipherExporterFailureKind.WrongKey;
 391
 392      if (sqliteException.Message.Contains("file is not a database", StringComparison.OrdinalIgnoreCase) ||
 393        sqliteException.Message.Contains("not a database", StringComparison.OrdinalIgnoreCase))
 394      {
 395        return SqlCipherExporterFailureKind.WrongKey;
 396      }
 397
 398      return SqlCipherExporterFailureKind.InvalidTarget;
 399    }
 400
 401    if (exception.InnerException is not null)
 402      return ClassifyOpenFailure(exception.InnerException);
 403
 404    return SqlCipherExporterFailureKind.OperationalFailure;
 405  }
 406
 407  private static string GetInnermostMessage(Exception exception)
 408  {
 409    var current = exception;
 410    while (current.InnerException is not null)
 411      current = current.InnerException;
 412
 413    return current.Message;
 414  }
 415
 416  private static SqlCipherProviderPackagingPath GetConfiguredProviderPath()
 417  {
 418#if USE_OFFICIAL_SQLCIPHER_PATH
 419    return SqlCipherProviderPackagingPath.BundleZeteticWithProviderSqlcipher;
 420#else
 421    return SqlCipherProviderPackagingPath.LegacyBundleESqlCipher;
 422#endif
 423  }
 424
 425  private sealed class SqlCipherConnectionFactory : ISqliteConnectionFactory
 426  {
 427    private readonly string _databasePath;
 428    private readonly string _password;
 429
 430    public SqlCipherConnectionFactory(string databasePath, string password)
 431    {
 432      _databasePath = Path.GetFullPath(databasePath);
 433      _password = password;
 434      Storage = new VaultStorageDescriptor(VaultStorageMode.EncryptedSqlite, $"Data Source={_databasePath}", _databasePa
 435    }
 436
 437    public VaultStorageDescriptor Storage { get; }
 438
 439    public SqliteConnection OpenConnection()
 440      => OpenEncryptedConnection(_databasePath, _password, SqliteOpenMode.ReadWriteCreate);
 441  }
 442}
 443
 444internal enum SqlCipherExporterFailureKind
 445{
 446  None = 0,
 447  NativeProviderLoadFailure = 1,
 448  CipherSupportUnavailable = 2,
 449  WrongKey = 3,
 450  InvalidTarget = 4,
 451  MigrationExportFailure = 5,
 452  ValidationFailure = 6,
 453  OperationalFailure = 7,
 454}
 455
 456internal enum SqlCipherProviderPackagingPath
 457{
 458  LegacyBundleESqlCipher = 0,
 459  BundleZeteticWithProviderSqlcipher = 1,
 460  OfficialZetetic = 2,
 461}
 462
 463internal sealed class SqlCipherEncryptedVaultMigrationException : InvalidOperationException
 464{
 465  internal SqlCipherEncryptedVaultMigrationException(SqlCipherExporterFailureKind failureKind, string message, Exception
 466    : base(message, innerException)
 467  {
 468    FailureKind = failureKind;
 469  }
 470
 471  internal SqlCipherExporterFailureKind FailureKind { get; }
 472}
 473
 35474internal sealed record SqlCipherRuntimeProbeResult(
 34475  bool IsAvailable,
 5476  string? CipherVersion,
 1477  string? SqliteVersion,
 14478  SqlCipherProviderPackagingPath ProviderPath,
 12479  bool IsProductionCrediblePath,
 4480  SqlCipherExporterFailureKind FailureKind,
 39481  string? Message);
 482
 483internal sealed record SqlCipherVaultOpenResult(
 484  bool Success,
 485  string? CipherVersion,
 486  SqlCipherProviderPackagingPath ProviderPath,
 487  bool IsProductionCrediblePath,
 488  SqlCipherExporterFailureKind FailureKind,
 489  string? Message);