/// <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;
 }
Exemple #2
0
        /// <summary>
        /// Helper to generate a new, cached candidate file using the underlying proxy provider.
        /// </summary>
        /// <returns>A cached, local database candidate.</returns>
        private async Task <IDatabaseCandidate> GetCachedCandidateAsync()
        {
            ITestableFile newCandidateFile = await this.proxyProvider.CreateWritableProxyAsync(CandidateFile.File);

            IDatabaseCandidate newCandidate = await this.candidateFactory.AssembleAsync(newCandidateFile);

            DebugHelper.Assert(newCandidateFile != CandidateFile);

            return(newCandidate);
        }
        public async Task DatabaseUnlockViewModel_CachedFileDoesNotChange()
        {
            Assert.IsTrue(this.viewModel.CacheDatabase, "Candidates in scope should be cached automatically");

            await ViewModelHeaderValidated();

            IDatabaseCandidate providedCandidate = (await ViewModelDecrypted()).Candidate;

            Assert.AreNotEqual(this.viewModel.CandidateFile, providedCandidate, "Even though it was cached, a new candidate wrapper should be generated");
            Assert.AreSame(this.viewModel.CandidateFile.File, providedCandidate.File, "The candidate wrappers should point to the same file underneath");
        }
 public CredentialStorageFailureEventArgs(
     ICredentialStorageProvider credentialProvider,
     ISavedCredentialsViewModelFactory credentialViewModelFactory,
     IDatabaseCandidate candidate,
     IBuffer credential
     ) : base()
 {
     this.credentialProvider         = credentialProvider ?? throw new ArgumentNullException(nameof(credentialProvider));
     this.credentialViewModelFactory = credentialViewModelFactory ?? throw new ArgumentNullException(nameof(credentialViewModelFactory));
     this.candidate  = candidate ?? throw new ArgumentNullException(nameof(candidate));
     this.credential = credential ?? throw new ArgumentNullException(nameof(credential));
 }
        public async Task DatabaseUnlockViewModel_SwitchToStoredCredential()
        {
            await DatabaseUnlockViewModel_NoStoredCredential();

            IDatabaseCandidate originalCandidate = this.viewModel.CandidateFile;

            await this.viewModel.UpdateCandidateFileAsync(this.alwaysStoredCandidate);
            await DatabaseUnlockViewModel_HasStoredCredential();

            await this.viewModel.UpdateCandidateFileAsync(originalCandidate);
            await DatabaseUnlockViewModel_NoStoredCredential();
        }
        public async Task DatabaseUnlockViewModel_GenerateCachedFile()
        {
            this.viewModel.CacheDatabase = true;

            await ViewModelHeaderValidated();

            IDatabaseCandidate providedCandidate = (await ViewModelDecrypted()).Candidate;

            Assert.AreNotSame(this.viewModel.CandidateFile.File, providedCandidate.File, "The decryption should spit out a new file");
            Assert.IsTrue(providedCandidate.IsAppOwned, "The new candidate should be app owned");
            Assert.IsFalse(this.viewModel.CandidateFile.IsAppOwned, "The original candidate should still not be app owned");
        }
 /// <summary>
 /// Navigates the Frame to the DatabaseUnlockView for the specified database file.
 /// </summary>
 /// <param name="file">The file to begin unlocking.</param>
 /// <param name="isSample">Whether this is the sample database.</param>
 private void NavigateToOpenedFile(IDatabaseCandidate file, bool isSample = false)
 {
     Frame.Navigate(
         typeof(DatabaseUnlockView),
         new NavigationParameter(
             new {
         file,
         isSampleFile = isSample
     }
             )
         );
 }
