private async Task RoundTrip() { ReaderResult initialHeaderResult = await this.reader.ReadHeaderAsync(await this.thisTestInfo.Database.AsIStorageFile.OpenReadAsync(), CancellationToken.None); Assert.AreEqual(ReaderResult.Success, initialHeaderResult, "Initial header read should be successful"); KdbxDecryptionResult result = await this.reader.DecryptFileAsync(await this.thisTestInfo.Database.AsIStorageFile.OpenReadAsync(), this.thisTestInfo.Password, this.thisTestInfo.Keyfile, CancellationToken.None); Assert.AreEqual(ReaderResult.Success, result.Result, "File should have initially decrypted properly"); KdbxDocument kdbxDoc = result.GetDocument(); IKdbxWriter writer = this.reader.GetWriter(); using (var stream = new InMemoryRandomAccessStream()) { bool writeResult = await writer.WriteAsync(stream, kdbxDoc, CancellationToken.None); Assert.IsTrue(writeResult, "File should have written successfully"); stream.Seek(0); KdbxReader newReader = new KdbxReader(); ReaderResult result2 = await newReader.ReadHeaderAsync(stream, CancellationToken.None); Assert.AreEqual(ReaderResult.Success, result2, "Header should been read back successfully after write"); KdbxDecryptionResult result3 = await newReader.DecryptFileAsync(stream, this.thisTestInfo.Password, this.thisTestInfo.Keyfile, CancellationToken.None); Assert.AreEqual(ReaderResult.Success, result3.Result, "File should have decrypted successfully after write"); KdbxDocument roundTrippedDocument = result3.GetDocument(); Assert.AreEqual(kdbxDoc, roundTrippedDocument, "Round-tripped document should be equal to original document"); } }
/// <summary> /// Initializes the instance. /// </summary> /// <param name="writer">IKdbxWriter used to persist the document.</param> /// <param name="settings">Provider for database serialization settings.</param> /// <param name="candidate">Default location to save the document.</param> /// <param name="syncContext">ISyncContext for property change notifications.</param> /// <param name="canSave">Stupid dumb hack since StorageFiles suck on phone and have inaccurate attributes.</param> public DefaultFilePersistenceService(IKdbxWriter writer, IDatabaseSettingsProvider settings, IDatabaseCandidate candidate, ISyncContext syncContext, bool canSave) { this.saveSemaphore = new SemaphoreSlim(1, 1); this.fileWriter = writer ?? throw new ArgumentNullException(nameof(writer)); this.settings = settings ?? throw new ArgumentNullException(nameof(settings)); this.defaultSaveFile = candidate ?? throw new ArgumentNullException(nameof(candidate)); this.syncContext = syncContext ?? throw new ArgumentNullException(nameof(syncContext)); CanSave = canSave; }
public async Task Init() { // Get database from test attributes Utils.DatabaseInfo dbInfo = await Utils.GetDatabaseInfoForTest(TestContext); this.dbPassword = dbInfo.Password; this.dbKeyFile = dbInfo.Keyfile; // Assert that databases named *ReadOnly* are actually readonly after a clone if (dbInfo.Database.Name.IndexOf("ReadOnly", StringComparison.OrdinalIgnoreCase) >= 0) { Assert.IsFalse( await dbInfo.Database.CheckWritableAsync(), $"This file is expected to be read-only; please verify this before testing: {dbInfo.Database.Name}" ); } this.saveFile = (await dbInfo.Database.AsIStorageFile.CopyAsync( ApplicationData.Current.TemporaryFolder, $"PersistenceTestDb-{Guid.NewGuid()}.kdbx", NameCollisionOption.ReplaceExisting )).AsWrapper(); // Use a KdbxReader to parse the database and get a corresponding writer KdbxReader reader = new KdbxReader(); using (IRandomAccessStream stream = await this.saveFile.AsIStorageFile.OpenReadAsync()) { await reader.ReadHeaderAsync(stream, CancellationToken.None); KdbxDecryptionResult decryption = await reader.DecryptFileAsync(stream, dbInfo.Password, dbInfo.Keyfile, CancellationToken.None); Assert.AreEqual(KdbxParserCode.Success, decryption.Result.Code); this.document = decryption.GetDocument(); } // Construct services we can use for the test this.writer = reader.GetWriter(); this.persistenceService = new DefaultFilePersistenceService( this.writer, this.writer, new StorageFileDatabaseCandidate(this.saveFile, true), new MockSyncContext(), true ); this.credentialStorage = new MockCredentialProvider(); this.masterKeyVm = new MasterKeyChangeViewModel( this.document, this.saveFile, new DatabaseCredentialProvider(this.persistenceService, this.credentialStorage), new MockFileService() ); this.settingsVm = new DatabaseSettingsViewModel(this.writer); }
private async Task RaiseDocumentReady(KdbxDocument document, IDatabaseCandidate candidate) { DebugHelper.Assert(HasGoodHeader); if (!HasGoodHeader) { throw new InvalidOperationException("Document cannot be ready, because the KdbxReader does not have good HeaderData."); } IDatabasePersistenceService persistenceService; if (IsSampleFile) { persistenceService = new DummyPersistenceService(); } else { IKdbxWriter writer = this.kdbxReader.GetWriter(); persistenceService = new DefaultFilePersistenceService( writer, writer, candidate, this.syncContext, await candidate.File.CheckWritableAsync() ); } DocumentReady?.Invoke( this, new DocumentReadyEventArgs( document, candidate, persistenceService, this.kdbxReader.HeaderData.GenerateRng(), this.keyChangeVmFactory ) ); }
/// <summary> /// Uses provided options to generate a database file. /// </summary> protected override async Task HandleCredentialsAsync(string confirmedPassword, ITestableFile chosenKeyFile) { CancellationTokenSource cts = new CancellationTokenSource(); IKdbxWriter writer = this.writerFactory.Assemble( confirmedPassword, chosenKeyFile, Settings.Cipher, Settings.GetKdfParameters() ); IRandomNumberGenerator rng = writer.HeaderData.GenerateRng(); KdbxDocument newDocument = new KdbxDocument(new KdbxMetadata("PassKeep Database")); if (!CreateEmpty) { IList <IKeePassGroup> groups = new List <IKeePassGroup> { new KdbxGroup(newDocument.Root.DatabaseGroup), new KdbxGroup(newDocument.Root.DatabaseGroup), new KdbxGroup(newDocument.Root.DatabaseGroup), new KdbxGroup(newDocument.Root.DatabaseGroup), new KdbxGroup(newDocument.Root.DatabaseGroup), new KdbxGroup(newDocument.Root.DatabaseGroup) }; groups[0].Title.ClearValue = "General"; groups[1].Title.ClearValue = "Windows"; groups[2].Title.ClearValue = "Network"; groups[3].Title.ClearValue = "Internet"; groups[4].Title.ClearValue = "eMail"; groups[5].Title.ClearValue = "Homebanking"; groups[0].IconID = 48; groups[1].IconID = 38; groups[2].IconID = 3; groups[3].IconID = 1; groups[4].IconID = 19; groups[5].IconID = 37; foreach (IKeePassGroup group in groups) { newDocument.Root.DatabaseGroup.Children.Add(group); } IList <IKeePassEntry> entries = new List <IKeePassEntry> { new KdbxEntry(newDocument.Root.DatabaseGroup, rng, newDocument.Metadata), new KdbxEntry(newDocument.Root.DatabaseGroup, rng, newDocument.Metadata) }; entries[0].Title.ClearValue = "Sample Entry"; entries[1].Title.ClearValue = "Sample Entry #2"; entries[0].UserName.ClearValue = "User Name"; entries[1].UserName.ClearValue = "Michael321"; entries[0].Password.ClearValue = "Password"; entries[1].Password.ClearValue = "12345"; entries[0].Url.ClearValue = "http://keepass.info/"; entries[1].Url.ClearValue = "http://keepass.info/help/kb/testform.html"; entries[0].Notes.ClearValue = "Notes"; foreach (IKeePassEntry entry in entries) { newDocument.Root.DatabaseGroup.Children.Add(entry); } } using (IRandomAccessStream stream = await File.AsIStorageFile.OpenAsync(FileAccessMode.ReadWrite)) { Task <bool> writeTask = writer.WriteAsync(stream, newDocument, cts.Token); this.taskNotificationService.PushOperation(writeTask, cts, AsyncOperationType.DatabaseEncryption); if (await writeTask) { this.futureAccessList.Add(File, File.AsIStorageItem.Name); IDatabaseCandidate candidate = await this.candidateFactory.AssembleAsync(File); IDatabasePersistenceService persistenceService = new DefaultFilePersistenceService( writer, writer, candidate, this.syncContext, true); DocumentReady?.Invoke( this, new DocumentReadyEventArgs( newDocument, candidate, persistenceService, writer.HeaderData.GenerateRng(), this.keyChangeVmFactory ) ); } } }