/// <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; }
/// <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 } ) ); }
/// <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)); }
/// <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); }
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 ) ); }
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(); } }
/// <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(); }
/// <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)); } }
/// <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); } }