Exemple #8
0
        /// <summary>
        /// Generates a new copy of <see cref="CandidateFile"/> that exists in a path controlled
        /// by the app.
        /// </summary>
        /// <returns>A task that completes when the candidate swp is completed.</returns>
        public async Task UseAppControlledDatabaseAsync()
        {
            if (!EligibleForAppControl)
            {
                throw new InvalidOperationException("Cannot generate an app-controlled database if not eligible");
            }

            IDatabaseCandidate newCandidate = await GetCachedCandidateAsync();

            DebugHelper.Assert(newCandidate.File != CandidateFile);

            await UpdateCandidateFileAsync(newCandidate);
        }
        /// <summary>
        /// Attempts to open the sample database.
        /// </summary>
        /// <param name="sender">The "open sample" button.</param>
        /// <param name="e">Args for the click.</param>
        private async void OpenSample_Click(object sender, RoutedEventArgs e)
        {
            // Locate the sample file
            StorageFolder installFolder = Package.Current.InstalledLocation;
            StorageFolder subFolder     = await installFolder.GetFolderAsync("Assets");

            IDatabaseCandidate sample =
                await DatabaseCandidateFactory.AssembleAsync(
                    new StorageFileWrapper(await subFolder.GetFileAsync("SampleDatabase.kdbx"))
                    );

            NavigateToOpenedFile(sample, true);
        }
        /// <summary>
        /// Initializes the EventArgs with the provided parameters.
        /// </summary>
        /// <param name="document">A model representing the decrypted XML document.</param>
        /// <param name="candidate">The file corresponding to the opened document.</param>
        /// <param name="persistenceService">A service that can persist the document.</param>
        /// <param name="rng">A random number generator that can encrypt protected strings for the document.</param>
        /// <param name="keyChangeVmFactory">Factory used to generate the value of <see cref="KeyChangeViewModel"/>.</param>
        public DocumentReadyEventArgs(
            KdbxDocument document,
            IDatabaseCandidate candidate,
            IDatabasePersistenceService persistenceService,
            IRandomNumberGenerator rng,
            IMasterKeyChangeViewModelFactory keyChangeVmFactory)
        {
            Document           = document;
            Candidate          = candidate;
            PersistenceService = persistenceService;
            Rng = rng;

            KeyChangeViewModel = keyChangeVmFactory?.Assemble(document, PersistenceService, candidate.File) ?? throw new ArgumentNullException(nameof(keyChangeVmFactory));
        }
Exemple #11
0
        /// <summary>
        /// Initializes a new instance of the class.
        /// </summary>
        /// <param name="syncContext">Synchronization context used for marshalling to the UI thread.</param>
        /// <param name="file">The candidate document file.</param>
        /// <param name="isSampleFile">Whether the file is a PassKeep sample.</param>
        /// <param name="futureAccessList">A database access list for persisting permission to the database.</param>
        /// <param name="reader">The IKdbxReader implementation used for parsing document files.</param>
        /// <param name="proxyProvider">Generates file proxies that the app controls.</param>
        /// <param name="candidateFactory">Factory used to generate new candidate files as needed.</param>
        /// <param name="keyChangeVmFactory">Factory used to generate the objects used to modify the master key of the database.</param>
        /// <param name="taskNotificationService">A service used to notify the UI of blocking operations.</param>
        /// <param name="identityService">The service used to verify the user's consent for saving credentials.</param>
        /// <param name="credentialProvider">The provider used to store/load saved credentials.</param>
        /// <param name="credentialViewModelFactory">A factory used to generate <see cref="ISavedCredentialsViewModel"/> instances.</param>
        public DatabaseUnlockViewModel(
            ISyncContext syncContext,
            IDatabaseCandidate file,
            bool isSampleFile,
            IDatabaseAccessList futureAccessList,
            IKdbxReader reader,
            IFileProxyProvider proxyProvider,
            IDatabaseCandidateFactory candidateFactory,
            IMasterKeyChangeViewModelFactory keyChangeVmFactory,
            ITaskNotificationService taskNotificationService,
            IIdentityVerificationService identityService,
            ICredentialStorageProvider credentialProvider,
            ISavedCredentialsViewModelFactory credentialViewModelFactory
            )
        {
            this.syncContext                = syncContext ?? throw new ArgumentNullException(nameof(syncContext));
            this.futureAccessList           = futureAccessList;
            this.kdbxReader                 = reader ?? throw new ArgumentNullException(nameof(reader));
            this.proxyProvider              = proxyProvider ?? throw new ArgumentNullException(nameof(proxyProvider));
            this.candidateFactory           = candidateFactory ?? throw new ArgumentNullException(nameof(candidateFactory));
            this.keyChangeVmFactory         = keyChangeVmFactory ?? throw new ArgumentNullException(nameof(keyChangeVmFactory));
            this.taskNotificationService    = taskNotificationService ?? throw new ArgumentNullException(nameof(taskNotificationService));
            this.identityService            = identityService ?? throw new ArgumentNullException(nameof(identityService));
            this.credentialProvider         = credentialProvider ?? throw new ArgumentNullException(nameof(credentialProvider));
            this.credentialViewModelFactory = credentialViewModelFactory ?? throw new ArgumentNullException(nameof(credentialViewModelFactory));

            SaveCredentials            = false;
            IdentityVerifiability      = UserConsentVerifierAvailability.Available;
            UnlockCommand              = new AsyncActionCommand(CanUnlock, DoUnlockAsync);
            UseSavedCredentialsCommand = new AsyncActionCommand(
                () => UnlockCommand.CanExecute(null) && HasSavedCredentials,
                DoUnlockWithSavedCredentials
                );
            IsSampleFile     = isSampleFile;
            RememberDatabase = true;

            this.initialConstruction = UpdateCandidateFileAsync(file);
        }
Exemple #12
0
        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
                    )
                );
        }
Exemple #13
0
        public async Task MultiEdit_Degenerate()
        {
            StorageFileDatabaseCandidateFactory factory = new StorageFileDatabaseCandidateFactory(new MockFileProxyProvider {
                ScopeValue = true
            });
            StorageFolder work = await Utils.GetWorkFolder();

            IDatabaseCandidate workDb = await factory.AssembleAsync(
                (await this.thisTestInfo.Database.AsIStorageFile.CopyAsync(work, "Work.kdbx", NameCollisionOption.ReplaceExisting))
                .AsWrapper()
                );

            IKdbxWriter  writer;
            KdbxDocument doc;

            var reader = new KdbxReader();

            using (IRandomAccessStream stream = await workDb.GetRandomReadAccessStreamAsync())
            {
                ReaderResult headerResult = await reader.ReadHeaderAsync(stream, CancellationToken.None);

                Assert.AreEqual(headerResult, ReaderResult.Success);
            }

            KdbxDecryptionResult bodyResult = null;

            using (IRandomAccessStream stream = await workDb.GetRandomReadAccessStreamAsync())
            {
                bodyResult = await reader.DecryptFileAsync(stream, this.thisTestInfo.Password, this.thisTestInfo.Keyfile, CancellationToken.None);

                Assert.AreEqual(bodyResult.Result, ReaderResult.Success);
            }

            writer = reader.GetWriter();
            doc    = bodyResult.GetDocument();

            IDatabasePersistenceService persistor = new DefaultFilePersistenceService(writer, writer, workDb, new MockSyncContext(), await workDb.File.CheckWritableAsync());

            Assert.IsTrue(persistor.CanSave);
            Assert.IsTrue(await persistor.Save(doc));

            // Remove the last group
            doc.Root.DatabaseGroup.Children.RemoveAt(
                doc.Root.DatabaseGroup.Children.IndexOf(
                    doc.Root.DatabaseGroup.Children.Last(node => node is IKeePassGroup)
                    )
                );
            Assert.IsTrue(await persistor.Save(doc));

            reader = new KdbxReader();
            using (IRandomAccessStream stream = await workDb.GetRandomReadAccessStreamAsync())
            {
                ReaderResult headerResult = await reader.ReadHeaderAsync(stream, CancellationToken.None);

                Assert.AreEqual(headerResult, ReaderResult.Success);
            }
            using (IRandomAccessStream stream = await workDb.GetRandomReadAccessStreamAsync())
            {
                bodyResult = await reader.DecryptFileAsync(stream, this.thisTestInfo.Password, this.thisTestInfo.Keyfile, CancellationToken.None);

                Assert.AreEqual(bodyResult.Result, ReaderResult.Success);
            }

            writer = reader.GetWriter();
            doc    = bodyResult.GetDocument();

            doc.Root.DatabaseGroup.Children.RemoveAt(
                doc.Root.DatabaseGroup.Children.IndexOf(
                    doc.Root.DatabaseGroup.Children.Last(node => node is IKeePassGroup)
                    )
                );
            Assert.IsTrue(await persistor.Save(doc));

            reader = new KdbxReader();
            using (IRandomAccessStream stream = await workDb.GetRandomReadAccessStreamAsync())
            {
                ReaderResult headerResult = await reader.ReadHeaderAsync(stream, CancellationToken.None);

                Assert.AreEqual(headerResult, ReaderResult.Success);
            }
            using (IRandomAccessStream stream = await workDb.GetRandomReadAccessStreamAsync())
            {
                bodyResult = await reader.DecryptFileAsync(stream, this.thisTestInfo.Password, this.thisTestInfo.Keyfile, CancellationToken.None);

                Assert.AreEqual(bodyResult.Result, ReaderResult.Success);
            }

            writer = reader.GetWriter();
            doc    = bodyResult.GetDocument();
        }
        public async Task Initialize()
        {
            TestDataAttribute dataAttr = GetTestAttribute <TestDataAttribute>();

            if (dataAttr != null && dataAttr.SkipInitialization)
            {
                return;
            }

            this.testDatabaseInfo = null;
            IDatabaseCandidate databaseValue = null;
            bool sampleValue = false;

            try
            {
                this.testDatabaseInfo = await Utils.GetDatabaseInfoForTest(TestContext);
            }
            catch (InvalidOperationException) { }

            if (dataAttr?.UseRealProxyProvider != true)
            {
                this.proxyProvider = new MockFileProxyProvider
                {
                    ScopeValue = (dataAttr?.InAppScope == true)
                };
            }
            else
            {
                StorageFolder proxyFolder = ApplicationData.Current.TemporaryFolder;
                proxyFolder = await proxyFolder.CreateFolderAsync("Proxies", CreationCollisionOption.OpenIfExists);

                this.proxyProvider = new FileProxyProvider(proxyFolder);
            }

            IDatabaseCandidateFactory candidateFactory = new StorageFileDatabaseCandidateFactory(this.proxyProvider);

            if (this.testDatabaseInfo != null)
            {
                databaseValue = await candidateFactory.AssembleAsync(this.testDatabaseInfo.Database);

                sampleValue = (dataAttr != null && dataAttr.InitSample);
            }

            if (dataAttr != null && !dataAttr.InitDatabase)
            {
                databaseValue = null;
            }

            this.accessList = new MockStorageItemAccessList();

            this.identityService = new MockIdentityVerifier()
            {
                CanVerify = dataAttr?.IdentityVerifierAvailable ?? UserConsentVerifierAvailability.NotConfiguredForUser,
                Verified  = dataAttr?.IdentityVerified ?? false
            };
            this.credentialProvider = new MockCredentialProvider();

            if (dataAttr?.StoredCredentials == true && databaseValue != null && this.testDatabaseInfo != null)
            {
                Assert.IsTrue(
                    await this.credentialProvider.TryStoreRawKeyAsync(
                        databaseValue.File,
                        this.testDatabaseInfo.RawKey
                        )
                    );
            }

            Utils.DatabaseInfo backupDatabase = await Utils.DatabaseMap["StructureTesting"];
            this.alwaysStoredCandidate = await candidateFactory.AssembleAsync(
                backupDatabase.Database
                );

            Assert.IsTrue(
                await this.credentialProvider.TryStoreRawKeyAsync(
                    this.alwaysStoredCandidate.File,
                    backupDatabase.RawKey
                    )
                );

            this.viewModel = new DatabaseUnlockViewModel(
                new MockSyncContext(),
                databaseValue,
                sampleValue,
                this.accessList,
                new KdbxReader(),
                this.proxyProvider,
                candidateFactory,
                new MasterKeyChangeViewModelFactory(new DatabaseCredentialProviderFactory(this.credentialProvider), new MockFileService()),
                new TaskNotificationService(),
                this.identityService,
                this.credentialProvider,
                new MockCredentialStorageViewModelFactory()
                );

            await this.viewModel.ActivateAsync();

            // Set various ViewModel properties if desired
            if (this.testDatabaseInfo != null && dataAttr != null)
            {
                if (dataAttr.SetPassword)
                {
                    this.viewModel.Password = this.testDatabaseInfo.Password;
                }

                if (dataAttr.SetKeyFile)
                {
                    this.viewModel.KeyFile = this.testDatabaseInfo.Keyfile;
                }
            }

            if (databaseValue != null)
            {
                await ViewModelHeaderValidated();
            }
        }
Exemple #15
0
        /// <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
                            )
                        );
                }
            }
        }
 public Task UpdateCandidateFileAsync(IDatabaseCandidate newCandidate)
 {
     throw new NotImplementedException();
 }
Exemple #17
0
        /// <summary>
        /// Updates the ViewModel with a new candidate file, which kicks off
        /// a new header validation and stored credential check.
        /// </summary>
        /// <param name="newCandidate">The new database candidate.</param>
        /// <returns>A task that completes when the candidate is updated.</returns>
        public async Task UpdateCandidateFileAsync(IDatabaseCandidate newCandidate)
        {
            IDatabaseCandidate oldCandidate = this._candidateFile;

            if (newCandidate != oldCandidate)
            {
                this._candidateFile = newCandidate;
                OnPropertyChanged(nameof(CandidateFile));
                if (newCandidate == null)
                {
                    CacheDatabase = false;
                }
                else if (newCandidate.IsAppOwned)
                {
                    CacheDatabase = true;
                }

                OnPropertyChanged(nameof(EligibleForAppControl));

                // Clear the keyfile for the old selection
                KeyFile = null;

                if (newCandidate != null)
                {
                    // Evaluate whether the new candidate is read-only
                    Task <bool> checkWritable = newCandidate.File?.AsIStorageFile.CheckWritableAsync();
                    checkWritable = checkWritable ?? Task.FromResult(false);

                    TaskScheduler syncContextScheduler;
                    if (SynchronizationContext.Current != null)
                    {
                        syncContextScheduler = TaskScheduler.FromCurrentSynchronizationContext();
                    }
                    else
                    {
                        // If there is no SyncContext for this thread (e.g. we are in a unit test
                        // or console scenario instead of running in an app), then just use the
                        // default scheduler because there is no UI thread to sync with.
                        syncContextScheduler = TaskScheduler.Current;
                    }

                    Task fileAccessUpdate = checkWritable.ContinueWith(
                        async(task) =>
                    {
                        IsReadOnly = !task.Result;
                        await ValidateHeader();
                    },
                        syncContextScheduler
                        );

                    // Evaluate whether we have saved credentials for this database
                    Task hasCredentialsUpdate = this.credentialProvider.GetRawKeyAsync(newCandidate.File)
                                                .ContinueWith(
                        (task) =>
                    {
                        HasSavedCredentials = task.Result != null;
                    },
                        syncContextScheduler
                        );


                    await Task.WhenAll(fileAccessUpdate, hasCredentialsUpdate);
                }
                else
                {
                    IsReadOnly          = false;
                    HasSavedCredentials = false;
                }

                ParseResult = null;
                OnPropertyChanged(nameof(ForbidTogglingRememberDatabase));
            }
        }
Exemple #18
0
        /// <summary>
        /// Attempts to unlock the document file.
        /// </summary>
        /// <param name="storedCredential">The key to use for decryption - if null, the ViewModel's
        /// credentials are used instead.</param>
        private async Task DoUnlockAsync(IBuffer storedCredential)
        {
            DebugHelper.Assert(CanUnlock());
            if (!CanUnlock())
            {
                throw new InvalidOperationException("The ViewModel is not in a state that can unlock the database!");
            }

            CancellationTokenSource cts = new CancellationTokenSource();

            try
            {
                using (IRandomAccessStream stream = await CandidateFile.GetRandomReadAccessStreamAsync())
                {
                    Task <KdbxDecryptionResult> decryptionTask;
                    if (storedCredential != null)
                    {
                        decryptionTask = this.kdbxReader.DecryptFile(stream, storedCredential, cts.Token);
                    }
                    else
                    {
                        decryptionTask = this.kdbxReader.DecryptFileAsync(stream, Password, KeyFile, cts.Token);
                    }

                    if (this.taskNotificationService.CurrentTask == null || this.taskNotificationService.CurrentTask.IsCompleted)
                    {
                        this.taskNotificationService.PushOperation(decryptionTask, cts, AsyncOperationType.DatabaseDecryption);
                    }
                    KdbxDecryptionResult result = await decryptionTask;

                    ParseResult = result.Result;

                    DebugHelper.Trace($"Got ParseResult from database unlock attempt: {ParseResult}");
                    if (!ParseResult.IsError)
                    {
                        // The database candidate to proceed into the next stage with
                        IDatabaseCandidate candidateToUse = CandidateFile;

                        if (CacheDatabase)
                        {
                            // We do not use UseAppControlledDatabaseAsync here because it has extra baggage.
                            // We don't need to refresh the view at this stage, just fire an event using
                            // the cached file.
                            candidateToUse = await GetCachedCandidateAsync();
                        }
                        if (RememberDatabase)
                        {
                            string accessToken = this.futureAccessList.Add(candidateToUse.File, candidateToUse.FileName);
                            DebugHelper.Trace($"Unlock was successful and database was remembered with token: {accessToken}");
                        }
                        else
                        {
                            DebugHelper.Trace("Unlock was successful but user opted not to remember the database.");
                        }

                        if (SaveCredentials)
                        {
                            bool storeCredential = false;

                            // If we were not already using a stored credential, we need user
                            // consent to continue.
                            if (storedCredential == null)
                            {
                                Task <bool> identityTask = this.identityService.VerifyIdentityAsync();
                                if (this.taskNotificationService.CurrentTask == null || this.taskNotificationService.CurrentTask.IsCompleted)
                                {
                                    this.taskNotificationService.PushOperation(identityTask, AsyncOperationType.IdentityVerification);
                                }

                                storeCredential  = await identityTask;
                                storedCredential = result.GetRawKey();
                            }
                            else
                            {
                                // If we have a stored credential, we already got consent.
                                storeCredential = true;
                            }

                            if (storeCredential)
                            {
                                if (!await this.credentialProvider.TryStoreRawKeyAsync(candidateToUse.File, storedCredential))
                                {
                                    EventHandler <CredentialStorageFailureEventArgs> handler = CredentialStorageFailed;
                                    if (handler != null)
                                    {
                                        // If we could not store a credential, give the View a chance to try again.
                                        CredentialStorageFailureEventArgs eventArgs =
                                            new CredentialStorageFailureEventArgs(
                                                this.credentialProvider,
                                                this.credentialViewModelFactory,
                                                candidateToUse,
                                                storedCredential
                                                );

                                        handler(this, eventArgs);
                                        await eventArgs.DeferAsync();
                                    }
                                }
                            }
                        }

                        await RaiseDocumentReady(result.GetDocument(), candidateToUse);
                    }
                }
            }
            catch (COMException)
            {
                // In the Windows 8.1 preview, opening a stream to a SkyDrive file can fail with no workaround.
                ParseResult = new ReaderResult(KdbxParserCode.UnableToReadFile);
            }
        }