From 7e44d47065626945ea8cf63a694d367fcf556592 Mon Sep 17 00:00:00 2001 From: Geoffroy BONNEVILLE Date: Tue, 24 Mar 2020 13:01:14 +0100 Subject: [PATCH] WIP Clean Architecture Windows 8.1 App Uses keepasslib v2.44 (temporarily) --- .../Common/Interfaces/ICryptographyClient.cs | 12 + .../Common/Interfaces/IDatabaseProxy.cs | 28 + .../Common/Interfaces/IFileProxy.cs | 13 + .../Common/Interfaces/IHasSelectableObject.cs | 7 + .../Common/Interfaces/IImportFormat.cs | 10 + .../Common/Interfaces/IIsEnabled.cs | 7 + .../Common/Interfaces/IPasswordProxy.cs | 11 + .../Interfaces/IProxyInvocationHandler.cs | 9 + .../Common/Interfaces/IRecentProxy.cs | 15 + .../Common/Interfaces/IResourceProxy.cs | 7 + .../Common/Interfaces/ISelectableModel.cs | 7 + .../Common/Interfaces/ISettingsProxy.cs | 8 + .../Common/Mappings/IMapFrom.cs | 10 + .../Common/Mappings/MappingProfile.cs | 30 + .../Common/Mappings/MappingProfiles.cs | 17 + .../CloseDatabase/CloseDatabaseCommand.cs | 31 + .../CreateDatabase/CreateDatabaseCommand.cs | 48 + .../SaveDatabase/SaveDatabaseCommand.cs | 31 + .../Database/Models/DatabaseVm.cs | 21 + .../Database/Models/MainVm.cs | 7 + .../IsDatabaseOpen/IsDatabaseOpenQuery.cs | 24 + .../Queries/OpenDatabase/OpenDatabaseQuery.cs | 48 + .../OpenDatabaseQueryValidator.cs | 12 + .../DependencyInjection.cs | 13 + .../Entry/Models/EntryVm.cs | 27 + .../Group/Models/GroupVm.cs | 33 + .../ModernKeePass.Application.csproj | 59 + .../Services/CryptographyService.cs | 22 + .../Services/DatabaseService.cs | 103 + .../Services/FileService.cs | 37 + .../Services/ImportService.cs | 35 + .../Services/RecentService.cs | 40 + .../Services/ResourceService.cs | 20 + .../Services/SecurityService.cs | 35 + .../Services/SettingsService.cs | 25 + .../Common/Interfaces/ICryptographyClient.cs | 12 + .../Common/Interfaces/IDatabaseProxy.cs | 28 + .../Common/Interfaces/IFileProxy.cs | 13 + .../Common/Interfaces/IHasSelectableObject.cs | 7 + .../Common/Interfaces/IImportFormat.cs | 10 + .../Common/Interfaces/IIsEnabled.cs | 7 + .../Common/Interfaces/IPasswordProxy.cs | 11 + .../Interfaces/IProxyInvocationHandler.cs | 9 + .../Common/Interfaces/IRecentProxy.cs | 15 + .../Common/Interfaces/IResourceProxy.cs | 7 + .../Common/Interfaces/ISelectableModel.cs | 7 + .../Common/Interfaces/ISettingsProxy.cs | 8 + .../Common/Mappings/IMapFrom.cs | 10 + .../Common/Mappings/MappingProfile.cs | 30 + .../Common/Mappings/MappingProfiles.cs | 17 + .../CloseDatabase/CloseDatabaseCommand.cs | 31 + .../CreateDatabase/CreateDatabaseCommand.cs | 48 + .../SaveDatabase/SaveDatabaseCommand.cs | 31 + .../Database/Models/DatabaseVm.cs | 21 + .../Database/Models/MainVm.cs | 7 + .../IsDatabaseOpen/IsDatabaseOpenQuery.cs | 24 + .../Queries/OpenDatabase/OpenDatabaseQuery.cs | 48 + .../OpenDatabaseQueryValidator.cs | 12 + .../Entry/Models/EntryVm.cs | 27 + .../Group/Models/GroupVm.cs | 33 + .../ModernKeePass.Application.csproj | 104 + .../Properties/AssemblyInfo.cs | 10 +- .../Services/CryptographyService.cs | 22 + .../Services/DatabaseService.cs | 103 + .../Services/FileService.cs | 37 + .../Services/ImportService.cs | 35 + .../Services/RecentService.cs | 40 + .../Services/ResourceService.cs | 20 + .../Services/SecurityService.cs | 35 + .../Services/SettingsService.cs | 25 + ModernKeePass.Application/project.json | 15 + .../AOP/NotifyPropertyChangedBase.cs | 28 + ModernKeePass.Domain.12/Dtos/Credentials.cs | 9 + ModernKeePass.Domain.12/Dtos/FileInfo.cs | 8 + .../Dtos/PasswordGenerationOptions.cs | 16 + .../Entities/BaseEntity.cs | 14 + .../Entities/DatabaseEntity.cs | 9 + .../Entities/EntryEntity.cs | 22 + .../Entities/GroupEntity.cs | 12 + .../Enums/CredentialStatusTypes.cs | 10 + ModernKeePass.Domain.12/Enums/Icon.cs | 54 + ModernKeePass.Domain.12/Enums/ImportFormat.cs | 7 + .../Exceptions/DatabaseClosedException.cs | 7 + .../Exceptions/DatabaseOpenException.cs | 7 + .../Exceptions/NavigationException.cs | 11 + .../Exceptions/SaveException.cs | 14 + .../ModernKeePass.Domain.csproj | 11 + .../AOP/NotifyPropertyChangedBase.cs | 28 + ModernKeePass.Domain/Dtos/Credentials.cs | 9 + ModernKeePass.Domain/Dtos/FileInfo.cs | 8 + .../Dtos/PasswordGenerationOptions.cs | 16 + ModernKeePass.Domain/Entities/BaseEntity.cs | 14 + .../Entities/DatabaseEntity.cs | 9 + ModernKeePass.Domain/Entities/EntryEntity.cs | 19 + ModernKeePass.Domain/Entities/GroupEntity.cs | 12 + .../Enums/CredentialStatusTypes.cs | 10 + ModernKeePass.Domain/Enums/Icon.cs | 54 + ModernKeePass.Domain/Enums/ImportFormat.cs | 7 + .../Exceptions/DatabaseClosedException.cs | 7 + .../Exceptions/DatabaseOpenException.cs | 7 + .../Exceptions/NavigationException.cs | 11 + .../Exceptions/SaveException.cs | 14 + .../ModernKeePass.Domain.csproj | 21 +- .../Properties/AssemblyInfo.cs | 6 +- .../project.json | 3 +- .../DependencyInjection.cs | 25 + .../File/CsvImportFormat.cs | 39 + .../KeePass/EntryMappingProfile.cs | 76 + .../KeePass/IconMapper.cs | 126 + .../KeePass/KeePassCryptographyClient.cs | 38 + .../KeePass/KeePassDatabaseClient.cs | 266 +++ .../KeePass/KeePassPasswordClient.cs | 46 + .../Libs/Windows.winmd | Bin .../ModernKeePass.Infrastructure.csproj | 80 + .../Properties/AssemblyInfo.cs | 30 + .../UWP/StorageFileClient.cs | 38 + .../UWP/UwpRecentFilesClient.cs | 63 + .../UWP/UwpResourceClient.cs | 17 + .../UWP/UwpSettingsClient.cs | 31 + ModernKeePass.Infrastructure/project.json | 11 + .../DependencyInjection.cs | 25 + .../File/CsvImportFormat.cs | 39 + .../KeePass/EntryMappingProfile.cs | 76 + .../KeePass/IconMapper.cs | 126 + .../KeePass/KeePassCryptographyClient.cs | 38 + .../KeePass/KeePassDatabaseClient.cs | 266 +++ .../KeePass/KeePassPasswordClient.cs | 46 + .../Libs/Windows.winmd | Bin 0 -> 3345728 bytes .../ModernKeePass.Infrastructure.csproj | 24 + .../UWP/StorageFileClient.cs | 38 + .../UWP/UwpRecentFilesClient.cs | 63 + .../UWP/UwpResourceClient.cs | 17 + .../UWP/UwpSettingsClient.cs | 31 + ModernKeePass.sln | 122 +- ModernKeePass/Interfaces/IDatabaseService.cs | 7 +- ModernKeePass/ModernKeePass.App.csproj | 56 +- ModernKeePass/Services/DatabaseService.cs | 12 +- ModernKeePass/ViewModels/CompositeKeyVm.cs | 20 +- ModernKeePass/packages.config | 24 +- .../Cryptography/Cipher/Chacha20Tests.cs | 204 -- .../Cipher/StandardAesEngineTests.cs | 74 - .../Cryptography/CryptoRandomStreamTests.cs | 56 - .../Cryptography/CryptoRandomTests.cs | 38 - .../Cryptography/Hash/Blake2bTests.cs | 101 - .../Cryptography/Hash/HmacTests.cs | 117 - .../Cryptography/Hash/SHAManagedTests.cs | 42 - .../Cryptography/HashingStreamExTests.cs | 83 - .../Cryptography/HmacOtpTests.cs | 32 - .../Cryptography/KeyDerivation/AesKdfTests.cs | 42 - .../Cryptography/KeyDerivation/Argon2Tests.cs | 146 -- .../Images/UnitTestLogo.scale-100.png | Bin 5789 -> 0 bytes .../Images/UnitTestSmallLogo.scale-100.png | Bin 745 -> 0 bytes .../Images/UnitTestSplashScreen.scale-100.png | Bin 9381 -> 0 bytes .../Images/UnitTestStoreLogo.scale-100.png | Bin 2005 -> 0 bytes .../Keys/CompositeKeyTests.cs | 35 - .../Keys/KcpCustomKeyTests.cs | 35 - ModernKeePassLib.Test/Keys/KcpKeyFileTests.cs | 84 - .../Keys/KcpPasswordTests.cs | 29 - .../ModernKeePass.LibTest.csproj | 201 -- ModernKeePassLib.Test/Package.appxmanifest | 32 - .../Serialization/HashedBlockStreamTests.cs | 73 - .../Serialization/KdbxFileTests.cs | 171 -- ModernKeePassLib.Test/Utility/GfxUtilTests.cs | 27 - ModernKeePassLib.Test/Utility/MemUtilTests.cs | 89 - ModernKeePassLib.Test/packages.config | 46 - .../Collections/AutoTypeConfig.cs | 244 -- .../Collections/ProtectedBinaryDictionary.cs | 172 -- .../Collections/ProtectedBinarySet.cs | 174 -- .../Collections/ProtectedStringDictionary.cs | 298 --- ModernKeePassLib/Collections/PwObjectList.cs | 380 --- ModernKeePassLib/Collections/PwObjectPool.cs | 232 -- .../Collections/StringDictionaryEx.cs | 130 -- .../Collections/VariantDictionary.cs | 415 ---- .../Cryptography/Cipher/ChaCha20Cipher.cs | 254 -- .../Cryptography/Cipher/ChaCha20Engine.cs | 177 -- .../Cryptography/Cipher/CipherPool.cs | 165 -- .../Cryptography/Cipher/CtrBlockCipher.cs | 109 - .../Cryptography/Cipher/ICipherEngine.cs | 87 - .../Cryptography/Cipher/Salsa20Cipher.cs | 165 -- .../Cryptography/Cipher/StandardAesEngine.cs | 173 -- ModernKeePassLib/Cryptography/CryptoRandom.cs | 398 ---- .../Cryptography/CryptoRandomStream.cs | 264 --- ModernKeePassLib/Cryptography/CryptoUtil.cs | 237 -- ModernKeePassLib/Cryptography/Hash/Blake2b.cs | 394 ---- ModernKeePassLib/Cryptography/Hash/HMAC.cs | 62 - .../Cryptography/Hash/HMACSHA1.cs | 10 - .../Cryptography/Hash/HMACSHA256.cs | 10 - .../Cryptography/Hash/HashAlgorithm.cs | 100 - .../Cryptography/Hash/SHA256Managed.cs | 9 - .../Cryptography/Hash/SHA512Managed.cs | 9 - .../Cryptography/HashingStreamEx.cs | 186 -- ModernKeePassLib/Cryptography/HmacOtp.cs | 96 - .../KeyDerivation/AesKdf.GCrypt.cs | 399 ---- .../Cryptography/KeyDerivation/AesKdf.cs | 281 --- .../KeyDerivation/Argon2Kdf.Core.cs | 637 ----- .../Cryptography/KeyDerivation/Argon2Kdf.cs | 144 -- .../Cryptography/KeyDerivation/KdfEngine.cs | 142 -- .../KeyDerivation/KdfParameters.cs | 80 - .../Cryptography/KeyDerivation/KdfPool.cs | 96 - .../CharSetBasedGenerator.cs | 65 - .../PasswordGenerator/CustomPwGenerator.cs | 66 - .../CustomPwGeneratorPool.cs | 110 - .../PatternBasedGenerator.cs | 184 -- .../PasswordGenerator/PwCharSet.cs | 351 --- .../PasswordGenerator/PwGenerator.cs | 167 -- .../PasswordGenerator/PwProfile.cs | 276 --- .../Cryptography/PopularPasswords.cs | 140 -- .../Cryptography/ProtectedData.cs | 18 - .../Cryptography/QualityEstimation.cs | 779 ------- ModernKeePassLib/Cryptography/SelfTest.cs | 1141 --------- ModernKeePassLib/Delegates/Handlers.cs | 66 - ModernKeePassLib/Interfaces/IDeepCloneable.cs | 37 - ModernKeePassLib/Interfaces/IStatusLogger.cs | 105 - ModernKeePassLib/Interfaces/IStructureItem.cs | 37 - ModernKeePassLib/Interfaces/ITimeLogger.cs | 105 - ModernKeePassLib/Interfaces/IUIOperations.cs | 37 - .../Interfaces/IXmlSerializerEx.cs | 33 - ModernKeePassLib/KeePassLib.pfx | Bin 1756 -> 0 bytes ModernKeePassLib/Keys/CompositeKey.cs | 295 --- ModernKeePassLib/Keys/IUserKey.cs | 46 - ModernKeePassLib/Keys/KcpCustomKey.cs | 68 - ModernKeePassLib/Keys/KcpKeyFile.cs | 350 --- ModernKeePassLib/Keys/KcpPassword.cs | 106 - ModernKeePassLib/Keys/KcpUserAccount.cs | 167 -- ModernKeePassLib/Keys/KeyProvider.cs | 152 -- ModernKeePassLib/Keys/KeyProviderPool.cs | 105 - ModernKeePassLib/Keys/KeyValidator.cs | 51 - ModernKeePassLib/Keys/KeyValidatorPool.cs | 86 - ModernKeePassLib/Keys/UserKeyType.cs | 33 - ModernKeePassLib/ModernKeePass.Lib.csproj | 188 -- .../ModernKeePassLib.nuget.targets | 9 - ModernKeePassLib/ModernKeePassLib.nuspec | 36 - .../ModernKeePassLibPCL.nuget.targets | 9 - ModernKeePassLib/Native/ClipboardU.cs | 190 -- ModernKeePassLib/Native/Native.PCL.cs | 88 - ModernKeePassLib/Native/NativeLib.cs | 449 ---- ModernKeePassLib/Native/NativeMethods.Unix.cs | 216 -- ModernKeePassLib/Native/NativeMethods.cs | 260 --- ModernKeePassLib/Properties/AssemblyInfo.cs | 44 - ModernKeePassLib/PwCustomIcon.cs | 131 -- ModernKeePassLib/PwDatabase.cs | 2077 ----------------- ModernKeePassLib/PwDefs.cs | 505 ---- ModernKeePassLib/PwDeletedObject.cs | 86 - ModernKeePassLib/PwEntry.cs | 947 -------- ModernKeePassLib/PwEnums.cs | 319 --- ModernKeePassLib/PwGroup.cs | 1664 ------------- ModernKeePassLib/PwUuid.cs | 215 -- ModernKeePassLib/README.md | 13 - ModernKeePassLib/Resources/KLRes.Generated.cs | 546 ----- ModernKeePassLib/Resources/KSRes.Generated.cs | 52 - ModernKeePassLib/Security/ProtectedBinary.cs | 418 ---- ModernKeePassLib/Security/ProtectedString.cs | 436 ---- ModernKeePassLib/Security/XorredBuffer.cs | 116 - .../Serialization/BinaryReaderEx.cs | 92 - ModernKeePassLib/Serialization/FileLock.cs | 274 --- .../Serialization/FileTransactionEx.cs | 473 ---- .../Serialization/HashedBlockStream.cs | 312 --- .../Serialization/HmacBlockStream.cs | 326 --- .../Serialization/IOConnection.cs | 926 -------- .../Serialization/IOConnectionInfo.cs | 392 ---- .../Serialization/IocProperties.cs | 192 -- .../Serialization/IocPropertyInfo.cs | 99 - .../Serialization/IocPropertyInfoPool.cs | 123 - .../Serialization/KdbxFile.Read.Streamed.cs | 1077 --------- .../Serialization/KdbxFile.Read.cs | 592 ----- .../Serialization/KdbxFile.Write.cs | 1051 --------- ModernKeePassLib/Serialization/KdbxFile.cs | 547 ----- .../Serialization/OldFormatException.cs | 66 - .../Translation/KPControlCustomization.cs | 400 ---- .../Translation/KPFormCustomization.cs | 108 - ModernKeePassLib/Translation/KPStringTable.cs | 102 - .../Translation/KPStringTableItem.cs | 51 - ModernKeePassLib/Translation/KPTranslation.cs | 312 --- .../Translation/KPTranslationProperties.cs | 105 - ModernKeePassLib/Utility/AppLogEx.cs | 103 - ModernKeePassLib/Utility/ColorTranslator.cs | 57 - ModernKeePassLib/Utility/GfxUtil.PCL.cs | 23 - ModernKeePassLib/Utility/GfxUtil.cs | 441 ---- ModernKeePassLib/Utility/MemUtil.cs | 872 ------- ModernKeePassLib/Utility/MessageService.cs | 459 ---- .../Utility/MonoWorkaround.PCL.cs | 10 - ModernKeePassLib/Utility/MonoWorkarounds.cs | 597 ----- ModernKeePassLib/Utility/StrUtil.cs | 1818 --------------- ModernKeePassLib/Utility/StreamExtensions.cs | 17 - ModernKeePassLib/Utility/TimeUtil.cs | 482 ---- ModernKeePassLib/Utility/TypeOverridePool.cs | 65 - ModernKeePassLib/Utility/UrlUtil.cs | 787 ------- ModernKeePassLib/Utility/XmlUtilEx.cs | 127 - ModernKeePassLib/app.config | 139 -- ModernKeePassLib/project.json | 16 - 290 files changed, 4084 insertions(+), 36416 deletions(-) create mode 100644 ModernKeePass.Application.12/Common/Interfaces/ICryptographyClient.cs create mode 100644 ModernKeePass.Application.12/Common/Interfaces/IDatabaseProxy.cs create mode 100644 ModernKeePass.Application.12/Common/Interfaces/IFileProxy.cs create mode 100644 ModernKeePass.Application.12/Common/Interfaces/IHasSelectableObject.cs create mode 100644 ModernKeePass.Application.12/Common/Interfaces/IImportFormat.cs create mode 100644 ModernKeePass.Application.12/Common/Interfaces/IIsEnabled.cs create mode 100644 ModernKeePass.Application.12/Common/Interfaces/IPasswordProxy.cs create mode 100644 ModernKeePass.Application.12/Common/Interfaces/IProxyInvocationHandler.cs create mode 100644 ModernKeePass.Application.12/Common/Interfaces/IRecentProxy.cs create mode 100644 ModernKeePass.Application.12/Common/Interfaces/IResourceProxy.cs create mode 100644 ModernKeePass.Application.12/Common/Interfaces/ISelectableModel.cs create mode 100644 ModernKeePass.Application.12/Common/Interfaces/ISettingsProxy.cs create mode 100644 ModernKeePass.Application.12/Common/Mappings/IMapFrom.cs create mode 100644 ModernKeePass.Application.12/Common/Mappings/MappingProfile.cs create mode 100644 ModernKeePass.Application.12/Common/Mappings/MappingProfiles.cs create mode 100644 ModernKeePass.Application.12/Database/Commands/CloseDatabase/CloseDatabaseCommand.cs create mode 100644 ModernKeePass.Application.12/Database/Commands/CreateDatabase/CreateDatabaseCommand.cs create mode 100644 ModernKeePass.Application.12/Database/Commands/SaveDatabase/SaveDatabaseCommand.cs create mode 100644 ModernKeePass.Application.12/Database/Models/DatabaseVm.cs create mode 100644 ModernKeePass.Application.12/Database/Models/MainVm.cs create mode 100644 ModernKeePass.Application.12/Database/Queries/IsDatabaseOpen/IsDatabaseOpenQuery.cs create mode 100644 ModernKeePass.Application.12/Database/Queries/OpenDatabase/OpenDatabaseQuery.cs create mode 100644 ModernKeePass.Application.12/Database/Queries/OpenDatabase/OpenDatabaseQueryValidator.cs create mode 100644 ModernKeePass.Application.12/DependencyInjection.cs create mode 100644 ModernKeePass.Application.12/Entry/Models/EntryVm.cs create mode 100644 ModernKeePass.Application.12/Group/Models/GroupVm.cs create mode 100644 ModernKeePass.Application.12/ModernKeePass.Application.csproj create mode 100644 ModernKeePass.Application.12/Services/CryptographyService.cs create mode 100644 ModernKeePass.Application.12/Services/DatabaseService.cs create mode 100644 ModernKeePass.Application.12/Services/FileService.cs create mode 100644 ModernKeePass.Application.12/Services/ImportService.cs create mode 100644 ModernKeePass.Application.12/Services/RecentService.cs create mode 100644 ModernKeePass.Application.12/Services/ResourceService.cs create mode 100644 ModernKeePass.Application.12/Services/SecurityService.cs create mode 100644 ModernKeePass.Application.12/Services/SettingsService.cs create mode 100644 ModernKeePass.Application/Common/Interfaces/ICryptographyClient.cs create mode 100644 ModernKeePass.Application/Common/Interfaces/IDatabaseProxy.cs create mode 100644 ModernKeePass.Application/Common/Interfaces/IFileProxy.cs create mode 100644 ModernKeePass.Application/Common/Interfaces/IHasSelectableObject.cs create mode 100644 ModernKeePass.Application/Common/Interfaces/IImportFormat.cs create mode 100644 ModernKeePass.Application/Common/Interfaces/IIsEnabled.cs create mode 100644 ModernKeePass.Application/Common/Interfaces/IPasswordProxy.cs create mode 100644 ModernKeePass.Application/Common/Interfaces/IProxyInvocationHandler.cs create mode 100644 ModernKeePass.Application/Common/Interfaces/IRecentProxy.cs create mode 100644 ModernKeePass.Application/Common/Interfaces/IResourceProxy.cs create mode 100644 ModernKeePass.Application/Common/Interfaces/ISelectableModel.cs create mode 100644 ModernKeePass.Application/Common/Interfaces/ISettingsProxy.cs create mode 100644 ModernKeePass.Application/Common/Mappings/IMapFrom.cs create mode 100644 ModernKeePass.Application/Common/Mappings/MappingProfile.cs create mode 100644 ModernKeePass.Application/Common/Mappings/MappingProfiles.cs create mode 100644 ModernKeePass.Application/Database/Commands/CloseDatabase/CloseDatabaseCommand.cs create mode 100644 ModernKeePass.Application/Database/Commands/CreateDatabase/CreateDatabaseCommand.cs create mode 100644 ModernKeePass.Application/Database/Commands/SaveDatabase/SaveDatabaseCommand.cs create mode 100644 ModernKeePass.Application/Database/Models/DatabaseVm.cs create mode 100644 ModernKeePass.Application/Database/Models/MainVm.cs create mode 100644 ModernKeePass.Application/Database/Queries/IsDatabaseOpen/IsDatabaseOpenQuery.cs create mode 100644 ModernKeePass.Application/Database/Queries/OpenDatabase/OpenDatabaseQuery.cs create mode 100644 ModernKeePass.Application/Database/Queries/OpenDatabase/OpenDatabaseQueryValidator.cs create mode 100644 ModernKeePass.Application/Entry/Models/EntryVm.cs create mode 100644 ModernKeePass.Application/Group/Models/GroupVm.cs create mode 100644 ModernKeePass.Application/ModernKeePass.Application.csproj rename {ModernKeePassLib.Test => ModernKeePass.Application}/Properties/AssemblyInfo.cs (76%) create mode 100644 ModernKeePass.Application/Services/CryptographyService.cs create mode 100644 ModernKeePass.Application/Services/DatabaseService.cs create mode 100644 ModernKeePass.Application/Services/FileService.cs create mode 100644 ModernKeePass.Application/Services/ImportService.cs create mode 100644 ModernKeePass.Application/Services/RecentService.cs create mode 100644 ModernKeePass.Application/Services/ResourceService.cs create mode 100644 ModernKeePass.Application/Services/SecurityService.cs create mode 100644 ModernKeePass.Application/Services/SettingsService.cs create mode 100644 ModernKeePass.Application/project.json create mode 100644 ModernKeePass.Domain.12/AOP/NotifyPropertyChangedBase.cs create mode 100644 ModernKeePass.Domain.12/Dtos/Credentials.cs create mode 100644 ModernKeePass.Domain.12/Dtos/FileInfo.cs create mode 100644 ModernKeePass.Domain.12/Dtos/PasswordGenerationOptions.cs create mode 100644 ModernKeePass.Domain.12/Entities/BaseEntity.cs create mode 100644 ModernKeePass.Domain.12/Entities/DatabaseEntity.cs create mode 100644 ModernKeePass.Domain.12/Entities/EntryEntity.cs create mode 100644 ModernKeePass.Domain.12/Entities/GroupEntity.cs create mode 100644 ModernKeePass.Domain.12/Enums/CredentialStatusTypes.cs create mode 100644 ModernKeePass.Domain.12/Enums/Icon.cs create mode 100644 ModernKeePass.Domain.12/Enums/ImportFormat.cs create mode 100644 ModernKeePass.Domain.12/Exceptions/DatabaseClosedException.cs create mode 100644 ModernKeePass.Domain.12/Exceptions/DatabaseOpenException.cs create mode 100644 ModernKeePass.Domain.12/Exceptions/NavigationException.cs create mode 100644 ModernKeePass.Domain.12/Exceptions/SaveException.cs create mode 100644 ModernKeePass.Domain.12/ModernKeePass.Domain.csproj create mode 100644 ModernKeePass.Domain/AOP/NotifyPropertyChangedBase.cs create mode 100644 ModernKeePass.Domain/Dtos/Credentials.cs create mode 100644 ModernKeePass.Domain/Dtos/FileInfo.cs create mode 100644 ModernKeePass.Domain/Dtos/PasswordGenerationOptions.cs create mode 100644 ModernKeePass.Domain/Entities/BaseEntity.cs create mode 100644 ModernKeePass.Domain/Entities/DatabaseEntity.cs create mode 100644 ModernKeePass.Domain/Entities/EntryEntity.cs create mode 100644 ModernKeePass.Domain/Entities/GroupEntity.cs create mode 100644 ModernKeePass.Domain/Enums/CredentialStatusTypes.cs create mode 100644 ModernKeePass.Domain/Enums/Icon.cs create mode 100644 ModernKeePass.Domain/Enums/ImportFormat.cs create mode 100644 ModernKeePass.Domain/Exceptions/DatabaseClosedException.cs create mode 100644 ModernKeePass.Domain/Exceptions/DatabaseOpenException.cs create mode 100644 ModernKeePass.Domain/Exceptions/NavigationException.cs create mode 100644 ModernKeePass.Domain/Exceptions/SaveException.cs rename ModernKeePass.Shared/ModernKeePass.Shared.csproj => ModernKeePass.Domain/ModernKeePass.Domain.csproj (70%) rename {ModernKeePass.Shared => ModernKeePass.Domain}/Properties/AssemblyInfo.cs (85%) rename {ModernKeePass.Shared => ModernKeePass.Domain}/project.json (72%) create mode 100644 ModernKeePass.Infrastructure/DependencyInjection.cs create mode 100644 ModernKeePass.Infrastructure/File/CsvImportFormat.cs create mode 100644 ModernKeePass.Infrastructure/KeePass/EntryMappingProfile.cs create mode 100644 ModernKeePass.Infrastructure/KeePass/IconMapper.cs create mode 100644 ModernKeePass.Infrastructure/KeePass/KeePassCryptographyClient.cs create mode 100644 ModernKeePass.Infrastructure/KeePass/KeePassDatabaseClient.cs create mode 100644 ModernKeePass.Infrastructure/KeePass/KeePassPasswordClient.cs rename {ModernKeePassLib => ModernKeePass.Infrastructure}/Libs/Windows.winmd (100%) create mode 100644 ModernKeePass.Infrastructure/ModernKeePass.Infrastructure.csproj create mode 100644 ModernKeePass.Infrastructure/Properties/AssemblyInfo.cs create mode 100644 ModernKeePass.Infrastructure/UWP/StorageFileClient.cs create mode 100644 ModernKeePass.Infrastructure/UWP/UwpRecentFilesClient.cs create mode 100644 ModernKeePass.Infrastructure/UWP/UwpResourceClient.cs create mode 100644 ModernKeePass.Infrastructure/UWP/UwpSettingsClient.cs create mode 100644 ModernKeePass.Infrastructure/project.json create mode 100644 ModernKeePass.Infrastucture.12/DependencyInjection.cs create mode 100644 ModernKeePass.Infrastucture.12/File/CsvImportFormat.cs create mode 100644 ModernKeePass.Infrastucture.12/KeePass/EntryMappingProfile.cs create mode 100644 ModernKeePass.Infrastucture.12/KeePass/IconMapper.cs create mode 100644 ModernKeePass.Infrastucture.12/KeePass/KeePassCryptographyClient.cs create mode 100644 ModernKeePass.Infrastucture.12/KeePass/KeePassDatabaseClient.cs create mode 100644 ModernKeePass.Infrastucture.12/KeePass/KeePassPasswordClient.cs create mode 100644 ModernKeePass.Infrastucture.12/Libs/Windows.winmd create mode 100644 ModernKeePass.Infrastucture.12/ModernKeePass.Infrastructure.csproj create mode 100644 ModernKeePass.Infrastucture.12/UWP/StorageFileClient.cs create mode 100644 ModernKeePass.Infrastucture.12/UWP/UwpRecentFilesClient.cs create mode 100644 ModernKeePass.Infrastucture.12/UWP/UwpResourceClient.cs create mode 100644 ModernKeePass.Infrastucture.12/UWP/UwpSettingsClient.cs delete mode 100644 ModernKeePassLib.Test/Cryptography/Cipher/Chacha20Tests.cs delete mode 100644 ModernKeePassLib.Test/Cryptography/Cipher/StandardAesEngineTests.cs delete mode 100644 ModernKeePassLib.Test/Cryptography/CryptoRandomStreamTests.cs delete mode 100644 ModernKeePassLib.Test/Cryptography/CryptoRandomTests.cs delete mode 100644 ModernKeePassLib.Test/Cryptography/Hash/Blake2bTests.cs delete mode 100644 ModernKeePassLib.Test/Cryptography/Hash/HmacTests.cs delete mode 100644 ModernKeePassLib.Test/Cryptography/Hash/SHAManagedTests.cs delete mode 100644 ModernKeePassLib.Test/Cryptography/HashingStreamExTests.cs delete mode 100644 ModernKeePassLib.Test/Cryptography/HmacOtpTests.cs delete mode 100644 ModernKeePassLib.Test/Cryptography/KeyDerivation/AesKdfTests.cs delete mode 100644 ModernKeePassLib.Test/Cryptography/KeyDerivation/Argon2Tests.cs delete mode 100644 ModernKeePassLib.Test/Images/UnitTestLogo.scale-100.png delete mode 100644 ModernKeePassLib.Test/Images/UnitTestSmallLogo.scale-100.png delete mode 100644 ModernKeePassLib.Test/Images/UnitTestSplashScreen.scale-100.png delete mode 100644 ModernKeePassLib.Test/Images/UnitTestStoreLogo.scale-100.png delete mode 100644 ModernKeePassLib.Test/Keys/CompositeKeyTests.cs delete mode 100644 ModernKeePassLib.Test/Keys/KcpCustomKeyTests.cs delete mode 100644 ModernKeePassLib.Test/Keys/KcpKeyFileTests.cs delete mode 100644 ModernKeePassLib.Test/Keys/KcpPasswordTests.cs delete mode 100644 ModernKeePassLib.Test/ModernKeePass.LibTest.csproj delete mode 100644 ModernKeePassLib.Test/Package.appxmanifest delete mode 100644 ModernKeePassLib.Test/Serialization/HashedBlockStreamTests.cs delete mode 100644 ModernKeePassLib.Test/Serialization/KdbxFileTests.cs delete mode 100644 ModernKeePassLib.Test/Utility/GfxUtilTests.cs delete mode 100644 ModernKeePassLib.Test/Utility/MemUtilTests.cs delete mode 100644 ModernKeePassLib.Test/packages.config delete mode 100644 ModernKeePassLib/Collections/AutoTypeConfig.cs delete mode 100644 ModernKeePassLib/Collections/ProtectedBinaryDictionary.cs delete mode 100644 ModernKeePassLib/Collections/ProtectedBinarySet.cs delete mode 100644 ModernKeePassLib/Collections/ProtectedStringDictionary.cs delete mode 100644 ModernKeePassLib/Collections/PwObjectList.cs delete mode 100644 ModernKeePassLib/Collections/PwObjectPool.cs delete mode 100644 ModernKeePassLib/Collections/StringDictionaryEx.cs delete mode 100644 ModernKeePassLib/Collections/VariantDictionary.cs delete mode 100644 ModernKeePassLib/Cryptography/Cipher/ChaCha20Cipher.cs delete mode 100644 ModernKeePassLib/Cryptography/Cipher/ChaCha20Engine.cs delete mode 100644 ModernKeePassLib/Cryptography/Cipher/CipherPool.cs delete mode 100644 ModernKeePassLib/Cryptography/Cipher/CtrBlockCipher.cs delete mode 100644 ModernKeePassLib/Cryptography/Cipher/ICipherEngine.cs delete mode 100644 ModernKeePassLib/Cryptography/Cipher/Salsa20Cipher.cs delete mode 100644 ModernKeePassLib/Cryptography/Cipher/StandardAesEngine.cs delete mode 100644 ModernKeePassLib/Cryptography/CryptoRandom.cs delete mode 100644 ModernKeePassLib/Cryptography/CryptoRandomStream.cs delete mode 100644 ModernKeePassLib/Cryptography/CryptoUtil.cs delete mode 100644 ModernKeePassLib/Cryptography/Hash/Blake2b.cs delete mode 100644 ModernKeePassLib/Cryptography/Hash/HMAC.cs delete mode 100644 ModernKeePassLib/Cryptography/Hash/HMACSHA1.cs delete mode 100644 ModernKeePassLib/Cryptography/Hash/HMACSHA256.cs delete mode 100644 ModernKeePassLib/Cryptography/Hash/HashAlgorithm.cs delete mode 100644 ModernKeePassLib/Cryptography/Hash/SHA256Managed.cs delete mode 100644 ModernKeePassLib/Cryptography/Hash/SHA512Managed.cs delete mode 100644 ModernKeePassLib/Cryptography/HashingStreamEx.cs delete mode 100644 ModernKeePassLib/Cryptography/HmacOtp.cs delete mode 100644 ModernKeePassLib/Cryptography/KeyDerivation/AesKdf.GCrypt.cs delete mode 100644 ModernKeePassLib/Cryptography/KeyDerivation/AesKdf.cs delete mode 100644 ModernKeePassLib/Cryptography/KeyDerivation/Argon2Kdf.Core.cs delete mode 100644 ModernKeePassLib/Cryptography/KeyDerivation/Argon2Kdf.cs delete mode 100644 ModernKeePassLib/Cryptography/KeyDerivation/KdfEngine.cs delete mode 100644 ModernKeePassLib/Cryptography/KeyDerivation/KdfParameters.cs delete mode 100644 ModernKeePassLib/Cryptography/KeyDerivation/KdfPool.cs delete mode 100644 ModernKeePassLib/Cryptography/PasswordGenerator/CharSetBasedGenerator.cs delete mode 100644 ModernKeePassLib/Cryptography/PasswordGenerator/CustomPwGenerator.cs delete mode 100644 ModernKeePassLib/Cryptography/PasswordGenerator/CustomPwGeneratorPool.cs delete mode 100644 ModernKeePassLib/Cryptography/PasswordGenerator/PatternBasedGenerator.cs delete mode 100644 ModernKeePassLib/Cryptography/PasswordGenerator/PwCharSet.cs delete mode 100644 ModernKeePassLib/Cryptography/PasswordGenerator/PwGenerator.cs delete mode 100644 ModernKeePassLib/Cryptography/PasswordGenerator/PwProfile.cs delete mode 100644 ModernKeePassLib/Cryptography/PopularPasswords.cs delete mode 100644 ModernKeePassLib/Cryptography/ProtectedData.cs delete mode 100644 ModernKeePassLib/Cryptography/QualityEstimation.cs delete mode 100644 ModernKeePassLib/Cryptography/SelfTest.cs delete mode 100644 ModernKeePassLib/Delegates/Handlers.cs delete mode 100644 ModernKeePassLib/Interfaces/IDeepCloneable.cs delete mode 100644 ModernKeePassLib/Interfaces/IStatusLogger.cs delete mode 100644 ModernKeePassLib/Interfaces/IStructureItem.cs delete mode 100644 ModernKeePassLib/Interfaces/ITimeLogger.cs delete mode 100644 ModernKeePassLib/Interfaces/IUIOperations.cs delete mode 100644 ModernKeePassLib/Interfaces/IXmlSerializerEx.cs delete mode 100644 ModernKeePassLib/KeePassLib.pfx delete mode 100644 ModernKeePassLib/Keys/CompositeKey.cs delete mode 100644 ModernKeePassLib/Keys/IUserKey.cs delete mode 100644 ModernKeePassLib/Keys/KcpCustomKey.cs delete mode 100644 ModernKeePassLib/Keys/KcpKeyFile.cs delete mode 100644 ModernKeePassLib/Keys/KcpPassword.cs delete mode 100644 ModernKeePassLib/Keys/KcpUserAccount.cs delete mode 100644 ModernKeePassLib/Keys/KeyProvider.cs delete mode 100644 ModernKeePassLib/Keys/KeyProviderPool.cs delete mode 100644 ModernKeePassLib/Keys/KeyValidator.cs delete mode 100644 ModernKeePassLib/Keys/KeyValidatorPool.cs delete mode 100644 ModernKeePassLib/Keys/UserKeyType.cs delete mode 100644 ModernKeePassLib/ModernKeePass.Lib.csproj delete mode 100644 ModernKeePassLib/ModernKeePassLib.nuget.targets delete mode 100644 ModernKeePassLib/ModernKeePassLib.nuspec delete mode 100644 ModernKeePassLib/ModernKeePassLibPCL.nuget.targets delete mode 100644 ModernKeePassLib/Native/ClipboardU.cs delete mode 100644 ModernKeePassLib/Native/Native.PCL.cs delete mode 100644 ModernKeePassLib/Native/NativeLib.cs delete mode 100644 ModernKeePassLib/Native/NativeMethods.Unix.cs delete mode 100644 ModernKeePassLib/Native/NativeMethods.cs delete mode 100644 ModernKeePassLib/Properties/AssemblyInfo.cs delete mode 100644 ModernKeePassLib/PwCustomIcon.cs delete mode 100644 ModernKeePassLib/PwDatabase.cs delete mode 100644 ModernKeePassLib/PwDefs.cs delete mode 100644 ModernKeePassLib/PwDeletedObject.cs delete mode 100644 ModernKeePassLib/PwEntry.cs delete mode 100644 ModernKeePassLib/PwEnums.cs delete mode 100644 ModernKeePassLib/PwGroup.cs delete mode 100644 ModernKeePassLib/PwUuid.cs delete mode 100644 ModernKeePassLib/README.md delete mode 100644 ModernKeePassLib/Resources/KLRes.Generated.cs delete mode 100644 ModernKeePassLib/Resources/KSRes.Generated.cs delete mode 100644 ModernKeePassLib/Security/ProtectedBinary.cs delete mode 100644 ModernKeePassLib/Security/ProtectedString.cs delete mode 100644 ModernKeePassLib/Security/XorredBuffer.cs delete mode 100644 ModernKeePassLib/Serialization/BinaryReaderEx.cs delete mode 100644 ModernKeePassLib/Serialization/FileLock.cs delete mode 100644 ModernKeePassLib/Serialization/FileTransactionEx.cs delete mode 100644 ModernKeePassLib/Serialization/HashedBlockStream.cs delete mode 100644 ModernKeePassLib/Serialization/HmacBlockStream.cs delete mode 100644 ModernKeePassLib/Serialization/IOConnection.cs delete mode 100644 ModernKeePassLib/Serialization/IOConnectionInfo.cs delete mode 100644 ModernKeePassLib/Serialization/IocProperties.cs delete mode 100644 ModernKeePassLib/Serialization/IocPropertyInfo.cs delete mode 100644 ModernKeePassLib/Serialization/IocPropertyInfoPool.cs delete mode 100644 ModernKeePassLib/Serialization/KdbxFile.Read.Streamed.cs delete mode 100644 ModernKeePassLib/Serialization/KdbxFile.Read.cs delete mode 100644 ModernKeePassLib/Serialization/KdbxFile.Write.cs delete mode 100644 ModernKeePassLib/Serialization/KdbxFile.cs delete mode 100644 ModernKeePassLib/Serialization/OldFormatException.cs delete mode 100644 ModernKeePassLib/Translation/KPControlCustomization.cs delete mode 100644 ModernKeePassLib/Translation/KPFormCustomization.cs delete mode 100644 ModernKeePassLib/Translation/KPStringTable.cs delete mode 100644 ModernKeePassLib/Translation/KPStringTableItem.cs delete mode 100644 ModernKeePassLib/Translation/KPTranslation.cs delete mode 100644 ModernKeePassLib/Translation/KPTranslationProperties.cs delete mode 100644 ModernKeePassLib/Utility/AppLogEx.cs delete mode 100644 ModernKeePassLib/Utility/ColorTranslator.cs delete mode 100644 ModernKeePassLib/Utility/GfxUtil.PCL.cs delete mode 100644 ModernKeePassLib/Utility/GfxUtil.cs delete mode 100644 ModernKeePassLib/Utility/MemUtil.cs delete mode 100644 ModernKeePassLib/Utility/MessageService.cs delete mode 100644 ModernKeePassLib/Utility/MonoWorkaround.PCL.cs delete mode 100644 ModernKeePassLib/Utility/MonoWorkarounds.cs delete mode 100644 ModernKeePassLib/Utility/StrUtil.cs delete mode 100644 ModernKeePassLib/Utility/StreamExtensions.cs delete mode 100644 ModernKeePassLib/Utility/TimeUtil.cs delete mode 100644 ModernKeePassLib/Utility/TypeOverridePool.cs delete mode 100644 ModernKeePassLib/Utility/UrlUtil.cs delete mode 100644 ModernKeePassLib/Utility/XmlUtilEx.cs delete mode 100644 ModernKeePassLib/app.config delete mode 100644 ModernKeePassLib/project.json diff --git a/ModernKeePass.Application.12/Common/Interfaces/ICryptographyClient.cs b/ModernKeePass.Application.12/Common/Interfaces/ICryptographyClient.cs new file mode 100644 index 0000000..f7c19ff --- /dev/null +++ b/ModernKeePass.Application.12/Common/Interfaces/ICryptographyClient.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using ModernKeePass.Domain.Entities; + +namespace ModernKeePass.Application.Common.Interfaces +{ + public interface ICryptographyClient + { + IEnumerable Ciphers { get; } + IEnumerable KeyDerivations { get; } + IEnumerable CompressionAlgorithms { get; } + } +} \ No newline at end of file diff --git a/ModernKeePass.Application.12/Common/Interfaces/IDatabaseProxy.cs b/ModernKeePass.Application.12/Common/Interfaces/IDatabaseProxy.cs new file mode 100644 index 0000000..de508b7 --- /dev/null +++ b/ModernKeePass.Application.12/Common/Interfaces/IDatabaseProxy.cs @@ -0,0 +1,28 @@ +using System.Threading.Tasks; +using ModernKeePass.Domain.Dtos; +using ModernKeePass.Domain.Entities; + +namespace ModernKeePass.Application.Common.Interfaces +{ + public interface IDatabaseProxy + { + bool IsOpen { get; } + GroupEntity RecycleBin { get; set; } + BaseEntity Cipher { get; set; } + BaseEntity KeyDerivation { get; set; } + string Compression { get; set; } + + Task Open(FileInfo fileInfo, Credentials credentials); + Task Create(FileInfo fileInfo, Credentials credentials); + Task SaveDatabase(); + Task SaveDatabase(FileInfo FileInfo); + Task UpdateCredentials(Credentials credentials); + void CloseDatabase(); + Task AddEntry(GroupEntity parentGroup, EntryEntity entity); + Task AddGroup(GroupEntity parentGroup, GroupEntity entity); + Task UpdateEntry(EntryEntity entity); + Task UpdateGroup(GroupEntity entity); + Task DeleteEntry(EntryEntity entity); + Task DeleteGroup(GroupEntity entity); + } +} \ No newline at end of file diff --git a/ModernKeePass.Application.12/Common/Interfaces/IFileProxy.cs b/ModernKeePass.Application.12/Common/Interfaces/IFileProxy.cs new file mode 100644 index 0000000..e9870e2 --- /dev/null +++ b/ModernKeePass.Application.12/Common/Interfaces/IFileProxy.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace ModernKeePass.Application.Common.Interfaces +{ + public interface IFileProxy + { + Task OpenBinaryFile(string path); + Task> OpenTextFile(string path); + Task WriteBinaryContentsToFile(string path, byte[] contents); + void ReleaseFile(string path); + } +} \ No newline at end of file diff --git a/ModernKeePass.Application.12/Common/Interfaces/IHasSelectableObject.cs b/ModernKeePass.Application.12/Common/Interfaces/IHasSelectableObject.cs new file mode 100644 index 0000000..f2fd247 --- /dev/null +++ b/ModernKeePass.Application.12/Common/Interfaces/IHasSelectableObject.cs @@ -0,0 +1,7 @@ +namespace ModernKeePass.Application.Common.Interfaces +{ + public interface IHasSelectableObject + { + ISelectableModel SelectedItem { get; set; } + } +} diff --git a/ModernKeePass.Application.12/Common/Interfaces/IImportFormat.cs b/ModernKeePass.Application.12/Common/Interfaces/IImportFormat.cs new file mode 100644 index 0000000..f581762 --- /dev/null +++ b/ModernKeePass.Application.12/Common/Interfaces/IImportFormat.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace ModernKeePass.Application.Common.Interfaces +{ + public interface IImportFormat + { + Task>> Import(string path); + } +} \ No newline at end of file diff --git a/ModernKeePass.Application.12/Common/Interfaces/IIsEnabled.cs b/ModernKeePass.Application.12/Common/Interfaces/IIsEnabled.cs new file mode 100644 index 0000000..1abc45a --- /dev/null +++ b/ModernKeePass.Application.12/Common/Interfaces/IIsEnabled.cs @@ -0,0 +1,7 @@ +namespace ModernKeePass.Application.Common.Interfaces +{ + public interface IIsEnabled + { + bool IsEnabled { get; } + } +} diff --git a/ModernKeePass.Application.12/Common/Interfaces/IPasswordProxy.cs b/ModernKeePass.Application.12/Common/Interfaces/IPasswordProxy.cs new file mode 100644 index 0000000..aec2b37 --- /dev/null +++ b/ModernKeePass.Application.12/Common/Interfaces/IPasswordProxy.cs @@ -0,0 +1,11 @@ +using ModernKeePass.Domain.Dtos; + +namespace ModernKeePass.Application.Common.Interfaces +{ + public interface IPasswordProxy + { + string GeneratePassword(PasswordGenerationOptions options); + uint EstimatePasswordComplexity(string password); + byte[] GenerateKeyFile(byte[] additionalEntropy); + } +} \ No newline at end of file diff --git a/ModernKeePass.Application.12/Common/Interfaces/IProxyInvocationHandler.cs b/ModernKeePass.Application.12/Common/Interfaces/IProxyInvocationHandler.cs new file mode 100644 index 0000000..5a04d1f --- /dev/null +++ b/ModernKeePass.Application.12/Common/Interfaces/IProxyInvocationHandler.cs @@ -0,0 +1,9 @@ +using System.Reflection; + +namespace ModernKeePass.Application.Common.Interfaces +{ + public interface IProxyInvocationHandler + { + object Invoke(object proxy, MethodInfo method, object[] parameters); + } +} \ No newline at end of file diff --git a/ModernKeePass.Application.12/Common/Interfaces/IRecentProxy.cs b/ModernKeePass.Application.12/Common/Interfaces/IRecentProxy.cs new file mode 100644 index 0000000..f0792ab --- /dev/null +++ b/ModernKeePass.Application.12/Common/Interfaces/IRecentProxy.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using ModernKeePass.Domain.Dtos; + +namespace ModernKeePass.Application.Common.Interfaces +{ + public interface IRecentProxy + { + int EntryCount { get; } + Task Get(string token); + Task> GetAll(); + Task Add(FileInfo recentItem); + void ClearAll(); + } +} \ No newline at end of file diff --git a/ModernKeePass.Application.12/Common/Interfaces/IResourceProxy.cs b/ModernKeePass.Application.12/Common/Interfaces/IResourceProxy.cs new file mode 100644 index 0000000..ba1530c --- /dev/null +++ b/ModernKeePass.Application.12/Common/Interfaces/IResourceProxy.cs @@ -0,0 +1,7 @@ +namespace ModernKeePass.Application.Common.Interfaces +{ + public interface IResourceProxy + { + string GetResourceValue(string key); + } +} \ No newline at end of file diff --git a/ModernKeePass.Application.12/Common/Interfaces/ISelectableModel.cs b/ModernKeePass.Application.12/Common/Interfaces/ISelectableModel.cs new file mode 100644 index 0000000..1102637 --- /dev/null +++ b/ModernKeePass.Application.12/Common/Interfaces/ISelectableModel.cs @@ -0,0 +1,7 @@ +namespace ModernKeePass.Application.Common.Interfaces +{ + public interface ISelectableModel + { + bool IsSelected { get; set; } + } +} \ No newline at end of file diff --git a/ModernKeePass.Application.12/Common/Interfaces/ISettingsProxy.cs b/ModernKeePass.Application.12/Common/Interfaces/ISettingsProxy.cs new file mode 100644 index 0000000..6471175 --- /dev/null +++ b/ModernKeePass.Application.12/Common/Interfaces/ISettingsProxy.cs @@ -0,0 +1,8 @@ +namespace ModernKeePass.Application.Common.Interfaces +{ + public interface ISettingsProxy + { + T GetSetting(string property, T defaultValue = default); + void PutSetting(string property, T value); + } +} \ No newline at end of file diff --git a/ModernKeePass.Application.12/Common/Mappings/IMapFrom.cs b/ModernKeePass.Application.12/Common/Mappings/IMapFrom.cs new file mode 100644 index 0000000..da4ab61 --- /dev/null +++ b/ModernKeePass.Application.12/Common/Mappings/IMapFrom.cs @@ -0,0 +1,10 @@ +using AutoMapper; + +namespace ModernKeePass.Application.Common.Mappings +{ + + public interface IMapFrom + { + void Mapping(Profile profile); + } +} \ No newline at end of file diff --git a/ModernKeePass.Application.12/Common/Mappings/MappingProfile.cs b/ModernKeePass.Application.12/Common/Mappings/MappingProfile.cs new file mode 100644 index 0000000..c572745 --- /dev/null +++ b/ModernKeePass.Application.12/Common/Mappings/MappingProfile.cs @@ -0,0 +1,30 @@ +using System; +using System.Linq; +using System.Reflection; +using AutoMapper; + +namespace ModernKeePass.Application.Common.Mappings +{ + public class MappingProfile : Profile + { + public MappingProfile() + { + ApplyMappingsFromAssembly(Assembly.GetExecutingAssembly()); + } + + private void ApplyMappingsFromAssembly(Assembly assembly) + { + var types = assembly.GetExportedTypes() + .Where(t => t.GetInterfaces().Any(i => + i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IMapFrom<>))) + .ToList(); + + foreach (var type in types) + { + var instance = Activator.CreateInstance(type); + var methodInfo = type.GetMethod("Mapping"); + methodInfo?.Invoke(instance, new object[] { this }); + } + } + } +} \ No newline at end of file diff --git a/ModernKeePass.Application.12/Common/Mappings/MappingProfiles.cs b/ModernKeePass.Application.12/Common/Mappings/MappingProfiles.cs new file mode 100644 index 0000000..dbd055d --- /dev/null +++ b/ModernKeePass.Application.12/Common/Mappings/MappingProfiles.cs @@ -0,0 +1,17 @@ +using AutoMapper; +using ModernKeePass.Application.Database.Models; +using ModernKeePass.Application.Entry.Models; +using ModernKeePass.Application.Group.Models; + +namespace ModernKeePass.Application.Common.Mappings +{ + public class MappingProfiles: Profile + { + public void ApplyMappings() + { + new DatabaseVm().Mapping(this); + new EntryVm().Mapping(this); + new GroupVm().Mapping(this); + } + } +} \ No newline at end of file diff --git a/ModernKeePass.Application.12/Database/Commands/CloseDatabase/CloseDatabaseCommand.cs b/ModernKeePass.Application.12/Database/Commands/CloseDatabase/CloseDatabaseCommand.cs new file mode 100644 index 0000000..904641e --- /dev/null +++ b/ModernKeePass.Application.12/Database/Commands/CloseDatabase/CloseDatabaseCommand.cs @@ -0,0 +1,31 @@ +using MediatR; +using System.Threading; +using System.Threading.Tasks; +using ModernKeePass.Application.Common.Interfaces; +using ModernKeePass.Application.Database.Queries.IsDatabaseOpen; +using ModernKeePass.Domain.Exceptions; + +namespace ModernKeePass.Application.Database.Commands.CloseDatabase +{ + public class CloseDatabaseCommand: IRequest + { + public class CloseDatabaseCommandHandler : IRequestHandler + { + private readonly IDatabaseProxy _database; + private readonly IMediator _mediator; + + public CloseDatabaseCommandHandler(IDatabaseProxy database, IMediator mediator) + { + _database = database; + _mediator = mediator; + } + + public async Task Handle(CloseDatabaseCommand message, CancellationToken cancellationToken) + { + var isDatabaseOpen = await _mediator.Send(new IsDatabaseOpenQuery(), cancellationToken); + if (isDatabaseOpen) _database.CloseDatabase(); + else throw new DatabaseClosedException(); + } + } + } +} \ No newline at end of file diff --git a/ModernKeePass.Application.12/Database/Commands/CreateDatabase/CreateDatabaseCommand.cs b/ModernKeePass.Application.12/Database/Commands/CreateDatabase/CreateDatabaseCommand.cs new file mode 100644 index 0000000..e0e7c44 --- /dev/null +++ b/ModernKeePass.Application.12/Database/Commands/CreateDatabase/CreateDatabaseCommand.cs @@ -0,0 +1,48 @@ +using MediatR; +using System.Threading; +using System.Threading.Tasks; +using AutoMapper; +using ModernKeePass.Application.Common.Interfaces; +using ModernKeePass.Application.Database.Models; +using ModernKeePass.Application.Database.Queries.IsDatabaseOpen; +using ModernKeePass.Application.Group.Models; +using ModernKeePass.Domain.Dtos; +using ModernKeePass.Domain.Exceptions; + +namespace ModernKeePass.Application.Database.Commands.CreateDatabase +{ + public class CreateDatabaseCommand : IRequest + { + public FileInfo FileInfo { get; set; } + public Credentials Credentials { get; set; } + + public class CreateDatabaseCommandHandler : IRequestHandler + { + private readonly IDatabaseProxy _database; + private readonly IMediator _mediator; + private readonly IMapper _mapper; + + public CreateDatabaseCommandHandler(IDatabaseProxy database, IMediator mediator, IMapper mapper) + { + _database = database; + _mediator = mediator; + _mapper = mapper; + } + + public async Task Handle(CreateDatabaseCommand message, CancellationToken cancellationToken) + { + var isDatabaseOpen = await _mediator.Send(new IsDatabaseOpenQuery(), cancellationToken); + if (isDatabaseOpen) throw new DatabaseOpenException(); + + var database = await _database.Create(message.FileInfo, message.Credentials); + var databaseVm = new DatabaseVm + { + IsOpen = true, + Name = database.Name, + RootGroup = _mapper.Map(database.RootGroupEntity) + }; + return databaseVm; + } + } + } +} \ No newline at end of file diff --git a/ModernKeePass.Application.12/Database/Commands/SaveDatabase/SaveDatabaseCommand.cs b/ModernKeePass.Application.12/Database/Commands/SaveDatabase/SaveDatabaseCommand.cs new file mode 100644 index 0000000..3d29159 --- /dev/null +++ b/ModernKeePass.Application.12/Database/Commands/SaveDatabase/SaveDatabaseCommand.cs @@ -0,0 +1,31 @@ +using MediatR; +using System.Threading; +using System.Threading.Tasks; +using ModernKeePass.Application.Common.Interfaces; +using ModernKeePass.Application.Database.Queries.IsDatabaseOpen; +using ModernKeePass.Domain.Exceptions; + +namespace ModernKeePass.Application.Database.Commands.SaveDatabase +{ + public class SaveDatabaseCommand : IRequest + { + public class SaveDatabaseCommandHandler : IRequestHandler + { + private readonly IDatabaseProxy _database; + private readonly IMediator _mediator; + + public SaveDatabaseCommandHandler(IDatabaseProxy database, IMediator mediator) + { + _database = database; + _mediator = mediator; + } + + public async Task Handle(SaveDatabaseCommand message, CancellationToken cancellationToken) + { + var isDatabaseOpen = await _mediator.Send(new IsDatabaseOpenQuery(), cancellationToken); + if (isDatabaseOpen) await _database.SaveDatabase(); + else throw new DatabaseClosedException(); + } + } + } +} \ No newline at end of file diff --git a/ModernKeePass.Application.12/Database/Models/DatabaseVm.cs b/ModernKeePass.Application.12/Database/Models/DatabaseVm.cs new file mode 100644 index 0000000..bc7cbde --- /dev/null +++ b/ModernKeePass.Application.12/Database/Models/DatabaseVm.cs @@ -0,0 +1,21 @@ +using AutoMapper; +using ModernKeePass.Application.Common.Mappings; +using ModernKeePass.Application.Group.Models; +using ModernKeePass.Domain.Entities; + +namespace ModernKeePass.Application.Database.Models +{ + public class DatabaseVm: IMapFrom + { + public bool IsOpen { get; set; } + public string Name { get; set; } + public GroupVm RootGroup { get; set; } + + public void Mapping(Profile profile) + { + profile.CreateMap() + .ForMember(d => d.Name, opts => opts.MapFrom(s => s.Name)) + .ForMember(d => d.RootGroup, opts => opts.MapFrom(s => s.RootGroupEntity)); + } + } +} \ No newline at end of file diff --git a/ModernKeePass.Application.12/Database/Models/MainVm.cs b/ModernKeePass.Application.12/Database/Models/MainVm.cs new file mode 100644 index 0000000..e13bb27 --- /dev/null +++ b/ModernKeePass.Application.12/Database/Models/MainVm.cs @@ -0,0 +1,7 @@ +namespace ModernKeePass.Application.Database.Models +{ + public class MainVm + { + + } +} \ No newline at end of file diff --git a/ModernKeePass.Application.12/Database/Queries/IsDatabaseOpen/IsDatabaseOpenQuery.cs b/ModernKeePass.Application.12/Database/Queries/IsDatabaseOpen/IsDatabaseOpenQuery.cs new file mode 100644 index 0000000..0266666 --- /dev/null +++ b/ModernKeePass.Application.12/Database/Queries/IsDatabaseOpen/IsDatabaseOpenQuery.cs @@ -0,0 +1,24 @@ +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using ModernKeePass.Application.Common.Interfaces; + +namespace ModernKeePass.Application.Database.Queries.IsDatabaseOpen +{ + public class IsDatabaseOpenQuery: IRequest + { + public class IsDatabaseOpenQueryHandler: IRequestHandler + { + private readonly IDatabaseProxy _databaseProxy; + + public IsDatabaseOpenQueryHandler(IDatabaseProxy databaseProxy) + { + _databaseProxy = databaseProxy; + } + public Task Handle(IsDatabaseOpenQuery request, CancellationToken cancellationToken) + { + return Task.FromResult(_databaseProxy.IsOpen); + } + } + } +} \ No newline at end of file diff --git a/ModernKeePass.Application.12/Database/Queries/OpenDatabase/OpenDatabaseQuery.cs b/ModernKeePass.Application.12/Database/Queries/OpenDatabase/OpenDatabaseQuery.cs new file mode 100644 index 0000000..0fd8917 --- /dev/null +++ b/ModernKeePass.Application.12/Database/Queries/OpenDatabase/OpenDatabaseQuery.cs @@ -0,0 +1,48 @@ +using System.Threading; +using System.Threading.Tasks; +using AutoMapper; +using MediatR; +using ModernKeePass.Application.Common.Interfaces; +using ModernKeePass.Application.Database.Models; +using ModernKeePass.Application.Database.Queries.IsDatabaseOpen; +using ModernKeePass.Application.Group.Models; +using ModernKeePass.Domain.Dtos; +using ModernKeePass.Domain.Exceptions; + +namespace ModernKeePass.Application.Database.Queries.OpenDatabase +{ + public class OpenDatabaseQuery: IRequest + { + public FileInfo FileInfo { get; set; } + public Credentials Credentials { get; set; } + + public class OpenDatabaseQueryHandler : IRequestHandler + { + private readonly IMapper _mapper; + private readonly IMediator _mediator; + private readonly IDatabaseProxy _databaseProxy; + + public OpenDatabaseQueryHandler(IMapper mapper, IMediator mediator, IDatabaseProxy databaseProxy) + { + _mapper = mapper; + _mediator = mediator; + _databaseProxy = databaseProxy; + } + + public async Task Handle(OpenDatabaseQuery request, CancellationToken cancellationToken) + { + var isDatabaseOpen = await _mediator.Send(new IsDatabaseOpenQuery(), cancellationToken); + if (isDatabaseOpen) throw new DatabaseOpenException(); + + var database = await _databaseProxy.Open(request.FileInfo, request.Credentials); + var databaseVm = new DatabaseVm + { + IsOpen = true, + Name = database.Name, + RootGroup = _mapper.Map(database.RootGroupEntity) + }; + return databaseVm; + } + } + } +} \ No newline at end of file diff --git a/ModernKeePass.Application.12/Database/Queries/OpenDatabase/OpenDatabaseQueryValidator.cs b/ModernKeePass.Application.12/Database/Queries/OpenDatabase/OpenDatabaseQueryValidator.cs new file mode 100644 index 0000000..d773f08 --- /dev/null +++ b/ModernKeePass.Application.12/Database/Queries/OpenDatabase/OpenDatabaseQueryValidator.cs @@ -0,0 +1,12 @@ +using FluentValidation; + +namespace ModernKeePass.Application.Database.Queries.OpenDatabase +{ + public class OpenDatabaseQueryValidator : AbstractValidator + { + public OpenDatabaseQueryValidator() + { + RuleFor(v => v.Credentials != null && v.FileInfo != null); + } + } +} \ No newline at end of file diff --git a/ModernKeePass.Application.12/DependencyInjection.cs b/ModernKeePass.Application.12/DependencyInjection.cs new file mode 100644 index 0000000..0328d9a --- /dev/null +++ b/ModernKeePass.Application.12/DependencyInjection.cs @@ -0,0 +1,13 @@ +using Microsoft.Extensions.DependencyInjection; + +namespace ModernKeePass.Application +{ + public static class DependencyInjection + { + public static IServiceCollection AddApplication(this IServiceCollection services) + { + + return services; + } + } +} \ No newline at end of file diff --git a/ModernKeePass.Application.12/Entry/Models/EntryVm.cs b/ModernKeePass.Application.12/Entry/Models/EntryVm.cs new file mode 100644 index 0000000..44f6bcd --- /dev/null +++ b/ModernKeePass.Application.12/Entry/Models/EntryVm.cs @@ -0,0 +1,27 @@ +using System.Drawing; +using AutoMapper; +using ModernKeePass.Application.Common.Mappings; +using ModernKeePass.Domain.Entities; +using ModernKeePass.Domain.Enums; + +namespace ModernKeePass.Application.Entry.Models +{ + public class EntryVm: IMapFrom + { + public string Id { get; set; } + public string Title { get; set; } + public Icon Icon { get; set; } + public Color ForegroundColor { get; set; } + public Color BackgroundColor { get; set; } + + public void Mapping(Profile profile) + { + profile.CreateMap() + .ForMember(d => d.Id, opts => opts.MapFrom(s => s.Id)) + .ForMember(d => d.Title, opts => opts.MapFrom(s => s.Name)) + .ForMember(d => d.Icon, opts => opts.MapFrom(s => s.Icon)) + .ForMember(d => d.ForegroundColor, opts => opts.MapFrom(s => s.ForegroundColor)) + .ForMember(d => d.BackgroundColor, opts => opts.MapFrom(s => s.BackgroundColor)); + } + } +} \ No newline at end of file diff --git a/ModernKeePass.Application.12/Group/Models/GroupVm.cs b/ModernKeePass.Application.12/Group/Models/GroupVm.cs new file mode 100644 index 0000000..3ba48f7 --- /dev/null +++ b/ModernKeePass.Application.12/Group/Models/GroupVm.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; +using System.Drawing; +using AutoMapper; +using ModernKeePass.Application.Common.Mappings; +using ModernKeePass.Application.Entry.Models; +using ModernKeePass.Domain.Entities; +using ModernKeePass.Domain.Enums; + +namespace ModernKeePass.Application.Group.Models +{ + public class GroupVm: IMapFrom + { + public string Id { get; set; } + public string Title { get; set; } + public Icon Icon { get; set; } + public Color ForegroundColor { get; set; } + public Color BackgroundColor { get; set; } + public List SubGroups { get; set; } = new List(); + public List Entries { get; set; } = new List(); + + public void Mapping(Profile profile) + { + profile.CreateMap() + .ForMember(d => d.Id, opts => opts.MapFrom(s => s.Id)) + .ForMember(d => d.Title, opts => opts.MapFrom(s => s.Name)) + .ForMember(d => d.Icon, opts => opts.MapFrom(s => s.Icon)) + .ForMember(d => d.ForegroundColor, opts => opts.MapFrom(s => s.ForegroundColor)) + .ForMember(d => d.BackgroundColor, opts => opts.MapFrom(s => s.BackgroundColor)) + .ForMember(d => d.Entries, opts => opts.MapFrom(s => s.Entries)) + .ForMember(d => d.SubGroups, opts => opts.MapFrom(s => s.SubGroups)); + } + } +} \ No newline at end of file diff --git a/ModernKeePass.Application.12/ModernKeePass.Application.csproj b/ModernKeePass.Application.12/ModernKeePass.Application.csproj new file mode 100644 index 0000000..8c133a3 --- /dev/null +++ b/ModernKeePass.Application.12/ModernKeePass.Application.csproj @@ -0,0 +1,59 @@ + + + + netstandard1.2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ModernKeePass.Application.12/Services/CryptographyService.cs b/ModernKeePass.Application.12/Services/CryptographyService.cs new file mode 100644 index 0000000..caa6a57 --- /dev/null +++ b/ModernKeePass.Application.12/Services/CryptographyService.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using ModernKeePass.Application.Common.Interfaces; +using ModernKeePass.Domain.Entities; +using ModernKeePass.Domain.Interfaces; + +namespace ModernKeePass.Application.Services +{ + public class CryptographyService : ICryptographyService + { + private readonly ICryptographyClient _cryptographyClient; + public IEnumerable Ciphers => _cryptographyClient.Ciphers; + + public IEnumerable KeyDerivations => _cryptographyClient.KeyDerivations; + + public IEnumerable CompressionAlgorithms => _cryptographyClient.CompressionAlgorithms; + + public CryptographyService(ICryptographyClient cryptographyClient) + { + _cryptographyClient = cryptographyClient; + } + } +} \ No newline at end of file diff --git a/ModernKeePass.Application.12/Services/DatabaseService.cs b/ModernKeePass.Application.12/Services/DatabaseService.cs new file mode 100644 index 0000000..cbfe4ce --- /dev/null +++ b/ModernKeePass.Application.12/Services/DatabaseService.cs @@ -0,0 +1,103 @@ +using System; +using System.Threading.Tasks; +using ModernKeePass.Application.Common.Interfaces; +using ModernKeePass.Domain.Dtos; +using ModernKeePass.Domain.Entities; +using ModernKeePass.Domain.Interfaces; + +namespace ModernKeePass.Application.Services +{ + public class DatabaseService: IDatabaseService + { + private readonly IDatabaseProxy _databaseProxy; + + public string Name { get; private set; } + public bool IsOpen { get; private set; } + public Domain.Entities.GroupEntity RootGroupEntity { get; private set; } + public Domain.Entities.GroupEntity RecycleBin + { + get => _databaseProxy.RecycleBin; + set => _databaseProxy.RecycleBin = value; + } + public Entity Cipher + { + get => _databaseProxy.Cipher; + set => _databaseProxy.Cipher = value; + } + public Entity KeyDerivation + { + get => _databaseProxy.KeyDerivation; + set => _databaseProxy.KeyDerivation = value; + } + public string Compression + { + get => _databaseProxy.Compression; + set => _databaseProxy.Compression = value; + } + public bool IsRecycleBinEnabled => RecycleBin != null; + + public DatabaseService(IDatabaseProxy databaseProxy) + { + _databaseProxy = databaseProxy; + } + + public async Task Open(FileInfo fileInfo, Credentials credentials) + { + RootGroupEntity = await _databaseProxy.Open(fileInfo, credentials); + Name = RootGroupEntity?.Name; + IsOpen = true; + } + + public async Task Create(FileInfo fileInfo, Credentials credentials) + { + RootGroupEntity = await _databaseProxy.Create(fileInfo, credentials); + Name = RootGroupEntity?.Name; + IsOpen = true; + } + + public async Task Save() + { + await _databaseProxy.SaveDatabase(); + } + + public async Task SaveAs(FileInfo fileInfo) + { + await _databaseProxy.SaveDatabase(fileInfo); + } + + public Task CreateRecycleBin(Domain.Entities.GroupEntity recycleBinGroupEntity) + { + throw new NotImplementedException(); + } + + public async Task UpdateCredentials(Credentials credentials) + { + await _databaseProxy.UpdateCredentials(credentials); + await Save(); + } + + public void Close() + { + _databaseProxy.CloseDatabase(); + IsOpen = false; + } + + public async Task AddEntity(GroupEntity parentEntity, Entity entity) + { + await _databaseProxy.AddEntity(parentEntity, entity); + //await Save(); + } + + public async Task UpdateEntity(Entity entity) + { + await _databaseProxy.UpdateEntity(entity); + } + + public async Task DeleteEntity(Entity entity) + { + if (IsRecycleBinEnabled) await AddEntity(RecycleBin, entity); + await _databaseProxy.DeleteEntity(entity); + //await Save(); + } + } +} \ No newline at end of file diff --git a/ModernKeePass.Application.12/Services/FileService.cs b/ModernKeePass.Application.12/Services/FileService.cs new file mode 100644 index 0000000..6d85c01 --- /dev/null +++ b/ModernKeePass.Application.12/Services/FileService.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using ModernKeePass.Application.Common.Interfaces; +using ModernKeePass.Domain.Interfaces; + +namespace ModernKeePass.Application.Services +{ + public class FileService: IFileService + { + private readonly IFileProxy _fileProxy; + + public FileService(IFileProxy fileProxy) + { + _fileProxy = fileProxy; + } + + public Task OpenBinaryFile(string path) + { + return _fileProxy.OpenBinaryFile(path); + } + + public Task WriteBinaryContentsToFile(string path, byte[] contents) + { + return _fileProxy.WriteBinaryContentsToFile(path, contents); + } + + public Task> OpenTextFile(string path) + { + return _fileProxy.OpenTextFile(path); + } + + public void ReleaseFile(string path) + { + _fileProxy.ReleaseFile(path); + } + } +} \ No newline at end of file diff --git a/ModernKeePass.Application.12/Services/ImportService.cs b/ModernKeePass.Application.12/Services/ImportService.cs new file mode 100644 index 0000000..ca1b40e --- /dev/null +++ b/ModernKeePass.Application.12/Services/ImportService.cs @@ -0,0 +1,35 @@ +using System; +using System.Threading.Tasks; +using ModernKeePass.Application.Common.Interfaces; +using ModernKeePass.Domain.Entities; +using ModernKeePass.Domain.Enums; +using ModernKeePass.Domain.Interfaces; + +namespace ModernKeePass.Application.Services +{ + public class ImportService: IImportService + { + private readonly Func _importFormatProviders; + + public ImportService(Func importFormatProviders) + { + _importFormatProviders = importFormatProviders; + } + + public async Task Import(ImportFormat format, string filePath, Group group) + { + var importProvider = _importFormatProviders(format); + var data = await importProvider.Import(filePath); + + /*foreach (var entity in data) + { + var entry = group.AddNewEntry(); + entry.Name = entity["0"]; + entry.UserName = entity["1"]; + entry.Password = entity["2"]; + if (entity.Count > 3) entry.Url = entity["3"]; + if (entity.Count > 4) entry.Notes = entity["4"]; + }*/ + } + } +} diff --git a/ModernKeePass.Application.12/Services/RecentService.cs b/ModernKeePass.Application.12/Services/RecentService.cs new file mode 100644 index 0000000..13f2625 --- /dev/null +++ b/ModernKeePass.Application.12/Services/RecentService.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using ModernKeePass.Application.Common.Interfaces; +using ModernKeePass.Domain.Dtos; +using ModernKeePass.Domain.Interfaces; + +namespace ModernKeePass.Application.Services +{ + public class RecentService: IRecentService + { + private readonly IRecentProxy _recentProxy; + + public RecentService(IRecentProxy recentProxy) + { + _recentProxy = recentProxy; + } + + public bool HasEntries => _recentProxy.EntryCount > 0; + + public async Task Get(string token) + { + return await _recentProxy.Get(token); + } + + public async Task> GetAll() + { + return await _recentProxy.GetAll(); + } + + public async Task Add(FileInfo recentItem) + { + await _recentProxy.Add(recentItem); + } + + public void ClearAll() + { + _recentProxy.ClearAll(); + } + } +} \ No newline at end of file diff --git a/ModernKeePass.Application.12/Services/ResourceService.cs b/ModernKeePass.Application.12/Services/ResourceService.cs new file mode 100644 index 0000000..8f3e920 --- /dev/null +++ b/ModernKeePass.Application.12/Services/ResourceService.cs @@ -0,0 +1,20 @@ +using ModernKeePass.Application.Common.Interfaces; +using ModernKeePass.Domain.Interfaces; + +namespace ModernKeePass.Application.Services +{ + public class ResourceService: IResourceService + { + private readonly IResourceProxy _resourceProxy; + + public ResourceService(IResourceProxy resourceProxy) + { + _resourceProxy = resourceProxy; + } + + public string GetResourceValue(string key) + { + return _resourceProxy.GetResourceValue(key); + } + } +} \ No newline at end of file diff --git a/ModernKeePass.Application.12/Services/SecurityService.cs b/ModernKeePass.Application.12/Services/SecurityService.cs new file mode 100644 index 0000000..14918fe --- /dev/null +++ b/ModernKeePass.Application.12/Services/SecurityService.cs @@ -0,0 +1,35 @@ +using System.Threading.Tasks; +using ModernKeePass.Application.Common.Interfaces; +using ModernKeePass.Domain.Dtos; +using ModernKeePass.Domain.Interfaces; + +namespace ModernKeePass.Application.Services +{ + public class SecurityService: ISecurityService + { + private readonly IPasswordProxy _passwordProxy; + private readonly IFileService _fileService; + + public SecurityService(IPasswordProxy passwordProxy, IFileService fileService) + { + _passwordProxy = passwordProxy; + _fileService = fileService; + } + + public string GeneratePassword(PasswordGenerationOptions options) + { + return _passwordProxy.GeneratePassword(options); + } + + public uint EstimatePasswordComplexity(string password) + { + return _passwordProxy.EstimatePasswordComplexity(password); + } + + public async Task GenerateKeyFile(string filePath) + { + var fileContents = _passwordProxy.GenerateKeyFile(null); + await _fileService.WriteBinaryContentsToFile(filePath, fileContents); + } + } +} \ No newline at end of file diff --git a/ModernKeePass.Application.12/Services/SettingsService.cs b/ModernKeePass.Application.12/Services/SettingsService.cs new file mode 100644 index 0000000..2c16006 --- /dev/null +++ b/ModernKeePass.Application.12/Services/SettingsService.cs @@ -0,0 +1,25 @@ +using ModernKeePass.Application.Common.Interfaces; +using ModernKeePass.Domain.Interfaces; + +namespace ModernKeePass.Application.Services +{ + public class SettingsService: ISettingsService + { + private readonly ISettingsProxy _settingsProxy; + + public SettingsService(ISettingsProxy settingsProxy) + { + _settingsProxy = settingsProxy; + } + + public T GetSetting(string property, T defaultValue = default) + { + return _settingsProxy.GetSetting(property, defaultValue); + } + + public void PutSetting(string property, T value) + { + _settingsProxy.PutSetting(property, value); + } + } +} \ No newline at end of file diff --git a/ModernKeePass.Application/Common/Interfaces/ICryptographyClient.cs b/ModernKeePass.Application/Common/Interfaces/ICryptographyClient.cs new file mode 100644 index 0000000..f7c19ff --- /dev/null +++ b/ModernKeePass.Application/Common/Interfaces/ICryptographyClient.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using ModernKeePass.Domain.Entities; + +namespace ModernKeePass.Application.Common.Interfaces +{ + public interface ICryptographyClient + { + IEnumerable Ciphers { get; } + IEnumerable KeyDerivations { get; } + IEnumerable CompressionAlgorithms { get; } + } +} \ No newline at end of file diff --git a/ModernKeePass.Application/Common/Interfaces/IDatabaseProxy.cs b/ModernKeePass.Application/Common/Interfaces/IDatabaseProxy.cs new file mode 100644 index 0000000..de508b7 --- /dev/null +++ b/ModernKeePass.Application/Common/Interfaces/IDatabaseProxy.cs @@ -0,0 +1,28 @@ +using System.Threading.Tasks; +using ModernKeePass.Domain.Dtos; +using ModernKeePass.Domain.Entities; + +namespace ModernKeePass.Application.Common.Interfaces +{ + public interface IDatabaseProxy + { + bool IsOpen { get; } + GroupEntity RecycleBin { get; set; } + BaseEntity Cipher { get; set; } + BaseEntity KeyDerivation { get; set; } + string Compression { get; set; } + + Task Open(FileInfo fileInfo, Credentials credentials); + Task Create(FileInfo fileInfo, Credentials credentials); + Task SaveDatabase(); + Task SaveDatabase(FileInfo FileInfo); + Task UpdateCredentials(Credentials credentials); + void CloseDatabase(); + Task AddEntry(GroupEntity parentGroup, EntryEntity entity); + Task AddGroup(GroupEntity parentGroup, GroupEntity entity); + Task UpdateEntry(EntryEntity entity); + Task UpdateGroup(GroupEntity entity); + Task DeleteEntry(EntryEntity entity); + Task DeleteGroup(GroupEntity entity); + } +} \ No newline at end of file diff --git a/ModernKeePass.Application/Common/Interfaces/IFileProxy.cs b/ModernKeePass.Application/Common/Interfaces/IFileProxy.cs new file mode 100644 index 0000000..e9870e2 --- /dev/null +++ b/ModernKeePass.Application/Common/Interfaces/IFileProxy.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace ModernKeePass.Application.Common.Interfaces +{ + public interface IFileProxy + { + Task OpenBinaryFile(string path); + Task> OpenTextFile(string path); + Task WriteBinaryContentsToFile(string path, byte[] contents); + void ReleaseFile(string path); + } +} \ No newline at end of file diff --git a/ModernKeePass.Application/Common/Interfaces/IHasSelectableObject.cs b/ModernKeePass.Application/Common/Interfaces/IHasSelectableObject.cs new file mode 100644 index 0000000..f2fd247 --- /dev/null +++ b/ModernKeePass.Application/Common/Interfaces/IHasSelectableObject.cs @@ -0,0 +1,7 @@ +namespace ModernKeePass.Application.Common.Interfaces +{ + public interface IHasSelectableObject + { + ISelectableModel SelectedItem { get; set; } + } +} diff --git a/ModernKeePass.Application/Common/Interfaces/IImportFormat.cs b/ModernKeePass.Application/Common/Interfaces/IImportFormat.cs new file mode 100644 index 0000000..f581762 --- /dev/null +++ b/ModernKeePass.Application/Common/Interfaces/IImportFormat.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace ModernKeePass.Application.Common.Interfaces +{ + public interface IImportFormat + { + Task>> Import(string path); + } +} \ No newline at end of file diff --git a/ModernKeePass.Application/Common/Interfaces/IIsEnabled.cs b/ModernKeePass.Application/Common/Interfaces/IIsEnabled.cs new file mode 100644 index 0000000..1abc45a --- /dev/null +++ b/ModernKeePass.Application/Common/Interfaces/IIsEnabled.cs @@ -0,0 +1,7 @@ +namespace ModernKeePass.Application.Common.Interfaces +{ + public interface IIsEnabled + { + bool IsEnabled { get; } + } +} diff --git a/ModernKeePass.Application/Common/Interfaces/IPasswordProxy.cs b/ModernKeePass.Application/Common/Interfaces/IPasswordProxy.cs new file mode 100644 index 0000000..aec2b37 --- /dev/null +++ b/ModernKeePass.Application/Common/Interfaces/IPasswordProxy.cs @@ -0,0 +1,11 @@ +using ModernKeePass.Domain.Dtos; + +namespace ModernKeePass.Application.Common.Interfaces +{ + public interface IPasswordProxy + { + string GeneratePassword(PasswordGenerationOptions options); + uint EstimatePasswordComplexity(string password); + byte[] GenerateKeyFile(byte[] additionalEntropy); + } +} \ No newline at end of file diff --git a/ModernKeePass.Application/Common/Interfaces/IProxyInvocationHandler.cs b/ModernKeePass.Application/Common/Interfaces/IProxyInvocationHandler.cs new file mode 100644 index 0000000..5a04d1f --- /dev/null +++ b/ModernKeePass.Application/Common/Interfaces/IProxyInvocationHandler.cs @@ -0,0 +1,9 @@ +using System.Reflection; + +namespace ModernKeePass.Application.Common.Interfaces +{ + public interface IProxyInvocationHandler + { + object Invoke(object proxy, MethodInfo method, object[] parameters); + } +} \ No newline at end of file diff --git a/ModernKeePass.Application/Common/Interfaces/IRecentProxy.cs b/ModernKeePass.Application/Common/Interfaces/IRecentProxy.cs new file mode 100644 index 0000000..f0792ab --- /dev/null +++ b/ModernKeePass.Application/Common/Interfaces/IRecentProxy.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using ModernKeePass.Domain.Dtos; + +namespace ModernKeePass.Application.Common.Interfaces +{ + public interface IRecentProxy + { + int EntryCount { get; } + Task Get(string token); + Task> GetAll(); + Task Add(FileInfo recentItem); + void ClearAll(); + } +} \ No newline at end of file diff --git a/ModernKeePass.Application/Common/Interfaces/IResourceProxy.cs b/ModernKeePass.Application/Common/Interfaces/IResourceProxy.cs new file mode 100644 index 0000000..ba1530c --- /dev/null +++ b/ModernKeePass.Application/Common/Interfaces/IResourceProxy.cs @@ -0,0 +1,7 @@ +namespace ModernKeePass.Application.Common.Interfaces +{ + public interface IResourceProxy + { + string GetResourceValue(string key); + } +} \ No newline at end of file diff --git a/ModernKeePass.Application/Common/Interfaces/ISelectableModel.cs b/ModernKeePass.Application/Common/Interfaces/ISelectableModel.cs new file mode 100644 index 0000000..1102637 --- /dev/null +++ b/ModernKeePass.Application/Common/Interfaces/ISelectableModel.cs @@ -0,0 +1,7 @@ +namespace ModernKeePass.Application.Common.Interfaces +{ + public interface ISelectableModel + { + bool IsSelected { get; set; } + } +} \ No newline at end of file diff --git a/ModernKeePass.Application/Common/Interfaces/ISettingsProxy.cs b/ModernKeePass.Application/Common/Interfaces/ISettingsProxy.cs new file mode 100644 index 0000000..6471175 --- /dev/null +++ b/ModernKeePass.Application/Common/Interfaces/ISettingsProxy.cs @@ -0,0 +1,8 @@ +namespace ModernKeePass.Application.Common.Interfaces +{ + public interface ISettingsProxy + { + T GetSetting(string property, T defaultValue = default); + void PutSetting(string property, T value); + } +} \ No newline at end of file diff --git a/ModernKeePass.Application/Common/Mappings/IMapFrom.cs b/ModernKeePass.Application/Common/Mappings/IMapFrom.cs new file mode 100644 index 0000000..da4ab61 --- /dev/null +++ b/ModernKeePass.Application/Common/Mappings/IMapFrom.cs @@ -0,0 +1,10 @@ +using AutoMapper; + +namespace ModernKeePass.Application.Common.Mappings +{ + + public interface IMapFrom + { + void Mapping(Profile profile); + } +} \ No newline at end of file diff --git a/ModernKeePass.Application/Common/Mappings/MappingProfile.cs b/ModernKeePass.Application/Common/Mappings/MappingProfile.cs new file mode 100644 index 0000000..c572745 --- /dev/null +++ b/ModernKeePass.Application/Common/Mappings/MappingProfile.cs @@ -0,0 +1,30 @@ +using System; +using System.Linq; +using System.Reflection; +using AutoMapper; + +namespace ModernKeePass.Application.Common.Mappings +{ + public class MappingProfile : Profile + { + public MappingProfile() + { + ApplyMappingsFromAssembly(Assembly.GetExecutingAssembly()); + } + + private void ApplyMappingsFromAssembly(Assembly assembly) + { + var types = assembly.GetExportedTypes() + .Where(t => t.GetInterfaces().Any(i => + i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IMapFrom<>))) + .ToList(); + + foreach (var type in types) + { + var instance = Activator.CreateInstance(type); + var methodInfo = type.GetMethod("Mapping"); + methodInfo?.Invoke(instance, new object[] { this }); + } + } + } +} \ No newline at end of file diff --git a/ModernKeePass.Application/Common/Mappings/MappingProfiles.cs b/ModernKeePass.Application/Common/Mappings/MappingProfiles.cs new file mode 100644 index 0000000..dbd055d --- /dev/null +++ b/ModernKeePass.Application/Common/Mappings/MappingProfiles.cs @@ -0,0 +1,17 @@ +using AutoMapper; +using ModernKeePass.Application.Database.Models; +using ModernKeePass.Application.Entry.Models; +using ModernKeePass.Application.Group.Models; + +namespace ModernKeePass.Application.Common.Mappings +{ + public class MappingProfiles: Profile + { + public void ApplyMappings() + { + new DatabaseVm().Mapping(this); + new EntryVm().Mapping(this); + new GroupVm().Mapping(this); + } + } +} \ No newline at end of file diff --git a/ModernKeePass.Application/Database/Commands/CloseDatabase/CloseDatabaseCommand.cs b/ModernKeePass.Application/Database/Commands/CloseDatabase/CloseDatabaseCommand.cs new file mode 100644 index 0000000..904641e --- /dev/null +++ b/ModernKeePass.Application/Database/Commands/CloseDatabase/CloseDatabaseCommand.cs @@ -0,0 +1,31 @@ +using MediatR; +using System.Threading; +using System.Threading.Tasks; +using ModernKeePass.Application.Common.Interfaces; +using ModernKeePass.Application.Database.Queries.IsDatabaseOpen; +using ModernKeePass.Domain.Exceptions; + +namespace ModernKeePass.Application.Database.Commands.CloseDatabase +{ + public class CloseDatabaseCommand: IRequest + { + public class CloseDatabaseCommandHandler : IRequestHandler + { + private readonly IDatabaseProxy _database; + private readonly IMediator _mediator; + + public CloseDatabaseCommandHandler(IDatabaseProxy database, IMediator mediator) + { + _database = database; + _mediator = mediator; + } + + public async Task Handle(CloseDatabaseCommand message, CancellationToken cancellationToken) + { + var isDatabaseOpen = await _mediator.Send(new IsDatabaseOpenQuery(), cancellationToken); + if (isDatabaseOpen) _database.CloseDatabase(); + else throw new DatabaseClosedException(); + } + } + } +} \ No newline at end of file diff --git a/ModernKeePass.Application/Database/Commands/CreateDatabase/CreateDatabaseCommand.cs b/ModernKeePass.Application/Database/Commands/CreateDatabase/CreateDatabaseCommand.cs new file mode 100644 index 0000000..e0e7c44 --- /dev/null +++ b/ModernKeePass.Application/Database/Commands/CreateDatabase/CreateDatabaseCommand.cs @@ -0,0 +1,48 @@ +using MediatR; +using System.Threading; +using System.Threading.Tasks; +using AutoMapper; +using ModernKeePass.Application.Common.Interfaces; +using ModernKeePass.Application.Database.Models; +using ModernKeePass.Application.Database.Queries.IsDatabaseOpen; +using ModernKeePass.Application.Group.Models; +using ModernKeePass.Domain.Dtos; +using ModernKeePass.Domain.Exceptions; + +namespace ModernKeePass.Application.Database.Commands.CreateDatabase +{ + public class CreateDatabaseCommand : IRequest + { + public FileInfo FileInfo { get; set; } + public Credentials Credentials { get; set; } + + public class CreateDatabaseCommandHandler : IRequestHandler + { + private readonly IDatabaseProxy _database; + private readonly IMediator _mediator; + private readonly IMapper _mapper; + + public CreateDatabaseCommandHandler(IDatabaseProxy database, IMediator mediator, IMapper mapper) + { + _database = database; + _mediator = mediator; + _mapper = mapper; + } + + public async Task Handle(CreateDatabaseCommand message, CancellationToken cancellationToken) + { + var isDatabaseOpen = await _mediator.Send(new IsDatabaseOpenQuery(), cancellationToken); + if (isDatabaseOpen) throw new DatabaseOpenException(); + + var database = await _database.Create(message.FileInfo, message.Credentials); + var databaseVm = new DatabaseVm + { + IsOpen = true, + Name = database.Name, + RootGroup = _mapper.Map(database.RootGroupEntity) + }; + return databaseVm; + } + } + } +} \ No newline at end of file diff --git a/ModernKeePass.Application/Database/Commands/SaveDatabase/SaveDatabaseCommand.cs b/ModernKeePass.Application/Database/Commands/SaveDatabase/SaveDatabaseCommand.cs new file mode 100644 index 0000000..3d29159 --- /dev/null +++ b/ModernKeePass.Application/Database/Commands/SaveDatabase/SaveDatabaseCommand.cs @@ -0,0 +1,31 @@ +using MediatR; +using System.Threading; +using System.Threading.Tasks; +using ModernKeePass.Application.Common.Interfaces; +using ModernKeePass.Application.Database.Queries.IsDatabaseOpen; +using ModernKeePass.Domain.Exceptions; + +namespace ModernKeePass.Application.Database.Commands.SaveDatabase +{ + public class SaveDatabaseCommand : IRequest + { + public class SaveDatabaseCommandHandler : IRequestHandler + { + private readonly IDatabaseProxy _database; + private readonly IMediator _mediator; + + public SaveDatabaseCommandHandler(IDatabaseProxy database, IMediator mediator) + { + _database = database; + _mediator = mediator; + } + + public async Task Handle(SaveDatabaseCommand message, CancellationToken cancellationToken) + { + var isDatabaseOpen = await _mediator.Send(new IsDatabaseOpenQuery(), cancellationToken); + if (isDatabaseOpen) await _database.SaveDatabase(); + else throw new DatabaseClosedException(); + } + } + } +} \ No newline at end of file diff --git a/ModernKeePass.Application/Database/Models/DatabaseVm.cs b/ModernKeePass.Application/Database/Models/DatabaseVm.cs new file mode 100644 index 0000000..bc7cbde --- /dev/null +++ b/ModernKeePass.Application/Database/Models/DatabaseVm.cs @@ -0,0 +1,21 @@ +using AutoMapper; +using ModernKeePass.Application.Common.Mappings; +using ModernKeePass.Application.Group.Models; +using ModernKeePass.Domain.Entities; + +namespace ModernKeePass.Application.Database.Models +{ + public class DatabaseVm: IMapFrom + { + public bool IsOpen { get; set; } + public string Name { get; set; } + public GroupVm RootGroup { get; set; } + + public void Mapping(Profile profile) + { + profile.CreateMap() + .ForMember(d => d.Name, opts => opts.MapFrom(s => s.Name)) + .ForMember(d => d.RootGroup, opts => opts.MapFrom(s => s.RootGroupEntity)); + } + } +} \ No newline at end of file diff --git a/ModernKeePass.Application/Database/Models/MainVm.cs b/ModernKeePass.Application/Database/Models/MainVm.cs new file mode 100644 index 0000000..e13bb27 --- /dev/null +++ b/ModernKeePass.Application/Database/Models/MainVm.cs @@ -0,0 +1,7 @@ +namespace ModernKeePass.Application.Database.Models +{ + public class MainVm + { + + } +} \ No newline at end of file diff --git a/ModernKeePass.Application/Database/Queries/IsDatabaseOpen/IsDatabaseOpenQuery.cs b/ModernKeePass.Application/Database/Queries/IsDatabaseOpen/IsDatabaseOpenQuery.cs new file mode 100644 index 0000000..0266666 --- /dev/null +++ b/ModernKeePass.Application/Database/Queries/IsDatabaseOpen/IsDatabaseOpenQuery.cs @@ -0,0 +1,24 @@ +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using ModernKeePass.Application.Common.Interfaces; + +namespace ModernKeePass.Application.Database.Queries.IsDatabaseOpen +{ + public class IsDatabaseOpenQuery: IRequest + { + public class IsDatabaseOpenQueryHandler: IRequestHandler + { + private readonly IDatabaseProxy _databaseProxy; + + public IsDatabaseOpenQueryHandler(IDatabaseProxy databaseProxy) + { + _databaseProxy = databaseProxy; + } + public Task Handle(IsDatabaseOpenQuery request, CancellationToken cancellationToken) + { + return Task.FromResult(_databaseProxy.IsOpen); + } + } + } +} \ No newline at end of file diff --git a/ModernKeePass.Application/Database/Queries/OpenDatabase/OpenDatabaseQuery.cs b/ModernKeePass.Application/Database/Queries/OpenDatabase/OpenDatabaseQuery.cs new file mode 100644 index 0000000..0fd8917 --- /dev/null +++ b/ModernKeePass.Application/Database/Queries/OpenDatabase/OpenDatabaseQuery.cs @@ -0,0 +1,48 @@ +using System.Threading; +using System.Threading.Tasks; +using AutoMapper; +using MediatR; +using ModernKeePass.Application.Common.Interfaces; +using ModernKeePass.Application.Database.Models; +using ModernKeePass.Application.Database.Queries.IsDatabaseOpen; +using ModernKeePass.Application.Group.Models; +using ModernKeePass.Domain.Dtos; +using ModernKeePass.Domain.Exceptions; + +namespace ModernKeePass.Application.Database.Queries.OpenDatabase +{ + public class OpenDatabaseQuery: IRequest + { + public FileInfo FileInfo { get; set; } + public Credentials Credentials { get; set; } + + public class OpenDatabaseQueryHandler : IRequestHandler + { + private readonly IMapper _mapper; + private readonly IMediator _mediator; + private readonly IDatabaseProxy _databaseProxy; + + public OpenDatabaseQueryHandler(IMapper mapper, IMediator mediator, IDatabaseProxy databaseProxy) + { + _mapper = mapper; + _mediator = mediator; + _databaseProxy = databaseProxy; + } + + public async Task Handle(OpenDatabaseQuery request, CancellationToken cancellationToken) + { + var isDatabaseOpen = await _mediator.Send(new IsDatabaseOpenQuery(), cancellationToken); + if (isDatabaseOpen) throw new DatabaseOpenException(); + + var database = await _databaseProxy.Open(request.FileInfo, request.Credentials); + var databaseVm = new DatabaseVm + { + IsOpen = true, + Name = database.Name, + RootGroup = _mapper.Map(database.RootGroupEntity) + }; + return databaseVm; + } + } + } +} \ No newline at end of file diff --git a/ModernKeePass.Application/Database/Queries/OpenDatabase/OpenDatabaseQueryValidator.cs b/ModernKeePass.Application/Database/Queries/OpenDatabase/OpenDatabaseQueryValidator.cs new file mode 100644 index 0000000..d773f08 --- /dev/null +++ b/ModernKeePass.Application/Database/Queries/OpenDatabase/OpenDatabaseQueryValidator.cs @@ -0,0 +1,12 @@ +using FluentValidation; + +namespace ModernKeePass.Application.Database.Queries.OpenDatabase +{ + public class OpenDatabaseQueryValidator : AbstractValidator + { + public OpenDatabaseQueryValidator() + { + RuleFor(v => v.Credentials != null && v.FileInfo != null); + } + } +} \ No newline at end of file diff --git a/ModernKeePass.Application/Entry/Models/EntryVm.cs b/ModernKeePass.Application/Entry/Models/EntryVm.cs new file mode 100644 index 0000000..44f6bcd --- /dev/null +++ b/ModernKeePass.Application/Entry/Models/EntryVm.cs @@ -0,0 +1,27 @@ +using System.Drawing; +using AutoMapper; +using ModernKeePass.Application.Common.Mappings; +using ModernKeePass.Domain.Entities; +using ModernKeePass.Domain.Enums; + +namespace ModernKeePass.Application.Entry.Models +{ + public class EntryVm: IMapFrom + { + public string Id { get; set; } + public string Title { get; set; } + public Icon Icon { get; set; } + public Color ForegroundColor { get; set; } + public Color BackgroundColor { get; set; } + + public void Mapping(Profile profile) + { + profile.CreateMap() + .ForMember(d => d.Id, opts => opts.MapFrom(s => s.Id)) + .ForMember(d => d.Title, opts => opts.MapFrom(s => s.Name)) + .ForMember(d => d.Icon, opts => opts.MapFrom(s => s.Icon)) + .ForMember(d => d.ForegroundColor, opts => opts.MapFrom(s => s.ForegroundColor)) + .ForMember(d => d.BackgroundColor, opts => opts.MapFrom(s => s.BackgroundColor)); + } + } +} \ No newline at end of file diff --git a/ModernKeePass.Application/Group/Models/GroupVm.cs b/ModernKeePass.Application/Group/Models/GroupVm.cs new file mode 100644 index 0000000..3ba48f7 --- /dev/null +++ b/ModernKeePass.Application/Group/Models/GroupVm.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; +using System.Drawing; +using AutoMapper; +using ModernKeePass.Application.Common.Mappings; +using ModernKeePass.Application.Entry.Models; +using ModernKeePass.Domain.Entities; +using ModernKeePass.Domain.Enums; + +namespace ModernKeePass.Application.Group.Models +{ + public class GroupVm: IMapFrom + { + public string Id { get; set; } + public string Title { get; set; } + public Icon Icon { get; set; } + public Color ForegroundColor { get; set; } + public Color BackgroundColor { get; set; } + public List SubGroups { get; set; } = new List(); + public List Entries { get; set; } = new List(); + + public void Mapping(Profile profile) + { + profile.CreateMap() + .ForMember(d => d.Id, opts => opts.MapFrom(s => s.Id)) + .ForMember(d => d.Title, opts => opts.MapFrom(s => s.Name)) + .ForMember(d => d.Icon, opts => opts.MapFrom(s => s.Icon)) + .ForMember(d => d.ForegroundColor, opts => opts.MapFrom(s => s.ForegroundColor)) + .ForMember(d => d.BackgroundColor, opts => opts.MapFrom(s => s.BackgroundColor)) + .ForMember(d => d.Entries, opts => opts.MapFrom(s => s.Entries)) + .ForMember(d => d.SubGroups, opts => opts.MapFrom(s => s.SubGroups)); + } + } +} \ No newline at end of file diff --git a/ModernKeePass.Application/ModernKeePass.Application.csproj b/ModernKeePass.Application/ModernKeePass.Application.csproj new file mode 100644 index 0000000..e90d893 --- /dev/null +++ b/ModernKeePass.Application/ModernKeePass.Application.csproj @@ -0,0 +1,104 @@ + + + + + 14.0 + Debug + AnyCPU + {42353562-5E43-459C-8E3E-2F21E575261D} + Library + Properties + ModernKeePass.Application + ModernKeePass.Application + en-US + 512 + {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + + + v5.0 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {9a0759f1-9069-4841-99e3-3bec44e17356} + ModernKeePass.Domain + + + + + \ No newline at end of file diff --git a/ModernKeePassLib.Test/Properties/AssemblyInfo.cs b/ModernKeePass.Application/Properties/AssemblyInfo.cs similarity index 76% rename from ModernKeePassLib.Test/Properties/AssemblyInfo.cs rename to ModernKeePass.Application/Properties/AssemblyInfo.cs index b2bfb87..dac2789 100644 --- a/ModernKeePassLib.Test/Properties/AssemblyInfo.cs +++ b/ModernKeePass.Application/Properties/AssemblyInfo.cs @@ -1,18 +1,20 @@ -using System.Reflection; +using System.Resources; +using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. -[assembly: AssemblyTitle("ModernKeePassLib.Test")] +[assembly: AssemblyTitle("ModernKeePass.Application")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("ModernKeePassLib.Test")] -[assembly: AssemblyCopyright("Copyright © 2017")] +[assembly: AssemblyProduct("ModernKeePass.Application")] +[assembly: AssemblyCopyright("Copyright © 2020")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] +[assembly: NeutralResourcesLanguage("en")] // Version information for an assembly consists of the following four values: // diff --git a/ModernKeePass.Application/Services/CryptographyService.cs b/ModernKeePass.Application/Services/CryptographyService.cs new file mode 100644 index 0000000..caa6a57 --- /dev/null +++ b/ModernKeePass.Application/Services/CryptographyService.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using ModernKeePass.Application.Common.Interfaces; +using ModernKeePass.Domain.Entities; +using ModernKeePass.Domain.Interfaces; + +namespace ModernKeePass.Application.Services +{ + public class CryptographyService : ICryptographyService + { + private readonly ICryptographyClient _cryptographyClient; + public IEnumerable Ciphers => _cryptographyClient.Ciphers; + + public IEnumerable KeyDerivations => _cryptographyClient.KeyDerivations; + + public IEnumerable CompressionAlgorithms => _cryptographyClient.CompressionAlgorithms; + + public CryptographyService(ICryptographyClient cryptographyClient) + { + _cryptographyClient = cryptographyClient; + } + } +} \ No newline at end of file diff --git a/ModernKeePass.Application/Services/DatabaseService.cs b/ModernKeePass.Application/Services/DatabaseService.cs new file mode 100644 index 0000000..cbfe4ce --- /dev/null +++ b/ModernKeePass.Application/Services/DatabaseService.cs @@ -0,0 +1,103 @@ +using System; +using System.Threading.Tasks; +using ModernKeePass.Application.Common.Interfaces; +using ModernKeePass.Domain.Dtos; +using ModernKeePass.Domain.Entities; +using ModernKeePass.Domain.Interfaces; + +namespace ModernKeePass.Application.Services +{ + public class DatabaseService: IDatabaseService + { + private readonly IDatabaseProxy _databaseProxy; + + public string Name { get; private set; } + public bool IsOpen { get; private set; } + public Domain.Entities.GroupEntity RootGroupEntity { get; private set; } + public Domain.Entities.GroupEntity RecycleBin + { + get => _databaseProxy.RecycleBin; + set => _databaseProxy.RecycleBin = value; + } + public Entity Cipher + { + get => _databaseProxy.Cipher; + set => _databaseProxy.Cipher = value; + } + public Entity KeyDerivation + { + get => _databaseProxy.KeyDerivation; + set => _databaseProxy.KeyDerivation = value; + } + public string Compression + { + get => _databaseProxy.Compression; + set => _databaseProxy.Compression = value; + } + public bool IsRecycleBinEnabled => RecycleBin != null; + + public DatabaseService(IDatabaseProxy databaseProxy) + { + _databaseProxy = databaseProxy; + } + + public async Task Open(FileInfo fileInfo, Credentials credentials) + { + RootGroupEntity = await _databaseProxy.Open(fileInfo, credentials); + Name = RootGroupEntity?.Name; + IsOpen = true; + } + + public async Task Create(FileInfo fileInfo, Credentials credentials) + { + RootGroupEntity = await _databaseProxy.Create(fileInfo, credentials); + Name = RootGroupEntity?.Name; + IsOpen = true; + } + + public async Task Save() + { + await _databaseProxy.SaveDatabase(); + } + + public async Task SaveAs(FileInfo fileInfo) + { + await _databaseProxy.SaveDatabase(fileInfo); + } + + public Task CreateRecycleBin(Domain.Entities.GroupEntity recycleBinGroupEntity) + { + throw new NotImplementedException(); + } + + public async Task UpdateCredentials(Credentials credentials) + { + await _databaseProxy.UpdateCredentials(credentials); + await Save(); + } + + public void Close() + { + _databaseProxy.CloseDatabase(); + IsOpen = false; + } + + public async Task AddEntity(GroupEntity parentEntity, Entity entity) + { + await _databaseProxy.AddEntity(parentEntity, entity); + //await Save(); + } + + public async Task UpdateEntity(Entity entity) + { + await _databaseProxy.UpdateEntity(entity); + } + + public async Task DeleteEntity(Entity entity) + { + if (IsRecycleBinEnabled) await AddEntity(RecycleBin, entity); + await _databaseProxy.DeleteEntity(entity); + //await Save(); + } + } +} \ No newline at end of file diff --git a/ModernKeePass.Application/Services/FileService.cs b/ModernKeePass.Application/Services/FileService.cs new file mode 100644 index 0000000..6d85c01 --- /dev/null +++ b/ModernKeePass.Application/Services/FileService.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using ModernKeePass.Application.Common.Interfaces; +using ModernKeePass.Domain.Interfaces; + +namespace ModernKeePass.Application.Services +{ + public class FileService: IFileService + { + private readonly IFileProxy _fileProxy; + + public FileService(IFileProxy fileProxy) + { + _fileProxy = fileProxy; + } + + public Task OpenBinaryFile(string path) + { + return _fileProxy.OpenBinaryFile(path); + } + + public Task WriteBinaryContentsToFile(string path, byte[] contents) + { + return _fileProxy.WriteBinaryContentsToFile(path, contents); + } + + public Task> OpenTextFile(string path) + { + return _fileProxy.OpenTextFile(path); + } + + public void ReleaseFile(string path) + { + _fileProxy.ReleaseFile(path); + } + } +} \ No newline at end of file diff --git a/ModernKeePass.Application/Services/ImportService.cs b/ModernKeePass.Application/Services/ImportService.cs new file mode 100644 index 0000000..ca1b40e --- /dev/null +++ b/ModernKeePass.Application/Services/ImportService.cs @@ -0,0 +1,35 @@ +using System; +using System.Threading.Tasks; +using ModernKeePass.Application.Common.Interfaces; +using ModernKeePass.Domain.Entities; +using ModernKeePass.Domain.Enums; +using ModernKeePass.Domain.Interfaces; + +namespace ModernKeePass.Application.Services +{ + public class ImportService: IImportService + { + private readonly Func _importFormatProviders; + + public ImportService(Func importFormatProviders) + { + _importFormatProviders = importFormatProviders; + } + + public async Task Import(ImportFormat format, string filePath, Group group) + { + var importProvider = _importFormatProviders(format); + var data = await importProvider.Import(filePath); + + /*foreach (var entity in data) + { + var entry = group.AddNewEntry(); + entry.Name = entity["0"]; + entry.UserName = entity["1"]; + entry.Password = entity["2"]; + if (entity.Count > 3) entry.Url = entity["3"]; + if (entity.Count > 4) entry.Notes = entity["4"]; + }*/ + } + } +} diff --git a/ModernKeePass.Application/Services/RecentService.cs b/ModernKeePass.Application/Services/RecentService.cs new file mode 100644 index 0000000..13f2625 --- /dev/null +++ b/ModernKeePass.Application/Services/RecentService.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using ModernKeePass.Application.Common.Interfaces; +using ModernKeePass.Domain.Dtos; +using ModernKeePass.Domain.Interfaces; + +namespace ModernKeePass.Application.Services +{ + public class RecentService: IRecentService + { + private readonly IRecentProxy _recentProxy; + + public RecentService(IRecentProxy recentProxy) + { + _recentProxy = recentProxy; + } + + public bool HasEntries => _recentProxy.EntryCount > 0; + + public async Task Get(string token) + { + return await _recentProxy.Get(token); + } + + public async Task> GetAll() + { + return await _recentProxy.GetAll(); + } + + public async Task Add(FileInfo recentItem) + { + await _recentProxy.Add(recentItem); + } + + public void ClearAll() + { + _recentProxy.ClearAll(); + } + } +} \ No newline at end of file diff --git a/ModernKeePass.Application/Services/ResourceService.cs b/ModernKeePass.Application/Services/ResourceService.cs new file mode 100644 index 0000000..8f3e920 --- /dev/null +++ b/ModernKeePass.Application/Services/ResourceService.cs @@ -0,0 +1,20 @@ +using ModernKeePass.Application.Common.Interfaces; +using ModernKeePass.Domain.Interfaces; + +namespace ModernKeePass.Application.Services +{ + public class ResourceService: IResourceService + { + private readonly IResourceProxy _resourceProxy; + + public ResourceService(IResourceProxy resourceProxy) + { + _resourceProxy = resourceProxy; + } + + public string GetResourceValue(string key) + { + return _resourceProxy.GetResourceValue(key); + } + } +} \ No newline at end of file diff --git a/ModernKeePass.Application/Services/SecurityService.cs b/ModernKeePass.Application/Services/SecurityService.cs new file mode 100644 index 0000000..14918fe --- /dev/null +++ b/ModernKeePass.Application/Services/SecurityService.cs @@ -0,0 +1,35 @@ +using System.Threading.Tasks; +using ModernKeePass.Application.Common.Interfaces; +using ModernKeePass.Domain.Dtos; +using ModernKeePass.Domain.Interfaces; + +namespace ModernKeePass.Application.Services +{ + public class SecurityService: ISecurityService + { + private readonly IPasswordProxy _passwordProxy; + private readonly IFileService _fileService; + + public SecurityService(IPasswordProxy passwordProxy, IFileService fileService) + { + _passwordProxy = passwordProxy; + _fileService = fileService; + } + + public string GeneratePassword(PasswordGenerationOptions options) + { + return _passwordProxy.GeneratePassword(options); + } + + public uint EstimatePasswordComplexity(string password) + { + return _passwordProxy.EstimatePasswordComplexity(password); + } + + public async Task GenerateKeyFile(string filePath) + { + var fileContents = _passwordProxy.GenerateKeyFile(null); + await _fileService.WriteBinaryContentsToFile(filePath, fileContents); + } + } +} \ No newline at end of file diff --git a/ModernKeePass.Application/Services/SettingsService.cs b/ModernKeePass.Application/Services/SettingsService.cs new file mode 100644 index 0000000..2c16006 --- /dev/null +++ b/ModernKeePass.Application/Services/SettingsService.cs @@ -0,0 +1,25 @@ +using ModernKeePass.Application.Common.Interfaces; +using ModernKeePass.Domain.Interfaces; + +namespace ModernKeePass.Application.Services +{ + public class SettingsService: ISettingsService + { + private readonly ISettingsProxy _settingsProxy; + + public SettingsService(ISettingsProxy settingsProxy) + { + _settingsProxy = settingsProxy; + } + + public T GetSetting(string property, T defaultValue = default) + { + return _settingsProxy.GetSetting(property, defaultValue); + } + + public void PutSetting(string property, T value) + { + _settingsProxy.PutSetting(property, value); + } + } +} \ No newline at end of file diff --git a/ModernKeePass.Application/project.json b/ModernKeePass.Application/project.json new file mode 100644 index 0000000..1118a9b --- /dev/null +++ b/ModernKeePass.Application/project.json @@ -0,0 +1,15 @@ +{ + "supports": {}, + "dependencies": { + "Autofac": "4.9.4", + "AutoMapper": "6.2.2", + "FluentValidation": "8.6.2", + "MediatR": "4.0.0", + "Microsoft.NETCore.Portable.Compatibility": "1.0.1", + "NETStandard.Library": "1.6.1", + "Splat": "3.0.0" + }, + "frameworks": { + "netstandard1.2": {} + } +} \ No newline at end of file diff --git a/ModernKeePass.Domain.12/AOP/NotifyPropertyChangedBase.cs b/ModernKeePass.Domain.12/AOP/NotifyPropertyChangedBase.cs new file mode 100644 index 0000000..c9ab235 --- /dev/null +++ b/ModernKeePass.Domain.12/AOP/NotifyPropertyChangedBase.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using System.ComponentModel; +using System.Runtime.CompilerServices; + +namespace ModernKeePass.Domain.AOP +{ + public class NotifyPropertyChangedBase : INotifyPropertyChanged + { + public event PropertyChangedEventHandler PropertyChanged; + + protected void OnPropertyChanged(string propertyName = "") + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + protected bool SetProperty(ref T property, T value, [CallerMemberName] string propertyName = "") + { + if (EqualityComparer.Default.Equals(property, value)) + { + return false; + } + + property = value; + OnPropertyChanged(propertyName); + return true; + } + } +} diff --git a/ModernKeePass.Domain.12/Dtos/Credentials.cs b/ModernKeePass.Domain.12/Dtos/Credentials.cs new file mode 100644 index 0000000..bdb3091 --- /dev/null +++ b/ModernKeePass.Domain.12/Dtos/Credentials.cs @@ -0,0 +1,9 @@ +namespace ModernKeePass.Domain.Dtos +{ + public class Credentials + { + public string Password { get; set; } + public string KeyFilePath { get; set; } + // TODO: add Windows Hello + } +} \ No newline at end of file diff --git a/ModernKeePass.Domain.12/Dtos/FileInfo.cs b/ModernKeePass.Domain.12/Dtos/FileInfo.cs new file mode 100644 index 0000000..ad122b6 --- /dev/null +++ b/ModernKeePass.Domain.12/Dtos/FileInfo.cs @@ -0,0 +1,8 @@ +namespace ModernKeePass.Domain.Dtos +{ + public class FileInfo + { + public string Name { get; set; } + public string Path { get; set; } + } +} \ No newline at end of file diff --git a/ModernKeePass.Domain.12/Dtos/PasswordGenerationOptions.cs b/ModernKeePass.Domain.12/Dtos/PasswordGenerationOptions.cs new file mode 100644 index 0000000..bc40931 --- /dev/null +++ b/ModernKeePass.Domain.12/Dtos/PasswordGenerationOptions.cs @@ -0,0 +1,16 @@ +namespace ModernKeePass.Domain.Dtos +{ + public class PasswordGenerationOptions + { + public int PasswordLength { get; set; } + public bool UpperCasePatternSelected { get; set; } + public bool LowerCasePatternSelected { get; set; } + public bool DigitsPatternSelected { get; set; } + public bool SpecialPatternSelected { get; set; } + public bool MinusPatternSelected { get; set; } + public bool UnderscorePatternSelected { get; set; } + public bool SpacePatternSelected { get; set; } + public bool BracketsPatternSelected { get; set; } + public string CustomChars { get; set; } + } +} \ No newline at end of file diff --git a/ModernKeePass.Domain.12/Entities/BaseEntity.cs b/ModernKeePass.Domain.12/Entities/BaseEntity.cs new file mode 100644 index 0000000..0ba8e03 --- /dev/null +++ b/ModernKeePass.Domain.12/Entities/BaseEntity.cs @@ -0,0 +1,14 @@ +using System; +using System.Drawing; + +namespace ModernKeePass.Domain.Entities +{ + public class BaseEntity + { + public string Id { get; set; } + public string Name { get; set; } + public Color ForegroundColor { get; set; } + public Color BackgroundColor { get; set; } + public DateTimeOffset LastModificationDate { get; set; } + } +} \ No newline at end of file diff --git a/ModernKeePass.Domain.12/Entities/DatabaseEntity.cs b/ModernKeePass.Domain.12/Entities/DatabaseEntity.cs new file mode 100644 index 0000000..3c58b65 --- /dev/null +++ b/ModernKeePass.Domain.12/Entities/DatabaseEntity.cs @@ -0,0 +1,9 @@ +namespace ModernKeePass.Domain.Entities +{ + public class DatabaseEntity + { + public string Name { get; set; } + + public GroupEntity RootGroupEntity { get; set; } + } +} \ No newline at end of file diff --git a/ModernKeePass.Domain.12/Entities/EntryEntity.cs b/ModernKeePass.Domain.12/Entities/EntryEntity.cs new file mode 100644 index 0000000..47139bb --- /dev/null +++ b/ModernKeePass.Domain.12/Entities/EntryEntity.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using ModernKeePass.Domain.Enums; + +namespace ModernKeePass.Domain.Entities +{ + public class EntryEntity: BaseEntity + { + public string UserName { get; set; } + public string Password { get; set; } + public Uri Url { get; set; } + public string Notes { get; set; } + public DateTimeOffset ExpirationDate { get; set; } + public Color ForegroundColor { get; set; } + public Color BackgroundColor { get; set; } + public Dictionary AdditionalFields { get; set; } = new Dictionary(); + public IEnumerable History { get; set; } + public Icon Icon { get; set; } + public bool HasExpirationDate { get; set; } + } +} \ No newline at end of file diff --git a/ModernKeePass.Domain.12/Entities/GroupEntity.cs b/ModernKeePass.Domain.12/Entities/GroupEntity.cs new file mode 100644 index 0000000..f538150 --- /dev/null +++ b/ModernKeePass.Domain.12/Entities/GroupEntity.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using ModernKeePass.Domain.Enums; + +namespace ModernKeePass.Domain.Entities +{ + public class GroupEntity : BaseEntity + { + public List SubGroups { get; set; } = new List(); + public List Entries { get; set; } = new List(); + public Icon Icon { get; set; } + } +} \ No newline at end of file diff --git a/ModernKeePass.Domain.12/Enums/CredentialStatusTypes.cs b/ModernKeePass.Domain.12/Enums/CredentialStatusTypes.cs new file mode 100644 index 0000000..7e63087 --- /dev/null +++ b/ModernKeePass.Domain.12/Enums/CredentialStatusTypes.cs @@ -0,0 +1,10 @@ +namespace ModernKeePass.Domain.Enums +{ + public enum CredentialStatusTypes + { + Normal = 0, + Error = 1, + Warning = 3, + Success = 5 + } +} \ No newline at end of file diff --git a/ModernKeePass.Domain.12/Enums/Icon.cs b/ModernKeePass.Domain.12/Enums/Icon.cs new file mode 100644 index 0000000..3584568 --- /dev/null +++ b/ModernKeePass.Domain.12/Enums/Icon.cs @@ -0,0 +1,54 @@ +namespace ModernKeePass.Domain.Enums +{ + public enum Icon + { + Delete, + Edit, + Save, + Cancel, + Accept, + Home, + Camera, + Setting, + Mail, + Find, + Help, + Clock, + Crop, + World, + Flag, + PreviewLink, + Document, + ProtectedDocument, + ContactInfo, + ViewAll, + Rotate, + List, + Shop, + BrowsePhotos, + Caption, + Repair, + Page, + Paste, + Important, + SlideShow, + MapDrive, + ContactPresence, + Contact, + Folder, + View, + Permissions, + Map, + CellPhone, + OutlineStar, + Calculator, + Library, + SyncFolder, + GoToStart, + ZeroBars, + FourBars, + Scan, + ReportHacked, + Stop + } +} diff --git a/ModernKeePass.Domain.12/Enums/ImportFormat.cs b/ModernKeePass.Domain.12/Enums/ImportFormat.cs new file mode 100644 index 0000000..9044d9c --- /dev/null +++ b/ModernKeePass.Domain.12/Enums/ImportFormat.cs @@ -0,0 +1,7 @@ +namespace ModernKeePass.Domain.Enums +{ + public enum ImportFormat + { + CSV + } +} diff --git a/ModernKeePass.Domain.12/Exceptions/DatabaseClosedException.cs b/ModernKeePass.Domain.12/Exceptions/DatabaseClosedException.cs new file mode 100644 index 0000000..3961243 --- /dev/null +++ b/ModernKeePass.Domain.12/Exceptions/DatabaseClosedException.cs @@ -0,0 +1,7 @@ +using System; + +namespace ModernKeePass.Domain.Exceptions +{ + public class DatabaseClosedException: Exception + { } +} \ No newline at end of file diff --git a/ModernKeePass.Domain.12/Exceptions/DatabaseOpenException.cs b/ModernKeePass.Domain.12/Exceptions/DatabaseOpenException.cs new file mode 100644 index 0000000..5633409 --- /dev/null +++ b/ModernKeePass.Domain.12/Exceptions/DatabaseOpenException.cs @@ -0,0 +1,7 @@ +using System; + +namespace ModernKeePass.Domain.Exceptions +{ + public class DatabaseOpenException: Exception + { } +} \ No newline at end of file diff --git a/ModernKeePass.Domain.12/Exceptions/NavigationException.cs b/ModernKeePass.Domain.12/Exceptions/NavigationException.cs new file mode 100644 index 0000000..ea93e00 --- /dev/null +++ b/ModernKeePass.Domain.12/Exceptions/NavigationException.cs @@ -0,0 +1,11 @@ +using System; + +namespace ModernKeePass.Domain.Exceptions +{ + public class NavigationException: Exception + { + public NavigationException(Type pageType) : base($"Failed to load Page {pageType.FullName}") + { + } + } +} \ No newline at end of file diff --git a/ModernKeePass.Domain.12/Exceptions/SaveException.cs b/ModernKeePass.Domain.12/Exceptions/SaveException.cs new file mode 100644 index 0000000..4ad0818 --- /dev/null +++ b/ModernKeePass.Domain.12/Exceptions/SaveException.cs @@ -0,0 +1,14 @@ +using System; + +namespace ModernKeePass.Domain.Exceptions +{ + public class SaveException : Exception + { + public new Exception InnerException { get; } + + public SaveException(Exception exception) + { + InnerException = exception; + } + } +} diff --git a/ModernKeePass.Domain.12/ModernKeePass.Domain.csproj b/ModernKeePass.Domain.12/ModernKeePass.Domain.csproj new file mode 100644 index 0000000..c2744a9 --- /dev/null +++ b/ModernKeePass.Domain.12/ModernKeePass.Domain.csproj @@ -0,0 +1,11 @@ + + + + netstandard1.2 + + + + + + + diff --git a/ModernKeePass.Domain/AOP/NotifyPropertyChangedBase.cs b/ModernKeePass.Domain/AOP/NotifyPropertyChangedBase.cs new file mode 100644 index 0000000..c9ab235 --- /dev/null +++ b/ModernKeePass.Domain/AOP/NotifyPropertyChangedBase.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using System.ComponentModel; +using System.Runtime.CompilerServices; + +namespace ModernKeePass.Domain.AOP +{ + public class NotifyPropertyChangedBase : INotifyPropertyChanged + { + public event PropertyChangedEventHandler PropertyChanged; + + protected void OnPropertyChanged(string propertyName = "") + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + protected bool SetProperty(ref T property, T value, [CallerMemberName] string propertyName = "") + { + if (EqualityComparer.Default.Equals(property, value)) + { + return false; + } + + property = value; + OnPropertyChanged(propertyName); + return true; + } + } +} diff --git a/ModernKeePass.Domain/Dtos/Credentials.cs b/ModernKeePass.Domain/Dtos/Credentials.cs new file mode 100644 index 0000000..bdb3091 --- /dev/null +++ b/ModernKeePass.Domain/Dtos/Credentials.cs @@ -0,0 +1,9 @@ +namespace ModernKeePass.Domain.Dtos +{ + public class Credentials + { + public string Password { get; set; } + public string KeyFilePath { get; set; } + // TODO: add Windows Hello + } +} \ No newline at end of file diff --git a/ModernKeePass.Domain/Dtos/FileInfo.cs b/ModernKeePass.Domain/Dtos/FileInfo.cs new file mode 100644 index 0000000..ad122b6 --- /dev/null +++ b/ModernKeePass.Domain/Dtos/FileInfo.cs @@ -0,0 +1,8 @@ +namespace ModernKeePass.Domain.Dtos +{ + public class FileInfo + { + public string Name { get; set; } + public string Path { get; set; } + } +} \ No newline at end of file diff --git a/ModernKeePass.Domain/Dtos/PasswordGenerationOptions.cs b/ModernKeePass.Domain/Dtos/PasswordGenerationOptions.cs new file mode 100644 index 0000000..bc40931 --- /dev/null +++ b/ModernKeePass.Domain/Dtos/PasswordGenerationOptions.cs @@ -0,0 +1,16 @@ +namespace ModernKeePass.Domain.Dtos +{ + public class PasswordGenerationOptions + { + public int PasswordLength { get; set; } + public bool UpperCasePatternSelected { get; set; } + public bool LowerCasePatternSelected { get; set; } + public bool DigitsPatternSelected { get; set; } + public bool SpecialPatternSelected { get; set; } + public bool MinusPatternSelected { get; set; } + public bool UnderscorePatternSelected { get; set; } + public bool SpacePatternSelected { get; set; } + public bool BracketsPatternSelected { get; set; } + public string CustomChars { get; set; } + } +} \ No newline at end of file diff --git a/ModernKeePass.Domain/Entities/BaseEntity.cs b/ModernKeePass.Domain/Entities/BaseEntity.cs new file mode 100644 index 0000000..0ba8e03 --- /dev/null +++ b/ModernKeePass.Domain/Entities/BaseEntity.cs @@ -0,0 +1,14 @@ +using System; +using System.Drawing; + +namespace ModernKeePass.Domain.Entities +{ + public class BaseEntity + { + public string Id { get; set; } + public string Name { get; set; } + public Color ForegroundColor { get; set; } + public Color BackgroundColor { get; set; } + public DateTimeOffset LastModificationDate { get; set; } + } +} \ No newline at end of file diff --git a/ModernKeePass.Domain/Entities/DatabaseEntity.cs b/ModernKeePass.Domain/Entities/DatabaseEntity.cs new file mode 100644 index 0000000..3c58b65 --- /dev/null +++ b/ModernKeePass.Domain/Entities/DatabaseEntity.cs @@ -0,0 +1,9 @@ +namespace ModernKeePass.Domain.Entities +{ + public class DatabaseEntity + { + public string Name { get; set; } + + public GroupEntity RootGroupEntity { get; set; } + } +} \ No newline at end of file diff --git a/ModernKeePass.Domain/Entities/EntryEntity.cs b/ModernKeePass.Domain/Entities/EntryEntity.cs new file mode 100644 index 0000000..e675947 --- /dev/null +++ b/ModernKeePass.Domain/Entities/EntryEntity.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using ModernKeePass.Domain.Enums; + +namespace ModernKeePass.Domain.Entities +{ + public class EntryEntity: BaseEntity + { + public string UserName { get; set; } + public string Password { get; set; } + public Uri Url { get; set; } + public string Notes { get; set; } + public DateTimeOffset ExpirationDate { get; set; } + public Dictionary AdditionalFields { get; set; } = new Dictionary(); + public IEnumerable History { get; set; } + public Icon Icon { get; set; } + public bool HasExpirationDate { get; set; } + } +} \ No newline at end of file diff --git a/ModernKeePass.Domain/Entities/GroupEntity.cs b/ModernKeePass.Domain/Entities/GroupEntity.cs new file mode 100644 index 0000000..f538150 --- /dev/null +++ b/ModernKeePass.Domain/Entities/GroupEntity.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using ModernKeePass.Domain.Enums; + +namespace ModernKeePass.Domain.Entities +{ + public class GroupEntity : BaseEntity + { + public List SubGroups { get; set; } = new List(); + public List Entries { get; set; } = new List(); + public Icon Icon { get; set; } + } +} \ No newline at end of file diff --git a/ModernKeePass.Domain/Enums/CredentialStatusTypes.cs b/ModernKeePass.Domain/Enums/CredentialStatusTypes.cs new file mode 100644 index 0000000..7e63087 --- /dev/null +++ b/ModernKeePass.Domain/Enums/CredentialStatusTypes.cs @@ -0,0 +1,10 @@ +namespace ModernKeePass.Domain.Enums +{ + public enum CredentialStatusTypes + { + Normal = 0, + Error = 1, + Warning = 3, + Success = 5 + } +} \ No newline at end of file diff --git a/ModernKeePass.Domain/Enums/Icon.cs b/ModernKeePass.Domain/Enums/Icon.cs new file mode 100644 index 0000000..3584568 --- /dev/null +++ b/ModernKeePass.Domain/Enums/Icon.cs @@ -0,0 +1,54 @@ +namespace ModernKeePass.Domain.Enums +{ + public enum Icon + { + Delete, + Edit, + Save, + Cancel, + Accept, + Home, + Camera, + Setting, + Mail, + Find, + Help, + Clock, + Crop, + World, + Flag, + PreviewLink, + Document, + ProtectedDocument, + ContactInfo, + ViewAll, + Rotate, + List, + Shop, + BrowsePhotos, + Caption, + Repair, + Page, + Paste, + Important, + SlideShow, + MapDrive, + ContactPresence, + Contact, + Folder, + View, + Permissions, + Map, + CellPhone, + OutlineStar, + Calculator, + Library, + SyncFolder, + GoToStart, + ZeroBars, + FourBars, + Scan, + ReportHacked, + Stop + } +} diff --git a/ModernKeePass.Domain/Enums/ImportFormat.cs b/ModernKeePass.Domain/Enums/ImportFormat.cs new file mode 100644 index 0000000..9044d9c --- /dev/null +++ b/ModernKeePass.Domain/Enums/ImportFormat.cs @@ -0,0 +1,7 @@ +namespace ModernKeePass.Domain.Enums +{ + public enum ImportFormat + { + CSV + } +} diff --git a/ModernKeePass.Domain/Exceptions/DatabaseClosedException.cs b/ModernKeePass.Domain/Exceptions/DatabaseClosedException.cs new file mode 100644 index 0000000..3961243 --- /dev/null +++ b/ModernKeePass.Domain/Exceptions/DatabaseClosedException.cs @@ -0,0 +1,7 @@ +using System; + +namespace ModernKeePass.Domain.Exceptions +{ + public class DatabaseClosedException: Exception + { } +} \ No newline at end of file diff --git a/ModernKeePass.Domain/Exceptions/DatabaseOpenException.cs b/ModernKeePass.Domain/Exceptions/DatabaseOpenException.cs new file mode 100644 index 0000000..5633409 --- /dev/null +++ b/ModernKeePass.Domain/Exceptions/DatabaseOpenException.cs @@ -0,0 +1,7 @@ +using System; + +namespace ModernKeePass.Domain.Exceptions +{ + public class DatabaseOpenException: Exception + { } +} \ No newline at end of file diff --git a/ModernKeePass.Domain/Exceptions/NavigationException.cs b/ModernKeePass.Domain/Exceptions/NavigationException.cs new file mode 100644 index 0000000..ea93e00 --- /dev/null +++ b/ModernKeePass.Domain/Exceptions/NavigationException.cs @@ -0,0 +1,11 @@ +using System; + +namespace ModernKeePass.Domain.Exceptions +{ + public class NavigationException: Exception + { + public NavigationException(Type pageType) : base($"Failed to load Page {pageType.FullName}") + { + } + } +} \ No newline at end of file diff --git a/ModernKeePass.Domain/Exceptions/SaveException.cs b/ModernKeePass.Domain/Exceptions/SaveException.cs new file mode 100644 index 0000000..4ad0818 --- /dev/null +++ b/ModernKeePass.Domain/Exceptions/SaveException.cs @@ -0,0 +1,14 @@ +using System; + +namespace ModernKeePass.Domain.Exceptions +{ + public class SaveException : Exception + { + public new Exception InnerException { get; } + + public SaveException(Exception exception) + { + InnerException = exception; + } + } +} diff --git a/ModernKeePass.Shared/ModernKeePass.Shared.csproj b/ModernKeePass.Domain/ModernKeePass.Domain.csproj similarity index 70% rename from ModernKeePass.Shared/ModernKeePass.Shared.csproj rename to ModernKeePass.Domain/ModernKeePass.Domain.csproj index 7c488ee..90f30a6 100644 --- a/ModernKeePass.Shared/ModernKeePass.Shared.csproj +++ b/ModernKeePass.Domain/ModernKeePass.Domain.csproj @@ -5,11 +5,11 @@ 14.0 Debug AnyCPU - {A3354969-5AAC-4075-8CBF-EA4805B59EFA} + {9A0759F1-9069-4841-99E3-3BEC44E17356} Library Properties - ModernKeePass.Shared - ModernKeePass.Shared + ModernKeePass.Domain + ModernKeePass.Domain en-US 512 {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} @@ -39,6 +39,21 @@ + + + + + + + + + + + + + + + diff --git a/ModernKeePass.Shared/Properties/AssemblyInfo.cs b/ModernKeePass.Domain/Properties/AssemblyInfo.cs similarity index 85% rename from ModernKeePass.Shared/Properties/AssemblyInfo.cs rename to ModernKeePass.Domain/Properties/AssemblyInfo.cs index 0685491..8e6bb49 100644 --- a/ModernKeePass.Shared/Properties/AssemblyInfo.cs +++ b/ModernKeePass.Domain/Properties/AssemblyInfo.cs @@ -6,12 +6,12 @@ using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. -[assembly: AssemblyTitle("ModernKeePass.Shared")] +[assembly: AssemblyTitle("ModernKeePass.Domain")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("ModernKeePass.Shared")] -[assembly: AssemblyCopyright("Copyright © 2018")] +[assembly: AssemblyProduct("ModernKeePass.Domain")] +[assembly: AssemblyCopyright("Copyright © 2020")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: NeutralResourcesLanguage("en")] diff --git a/ModernKeePass.Shared/project.json b/ModernKeePass.Domain/project.json similarity index 72% rename from ModernKeePass.Shared/project.json rename to ModernKeePass.Domain/project.json index b737941..942ec30 100644 --- a/ModernKeePass.Shared/project.json +++ b/ModernKeePass.Domain/project.json @@ -2,7 +2,8 @@ "supports": {}, "dependencies": { "Microsoft.NETCore.Portable.Compatibility": "1.0.1", - "NETStandard.Library": "1.6.0" + "NETStandard.Library": "1.6.1", + "Splat": "3.0.0" }, "frameworks": { "netstandard1.2": {} diff --git a/ModernKeePass.Infrastructure/DependencyInjection.cs b/ModernKeePass.Infrastructure/DependencyInjection.cs new file mode 100644 index 0000000..bbebb55 --- /dev/null +++ b/ModernKeePass.Infrastructure/DependencyInjection.cs @@ -0,0 +1,25 @@ +using Autofac; +using AutoMapper; +using ModernKeePass.Application.Common.Interfaces; +using ModernKeePass.Infrastructure.KeePass; +using ModernKeePass.Infrastructure.UWP; + +namespace ModernKeePass.Infrastructure +{ + public class DependencyInjection: Module + { + protected override void Load(ContainerBuilder builder) + { + builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As(); + builder.RegisterType().As(); + builder.RegisterType().As(); + builder.RegisterType().As(); + builder.RegisterType().As(); + + // Register Automapper profiles + builder.RegisterType().As(); + } + } +} \ No newline at end of file diff --git a/ModernKeePass.Infrastructure/File/CsvImportFormat.cs b/ModernKeePass.Infrastructure/File/CsvImportFormat.cs new file mode 100644 index 0000000..a240a50 --- /dev/null +++ b/ModernKeePass.Infrastructure/File/CsvImportFormat.cs @@ -0,0 +1,39 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using ModernKeePass.Application.Common.Interfaces; + +namespace ModernKeePass.Infrastructure.File +{ + public class CsvImportFormat: IImportFormat + { + private readonly IFileProxy _fileService; + private const bool HasHeaderRow = true; + private const char Delimiter = ';'; + private const char LineDelimiter = '\n'; + + public CsvImportFormat(IFileProxy fileService) + { + _fileService = fileService; + } + + public async Task>> Import(string path) + { + var parsedResult = new List>(); + var content = await _fileService.OpenTextFile(path); + foreach (var line in content) + { + var fields = line.Split(Delimiter); + var recordItem = new Dictionary(); + var i = 0; + foreach (var field in fields) + { + recordItem.Add(i.ToString(), field); + i++; + } + parsedResult.Add(recordItem); + } + + return parsedResult; + } + } +} \ No newline at end of file diff --git a/ModernKeePass.Infrastructure/KeePass/EntryMappingProfile.cs b/ModernKeePass.Infrastructure/KeePass/EntryMappingProfile.cs new file mode 100644 index 0000000..a767cf9 --- /dev/null +++ b/ModernKeePass.Infrastructure/KeePass/EntryMappingProfile.cs @@ -0,0 +1,76 @@ +using System; +using System.Linq; +using AutoMapper; +using ModernKeePass.Domain.Entities; +using ModernKeePassLib; +using ModernKeePassLib.Security; + +namespace ModernKeePass.Infrastructure.KeePass +{ + public class EntryMappingProfile: Profile + { + public EntryMappingProfile() + { + FromModelToDto(); + FromDtoToModel(); + } + + private void FromDtoToModel() + { + CreateMap() + .ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Uuid.ToHexString())) + .ForMember(dest => dest.Name, opt => opt.MapFrom(src => GetEntryValue(src, PwDefs.TitleField))) + .ForMember(dest => dest.UserName, opt => opt.MapFrom(src => GetEntryValue(src, PwDefs.UserNameField))) + .ForMember(dest => dest.Password, opt => opt.MapFrom(src => GetEntryValue(src, PwDefs.PasswordField))) + .ForMember(dest => dest.Url, opt => + { + opt.PreCondition(src => Uri.TryCreate(GetEntryValue(src, PwDefs.UrlField), UriKind.Absolute, out _)); + opt.MapFrom(src => new Uri(GetEntryValue(src, PwDefs.UrlField))); + }) + .ForMember(dest => dest.Notes, opt => opt.MapFrom(src => GetEntryValue(src, PwDefs.NotesField))) + .ForMember(dest => dest.ForegroundColor, opt => opt.MapFrom(src => src.ForegroundColor)) + .ForMember(dest => dest.BackgroundColor, opt => opt.MapFrom(src => src.BackgroundColor)) + .ForMember(dest => dest.ExpirationDate, opt => opt.MapFrom(src => new DateTimeOffset(src.ExpiryTime))) + .ForMember(dest => dest.HasExpirationDate, opt => opt.MapFrom(src => src.Expires)) + .ForMember(dest => dest.Icon, opt => opt.MapFrom(src => IconMapper.MapPwIconToIcon(src.IconId))) + .ForMember(dest => dest.AdditionalFields, opt => opt.MapFrom(src => + src.Strings.Where(s => !PwDefs.GetStandardFields().Contains(s.Key)).ToDictionary(s => s.Key, s => GetEntryValue(src, s.Key)))) + .ForMember(dest => dest.LastModificationDate, opt => opt.MapFrom(src => new DateTimeOffset(src.LastModificationTime))); + } + + private void FromModelToDto() + { + CreateMap().ConvertUsing(); + } + + private string GetEntryValue(PwEntry entry, string key) => entry.Strings.GetSafe(key).ReadString(); + } + + public class EntryToPwEntryDictionaryConverter : ITypeConverter + { + public PwEntry Convert(EntryEntity source, PwEntry destination, ResolutionContext context) + { + //destination.Uuid = new PwUuid(System.Convert.FromBase64String(source.Id)); + destination.ExpiryTime = source.ExpirationDate.DateTime; + destination.Expires = source.HasExpirationDate; + destination.LastModificationTime = source.LastModificationDate.DateTime; + destination.BackgroundColor = source.BackgroundColor; + destination.ForegroundColor = source.ForegroundColor; + destination.IconId = IconMapper.MapIconToPwIcon(source.Icon); + SetEntryValue(destination, PwDefs.TitleField, source.Name); + SetEntryValue(destination, PwDefs.UserNameField, source.UserName); + SetEntryValue(destination, PwDefs.PasswordField, source.Password); + SetEntryValue(destination, PwDefs.UrlField, source.Url?.ToString()); + SetEntryValue(destination, PwDefs.NotesField, source.Notes); + foreach (var additionalField in source.AdditionalFields) + { + SetEntryValue(destination, additionalField.Key, additionalField.Value); + } + return destination; + } + private void SetEntryValue(PwEntry entry, string key, string newValue) + { + if (newValue != null) entry.Strings.Set(key, new ProtectedString(true, newValue)); + } + } +} \ No newline at end of file diff --git a/ModernKeePass.Infrastructure/KeePass/IconMapper.cs b/ModernKeePass.Infrastructure/KeePass/IconMapper.cs new file mode 100644 index 0000000..47c254a --- /dev/null +++ b/ModernKeePass.Infrastructure/KeePass/IconMapper.cs @@ -0,0 +1,126 @@ +using ModernKeePass.Domain.Enums; +using ModernKeePassLib; + +namespace ModernKeePass.Infrastructure.KeePass +{ + public static class IconMapper + { + public static Icon MapPwIconToIcon(PwIcon value) + { + switch (value) + { + case PwIcon.Key: return Icon.Permissions; + case PwIcon.WorldSocket: + case PwIcon.World: return Icon.World; + case PwIcon.Warning: return Icon.Important; + case PwIcon.WorldComputer: + case PwIcon.Drive: + case PwIcon.DriveWindows: + case PwIcon.NetworkServer: return Icon.MapDrive; + case PwIcon.MarkedDirectory: return Icon.Map; + case PwIcon.UserCommunication: return Icon.ContactInfo; + case PwIcon.Parts: return Icon.ViewAll; + case PwIcon.Notepad: return Icon.Document; + case PwIcon.Identity: return Icon.Contact; + case PwIcon.PaperReady: return Icon.SyncFolder; + case PwIcon.Digicam: return Icon.Camera; + case PwIcon.IRCommunication: return Icon.View; + case PwIcon.Energy: return Icon.ZeroBars; + case PwIcon.Scanner: return Icon.Scan; + case PwIcon.CDRom: return Icon.Rotate; + case PwIcon.Monitor: return Icon.Caption; + case PwIcon.EMailBox: + case PwIcon.EMail: return Icon.Mail; + case PwIcon.Configuration: return Icon.Setting; + case PwIcon.ClipboardReady: return Icon.Paste; + case PwIcon.PaperNew: return Icon.Page; + case PwIcon.Screen: return Icon.GoToStart; + case PwIcon.EnergyCareful: return Icon.FourBars; + case PwIcon.Disk: return Icon.Save; + case PwIcon.Console: return Icon.SlideShow; + case PwIcon.Printer: return Icon.Scan; + case PwIcon.ProgramIcons: return Icon.GoToStart; + case PwIcon.Settings: + case PwIcon.Tool: return Icon.Repair; + case PwIcon.Archive: return Icon.Crop; + case PwIcon.Count: return Icon.Calculator; + case PwIcon.Clock: return Icon.Clock; + case PwIcon.EMailSearch: return Icon.Find; + case PwIcon.PaperFlag: return Icon.Flag; + case PwIcon.TrashBin: return Icon.Delete; + case PwIcon.Expired: return Icon.ReportHacked; + case PwIcon.Info: return Icon.Help; + case PwIcon.Folder: + case PwIcon.FolderOpen: + case PwIcon.FolderPackage: return Icon.Folder; + case PwIcon.PaperLocked: return Icon.ProtectedDocument; + case PwIcon.Checked: return Icon.Accept; + case PwIcon.Pen: return Icon.Edit; + case PwIcon.Thumbnail: return Icon.BrowsePhotos; + case PwIcon.Book: return Icon.Library; + case PwIcon.List: return Icon.List; + case PwIcon.UserKey: return Icon.ContactPresence; + case PwIcon.Home: return Icon.Home; + case PwIcon.Star: return Icon.OutlineStar; + case PwIcon.Money: return Icon.Shop; + case PwIcon.Certificate: return Icon.PreviewLink; + case PwIcon.BlackBerry: return Icon.CellPhone; + default: return Icon.Stop; + } + } + + public static PwIcon MapIconToPwIcon(Icon value) + { + switch (value) + { + case Icon.Delete: return PwIcon.TrashBin; + case Icon.Edit: return PwIcon.Pen; + case Icon.Save: return PwIcon.Disk; + case Icon.Cancel: return PwIcon.Expired; + case Icon.Accept: return PwIcon.Checked; + case Icon.Home: return PwIcon.Home; + case Icon.Camera: return PwIcon.Digicam; + case Icon.Setting: return PwIcon.Configuration; + case Icon.Mail: return PwIcon.EMail; + case Icon.Find: return PwIcon.EMailSearch; + case Icon.Help: return PwIcon.Info; + case Icon.Clock: return PwIcon.Clock; + case Icon.Crop: return PwIcon.Archive; + case Icon.World: return PwIcon.World; + case Icon.Flag: return PwIcon.PaperFlag; + case Icon.PreviewLink: return PwIcon.Certificate; + case Icon.Document: return PwIcon.Notepad; + case Icon.ProtectedDocument: return PwIcon.PaperLocked; + case Icon.ContactInfo: return PwIcon.UserCommunication; + case Icon.ViewAll: return PwIcon.Parts; + case Icon.Rotate: return PwIcon.CDRom; + case Icon.List: return PwIcon.List; + case Icon.Shop: return PwIcon.Money; + case Icon.BrowsePhotos: return PwIcon.Thumbnail; + case Icon.Caption: return PwIcon.Monitor; + case Icon.Repair: return PwIcon.Tool; + case Icon.Page: return PwIcon.PaperNew; + case Icon.Paste: return PwIcon.ClipboardReady; + case Icon.Important: return PwIcon.Warning; + case Icon.SlideShow: return PwIcon.Console; + case Icon.MapDrive: return PwIcon.NetworkServer; + case Icon.ContactPresence: return PwIcon.UserKey; + case Icon.Contact: return PwIcon.Identity; + case Icon.Folder: return PwIcon.Folder; + case Icon.View: return PwIcon.IRCommunication; + case Icon.Permissions: return PwIcon.Key; + case Icon.Map: return PwIcon.MarkedDirectory; + case Icon.CellPhone: return PwIcon.BlackBerry; + case Icon.OutlineStar: return PwIcon.Star; + case Icon.Calculator: return PwIcon.Count; + case Icon.Library: return PwIcon.Book; + case Icon.SyncFolder: return PwIcon.PaperReady; + case Icon.GoToStart: return PwIcon.Screen; + case Icon.ZeroBars: return PwIcon.Energy; + case Icon.FourBars: return PwIcon.EnergyCareful; + case Icon.Scan: return PwIcon.Scanner; + default: return PwIcon.Key; + } + } + } +} diff --git a/ModernKeePass.Infrastructure/KeePass/KeePassCryptographyClient.cs b/ModernKeePass.Infrastructure/KeePass/KeePassCryptographyClient.cs new file mode 100644 index 0000000..9dfb3b0 --- /dev/null +++ b/ModernKeePass.Infrastructure/KeePass/KeePassCryptographyClient.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using ModernKeePass.Application.Common.Interfaces; +using ModernKeePass.Domain.Entities; +using ModernKeePassLib; +using ModernKeePassLib.Cryptography.Cipher; +using ModernKeePassLib.Cryptography.KeyDerivation; + +namespace ModernKeePass.Infrastructure.KeePass +{ + public class KeePassCryptographyClient: ICryptographyClient + { + public IEnumerable Ciphers + { + get + { + for (var inx = 0; inx < CipherPool.GlobalPool.EngineCount; inx++) + { + var cipher = CipherPool.GlobalPool[inx]; + yield return new BaseEntity + { + Id = cipher.CipherUuid.ToHexString(), + Name = cipher.DisplayName + }; + } + } + } + + public IEnumerable KeyDerivations => KdfPool.Engines.Select(e => new BaseEntity + { + Id = e.Uuid.ToHexString(), + Name = e.Name + }); + + public IEnumerable CompressionAlgorithms => Enum.GetNames(typeof(PwCompressionAlgorithm)).Take((int) PwCompressionAlgorithm.Count); + } +} \ No newline at end of file diff --git a/ModernKeePass.Infrastructure/KeePass/KeePassDatabaseClient.cs b/ModernKeePass.Infrastructure/KeePass/KeePassDatabaseClient.cs new file mode 100644 index 0000000..47aff20 --- /dev/null +++ b/ModernKeePass.Infrastructure/KeePass/KeePassDatabaseClient.cs @@ -0,0 +1,266 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using AutoMapper; +using ModernKeePass.Application.Common.Interfaces; +using ModernKeePass.Domain.Dtos; +using ModernKeePass.Domain.Entities; +using ModernKeePass.Domain.Exceptions; +using ModernKeePassLib; +using ModernKeePassLib.Cryptography.Cipher; +using ModernKeePassLib.Cryptography.KeyDerivation; +using ModernKeePassLib.Interfaces; +using ModernKeePassLib.Keys; +using ModernKeePassLib.Serialization; +using ModernKeePassLib.Utility; + +namespace ModernKeePass.Infrastructure.KeePass +{ + public class KeePassDatabaseClient: IDatabaseProxy + { + private readonly ISettingsProxy _settings; + private readonly IFileProxy _fileService; + private readonly IMapper _mapper; + private readonly PwDatabase _pwDatabase = new PwDatabase(); + private string _fileAccessToken; + private CompositeKey _compositeKey; + + public bool IsOpen => (_pwDatabase?.IsOpen).GetValueOrDefault(); + + public GroupEntity RecycleBin { get; set; } + + public BaseEntity Cipher + { + get + { + var cipher = CipherPool.GlobalPool.GetCipher(_pwDatabase.DataCipherUuid); + return new BaseEntity + { + Id = cipher.CipherUuid.ToHexString(), + Name = cipher.DisplayName + }; + } + set => _pwDatabase.DataCipherUuid = BuildIdFromString(value.Id); + } + + public BaseEntity KeyDerivation + { + get + { + var keyDerivation = KdfPool.Engines.First(e => e.Uuid.Equals(_pwDatabase.KdfParameters.KdfUuid)); + return new BaseEntity + { + Id = keyDerivation.Uuid.ToHexString(), + Name = keyDerivation.Name + }; + } + set => _pwDatabase.KdfParameters = KdfPool.Engines + .FirstOrDefault(e => e.Uuid.Equals(BuildIdFromString(value.Name)))?.GetDefaultParameters(); + } + + public string Compression + { + get => _pwDatabase.Compression.ToString("G"); + set => _pwDatabase.Compression = (PwCompressionAlgorithm)Enum.Parse(typeof(PwCompressionAlgorithm), value); + } + + public KeePassDatabaseClient(ISettingsProxy settings, IFileProxy fileService, IMapper mapper) + { + _settings = settings; + _fileService = fileService; + _mapper = mapper; + } + + public async Task Open(FileInfo fileInfo, Credentials credentials) + { + try + { + _compositeKey = await CreateCompositeKey(credentials); + var ioConnection = await BuildConnectionInfo(fileInfo); + + _pwDatabase.Open(ioConnection, _compositeKey, new NullStatusLogger()); + + _fileAccessToken = fileInfo.Path; + + return new DatabaseEntity + { + RootGroupEntity = BuildHierarchy(_pwDatabase.RootGroup) + }; + } + catch (InvalidCompositeKeyException ex) + { + throw new ArgumentException(ex.Message, ex); + } + } + + public async Task Create(FileInfo fileInfo, Credentials credentials) + { + _compositeKey = await CreateCompositeKey(credentials); + var ioConnection = await BuildConnectionInfo(fileInfo); + + _pwDatabase.New(ioConnection, _compositeKey); + + var fileFormat = _settings.GetSetting("DefaultFileFormat"); + switch (fileFormat) + { + case "4": + _pwDatabase.KdfParameters = KdfPool.Get("Argon2").GetDefaultParameters(); + break; + } + + _fileAccessToken = fileInfo.Path; + + // TODO: create sample data depending on settings + return new DatabaseEntity + { + RootGroupEntity = BuildHierarchy(_pwDatabase.RootGroup) + }; + } + + public async Task SaveDatabase() + { + if (!_pwDatabase.IsOpen) return; + try + { + _pwDatabase.Save(new NullStatusLogger()); + await _fileService.WriteBinaryContentsToFile(_fileAccessToken, _pwDatabase.IOConnectionInfo.Bytes); + } + catch (Exception e) + { + throw new SaveException(e); + } + } + + public async Task SaveDatabase(FileInfo fileInfo) + { + try + { + var newFileContents = await _fileService.OpenBinaryFile(fileInfo.Path); + _pwDatabase.SaveAs(IOConnectionInfo.FromByteArray(newFileContents), true, new NullStatusLogger()); + await _fileService.WriteBinaryContentsToFile(fileInfo.Path, _pwDatabase.IOConnectionInfo.Bytes); + + _fileService.ReleaseFile(_fileAccessToken); + _fileAccessToken = fileInfo.Path; + } + catch (Exception e) + { + throw new SaveException(e); + } + } + + public void CloseDatabase() + { + _pwDatabase?.Close(); + _fileService.ReleaseFile(_fileAccessToken); + } + + public async Task AddEntry(GroupEntity parentGroupEntity, EntryEntity entry) + { + await Task.Run(() => + { + var parentPwGroup = _pwDatabase.RootGroup.FindGroup(BuildIdFromString(parentGroupEntity.Id), true); + + var pwEntry = new PwEntry(true, true); + _mapper.Map(entry, pwEntry); + parentPwGroup.AddEntry(pwEntry, true); + entry.Id = pwEntry.Uuid.ToHexString(); + }); + } + public async Task AddGroup(GroupEntity parentGroupEntity, GroupEntity group) + { + await Task.Run(() => + { + var parentPwGroup = _pwDatabase.RootGroup.FindGroup(BuildIdFromString(parentGroupEntity.Id), true); + + var pwGroup = new PwGroup(true, true) + { + Name = group.Name + }; + parentPwGroup.AddGroup(pwGroup, true); + group.Id = pwGroup.Uuid.ToHexString(); + + }); + } + + public Task UpdateEntry(EntryEntity entry) + { + throw new NotImplementedException(); + } + + public Task UpdateGroup(GroupEntity group) + { + throw new NotImplementedException(); + } + + public async Task DeleteEntry(EntryEntity entry) + { + await Task.Run(() => + { + var pwEntry = _pwDatabase.RootGroup.FindEntry(BuildIdFromString(entry.Id), true); + var id = pwEntry.Uuid; + pwEntry.ParentGroup.Entries.Remove(pwEntry); + + if (_pwDatabase.RecycleBinEnabled) + { + _pwDatabase.DeletedObjects.Add(new PwDeletedObject(id, DateTime.UtcNow)); + } + }); + } + public async Task DeleteGroup(GroupEntity group) + { + await Task.Run(() => + { + var pwGroup = _pwDatabase.RootGroup.FindGroup(BuildIdFromString(group.Id), true); + var id = pwGroup.Uuid; + pwGroup.ParentGroup.Groups.Remove(pwGroup); + + if (_pwDatabase.RecycleBinEnabled) + { + _pwDatabase.DeletedObjects.Add(new PwDeletedObject(id, DateTime.UtcNow)); + } + }); + } + + public async Task UpdateCredentials(Credentials credentials) + { + _pwDatabase.MasterKey = await CreateCompositeKey(credentials); + } + + private async Task CreateCompositeKey(Credentials credentials) + { + var compositeKey = new CompositeKey(); + if (!string.IsNullOrEmpty(credentials.Password)) compositeKey.AddUserKey(new KcpPassword(credentials.Password)); + if (!string.IsNullOrEmpty(credentials.KeyFilePath)) + { + var kcpFileContents = await _fileService.OpenBinaryFile(credentials.KeyFilePath); + compositeKey.AddUserKey(new KcpKeyFile(IOConnectionInfo.FromByteArray(kcpFileContents))); + } + return compositeKey; + } + + private async Task BuildConnectionInfo(FileInfo fileInfo) + { + var fileContents = await _fileService.OpenBinaryFile(fileInfo.Path); + return IOConnectionInfo.FromByteArray(fileContents); + } + + private GroupEntity BuildHierarchy(PwGroup pwGroup) + { + // TODO: build entity hierarchy in an iterative way or implement lazy loading + var group = new GroupEntity + { + Id = pwGroup.Uuid.ToHexString(), + Name = pwGroup.Name, + Icon = IconMapper.MapPwIconToIcon(pwGroup.IconId), + Entries = pwGroup.Entries.Select(e => _mapper.Map(e)).ToList(), + SubGroups = pwGroup.Groups.Select(BuildHierarchy).ToList() + }; + return group; + } + + private PwUuid BuildIdFromString(string id) + { + return new PwUuid(MemUtil.HexStringToByteArray(id)); + } + } +} \ No newline at end of file diff --git a/ModernKeePass.Infrastructure/KeePass/KeePassPasswordClient.cs b/ModernKeePass.Infrastructure/KeePass/KeePassPasswordClient.cs new file mode 100644 index 0000000..199ae47 --- /dev/null +++ b/ModernKeePass.Infrastructure/KeePass/KeePassPasswordClient.cs @@ -0,0 +1,46 @@ +using ModernKeePass.Application.Common.Interfaces; +using ModernKeePass.Domain.Dtos; +using ModernKeePassLib.Cryptography; +using ModernKeePassLib.Cryptography.PasswordGenerator; +using ModernKeePassLib.Keys; + +namespace ModernKeePass.Infrastructure.KeePass +{ + public class KeePassPasswordClient: IPasswordProxy + { + public string GeneratePassword(PasswordGenerationOptions options) + { + var pwProfile = new PwProfile + { + GeneratorType = PasswordGeneratorType.CharSet, + Length = (uint)options.PasswordLength, + CharSet = new PwCharSet() + }; + + if (options.UpperCasePatternSelected) pwProfile.CharSet.Add(PwCharSet.UpperCase); + if (options.LowerCasePatternSelected) pwProfile.CharSet.Add(PwCharSet.LowerCase); + if (options.DigitsPatternSelected) pwProfile.CharSet.Add(PwCharSet.Digits); + if (options.SpecialPatternSelected) pwProfile.CharSet.Add(PwCharSet.Special); + if (options.MinusPatternSelected) pwProfile.CharSet.Add('-'); + if (options.UnderscorePatternSelected) pwProfile.CharSet.Add('_'); + if (options.SpacePatternSelected) pwProfile.CharSet.Add(' '); + if (options.BracketsPatternSelected) pwProfile.CharSet.Add(PwCharSet.Brackets); + + pwProfile.CharSet.Add(options.CustomChars); + + PwGenerator.Generate(out var password, pwProfile, null, new CustomPwGeneratorPool()); + + return password.ReadString(); + } + + public uint EstimatePasswordComplexity(string password) + { + return QualityEstimation.EstimatePasswordBits(password?.ToCharArray()); + } + + public byte[] GenerateKeyFile(byte[] additionalEntropy) + { + return KcpKeyFile.Create(additionalEntropy); + } + } +} \ No newline at end of file diff --git a/ModernKeePassLib/Libs/Windows.winmd b/ModernKeePass.Infrastructure/Libs/Windows.winmd similarity index 100% rename from ModernKeePassLib/Libs/Windows.winmd rename to ModernKeePass.Infrastructure/Libs/Windows.winmd diff --git a/ModernKeePass.Infrastructure/ModernKeePass.Infrastructure.csproj b/ModernKeePass.Infrastructure/ModernKeePass.Infrastructure.csproj new file mode 100644 index 0000000..bac34e7 --- /dev/null +++ b/ModernKeePass.Infrastructure/ModernKeePass.Infrastructure.csproj @@ -0,0 +1,80 @@ + + + + + 14.0 + Debug + AnyCPU + {09577E4C-4899-45B9-BF80-1803D617CCAE} + Library + Properties + ModernKeePass.Infrastructure + ModernKeePass.Infrastructure + en-US + 512 + {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + + + v5.0 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + + {42353562-5e43-459c-8e3e-2f21e575261d} + ModernKeePass.Application + + + {9a0759f1-9069-4841-99e3-3bec44e17356} + ModernKeePass.Domain + + + + + False + Libs\Windows.winmd + + + + + \ No newline at end of file diff --git a/ModernKeePass.Infrastructure/Properties/AssemblyInfo.cs b/ModernKeePass.Infrastructure/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..437fcf1 --- /dev/null +++ b/ModernKeePass.Infrastructure/Properties/AssemblyInfo.cs @@ -0,0 +1,30 @@ +using System.Resources; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("ModernKeePass.Infrastructure")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("ModernKeePass.Infrastructure")] +[assembly: AssemblyCopyright("Copyright © 2020")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: NeutralResourcesLanguage("en")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/ModernKeePass.Infrastructure/UWP/StorageFileClient.cs b/ModernKeePass.Infrastructure/UWP/StorageFileClient.cs new file mode 100644 index 0000000..4337d42 --- /dev/null +++ b/ModernKeePass.Infrastructure/UWP/StorageFileClient.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices.WindowsRuntime; +using System.Threading.Tasks; +using Windows.Storage; +using Windows.Storage.AccessCache; +using ModernKeePass.Application.Common.Interfaces; + +namespace ModernKeePass.Infrastructure.UWP +{ + public class StorageFileClient: IFileProxy + { + public async Task OpenBinaryFile(string path) + { + var file = await StorageApplicationPermissions.FutureAccessList.GetFileAsync(path); + var result = await FileIO.ReadBufferAsync(file); + return result.ToArray(); + } + + public async Task> OpenTextFile(string path) + { + var file = await StorageApplicationPermissions.FutureAccessList.GetFileAsync(path); + var result = await FileIO.ReadLinesAsync(file); + return result; + } + + public void ReleaseFile(string path) + { + StorageApplicationPermissions.FutureAccessList.Remove(path); + } + + public async Task WriteBinaryContentsToFile(string path, byte[] contents) + { + var file = await StorageApplicationPermissions.FutureAccessList.GetFileAsync(path); + await FileIO.WriteBytesAsync(file, contents); + } + } +} \ No newline at end of file diff --git a/ModernKeePass.Infrastructure/UWP/UwpRecentFilesClient.cs b/ModernKeePass.Infrastructure/UWP/UwpRecentFilesClient.cs new file mode 100644 index 0000000..7dd750a --- /dev/null +++ b/ModernKeePass.Infrastructure/UWP/UwpRecentFilesClient.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Windows.Storage.AccessCache; +using ModernKeePass.Application.Common.Interfaces; +using ModernKeePass.Domain.Dtos; + +namespace ModernKeePass.Infrastructure.UWP +{ + public class UwpRecentFilesClient: IRecentProxy + { + private readonly StorageItemMostRecentlyUsedList _mru = StorageApplicationPermissions.MostRecentlyUsedList; + + public int EntryCount => _mru.Entries.Count; + + public async Task Get(string token) + { + var recentEntry = _mru.Entries.FirstOrDefault(e => e.Token == token); + var file = await _mru.GetFileAsync(token); + StorageApplicationPermissions.FutureAccessList.AddOrReplace(recentEntry.Metadata, file); + return new FileInfo + { + Name = file.DisplayName, + Path = recentEntry.Metadata + }; + } + + public async Task> GetAll() + { + var result = new List(); + foreach (var entry in _mru.Entries) + { + try + { + var recentItem = await Get(entry.Token); + result.Add(recentItem); + } + catch (Exception) + { + _mru.Remove(entry.Token); + } + } + return result; + } + + public async Task Add(FileInfo recentItem) + { + var file = await StorageApplicationPermissions.FutureAccessList.GetFileAsync(recentItem.Path); + _mru.Add(file, recentItem.Path); + } + + public void ClearAll() + { + for (var i = _mru.Entries.Count; i > 0; i--) + { + var entry = _mru.Entries[i]; + StorageApplicationPermissions.FutureAccessList.Remove(entry.Metadata); + _mru.Remove(entry.Token); + } + } + } +} \ No newline at end of file diff --git a/ModernKeePass.Infrastructure/UWP/UwpResourceClient.cs b/ModernKeePass.Infrastructure/UWP/UwpResourceClient.cs new file mode 100644 index 0000000..6ddf960 --- /dev/null +++ b/ModernKeePass.Infrastructure/UWP/UwpResourceClient.cs @@ -0,0 +1,17 @@ +using Windows.ApplicationModel.Resources; +using ModernKeePass.Application.Common.Interfaces; + +namespace ModernKeePass.Infrastructure.UWP +{ + public class UwpResourceClient: IResourceProxy + { + private const string ResourceFileName = "CodeBehind"; + private readonly ResourceLoader _resourceLoader = ResourceLoader.GetForCurrentView(); + + public string GetResourceValue(string key) + { + var resource = _resourceLoader.GetString($"/{ResourceFileName}/{key}"); + return resource; + } + } +} \ No newline at end of file diff --git a/ModernKeePass.Infrastructure/UWP/UwpSettingsClient.cs b/ModernKeePass.Infrastructure/UWP/UwpSettingsClient.cs new file mode 100644 index 0000000..612f1d7 --- /dev/null +++ b/ModernKeePass.Infrastructure/UWP/UwpSettingsClient.cs @@ -0,0 +1,31 @@ +using System; +using Windows.Foundation.Collections; +using Windows.Storage; +using ModernKeePass.Application.Common.Interfaces; + +namespace ModernKeePass.Infrastructure.UWP +{ + public class UwpSettingsClient : ISettingsProxy + { + private readonly IPropertySet _values = ApplicationData.Current.LocalSettings.Values; + + public T GetSetting(string property, T defaultValue = default) + { + try + { + return (T)Convert.ChangeType(_values[property], typeof(T)); + } + catch (InvalidCastException) + { + return defaultValue; + } + } + + public void PutSetting(string property, T value) + { + if (_values.ContainsKey(property)) + _values[property] = value; + else _values.Add(property, value); + } + } +} diff --git a/ModernKeePass.Infrastructure/project.json b/ModernKeePass.Infrastructure/project.json new file mode 100644 index 0000000..450d6f3 --- /dev/null +++ b/ModernKeePass.Infrastructure/project.json @@ -0,0 +1,11 @@ +{ + "supports": {}, + "dependencies": { + "Microsoft.NETCore.Portable.Compatibility": "1.0.1", + "ModernKeePassLib": "2.44.1", + "NETStandard.Library": "1.6.1" + }, + "frameworks": { + "netstandard1.2": {} + } +} \ No newline at end of file diff --git a/ModernKeePass.Infrastucture.12/DependencyInjection.cs b/ModernKeePass.Infrastucture.12/DependencyInjection.cs new file mode 100644 index 0000000..bbebb55 --- /dev/null +++ b/ModernKeePass.Infrastucture.12/DependencyInjection.cs @@ -0,0 +1,25 @@ +using Autofac; +using AutoMapper; +using ModernKeePass.Application.Common.Interfaces; +using ModernKeePass.Infrastructure.KeePass; +using ModernKeePass.Infrastructure.UWP; + +namespace ModernKeePass.Infrastructure +{ + public class DependencyInjection: Module + { + protected override void Load(ContainerBuilder builder) + { + builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As(); + builder.RegisterType().As(); + builder.RegisterType().As(); + builder.RegisterType().As(); + builder.RegisterType().As(); + + // Register Automapper profiles + builder.RegisterType().As(); + } + } +} \ No newline at end of file diff --git a/ModernKeePass.Infrastucture.12/File/CsvImportFormat.cs b/ModernKeePass.Infrastucture.12/File/CsvImportFormat.cs new file mode 100644 index 0000000..a240a50 --- /dev/null +++ b/ModernKeePass.Infrastucture.12/File/CsvImportFormat.cs @@ -0,0 +1,39 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using ModernKeePass.Application.Common.Interfaces; + +namespace ModernKeePass.Infrastructure.File +{ + public class CsvImportFormat: IImportFormat + { + private readonly IFileProxy _fileService; + private const bool HasHeaderRow = true; + private const char Delimiter = ';'; + private const char LineDelimiter = '\n'; + + public CsvImportFormat(IFileProxy fileService) + { + _fileService = fileService; + } + + public async Task>> Import(string path) + { + var parsedResult = new List>(); + var content = await _fileService.OpenTextFile(path); + foreach (var line in content) + { + var fields = line.Split(Delimiter); + var recordItem = new Dictionary(); + var i = 0; + foreach (var field in fields) + { + recordItem.Add(i.ToString(), field); + i++; + } + parsedResult.Add(recordItem); + } + + return parsedResult; + } + } +} \ No newline at end of file diff --git a/ModernKeePass.Infrastucture.12/KeePass/EntryMappingProfile.cs b/ModernKeePass.Infrastucture.12/KeePass/EntryMappingProfile.cs new file mode 100644 index 0000000..a767cf9 --- /dev/null +++ b/ModernKeePass.Infrastucture.12/KeePass/EntryMappingProfile.cs @@ -0,0 +1,76 @@ +using System; +using System.Linq; +using AutoMapper; +using ModernKeePass.Domain.Entities; +using ModernKeePassLib; +using ModernKeePassLib.Security; + +namespace ModernKeePass.Infrastructure.KeePass +{ + public class EntryMappingProfile: Profile + { + public EntryMappingProfile() + { + FromModelToDto(); + FromDtoToModel(); + } + + private void FromDtoToModel() + { + CreateMap() + .ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Uuid.ToHexString())) + .ForMember(dest => dest.Name, opt => opt.MapFrom(src => GetEntryValue(src, PwDefs.TitleField))) + .ForMember(dest => dest.UserName, opt => opt.MapFrom(src => GetEntryValue(src, PwDefs.UserNameField))) + .ForMember(dest => dest.Password, opt => opt.MapFrom(src => GetEntryValue(src, PwDefs.PasswordField))) + .ForMember(dest => dest.Url, opt => + { + opt.PreCondition(src => Uri.TryCreate(GetEntryValue(src, PwDefs.UrlField), UriKind.Absolute, out _)); + opt.MapFrom(src => new Uri(GetEntryValue(src, PwDefs.UrlField))); + }) + .ForMember(dest => dest.Notes, opt => opt.MapFrom(src => GetEntryValue(src, PwDefs.NotesField))) + .ForMember(dest => dest.ForegroundColor, opt => opt.MapFrom(src => src.ForegroundColor)) + .ForMember(dest => dest.BackgroundColor, opt => opt.MapFrom(src => src.BackgroundColor)) + .ForMember(dest => dest.ExpirationDate, opt => opt.MapFrom(src => new DateTimeOffset(src.ExpiryTime))) + .ForMember(dest => dest.HasExpirationDate, opt => opt.MapFrom(src => src.Expires)) + .ForMember(dest => dest.Icon, opt => opt.MapFrom(src => IconMapper.MapPwIconToIcon(src.IconId))) + .ForMember(dest => dest.AdditionalFields, opt => opt.MapFrom(src => + src.Strings.Where(s => !PwDefs.GetStandardFields().Contains(s.Key)).ToDictionary(s => s.Key, s => GetEntryValue(src, s.Key)))) + .ForMember(dest => dest.LastModificationDate, opt => opt.MapFrom(src => new DateTimeOffset(src.LastModificationTime))); + } + + private void FromModelToDto() + { + CreateMap().ConvertUsing(); + } + + private string GetEntryValue(PwEntry entry, string key) => entry.Strings.GetSafe(key).ReadString(); + } + + public class EntryToPwEntryDictionaryConverter : ITypeConverter + { + public PwEntry Convert(EntryEntity source, PwEntry destination, ResolutionContext context) + { + //destination.Uuid = new PwUuid(System.Convert.FromBase64String(source.Id)); + destination.ExpiryTime = source.ExpirationDate.DateTime; + destination.Expires = source.HasExpirationDate; + destination.LastModificationTime = source.LastModificationDate.DateTime; + destination.BackgroundColor = source.BackgroundColor; + destination.ForegroundColor = source.ForegroundColor; + destination.IconId = IconMapper.MapIconToPwIcon(source.Icon); + SetEntryValue(destination, PwDefs.TitleField, source.Name); + SetEntryValue(destination, PwDefs.UserNameField, source.UserName); + SetEntryValue(destination, PwDefs.PasswordField, source.Password); + SetEntryValue(destination, PwDefs.UrlField, source.Url?.ToString()); + SetEntryValue(destination, PwDefs.NotesField, source.Notes); + foreach (var additionalField in source.AdditionalFields) + { + SetEntryValue(destination, additionalField.Key, additionalField.Value); + } + return destination; + } + private void SetEntryValue(PwEntry entry, string key, string newValue) + { + if (newValue != null) entry.Strings.Set(key, new ProtectedString(true, newValue)); + } + } +} \ No newline at end of file diff --git a/ModernKeePass.Infrastucture.12/KeePass/IconMapper.cs b/ModernKeePass.Infrastucture.12/KeePass/IconMapper.cs new file mode 100644 index 0000000..47c254a --- /dev/null +++ b/ModernKeePass.Infrastucture.12/KeePass/IconMapper.cs @@ -0,0 +1,126 @@ +using ModernKeePass.Domain.Enums; +using ModernKeePassLib; + +namespace ModernKeePass.Infrastructure.KeePass +{ + public static class IconMapper + { + public static Icon MapPwIconToIcon(PwIcon value) + { + switch (value) + { + case PwIcon.Key: return Icon.Permissions; + case PwIcon.WorldSocket: + case PwIcon.World: return Icon.World; + case PwIcon.Warning: return Icon.Important; + case PwIcon.WorldComputer: + case PwIcon.Drive: + case PwIcon.DriveWindows: + case PwIcon.NetworkServer: return Icon.MapDrive; + case PwIcon.MarkedDirectory: return Icon.Map; + case PwIcon.UserCommunication: return Icon.ContactInfo; + case PwIcon.Parts: return Icon.ViewAll; + case PwIcon.Notepad: return Icon.Document; + case PwIcon.Identity: return Icon.Contact; + case PwIcon.PaperReady: return Icon.SyncFolder; + case PwIcon.Digicam: return Icon.Camera; + case PwIcon.IRCommunication: return Icon.View; + case PwIcon.Energy: return Icon.ZeroBars; + case PwIcon.Scanner: return Icon.Scan; + case PwIcon.CDRom: return Icon.Rotate; + case PwIcon.Monitor: return Icon.Caption; + case PwIcon.EMailBox: + case PwIcon.EMail: return Icon.Mail; + case PwIcon.Configuration: return Icon.Setting; + case PwIcon.ClipboardReady: return Icon.Paste; + case PwIcon.PaperNew: return Icon.Page; + case PwIcon.Screen: return Icon.GoToStart; + case PwIcon.EnergyCareful: return Icon.FourBars; + case PwIcon.Disk: return Icon.Save; + case PwIcon.Console: return Icon.SlideShow; + case PwIcon.Printer: return Icon.Scan; + case PwIcon.ProgramIcons: return Icon.GoToStart; + case PwIcon.Settings: + case PwIcon.Tool: return Icon.Repair; + case PwIcon.Archive: return Icon.Crop; + case PwIcon.Count: return Icon.Calculator; + case PwIcon.Clock: return Icon.Clock; + case PwIcon.EMailSearch: return Icon.Find; + case PwIcon.PaperFlag: return Icon.Flag; + case PwIcon.TrashBin: return Icon.Delete; + case PwIcon.Expired: return Icon.ReportHacked; + case PwIcon.Info: return Icon.Help; + case PwIcon.Folder: + case PwIcon.FolderOpen: + case PwIcon.FolderPackage: return Icon.Folder; + case PwIcon.PaperLocked: return Icon.ProtectedDocument; + case PwIcon.Checked: return Icon.Accept; + case PwIcon.Pen: return Icon.Edit; + case PwIcon.Thumbnail: return Icon.BrowsePhotos; + case PwIcon.Book: return Icon.Library; + case PwIcon.List: return Icon.List; + case PwIcon.UserKey: return Icon.ContactPresence; + case PwIcon.Home: return Icon.Home; + case PwIcon.Star: return Icon.OutlineStar; + case PwIcon.Money: return Icon.Shop; + case PwIcon.Certificate: return Icon.PreviewLink; + case PwIcon.BlackBerry: return Icon.CellPhone; + default: return Icon.Stop; + } + } + + public static PwIcon MapIconToPwIcon(Icon value) + { + switch (value) + { + case Icon.Delete: return PwIcon.TrashBin; + case Icon.Edit: return PwIcon.Pen; + case Icon.Save: return PwIcon.Disk; + case Icon.Cancel: return PwIcon.Expired; + case Icon.Accept: return PwIcon.Checked; + case Icon.Home: return PwIcon.Home; + case Icon.Camera: return PwIcon.Digicam; + case Icon.Setting: return PwIcon.Configuration; + case Icon.Mail: return PwIcon.EMail; + case Icon.Find: return PwIcon.EMailSearch; + case Icon.Help: return PwIcon.Info; + case Icon.Clock: return PwIcon.Clock; + case Icon.Crop: return PwIcon.Archive; + case Icon.World: return PwIcon.World; + case Icon.Flag: return PwIcon.PaperFlag; + case Icon.PreviewLink: return PwIcon.Certificate; + case Icon.Document: return PwIcon.Notepad; + case Icon.ProtectedDocument: return PwIcon.PaperLocked; + case Icon.ContactInfo: return PwIcon.UserCommunication; + case Icon.ViewAll: return PwIcon.Parts; + case Icon.Rotate: return PwIcon.CDRom; + case Icon.List: return PwIcon.List; + case Icon.Shop: return PwIcon.Money; + case Icon.BrowsePhotos: return PwIcon.Thumbnail; + case Icon.Caption: return PwIcon.Monitor; + case Icon.Repair: return PwIcon.Tool; + case Icon.Page: return PwIcon.PaperNew; + case Icon.Paste: return PwIcon.ClipboardReady; + case Icon.Important: return PwIcon.Warning; + case Icon.SlideShow: return PwIcon.Console; + case Icon.MapDrive: return PwIcon.NetworkServer; + case Icon.ContactPresence: return PwIcon.UserKey; + case Icon.Contact: return PwIcon.Identity; + case Icon.Folder: return PwIcon.Folder; + case Icon.View: return PwIcon.IRCommunication; + case Icon.Permissions: return PwIcon.Key; + case Icon.Map: return PwIcon.MarkedDirectory; + case Icon.CellPhone: return PwIcon.BlackBerry; + case Icon.OutlineStar: return PwIcon.Star; + case Icon.Calculator: return PwIcon.Count; + case Icon.Library: return PwIcon.Book; + case Icon.SyncFolder: return PwIcon.PaperReady; + case Icon.GoToStart: return PwIcon.Screen; + case Icon.ZeroBars: return PwIcon.Energy; + case Icon.FourBars: return PwIcon.EnergyCareful; + case Icon.Scan: return PwIcon.Scanner; + default: return PwIcon.Key; + } + } + } +} diff --git a/ModernKeePass.Infrastucture.12/KeePass/KeePassCryptographyClient.cs b/ModernKeePass.Infrastucture.12/KeePass/KeePassCryptographyClient.cs new file mode 100644 index 0000000..9dfb3b0 --- /dev/null +++ b/ModernKeePass.Infrastucture.12/KeePass/KeePassCryptographyClient.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using ModernKeePass.Application.Common.Interfaces; +using ModernKeePass.Domain.Entities; +using ModernKeePassLib; +using ModernKeePassLib.Cryptography.Cipher; +using ModernKeePassLib.Cryptography.KeyDerivation; + +namespace ModernKeePass.Infrastructure.KeePass +{ + public class KeePassCryptographyClient: ICryptographyClient + { + public IEnumerable Ciphers + { + get + { + for (var inx = 0; inx < CipherPool.GlobalPool.EngineCount; inx++) + { + var cipher = CipherPool.GlobalPool[inx]; + yield return new BaseEntity + { + Id = cipher.CipherUuid.ToHexString(), + Name = cipher.DisplayName + }; + } + } + } + + public IEnumerable KeyDerivations => KdfPool.Engines.Select(e => new BaseEntity + { + Id = e.Uuid.ToHexString(), + Name = e.Name + }); + + public IEnumerable CompressionAlgorithms => Enum.GetNames(typeof(PwCompressionAlgorithm)).Take((int) PwCompressionAlgorithm.Count); + } +} \ No newline at end of file diff --git a/ModernKeePass.Infrastucture.12/KeePass/KeePassDatabaseClient.cs b/ModernKeePass.Infrastucture.12/KeePass/KeePassDatabaseClient.cs new file mode 100644 index 0000000..47aff20 --- /dev/null +++ b/ModernKeePass.Infrastucture.12/KeePass/KeePassDatabaseClient.cs @@ -0,0 +1,266 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using AutoMapper; +using ModernKeePass.Application.Common.Interfaces; +using ModernKeePass.Domain.Dtos; +using ModernKeePass.Domain.Entities; +using ModernKeePass.Domain.Exceptions; +using ModernKeePassLib; +using ModernKeePassLib.Cryptography.Cipher; +using ModernKeePassLib.Cryptography.KeyDerivation; +using ModernKeePassLib.Interfaces; +using ModernKeePassLib.Keys; +using ModernKeePassLib.Serialization; +using ModernKeePassLib.Utility; + +namespace ModernKeePass.Infrastructure.KeePass +{ + public class KeePassDatabaseClient: IDatabaseProxy + { + private readonly ISettingsProxy _settings; + private readonly IFileProxy _fileService; + private readonly IMapper _mapper; + private readonly PwDatabase _pwDatabase = new PwDatabase(); + private string _fileAccessToken; + private CompositeKey _compositeKey; + + public bool IsOpen => (_pwDatabase?.IsOpen).GetValueOrDefault(); + + public GroupEntity RecycleBin { get; set; } + + public BaseEntity Cipher + { + get + { + var cipher = CipherPool.GlobalPool.GetCipher(_pwDatabase.DataCipherUuid); + return new BaseEntity + { + Id = cipher.CipherUuid.ToHexString(), + Name = cipher.DisplayName + }; + } + set => _pwDatabase.DataCipherUuid = BuildIdFromString(value.Id); + } + + public BaseEntity KeyDerivation + { + get + { + var keyDerivation = KdfPool.Engines.First(e => e.Uuid.Equals(_pwDatabase.KdfParameters.KdfUuid)); + return new BaseEntity + { + Id = keyDerivation.Uuid.ToHexString(), + Name = keyDerivation.Name + }; + } + set => _pwDatabase.KdfParameters = KdfPool.Engines + .FirstOrDefault(e => e.Uuid.Equals(BuildIdFromString(value.Name)))?.GetDefaultParameters(); + } + + public string Compression + { + get => _pwDatabase.Compression.ToString("G"); + set => _pwDatabase.Compression = (PwCompressionAlgorithm)Enum.Parse(typeof(PwCompressionAlgorithm), value); + } + + public KeePassDatabaseClient(ISettingsProxy settings, IFileProxy fileService, IMapper mapper) + { + _settings = settings; + _fileService = fileService; + _mapper = mapper; + } + + public async Task Open(FileInfo fileInfo, Credentials credentials) + { + try + { + _compositeKey = await CreateCompositeKey(credentials); + var ioConnection = await BuildConnectionInfo(fileInfo); + + _pwDatabase.Open(ioConnection, _compositeKey, new NullStatusLogger()); + + _fileAccessToken = fileInfo.Path; + + return new DatabaseEntity + { + RootGroupEntity = BuildHierarchy(_pwDatabase.RootGroup) + }; + } + catch (InvalidCompositeKeyException ex) + { + throw new ArgumentException(ex.Message, ex); + } + } + + public async Task Create(FileInfo fileInfo, Credentials credentials) + { + _compositeKey = await CreateCompositeKey(credentials); + var ioConnection = await BuildConnectionInfo(fileInfo); + + _pwDatabase.New(ioConnection, _compositeKey); + + var fileFormat = _settings.GetSetting("DefaultFileFormat"); + switch (fileFormat) + { + case "4": + _pwDatabase.KdfParameters = KdfPool.Get("Argon2").GetDefaultParameters(); + break; + } + + _fileAccessToken = fileInfo.Path; + + // TODO: create sample data depending on settings + return new DatabaseEntity + { + RootGroupEntity = BuildHierarchy(_pwDatabase.RootGroup) + }; + } + + public async Task SaveDatabase() + { + if (!_pwDatabase.IsOpen) return; + try + { + _pwDatabase.Save(new NullStatusLogger()); + await _fileService.WriteBinaryContentsToFile(_fileAccessToken, _pwDatabase.IOConnectionInfo.Bytes); + } + catch (Exception e) + { + throw new SaveException(e); + } + } + + public async Task SaveDatabase(FileInfo fileInfo) + { + try + { + var newFileContents = await _fileService.OpenBinaryFile(fileInfo.Path); + _pwDatabase.SaveAs(IOConnectionInfo.FromByteArray(newFileContents), true, new NullStatusLogger()); + await _fileService.WriteBinaryContentsToFile(fileInfo.Path, _pwDatabase.IOConnectionInfo.Bytes); + + _fileService.ReleaseFile(_fileAccessToken); + _fileAccessToken = fileInfo.Path; + } + catch (Exception e) + { + throw new SaveException(e); + } + } + + public void CloseDatabase() + { + _pwDatabase?.Close(); + _fileService.ReleaseFile(_fileAccessToken); + } + + public async Task AddEntry(GroupEntity parentGroupEntity, EntryEntity entry) + { + await Task.Run(() => + { + var parentPwGroup = _pwDatabase.RootGroup.FindGroup(BuildIdFromString(parentGroupEntity.Id), true); + + var pwEntry = new PwEntry(true, true); + _mapper.Map(entry, pwEntry); + parentPwGroup.AddEntry(pwEntry, true); + entry.Id = pwEntry.Uuid.ToHexString(); + }); + } + public async Task AddGroup(GroupEntity parentGroupEntity, GroupEntity group) + { + await Task.Run(() => + { + var parentPwGroup = _pwDatabase.RootGroup.FindGroup(BuildIdFromString(parentGroupEntity.Id), true); + + var pwGroup = new PwGroup(true, true) + { + Name = group.Name + }; + parentPwGroup.AddGroup(pwGroup, true); + group.Id = pwGroup.Uuid.ToHexString(); + + }); + } + + public Task UpdateEntry(EntryEntity entry) + { + throw new NotImplementedException(); + } + + public Task UpdateGroup(GroupEntity group) + { + throw new NotImplementedException(); + } + + public async Task DeleteEntry(EntryEntity entry) + { + await Task.Run(() => + { + var pwEntry = _pwDatabase.RootGroup.FindEntry(BuildIdFromString(entry.Id), true); + var id = pwEntry.Uuid; + pwEntry.ParentGroup.Entries.Remove(pwEntry); + + if (_pwDatabase.RecycleBinEnabled) + { + _pwDatabase.DeletedObjects.Add(new PwDeletedObject(id, DateTime.UtcNow)); + } + }); + } + public async Task DeleteGroup(GroupEntity group) + { + await Task.Run(() => + { + var pwGroup = _pwDatabase.RootGroup.FindGroup(BuildIdFromString(group.Id), true); + var id = pwGroup.Uuid; + pwGroup.ParentGroup.Groups.Remove(pwGroup); + + if (_pwDatabase.RecycleBinEnabled) + { + _pwDatabase.DeletedObjects.Add(new PwDeletedObject(id, DateTime.UtcNow)); + } + }); + } + + public async Task UpdateCredentials(Credentials credentials) + { + _pwDatabase.MasterKey = await CreateCompositeKey(credentials); + } + + private async Task CreateCompositeKey(Credentials credentials) + { + var compositeKey = new CompositeKey(); + if (!string.IsNullOrEmpty(credentials.Password)) compositeKey.AddUserKey(new KcpPassword(credentials.Password)); + if (!string.IsNullOrEmpty(credentials.KeyFilePath)) + { + var kcpFileContents = await _fileService.OpenBinaryFile(credentials.KeyFilePath); + compositeKey.AddUserKey(new KcpKeyFile(IOConnectionInfo.FromByteArray(kcpFileContents))); + } + return compositeKey; + } + + private async Task BuildConnectionInfo(FileInfo fileInfo) + { + var fileContents = await _fileService.OpenBinaryFile(fileInfo.Path); + return IOConnectionInfo.FromByteArray(fileContents); + } + + private GroupEntity BuildHierarchy(PwGroup pwGroup) + { + // TODO: build entity hierarchy in an iterative way or implement lazy loading + var group = new GroupEntity + { + Id = pwGroup.Uuid.ToHexString(), + Name = pwGroup.Name, + Icon = IconMapper.MapPwIconToIcon(pwGroup.IconId), + Entries = pwGroup.Entries.Select(e => _mapper.Map(e)).ToList(), + SubGroups = pwGroup.Groups.Select(BuildHierarchy).ToList() + }; + return group; + } + + private PwUuid BuildIdFromString(string id) + { + return new PwUuid(MemUtil.HexStringToByteArray(id)); + } + } +} \ No newline at end of file diff --git a/ModernKeePass.Infrastucture.12/KeePass/KeePassPasswordClient.cs b/ModernKeePass.Infrastucture.12/KeePass/KeePassPasswordClient.cs new file mode 100644 index 0000000..199ae47 --- /dev/null +++ b/ModernKeePass.Infrastucture.12/KeePass/KeePassPasswordClient.cs @@ -0,0 +1,46 @@ +using ModernKeePass.Application.Common.Interfaces; +using ModernKeePass.Domain.Dtos; +using ModernKeePassLib.Cryptography; +using ModernKeePassLib.Cryptography.PasswordGenerator; +using ModernKeePassLib.Keys; + +namespace ModernKeePass.Infrastructure.KeePass +{ + public class KeePassPasswordClient: IPasswordProxy + { + public string GeneratePassword(PasswordGenerationOptions options) + { + var pwProfile = new PwProfile + { + GeneratorType = PasswordGeneratorType.CharSet, + Length = (uint)options.PasswordLength, + CharSet = new PwCharSet() + }; + + if (options.UpperCasePatternSelected) pwProfile.CharSet.Add(PwCharSet.UpperCase); + if (options.LowerCasePatternSelected) pwProfile.CharSet.Add(PwCharSet.LowerCase); + if (options.DigitsPatternSelected) pwProfile.CharSet.Add(PwCharSet.Digits); + if (options.SpecialPatternSelected) pwProfile.CharSet.Add(PwCharSet.Special); + if (options.MinusPatternSelected) pwProfile.CharSet.Add('-'); + if (options.UnderscorePatternSelected) pwProfile.CharSet.Add('_'); + if (options.SpacePatternSelected) pwProfile.CharSet.Add(' '); + if (options.BracketsPatternSelected) pwProfile.CharSet.Add(PwCharSet.Brackets); + + pwProfile.CharSet.Add(options.CustomChars); + + PwGenerator.Generate(out var password, pwProfile, null, new CustomPwGeneratorPool()); + + return password.ReadString(); + } + + public uint EstimatePasswordComplexity(string password) + { + return QualityEstimation.EstimatePasswordBits(password?.ToCharArray()); + } + + public byte[] GenerateKeyFile(byte[] additionalEntropy) + { + return KcpKeyFile.Create(additionalEntropy); + } + } +} \ No newline at end of file diff --git a/ModernKeePass.Infrastucture.12/Libs/Windows.winmd b/ModernKeePass.Infrastucture.12/Libs/Windows.winmd new file mode 100644 index 0000000000000000000000000000000000000000..d6498e8b3d83419201c7e2ab5542436b091096fb GIT binary patch literal 3345728 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

I$kWZBs zXDJ3tSc<14EQP)2<7%Fz7%E{Yo|do_!z8TdXDBh2;@MEHuK7rg<4Nc}I4g%M<_aV~ zBw^X@?FDyqSoT*WEc-+W%RWiMvQL(M%XSPQAHOXPz+t}0p*Ci()c9oeyNjeWHgTD8TP_C}kndzho zVkl|)jj(ro41U4HZ~a{fw?0e4t-lw_)ot~C3D2(&B&^lh64vS*32XI33F~k!Md$mx zP_8a1C2BvBaDV3eRB3VU&jJbeXQ71qvq-}ISxnKHyCf9UMtB{pje3@()IWq3ulQEX zQpTh-hxCDbB4N+JOmaJ}RxL3qSsqEX#Gnz*(-o3?Aax)sB`nt}3Cs0qC|B3mYA!$@pG3 z#_yXX!_K(31vg7>s-6%uM_yYbr;efb!yU+03HN84g!}U)C22v0#Gotk*)Cc3PeRZN z@>M8ym%?rBlyF<$NVu(C5^ifZCB|*-k#Jkzg<|!?+Sn&y&if_I`2a;Dm+wQlx+NU+ z5{+CUVa|sX!<-LOG)_4Z%GG;#RKk3Ik}#j2DcZwhp`L-I1j@;M`!>SNAI-taN!BztGM zIif!$XQm{3UtV%LU1>2k~9Q7Fc;D83VCjHvC7D@}bM;A|>p#8gIh<4@JM0 zaz(=5EBQ~t@3aS4mt%kU^#q(3_hHplOu}DFv3IUq48Po-DB&-&WRP%MNfK@=qlDYK zPQq;^OSmn2o6BvTzj~QTGTr}nLuLto^)icOyZ`Nm6bXOVHLHZb>zYlH1?xq3W8N>j zO3L#&P6C@JIL9KT6Y*S|S_vt(!@*3O~V zx?&yc4ecS`u3+X51P=k_ki(;vBC7`Hd{+#VkK+?@plC0Uj_ zDI{sW-^o3aT)UkVmbCCQz(ph#y%d#n#_A)R0WKyPiWygv;*zI)suGghkGPnU5`Itk zUP^f8w#1oRDwL~NHkS617H2z*gzd15V%QGt9YS~i%yw8#!glz8gzd1rgzd0`gzd1R zgzfM_3EN>M3EN?13EN>63EN>+3EN>c3EN?H3EN=}3ESaA61Kyd61Kxy61KzI61KxS z61Ky-61GEo3)Jd4HQCys%|AaZv36)uUt;ahq=CfRp-DrDwL_Ce5^IMhjV0C&P3&z_ zw{>fWCQT&P4o#X$tR0#(lkkhs&AmkPQIGylNsIHE%b2t{uep!;RB3Tub6ZGw&21^+ zHTQ7|ueq%xyymu+@S6LCm(*mt=32R)l-M=bq>aR`xh8ETcFi?u=Orq8doTEHZhsYy zgjeAX%Ezw4mQTlP^68{}cx8-)SH{kY;g!+etaE2BuZ&&2M86u=O=4F@+j@5|Q7L*T zA8tJoZoQ{sxOIDrFa6egdx^H*N5ZZ5^%8BppYq|AIuc%~`zwZ5>H!j7sRw#VP3Esr z50dcLs0W8)_bsiTaC6H~NxDJoYBfZX+BY#6ff^esc^@(s@^mD*6N6jum7ZaerI42q z^Ngf)5`JAh58ktrREH$u1(o5FCkMrX=MeLpq&MU;WP~KCT`b6k5zt6UZiszPW|Sn~ z7I$|sT2caHcNb$M^{#WzMxU3ofY`Iqv65R~abv4-lEM&+887M3CNbQf36j1L+n*OC z4{gDFCAfCKD0vjJ0P>RLY?WBB6!Nmqy^+v$p?^5kSUTcAUz;cC6~6iI!u*h+Un|Xn&b)GYuWxxm-K_!{=6o6^(l8Z z__|~k#E!)bNfo@8^bSh;hGbn|It>2dH#*EO#Huz09$@u9Q z2P2?Gp>mLKaKTVW0njERpQ4iVEz8 zbwrXp^YH{2^0DMW+>2&~ER|Hl*EUQ(k<7BWILI=|4lm0kJus_wD`Hki7C(krYsgAT z(V8x1l_WdPL5um6l5%@*_at<+v7RuFkgX_Gc#o5$XYQ*L^8rU$SaTWecee3`?}vG?CXA)u&+BMVPAJz!oKbg3H!P;683dxCG6|YN!Zu@ zDPdoCUc$caFA4j)3ljEqe@occ{Uc#tcTvK=?vjLk-DL^;x_>3?>#j)H*Zn79Ul*Vc zvb(_zG4^#a3H!PP3H!Q4FF(iG*JY5fuS=rD*w6Yn-G=mMwIWRdUdeo3cq}&v!A|B?bJON;xDS_z~Ilk{wuw zW_RW{NM7(;&na2uzbR+W>D@k0^>UM>z#^B=&63i7%y^3=|5_K5OY+7MC%Gl%K6R4E zOLVVpYYW^x|9$tnYf~hZA$D!LO)+_Jmon@TtkRK`fQ*COE~)frLa+vsPtp?d1>_D% z&VvcTX4Fr9NfF38$eofMRTIN!{RO;ih!uYp_iQ!ry(Y<_0l4#OgYPv-QicY>`w)B1 z>S{In53cM*B=@dzQdBZ+hm&HGJ9jxL?j?HLw1k(m_?>gy9d#t7Aa+MxQu$n5;PScG zOH}iv{->nH7iCQd@?rlOlNMhEv14(cw!WmQI~Mm#HbCrHl$NwBoDd`@_8yL#*tzB)4XE zF|{RyAQofK?OaKFe2;HvH^JArCBq;&AoV08(N@pn=sYZ$46#u_123t`6Slh6(ok~A z_ayez(6#6J+gwaz$tWN5h~yhT`_x1-%1cwp8(x}8?)Izqn@cY2aoc)SlFi3FCdu#j zxrL;RmzI(qeACTKbQahXI4jq+k`^yAKOvZmI=nWf^);y|Y4OD^@qTV|{5qSD!BcX` zUyvsx{qW63i+NHq8e%bRBz0T55m{SFdx*99c9Opn+!M_9lFW&TVN3@}FN|DHA?J>g z(GWZCoh9eyx|lAK%=27KH%hEHN?~8X=uU}uddZ!Yk&J-Y9`^7tsmUDrdANM|zEdDy!*GqJ~`guu<@7{-AAA1#k@SRYhuCuw5Dd}V8N$U9f)%ld*T=|5c zJ)R0JkbH!rW3tFgbPg{0pW=GaBF!*!a!t%fUee-aci@+e(Uu~q0kQMqW5v|RRnKCU zN}hmN%qNokzDHUnso{I1<&vc5+??kM$qkTL9CIm>-}~bCop3g-lEnJC{&2OVa97M4 zpv^NDSA4&61;++|2D3Np{RWSs%Vt@^qb8a1QxwlZ=5_Nw<56?i_YVc<1ny zO=eeBsNZR-}_`dZLwbk#uq{UPG z6*iLG{tCNSF?J7-6*aa`IdAb-tNqvHd_bwn_H)NUa_swGV!jUk66v%1BC;@HC@lBJM>kRK)M#=9%^QOQ1tZR;n=i5FeW&ytM( z+&LyGa&Kag6FH|z#^s3xxgozura|&Sj!XJs++=6duac1vYd#qJ-zyB}utS zuI4XG8ba*o{43!(ctyf<(B`1+Djc1IF$vGX1PRZf50af4*f1Q(N2atdNGHjnT2=cBPM z<|fHLh{fD2$>3vdkreVVxg_1jxqNaaGO#}(h=?O8RT=HB+aL~U&1#oOH111c59!?NP6Gm z#O`a|KF>t!wVcaI8v0ZZNM>RbP&g64VkTJvDGsS1`NGFklzi)B9+XVF#@D z%gc^eYI1ciZ6w$GQAu0LY2Wv@la%!_?InB{xP#=Eb*{(mD2aLLBx!Th#dMZ@?PIz~ z_)c+GN!I-?RX52A|L%5oNk*S@56Ss`E>%y-Wq;?{OLCJR6ZMuH^D%uSclx{2zLGM2 zTm2}(D>&bKV3gEfvKTTDGLVvd$X`7NQ-aMnmo3#(k~D~=8cInX?5~W&D8V>fn}QXX zE0D~BTn8CWNp9v-ji3a{KGjIc9X{1)O7c*DU4EVtT*0-;wl!9AC$2=ct?`uP20qmb zl;B~X>P1O!h^2a&l03(!nn(%WLe93WNs{G$Tdz`*=Y8mo=~PNk7*{k)l`5$Lv29JK zB!A^oy-o>c`&2U|YkjIWDaj2!a@(3o2@-Hc8;+y%wj?)X9OPX}atWX6JxVYM*GfzE zzGODUQq86$_x7niqy%k|^JK)#mGp$nfXt^P7xZtijU|-iV;{S6 zeM||KqKC9PTq@ZGu~f^vM0Z~+yx?7!1UIi0No$CWMpr7P=T^4{W|d?l#MZ8SDw({+ z#jKXhhFHuRNt1o}QbH5lVMy9Reub=+*r+Y@qZoHfI!wU&D9CzAKgeU)!!IbY4o|un z&JB`Ucm3b8?11|P9LA(3H}W$ukqlhqYIUQ}CpGyxFPkK%{HS)bm#9{^aO?5)kGL^@ zB-Tc!LSCULM+{&x3C7e0gDdEh? zHxiC~cS$(%-7VqBcaMZ4-)|)x`F`5{{0~NH{t^E8*z)oP?v}KP4O;pOcBH`%xKM6<2L3f;Xf!#H6bR3g#betfW z=x4eTC7h4SAmIo)Nx~6yMhQpI*GV{nPL^;49hYzfok_wGbY?G6ZL1FSF%btyOK@9_LE)0_LD=hwX}QIf4yWc#GcgLAQ_D(`}V9qr{r~rJzu_2 zQrPEnljH%P&&`tNKA&48J$*j8BzC>1f-^U_r0J_}J}Qr-4aBY&w|UtR>kYBl-Mo^b z=mBhnKqR+#$)}j7@N~dp@_R{5Zn42#FA7Nb9QG~=+v?qt3VvNdK}nBQE}ue@0zT#* zNqrwv*h|zBig<~>5fll(5mZz$%kiasn@=bv`5a%H855i)szp{lT`N- ztqiH5d}6q(dk_P5=5xt65f2CO4Ss@jrAAyXlBB%>-N z1o=>7btTgwb}rYG)W^M+%>X|vdDP#1HSm&}oa-akdK*eU{?bV!$qqjc*;w+fUqkhX zB-y8G;w7r*re4zGYa8O%!%?e|Y=aztG*doReIMTZntUErKHW#hg1=FRkvt9g4)U1t zS>~TawD1xgua?T^1l}yOS))k)g4~EZw8s@w7yYxvw30N3SWIh4(R1!i(kCPpAr|wb zq}W*((?;?j#A4b??3dSUTkRzFOJ^3-UQ%ulp1h$Ybdc16*tyfm%ZAvh!FXzoYiVc6 zmtML`S|gRkboY{)oZp{cJtbwl^pfoGBbVNiPrUSz*zdlr!ZGdZC29%%yrji@Rd@YZ zBqJf#kM&o~%!ckv86a5zv6z998E?CDXOLtr#9{_ZtdB~@{LfQfa8KfU;~|o-{hY&4 z$xpt`KkX$d#V{{v@p6sb@rtAY#E#c9iYeC1%^yB1sSL50;gY78uxbd$^f^fz$jy)u zlDN-jq$H2eXO!e?^z)^VYP94>ND;^w3D+z=FX5V{v68RQV_P3JPLc+(S{*Oh@Un}U zAUOoFm=`2COQ!iZus)FFIEE+WIPSA0cS62|%#rlK8`f(eA4tY@3&B{iA#JYO=$ zuN+<=xx@EJ3zg5aZCo8Ll1zkH{VbL&{SePjv8^SNjS#z@ek5s#Z*W=6$C9=Xi&-i; zJjBh(d?GmyvDt%VlC1M%!5NfvxuhiI7sv{Ujac_VR!TTx{nSfp@&kw65nL@v_`=B= z$wc4gKa;$<*2S!q_6jqFXTr_mND)qAC=q=v8%&RlBVCf>-*1=ju4AECYkSJ(j@DA%rBC=C%A1L zmpllue14UzXyvZdCnVb-*2Yds>V4tv(0-G2hgi(-lJW%;f&`TGl;kl;GUT*m)C7E+ z4r}NBkW7MDKXz8KyD#2!L(DnJuMmqlPl+9Q+>Ooul5no+g5>K~Zbm;6OJ%d;e=|n2 zPZuR@y_Y0xy_Y3yz5hx^`gM#~B-8ym#{VQO@Rc4ryMrG17ueTzo`Tr1h)H_l>D#TS zu>{Ek|2!@OCDw7D`vtcoN_=*8H{Qu8`3PcX%5{>xzE4P&oPgNeMO<>+mm-toiZ4ZG z$!4s<`xoP;ERutezaS}+%6MXECCw^n1hJB4lVrfLu$b(Un;{mHgAyzIr<={VUQ*y1 zX^GYH0cPW)m|Tlv!Dt-i8uOfD~}$+LI4Ye62#5Pwf{tK=qs zw|tu<$*0OI`N@wWZ*W^?3e@fIFGbSx=z4348N$%CQtT#ReDMe9Fa-W1f$^BkZlX(OwQMpR{m}@01 z{&Oc+QpTjk-&}yc7ads{ZY#b3Vm(_q$zxS<_Jbafbb?q+1@Re)a|5TKegIG)@ zN~}yf64U{^}XPC-~9O%$p`*C zZQx^~vN!ynk`|wVLSf1wU`$&4Lx|n4Hqy41`1kA@Uz5)xUee;6nT&)plT8#;t*`43 zn@So&tX7*zY;Mr*$C^txH~5&u<`^xeg@kjAk9*k=E9>v0T1oaGXS-gs_L7>+8O0|g znfwZYC%r^<*v3m*{O0Md#v-``Vl~!QF|V$U1$}S`+ezMl41%^pwW^`5Q}+UGIYXhahL!o&N6nE;fmk2)p_gbhI#-?s zixk6>E|##QOC+4d`bffAtdAwF+qpTSrILXVJ99sg%s+r{#h{HXla$B(yvcG&62?0R zAS)#APKX8DAS)#+A%NghNy6|T*accGxfNnDpHX6)j^LL=TVTBnCC+imItj-q>m?kg zd@kWQ_a1X?&;KKt0>?>X40?M6vuh#mKvBsB}WE85MHmJqw5-6AQFGZsw5QO+eP21$kFmMra$QFu$d z`6Af>v3kDEOVmH-m9T%lT{6+n$>ftfe!v}zJ0wH=Ihfx|REKwZ!JGag-1!xWjo+=U z7EnGM9p4p-eJ9YqFk@eBy<1{mm@z3Ru`kS+6q48%W=!sp*cWC@3QOz@GbTkO_JtXf zq7wVUj7c$xePPC=xWv9NV^Tt5Uzjl|DX}linA|I|FU**flGqn!OzxA|7iLWEm)I9( zOiD}a3o|BVB=&_Fld=-~!i-5diG5+lt)}>aHOxlZn4sD~lWG2Mciqw!i|Fj!7JtUa{ zu`{KnBx^2rHr0|8fLKgz$)vLhfvwW5Bl!SgXG%TE63jw`YjPy(Ar{j>avjE2`!OTb zP;#>$Z8eru!yK&D{3DWm7;%_1kvzQ4NmEI~eNLKDk`|yg7UFm{r=+aA6MZjA@t9-} z#8$Jmkc`2|#kSQ_(#F@}UjijvK z=eCrvjV;GMw+rR!Z{>CLk{0LwM8f^)#F!ZOr?Z6n)5S|_vh9!E6?Bu>{+M+467^9% z(uuV(3fAiTw%XIj;I|ijTkR!bTkS1jTkRuZTkR`hTkR)dTkS7lTOA-_TOBB2TOBN6 zTYXBxwmMY8w)(V$ZS@%mThy}>wy5D=Qj@JkS*<=Nu@-JJLSil4WTeDexXCDqwQ!Ts zp;*s980VnP$&8VVhS-_=yo9Y~tc0y)93|<{qqsZ6Suj4GT!OqH8HeX_MT%k-AVuwY zqJ-^vQYcsVev`dKtv3?x&#R1yaetlFJk5@mF05?vOr?_q(K&^OJ(^iO(&MmGNwwYbVow) zGH!QPQj$hicJoN9B(|;7NacjJYB{e7W9(TZ^I1!YF`sqJC*|Be^t)~F4vQoMdaaCj z!nvN3ROFvnPz&<8q|yWU8ZBglq(E7$VSsFu6zSvNE0Hw!vQ<*7KrCp0RNEx>o1J#2 z_oXEG7ck`$|%(d}4614%RtHPBnG=vr{tRKX+I8jif*J z$F{yp(zat_SUssIjbKe^}cd32PHX|xR^tdXRtpuGkI9Dxs)quB)xOFm?MfgiCQg( znvW#bKNhYdJ{rXocdg|o$v-7rtsawPE9oRna-58zxyTZ z{Yp#N`<0Qf_bV%5?^jO3-tPekd%yA$_I?#4?ENZA*!w*wVeeN-!rrg4guP!C346b) z683)8B<%gFOW6C>kg)fANW$K)ri8s;EeU(SNZ9+;Q4D*ex)SzCl%HelA?x{=pJVJH z>w8HJtcSdAGw#4bvAg#%Sb1gd<}{`x6~SFWR>V9a8Rey^W>f8+ZMY;~*2M7Ll;mw;CcGa{ynNgBOncUs`Xi1Ut_+l-3+A)-*fmpjc7xFwMg{j6$ zm};D47*;yjn;+vTN$bnIJE94ahc~*X_L7vISl?px6G^vEoV>tPDPOF0@8!HGsr7`L zBYH_P)u(z{Qogc_c}0@Hij#?yq%?ff@>kT@B*}e$xjmc`3VVPvh@qs#7&A3p%-@Ki zL@}x9V*W!6C5oArE+*raAV@DMkm)`qO7&VOc7J#bGr%vS=ACe#b0XE7O2wGB(rqgb zV%}vwDTP|Q`k6&ZN=rx#@+0Ow$*!!iV90&=CWfR1QrWD?Y{`18Ys!b#JBO0O?+kn> z;d^a!C46IUo`mn&&6n_vxdjrw`?iph^vt7fh4!LQSQ`aVip5^=1=D*$9 z9Z9bO?wyF$iuuOBWwS=I(5L!L($uF~D_QMRt)nE}_I50=H;~p#CLoo)3HU`Qtmo?3 z=Z#+QE8Bisn{uu!<+Yd_Ge+{lYwqamRz8`I;u~ey)}ByUV|7tu-+D=l zGoSAy%x5np#(egr%cn8&*`H1xgB(aFPeQ(DK5_QyKSz%wHhjAea!JyvZY+2Ya#_;0Nn)@767?jPV!x|#PI5nEIHLO7A!-~nIw&{Kc7KTBo|Q9t&kiNo?kg7 zJS!vNSs97-koG(`5}OCGdcKkQq|EiTdXuCq&Psdl?Pf}nonPN!e{PZ7IVB;KT#|O5 z{F&@Dp z5+1=wc-$%OTA2mgqD1>#RQa%^#k{yww*H5L-)Ho9r^S_ucc&#JygQYsRwb&{lIgZ} z1NJbz0it@QH6zfHQ!I@(B>X9sd(#idbDOuY|X-blvUv?EPBUz4ACgmh` zk;>jjD^E%JaFNTWf~55f_oiD#$$G@tR|OuFeBz}NC20%9a;_|S2xs>lD0>x(?)|Dt z>>ddj2h}LyJzI82^-$Qt3n8BxUZNxT5GBSVSX06yDAAo>B;0x}rLsFT+j{L#toM5c zb!gXuIzGm=s1n#83fAD^J>PtYsmFZc-SZ>_*FT2$FDNnQQ$JlkWswgh`dyfYOcm$3 z(}<#Tr?G_RP9!{cC~luCV_TH8@QofTSCe%6QwuSaD5hDun1+a5;~@Lrq6w3giI$eeZ3rV@`%KDU*j_%!qtCNM=Cnjn$5l`dFiA@9%b! zG>6z*ww)!zzQc6@$D)g5ETjaatE3H9S=f7=-6Z`X_ReT`N#PWCHuaE{hgf^=Daq@< zsL)GN)PGT-x8z7A_oieY$puIWG3bRl93Uw^E*8oV zi9Nq~3Ng=8QjXz%BOme^A-Mo40U0T|UVJF{34)-JKBF+o)qC-$4?vuJHL# z*tghOF!p~+TAW9jF?i$2ALVgARa%@!dAx*2d4hyT`2`7&@{1B4<(DKp$}dZJlwXnX zC{L8|C{L2`C{LE~D8DMaoMLtRhup`;JQdXl-4E103mhf>Uw3_b1U z$L34EuIYNn1(GSg&s-?k`ICFUV3DL0MlRNiE|zS1#=Rf6M6w@(3tI58q#EA2z7Zu| zDrpX}m}Qitp_lOW&e9mYNbbQtTQ9m&vcT`*r;;_k6ssj29&zPbBiZ?{+vm?D_9puT zw2iftlm&i0+j>cPtkkttXP--2VKuMG7n1#c)#8RwuI?o^dchrt@3A9cOW33swuH?R zwuCJbwuG$`wuEgGwuCReq$XRrY=zQxiIvM_hs4Td@|DEOW%6|>c7?5uajqTrostF+ zYnk6j_FQnScNZl*2dAQ*cS{!F>hKoiJ4(uJ$?ghrKr#qz%-(AGUQ#!&Th;l4WllaE>B?2L1K4k_7%P(lKzeGP8PO)R5A*(5OR!?Qn-#Q>2XOrv|KCuuau;B z@ePwtkm`iwu7!!gYRGSrQ&@#!_p7HVDJ;bwl9#ZieJe&}XC&|VRqp2`kLPgt{7DIG zbrZIAo|0nMl-sbE|48hbawp_sC|9@4OJ3ZS$WleZRF|1gxUHSo*1r;4Z`vHQD?uOp z!{4T)>6>GrB!q(RXNGH1hvVJ93=)em$*5F)4`4MWy4qyPd$?9wjb);wticm8dmfir zvIEi*l0{Ohw|h2`B54ZAj(oC8j$t0sa?U2X46&H(l%(pIy|fivIV8hxb}Mjhko_;8mD0yX&yUN@o`R3nPa0D@tG%T1HoQ32GW9&-A z+PG7~+9)7lZQLbcZQLzkZ4{KSHVRQ>H@)E1DJ}=^({ZOpc#c}Pfj2I`@>P#MnR8kE4`Kpu@ zUcahSlJ0EjuEI4!x%y0b$jgXZhT_)>24L%yO>ujg(*oP7#e7o64Z%8noK1BkuS4t_ zSC5i31~b5Ik?LVeO0oP{p@dWoB=+sca*&3SLUGr68%dgVbJw`Wk}1EqUh5G_c8o!+ z*J>iEIl!fADv7UishUYnqg zmXZ@aTrcssWL%bnpfhTtwd94S_|-Y|*iTBDWJn16=eCj+wcJ_Rfs)eDUw5vOBd(+! z6~kvYog}=1be7m#09LLp66>)|x>9tN=_cV-riUazjrBsgdZm-WkUo-4-zNmmLi$N6 z;<_^i(mxby&n9m`1}i4^enRju0rG4*`4%!Fo%{kBBN>i)$bTT?CHLUD=ZL0w zds<@ab`rnD7k?xxTi~ZF(DzQ1T)_Qd7Q{>n#rD}GH{?}`?Q)O7LyWO_QO3z?Bl zT0v$?Y@d5UW=U+H&n(CNv&8oKDa6c?*glVd%nilvWbW!73%}f9D!KO;!sf1WXq=wVwOlOA55$TA4@DBi&-kMd@N>JDAtcP#}#cH_HenRTh+wi z7061a3M^(inw}o#a8tBFN{Il-)kn21=5xrn6KV zCGCBx&6Jc*AG&RAr6k$l8-1kUwEQtjjAm33^=I(K+_EC~-Ri15YzhpYZw)H(F{1=2B-!e|$5-Jgm82J>1LPPbrKx}0>laFrt@pES9hbC* z*tSkkQil3B!hWM9*-E+Pi1}TT9kLE`nv$~3k7dqKk~X$Z47MTWtmFV>H{?%B%2&Hw zx&ERg*^0Y^h`A7ooduS&jTHW&1eWs WkE&gUSPDan?zrMf~1Ea!g_^B*PI+Ox&P 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ℜ_U+u)kkL6ab@TO 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ⅇtr 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

f<>PiIcI@ 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 literal 0 HcmV?d00001 diff --git a/ModernKeePass.Infrastucture.12/ModernKeePass.Infrastructure.csproj b/ModernKeePass.Infrastucture.12/ModernKeePass.Infrastructure.csproj new file mode 100644 index 0000000..a4a5bf1 --- /dev/null +++ b/ModernKeePass.Infrastucture.12/ModernKeePass.Infrastructure.csproj @@ -0,0 +1,24 @@ + + + + netstandard1.2 + + + + + + + + + + + + + + + Libs\Windows.winmd + true + + + + diff --git a/ModernKeePass.Infrastucture.12/UWP/StorageFileClient.cs b/ModernKeePass.Infrastucture.12/UWP/StorageFileClient.cs new file mode 100644 index 0000000..4337d42 --- /dev/null +++ b/ModernKeePass.Infrastucture.12/UWP/StorageFileClient.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices.WindowsRuntime; +using System.Threading.Tasks; +using Windows.Storage; +using Windows.Storage.AccessCache; +using ModernKeePass.Application.Common.Interfaces; + +namespace ModernKeePass.Infrastructure.UWP +{ + public class StorageFileClient: IFileProxy + { + public async Task OpenBinaryFile(string path) + { + var file = await StorageApplicationPermissions.FutureAccessList.GetFileAsync(path); + var result = await FileIO.ReadBufferAsync(file); + return result.ToArray(); + } + + public async Task> OpenTextFile(string path) + { + var file = await StorageApplicationPermissions.FutureAccessList.GetFileAsync(path); + var result = await FileIO.ReadLinesAsync(file); + return result; + } + + public void ReleaseFile(string path) + { + StorageApplicationPermissions.FutureAccessList.Remove(path); + } + + public async Task WriteBinaryContentsToFile(string path, byte[] contents) + { + var file = await StorageApplicationPermissions.FutureAccessList.GetFileAsync(path); + await FileIO.WriteBytesAsync(file, contents); + } + } +} \ No newline at end of file diff --git a/ModernKeePass.Infrastucture.12/UWP/UwpRecentFilesClient.cs b/ModernKeePass.Infrastucture.12/UWP/UwpRecentFilesClient.cs new file mode 100644 index 0000000..7dd750a --- /dev/null +++ b/ModernKeePass.Infrastucture.12/UWP/UwpRecentFilesClient.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Windows.Storage.AccessCache; +using ModernKeePass.Application.Common.Interfaces; +using ModernKeePass.Domain.Dtos; + +namespace ModernKeePass.Infrastructure.UWP +{ + public class UwpRecentFilesClient: IRecentProxy + { + private readonly StorageItemMostRecentlyUsedList _mru = StorageApplicationPermissions.MostRecentlyUsedList; + + public int EntryCount => _mru.Entries.Count; + + public async Task Get(string token) + { + var recentEntry = _mru.Entries.FirstOrDefault(e => e.Token == token); + var file = await _mru.GetFileAsync(token); + StorageApplicationPermissions.FutureAccessList.AddOrReplace(recentEntry.Metadata, file); + return new FileInfo + { + Name = file.DisplayName, + Path = recentEntry.Metadata + }; + } + + public async Task> GetAll() + { + var result = new List(); + foreach (var entry in _mru.Entries) + { + try + { + var recentItem = await Get(entry.Token); + result.Add(recentItem); + } + catch (Exception) + { + _mru.Remove(entry.Token); + } + } + return result; + } + + public async Task Add(FileInfo recentItem) + { + var file = await StorageApplicationPermissions.FutureAccessList.GetFileAsync(recentItem.Path); + _mru.Add(file, recentItem.Path); + } + + public void ClearAll() + { + for (var i = _mru.Entries.Count; i > 0; i--) + { + var entry = _mru.Entries[i]; + StorageApplicationPermissions.FutureAccessList.Remove(entry.Metadata); + _mru.Remove(entry.Token); + } + } + } +} \ No newline at end of file diff --git a/ModernKeePass.Infrastucture.12/UWP/UwpResourceClient.cs b/ModernKeePass.Infrastucture.12/UWP/UwpResourceClient.cs new file mode 100644 index 0000000..6ddf960 --- /dev/null +++ b/ModernKeePass.Infrastucture.12/UWP/UwpResourceClient.cs @@ -0,0 +1,17 @@ +using Windows.ApplicationModel.Resources; +using ModernKeePass.Application.Common.Interfaces; + +namespace ModernKeePass.Infrastructure.UWP +{ + public class UwpResourceClient: IResourceProxy + { + private const string ResourceFileName = "CodeBehind"; + private readonly ResourceLoader _resourceLoader = ResourceLoader.GetForCurrentView(); + + public string GetResourceValue(string key) + { + var resource = _resourceLoader.GetString($"/{ResourceFileName}/{key}"); + return resource; + } + } +} \ No newline at end of file diff --git a/ModernKeePass.Infrastucture.12/UWP/UwpSettingsClient.cs b/ModernKeePass.Infrastucture.12/UWP/UwpSettingsClient.cs new file mode 100644 index 0000000..612f1d7 --- /dev/null +++ b/ModernKeePass.Infrastucture.12/UWP/UwpSettingsClient.cs @@ -0,0 +1,31 @@ +using System; +using Windows.Foundation.Collections; +using Windows.Storage; +using ModernKeePass.Application.Common.Interfaces; + +namespace ModernKeePass.Infrastructure.UWP +{ + public class UwpSettingsClient : ISettingsProxy + { + private readonly IPropertySet _values = ApplicationData.Current.LocalSettings.Values; + + public T GetSetting(string property, T defaultValue = default) + { + try + { + return (T)Convert.ChangeType(_values[property], typeof(T)); + } + catch (InvalidCastException) + { + return defaultValue; + } + } + + public void PutSetting(string property, T value) + { + if (_values.ContainsKey(property)) + _values[property] = value; + else _values.Add(property, value); + } + } +} diff --git a/ModernKeePass.sln b/ModernKeePass.sln index 9c214f3..e1f0c0b 100644 --- a/ModernKeePass.sln +++ b/ModernKeePass.sln @@ -5,12 +5,27 @@ VisualStudioVersion = 14.0.25420.1 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ModernKeePass.App", "ModernKeePass\ModernKeePass.App.csproj", "{A0CFC681-769B-405A-8482-0CDEE595A91F}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ModernKeePass.Lib", "ModernKeePassLib\ModernKeePass.Lib.csproj", "{2E710089-9559-4967-846C-E763DD1F3ACB}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ModernKeePass.LibTest", "ModernKeePassLib.Test\ModernKeePass.LibTest.csproj", "{0A4279CF-2A67-4868-9906-052E50C25F3B}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ModernKeePass.AppTest", "ModernKeePassApp.Test\ModernKeePass.AppTest.csproj", "{7E80F5E7-724A-4668-9333-B10F5D75C6D0}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{107C7C00-56F4-41B0-A8CC-0156C46A3650}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "solution items", "solution items", "{3779FC26-0435-4823-81F5-3F27A525E991}" + ProjectSection(SolutionItems) = preProject + PRIVACY = PRIVACY + README.md = README.md + UpdateVersion.ps1 = UpdateVersion.ps1 + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{0B30588B-07B8-4A88-A268-F58D06EA1627}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "presentation", "presentation", "{C7DB9A6F-77A8-4FE5-83CB-9C11F7100647}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ModernKeePass.Application", "ModernKeePass.Application\ModernKeePass.Application.csproj", "{42353562-5E43-459C-8E3E-2F21E575261D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ModernKeePass.Domain", "ModernKeePass.Domain\ModernKeePass.Domain.csproj", "{9A0759F1-9069-4841-99E3-3BEC44E17356}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ModernKeePass.Infrastructure", "ModernKeePass.Infrastructure\ModernKeePass.Infrastructure.csproj", "{09577E4C-4899-45B9-BF80-1803D617CCAE}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -47,46 +62,6 @@ Global {A0CFC681-769B-405A-8482-0CDEE595A91F}.Release|x86.ActiveCfg = Release|x86 {A0CFC681-769B-405A-8482-0CDEE595A91F}.Release|x86.Build.0 = Release|x86 {A0CFC681-769B-405A-8482-0CDEE595A91F}.Release|x86.Deploy.0 = Release|x86 - {2E710089-9559-4967-846C-E763DD1F3ACB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2E710089-9559-4967-846C-E763DD1F3ACB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2E710089-9559-4967-846C-E763DD1F3ACB}.Debug|ARM.ActiveCfg = Debug|Any CPU - {2E710089-9559-4967-846C-E763DD1F3ACB}.Debug|ARM.Build.0 = Debug|Any CPU - {2E710089-9559-4967-846C-E763DD1F3ACB}.Debug|x64.ActiveCfg = Debug|Any CPU - {2E710089-9559-4967-846C-E763DD1F3ACB}.Debug|x64.Build.0 = Debug|Any CPU - {2E710089-9559-4967-846C-E763DD1F3ACB}.Debug|x86.ActiveCfg = Debug|Any CPU - {2E710089-9559-4967-846C-E763DD1F3ACB}.Debug|x86.Build.0 = Debug|Any CPU - {2E710089-9559-4967-846C-E763DD1F3ACB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2E710089-9559-4967-846C-E763DD1F3ACB}.Release|Any CPU.Build.0 = Release|Any CPU - {2E710089-9559-4967-846C-E763DD1F3ACB}.Release|ARM.ActiveCfg = Release|Any CPU - {2E710089-9559-4967-846C-E763DD1F3ACB}.Release|ARM.Build.0 = Release|Any CPU - {2E710089-9559-4967-846C-E763DD1F3ACB}.Release|x64.ActiveCfg = Release|Any CPU - {2E710089-9559-4967-846C-E763DD1F3ACB}.Release|x64.Build.0 = Release|Any CPU - {2E710089-9559-4967-846C-E763DD1F3ACB}.Release|x86.ActiveCfg = Release|Any CPU - {2E710089-9559-4967-846C-E763DD1F3ACB}.Release|x86.Build.0 = Release|Any CPU - {0A4279CF-2A67-4868-9906-052E50C25F3B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0A4279CF-2A67-4868-9906-052E50C25F3B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0A4279CF-2A67-4868-9906-052E50C25F3B}.Debug|Any CPU.Deploy.0 = Debug|Any CPU - {0A4279CF-2A67-4868-9906-052E50C25F3B}.Debug|ARM.ActiveCfg = Debug|ARM - {0A4279CF-2A67-4868-9906-052E50C25F3B}.Debug|ARM.Build.0 = Debug|ARM - {0A4279CF-2A67-4868-9906-052E50C25F3B}.Debug|ARM.Deploy.0 = Debug|ARM - {0A4279CF-2A67-4868-9906-052E50C25F3B}.Debug|x64.ActiveCfg = Debug|x64 - {0A4279CF-2A67-4868-9906-052E50C25F3B}.Debug|x64.Build.0 = Debug|x64 - {0A4279CF-2A67-4868-9906-052E50C25F3B}.Debug|x64.Deploy.0 = Debug|x64 - {0A4279CF-2A67-4868-9906-052E50C25F3B}.Debug|x86.ActiveCfg = Debug|x86 - {0A4279CF-2A67-4868-9906-052E50C25F3B}.Debug|x86.Build.0 = Debug|x86 - {0A4279CF-2A67-4868-9906-052E50C25F3B}.Debug|x86.Deploy.0 = Debug|x86 - {0A4279CF-2A67-4868-9906-052E50C25F3B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0A4279CF-2A67-4868-9906-052E50C25F3B}.Release|Any CPU.Build.0 = Release|Any CPU - {0A4279CF-2A67-4868-9906-052E50C25F3B}.Release|Any CPU.Deploy.0 = Release|Any CPU - {0A4279CF-2A67-4868-9906-052E50C25F3B}.Release|ARM.ActiveCfg = Release|ARM - {0A4279CF-2A67-4868-9906-052E50C25F3B}.Release|ARM.Build.0 = Release|ARM - {0A4279CF-2A67-4868-9906-052E50C25F3B}.Release|ARM.Deploy.0 = Release|ARM - {0A4279CF-2A67-4868-9906-052E50C25F3B}.Release|x64.ActiveCfg = Release|x64 - {0A4279CF-2A67-4868-9906-052E50C25F3B}.Release|x64.Build.0 = Release|x64 - {0A4279CF-2A67-4868-9906-052E50C25F3B}.Release|x64.Deploy.0 = Release|x64 - {0A4279CF-2A67-4868-9906-052E50C25F3B}.Release|x86.ActiveCfg = Release|x86 - {0A4279CF-2A67-4868-9906-052E50C25F3B}.Release|x86.Build.0 = Release|x86 - {0A4279CF-2A67-4868-9906-052E50C25F3B}.Release|x86.Deploy.0 = Release|x86 {7E80F5E7-724A-4668-9333-B10F5D75C6D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7E80F5E7-724A-4668-9333-B10F5D75C6D0}.Debug|Any CPU.Build.0 = Debug|Any CPU {7E80F5E7-724A-4668-9333-B10F5D75C6D0}.Debug|Any CPU.Deploy.0 = Debug|Any CPU @@ -111,8 +86,67 @@ Global {7E80F5E7-724A-4668-9333-B10F5D75C6D0}.Release|x86.ActiveCfg = Release|x86 {7E80F5E7-724A-4668-9333-B10F5D75C6D0}.Release|x86.Build.0 = Release|x86 {7E80F5E7-724A-4668-9333-B10F5D75C6D0}.Release|x86.Deploy.0 = Release|x86 + {42353562-5E43-459C-8E3E-2F21E575261D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {42353562-5E43-459C-8E3E-2F21E575261D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {42353562-5E43-459C-8E3E-2F21E575261D}.Debug|ARM.ActiveCfg = Debug|Any CPU + {42353562-5E43-459C-8E3E-2F21E575261D}.Debug|ARM.Build.0 = Debug|Any CPU + {42353562-5E43-459C-8E3E-2F21E575261D}.Debug|x64.ActiveCfg = Debug|Any CPU + {42353562-5E43-459C-8E3E-2F21E575261D}.Debug|x64.Build.0 = Debug|Any CPU + {42353562-5E43-459C-8E3E-2F21E575261D}.Debug|x86.ActiveCfg = Debug|Any CPU + {42353562-5E43-459C-8E3E-2F21E575261D}.Debug|x86.Build.0 = Debug|Any CPU + {42353562-5E43-459C-8E3E-2F21E575261D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {42353562-5E43-459C-8E3E-2F21E575261D}.Release|Any CPU.Build.0 = Release|Any CPU + {42353562-5E43-459C-8E3E-2F21E575261D}.Release|ARM.ActiveCfg = Release|Any CPU + {42353562-5E43-459C-8E3E-2F21E575261D}.Release|ARM.Build.0 = Release|Any CPU + {42353562-5E43-459C-8E3E-2F21E575261D}.Release|x64.ActiveCfg = Release|Any CPU + {42353562-5E43-459C-8E3E-2F21E575261D}.Release|x64.Build.0 = Release|Any CPU + {42353562-5E43-459C-8E3E-2F21E575261D}.Release|x86.ActiveCfg = Release|Any CPU + {42353562-5E43-459C-8E3E-2F21E575261D}.Release|x86.Build.0 = Release|Any CPU + {9A0759F1-9069-4841-99E3-3BEC44E17356}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9A0759F1-9069-4841-99E3-3BEC44E17356}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9A0759F1-9069-4841-99E3-3BEC44E17356}.Debug|ARM.ActiveCfg = Debug|Any CPU + {9A0759F1-9069-4841-99E3-3BEC44E17356}.Debug|ARM.Build.0 = Debug|Any CPU + {9A0759F1-9069-4841-99E3-3BEC44E17356}.Debug|x64.ActiveCfg = Debug|Any CPU + {9A0759F1-9069-4841-99E3-3BEC44E17356}.Debug|x64.Build.0 = Debug|Any CPU + {9A0759F1-9069-4841-99E3-3BEC44E17356}.Debug|x86.ActiveCfg = Debug|Any CPU + {9A0759F1-9069-4841-99E3-3BEC44E17356}.Debug|x86.Build.0 = Debug|Any CPU + {9A0759F1-9069-4841-99E3-3BEC44E17356}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9A0759F1-9069-4841-99E3-3BEC44E17356}.Release|Any CPU.Build.0 = Release|Any CPU + {9A0759F1-9069-4841-99E3-3BEC44E17356}.Release|ARM.ActiveCfg = Release|Any CPU + {9A0759F1-9069-4841-99E3-3BEC44E17356}.Release|ARM.Build.0 = Release|Any CPU + {9A0759F1-9069-4841-99E3-3BEC44E17356}.Release|x64.ActiveCfg = Release|Any CPU + {9A0759F1-9069-4841-99E3-3BEC44E17356}.Release|x64.Build.0 = Release|Any CPU + {9A0759F1-9069-4841-99E3-3BEC44E17356}.Release|x86.ActiveCfg = Release|Any CPU + {9A0759F1-9069-4841-99E3-3BEC44E17356}.Release|x86.Build.0 = Release|Any CPU + {09577E4C-4899-45B9-BF80-1803D617CCAE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {09577E4C-4899-45B9-BF80-1803D617CCAE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {09577E4C-4899-45B9-BF80-1803D617CCAE}.Debug|ARM.ActiveCfg = Debug|Any CPU + {09577E4C-4899-45B9-BF80-1803D617CCAE}.Debug|ARM.Build.0 = Debug|Any CPU + {09577E4C-4899-45B9-BF80-1803D617CCAE}.Debug|x64.ActiveCfg = Debug|Any CPU + {09577E4C-4899-45B9-BF80-1803D617CCAE}.Debug|x64.Build.0 = Debug|Any CPU + {09577E4C-4899-45B9-BF80-1803D617CCAE}.Debug|x86.ActiveCfg = Debug|Any CPU + {09577E4C-4899-45B9-BF80-1803D617CCAE}.Debug|x86.Build.0 = Debug|Any CPU + {09577E4C-4899-45B9-BF80-1803D617CCAE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {09577E4C-4899-45B9-BF80-1803D617CCAE}.Release|Any CPU.Build.0 = Release|Any CPU + {09577E4C-4899-45B9-BF80-1803D617CCAE}.Release|ARM.ActiveCfg = Release|Any CPU + {09577E4C-4899-45B9-BF80-1803D617CCAE}.Release|ARM.Build.0 = Release|Any CPU + {09577E4C-4899-45B9-BF80-1803D617CCAE}.Release|x64.ActiveCfg = Release|Any CPU + {09577E4C-4899-45B9-BF80-1803D617CCAE}.Release|x64.Build.0 = Release|Any CPU + {09577E4C-4899-45B9-BF80-1803D617CCAE}.Release|x86.ActiveCfg = Release|Any CPU + {09577E4C-4899-45B9-BF80-1803D617CCAE}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {A0CFC681-769B-405A-8482-0CDEE595A91F} = {C7DB9A6F-77A8-4FE5-83CB-9C11F7100647} + {7E80F5E7-724A-4668-9333-B10F5D75C6D0} = {107C7C00-56F4-41B0-A8CC-0156C46A3650} + {C7DB9A6F-77A8-4FE5-83CB-9C11F7100647} = {0B30588B-07B8-4A88-A268-F58D06EA1627} + {42353562-5E43-459C-8E3E-2F21E575261D} = {0B30588B-07B8-4A88-A268-F58D06EA1627} + {9A0759F1-9069-4841-99E3-3BEC44E17356} = {0B30588B-07B8-4A88-A268-F58D06EA1627} + {09577E4C-4899-45B9-BF80-1803D617CCAE} = {0B30588B-07B8-4A88-A268-F58D06EA1627} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {0ADC1BC6-B1CA-427D-A97C-3CA40AAB0428} + EndGlobalSection EndGlobal diff --git a/ModernKeePass/Interfaces/IDatabaseService.cs b/ModernKeePass/Interfaces/IDatabaseService.cs index f527c3b..6931207 100644 --- a/ModernKeePass/Interfaces/IDatabaseService.cs +++ b/ModernKeePass/Interfaces/IDatabaseService.cs @@ -1,4 +1,5 @@ -using Windows.Storage; +using System.Threading.Tasks; +using Windows.Storage; using ModernKeePass.ViewModels; using ModernKeePassLib; using ModernKeePassLib.Cryptography.KeyDerivation; @@ -18,10 +19,10 @@ namespace ModernKeePass.Interfaces bool IsOpen { get; } bool HasChanged { get; set; } - void Open(StorageFile databaseFile, CompositeKey key, bool createNew = false); + Task Open(StorageFile databaseFile, CompositeKey key, bool createNew = false); void ReOpen(); void Save(); - void Save(StorageFile file); + Task Save(StorageFile file); void CreateRecycleBin(string title); void AddDeletedItem(PwUuid id); void Close(bool releaseFile = true); diff --git a/ModernKeePass/ModernKeePass.App.csproj b/ModernKeePass/ModernKeePass.App.csproj index 725b170..7027814 100644 --- a/ModernKeePass/ModernKeePass.App.csproj +++ b/ModernKeePass/ModernKeePass.App.csproj @@ -393,8 +393,8 @@ - - ..\packages\Portable.BouncyCastle.1.8.3\lib\netstandard1.0\BouncyCastle.Crypto.dll + + ..\packages\Portable.BouncyCastle.1.8.5\lib\netstandard1.0\BouncyCastle.Crypto.dll True @@ -409,52 +409,56 @@ ..\packages\Microsoft.Toolkit.Uwp.Notifications.2.0.0\lib\dotnet\Microsoft.Toolkit.Uwp.Notifications.dll True - - ..\packages\ModernKeePassLib.2.39.1\lib\netstandard1.2\ModernKeePassLib.dll + + ..\packages\ModernKeePassLib.2.44.1\lib\netstandard1.2\ModernKeePassLib.dll + True + + + ..\packages\SixLabors.Core.1.0.0-beta0006\lib\netstandard1.1\SixLabors.Core.dll + True + + + ..\packages\SixLabors.ImageSharp.1.0.0-beta0005\lib\netstandard1.1\SixLabors.ImageSharp.dll True ..\packages\Splat.3.0.0\lib\netstandard1.1\Splat.dll True + + ..\packages\System.Buffers.4.5.0\lib\netstandard1.1\System.Buffers.dll + True + ..\packages\System.Collections.Immutable.1.5.0\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll True - - ..\packages\System.Composition.AttributedModel.1.2.0\lib\portable-net45+win8+wp8+wpa81\System.Composition.AttributedModel.dll - True - - - ..\packages\System.Composition.Convention.1.2.0\lib\portable-net45+win8+wp8+wpa81\System.Composition.Convention.dll - True - - - ..\packages\System.Composition.Hosting.1.2.0\lib\portable-net45+win8+wp8+wpa81\System.Composition.Hosting.dll - True - - - ..\packages\System.Composition.Runtime.1.2.0\lib\portable-net45+win8+wp8+wpa81\System.Composition.Runtime.dll - True - - - ..\packages\System.Composition.TypedParts.1.2.0\lib\portable-net45+win8+wp8+wpa81\System.Composition.TypedParts.dll - True - ..\packages\System.Drawing.Primitives.4.3.0\lib\netstandard1.1\System.Drawing.Primitives.dll True + + ..\packages\System.Memory.4.5.1\lib\netstandard1.1\System.Memory.dll + True + + + ..\packages\System.Numerics.Vectors.4.4.0\lib\portable-net45+win8+wp8+wpa81\System.Numerics.Vectors.dll + True + ..\packages\System.Reflection.Metadata.1.6.0\lib\portable-net45+win8\System.Reflection.Metadata.dll True + + ..\packages\System.Runtime.CompilerServices.Unsafe.4.5.1\lib\netstandard1.0\System.Runtime.CompilerServices.Unsafe.dll + True + ..\packages\System.Runtime.InteropServices.RuntimeInformation.4.3.0\lib\win8\System.Runtime.InteropServices.RuntimeInformation.dll True - - ..\packages\Validation.2.4.18\lib\portable-net45+win8+wp8+wpa81\Validation.dll + + ..\packages\System.ValueTuple.4.5.0\lib\netstandard1.0\System.ValueTuple.dll True diff --git a/ModernKeePass/Services/DatabaseService.cs b/ModernKeePass/Services/DatabaseService.cs index edc4cc9..f79bbc3 100644 --- a/ModernKeePass/Services/DatabaseService.cs +++ b/ModernKeePass/Services/DatabaseService.cs @@ -1,4 +1,6 @@ using System; +using System.Runtime.InteropServices.WindowsRuntime; +using System.Threading.Tasks; using Windows.Storage; using Microsoft.HockeyApp; using ModernKeePass.Exceptions; @@ -80,7 +82,7 @@ namespace ModernKeePass.Services /// The database composite key /// True to create a new database before opening it /// An error message, if any - public void Open(StorageFile databaseFile, CompositeKey key, bool createNew = false) + public async Task Open(StorageFile databaseFile, CompositeKey key, bool createNew = false) { try { @@ -94,7 +96,8 @@ namespace ModernKeePass.Services } _compositeKey = key; - var ioConnection = IOConnectionInfo.FromFile(databaseFile); + var fileContents = await FileIO.ReadBufferAsync(databaseFile); + var ioConnection = IOConnectionInfo.FromByteArray(fileContents.ToArray()); if (createNew) { _pwDatabase.New(ioConnection, key); @@ -144,13 +147,14 @@ namespace ModernKeePass.Services /// Save the current database to another file and open it /// /// The new database file - public void Save(StorageFile file) + public async Task Save(StorageFile file) { var oldFile = _databaseFile; _databaseFile = file; try { - _pwDatabase.SaveAs(IOConnectionInfo.FromFile(_databaseFile), true, new NullStatusLogger()); + var fileContents = await FileIO.ReadBufferAsync(file); + _pwDatabase.SaveAs(IOConnectionInfo.FromByteArray(fileContents.ToArray()), true, new NullStatusLogger()); } catch { diff --git a/ModernKeePass/ViewModels/CompositeKeyVm.cs b/ModernKeePass/ViewModels/CompositeKeyVm.cs index f6e7437..6bf5ac4 100644 --- a/ModernKeePass/ViewModels/CompositeKeyVm.cs +++ b/ModernKeePass/ViewModels/CompositeKeyVm.cs @@ -1,4 +1,5 @@ using System; +using System.Runtime.InteropServices.WindowsRuntime; using System.Text; using System.Threading.Tasks; using Windows.Storage; @@ -126,7 +127,7 @@ namespace ModernKeePass.ViewModels { _isOpening = true; OnPropertyChanged("IsValid"); - Database.Open(databaseFile, CreateCompositeKey(), createNew); + await Database.Open(databaseFile, await CreateCompositeKey(), createNew); await Task.Run(() => RootGroup = Database.RootGroup); return true; } @@ -151,16 +152,17 @@ namespace ModernKeePass.ViewModels return false; } - public void UpdateKey() + public async Task UpdateKey() { - Database.UpdateCompositeKey(CreateCompositeKey()); + Database.UpdateCompositeKey(await CreateCompositeKey()); UpdateStatus(_resource.GetResourceValue("CompositeKeyUpdated"), StatusTypes.Success); } - public void CreateKeyFile(StorageFile file) + public async Task CreateKeyFile(StorageFile file) { // TODO: implement entropy generator - KcpKeyFile.Create(file, null); + var fileContents = await FileIO.ReadBufferAsync(file); + KcpKeyFile.Create(fileContents.ToArray()); KeyFile = file; } @@ -170,11 +172,15 @@ namespace ModernKeePass.ViewModels StatusType = (int)type; } - private CompositeKey CreateCompositeKey() + private async Task CreateCompositeKey() { var compositeKey = new CompositeKey(); if (HasPassword) compositeKey.AddUserKey(new KcpPassword(Password)); - if (HasKeyFile && KeyFile != null) compositeKey.AddUserKey(new KcpKeyFile(IOConnectionInfo.FromFile(KeyFile))); + if (HasKeyFile && KeyFile != null) + { + var fileContents = await FileIO.ReadBufferAsync(KeyFile); + compositeKey.AddUserKey(new KcpKeyFile(IOConnectionInfo.FromByteArray(fileContents.ToArray()))); + } if (HasUserAccount) compositeKey.AddUserKey(new KcpUserAccount()); return compositeKey; } diff --git a/ModernKeePass/packages.config b/ModernKeePass/packages.config index 87f3852..13303fe 100644 --- a/ModernKeePass/packages.config +++ b/ModernKeePass/packages.config @@ -3,35 +3,35 @@ - - + - + - + + + + - - - - - - + + + + @@ -39,7 +39,7 @@ - + + - \ No newline at end of file diff --git a/ModernKeePassLib.Test/Cryptography/Cipher/Chacha20Tests.cs b/ModernKeePassLib.Test/Cryptography/Cipher/Chacha20Tests.cs deleted file mode 100644 index de211be..0000000 --- a/ModernKeePassLib.Test/Cryptography/Cipher/Chacha20Tests.cs +++ /dev/null @@ -1,204 +0,0 @@ -using System; -using System.Diagnostics; -using System.IO; -using Microsoft.VisualStudio.TestPlatform.UnitTestFramework; -using ModernKeePassLib.Cryptography; -using ModernKeePassLib.Cryptography.Cipher; -using ModernKeePassLib.Utility; - -namespace ModernKeePassLib.Test.Cryptography.Cipher -{ - [TestClass] - public class Chacha20Tests - { - [TestMethod] - public void TestChacha20() - { - // ====================================================== - // Test vector from RFC 7539, section 2.3.2 - - byte[] pbKey = new byte[32]; - for (int i = 0; i < 32; ++i) pbKey[i] = (byte)i; - - byte[] pbIV = new byte[12]; - pbIV[3] = 0x09; - pbIV[7] = 0x4A; - - byte[] pbExpc = new byte[64] { - 0x10, 0xF1, 0xE7, 0xE4, 0xD1, 0x3B, 0x59, 0x15, - 0x50, 0x0F, 0xDD, 0x1F, 0xA3, 0x20, 0x71, 0xC4, - 0xC7, 0xD1, 0xF4, 0xC7, 0x33, 0xC0, 0x68, 0x03, - 0x04, 0x22, 0xAA, 0x9A, 0xC3, 0xD4, 0x6C, 0x4E, - 0xD2, 0x82, 0x64, 0x46, 0x07, 0x9F, 0xAA, 0x09, - 0x14, 0xC2, 0xD7, 0x05, 0xD9, 0x8B, 0x02, 0xA2, - 0xB5, 0x12, 0x9C, 0xD1, 0xDE, 0x16, 0x4E, 0xB9, - 0xCB, 0xD0, 0x83, 0xE8, 0xA2, 0x50, 0x3C, 0x4E - }; - - byte[] pb = new byte[64]; - - using (ChaCha20Cipher c = new ChaCha20Cipher(pbKey, pbIV)) - { - c.Seek(64, SeekOrigin.Begin); // Skip first block - c.Encrypt(pb, 0, pb.Length); - - Assert.IsTrue(MemUtil.ArraysEqual(pb, pbExpc)); - } - -#if DEBUG - // ====================================================== - // Test vector from RFC 7539, section 2.4.2 - - pbIV[3] = 0; - - pb = StrUtil.Utf8.GetBytes("Ladies and Gentlemen of the clas" + - @"s of '99: If I could offer you only one tip for " + - @"the future, sunscreen would be it."); - - pbExpc = new byte[] { - 0x6E, 0x2E, 0x35, 0x9A, 0x25, 0x68, 0xF9, 0x80, - 0x41, 0xBA, 0x07, 0x28, 0xDD, 0x0D, 0x69, 0x81, - 0xE9, 0x7E, 0x7A, 0xEC, 0x1D, 0x43, 0x60, 0xC2, - 0x0A, 0x27, 0xAF, 0xCC, 0xFD, 0x9F, 0xAE, 0x0B, - 0xF9, 0x1B, 0x65, 0xC5, 0x52, 0x47, 0x33, 0xAB, - 0x8F, 0x59, 0x3D, 0xAB, 0xCD, 0x62, 0xB3, 0x57, - 0x16, 0x39, 0xD6, 0x24, 0xE6, 0x51, 0x52, 0xAB, - 0x8F, 0x53, 0x0C, 0x35, 0x9F, 0x08, 0x61, 0xD8, - 0x07, 0xCA, 0x0D, 0xBF, 0x50, 0x0D, 0x6A, 0x61, - 0x56, 0xA3, 0x8E, 0x08, 0x8A, 0x22, 0xB6, 0x5E, - 0x52, 0xBC, 0x51, 0x4D, 0x16, 0xCC, 0xF8, 0x06, - 0x81, 0x8C, 0xE9, 0x1A, 0xB7, 0x79, 0x37, 0x36, - 0x5A, 0xF9, 0x0B, 0xBF, 0x74, 0xA3, 0x5B, 0xE6, - 0xB4, 0x0B, 0x8E, 0xED, 0xF2, 0x78, 0x5E, 0x42, - 0x87, 0x4D - }; - - byte[] pb64 = new byte[64]; - - using (ChaCha20Cipher c = new ChaCha20Cipher(pbKey, pbIV)) - { - c.Encrypt(pb64, 0, pb64.Length); // Skip first block - c.Encrypt(pb, 0, pb.Length); - - Assert.IsTrue(MemUtil.ArraysEqual(pb, pbExpc)); - } - - // ====================================================== - // Test vector from RFC 7539, appendix A.2 #2 - - Array.Clear(pbKey, 0, pbKey.Length); - pbKey[31] = 1; - - Array.Clear(pbIV, 0, pbIV.Length); - pbIV[11] = 2; - - pb = StrUtil.Utf8.GetBytes("Any submission to the IETF inten" + - "ded by the Contributor for publication as all or" + - " part of an IETF Internet-Draft or RFC and any s" + - "tatement made within the context of an IETF acti" + - "vity is considered an \"IETF Contribution\". Such " + - "statements include oral statements in IETF sessi" + - "ons, as well as written and electronic communica" + - "tions made at any time or place, which are addressed to"); - - pbExpc = MemUtil.HexStringToByteArray( - "A3FBF07DF3FA2FDE4F376CA23E82737041605D9F4F4F57BD8CFF2C1D4B7955EC" + - "2A97948BD3722915C8F3D337F7D370050E9E96D647B7C39F56E031CA5EB6250D" + - "4042E02785ECECFA4B4BB5E8EAD0440E20B6E8DB09D881A7C6132F420E527950" + - "42BDFA7773D8A9051447B3291CE1411C680465552AA6C405B7764D5E87BEA85A" + - "D00F8449ED8F72D0D662AB052691CA66424BC86D2DF80EA41F43ABF937D3259D" + - "C4B2D0DFB48A6C9139DDD7F76966E928E635553BA76C5C879D7B35D49EB2E62B" + - "0871CDAC638939E25E8A1E0EF9D5280FA8CA328B351C3C765989CBCF3DAA8B6C" + - "CC3AAF9F3979C92B3720FC88DC95ED84A1BE059C6499B9FDA236E7E818B04B0B" + - "C39C1E876B193BFE5569753F88128CC08AAA9B63D1A16F80EF2554D7189C411F" + - "5869CA52C5B83FA36FF216B9C1D30062BEBCFD2DC5BCE0911934FDA79A86F6E6" + - "98CED759C3FF9B6477338F3DA4F9CD8514EA9982CCAFB341B2384DD902F3D1AB" + - "7AC61DD29C6F21BA5B862F3730E37CFDC4FD806C22F221"); - - using (MemoryStream msEnc = new MemoryStream()) - { - using (ChaCha20Stream c = new ChaCha20Stream(msEnc, true, pbKey, pbIV)) - { - Random r = CryptoRandom.NewWeakRandom(); - r.NextBytes(pb64); - c.Write(pb64, 0, pb64.Length); // Skip first block - - int p = 0; - while (p < pb.Length) - { - int cb = r.Next(1, pb.Length - p + 1); - c.Write(pb, p, cb); - p += cb; - } - Debug.Assert(p == pb.Length); - } - - byte[] pbEnc0 = msEnc.ToArray(); - byte[] pbEnc = MemUtil.Mid(pbEnc0, 64, pbEnc0.Length - 64); - Assert.IsTrue(MemUtil.ArraysEqual(pbEnc, pbExpc)); - - using (MemoryStream msCT = new MemoryStream(pbEnc0, false)) - { - using (ChaCha20Stream cDec = new ChaCha20Stream(msCT, false, - pbKey, pbIV)) - { - byte[] pbPT = MemUtil.Read(cDec, pbEnc0.Length); - - Assert.IsTrue(cDec.ReadByte() < 0); - Assert.IsTrue(MemUtil.ArraysEqual(MemUtil.Mid(pbPT, 0, 64), pb64)); - Assert.IsTrue(MemUtil.ArraysEqual(MemUtil.Mid(pbPT, 64, pbEnc.Length), pb)); - } - } - } - - // ====================================================== - // Test vector TC8 from RFC draft by J. Strombergson: - // https://tools.ietf.org/html/draft-strombergson-chacha-test-vectors-01 - - pbKey = new byte[32] { - 0xC4, 0x6E, 0xC1, 0xB1, 0x8C, 0xE8, 0xA8, 0x78, - 0x72, 0x5A, 0x37, 0xE7, 0x80, 0xDF, 0xB7, 0x35, - 0x1F, 0x68, 0xED, 0x2E, 0x19, 0x4C, 0x79, 0xFB, - 0xC6, 0xAE, 0xBE, 0xE1, 0xA6, 0x67, 0x97, 0x5D - }; - - // The first 4 bytes are set to zero and a large counter - // is used; this makes the RFC 7539 version of ChaCha20 - // compatible with the original specification by - // D. J. Bernstein. - pbIV = new byte[12] { 0x00, 0x00, 0x00, 0x00, - 0x1A, 0xDA, 0x31, 0xD5, 0xCF, 0x68, 0x82, 0x21 - }; - - pb = new byte[128]; - - pbExpc = new byte[128] { - 0xF6, 0x3A, 0x89, 0xB7, 0x5C, 0x22, 0x71, 0xF9, - 0x36, 0x88, 0x16, 0x54, 0x2B, 0xA5, 0x2F, 0x06, - 0xED, 0x49, 0x24, 0x17, 0x92, 0x30, 0x2B, 0x00, - 0xB5, 0xE8, 0xF8, 0x0A, 0xE9, 0xA4, 0x73, 0xAF, - 0xC2, 0x5B, 0x21, 0x8F, 0x51, 0x9A, 0xF0, 0xFD, - 0xD4, 0x06, 0x36, 0x2E, 0x8D, 0x69, 0xDE, 0x7F, - 0x54, 0xC6, 0x04, 0xA6, 0xE0, 0x0F, 0x35, 0x3F, - 0x11, 0x0F, 0x77, 0x1B, 0xDC, 0xA8, 0xAB, 0x92, - - 0xE5, 0xFB, 0xC3, 0x4E, 0x60, 0xA1, 0xD9, 0xA9, - 0xDB, 0x17, 0x34, 0x5B, 0x0A, 0x40, 0x27, 0x36, - 0x85, 0x3B, 0xF9, 0x10, 0xB0, 0x60, 0xBD, 0xF1, - 0xF8, 0x97, 0xB6, 0x29, 0x0F, 0x01, 0xD1, 0x38, - 0xAE, 0x2C, 0x4C, 0x90, 0x22, 0x5B, 0xA9, 0xEA, - 0x14, 0xD5, 0x18, 0xF5, 0x59, 0x29, 0xDE, 0xA0, - 0x98, 0xCA, 0x7A, 0x6C, 0xCF, 0xE6, 0x12, 0x27, - 0x05, 0x3C, 0x84, 0xE4, 0x9A, 0x4A, 0x33, 0x32 - }; - - using (ChaCha20Cipher c = new ChaCha20Cipher(pbKey, pbIV, true)) - { - c.Decrypt(pb, 0, pb.Length); - - Assert.IsTrue(MemUtil.ArraysEqual(pb, pbExpc)); - } -#endif - } - } -} \ No newline at end of file diff --git a/ModernKeePassLib.Test/Cryptography/Cipher/StandardAesEngineTests.cs b/ModernKeePassLib.Test/Cryptography/Cipher/StandardAesEngineTests.cs deleted file mode 100644 index 0719418..0000000 --- a/ModernKeePassLib.Test/Cryptography/Cipher/StandardAesEngineTests.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System.IO; -using System.Text; -using Microsoft.VisualStudio.TestPlatform.UnitTestFramework; -using ModernKeePassLib.Serialization; -using ModernKeePassLib.Cryptography.Cipher; -using ModernKeePassLib.Utility; -using Org.BouncyCastle.Crypto.Engines; -using Org.BouncyCastle.Crypto.Parameters; - -namespace ModernKeePassLib.Test.Cryptography.Cipher -{ - [TestClass()] - public class StandardAesEngineTests - { - // Test vector (official ECB test vector #356) - private readonly byte[] _pbReferenceCt = - { - 0x75, 0xD1, 0x1B, 0x0E, 0x3A, 0x68, 0xC4, 0x22, - 0x3D, 0x88, 0xDB, 0xF0, 0x17, 0x97, 0x7D, 0xD7 - }; - private readonly byte[] _pbIv = new byte[16]; - private readonly byte[] _pbTestKey = new byte[32]; - private readonly byte[] _pbTestData = - { - 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 - }; - - [TestMethod] - public void TestEncryptStream() - { - using (var outStream = new MemoryStream(new byte[16])) - { - var aes = new StandardAesEngine(); - using (var inStream = aes.EncryptStream(outStream, _pbTestKey, _pbIv)) - { - new BinaryWriter(inStream).Write(_pbTestData); - Assert.AreEqual(16, outStream.Position); - outStream.Position = 0; - var outBytes = new BinaryReaderEx(outStream, Encoding.UTF8, string.Empty).ReadBytes(16); - Assert.IsTrue(MemUtil.ArraysEqual(outBytes, _pbReferenceCt)); - } - } - } - - [TestMethod] - public void TestDecryptStream() - { - // Possible Mono Bug? This only works with size >= 48 - using (var inStream = new MemoryStream(new byte[32])) - { - inStream.Write(_pbReferenceCt, 0, _pbReferenceCt.Length); - inStream.Position = 0; - var aes = new StandardAesEngine(); - using (var outStream = aes.DecryptStream(inStream, _pbTestKey, _pbIv)) - { - var outBytes = new BinaryReaderEx(outStream, Encoding.UTF8, string.Empty).ReadBytes(16); - Assert.IsTrue(MemUtil.ArraysEqual(outBytes, _pbTestData)); - } - } - } - - [TestMethod] - public void TestBouncyCastleAes() - { - var aesEngine = new AesEngine(); - //var parametersWithIv = new ParametersWithIV(new KeyParameter(pbTestKey), pbIV); - aesEngine.Init(true, new KeyParameter(_pbTestKey)); - Assert.AreEqual(aesEngine.GetBlockSize(), _pbTestData.Length); - aesEngine.ProcessBlock(_pbTestData, 0, _pbTestData, 0); - Assert.IsTrue(MemUtil.ArraysEqual(_pbTestData, _pbReferenceCt)); - } - } -} diff --git a/ModernKeePassLib.Test/Cryptography/CryptoRandomStreamTests.cs b/ModernKeePassLib.Test/Cryptography/CryptoRandomStreamTests.cs deleted file mode 100644 index 1a3a48e..0000000 --- a/ModernKeePassLib.Test/Cryptography/CryptoRandomStreamTests.cs +++ /dev/null @@ -1,56 +0,0 @@ -using Microsoft.VisualStudio.TestPlatform.UnitTestFramework; - -using ModernKeePassLib.Cryptography; -using ModernKeePassLib.Utility; - -namespace ModernKeePassLib.Test.Cryptography -{ - [TestClass] - public class CryptoRandomStreamTests - { - private void TestGetRandomBytes(CryptoRandomStream stream) - { - const uint length = 16; - var bytes1 = stream.GetRandomBytes(length); - Assert.AreEqual(bytes1.Length, (int)length); - var bytes2 = stream.GetRandomBytes(length); - Assert.IsFalse(MemUtil.ArraysEqual(bytes2, bytes1)); - } - - [TestMethod] - public void TestGetRandomBytesCrsAlgorithmSalsa20() - { - var stream = new CryptoRandomStream(CrsAlgorithm.Salsa20, new byte[16]); - TestGetRandomBytes(stream); - } - - [TestMethod] - public void TestGetRandomBytesCrsAlgorithmArcFourVariant() - { - var stream = new CryptoRandomStream(CrsAlgorithm.ArcFourVariant, new byte[16]); - TestGetRandomBytes(stream); - } - - private void TestGetRandomInt64(CryptoRandomStream stream) - { - var value1 = stream.GetRandomUInt64(); - var value2 = stream.GetRandomUInt64(); - Assert.AreNotEqual(value2, value1); - } - - [TestMethod] - public void TestGetRandomInt64AlgorithmSalsa20() - { - var stream = new CryptoRandomStream(CrsAlgorithm.Salsa20, new byte[16]); - TestGetRandomInt64(stream); - } - - [TestMethod] - public void TestGetRandomInt64AlgorithmArcFourVariant() - { - var stream = new CryptoRandomStream(CrsAlgorithm.ArcFourVariant, new byte[16]); - TestGetRandomInt64(stream); - } - } -} - diff --git a/ModernKeePassLib.Test/Cryptography/CryptoRandomTests.cs b/ModernKeePassLib.Test/Cryptography/CryptoRandomTests.cs deleted file mode 100644 index 9a95d2e..0000000 --- a/ModernKeePassLib.Test/Cryptography/CryptoRandomTests.cs +++ /dev/null @@ -1,38 +0,0 @@ -using Microsoft.VisualStudio.TestPlatform.UnitTestFramework; -using ModernKeePassLib.Cryptography; - -namespace ModernKeePassLib.Test.Cryptography -{ - [TestClass()] - public class CryptoRandomTests - { - [TestMethod] - public void TestAddEntropy() - { - // just making sure it does not throw an exception - CryptoRandom.Instance.AddEntropy(new byte[1]); - } - - [TestMethod] - public void TestGetRandomBytes() - { - const int length = 32; - var bytes1 = CryptoRandom.Instance.GetRandomBytes(length); - Assert.AreEqual(bytes1.Length, length); - var bytes2 = CryptoRandom.Instance.GetRandomBytes(length); - Assert.AreNotEqual(bytes2, bytes1); - } - - [TestMethod] - public void TestGeneratedBytesCount() - { - const int length = 1; - CryptoRandom.Instance.GetRandomBytes(length); - var count1 = CryptoRandom.Instance.GeneratedBytesCount; - CryptoRandom.Instance.GetRandomBytes(length); - var count2 = CryptoRandom.Instance.GeneratedBytesCount; - Assert.IsTrue(count2 > count1); - } - } -} - diff --git a/ModernKeePassLib.Test/Cryptography/Hash/Blake2bTests.cs b/ModernKeePassLib.Test/Cryptography/Hash/Blake2bTests.cs deleted file mode 100644 index fc0fc25..0000000 --- a/ModernKeePassLib.Test/Cryptography/Hash/Blake2bTests.cs +++ /dev/null @@ -1,101 +0,0 @@ -using System; -using System.Text; -using Microsoft.VisualStudio.TestPlatform.UnitTestFramework; -using ModernKeePassLib.Cryptography; -using ModernKeePassLib.Cryptography.Hash; -using ModernKeePassLib.Utility; - -namespace ModernKeePassLib.Test.Cryptography.Hash -{ - [TestClass] - public class Blake2bTests - { - [TestMethod] - public void TestBlake2bUtf8() - { - Blake2b h = new Blake2b(); - - // ====================================================== - // From https://tools.ietf.org/html/rfc7693 - - byte[] pbData = StrUtil.Utf8.GetBytes("abc"); - byte[] pbExpc = new byte[64] - { - 0xBA, 0x80, 0xA5, 0x3F, 0x98, 0x1C, 0x4D, 0x0D, - 0x6A, 0x27, 0x97, 0xB6, 0x9F, 0x12, 0xF6, 0xE9, - 0x4C, 0x21, 0x2F, 0x14, 0x68, 0x5A, 0xC4, 0xB7, - 0x4B, 0x12, 0xBB, 0x6F, 0xDB, 0xFF, 0xA2, 0xD1, - 0x7D, 0x87, 0xC5, 0x39, 0x2A, 0xAB, 0x79, 0x2D, - 0xC2, 0x52, 0xD5, 0xDE, 0x45, 0x33, 0xCC, 0x95, - 0x18, 0xD3, 0x8A, 0xA8, 0xDB, 0xF1, 0x92, 0x5A, - 0xB9, 0x23, 0x86, 0xED, 0xD4, 0x00, 0x99, 0x23 - }; - - byte[] pbC = h.ComputeHash(pbData); - Assert.IsTrue(MemUtil.ArraysEqual(pbC, pbExpc)); - } - - [TestMethod] - public void TestBlake2bEmpty() - { - // ====================================================== - // Computed using the official b2sum tool - Blake2b h = new Blake2b(); - - var pbExpc = new byte[64] - { - 0x78, 0x6A, 0x02, 0xF7, 0x42, 0x01, 0x59, 0x03, - 0xC6, 0xC6, 0xFD, 0x85, 0x25, 0x52, 0xD2, 0x72, - 0x91, 0x2F, 0x47, 0x40, 0xE1, 0x58, 0x47, 0x61, - 0x8A, 0x86, 0xE2, 0x17, 0xF7, 0x1F, 0x54, 0x19, - 0xD2, 0x5E, 0x10, 0x31, 0xAF, 0xEE, 0x58, 0x53, - 0x13, 0x89, 0x64, 0x44, 0x93, 0x4E, 0xB0, 0x4B, - 0x90, 0x3A, 0x68, 0x5B, 0x14, 0x48, 0xB7, 0x55, - 0xD5, 0x6F, 0x70, 0x1A, 0xFE, 0x9B, 0xE2, 0xCE - }; - - var pbC = h.ComputeHash(new byte[0]); - Assert.IsTrue(MemUtil.ArraysEqual(pbC, pbExpc)); - } - - [TestMethod] - public void TestBlake2bString() - { - // ====================================================== - // Computed using the official b2sum tool - Blake2b h = new Blake2b(); - - string strS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.:,;_-\r\n"; - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < 1000; ++i) sb.Append(strS); - var pbData = StrUtil.Utf8.GetBytes(sb.ToString()); - - var pbExpc = new byte[64] { - 0x59, 0x69, 0x8D, 0x3B, 0x83, 0xF4, 0x02, 0x4E, - 0xD8, 0x99, 0x26, 0x0E, 0xF4, 0xE5, 0x9F, 0x20, - 0xDC, 0x31, 0xEE, 0x5B, 0x45, 0xEA, 0xBB, 0xFC, - 0x1C, 0x0A, 0x8E, 0xED, 0xAA, 0x7A, 0xFF, 0x50, - 0x82, 0xA5, 0x8F, 0xBC, 0x4A, 0x46, 0xFC, 0xC5, - 0xEF, 0x44, 0x4E, 0x89, 0x80, 0x7D, 0x3F, 0x1C, - 0xC1, 0x94, 0x45, 0xBB, 0xC0, 0x2C, 0x95, 0xAA, - 0x3F, 0x08, 0x8A, 0x93, 0xF8, 0x75, 0x91, 0xB0 - }; - - Random r = CryptoRandom.NewWeakRandom(); - int p = 0; - while (p < pbData.Length) - { - int cb = r.Next(1, pbData.Length - p + 1); - h.TransformBlock(pbData, p, cb, pbData, p); - p += cb; - } - Assert.AreEqual(p, pbData.Length); - - h.TransformFinalBlock(new byte[0], 0, 0); - - Assert.IsTrue(MemUtil.ArraysEqual(h.Hash, pbExpc)); - - h.Clear(); - } - } -} \ No newline at end of file diff --git a/ModernKeePassLib.Test/Cryptography/Hash/HmacTests.cs b/ModernKeePassLib.Test/Cryptography/Hash/HmacTests.cs deleted file mode 100644 index 8f42feb..0000000 --- a/ModernKeePassLib.Test/Cryptography/Hash/HmacTests.cs +++ /dev/null @@ -1,117 +0,0 @@ -using Microsoft.VisualStudio.TestPlatform.UnitTestFramework; -using ModernKeePassLib.Cryptography; -using ModernKeePassLib.Cryptography.Hash; -using ModernKeePassLib.Utility; - -namespace ModernKeePassLib.Test.Cryptography.Hash -{ - [TestClass] - public class HmacTests - { - [TestMethod] - public void TestHmac1() - { - // Test vectors from RFC 4231 - - var pbKey = new byte[20]; - for (var i = 0; i < pbKey.Length; ++i) pbKey[i] = 0x0B; - var pbMsg = StrUtil.Utf8.GetBytes("Hi There"); - var pbExpc = new byte[] - { - 0xB0, 0x34, 0x4C, 0x61, 0xD8, 0xDB, 0x38, 0x53, - 0x5C, 0xA8, 0xAF, 0xCE, 0xAF, 0x0B, 0xF1, 0x2B, - 0x88, 0x1D, 0xC2, 0x00, 0xC9, 0x83, 0x3D, 0xA7, - 0x26, 0xE9, 0x37, 0x6C, 0x2E, 0x32, 0xCF, 0xF7 - }; - HmacEval(pbKey, pbMsg, pbExpc); - } - - [TestMethod] - public void TestHmac2() - { - var pbKey = new byte[131]; - for (var i = 0; i < pbKey.Length; ++i) pbKey[i] = 0xAA; - var pbMsg = StrUtil.Utf8.GetBytes( - "This is a test using a larger than block-size key and " + - "a larger than block-size data. The key needs to be " + - "hashed before being used by the HMAC algorithm."); - var pbExpc = new byte[] { - 0x9B, 0x09, 0xFF, 0xA7, 0x1B, 0x94, 0x2F, 0xCB, - 0x27, 0x63, 0x5F, 0xBC, 0xD5, 0xB0, 0xE9, 0x44, - 0xBF, 0xDC, 0x63, 0x64, 0x4F, 0x07, 0x13, 0x93, - 0x8A, 0x7F, 0x51, 0x53, 0x5C, 0x3A, 0x35, 0xE2 - }; - HmacEval(pbKey, pbMsg, pbExpc); - } - - [TestMethod] - public void TestHmacSha1ComputeHash() - { - var expectedHash = "AC2C2E614882CE7158F69B7E3B12114465945D01"; - var message = StrUtil.Utf8.GetBytes("testing123"); - var key = StrUtil.Utf8.GetBytes("hello"); - using (var result = new HMACSHA1(key)) - { - Assert.AreEqual(ByteToString(result.ComputeHash(message)), expectedHash); - } - } - - [TestMethod] - public void TestHmacSha256ComputeHash() - { - var expectedHash = "09C1BD2DE4E5659C0EFAF9E6AE4723E9CF96B69609B4E562F6AFF1745D7BF4E0"; - var message = StrUtil.Utf8.GetBytes("testing123"); - var key = StrUtil.Utf8.GetBytes("hello"); - using (var result = new HMACSHA256(key)) - { - Assert.AreEqual(ByteToString(result.ComputeHash(message)), expectedHash); - } - } - - private static string ByteToString(byte[] buff) - { - string sbinary = ""; - - for (int i = 0; i < buff.Length; i++) - { - sbinary += buff[i].ToString("X2"); // hex format - } - return (sbinary); - } - - [TestMethod] - public void TestHmacOtp() - { - var pbSecret = StrUtil.Utf8.GetBytes("12345678901234567890"); - var vExp = new []{ "755224", "287082", "359152", - "969429", "338314", "254676", "287922", "162583", "399871", - "520489" }; - - for (var i = 0; i < vExp.Length; ++i) - { - Assert.AreEqual(HmacOtp.Generate(pbSecret, (ulong)i, 6, false, -1), vExp[i]); - } - } - - private static void HmacEval(byte[] pbKey, byte[] pbMsg, - byte[] pbExpc) - { - using (var h = new HMACSHA256(pbKey)) - { - h.TransformBlock(pbMsg, 0, pbMsg.Length, pbMsg, 0); - h.TransformFinalBlock(new byte[0], 0, 0); - - byte[] pbHash = h.Hash; - Assert.IsTrue(MemUtil.ArraysEqual(pbHash, pbExpc)); - - // Reuse the object - h.Initialize(); - h.TransformBlock(pbMsg, 0, pbMsg.Length, pbMsg, 0); - h.TransformFinalBlock(new byte[0], 0, 0); - - pbHash = h.Hash; - Assert.IsTrue(MemUtil.ArraysEqual(pbHash, pbExpc)); - } - } - } -} \ No newline at end of file diff --git a/ModernKeePassLib.Test/Cryptography/Hash/SHAManagedTests.cs b/ModernKeePassLib.Test/Cryptography/Hash/SHAManagedTests.cs deleted file mode 100644 index 7f543b4..0000000 --- a/ModernKeePassLib.Test/Cryptography/Hash/SHAManagedTests.cs +++ /dev/null @@ -1,42 +0,0 @@ -using Microsoft.VisualStudio.TestPlatform.UnitTestFramework; -using ModernKeePassLib.Cryptography.Hash; -using ModernKeePassLib.Utility; - -namespace ModernKeePassLib.Test.Cryptography.Hash -{ - [TestClass] - public class SHAManagedTests - { - [TestMethod] - public void TestSha256ComputeHash() - { - var expectedHash = "B822F1CD2DCFC685B47E83E3980289FD5D8E3FF3A82DEF24D7D1D68BB272EB32"; - var message = StrUtil.Utf8.GetBytes("testing123"); - using (var result = new SHA256Managed()) - { - Assert.AreEqual(ByteToString(result.ComputeHash(message)), expectedHash); - } - } - - [TestMethod] - public void TestSha512ComputeHash() - { - var expectedHash = "4120117B3190BA5E24044732B0B09AA9ED50EB1567705ABCBFA78431A4E0A96B1152ED7F4925966B1C82325E186A8100E692E6D2FCB6702572765820D25C7E9E"; - var message = StrUtil.Utf8.GetBytes("testing123"); - using (var result = new SHA512Managed()) - { - Assert.AreEqual(ByteToString(result.ComputeHash(message)), expectedHash); - } - } - private static string ByteToString(byte[] buff) - { - string sbinary = ""; - - for (int i = 0; i < buff.Length; i++) - { - sbinary += buff[i].ToString("X2"); // hex format - } - return (sbinary); - } - } -} \ No newline at end of file diff --git a/ModernKeePassLib.Test/Cryptography/HashingStreamExTests.cs b/ModernKeePassLib.Test/Cryptography/HashingStreamExTests.cs deleted file mode 100644 index ad8b0bb..0000000 --- a/ModernKeePassLib.Test/Cryptography/HashingStreamExTests.cs +++ /dev/null @@ -1,83 +0,0 @@ -using System.IO; -using Microsoft.VisualStudio.TestPlatform.UnitTestFramework; - -using ModernKeePassLib.Cryptography; -using ModernKeePassLib.Utility; - -namespace ModernKeePassLib.Test.Cryptography -{ - [TestClass()] - public class HashingStreamExTests - { - const string data = "test"; - - // The expected hash includes the \n added by WriteLine - static readonly byte[] sha256HashOfData = - { - 0xf2, 0xca, 0x1b, 0xb6, 0xc7, 0xe9, 0x07, 0xd0, - 0x6d, 0xaf, 0xe4, 0x68, 0x7e, 0x57, 0x9f, 0xce, - 0x76, 0xb3, 0x7e, 0x4e, 0x93, 0xb7, 0x60, 0x50, - 0x22, 0xda, 0x52, 0xe6, 0xcc, 0xc2, 0x6f, 0xd2 - }; - - [TestMethod] - public void TestRead() - { - // if we use larger size, StreamReader will read past newline and cause bad hash - var bytes = new byte[data.Length + 1]; - using (var ms = new MemoryStream(bytes)) - { - using (var sw = new StreamWriter(ms)) - { - // set NewLine to ensure we don't run into cross-platform issues on Windows - sw.NewLine = "\n"; - sw.WriteLine(data); - } - } - using (var ms = new MemoryStream(bytes)) - { - using (var hs = new HashingStreamEx(ms, false, null)) - { - using (var sr = new StreamReader(hs)) - { - var read = sr.ReadLine(); - Assert.AreEqual(read, data); - } - // When the StreamReader is disposed, it calls Dispose on the - //HasingStreamEx, which computes the hash. - Assert.IsTrue(MemUtil.ArraysEqual(hs.Hash, sha256HashOfData)); - } - } - } - - [TestMethod] - public void TestWrite() - { - var bytes = new byte[16]; - using (var ms = new MemoryStream(bytes)) - { - using (var hs = new HashingStreamEx(ms, true, null)) - { - using (var sw = new StreamWriter(hs)) - { - // set NewLine to ensure we don't run into cross-platform issues on Windows - sw.NewLine = "\n"; - sw.WriteLine(data); - } - // When the StreamWriter is disposed, it calls Dispose on the - //HasingStreamEx, which computes the hash. - Assert.IsTrue(MemUtil.ArraysEqual(hs.Hash, sha256HashOfData)); - } - } - using (var ms = new MemoryStream(bytes)) - { - using (var sr = new StreamReader(ms)) - { - var read = sr.ReadLine(); - Assert.AreEqual(read, data); - } - } - } - } -} - diff --git a/ModernKeePassLib.Test/Cryptography/HmacOtpTests.cs b/ModernKeePassLib.Test/Cryptography/HmacOtpTests.cs deleted file mode 100644 index 6ea1a17..0000000 --- a/ModernKeePassLib.Test/Cryptography/HmacOtpTests.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Text; -using Microsoft.VisualStudio.TestPlatform.UnitTestFramework; -using ModernKeePassLib.Cryptography; - -namespace ModernKeePassLib.Test.Cryptography -{ - [TestClass()] - public class HmacOtpTests - { - // Using the test case from Appendix D of RFC 4226 - - const string secret = "12345678901234567890"; - - static readonly string[] expectedHOTP = new string[] - { - "755224", "287082", "359152", "969429", "338314", - "254676", "287922", "162583", "399871", "520489" - }; - - [TestMethod] - public void TestGenerate() - { - var secretBytes = Encoding.UTF8.GetBytes(secret); - - for (ulong i = 0; i < 10; i++) - { - var hotp = HmacOtp.Generate(secretBytes, i, 6, false, -1); - Assert.AreEqual(hotp, expectedHOTP[i]); - } - } - } -} diff --git a/ModernKeePassLib.Test/Cryptography/KeyDerivation/AesKdfTests.cs b/ModernKeePassLib.Test/Cryptography/KeyDerivation/AesKdfTests.cs deleted file mode 100644 index 790c1ec..0000000 --- a/ModernKeePassLib.Test/Cryptography/KeyDerivation/AesKdfTests.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; -using Microsoft.VisualStudio.TestPlatform.UnitTestFramework; -using ModernKeePassLib.Cryptography; -using ModernKeePassLib.Cryptography.KeyDerivation; -using ModernKeePassLib.Utility; - -namespace ModernKeePassLib.Test.Cryptography.KeyDerivation -{ - [TestClass] - public class AesKdfTests - { - [TestMethod] - public void TestAesKdf() - { - // Up to KeePass 2.34, the OtpKeyProv plugin used the public - // CompositeKey.TransformKeyManaged method (and a finalizing - // SHA-256 computation), which became an internal method of - // the AesKdf class in KeePass 2.35, thus OtpKeyProv now - // uses the AesKdf class; here we ensure that the results - // are the same - var r = CryptoRandom.NewWeakRandom(); - var pbKey = new byte[32]; - r.NextBytes(pbKey); - var pbSeed = new byte[32]; - r.NextBytes(pbSeed); - var uRounds = (ulong)r.Next(1, 0x7FFF); - - var pbMan = new byte[pbKey.Length]; - Array.Copy(pbKey, pbMan, pbKey.Length); - Assert.IsTrue(AesKdf.TransformKeyManaged(pbMan, pbSeed, uRounds)); - pbMan = CryptoUtil.HashSha256(pbMan); - - var kdf = new AesKdf(); - var p = kdf.GetDefaultParameters(); - p.SetUInt64(AesKdf.ParamRounds, uRounds); - p.SetByteArray(AesKdf.ParamSeed, pbSeed); - var pbKdf = kdf.Transform(pbKey, p); - - Assert.IsTrue(MemUtil.ArraysEqual(pbMan, pbKdf)); - } - } -} \ No newline at end of file diff --git a/ModernKeePassLib.Test/Cryptography/KeyDerivation/Argon2Tests.cs b/ModernKeePassLib.Test/Cryptography/KeyDerivation/Argon2Tests.cs deleted file mode 100644 index 2b66292..0000000 --- a/ModernKeePassLib.Test/Cryptography/KeyDerivation/Argon2Tests.cs +++ /dev/null @@ -1,146 +0,0 @@ -using Microsoft.VisualStudio.TestPlatform.UnitTestFramework; -using ModernKeePassLib.Cryptography.KeyDerivation; -using ModernKeePassLib.Utility; - -namespace ModernKeePassLib.Test.Cryptography.KeyDerivation -{ - [TestClass] - public class Argon2Tests - { - [TestMethod] - public void TestArgon2() - { - Argon2Kdf kdf = new Argon2Kdf(); - - // ====================================================== - // From the official Argon2 1.3 reference code package - // (test vector for Argon2d 1.3); also on - // https://tools.ietf.org/html/draft-irtf-cfrg-argon2-00 - - var p = kdf.GetDefaultParameters(); - kdf.Randomize(p); - - Assert.AreEqual(p.GetUInt32(Argon2Kdf.ParamVersion, 0), 0x13U); - - byte[] pbMsg = new byte[32]; - for (int i = 0; i < pbMsg.Length; ++i) pbMsg[i] = 1; - - p.SetUInt64(Argon2Kdf.ParamMemory, 32 * 1024); - p.SetUInt64(Argon2Kdf.ParamIterations, 3); - p.SetUInt32(Argon2Kdf.ParamParallelism, 4); - - byte[] pbSalt = new byte[16]; - for (int i = 0; i < pbSalt.Length; ++i) pbSalt[i] = 2; - p.SetByteArray(Argon2Kdf.ParamSalt, pbSalt); - - byte[] pbKey = new byte[8]; - for (int i = 0; i < pbKey.Length; ++i) pbKey[i] = 3; - p.SetByteArray(Argon2Kdf.ParamSecretKey, pbKey); - - byte[] pbAssoc = new byte[12]; - for (int i = 0; i < pbAssoc.Length; ++i) pbAssoc[i] = 4; - p.SetByteArray(Argon2Kdf.ParamAssocData, pbAssoc); - - byte[] pbExpc = new byte[32] { - 0x51, 0x2B, 0x39, 0x1B, 0x6F, 0x11, 0x62, 0x97, - 0x53, 0x71, 0xD3, 0x09, 0x19, 0x73, 0x42, 0x94, - 0xF8, 0x68, 0xE3, 0xBE, 0x39, 0x84, 0xF3, 0xC1, - 0xA1, 0x3A, 0x4D, 0xB9, 0xFA, 0xBE, 0x4A, 0xCB - }; - - byte[] pb = kdf.Transform(pbMsg, p); - - Assert.IsTrue(MemUtil.ArraysEqual(pb, pbExpc)); - - // ====================================================== - // From the official Argon2 1.3 reference code package - // (test vector for Argon2d 1.0) - - p.SetUInt32(Argon2Kdf.ParamVersion, 0x10); - - pbExpc = new byte[32] { - 0x96, 0xA9, 0xD4, 0xE5, 0xA1, 0x73, 0x40, 0x92, - 0xC8, 0x5E, 0x29, 0xF4, 0x10, 0xA4, 0x59, 0x14, - 0xA5, 0xDD, 0x1F, 0x5C, 0xBF, 0x08, 0xB2, 0x67, - 0x0D, 0xA6, 0x8A, 0x02, 0x85, 0xAB, 0xF3, 0x2B - }; - - pb = kdf.Transform(pbMsg, p); - - Assert.IsTrue(MemUtil.ArraysEqual(pb, pbExpc)); - - // ====================================================== - // From the official 'phc-winner-argon2-20151206.zip' - // (test vector for Argon2d 1.0) - - p.SetUInt64(Argon2Kdf.ParamMemory, 16 * 1024); - - pbExpc = new byte[32] { - 0x57, 0xB0, 0x61, 0x3B, 0xFD, 0xD4, 0x13, 0x1A, - 0x0C, 0x34, 0x88, 0x34, 0xC6, 0x72, 0x9C, 0x2C, - 0x72, 0x29, 0x92, 0x1E, 0x6B, 0xBA, 0x37, 0x66, - 0x5D, 0x97, 0x8C, 0x4F, 0xE7, 0x17, 0x5E, 0xD2 - }; - - pb = kdf.Transform(pbMsg, p); - - Assert.IsTrue(MemUtil.ArraysEqual(pb, pbExpc)); - - // ====================================================== - // Computed using the official 'argon2' application - // (test vectors for Argon2d 1.3) - - p = kdf.GetDefaultParameters(); - - pbMsg = StrUtil.Utf8.GetBytes("ABC1234"); - - p.SetUInt64(Argon2Kdf.ParamMemory, (1 << 11) * 1024); // 2 MB - p.SetUInt64(Argon2Kdf.ParamIterations, 2); - p.SetUInt32(Argon2Kdf.ParamParallelism, 2); - - pbSalt = StrUtil.Utf8.GetBytes("somesalt"); - p.SetByteArray(Argon2Kdf.ParamSalt, pbSalt); - - pbExpc = new byte[32] { - 0x29, 0xCB, 0xD3, 0xA1, 0x93, 0x76, 0xF7, 0xA2, - 0xFC, 0xDF, 0xB0, 0x68, 0xAC, 0x0B, 0x99, 0xBA, - 0x40, 0xAC, 0x09, 0x01, 0x73, 0x42, 0xCE, 0xF1, - 0x29, 0xCC, 0xA1, 0x4F, 0xE1, 0xC1, 0xB7, 0xA3 - }; - - pb = kdf.Transform(pbMsg, p); - - Assert.IsTrue(MemUtil.ArraysEqual(pb, pbExpc)); - - p.SetUInt64(Argon2Kdf.ParamMemory, (1 << 10) * 1024); // 1 MB - p.SetUInt64(Argon2Kdf.ParamIterations, 3); - - pbExpc = new byte[32] { - 0x7A, 0xBE, 0x1C, 0x1C, 0x8D, 0x7F, 0xD6, 0xDC, - 0x7C, 0x94, 0x06, 0x3E, 0xD8, 0xBC, 0xD8, 0x1C, - 0x2F, 0x87, 0x84, 0x99, 0x12, 0x83, 0xFE, 0x76, - 0x00, 0x64, 0xC4, 0x58, 0xA4, 0xDA, 0x35, 0x70 - }; - - pb = kdf.Transform(pbMsg, p); - - Assert.IsTrue(MemUtil.ArraysEqual(pb, pbExpc)); - - // TODO: Out of memory exception - /*p.SetUInt64(Argon2Kdf.ParamMemory, (1 << 20) * 1024); // 1 GB - p.SetUInt64(Argon2Kdf.ParamIterations, 2); - p.SetUInt32(Argon2Kdf.ParamParallelism, 3); - - pbExpc = new byte[32] { - 0xE6, 0xE7, 0xCB, 0xF5, 0x5A, 0x06, 0x93, 0x05, - 0x32, 0xBA, 0x86, 0xC6, 0x1F, 0x45, 0x17, 0x99, - 0x65, 0x41, 0x77, 0xF9, 0x30, 0x55, 0x9A, 0xE8, - 0x3D, 0x21, 0x48, 0xC6, 0x2D, 0x0C, 0x49, 0x11 - }; - - pb = kdf.Transform(pbMsg, p); - - Assert.IsTrue(MemUtil.ArraysEqual(pb, pbExpc));*/ - } - } -} \ No newline at end of file diff --git a/ModernKeePassLib.Test/Images/UnitTestLogo.scale-100.png b/ModernKeePassLib.Test/Images/UnitTestLogo.scale-100.png deleted file mode 100644 index ebd735aa9352cf97bfcbe11dc4281617d570dca4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5789 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 diff --git a/ModernKeePassLib.Test/Images/UnitTestSmallLogo.scale-100.png b/ModernKeePassLib.Test/Images/UnitTestSmallLogo.scale-100.png deleted file mode 100644 index 92dd1058fbfb70c4491d934fe5f60ff2bacd41e9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 745 zcmVGVQpR4ciyoglSwB91#GhGhn{+`40 zKOYZLtJUy5M#Hz+??BTuEf5G^US9q^+F&rqWU^c?_f@o5EFKPriyn{1Bb7>Dq2Gpm zj6OIx0Dx2~HJwfYfY0ae?(T|2B8f!u{QL|62qB-(r&KC$Z*SK_*J?F}VN52|>FMcU zFfbSl3WWjytX69-my^roXJ=<#ua{)79{TC&>GAP#Z*OmVdpj13x!rDr5V@9aw;PYg zH#axi?RKlx`izXlVo|Hrs#L0EGRd+maoTJ)o6W}O^8uh-F3h|y@YSS;jQC=}8(jWHGo1n>Siolbo|pUdSMkH^GQuh+}v@;m@7 z&Jc>C4i68Hj*hr+T!t~ezP`S?y22R0k0z$q>n|=YHa0d^OdOHmY;9-&aER;zuc8|`*G;bM$g zmK6$x05F+MNKYb!91h14^s=n;`Mg%E?Q}Y^Sj_A71_A*9C=?2bL}EUktX3=67~#xX zqtR?_Z50ZIlarIdV8G+?{C+<<@8RKLe}A70Rh>@PY&O@+Iv5NhgzoO{=BuF5Xoy6j zot>Spu7XS^v$&ZilS!#ml1in7OJvMsGGeiKZ5iKgy!-omnN0R!*h(_~++zV_yx462 b>BH;~a9|}kOw?>(00000NkvXXu0mjfaI93Z diff --git a/ModernKeePassLib.Test/Images/UnitTestSplashScreen.scale-100.png b/ModernKeePassLib.Test/Images/UnitTestSplashScreen.scale-100.png deleted file mode 100644 index 193187f108f50dd242cb5c589b02a29691b7432a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9381 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\r\n" + - "\r\n" + - "\t\r\n" + - "\t\t1.00\r\n" + - "\t\r\n" + - "\t\r\n" + - "\t\t"; - - private const string ExpectedFileEnd = "\t\r\n" + - ""; - - [TestMethod] - public void TestConstruct() - { - var expectedKeyData = new byte[32] - { - 0xC1, 0xB1, 0x12, 0x77, 0x23, 0xB8, 0x99, 0xB8, - 0xB9, 0x3B, 0x1B, 0xFF, 0x6C, 0xBE, 0xA1, 0x5B, - 0x8B, 0x99, 0xAC, 0xBD, 0x99, 0x51, 0x85, 0x95, - 0x31, 0xAA, 0x14, 0x3D, 0x95, 0xBF, 0x63, 0xFF - }; - - //var fullPath = Path.Combine(ApplicationData.Current.TemporaryFolder.Path, TestCreateFile); - var file = ApplicationData.Current.TemporaryFolder.CreateFileAsync(TestCreateFile).GetAwaiter().GetResult(); - using (var fs = file.OpenStreamForWriteAsync().GetAwaiter().GetResult()) - { - using (var sw = new StreamWriter(fs)) - { - sw.Write(ExpectedFileStart); - sw.Write(TestKey); - sw.Write(ExpectedFileEnd); - } - } - - try - { - var keyFile = new KcpKeyFile(file); - var keyData = keyFile.KeyData.ReadData(); - Assert.IsTrue(MemUtil.ArraysEqual(keyData, expectedKeyData)); - } - finally - { - file.DeleteAsync().GetAwaiter().GetResult(); - } - } - - [TestMethod] - public void TestCreate() - { - //var fullPath = Path.Combine(ApplicationData.Current.TemporaryFolder.Path, TestCreateFile); - var file = ApplicationData.Current.TemporaryFolder.CreateFileAsync(TestCreateFile).GetAwaiter().GetResult(); - KcpKeyFile.Create(file, null); - try - { - var fileContents = FileIO.ReadTextAsync(file).GetAwaiter().GetResult(); - - Assert.AreEqual(185, fileContents.Length); - Assert.IsTrue(fileContents.StartsWith(ExpectedFileStart)); - Assert.IsTrue(fileContents.EndsWith(ExpectedFileEnd)); - } - finally - { - file.DeleteAsync().GetAwaiter().GetResult(); - } - } - } -} - diff --git a/ModernKeePassLib.Test/Keys/KcpPasswordTests.cs b/ModernKeePassLib.Test/Keys/KcpPasswordTests.cs deleted file mode 100644 index 30e17ef..0000000 --- a/ModernKeePassLib.Test/Keys/KcpPasswordTests.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Microsoft.VisualStudio.TestPlatform.UnitTestFramework; -using ModernKeePassLib.Keys; -using ModernKeePassLib.Utility; - -namespace ModernKeePassLib.Test.Keys -{ - [TestClass()] - public class KcpPasswordTests - { - const string testPassword = "password"; - - [TestMethod] - public void TestConstruct() - { - var expectedHash = new byte[32] - { - 0x5E, 0x88, 0x48, 0x98, 0xDA, 0x28, 0x04, 0x71, - 0x51, 0xD0, 0xE5, 0x6F, 0x8D, 0xC6, 0x29, 0x27, - 0x73, 0x60, 0x3D, 0x0D, 0x6A, 0xAB, 0xBD, 0xD6, - 0x2A, 0x11, 0xEF, 0x72, 0x1D, 0x15, 0x42, 0xD8 - }; - - var key = new KcpPassword(testPassword); - var keyData = key.KeyData.ReadData(); - Assert.IsTrue(MemUtil.ArraysEqual(keyData, expectedHash)); - } - } -} - diff --git a/ModernKeePassLib.Test/ModernKeePass.LibTest.csproj b/ModernKeePassLib.Test/ModernKeePass.LibTest.csproj deleted file mode 100644 index 6ccaa1e..0000000 --- a/ModernKeePassLib.Test/ModernKeePass.LibTest.csproj +++ /dev/null @@ -1,201 +0,0 @@ - - - - - Debug - AnyCPU - 8.0.30703 - 2.0 - {0A4279CF-2A67-4868-9906-052E50C25F3B} - Library - Properties - ModernKeePassLib.Test - ModernKeePassLib.Test - en-US - 8.1 - 14 - 512 - {BC8A1FFA-BEE3-4634-8014-F334798102B3};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - - - Never - False - - - ED3AA34F46D03498F989901C5DB2742B65D72F60 - True - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE;NETFX_CORE - prompt - 4 - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE;NETFX_CORE - prompt - 4 - - - true - bin\ARM\Debug\ - DEBUG;TRACE;NETFX_CORE - ;2008 - full - ARM - false - prompt - true - - - bin\ARM\Release\ - TRACE;NETFX_CORE - true - ;2008 - pdbonly - ARM - false - prompt - true - - - true - bin\x64\Debug\ - DEBUG;TRACE;NETFX_CORE - ;2008 - full - x64 - false - prompt - true - - - bin\x64\Release\ - TRACE;NETFX_CORE - true - ;2008 - pdbonly - x64 - false - prompt - true - - - true - bin\x86\Debug\ - DEBUG;TRACE;NETFX_CORE - ;2008 - full - x86 - false - prompt - true - - - bin\x86\Release\ - TRACE;NETFX_CORE - true - ;2008 - pdbonly - x86 - false - prompt - true - - - True - true - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Designer - - - - - - ..\packages\Portable.BouncyCastle.1.8.3\lib\netstandard1.0\BouncyCastle.Crypto.dll - True - - - ..\packages\ModernKeePassLib.2.39.1\lib\netstandard1.2\ModernKeePassLib.dll - True - - - ..\packages\Splat.3.0.0\lib\netstandard1.1\Splat.dll - True - - - ..\packages\System.Drawing.Primitives.4.3.0\lib\netstandard1.1\System.Drawing.Primitives.dll - True - - - ..\packages\System.Runtime.InteropServices.RuntimeInformation.4.3.0\lib\win8\System.Runtime.InteropServices.RuntimeInformation.dll - True - - - ..\packages\Validation.2.4.18\lib\portable-net45+win8+wp8+wpa81\Validation.dll - True - - - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - - 14.0 - - - - \ No newline at end of file diff --git a/ModernKeePassLib.Test/Package.appxmanifest b/ModernKeePassLib.Test/Package.appxmanifest deleted file mode 100644 index 82d3b80..0000000 --- a/ModernKeePassLib.Test/Package.appxmanifest +++ /dev/null @@ -1,32 +0,0 @@ - - - - - ModernKeePassLib.Test - wismna - Images\UnitTestStoreLogo.png - ModernKeePassLib.Test - - - 6.3.0 - 6.3.0 - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/ModernKeePassLib.Test/Serialization/HashedBlockStreamTests.cs b/ModernKeePassLib.Test/Serialization/HashedBlockStreamTests.cs deleted file mode 100644 index 4956281..0000000 --- a/ModernKeePassLib.Test/Serialization/HashedBlockStreamTests.cs +++ /dev/null @@ -1,73 +0,0 @@ -using System.IO; -using Microsoft.VisualStudio.TestPlatform.UnitTestFramework; -using ModernKeePassLib.Serialization; -using ModernKeePassLib.Utility; - -namespace ModernKeePassLib.Test.Serialization -{ - [TestClass()] - public class HashedBlockStreamTests - { - static readonly byte[] data = new byte[16]; - - static readonly byte[] hashStreamData = new byte[] - { - // The first 4 bytes are an integer indicating the block index - 0x00, 0x00, 0x00, 0x00, - // Then the SHA-256 hash of the data - 0x37, 0x47, 0x08, 0xFF, 0xF7, 0x71, 0x9D, 0xD5, - 0x97, 0x9E, 0xC8, 0x75, 0xD5, 0x6C, 0xD2, 0x28, - 0x6F, 0x6D, 0x3C, 0xF7, 0xEC, 0x31, 0x7A, 0x3B, - 0x25, 0x63, 0x2A, 0xAB, 0x28, 0xEC, 0x37, 0xBB, - // then an integer that is the length of the data - 0x10, 0x00, 0x00, 0x00, - // and finally the data itself - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // Next, a terminating block - 0x01, 0x00, 0x00, 0x00, - // terminating block is indicated by a hash of all 0s... - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // ...and by a size of 0 - 0x00, 0x00, 0x00, 0x00 - }; - - [TestMethod] - public void TestRead() - { - using (var ms = new MemoryStream(hashStreamData)) - { - using (var hbs = new HashedBlockStream(ms, false)) - { - using (var br = new BinaryReader(hbs)) - { - var bytes = br.ReadBytes(data.Length); - Assert.IsTrue(MemUtil.ArraysEqual(bytes, data)); - Assert.ThrowsException(() => br.ReadByte()); - } - } - } - } - - [TestMethod] - public void TestWrite() - { - var buffer = new byte[hashStreamData.Length]; - using (var ms = new MemoryStream(buffer)) - { - using (var hbs = new HashedBlockStream(ms, true)) - { - using (var bw = new BinaryWriter(hbs)) - { - bw.Write(data); - } - } - Assert.IsTrue(MemUtil.ArraysEqual(buffer, hashStreamData)); - } - } - } -} - diff --git a/ModernKeePassLib.Test/Serialization/KdbxFileTests.cs b/ModernKeePassLib.Test/Serialization/KdbxFileTests.cs deleted file mode 100644 index 03face6..0000000 --- a/ModernKeePassLib.Test/Serialization/KdbxFileTests.cs +++ /dev/null @@ -1,171 +0,0 @@ -using System; -using System.Globalization; -using System.IO; -using System.Text; -using Microsoft.VisualStudio.TestPlatform.UnitTestFramework; -using ModernKeePassLib.Keys; -using ModernKeePassLib.Security; -using ModernKeePassLib.Serialization; -using ModernKeePassLib.Collections; - -namespace ModernKeePassLib.Test.Serialization -{ - [TestClass()] - public class KdbxFileTests - { - const string TestLocalizedAppName = "My Localized App Name"; - - const string TestDatabaseName = "My Database Name"; - const string TestDatabaseDescription = "My Database Description"; - const string TestDefaultUserName = "My Default User Name"; - const string TestColor = "#FF0000"; // Red - - const string TestRootGroupName = "My Root Group Name"; - const string TestRootGroupNotes = "My Root Group Notes"; - const string TestRootGroupDefaultAutoTypeSequence = "My Root Group Default Auto Type Sequence"; - - const string TestDatabase = "\r\n" + - "\r\n" + - "\t\r\n" + - "\t\t" + TestLocalizedAppName + "\r\n" + - "\t\t" + TestDatabaseName + "\r\n" + - "\t\t2017-10-23T08:03:55Z\r\n" + - "\t\t" + TestDatabaseDescription + "\r\n" + - "\t\t2017-10-23T08:03:55Z\r\n" + - "\t\t" + TestDefaultUserName + "\r\n" + - "\t\t2017-10-23T08:03:55Z\r\n" + - "\t\t365\r\n" + - //"\t\t" + testColor + "\r\n" + - "\t\t\r\n" + - "\t\t2017-10-23T08:03:55Z\r\n" + - "\t\t-1\r\n" + - "\t\t-1\r\n" + - "\t\t\r\n" + - "\t\t\tFalse\r\n" + - "\t\t\tFalse\r\n" + - "\t\t\tTrue\r\n" + - "\t\t\tFalse\r\n" + - "\t\t\tFalse\r\n" + - "\t\t\r\n" + - "\t\tTrue\r\n" + - "\t\tAAAAAAAAAAAAAAAAAAAAAA==\r\n" + - "\t\t2017-10-23T08:03:55Z\r\n" + - "\t\tAAAAAAAAAAAAAAAAAAAAAA==\r\n" + - "\t\t2017-10-23T08:03:55Z\r\n" + - "\t\t10\r\n" + - "\t\t6291456\r\n" + - "\t\tAAAAAAAAAAAAAAAAAAAAAA==\r\n" + - "\t\tAAAAAAAAAAAAAAAAAAAAAA==\r\n" + - "\t\t\r\n" + - "\t\t\r\n" + - "\t\r\n" + - "\t\r\n" + - "\t\t\r\n" + - "\t\t\tAAAAAAAAAAAAAAAAAAAAAA==\r\n" + - "\t\t\t" + TestRootGroupName + "\r\n" + - "\t\t\t" + TestRootGroupNotes + "\r\n" + - "\t\t\t49\r\n" + - "\t\t\t\r\n" + - "\t\t\t\t2017-10-23T08:03:55Z\r\n" + - "\t\t\t\t2017-10-23T08:03:55Z\r\n" + - "\t\t\t\t2017-10-23T08:03:55Z\r\n" + - "\t\t\t\t2017-10-23T08:03:55Z\r\n" + - "\t\t\t\tFalse\r\n" + - "\t\t\t\t0\r\n" + - "\t\t\t\t2017-10-23T08:03:55Z\r\n" + - "\t\t\t\r\n" + - "\t\t\tTrue\r\n" + - "\t\t\t" + TestRootGroupDefaultAutoTypeSequence + "\r\n" + - "\t\t\tnull\r\n" + - "\t\t\tnull\r\n" + - "\t\t\tAAAAAAAAAAAAAAAAAAAAAA==\r\n" + - "\t\t\r\n" + - "\t\t\r\n" + - "\t\r\n" + - ""; - - const string TestDate = "2017-10-23T08:03:55Z"; - - [TestMethod] - public void TestLoad() - { - var database = new PwDatabase(); - using (var ms = new MemoryStream(Encoding.UTF8.GetBytes(TestDatabase))) - { - var file = new KdbxFile(database); - file.Load(ms, KdbxFormat.PlainXml, null); - } - //Assert.That(database.Color.ToArgb(), Is.EqualTo(Color.Red.ToArgb())); - Assert.AreEqual(database.Compression, PwCompressionAlgorithm.GZip); - //Assert.That (database.CustomData, Is.EqualTo ()); - Assert.IsTrue(database.CustomIcons.Count == 0); - } - - [TestMethod] - public void TestSave() - { - var buffer = new byte[4096]; - using (var ms = new MemoryStream(buffer)) - { - var database = new PwDatabase(); - database.New(new IOConnectionInfo(), new CompositeKey()); - var date = DateTime.Parse(TestDate, CultureInfo.CurrentCulture, DateTimeStyles.AdjustToUniversal); - //var date = DateTime.UtcNow; - PwDatabase.LocalizedAppName = TestLocalizedAppName; - database.Name = TestDatabaseName; - database.NameChanged = date; - database.Description = TestDatabaseDescription; - database.DescriptionChanged = date; - database.DefaultUserName = TestDefaultUserName; - database.DefaultUserNameChanged = date; - //database.Color = Color.Red; - database.MasterKeyChanged = date; - database.RecycleBinChanged = date; - database.EntryTemplatesGroupChanged = date; - database.RootGroup.Uuid = PwUuid.Zero; - database.RootGroup.Name = TestRootGroupName; - database.RootGroup.Notes = TestRootGroupNotes; - database.RootGroup.DefaultAutoTypeSequence = TestRootGroupDefaultAutoTypeSequence; - database.RootGroup.CreationTime = date; - database.RootGroup.LastModificationTime = date; - database.RootGroup.LastAccessTime = date; - database.RootGroup.ExpiryTime = date; - database.RootGroup.LocationChanged = date; - var file = new KdbxFile(database); - file.Save(ms, null, KdbxFormat.PlainXml, null); - } - var fileContents = Encoding.UTF8.GetString(buffer, 0, buffer.Length).Replace("\0", ""); - if (typeof(KdbxFile).Namespace.StartsWith("KeePassLib.")) - { - // Upstream KeePassLib does not specify line endings for XmlTextWriter, - // so it uses native line endings. - fileContents = fileContents.Replace("\n", "\r\n"); - } - Assert.AreEqual(fileContents, TestDatabase); - } - - [TestMethod] - public void TestSearch() - { - var database = new PwDatabase(); - using (var ms = new MemoryStream(Encoding.UTF8.GetBytes(TestDatabase))) - { - var file = new KdbxFile(database); - file.Load(ms, KdbxFormat.PlainXml, null); - } - var sp = new SearchParameters() - { - SearchString = "sfsoiwsefsi" - }; - var listStorage = new PwObjectList(); - database.RootGroup.SearchEntries(sp, listStorage); - Assert.AreEqual(0U, listStorage.UCount); - var entry = new PwEntry(true, true); - entry.Strings.Set("Title", new ProtectedString(false, "NaMe")); - database.RootGroup.AddEntry(entry, true); - sp.SearchString = "name"; - database.RootGroup.SearchEntries(sp, listStorage); - Assert.AreEqual(1U, listStorage.UCount); - } - } -} diff --git a/ModernKeePassLib.Test/Utility/GfxUtilTests.cs b/ModernKeePassLib.Test/Utility/GfxUtilTests.cs deleted file mode 100644 index fe28430..0000000 --- a/ModernKeePassLib.Test/Utility/GfxUtilTests.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using Microsoft.VisualStudio.TestPlatform.UnitTestFramework; -using ModernKeePassLib.Utility; - -namespace ModernKeePassLib.Test.Utility -{ - [TestClass] - public class GfxUtilTests - { - // 16x16 all white PNG file, base64 encoded - const string testImageData = - "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAACXBIWXMAAAsTAAA" + - "LEwEAmpwYAAAAB3RJTUUH3wMOFgIgmTCUMQAAABl0RVh0Q29tbWVudABDcmVhdG" + - "VkIHdpdGggR0lNUFeBDhcAAAAaSURBVCjPY/z//z8DKYCJgUQwqmFUw9DRAABVb" + - "QMdny4VogAAAABJRU5ErkJggg=="; - - [TestMethod] - public void TestLoadImage () - { - var testData = Convert.FromBase64String (testImageData); - var image = GfxUtil.ScaleImage(testData, 16, 16); - //var image = GfxUtil.LoadImage(testData); - Assert.AreEqual(image.Width, 16); - Assert.AreEqual(image.Height, 16); - } - } -} diff --git a/ModernKeePassLib.Test/Utility/MemUtilTests.cs b/ModernKeePassLib.Test/Utility/MemUtilTests.cs deleted file mode 100644 index eccf514..0000000 --- a/ModernKeePassLib.Test/Utility/MemUtilTests.cs +++ /dev/null @@ -1,89 +0,0 @@ -using System.Text; -using Microsoft.VisualStudio.TestPlatform.UnitTestFramework; -using ModernKeePassLib.Cryptography; -using ModernKeePassLib.Utility; - -namespace ModernKeePassLib.Test.Utility -{ - [TestClass] - public class MemUtilTests - { - private byte[] _pb = CryptoRandom.Instance.GetRandomBytes((uint)CryptoRandom.NewWeakRandom().Next(0, 0x2FFFF)); - - [TestMethod] - public void TestGzip() - { - var pbCompressed = MemUtil.Compress(_pb); - Assert.IsTrue(MemUtil.ArraysEqual(MemUtil.Decompress(pbCompressed), _pb)); - } - - [TestMethod] - public void TestMemUtil() - { - Encoding enc = StrUtil.Utf8; - _pb = enc.GetBytes("012345678901234567890a"); - byte[] pbN = enc.GetBytes("9012"); - Assert.AreEqual(MemUtil.IndexOf(_pb, pbN), 9); - - pbN = enc.GetBytes("01234567890123"); - Assert.AreEqual(MemUtil.IndexOf(_pb, pbN), 0); - - pbN = enc.GetBytes("a"); - Assert.AreEqual(MemUtil.IndexOf(_pb, pbN), 21); - - pbN = enc.GetBytes("0a"); - Assert.AreEqual(MemUtil.IndexOf(_pb, pbN), 20); - - pbN = enc.GetBytes("1"); - Assert.AreEqual(MemUtil.IndexOf(_pb, pbN), 1); - - pbN = enc.GetBytes("b"); - Assert.IsTrue(MemUtil.IndexOf(_pb, pbN) < 0); - - pbN = enc.GetBytes("012b"); - Assert.IsTrue(MemUtil.IndexOf(_pb, pbN) < 0); - } - - [TestMethod] - public void TestBase32() - { - byte[] pbRes = MemUtil.ParseBase32("MY======"); - byte[] pbExp = Encoding.UTF8.GetBytes("f"); - Assert.IsTrue(MemUtil.ArraysEqual(pbRes, pbExp)); - - pbRes = MemUtil.ParseBase32("MZXQ===="); - pbExp = Encoding.UTF8.GetBytes("fo"); - Assert.IsTrue(MemUtil.ArraysEqual(pbRes, pbExp)); - - pbRes = MemUtil.ParseBase32("MZXW6==="); - pbExp = Encoding.UTF8.GetBytes("foo"); - Assert.IsTrue(MemUtil.ArraysEqual(pbRes, pbExp)); - - pbRes = MemUtil.ParseBase32("MZXW6YQ="); - pbExp = Encoding.UTF8.GetBytes("foob"); - Assert.IsTrue(MemUtil.ArraysEqual(pbRes, pbExp)); - - pbRes = MemUtil.ParseBase32("MZXW6YTB"); - pbExp = Encoding.UTF8.GetBytes("fooba"); - Assert.IsTrue(MemUtil.ArraysEqual(pbRes, pbExp)); - - pbRes = MemUtil.ParseBase32("MZXW6YTBOI======"); - pbExp = Encoding.UTF8.GetBytes("foobar"); - Assert.IsTrue(MemUtil.ArraysEqual(pbRes, pbExp)); - - pbRes = MemUtil.ParseBase32("JNSXSIDQOJXXM2LEMVZCAYTBONSWIIDPNYQG63TFFV2GS3LFEBYGC43TO5XXEZDTFY======"); - pbExp = Encoding.UTF8.GetBytes("Key provider based on one-time passwords."); - Assert.IsTrue(MemUtil.ArraysEqual(pbRes, pbExp)); - } - - [TestMethod] - public void TestMemUtil2() - { - var i = 0 - 0x10203040; - var pbRes = MemUtil.Int32ToBytes(i); - Assert.AreEqual(MemUtil.ByteArrayToHexString(pbRes), "C0CFDFEF"); - Assert.AreEqual(MemUtil.BytesToUInt32(pbRes), (uint)i); - Assert.AreEqual(MemUtil.BytesToInt32(pbRes), i); - } - } -} \ No newline at end of file diff --git a/ModernKeePassLib.Test/packages.config b/ModernKeePassLib.Test/packages.config deleted file mode 100644 index 0a38eab..0000000 --- a/ModernKeePassLib.Test/packages.config +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/ModernKeePassLib/Collections/AutoTypeConfig.cs b/ModernKeePassLib/Collections/AutoTypeConfig.cs deleted file mode 100644 index b53f9b7..0000000 --- a/ModernKeePassLib/Collections/AutoTypeConfig.cs +++ /dev/null @@ -1,244 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; -using System.Diagnostics; - -using ModernKeePassLib.Interfaces; - -namespace ModernKeePassLib.Collections -{ - [Flags] - public enum AutoTypeObfuscationOptions - { - None = 0, - UseClipboard = 1 - } - - public sealed class AutoTypeAssociation : IEquatable, - IDeepCloneable - { - private string m_strWindow = string.Empty; - public string WindowName - { - get { return m_strWindow; } - set - { - Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); - m_strWindow = value; - } - } - - private string m_strSequence = string.Empty; - public string Sequence - { - get { return m_strSequence; } - set - { - Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); - m_strSequence = value; - } - } - - public AutoTypeAssociation() { } - - public AutoTypeAssociation(string strWindow, string strSeq) - { - if(strWindow == null) throw new ArgumentNullException("strWindow"); - if(strSeq == null) throw new ArgumentNullException("strSeq"); - - m_strWindow = strWindow; - m_strSequence = strSeq; - } - - public bool Equals(AutoTypeAssociation other) - { - if(other == null) return false; - - if(m_strWindow != other.m_strWindow) return false; - if(m_strSequence != other.m_strSequence) return false; - - return true; - } - - public AutoTypeAssociation CloneDeep() - { - return (AutoTypeAssociation)this.MemberwiseClone(); - } - } - - ///

- /// A list of auto-type associations. - /// - public sealed class AutoTypeConfig : IEquatable, - IDeepCloneable - { - private bool m_bEnabled = true; - private AutoTypeObfuscationOptions m_atooObfuscation = - AutoTypeObfuscationOptions.None; - private string m_strDefaultSequence = string.Empty; - private List m_lWindowAssocs = - new List(); - - /// - /// Specify whether auto-type is enabled or not. - /// - public bool Enabled - { - get { return m_bEnabled; } - set { m_bEnabled = value; } - } - - /// - /// Specify whether the typing should be obfuscated. - /// - public AutoTypeObfuscationOptions ObfuscationOptions - { - get { return m_atooObfuscation; } - set { m_atooObfuscation = value; } - } - - /// - /// The default keystroke sequence that is auto-typed if - /// no matching window is found in the Associations - /// container. - /// - public string DefaultSequence - { - get { return m_strDefaultSequence; } - set - { - Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); - m_strDefaultSequence = value; - } - } - - /// - /// Get all auto-type window/keystroke sequence pairs. - /// - public IEnumerable Associations - { - get { return m_lWindowAssocs; } - } - - public int AssociationsCount - { - get { return m_lWindowAssocs.Count; } - } - - /// - /// Construct a new auto-type associations list. - /// - public AutoTypeConfig() - { - } - - /// - /// Remove all associations. - /// - public void Clear() - { - m_lWindowAssocs.Clear(); - } - - /// - /// Clone the auto-type associations list. - /// - /// New, cloned object. - public AutoTypeConfig CloneDeep() - { - AutoTypeConfig newCfg = new AutoTypeConfig(); - - newCfg.m_bEnabled = m_bEnabled; - newCfg.m_atooObfuscation = m_atooObfuscation; - newCfg.m_strDefaultSequence = m_strDefaultSequence; - - foreach(AutoTypeAssociation a in m_lWindowAssocs) - newCfg.Add(a.CloneDeep()); - - return newCfg; - } - - public bool Equals(AutoTypeConfig other) - { - if(other == null) { Debug.Assert(false); return false; } - - if(m_bEnabled != other.m_bEnabled) return false; - if(m_atooObfuscation != other.m_atooObfuscation) return false; - if(m_strDefaultSequence != other.m_strDefaultSequence) return false; - - if(m_lWindowAssocs.Count != other.m_lWindowAssocs.Count) return false; - for(int i = 0; i < m_lWindowAssocs.Count; ++i) - { - if(!m_lWindowAssocs[i].Equals(other.m_lWindowAssocs[i])) - return false; - } - - return true; - } - - public AutoTypeAssociation GetAt(int iIndex) - { - if((iIndex < 0) || (iIndex >= m_lWindowAssocs.Count)) - throw new ArgumentOutOfRangeException("iIndex"); - - return m_lWindowAssocs[iIndex]; - } - - public void Add(AutoTypeAssociation a) - { - if(a == null) { Debug.Assert(false); throw new ArgumentNullException("a"); } - - m_lWindowAssocs.Add(a); - } - - public void Insert(int iIndex, AutoTypeAssociation a) - { - if((iIndex < 0) || (iIndex > m_lWindowAssocs.Count)) - throw new ArgumentOutOfRangeException("iIndex"); - if(a == null) { Debug.Assert(false); throw new ArgumentNullException("a"); } - - m_lWindowAssocs.Insert(iIndex, a); - } - - public void RemoveAt(int iIndex) - { - if((iIndex < 0) || (iIndex >= m_lWindowAssocs.Count)) - throw new ArgumentOutOfRangeException("iIndex"); - - m_lWindowAssocs.RemoveAt(iIndex); - } - - // public void Sort() - // { - // m_lWindowAssocs.Sort(AutoTypeConfig.AssocCompareFn); - // } - - // private static int AssocCompareFn(AutoTypeAssociation x, - // AutoTypeAssociation y) - // { - // if(x == null) { Debug.Assert(false); return ((y == null) ? 0 : -1); } - // if(y == null) { Debug.Assert(false); return 1; } - // int cn = x.WindowName.CompareTo(y.WindowName); - // if(cn != 0) return cn; - // return x.Sequence.CompareTo(y.Sequence); - // } - } -} diff --git a/ModernKeePassLib/Collections/ProtectedBinaryDictionary.cs b/ModernKeePassLib/Collections/ProtectedBinaryDictionary.cs deleted file mode 100644 index 927d35c..0000000 --- a/ModernKeePassLib/Collections/ProtectedBinaryDictionary.cs +++ /dev/null @@ -1,172 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Text; -using System.Diagnostics; - -using ModernKeePassLib.Interfaces; -using ModernKeePassLib.Security; - -#if KeePassLibSD -using KeePassLibSD; -#endif - -namespace ModernKeePassLib.Collections -{ - /// - /// A list of ProtectedBinary objects (dictionary). - /// - public sealed class ProtectedBinaryDictionary : - IDeepCloneable, - IEnumerable> - { - private SortedDictionary m_vBinaries = - new SortedDictionary(); - - /// - /// Get the number of binaries in this entry. - /// - public uint UCount - { - get { return (uint)m_vBinaries.Count; } - } - - /// - /// Construct a new list of protected binaries. - /// - public ProtectedBinaryDictionary() - { - } - - IEnumerator IEnumerable.GetEnumerator() - { - return m_vBinaries.GetEnumerator(); - } - - public IEnumerator> GetEnumerator() - { - return m_vBinaries.GetEnumerator(); - } - - public void Clear() - { - m_vBinaries.Clear(); - } - - /// - /// Clone the current ProtectedBinaryList object, including all - /// stored protected strings. - /// - /// New ProtectedBinaryList object. - public ProtectedBinaryDictionary CloneDeep() - { - ProtectedBinaryDictionary plNew = new ProtectedBinaryDictionary(); - - foreach(KeyValuePair kvpBin in m_vBinaries) - { - // ProtectedBinary objects are immutable - plNew.Set(kvpBin.Key, kvpBin.Value); - } - - return plNew; - } - - public bool EqualsDictionary(ProtectedBinaryDictionary dict) - { - if(dict == null) { Debug.Assert(false); return false; } - - if(m_vBinaries.Count != dict.m_vBinaries.Count) return false; - - foreach(KeyValuePair kvp in m_vBinaries) - { - ProtectedBinary pb = dict.Get(kvp.Key); - if(pb == null) return false; - if(!pb.Equals(kvp.Value)) return false; - } - - return true; - } - - /// - /// Get one of the stored binaries. - /// - /// Binary identifier. - /// Protected binary. If the binary identified by - /// cannot be found, the function - /// returns null. - /// Thrown if the input - /// parameter is null. - public ProtectedBinary Get(string strName) - { - Debug.Assert(strName != null); if(strName == null) throw new ArgumentNullException("strName"); - - ProtectedBinary pb; - if(m_vBinaries.TryGetValue(strName, out pb)) return pb; - - return null; - } - - /// - /// Set a binary object. - /// - /// Identifier of the binary field to modify. - /// New value. This parameter must not be null. - /// Thrown if any of the input - /// parameters is null. - public void Set(string strField, ProtectedBinary pbNewValue) - { - Debug.Assert(strField != null); if(strField == null) throw new ArgumentNullException("strField"); - Debug.Assert(pbNewValue != null); if(pbNewValue == null) throw new ArgumentNullException("pbNewValue"); - - m_vBinaries[strField] = pbNewValue; - } - - /// - /// Remove a binary object. - /// - /// Identifier of the binary field to remove. - /// Returns true if the object has been successfully - /// removed, otherwise false. - /// Thrown if the input parameter - /// is null. - public bool Remove(string strField) - { - Debug.Assert(strField != null); if(strField == null) throw new ArgumentNullException("strField"); - - return m_vBinaries.Remove(strField); - } - - public string KeysToString() - { - if(m_vBinaries.Count == 0) return string.Empty; - - StringBuilder sb = new StringBuilder(); - foreach(KeyValuePair kvp in m_vBinaries) - { - if(sb.Length > 0) sb.Append(", "); - sb.Append(kvp.Key); - } - - return sb.ToString(); - } - } -} diff --git a/ModernKeePassLib/Collections/ProtectedBinarySet.cs b/ModernKeePassLib/Collections/ProtectedBinarySet.cs deleted file mode 100644 index 9cfac59..0000000 --- a/ModernKeePassLib/Collections/ProtectedBinarySet.cs +++ /dev/null @@ -1,174 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics; -using System.Text; - -using ModernKeePassLib.Delegates; -using ModernKeePassLib.Security; - -namespace ModernKeePassLib.Collections -{ - internal sealed class ProtectedBinarySet : IEnumerable> - { - private Dictionary m_d = - new Dictionary(); - - public ProtectedBinarySet() - { - } - - IEnumerator IEnumerable.GetEnumerator() - { - return m_d.GetEnumerator(); - } - - public IEnumerator> GetEnumerator() - { - return m_d.GetEnumerator(); - } - - public void Clear() - { - m_d.Clear(); - } - - private int GetFreeID() - { - int i = m_d.Count; - while(m_d.ContainsKey(i)) { ++i; } - Debug.Assert(i == m_d.Count); // m_d.Count should be free - return i; - } - - public ProtectedBinary Get(int iID) - { - ProtectedBinary pb; - if(m_d.TryGetValue(iID, out pb)) return pb; - - // Debug.Assert(false); // No assert - return null; - } - - public int Find(ProtectedBinary pb) - { - if(pb == null) { Debug.Assert(false); return -1; } - - // Fast search by reference - foreach(KeyValuePair kvp in m_d) - { - if(object.ReferenceEquals(pb, kvp.Value)) - { - Debug.Assert(pb.Equals(kvp.Value)); - return kvp.Key; - } - } - - // Slow search by content - foreach(KeyValuePair kvp in m_d) - { - if(pb.Equals(kvp.Value)) return kvp.Key; - } - - // Debug.Assert(false); // No assert - return -1; - } - - public void Set(int iID, ProtectedBinary pb) - { - if(iID < 0) { Debug.Assert(false); return; } - if(pb == null) { Debug.Assert(false); return; } - - m_d[iID] = pb; - } - - public void Add(ProtectedBinary pb) - { - if(pb == null) { Debug.Assert(false); return; } - - int i = Find(pb); - if(i >= 0) return; // Exists already - - i = GetFreeID(); - m_d[i] = pb; - } - - public void AddFrom(ProtectedBinaryDictionary d) - { - if(d == null) { Debug.Assert(false); return; } - - foreach(KeyValuePair kvp in d) - { - Add(kvp.Value); - } - } - - public void AddFrom(PwGroup pg) - { - if(pg == null) { Debug.Assert(false); return; } - - EntryHandler eh = delegate(PwEntry pe) - { - if(pe == null) { Debug.Assert(false); return true; } - - AddFrom(pe.Binaries); - foreach(PwEntry peHistory in pe.History) - { - if(peHistory == null) { Debug.Assert(false); continue; } - AddFrom(peHistory.Binaries); - } - - return true; - }; - - pg.TraverseTree(TraversalMethod.PreOrder, null, eh); - } - - public ProtectedBinary[] ToArray() - { - int n = m_d.Count; - ProtectedBinary[] v = new ProtectedBinary[n]; - - foreach(KeyValuePair kvp in m_d) - { - if((kvp.Key < 0) || (kvp.Key >= n)) - { - Debug.Assert(false); - throw new InvalidOperationException(); - } - - v[kvp.Key] = kvp.Value; - } - - for(int i = 0; i < n; ++i) - { - if(v[i] == null) - { - Debug.Assert(false); - throw new InvalidOperationException(); - } - } - - return v; - } - } -} diff --git a/ModernKeePassLib/Collections/ProtectedStringDictionary.cs b/ModernKeePassLib/Collections/ProtectedStringDictionary.cs deleted file mode 100644 index b60b8aa..0000000 --- a/ModernKeePassLib/Collections/ProtectedStringDictionary.cs +++ /dev/null @@ -1,298 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics; -using System.Text; - -using ModernKeePassLib.Interfaces; -using ModernKeePassLib.Security; -using ModernKeePassLib.Utility; - -#if KeePassLibSD -using KeePassLibSD; -#endif - -namespace ModernKeePassLib.Collections -{ - /// - /// A list of ProtectedString objects (dictionary). - /// - public sealed class ProtectedStringDictionary : - IDeepCloneable, - IEnumerable> - { - private SortedDictionary m_vStrings = - new SortedDictionary(); - - /// - /// Get the number of strings in this entry. - /// - public uint UCount - { - get { return (uint)m_vStrings.Count; } - } - - /// - /// Construct a new list of protected strings. - /// - public ProtectedStringDictionary() - { - } - - IEnumerator IEnumerable.GetEnumerator() - { - return m_vStrings.GetEnumerator(); - } - - public IEnumerator> GetEnumerator() - { - return m_vStrings.GetEnumerator(); - } - - public void Clear() - { - m_vStrings.Clear(); - } - - /// - /// Clone the current ProtectedStringList object, including all - /// stored protected strings. - /// - /// New ProtectedStringList object. - public ProtectedStringDictionary CloneDeep() - { - ProtectedStringDictionary plNew = new ProtectedStringDictionary(); - - foreach(KeyValuePair kvpStr in m_vStrings) - { - // ProtectedString objects are immutable - plNew.Set(kvpStr.Key, kvpStr.Value); - } - - return plNew; - } - - [Obsolete] - public bool EqualsDictionary(ProtectedStringDictionary dict) - { - return EqualsDictionary(dict, PwCompareOptions.None, MemProtCmpMode.None); - } - - [Obsolete] - public bool EqualsDictionary(ProtectedStringDictionary dict, - MemProtCmpMode mpCompare) - { - return EqualsDictionary(dict, PwCompareOptions.None, mpCompare); - } - - public bool EqualsDictionary(ProtectedStringDictionary dict, - PwCompareOptions pwOpt, MemProtCmpMode mpCompare) - { - if(dict == null) { Debug.Assert(false); return false; } - - bool bNeEqStd = ((pwOpt & PwCompareOptions.NullEmptyEquivStd) != - PwCompareOptions.None); - if(!bNeEqStd) - { - if(m_vStrings.Count != dict.m_vStrings.Count) return false; - } - - foreach(KeyValuePair kvp in m_vStrings) - { - bool bStdField = PwDefs.IsStandardField(kvp.Key); - ProtectedString ps = dict.Get(kvp.Key); - - if(bNeEqStd && (ps == null) && bStdField) - ps = ProtectedString.Empty; - - if(ps == null) return false; - - if(mpCompare == MemProtCmpMode.Full) - { - if(ps.IsProtected != kvp.Value.IsProtected) return false; - } - else if(mpCompare == MemProtCmpMode.CustomOnly) - { - if(!bStdField && (ps.IsProtected != kvp.Value.IsProtected)) - return false; - } - - if(!ps.Equals(kvp.Value, false)) return false; - } - - if(bNeEqStd) - { - foreach(KeyValuePair kvp in dict.m_vStrings) - { - ProtectedString ps = Get(kvp.Key); - - if(ps != null) continue; // Compared previously - if(!PwDefs.IsStandardField(kvp.Key)) return false; - if(!kvp.Value.IsEmpty) return false; - } - } - - return true; - } - - /// - /// Get one of the protected strings. - /// - /// String identifier. - /// Protected string. If the string identified by - /// cannot be found, the function - /// returns null. - /// Thrown if the input parameter - /// is null. - public ProtectedString Get(string strName) - { - Debug.Assert(strName != null); if(strName == null) throw new ArgumentNullException("strName"); - - ProtectedString ps; - if(m_vStrings.TryGetValue(strName, out ps)) return ps; - - return null; - } - - /// - /// Get one of the protected strings. The return value is never null. - /// If the requested string cannot be found, an empty protected string - /// object is returned. - /// - /// String identifier. - /// Returns a protected string object. If the standard string - /// has not been set yet, the return value is an empty string (""). - /// Thrown if the input - /// parameter is null. - public ProtectedString GetSafe(string strName) - { - Debug.Assert(strName != null); if(strName == null) throw new ArgumentNullException("strName"); - - ProtectedString ps; - if(m_vStrings.TryGetValue(strName, out ps)) return ps; - - return ProtectedString.Empty; - } - - /// - /// Test if a named string exists. - /// - /// Name of the string to try. - /// Returns true if the string exists, otherwise false. - /// Thrown if - /// is null. - public bool Exists(string strName) - { - Debug.Assert(strName != null); if(strName == null) throw new ArgumentNullException("strName"); - - return m_vStrings.ContainsKey(strName); - } - - /// - /// Get one of the protected strings. If the string doesn't exist, the - /// return value is an empty string (""). - /// - /// Name of the requested string. - /// Requested string value or an empty string, if the named - /// string doesn't exist. - /// Thrown if the input - /// parameter is null. - public string ReadSafe(string strName) - { - Debug.Assert(strName != null); if(strName == null) throw new ArgumentNullException("strName"); - - ProtectedString ps; - if(m_vStrings.TryGetValue(strName, out ps)) - return ps.ReadString(); - - return string.Empty; - } - - /// - /// Get one of the entry strings. If the string doesn't exist, the - /// return value is an empty string (""). If the string is - /// in-memory protected, the return value is PwDefs.HiddenPassword. - /// - /// Name of the requested string. - /// Returns the requested string in plain-text or - /// PwDefs.HiddenPassword if the string cannot be found. - /// Thrown if the input - /// parameter is null. - public string ReadSafeEx(string strName) - { - Debug.Assert(strName != null); if(strName == null) throw new ArgumentNullException("strName"); - - ProtectedString ps; - if(m_vStrings.TryGetValue(strName, out ps)) - { - if(ps.IsProtected) return PwDefs.HiddenPassword; - return ps.ReadString(); - } - - return string.Empty; - } - - /// - /// Set a string. - /// - /// Identifier of the string field to modify. - /// New value. This parameter must not be null. - /// Thrown if one of the input - /// parameters is null. - public void Set(string strField, ProtectedString psNewValue) - { - Debug.Assert(strField != null); if(strField == null) throw new ArgumentNullException("strField"); - Debug.Assert(psNewValue != null); if(psNewValue == null) throw new ArgumentNullException("psNewValue"); - - m_vStrings[strField] = psNewValue; - } - - /// - /// Delete a string. - /// - /// Name of the string field to delete. - /// Returns true if the field has been successfully - /// removed, otherwise the return value is false. - /// Thrown if the input - /// parameter is null. - public bool Remove(string strField) - { - Debug.Assert(strField != null); if(strField == null) throw new ArgumentNullException("strField"); - - return m_vStrings.Remove(strField); - } - - public List GetKeys() - { - return new List(m_vStrings.Keys); - } - - public void EnableProtection(string strField, bool bProtect) - { - ProtectedString ps = Get(strField); - if(ps == null) return; // Nothing to do, no assert - - if(ps.IsProtected != bProtect) - Set(strField, ps.WithProtection(bProtect)); - } - } -} diff --git a/ModernKeePassLib/Collections/PwObjectList.cs b/ModernKeePassLib/Collections/PwObjectList.cs deleted file mode 100644 index 9d97763..0000000 --- a/ModernKeePassLib/Collections/PwObjectList.cs +++ /dev/null @@ -1,380 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics; - -using ModernKeePassLib.Interfaces; - -namespace ModernKeePassLib.Collections -{ - /// - /// List of objects that implement IDeepCloneable, - /// and cannot be null. - /// - /// Type specifier. - public sealed class PwObjectList : IEnumerable - where T : class, IDeepCloneable - { - private List m_vObjects = new List(); - - /// - /// Get number of objects in this list. - /// - public uint UCount - { - get { return (uint)m_vObjects.Count; } - } - - /// - /// Construct a new list of objects. - /// - public PwObjectList() - { - } - - IEnumerator IEnumerable.GetEnumerator() - { - return m_vObjects.GetEnumerator(); - } - - public IEnumerator GetEnumerator() - { - return m_vObjects.GetEnumerator(); - } - - public void Clear() - { - // Do not destroy contained objects! - m_vObjects.Clear(); - } - - /// - /// Clone the current PwObjectList, including all - /// stored objects (deep copy). - /// - /// New PwObjectList. - public PwObjectList CloneDeep() - { - PwObjectList pl = new PwObjectList(); - - foreach(T po in m_vObjects) - pl.Add(po.CloneDeep()); - - return pl; - } - - public PwObjectList CloneShallow() - { - PwObjectList tNew = new PwObjectList(); - - foreach(T po in m_vObjects) tNew.Add(po); - - return tNew; - } - - public List CloneShallowToList() - { - PwObjectList tNew = CloneShallow(); - return tNew.m_vObjects; - } - - /// - /// Add an object to this list. - /// - /// Object to be added. - /// Thrown if the input - /// parameter is null. - public void Add(T pwObject) - { - Debug.Assert(pwObject != null); - if(pwObject == null) throw new ArgumentNullException("pwObject"); - - m_vObjects.Add(pwObject); - } - - public void Add(PwObjectList vObjects) - { - Debug.Assert(vObjects != null); - if(vObjects == null) throw new ArgumentNullException("vObjects"); - - foreach(T po in vObjects) - { - m_vObjects.Add(po); - } - } - - public void Add(List vObjects) - { - Debug.Assert(vObjects != null); - if(vObjects == null) throw new ArgumentNullException("vObjects"); - - foreach(T po in vObjects) - { - m_vObjects.Add(po); - } - } - - public void Insert(uint uIndex, T pwObject) - { - Debug.Assert(pwObject != null); - if(pwObject == null) throw new ArgumentNullException("pwObject"); - - m_vObjects.Insert((int)uIndex, pwObject); - } - - /// - /// Get an object of the list. - /// - /// Index of the object to get. Must be valid, otherwise an - /// exception is thrown. - /// Reference to an existing T object. Is never null. - public T GetAt(uint uIndex) - { - Debug.Assert(uIndex < m_vObjects.Count); - if(uIndex >= m_vObjects.Count) throw new ArgumentOutOfRangeException("uIndex"); - - return m_vObjects[(int)uIndex]; - } - - public void SetAt(uint uIndex, T pwObject) - { - Debug.Assert(pwObject != null); - if(pwObject == null) throw new ArgumentNullException("pwObject"); - if(uIndex >= (uint)m_vObjects.Count) - throw new ArgumentOutOfRangeException("uIndex"); - - m_vObjects[(int)uIndex] = pwObject; - } - - /// - /// Get a range of objects. - /// - /// Index of the first object to be - /// returned (inclusive). - /// Index of the last object to be - /// returned (inclusive). - /// - public List GetRange(uint uStartIndexIncl, uint uEndIndexIncl) - { - if(uStartIndexIncl >= (uint)m_vObjects.Count) - throw new ArgumentOutOfRangeException("uStartIndexIncl"); - if(uEndIndexIncl >= (uint)m_vObjects.Count) - throw new ArgumentOutOfRangeException("uEndIndexIncl"); - if(uStartIndexIncl > uEndIndexIncl) - throw new ArgumentException(); - - List list = new List((int)(uEndIndexIncl - uStartIndexIncl) + 1); - for(uint u = uStartIndexIncl; u <= uEndIndexIncl; ++u) - { - list.Add(m_vObjects[(int)u]); - } - - return list; - } - - public int IndexOf(T pwReference) - { - Debug.Assert(pwReference != null); if(pwReference == null) throw new ArgumentNullException("pwReference"); - - return m_vObjects.IndexOf(pwReference); - } - - /// - /// Delete an object of this list. The object to be deleted is identified - /// by a reference handle. - /// - /// Reference of the object to be deleted. - /// Returns true if the object was deleted, false if - /// the object wasn't found in this list. - /// Thrown if the input - /// parameter is null. - public bool Remove(T pwReference) - { - Debug.Assert(pwReference != null); if(pwReference == null) throw new ArgumentNullException("pwReference"); - - return m_vObjects.Remove(pwReference); - } - - public void RemoveAt(uint uIndex) - { - m_vObjects.RemoveAt((int)uIndex); - } - - /// - /// Move an object up or down. - /// - /// The object to be moved. - /// Move one up. If false, move one down. - public void MoveOne(T tObject, bool bUp) - { - Debug.Assert(tObject != null); - if(tObject == null) throw new ArgumentNullException("tObject"); - - int nCount = m_vObjects.Count; - if(nCount <= 1) return; - - int nIndex = m_vObjects.IndexOf(tObject); - if(nIndex < 0) { Debug.Assert(false); return; } - - if(bUp && (nIndex > 0)) // No assert for top item - { - T tTemp = m_vObjects[nIndex - 1]; - m_vObjects[nIndex - 1] = m_vObjects[nIndex]; - m_vObjects[nIndex] = tTemp; - } - else if(!bUp && (nIndex != (nCount - 1))) // No assert for bottom item - { - T tTemp = m_vObjects[nIndex + 1]; - m_vObjects[nIndex + 1] = m_vObjects[nIndex]; - m_vObjects[nIndex] = tTemp; - } - } - - public void MoveOne(T[] vObjects, bool bUp) - { - Debug.Assert(vObjects != null); - if(vObjects == null) throw new ArgumentNullException("vObjects"); - - List lIndices = new List(); - foreach(T t in vObjects) - { - if(t == null) { Debug.Assert(false); continue; } - - int p = IndexOf(t); - if(p >= 0) lIndices.Add(p); - else { Debug.Assert(false); } - } - - MoveOne(lIndices.ToArray(), bUp); - } - - public void MoveOne(int[] vIndices, bool bUp) - { - Debug.Assert(vIndices != null); - if(vIndices == null) throw new ArgumentNullException("vIndices"); - - int n = m_vObjects.Count; - if(n <= 1) return; // No moving possible - - int m = vIndices.Length; - if(m == 0) return; // Nothing to move - - int[] v = new int[m]; - Array.Copy(vIndices, v, m); - Array.Sort(v); - - if((bUp && (v[0] <= 0)) || (!bUp && (v[m - 1] >= (n - 1)))) - return; // Moving as a block is not possible - - int iStart = (bUp ? 0 : (m - 1)); - int iExcl = (bUp ? m : -1); - int iStep = (bUp ? 1 : -1); - - for(int i = iStart; i != iExcl; i += iStep) - { - int p = v[i]; - if((p < 0) || (p >= n)) { Debug.Assert(false); continue; } - - T t = m_vObjects[p]; - - if(bUp) - { - Debug.Assert(p > 0); - m_vObjects.RemoveAt(p); - m_vObjects.Insert(p - 1, t); - } - else // Down - { - Debug.Assert(p < (n - 1)); - m_vObjects.RemoveAt(p); - m_vObjects.Insert(p + 1, t); - } - } - } - - /// - /// Move some of the objects in this list to the top/bottom. - /// - /// List of objects to be moved. - /// Move to top. If false, move to bottom. - public void MoveTopBottom(T[] vObjects, bool bTop) - { - Debug.Assert(vObjects != null); - if(vObjects == null) throw new ArgumentNullException("vObjects"); - - if(vObjects.Length == 0) return; - - int nCount = m_vObjects.Count; - foreach(T t in vObjects) m_vObjects.Remove(t); - - if(bTop) - { - int nPos = 0; - foreach(T t in vObjects) - { - m_vObjects.Insert(nPos, t); - ++nPos; - } - } - else // Move to bottom - { - foreach(T t in vObjects) m_vObjects.Add(t); - } - - Debug.Assert(nCount == m_vObjects.Count); - if(nCount != m_vObjects.Count) - throw new ArgumentException("At least one of the T objects in the vObjects list doesn't exist!"); - } - - public void Sort(IComparer tComparer) - { - if(tComparer == null) throw new ArgumentNullException("tComparer"); - - m_vObjects.Sort(tComparer); - } - - public void Sort(Comparison tComparison) - { - if(tComparison == null) throw new ArgumentNullException("tComparison"); - - m_vObjects.Sort(tComparison); - } - - public static PwObjectList FromArray(T[] tArray) - { - if(tArray == null) throw new ArgumentNullException("tArray"); - - PwObjectList l = new PwObjectList(); - foreach(T t in tArray) { l.Add(t); } - return l; - } - - public static PwObjectList FromList(List tList) - { - if(tList == null) throw new ArgumentNullException("tList"); - - PwObjectList l = new PwObjectList(); - l.Add(tList); - return l; - } - } -} diff --git a/ModernKeePassLib/Collections/PwObjectPool.cs b/ModernKeePassLib/Collections/PwObjectPool.cs deleted file mode 100644 index 5c15a00..0000000 --- a/ModernKeePassLib/Collections/PwObjectPool.cs +++ /dev/null @@ -1,232 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics; -using System.Text; - -using ModernKeePassLib.Delegates; -using ModernKeePassLib.Interfaces; -using ModernKeePassLib.Utility; - -#if KeePassLibSD -using KeePassLibSD; -#endif - -namespace ModernKeePassLib.Collections -{ - public sealed class PwObjectPool - { - private SortedDictionary m_dict = - new SortedDictionary(); - - public static PwObjectPool FromGroupRecursive(PwGroup pgRoot, bool bEntries) - { - if(pgRoot == null) throw new ArgumentNullException("pgRoot"); - - PwObjectPool p = new PwObjectPool(); - - if(!bEntries) p.m_dict[pgRoot.Uuid] = pgRoot; - GroupHandler gh = delegate(PwGroup pg) - { - p.m_dict[pg.Uuid] = pg; - return true; - }; - - EntryHandler eh = delegate(PwEntry pe) - { - p.m_dict[pe.Uuid] = pe; - return true; - }; - - pgRoot.TraverseTree(TraversalMethod.PreOrder, bEntries ? null : gh, - bEntries ? eh : null); - return p; - } - - public IStructureItem Get(PwUuid pwUuid) - { - IStructureItem pItem; - m_dict.TryGetValue(pwUuid, out pItem); - return pItem; - } - - public bool ContainsOnlyType(Type t) - { - foreach(KeyValuePair kvp in m_dict) - { - if(kvp.Value.GetType() != t) return false; - } - - return true; - } - } - - internal sealed class PwObjectPoolEx - { - private Dictionary m_dUuidToId = - new Dictionary(); - private Dictionary m_dIdToItem = - new Dictionary(); - - private PwObjectPoolEx() - { - } - - public static PwObjectPoolEx FromGroup(PwGroup pg) - { - PwObjectPoolEx p = new PwObjectPoolEx(); - - if(pg == null) { Debug.Assert(false); return p; } - - ulong uFreeId = 2; // 0 = "not found", 1 is a hole - - p.m_dUuidToId[pg.Uuid] = uFreeId; - p.m_dIdToItem[uFreeId] = pg; - uFreeId += 2; // Make hole - - p.AddGroupRec(pg, ref uFreeId); - return p; - } - - private void AddGroupRec(PwGroup pg, ref ulong uFreeId) - { - if(pg == null) { Debug.Assert(false); return; } - - ulong uId = uFreeId; - - // Consecutive entries must have consecutive IDs - foreach(PwEntry pe in pg.Entries) - { - Debug.Assert(!m_dUuidToId.ContainsKey(pe.Uuid)); - Debug.Assert(!m_dIdToItem.ContainsValue(pe)); - - m_dUuidToId[pe.Uuid] = uId; - m_dIdToItem[uId] = pe; - ++uId; - } - ++uId; // Make hole - - // Consecutive groups must have consecutive IDs - foreach(PwGroup pgSub in pg.Groups) - { - Debug.Assert(!m_dUuidToId.ContainsKey(pgSub.Uuid)); - Debug.Assert(!m_dIdToItem.ContainsValue(pgSub)); - - m_dUuidToId[pgSub.Uuid] = uId; - m_dIdToItem[uId] = pgSub; - ++uId; - } - ++uId; // Make hole - - foreach(PwGroup pgSub in pg.Groups) - { - AddGroupRec(pgSub, ref uId); - } - - uFreeId = uId; - } - - public ulong GetIdByUuid(PwUuid pwUuid) - { - if(pwUuid == null) { Debug.Assert(false); return 0; } - - ulong uId; - m_dUuidToId.TryGetValue(pwUuid, out uId); - return uId; - } - - public IStructureItem GetItemByUuid(PwUuid pwUuid) - { - if(pwUuid == null) { Debug.Assert(false); return null; } - - ulong uId; - if(!m_dUuidToId.TryGetValue(pwUuid, out uId)) return null; - Debug.Assert(uId != 0); - - return GetItemById(uId); - } - - public IStructureItem GetItemById(ulong uId) - { - IStructureItem p; - m_dIdToItem.TryGetValue(uId, out p); - return p; - } - } - - internal sealed class PwObjectBlock : IEnumerable - where T : class, ITimeLogger, IStructureItem, IDeepCloneable - { - private List m_l = new List(); - - public T PrimaryItem - { - get { return ((m_l.Count > 0) ? m_l[0] : null); } - } - - private DateTime m_dtLocationChanged = TimeUtil.SafeMinValueUtc; - public DateTime LocationChanged - { - get { return m_dtLocationChanged; } - } - - private PwObjectPoolEx m_poolAssoc = null; - public PwObjectPoolEx PoolAssoc - { - get { return m_poolAssoc; } - } - - public PwObjectBlock() - { - } - -#if DEBUG - public override string ToString() - { - return ("PwObjectBlock, Count = " + m_l.Count.ToString()); - } -#endif - - IEnumerator IEnumerable.GetEnumerator() - { - return m_l.GetEnumerator(); - } - - public IEnumerator GetEnumerator() - { - return m_l.GetEnumerator(); - } - - public void Add(T t, DateTime dtLoc, PwObjectPoolEx pool) - { - if(t == null) { Debug.Assert(false); return; } - - m_l.Add(t); - - if(dtLoc > m_dtLocationChanged) - { - m_dtLocationChanged = dtLoc; - m_poolAssoc = pool; - } - } - } -} diff --git a/ModernKeePassLib/Collections/StringDictionaryEx.cs b/ModernKeePassLib/Collections/StringDictionaryEx.cs deleted file mode 100644 index 570ece3..0000000 --- a/ModernKeePassLib/Collections/StringDictionaryEx.cs +++ /dev/null @@ -1,130 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Text; -using System.Diagnostics; - -using ModernKeePassLib.Interfaces; - -#if KeePassLibSD -using KeePassLibSD; -#endif - -namespace ModernKeePassLib.Collections -{ - public sealed class StringDictionaryEx : IDeepCloneable, - IEnumerable>, IEquatable - { - private SortedDictionary m_dict = - new SortedDictionary(); - - public int Count - { - get { return m_dict.Count; } - } - - public StringDictionaryEx() - { - } - - IEnumerator IEnumerable.GetEnumerator() - { - return m_dict.GetEnumerator(); - } - - public IEnumerator> GetEnumerator() - { - return m_dict.GetEnumerator(); - } - - public StringDictionaryEx CloneDeep() - { - StringDictionaryEx sdNew = new StringDictionaryEx(); - - foreach(KeyValuePair kvp in m_dict) - sdNew.m_dict[kvp.Key] = kvp.Value; // Strings are immutable - - return sdNew; - } - - public bool Equals(StringDictionaryEx sdOther) - { - if(sdOther == null) { Debug.Assert(false); return false; } - - if(m_dict.Count != sdOther.m_dict.Count) return false; - - foreach(KeyValuePair kvp in sdOther.m_dict) - { - string str = Get(kvp.Key); - if((str == null) || (str != kvp.Value)) return false; - } - - return true; - } - - public string Get(string strName) - { - if(strName == null) { Debug.Assert(false); throw new ArgumentNullException("strName"); } - - string s; - if(m_dict.TryGetValue(strName, out s)) return s; - return null; - } - - public bool Exists(string strName) - { - if(strName == null) { Debug.Assert(false); throw new ArgumentNullException("strName"); } - - return m_dict.ContainsKey(strName); - } - - /// - /// Set a string. - /// - /// Identifier of the string field to modify. - /// New value. This parameter must not be null. - /// Thrown if one of the input - /// parameters is null. - public void Set(string strField, string strNewValue) - { - if(strField == null) { Debug.Assert(false); throw new ArgumentNullException("strField"); } - if(strNewValue == null) { Debug.Assert(false); throw new ArgumentNullException("strNewValue"); } - - m_dict[strField] = strNewValue; - } - - /// - /// Delete a string. - /// - /// Name of the string field to delete. - /// Returns true, if the field has been successfully - /// removed. Otherwise, the return value is false. - /// Thrown if the input - /// parameter is null. - public bool Remove(string strField) - { - if(strField == null) { Debug.Assert(false); throw new ArgumentNullException("strField"); } - - return m_dict.Remove(strField); - } - } -} diff --git a/ModernKeePassLib/Collections/VariantDictionary.cs b/ModernKeePassLib/Collections/VariantDictionary.cs deleted file mode 100644 index cb66d90..0000000 --- a/ModernKeePassLib/Collections/VariantDictionary.cs +++ /dev/null @@ -1,415 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Text; - -using ModernKeePassLib.Resources; -using ModernKeePassLib.Utility; - -namespace ModernKeePassLib.Collections -{ - public class VariantDictionary - { - private const ushort VdVersion = 0x0100; - private const ushort VdmCritical = 0xFF00; - private const ushort VdmInfo = 0x00FF; - - private Dictionary m_d = new Dictionary(); - - private enum VdType : byte - { - None = 0, - - // Byte = 0x02, - // UInt16 = 0x03, - UInt32 = 0x04, - UInt64 = 0x05, - - // Signed mask: 0x08 - Bool = 0x08, - // SByte = 0x0A, - // Int16 = 0x0B, - Int32 = 0x0C, - Int64 = 0x0D, - - // Float = 0x10, - // Double = 0x11, - // Decimal = 0x12, - - // Char = 0x17, // 16-bit Unicode character - String = 0x18, - - // Array mask: 0x40 - ByteArray = 0x42 - } - - public int Count - { - get { return m_d.Count; } - } - - public VariantDictionary() - { - Debug.Assert((VdmCritical & VdmInfo) == ushort.MinValue); - Debug.Assert((VdmCritical | VdmInfo) == ushort.MaxValue); - } - - private bool Get(string strName, out T t) - { - t = default(T); - - if(string.IsNullOrEmpty(strName)) { Debug.Assert(false); return false; } - - object o; - if(!m_d.TryGetValue(strName, out o)) return false; // No assert - - if(o == null) { Debug.Assert(false); return false; } - if(o.GetType() != typeof(T)) { Debug.Assert(false); return false; } - - t = (T)o; - return true; - } - - private void SetStruct(string strName, T t) - where T : struct - { - if(string.IsNullOrEmpty(strName)) { Debug.Assert(false); return; } - -#if DEBUG - T tEx; - Get(strName, out tEx); // Assert same type -#endif - - m_d[strName] = t; - } - - private void SetRef(string strName, T t) - where T : class - { - if(string.IsNullOrEmpty(strName)) { Debug.Assert(false); return; } - if(t == null) { Debug.Assert(false); return; } - -#if DEBUG - T tEx; - Get(strName, out tEx); // Assert same type -#endif - - m_d[strName] = t; - } - - public bool Remove(string strName) - { - if(string.IsNullOrEmpty(strName)) { Debug.Assert(false); return false; } - - return m_d.Remove(strName); - } - - public void CopyTo(VariantDictionary d) - { - if(d == null) { Debug.Assert(false); return; } - - // Do not clear the target - foreach(KeyValuePair kvp in m_d) - { - d.m_d[kvp.Key] = kvp.Value; - } - } - - public Type GetTypeOf(string strName) - { - if(string.IsNullOrEmpty(strName)) { Debug.Assert(false); return null; } - - object o; - m_d.TryGetValue(strName, out o); - if(o == null) return null; // No assert - - return o.GetType(); - } - - public uint GetUInt32(string strName, uint uDefault) - { - uint u; - if(Get(strName, out u)) return u; - return uDefault; - } - - public void SetUInt32(string strName, uint uValue) - { - SetStruct(strName, uValue); - } - - public ulong GetUInt64(string strName, ulong uDefault) - { - ulong u; - if(Get(strName, out u)) return u; - return uDefault; - } - - public void SetUInt64(string strName, ulong uValue) - { - SetStruct(strName, uValue); - } - - public bool GetBool(string strName, bool bDefault) - { - bool b; - if(Get(strName, out b)) return b; - return bDefault; - } - - public void SetBool(string strName, bool bValue) - { - SetStruct(strName, bValue); - } - - public int GetInt32(string strName, int iDefault) - { - int i; - if(Get(strName, out i)) return i; - return iDefault; - } - - public void SetInt32(string strName, int iValue) - { - SetStruct(strName, iValue); - } - - public long GetInt64(string strName, long lDefault) - { - long l; - if(Get(strName, out l)) return l; - return lDefault; - } - - public void SetInt64(string strName, long lValue) - { - SetStruct(strName, lValue); - } - - public string GetString(string strName) - { - string str; - Get(strName, out str); - return str; - } - - public void SetString(string strName, string strValue) - { - SetRef(strName, strValue); - } - - public byte[] GetByteArray(string strName) - { - byte[] pb; - Get(strName, out pb); - return pb; - } - - public void SetByteArray(string strName, byte[] pbValue) - { - SetRef(strName, pbValue); - } - - /// - /// Create a deep copy. - /// - public virtual object Clone() - { - VariantDictionary vdNew = new VariantDictionary(); - - foreach(KeyValuePair kvp in m_d) - { - object o = kvp.Value; - if(o == null) { Debug.Assert(false); continue; } - - Type t = o.GetType(); - if(t == typeof(byte[])) - { - byte[] p = (byte[])o; - byte[] pNew = new byte[p.Length]; - if(p.Length > 0) Array.Copy(p, pNew, p.Length); - - o = pNew; - } - - vdNew.m_d[kvp.Key] = o; - } - - return vdNew; - } - - public static byte[] Serialize(VariantDictionary p) - { - if(p == null) { Debug.Assert(false); return null; } - - byte[] pbRet; - using(MemoryStream ms = new MemoryStream()) - { - MemUtil.Write(ms, MemUtil.UInt16ToBytes(VdVersion)); - - foreach(KeyValuePair kvp in p.m_d) - { - string strName = kvp.Key; - if(string.IsNullOrEmpty(strName)) { Debug.Assert(false); continue; } - byte[] pbName = StrUtil.Utf8.GetBytes(strName); - - object o = kvp.Value; - if(o == null) { Debug.Assert(false); continue; } - - Type t = o.GetType(); - VdType vt = VdType.None; - byte[] pbValue = null; - if(t == typeof(uint)) - { - vt = VdType.UInt32; - pbValue = MemUtil.UInt32ToBytes((uint)o); - } - else if(t == typeof(ulong)) - { - vt = VdType.UInt64; - pbValue = MemUtil.UInt64ToBytes((ulong)o); - } - else if(t == typeof(bool)) - { - vt = VdType.Bool; - pbValue = new byte[1]; - pbValue[0] = ((bool)o ? (byte)1 : (byte)0); - } - else if(t == typeof(int)) - { - vt = VdType.Int32; - pbValue = MemUtil.Int32ToBytes((int)o); - } - else if(t == typeof(long)) - { - vt = VdType.Int64; - pbValue = MemUtil.Int64ToBytes((long)o); - } - else if(t == typeof(string)) - { - vt = VdType.String; - pbValue = StrUtil.Utf8.GetBytes((string)o); - } - else if(t == typeof(byte[])) - { - vt = VdType.ByteArray; - pbValue = (byte[])o; - } - else { Debug.Assert(false); continue; } // Unknown type - - ms.WriteByte((byte)vt); - MemUtil.Write(ms, MemUtil.Int32ToBytes(pbName.Length)); - MemUtil.Write(ms, pbName); - MemUtil.Write(ms, MemUtil.Int32ToBytes(pbValue.Length)); - MemUtil.Write(ms, pbValue); - } - - ms.WriteByte((byte)VdType.None); - pbRet = ms.ToArray(); - } - - return pbRet; - } - - public static VariantDictionary Deserialize(byte[] pb) - { - if(pb == null) { Debug.Assert(false); return null; } - - VariantDictionary d = new VariantDictionary(); - using(MemoryStream ms = new MemoryStream(pb, false)) - { - ushort uVersion = MemUtil.BytesToUInt16(MemUtil.Read(ms, 2)); - if((uVersion & VdmCritical) > (VdVersion & VdmCritical)) - throw new FormatException(KLRes.FileNewVerReq); - - while(true) - { - int iType = ms.ReadByte(); - if(iType < 0) throw new EndOfStreamException(KLRes.FileCorrupted); - byte btType = (byte)iType; - if(btType == (byte)VdType.None) break; - - int cbName = MemUtil.BytesToInt32(MemUtil.Read(ms, 4)); - byte[] pbName = MemUtil.Read(ms, cbName); - if(pbName.Length != cbName) - throw new EndOfStreamException(KLRes.FileCorrupted); - string strName = StrUtil.Utf8.GetString(pbName, 0, pbName.Length); - - int cbValue = MemUtil.BytesToInt32(MemUtil.Read(ms, 4)); - byte[] pbValue = MemUtil.Read(ms, cbValue); - if(pbValue.Length != cbValue) - throw new EndOfStreamException(KLRes.FileCorrupted); - - switch(btType) - { - case (byte)VdType.UInt32: - if(cbValue == 4) - d.SetUInt32(strName, MemUtil.BytesToUInt32(pbValue)); - else { Debug.Assert(false); } - break; - - case (byte)VdType.UInt64: - if(cbValue == 8) - d.SetUInt64(strName, MemUtil.BytesToUInt64(pbValue)); - else { Debug.Assert(false); } - break; - - case (byte)VdType.Bool: - if(cbValue == 1) - d.SetBool(strName, (pbValue[0] != 0)); - else { Debug.Assert(false); } - break; - - case (byte)VdType.Int32: - if(cbValue == 4) - d.SetInt32(strName, MemUtil.BytesToInt32(pbValue)); - else { Debug.Assert(false); } - break; - - case (byte)VdType.Int64: - if(cbValue == 8) - d.SetInt64(strName, MemUtil.BytesToInt64(pbValue)); - else { Debug.Assert(false); } - break; - - case (byte)VdType.String: - d.SetString(strName, StrUtil.Utf8.GetString(pbValue, 0, pbValue.Length)); - break; - - case (byte)VdType.ByteArray: - d.SetByteArray(strName, pbValue); - break; - - default: - Debug.Assert(false); // Unknown type - break; - } - } - - Debug.Assert(ms.ReadByte() < 0); - } - - return d; - } - } -} diff --git a/ModernKeePassLib/Cryptography/Cipher/ChaCha20Cipher.cs b/ModernKeePassLib/Cryptography/Cipher/ChaCha20Cipher.cs deleted file mode 100644 index 907c17a..0000000 --- a/ModernKeePassLib/Cryptography/Cipher/ChaCha20Cipher.cs +++ /dev/null @@ -1,254 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; - -using ModernKeePassLib.Resources; -using ModernKeePassLib.Utility; - -namespace ModernKeePassLib.Cryptography.Cipher -{ - /// - /// Implementation of the ChaCha20 cipher with a 96-bit nonce, - /// as specified in RFC 7539. - /// https://tools.ietf.org/html/rfc7539 - /// - public sealed class ChaCha20Cipher : CtrBlockCipher - { - private uint[] m_s = new uint[16]; // State - private uint[] m_x = new uint[16]; // Working buffer - - private bool m_bLargeCounter; // See constructor documentation - - private static readonly uint[] g_sigma = new uint[4] { - 0x61707865, 0x3320646E, 0x79622D32, 0x6B206574 - }; - - private const string StrNameRfc = "ChaCha20 (RFC 7539)"; - - public override int BlockSize - { - get { return 64; } - } - - public ChaCha20Cipher(byte[] pbKey32, byte[] pbIV12) : - this(pbKey32, pbIV12, false) - { - } - - /// - /// Constructor. - /// - /// Key (32 bytes). - /// Nonce (12 bytes). - /// If false, the RFC 7539 version - /// of ChaCha20 is used. In this case, only 256 GB of data can be - /// encrypted securely (because the block counter is a 32-bit variable); - /// an attempt to encrypt more data throws an exception. - /// If is true, the 32-bit - /// counter overflows to another 32-bit variable (i.e. the counter - /// effectively is a 64-bit variable), like in the original ChaCha20 - /// specification by D. J. Bernstein (which has a 64-bit counter and a - /// 64-bit nonce). To be compatible with this version, the 64-bit nonce - /// must be stored in the last 8 bytes of - /// and the first 4 bytes must be 0. - /// If the IV was generated randomly, a 12-byte IV and a large counter - /// can be used to securely encrypt more than 256 GB of data (but note - /// this is incompatible with RFC 7539 and the original specification). - public ChaCha20Cipher(byte[] pbKey32, byte[] pbIV12, bool bLargeCounter) : - base() - { - if(pbKey32 == null) throw new ArgumentNullException("pbKey32"); - if(pbKey32.Length != 32) throw new ArgumentOutOfRangeException("pbKey32"); - if(pbIV12 == null) throw new ArgumentNullException("pbIV12"); - if(pbIV12.Length != 12) throw new ArgumentOutOfRangeException("pbIV12"); - - m_bLargeCounter = bLargeCounter; - - // Key setup - m_s[4] = MemUtil.BytesToUInt32(pbKey32, 0); - m_s[5] = MemUtil.BytesToUInt32(pbKey32, 4); - m_s[6] = MemUtil.BytesToUInt32(pbKey32, 8); - m_s[7] = MemUtil.BytesToUInt32(pbKey32, 12); - m_s[8] = MemUtil.BytesToUInt32(pbKey32, 16); - m_s[9] = MemUtil.BytesToUInt32(pbKey32, 20); - m_s[10] = MemUtil.BytesToUInt32(pbKey32, 24); - m_s[11] = MemUtil.BytesToUInt32(pbKey32, 28); - m_s[0] = g_sigma[0]; - m_s[1] = g_sigma[1]; - m_s[2] = g_sigma[2]; - m_s[3] = g_sigma[3]; - - // IV setup - m_s[12] = 0; // Counter - m_s[13] = MemUtil.BytesToUInt32(pbIV12, 0); - m_s[14] = MemUtil.BytesToUInt32(pbIV12, 4); - m_s[15] = MemUtil.BytesToUInt32(pbIV12, 8); - } - - protected override void Dispose(bool bDisposing) - { - if(bDisposing) - { - MemUtil.ZeroArray(m_s); - MemUtil.ZeroArray(m_x); - } - - base.Dispose(bDisposing); - } - - protected override void NextBlock(byte[] pBlock) - { - if(pBlock == null) throw new ArgumentNullException("pBlock"); - if(pBlock.Length != 64) throw new ArgumentOutOfRangeException("pBlock"); - - // x is a local alias for the working buffer; with this, - // the compiler/runtime might remove some checks - uint[] x = m_x; - if(x == null) throw new InvalidOperationException(); - if(x.Length < 16) throw new InvalidOperationException(); - - uint[] s = m_s; - if(s == null) throw new InvalidOperationException(); - if(s.Length < 16) throw new InvalidOperationException(); - - Array.Copy(s, x, 16); - - unchecked - { - // 10 * 8 quarter rounds = 20 rounds - for(int i = 0; i < 10; ++i) - { - // Column quarter rounds - x[ 0] += x[ 4]; - x[12] = MemUtil.RotateLeft32(x[12] ^ x[ 0], 16); - x[ 8] += x[12]; - x[ 4] = MemUtil.RotateLeft32(x[ 4] ^ x[ 8], 12); - x[ 0] += x[ 4]; - x[12] = MemUtil.RotateLeft32(x[12] ^ x[ 0], 8); - x[ 8] += x[12]; - x[ 4] = MemUtil.RotateLeft32(x[ 4] ^ x[ 8], 7); - - x[ 1] += x[ 5]; - x[13] = MemUtil.RotateLeft32(x[13] ^ x[ 1], 16); - x[ 9] += x[13]; - x[ 5] = MemUtil.RotateLeft32(x[ 5] ^ x[ 9], 12); - x[ 1] += x[ 5]; - x[13] = MemUtil.RotateLeft32(x[13] ^ x[ 1], 8); - x[ 9] += x[13]; - x[ 5] = MemUtil.RotateLeft32(x[ 5] ^ x[ 9], 7); - - x[ 2] += x[ 6]; - x[14] = MemUtil.RotateLeft32(x[14] ^ x[ 2], 16); - x[10] += x[14]; - x[ 6] = MemUtil.RotateLeft32(x[ 6] ^ x[10], 12); - x[ 2] += x[ 6]; - x[14] = MemUtil.RotateLeft32(x[14] ^ x[ 2], 8); - x[10] += x[14]; - x[ 6] = MemUtil.RotateLeft32(x[ 6] ^ x[10], 7); - - x[ 3] += x[ 7]; - x[15] = MemUtil.RotateLeft32(x[15] ^ x[ 3], 16); - x[11] += x[15]; - x[ 7] = MemUtil.RotateLeft32(x[ 7] ^ x[11], 12); - x[ 3] += x[ 7]; - x[15] = MemUtil.RotateLeft32(x[15] ^ x[ 3], 8); - x[11] += x[15]; - x[ 7] = MemUtil.RotateLeft32(x[ 7] ^ x[11], 7); - - // Diagonal quarter rounds - x[ 0] += x[ 5]; - x[15] = MemUtil.RotateLeft32(x[15] ^ x[ 0], 16); - x[10] += x[15]; - x[ 5] = MemUtil.RotateLeft32(x[ 5] ^ x[10], 12); - x[ 0] += x[ 5]; - x[15] = MemUtil.RotateLeft32(x[15] ^ x[ 0], 8); - x[10] += x[15]; - x[ 5] = MemUtil.RotateLeft32(x[ 5] ^ x[10], 7); - - x[ 1] += x[ 6]; - x[12] = MemUtil.RotateLeft32(x[12] ^ x[ 1], 16); - x[11] += x[12]; - x[ 6] = MemUtil.RotateLeft32(x[ 6] ^ x[11], 12); - x[ 1] += x[ 6]; - x[12] = MemUtil.RotateLeft32(x[12] ^ x[ 1], 8); - x[11] += x[12]; - x[ 6] = MemUtil.RotateLeft32(x[ 6] ^ x[11], 7); - - x[ 2] += x[ 7]; - x[13] = MemUtil.RotateLeft32(x[13] ^ x[ 2], 16); - x[ 8] += x[13]; - x[ 7] = MemUtil.RotateLeft32(x[ 7] ^ x[ 8], 12); - x[ 2] += x[ 7]; - x[13] = MemUtil.RotateLeft32(x[13] ^ x[ 2], 8); - x[ 8] += x[13]; - x[ 7] = MemUtil.RotateLeft32(x[ 7] ^ x[ 8], 7); - - x[ 3] += x[ 4]; - x[14] = MemUtil.RotateLeft32(x[14] ^ x[ 3], 16); - x[ 9] += x[14]; - x[ 4] = MemUtil.RotateLeft32(x[ 4] ^ x[ 9], 12); - x[ 3] += x[ 4]; - x[14] = MemUtil.RotateLeft32(x[14] ^ x[ 3], 8); - x[ 9] += x[14]; - x[ 4] = MemUtil.RotateLeft32(x[ 4] ^ x[ 9], 7); - } - - for(int i = 0; i < 16; ++i) x[i] += s[i]; - - for(int i = 0; i < 16; ++i) - { - int i4 = i << 2; - uint xi = x[i]; - - pBlock[i4] = (byte)xi; - pBlock[i4 + 1] = (byte)(xi >> 8); - pBlock[i4 + 2] = (byte)(xi >> 16); - pBlock[i4 + 3] = (byte)(xi >> 24); - } - - ++s[12]; - if(s[12] == 0) - { - if(!m_bLargeCounter) - throw new InvalidOperationException( - KLRes.EncDataTooLarge.Replace(@"{PARAM}", StrNameRfc)); - ++s[13]; // Increment high half of large counter - } - } - } - - public long Seek(long lOffset, SeekOrigin so) - { - if(so != SeekOrigin.Begin) throw new NotSupportedException(); - - if((lOffset < 0) || ((lOffset & 63) != 0) || - ((lOffset >> 6) > (long)uint.MaxValue)) - throw new ArgumentOutOfRangeException("lOffset"); - - m_s[12] = (uint)(lOffset >> 6); - InvalidateBlock(); - - return lOffset; - } - } -} diff --git a/ModernKeePassLib/Cryptography/Cipher/ChaCha20Engine.cs b/ModernKeePassLib/Cryptography/Cipher/ChaCha20Engine.cs deleted file mode 100644 index 31ab9e5..0000000 --- a/ModernKeePassLib/Cryptography/Cipher/ChaCha20Engine.cs +++ /dev/null @@ -1,177 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Text; - -using ModernKeePassLib.Resources; - -namespace ModernKeePassLib.Cryptography.Cipher -{ - public sealed class ChaCha20Engine : ICipherEngine2 - { - private PwUuid m_uuid = new PwUuid(new byte[] { - 0xD6, 0x03, 0x8A, 0x2B, 0x8B, 0x6F, 0x4C, 0xB5, - 0xA5, 0x24, 0x33, 0x9A, 0x31, 0xDB, 0xB5, 0x9A - }); - - public PwUuid CipherUuid - { - get { return m_uuid; } - } - - public string DisplayName - { - get - { - return ("ChaCha20 (" + KLRes.KeyBits.Replace(@"{PARAM}", - "256") + ", RFC 7539)"); - } - } - - public int KeyLength - { - get { return 32; } - } - - public int IVLength - { - get { return 12; } // 96 bits - } - - public Stream EncryptStream(Stream sPlainText, byte[] pbKey, byte[] pbIV) - { - return new ChaCha20Stream(sPlainText, true, pbKey, pbIV); - } - - public Stream DecryptStream(Stream sEncrypted, byte[] pbKey, byte[] pbIV) - { - return new ChaCha20Stream(sEncrypted, false, pbKey, pbIV); - } - } - - public sealed class ChaCha20Stream : Stream - { - private Stream m_sBase; - private readonly bool m_bWriting; - private ChaCha20Cipher m_c; - - private byte[] m_pbBuffer = null; - - public override bool CanRead - { - get { return !m_bWriting; } - } - - public override bool CanSeek - { - get { return false; } - } - - public override bool CanWrite - { - get { return m_bWriting; } - } - - public override long Length - { - get { Debug.Assert(false); throw new NotSupportedException(); } - } - - public override long Position - { - get { Debug.Assert(false); throw new NotSupportedException(); } - set { Debug.Assert(false); throw new NotSupportedException(); } - } - - public ChaCha20Stream(Stream sBase, bool bWriting, byte[] pbKey32, - byte[] pbIV12) - { - if(sBase == null) throw new ArgumentNullException("sBase"); - - m_sBase = sBase; - m_bWriting = bWriting; - m_c = new ChaCha20Cipher(pbKey32, pbIV12); - } - - protected override void Dispose(bool bDisposing) - { - if(bDisposing) - { - if(m_sBase != null) - { - m_c.Dispose(); - m_c = null; - - m_sBase.Dispose(); - m_sBase = null; - } - - m_pbBuffer = null; - } - - base.Dispose(bDisposing); - } - - public override void Flush() - { - Debug.Assert(m_sBase != null); - if(m_bWriting && (m_sBase != null)) m_sBase.Flush(); - } - - public override long Seek(long lOffset, SeekOrigin soOrigin) - { - Debug.Assert(false); - throw new NotImplementedException(); - } - - public override void SetLength(long lValue) - { - Debug.Assert(false); - throw new NotImplementedException(); - } - - public override int Read(byte[] pbBuffer, int iOffset, int nCount) - { - if(m_bWriting) throw new InvalidOperationException(); - - int cbRead = m_sBase.Read(pbBuffer, iOffset, nCount); - m_c.Decrypt(pbBuffer, iOffset, cbRead); - return cbRead; - } - - public override void Write(byte[] pbBuffer, int iOffset, int nCount) - { - if(nCount < 0) throw new ArgumentOutOfRangeException("nCount"); - if(nCount == 0) return; - - if(!m_bWriting) throw new InvalidOperationException(); - - if((m_pbBuffer == null) || (m_pbBuffer.Length < nCount)) - m_pbBuffer = new byte[nCount]; - Array.Copy(pbBuffer, iOffset, m_pbBuffer, 0, nCount); - - m_c.Encrypt(m_pbBuffer, 0, nCount); - m_sBase.Write(m_pbBuffer, 0, nCount); - } - } -} diff --git a/ModernKeePassLib/Cryptography/Cipher/CipherPool.cs b/ModernKeePassLib/Cryptography/Cipher/CipherPool.cs deleted file mode 100644 index 49df33b..0000000 --- a/ModernKeePassLib/Cryptography/Cipher/CipherPool.cs +++ /dev/null @@ -1,165 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; -using System.Text; -using System.IO; -using System.Diagnostics; - -namespace ModernKeePassLib.Cryptography.Cipher -{ - /// - /// Pool of encryption/decryption algorithms (ciphers). - /// - public sealed class CipherPool - { - private List m_vCiphers = new List(); - private static CipherPool m_poolGlobal = null; - - /// - /// Reference to the global cipher pool. - /// - public static CipherPool GlobalPool - { - get - { - CipherPool cp = m_poolGlobal; - if(cp == null) - { - cp = new CipherPool(); - cp.AddCipher(new StandardAesEngine()); - cp.AddCipher(new ChaCha20Engine()); - - m_poolGlobal = cp; - } - - return cp; - } - } - - /// - /// Remove all cipher engines from the current pool. - /// - public void Clear() - { - m_vCiphers.Clear(); - } - - /// - /// Add a cipher engine to the pool. - /// - /// Cipher engine to add. Must not be null. - public void AddCipher(ICipherEngine csEngine) - { - Debug.Assert(csEngine != null); - if(csEngine == null) throw new ArgumentNullException("csEngine"); - - // Return if a cipher with that ID is registered already. - for(int i = 0; i < m_vCiphers.Count; ++i) - if(m_vCiphers[i].CipherUuid.Equals(csEngine.CipherUuid)) - return; - - m_vCiphers.Add(csEngine); - } - - /// - /// Get a cipher identified by its UUID. - /// - /// UUID of the cipher to return. - /// Reference to the requested cipher. If the cipher is - /// not found, null is returned. - public ICipherEngine GetCipher(PwUuid uuidCipher) - { - foreach(ICipherEngine iEngine in m_vCiphers) - { - if(iEngine.CipherUuid.Equals(uuidCipher)) - return iEngine; - } - - return null; - } - - /// - /// Get the index of a cipher. This index is temporary and should - /// not be stored or used to identify a cipher. - /// - /// UUID of the cipher. - /// Index of the requested cipher. Returns -1 if - /// the specified cipher is not found. - public int GetCipherIndex(PwUuid uuidCipher) - { - for(int i = 0; i < m_vCiphers.Count; ++i) - { - if(m_vCiphers[i].CipherUuid.Equals(uuidCipher)) - return i; - } - - Debug.Assert(false); - return -1; - } - - /// - /// Get the index of a cipher. This index is temporary and should - /// not be stored or used to identify a cipher. - /// - /// Name of the cipher. Note that - /// multiple ciphers can have the same name. In this case, the - /// first matching cipher is returned. - /// Cipher with the specified name or -1 if - /// no cipher with that name is found. - public int GetCipherIndex(string strDisplayName) - { - for(int i = 0; i < m_vCiphers.Count; ++i) - if(m_vCiphers[i].DisplayName == strDisplayName) - return i; - - Debug.Assert(false); - return -1; - } - - /// - /// Get the number of cipher engines in this pool. - /// - public int EngineCount - { - get { return m_vCiphers.Count; } - } - - /// - /// Get the cipher engine at the specified position. Throws - /// an exception if the index is invalid. You can use this - /// to iterate over all ciphers, but do not use it to - /// identify ciphers. - /// - /// Index of the requested cipher engine. - /// Reference to the cipher engine at the specified - /// position. - public ICipherEngine this[int nIndex] - { - get - { - if((nIndex < 0) || (nIndex >= m_vCiphers.Count)) - throw new ArgumentOutOfRangeException("nIndex"); - - return m_vCiphers[nIndex]; - } - } - } -} diff --git a/ModernKeePassLib/Cryptography/Cipher/CtrBlockCipher.cs b/ModernKeePassLib/Cryptography/Cipher/CtrBlockCipher.cs deleted file mode 100644 index a428e7c..0000000 --- a/ModernKeePassLib/Cryptography/Cipher/CtrBlockCipher.cs +++ /dev/null @@ -1,109 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Text; - -using ModernKeePassLib.Utility; - -namespace ModernKeePassLib.Cryptography.Cipher -{ - public abstract class CtrBlockCipher : IDisposable - { - private bool m_bDisposed = false; - - private byte[] m_pBlock; - private int m_iBlockPos; - - public abstract int BlockSize - { - get; - } - - public CtrBlockCipher() - { - int cb = this.BlockSize; - if(cb <= 0) throw new InvalidOperationException("this.BlockSize"); - - m_pBlock = new byte[cb]; - m_iBlockPos = cb; - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - protected virtual void Dispose(bool bDisposing) - { - if(bDisposing) - { - MemUtil.ZeroByteArray(m_pBlock); - m_iBlockPos = m_pBlock.Length; - - m_bDisposed = true; - } - } - - protected void InvalidateBlock() - { - m_iBlockPos = m_pBlock.Length; - } - - protected abstract void NextBlock(byte[] pBlock); - - public void Encrypt(byte[] m, int iOffset, int cb) - { - if(m_bDisposed) throw new ObjectDisposedException(null); - if(m == null) throw new ArgumentNullException("m"); - if(iOffset < 0) throw new ArgumentOutOfRangeException("iOffset"); - if(cb < 0) throw new ArgumentOutOfRangeException("cb"); - if(iOffset > (m.Length - cb)) throw new ArgumentOutOfRangeException("cb"); - - int cbBlock = m_pBlock.Length; - - while(cb > 0) - { - Debug.Assert(m_iBlockPos <= cbBlock); - if(m_iBlockPos == cbBlock) - { - NextBlock(m_pBlock); - m_iBlockPos = 0; - } - - int cbCopy = Math.Min(cbBlock - m_iBlockPos, cb); - Debug.Assert(cbCopy > 0); - - MemUtil.XorArray(m_pBlock, m_iBlockPos, m, iOffset, cbCopy); - - m_iBlockPos += cbCopy; - iOffset += cbCopy; - cb -= cbCopy; - } - } - - public void Decrypt(byte[] m, int iOffset, int cb) - { - Encrypt(m, iOffset, cb); - } - } -} diff --git a/ModernKeePassLib/Cryptography/Cipher/ICipherEngine.cs b/ModernKeePassLib/Cryptography/Cipher/ICipherEngine.cs deleted file mode 100644 index d4e257c..0000000 --- a/ModernKeePassLib/Cryptography/Cipher/ICipherEngine.cs +++ /dev/null @@ -1,87 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.IO; - -namespace ModernKeePassLib.Cryptography.Cipher -{ - /// - /// Interface of an encryption/decryption class. - /// - public interface ICipherEngine - { - /// - /// UUID of the engine. If you want to write an engine/plugin, - /// please contact the KeePass team to obtain a new UUID. - /// - PwUuid CipherUuid - { - get; - } - - /// - /// String displayed in the list of available encryption/decryption - /// engines in the GUI. - /// - string DisplayName - { - get; - } - - /// - /// Encrypt a stream. - /// - /// Stream to read the plain-text from. - /// Key to use. - /// Initialization vector. - /// Stream, from which the encrypted data can be read. - Stream EncryptStream(Stream sPlainText, byte[] pbKey, byte[] pbIV); - - /// - /// Decrypt a stream. - /// - /// Stream to read the encrypted data from. - /// Key to use. - /// Initialization vector. - /// Stream, from which the decrypted data can be read. - Stream DecryptStream(Stream sEncrypted, byte[] pbKey, byte[] pbIV); - } - - public interface ICipherEngine2 : ICipherEngine - { - /// - /// Length of an encryption key in bytes. - /// The base ICipherEngine assumes 32. - /// - int KeyLength - { - get; - } - - /// - /// Length of the initialization vector in bytes. - /// The base ICipherEngine assumes 16. - /// - int IVLength - { - get; - } - } -} diff --git a/ModernKeePassLib/Cryptography/Cipher/Salsa20Cipher.cs b/ModernKeePassLib/Cryptography/Cipher/Salsa20Cipher.cs deleted file mode 100644 index bec707b..0000000 --- a/ModernKeePassLib/Cryptography/Cipher/Salsa20Cipher.cs +++ /dev/null @@ -1,165 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -// Implementation of the Salsa20 cipher, based on the eSTREAM -// submission by D. J. Bernstein. - -using System; -using System.Collections.Generic; -using System.Diagnostics; - -using ModernKeePassLib.Utility; - -namespace ModernKeePassLib.Cryptography.Cipher -{ - public sealed class Salsa20Cipher : CtrBlockCipher - { - private uint[] m_s = new uint[16]; // State - private uint[] m_x = new uint[16]; // Working buffer - - private static readonly uint[] g_sigma = new uint[4] { - 0x61707865, 0x3320646E, 0x79622D32, 0x6B206574 - }; - - public override int BlockSize - { - get { return 64; } - } - - public Salsa20Cipher(byte[] pbKey32, byte[] pbIV8) : base() - { - if(pbKey32 == null) throw new ArgumentNullException("pbKey32"); - if(pbKey32.Length != 32) throw new ArgumentOutOfRangeException("pbKey32"); - if(pbIV8 == null) throw new ArgumentNullException("pbIV8"); - if(pbIV8.Length != 8) throw new ArgumentOutOfRangeException("pbIV8"); - - // Key setup - m_s[1] = MemUtil.BytesToUInt32(pbKey32, 0); - m_s[2] = MemUtil.BytesToUInt32(pbKey32, 4); - m_s[3] = MemUtil.BytesToUInt32(pbKey32, 8); - m_s[4] = MemUtil.BytesToUInt32(pbKey32, 12); - m_s[11] = MemUtil.BytesToUInt32(pbKey32, 16); - m_s[12] = MemUtil.BytesToUInt32(pbKey32, 20); - m_s[13] = MemUtil.BytesToUInt32(pbKey32, 24); - m_s[14] = MemUtil.BytesToUInt32(pbKey32, 28); - m_s[0] = g_sigma[0]; - m_s[5] = g_sigma[1]; - m_s[10] = g_sigma[2]; - m_s[15] = g_sigma[3]; - - // IV setup - m_s[6] = MemUtil.BytesToUInt32(pbIV8, 0); - m_s[7] = MemUtil.BytesToUInt32(pbIV8, 4); - m_s[8] = 0; // Counter, low - m_s[9] = 0; // Counter, high - } - - protected override void Dispose(bool bDisposing) - { - if(bDisposing) - { - MemUtil.ZeroArray(m_s); - MemUtil.ZeroArray(m_x); - } - - base.Dispose(bDisposing); - } - - protected override void NextBlock(byte[] pBlock) - { - if(pBlock == null) throw new ArgumentNullException("pBlock"); - if(pBlock.Length != 64) throw new ArgumentOutOfRangeException("pBlock"); - - // x is a local alias for the working buffer; with this, - // the compiler/runtime might remove some checks - uint[] x = m_x; - if(x == null) throw new InvalidOperationException(); - if(x.Length < 16) throw new InvalidOperationException(); - - uint[] s = m_s; - if(s == null) throw new InvalidOperationException(); - if(s.Length < 16) throw new InvalidOperationException(); - - Array.Copy(s, x, 16); - - unchecked - { - // 10 * 8 quarter rounds = 20 rounds - for(int i = 0; i < 10; ++i) - { - x[ 4] ^= MemUtil.RotateLeft32(x[ 0] + x[12], 7); - x[ 8] ^= MemUtil.RotateLeft32(x[ 4] + x[ 0], 9); - x[12] ^= MemUtil.RotateLeft32(x[ 8] + x[ 4], 13); - x[ 0] ^= MemUtil.RotateLeft32(x[12] + x[ 8], 18); - - x[ 9] ^= MemUtil.RotateLeft32(x[ 5] + x[ 1], 7); - x[13] ^= MemUtil.RotateLeft32(x[ 9] + x[ 5], 9); - x[ 1] ^= MemUtil.RotateLeft32(x[13] + x[ 9], 13); - x[ 5] ^= MemUtil.RotateLeft32(x[ 1] + x[13], 18); - - x[14] ^= MemUtil.RotateLeft32(x[10] + x[ 6], 7); - x[ 2] ^= MemUtil.RotateLeft32(x[14] + x[10], 9); - x[ 6] ^= MemUtil.RotateLeft32(x[ 2] + x[14], 13); - x[10] ^= MemUtil.RotateLeft32(x[ 6] + x[ 2], 18); - - x[ 3] ^= MemUtil.RotateLeft32(x[15] + x[11], 7); - x[ 7] ^= MemUtil.RotateLeft32(x[ 3] + x[15], 9); - x[11] ^= MemUtil.RotateLeft32(x[ 7] + x[ 3], 13); - x[15] ^= MemUtil.RotateLeft32(x[11] + x[ 7], 18); - - x[ 1] ^= MemUtil.RotateLeft32(x[ 0] + x[ 3], 7); - x[ 2] ^= MemUtil.RotateLeft32(x[ 1] + x[ 0], 9); - x[ 3] ^= MemUtil.RotateLeft32(x[ 2] + x[ 1], 13); - x[ 0] ^= MemUtil.RotateLeft32(x[ 3] + x[ 2], 18); - - x[ 6] ^= MemUtil.RotateLeft32(x[ 5] + x[ 4], 7); - x[ 7] ^= MemUtil.RotateLeft32(x[ 6] + x[ 5], 9); - x[ 4] ^= MemUtil.RotateLeft32(x[ 7] + x[ 6], 13); - x[ 5] ^= MemUtil.RotateLeft32(x[ 4] + x[ 7], 18); - - x[11] ^= MemUtil.RotateLeft32(x[10] + x[ 9], 7); - x[ 8] ^= MemUtil.RotateLeft32(x[11] + x[10], 9); - x[ 9] ^= MemUtil.RotateLeft32(x[ 8] + x[11], 13); - x[10] ^= MemUtil.RotateLeft32(x[ 9] + x[ 8], 18); - - x[12] ^= MemUtil.RotateLeft32(x[15] + x[14], 7); - x[13] ^= MemUtil.RotateLeft32(x[12] + x[15], 9); - x[14] ^= MemUtil.RotateLeft32(x[13] + x[12], 13); - x[15] ^= MemUtil.RotateLeft32(x[14] + x[13], 18); - } - - for(int i = 0; i < 16; ++i) x[i] += s[i]; - - for(int i = 0; i < 16; ++i) - { - int i4 = i << 2; - uint xi = x[i]; - - pBlock[i4] = (byte)xi; - pBlock[i4 + 1] = (byte)(xi >> 8); - pBlock[i4 + 2] = (byte)(xi >> 16); - pBlock[i4 + 3] = (byte)(xi >> 24); - } - - ++s[8]; - if(s[8] == 0) ++s[9]; - } - } - } -} diff --git a/ModernKeePassLib/Cryptography/Cipher/StandardAesEngine.cs b/ModernKeePassLib/Cryptography/Cipher/StandardAesEngine.cs deleted file mode 100644 index d47da91..0000000 --- a/ModernKeePassLib/Cryptography/Cipher/StandardAesEngine.cs +++ /dev/null @@ -1,173 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; -using System.Text; -using System.IO; -using System.Security; -using System.Diagnostics; -#if ModernKeePassLib -using ModernKeePassLib.Resources; -using Org.BouncyCastle.Crypto; -using Org.BouncyCastle.Crypto.Engines; -using Org.BouncyCastle.Crypto.IO; -using Org.BouncyCastle.Crypto.Modes; -using Org.BouncyCastle.Crypto.Paddings; -using Org.BouncyCastle.Crypto.Parameters; -#else -using System.Security.Cryptography; -#endif - -namespace ModernKeePassLib.Cryptography.Cipher -{ - /// - /// Standard AES cipher implementation. - /// - public sealed class StandardAesEngine : ICipherEngine - { -#if !ModernKeePassLib && !KeePassUAP - private const CipherMode m_rCipherMode = CipherMode.CBC; - private const PaddingMode m_rCipherPadding = PaddingMode.PKCS7; -#endif - - private static PwUuid g_uuidAes = null; - - /// - /// UUID of the cipher engine. This ID uniquely identifies the - /// AES engine. Must not be used by other ciphers. - /// - public static PwUuid AesUuid - { - get - { - PwUuid pu = g_uuidAes; - if(pu == null) - { - pu = new PwUuid(new byte[] { - 0x31, 0xC1, 0xF2, 0xE6, 0xBF, 0x71, 0x43, 0x50, - 0xBE, 0x58, 0x05, 0x21, 0x6A, 0xFC, 0x5A, 0xFF }); - g_uuidAes = pu; - } - - return pu; - } - } - - /// - /// Get the UUID of this cipher engine as PwUuid object. - /// - public PwUuid CipherUuid - { - get { return StandardAesEngine.AesUuid; } - } - - /// - /// Get a displayable name describing this cipher engine. - /// - public string DisplayName - { - get - { - return ("AES/Rijndael (" + KLRes.KeyBits.Replace(@"{PARAM}", - "256") + ", FIPS 197)"); - } - } - - private static void ValidateArguments(Stream stream, bool bEncrypt, byte[] pbKey, byte[] pbIV) - { - Debug.Assert(stream != null); if(stream == null) throw new ArgumentNullException("stream"); - - Debug.Assert(pbKey != null); if(pbKey == null) throw new ArgumentNullException("pbKey"); - Debug.Assert(pbKey.Length == 32); - if(pbKey.Length != 32) throw new ArgumentException("Key must be 256 bits wide!"); - - Debug.Assert(pbIV != null); if(pbIV == null) throw new ArgumentNullException("pbIV"); - Debug.Assert(pbIV.Length == 16); - if(pbIV.Length != 16) throw new ArgumentException("Initialization vector must be 128 bits wide!"); - - if(bEncrypt) - { - Debug.Assert(stream.CanWrite); - if(!stream.CanWrite) throw new ArgumentException("Stream must be writable!"); - } - else // Decrypt - { - Debug.Assert(stream.CanRead); - if(!stream.CanRead) throw new ArgumentException("Encrypted stream must be readable!"); - } - } - - private static Stream CreateStream(Stream s, bool bEncrypt, byte[] pbKey, byte[] pbIV) - { - StandardAesEngine.ValidateArguments(s, bEncrypt, pbKey, pbIV); - - byte[] pbLocalIV = new byte[16]; - Array.Copy(pbIV, pbLocalIV, 16); - - byte[] pbLocalKey = new byte[32]; - Array.Copy(pbKey, pbLocalKey, 32); - -#if ModernKeePassLib - var cbc = new CbcBlockCipher(new AesEngine()); - var bc = new PaddedBufferedBlockCipher(cbc, - new Pkcs7Padding()); - var kp = new KeyParameter(pbLocalKey); - var prmIV = new ParametersWithIV(kp, pbLocalIV); - bc.Init(bEncrypt, prmIV); - - var cpRead = (bEncrypt ? null : bc); - var cpWrite = (bEncrypt ? bc : null); - return new CipherStream(s, cpRead, cpWrite); -#elif KeePassUAP - return StandardAesEngineExt.CreateStream(s, bEncrypt, pbLocalKey, pbLocalIV); -#else - SymmetricAlgorithm a = CryptoUtil.CreateAes(); - if(a.BlockSize != 128) // AES block size - { - Debug.Assert(false); - a.BlockSize = 128; - } - - a.IV = pbLocalIV; - a.KeySize = 256; - a.Key = pbLocalKey; - a.Mode = m_rCipherMode; - a.Padding = m_rCipherPadding; - - ICryptoTransform iTransform = (bEncrypt ? a.CreateEncryptor() : a.CreateDecryptor()); - Debug.Assert(iTransform != null); - if(iTransform == null) throw new SecurityException("Unable to create AES transform!"); - - return new CryptoStream(s, iTransform, bEncrypt ? CryptoStreamMode.Write : - CryptoStreamMode.Read); -#endif - } - - public Stream EncryptStream(Stream sPlainText, byte[] pbKey, byte[] pbIV) - { - return StandardAesEngine.CreateStream(sPlainText, true, pbKey, pbIV); - } - - public Stream DecryptStream(Stream sEncrypted, byte[] pbKey, byte[] pbIV) - { - return StandardAesEngine.CreateStream(sEncrypted, false, pbKey, pbIV); - } - } -} diff --git a/ModernKeePassLib/Cryptography/CryptoRandom.cs b/ModernKeePassLib/Cryptography/CryptoRandom.cs deleted file mode 100644 index 1a83aab..0000000 --- a/ModernKeePassLib/Cryptography/CryptoRandom.cs +++ /dev/null @@ -1,398 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics; -using System.Globalization; -using System.IO; -#if ModernKeePassLib -using ModernKeePassLib.Cryptography.Hash; -using Windows.Security.Cryptography; -#else -using System.Security.Cryptography; -using System.Windows.Forms; -#endif - -using ModernKeePassLib.Native; -using ModernKeePassLib.Utility; - -namespace ModernKeePassLib.Cryptography -{ - /// - /// Cryptographically secure pseudo-random number generator. - /// The returned values are unpredictable and cannot be reproduced. - /// CryptoRandom is a singleton class. - /// - public sealed class CryptoRandom - { - private byte[] m_pbEntropyPool = new byte[64]; - private ulong m_uCounter; -#if !ModernKeePassLib - private RNGCryptoServiceProvider m_rng = new RNGCryptoServiceProvider(); -#endif - private ulong m_uGeneratedBytesCount = 0; - - private static readonly object g_oSyncRoot = new object(); - private readonly object m_oSyncRoot = new object(); - - private static CryptoRandom g_pInstance = null; - public static CryptoRandom Instance - { - get - { - CryptoRandom cr; - lock(g_oSyncRoot) - { - cr = g_pInstance; - if(cr == null) - { - cr = new CryptoRandom(); - g_pInstance = cr; - } - } - - return cr; - } - } - - /// - /// Get the number of random bytes that this instance generated so far. - /// Note that this number can be higher than the number of random bytes - /// actually requested using the GetRandomBytes method. - /// - public ulong GeneratedBytesCount - { - get - { - ulong u; - lock(m_oSyncRoot) { u = m_uGeneratedBytesCount; } - return u; - } - } - - /// - /// Event that is triggered whenever the internal GenerateRandom256 - /// method is called to generate random bytes. - /// - public event EventHandler GenerateRandom256Pre; - - private CryptoRandom() - { - // Random rWeak = new Random(); // Based on tick count - // byte[] pb = new byte[8]; - // rWeak.NextBytes(pb); - // m_uCounter = MemUtil.BytesToUInt64(pb); - m_uCounter = (ulong)DateTime.UtcNow.ToBinary(); - - AddEntropy(GetSystemData()); - AddEntropy(GetCspData()); - } - - /// - /// Update the internal seed of the random number generator based - /// on entropy data. - /// This method is thread-safe. - /// - /// Entropy bytes. - public void AddEntropy(byte[] pbEntropy) - { - if(pbEntropy == null) { Debug.Assert(false); return; } - if(pbEntropy.Length == 0) { Debug.Assert(false); return; } - - byte[] pbNewData = pbEntropy; - if(pbEntropy.Length > 64) - { -#if KeePassLibSD - using(SHA256Managed shaNew = new SHA256Managed()) -#else - using(SHA512Managed shaNew = new SHA512Managed()) -#endif - { - pbNewData = shaNew.ComputeHash(pbEntropy); - } - } - - lock(m_oSyncRoot) - { - int cbPool = m_pbEntropyPool.Length; - int cbNew = pbNewData.Length; - - byte[] pbCmp = new byte[cbPool + cbNew]; - Array.Copy(m_pbEntropyPool, pbCmp, cbPool); - Array.Copy(pbNewData, 0, pbCmp, cbPool, cbNew); - - MemUtil.ZeroByteArray(m_pbEntropyPool); - -#if KeePassLibSD - using(SHA256Managed shaPool = new SHA256Managed()) -#else - using(SHA512Managed shaPool = new SHA512Managed()) -#endif - { - m_pbEntropyPool = shaPool.ComputeHash(pbCmp); - } - - MemUtil.ZeroByteArray(pbCmp); - } - } - - private static byte[] GetSystemData() - { - MemoryStream ms = new MemoryStream(); - byte[] pb; - - pb = MemUtil.Int32ToBytes(Environment.TickCount); - MemUtil.Write(ms, pb); - - pb = MemUtil.Int64ToBytes(DateTime.UtcNow.ToBinary()); - MemUtil.Write(ms, pb); - -#if (!ModernKeePassLib && !KeePassLibSD) - // In try-catch for systems without GUI; - // https://sourceforge.net/p/keepass/discussion/329221/thread/20335b73/ - try - { - Point pt = Cursor.Position; - pb = MemUtil.Int32ToBytes(pt.X); - MemUtil.Write(ms, pb); - pb = MemUtil.Int32ToBytes(pt.Y); - MemUtil.Write(ms, pb); - } - catch(Exception) { } -#endif -#if ModernKeePassLib - pb = MemUtil.UInt32ToBytes((uint)Environment.ProcessorCount); - ms.Write(pb, 0, pb.Length); - - pb = MemUtil.UInt32ToBytes((uint)Environment.CurrentManagedThreadId); - ms.Write(pb, 0, pb.Length); -#else - pb = MemUtil.UInt32ToBytes((uint)NativeLib.GetPlatformID()); - MemUtil.Write(ms, pb); - - try - { -#if KeePassUAP - string strOS = EnvironmentExt.OSVersion.VersionString; -#else - string strOS = Environment.OSVersion.VersionString; -#endif - AddStrHash(ms, strOS); - - pb = MemUtil.Int32ToBytes(Environment.ProcessorCount); - MemUtil.Write(ms, pb); - -#if !KeePassUAP - AddStrHash(ms, Environment.CommandLine); - - pb = MemUtil.Int64ToBytes(Environment.WorkingSet); - MemUtil.Write(ms, pb); -#endif - } - catch(Exception) { Debug.Assert(false); } - - try - { - foreach(DictionaryEntry de in Environment.GetEnvironmentVariables()) - { - AddStrHash(ms, (de.Key as string)); - AddStrHash(ms, (de.Value as string)); - } - } - catch(Exception) { Debug.Assert(false); } - -#if KeePassUAP - pb = DiagnosticsExt.GetProcessEntropy(); - MemUtil.Write(ms, pb); -#elif !KeePassLibSD - try - { - using(Process p = Process.GetCurrentProcess()) - { - pb = MemUtil.Int64ToBytes(p.Handle.ToInt64()); - MemUtil.Write(ms, pb); - pb = MemUtil.Int32ToBytes(p.HandleCount); - MemUtil.Write(ms, pb); - pb = MemUtil.Int32ToBytes(p.Id); - MemUtil.Write(ms, pb); - pb = MemUtil.Int64ToBytes(p.NonpagedSystemMemorySize64); - MemUtil.Write(ms, pb); - pb = MemUtil.Int64ToBytes(p.PagedMemorySize64); - MemUtil.Write(ms, pb); - pb = MemUtil.Int64ToBytes(p.PagedSystemMemorySize64); - MemUtil.Write(ms, pb); - pb = MemUtil.Int64ToBytes(p.PeakPagedMemorySize64); - MemUtil.Write(ms, pb); - pb = MemUtil.Int64ToBytes(p.PeakVirtualMemorySize64); - MemUtil.Write(ms, pb); - pb = MemUtil.Int64ToBytes(p.PeakWorkingSet64); - MemUtil.Write(ms, pb); - pb = MemUtil.Int64ToBytes(p.PrivateMemorySize64); - MemUtil.Write(ms, pb); - pb = MemUtil.Int64ToBytes(p.StartTime.ToBinary()); - MemUtil.Write(ms, pb); - pb = MemUtil.Int64ToBytes(p.VirtualMemorySize64); - MemUtil.Write(ms, pb); - pb = MemUtil.Int64ToBytes(p.WorkingSet64); - MemUtil.Write(ms, pb); - - // Not supported in Mono 1.2.6: - // pb = MemUtil.UInt32ToBytes((uint)p.SessionId); - // MemUtil.Write(ms, pb); - } - } - catch(Exception) { Debug.Assert(NativeLib.IsUnix()); } -#endif -#endif - - try - { - CultureInfo ci = CultureInfo.CurrentCulture; - if(ci != null) - { - pb = MemUtil.Int32ToBytes(ci.GetHashCode()); - MemUtil.Write(ms, pb); - } - else { Debug.Assert(false); } - } - catch(Exception) { Debug.Assert(false); } - - pb = Guid.NewGuid().ToByteArray(); - MemUtil.Write(ms, pb); - - byte[] pbAll = ms.ToArray(); - ms.Dispose(); - return pbAll; - } - - private static void AddStrHash(Stream s, string str) - { - if(s == null) { Debug.Assert(false); return; } - if(string.IsNullOrEmpty(str)) return; - - byte[] pbUtf8 = StrUtil.Utf8.GetBytes(str); - byte[] pbHash = CryptoUtil.HashSha256(pbUtf8); - MemUtil.Write(s, pbHash); - } - - private byte[] GetCspData() - { - byte[] pbCspRandom = new byte[32]; -#if ModernKeePassLib - CryptographicBuffer.CopyToByteArray(CryptographicBuffer.GenerateRandom(32), out pbCspRandom); -#else - m_rng.GetBytes(pbCspRandom); -#endif - return pbCspRandom; - } - - private byte[] GenerateRandom256() - { - if(this.GenerateRandom256Pre != null) - this.GenerateRandom256Pre(this, EventArgs.Empty); - - byte[] pbCmp; - lock(m_oSyncRoot) - { - m_uCounter += 0x74D8B29E4D38E161UL; // Prime number - byte[] pbCounter = MemUtil.UInt64ToBytes(m_uCounter); - - byte[] pbCspRandom = GetCspData(); - - int cbPool = m_pbEntropyPool.Length; - int cbCtr = pbCounter.Length; - int cbCsp = pbCspRandom.Length; - - pbCmp = new byte[cbPool + cbCtr + cbCsp]; - Array.Copy(m_pbEntropyPool, pbCmp, cbPool); - Array.Copy(pbCounter, 0, pbCmp, cbPool, cbCtr); - Array.Copy(pbCspRandom, 0, pbCmp, cbPool + cbCtr, cbCsp); - - MemUtil.ZeroByteArray(pbCspRandom); - - m_uGeneratedBytesCount += 32; - } - - byte[] pbRet = CryptoUtil.HashSha256(pbCmp); - MemUtil.ZeroByteArray(pbCmp); - return pbRet; - } - - /// - /// Get a number of cryptographically strong random bytes. - /// This method is thread-safe. - /// - /// Number of requested random bytes. - /// A byte array consisting of - /// random bytes. - public byte[] GetRandomBytes(uint uRequestedBytes) - { - if(uRequestedBytes == 0) return MemUtil.EmptyByteArray; - if(uRequestedBytes > (uint)int.MaxValue) - { - Debug.Assert(false); - throw new ArgumentOutOfRangeException("uRequestedBytes"); - } - - int cbRem = (int)uRequestedBytes; - byte[] pbRes = new byte[cbRem]; - int iPos = 0; - - while(cbRem != 0) - { - byte[] pbRandom256 = GenerateRandom256(); - Debug.Assert(pbRandom256.Length == 32); - - int cbCopy = Math.Min(cbRem, pbRandom256.Length); - Array.Copy(pbRandom256, 0, pbRes, iPos, cbCopy); - - MemUtil.ZeroByteArray(pbRandom256); - - iPos += cbCopy; - cbRem -= cbCopy; - } - - Debug.Assert(iPos == pbRes.Length); - return pbRes; - } - - private static int g_iWeakSeed = 0; - public static Random NewWeakRandom() - { - long s64 = DateTime.UtcNow.ToBinary(); - int s32 = (int)((s64 >> 32) ^ s64); - - lock(g_oSyncRoot) - { - unchecked - { - g_iWeakSeed += 0x78A8C4B7; // Prime number - s32 ^= g_iWeakSeed; - } - } - - // Prevent overflow in the Random constructor of .NET 2.0 - if(s32 == int.MinValue) s32 = int.MaxValue; - - return new Random(s32); - } - } -} diff --git a/ModernKeePassLib/Cryptography/CryptoRandomStream.cs b/ModernKeePassLib/Cryptography/CryptoRandomStream.cs deleted file mode 100644 index 091cd5d..0000000 --- a/ModernKeePassLib/Cryptography/CryptoRandomStream.cs +++ /dev/null @@ -1,264 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Diagnostics; - -#if ModernKeePassLib -using ModernKeePassLib.Cryptography.Hash; -#elif !KeePassUAP -using System.Security.Cryptography; -#endif - -using ModernKeePassLib.Cryptography.Cipher; -using ModernKeePassLib.Utility; - -namespace ModernKeePassLib.Cryptography -{ - /// - /// Algorithms supported by CryptoRandomStream. - /// - public enum CrsAlgorithm - { - /// - /// Not supported. - /// - Null = 0, - - /// - /// A variant of the ARCFour algorithm (RC4 incompatible). - /// Insecure; for backward compatibility only. - /// - ArcFourVariant = 1, - - /// - /// Salsa20 stream cipher algorithm. - /// - Salsa20 = 2, - - /// - /// ChaCha20 stream cipher algorithm. - /// - ChaCha20 = 3, - - Count = 4 - } - - /// - /// A random stream class. The class is initialized using random - /// bytes provided by the caller. The produced stream has random - /// properties, but for the same seed always the same stream - /// is produced, i.e. this class can be used as stream cipher. - /// - public sealed class CryptoRandomStream : IDisposable - { - private readonly CrsAlgorithm m_crsAlgorithm; - private bool m_bDisposed = false; - - private byte[] m_pbState = null; - private byte m_i = 0; - private byte m_j = 0; - - private Salsa20Cipher m_salsa20 = null; - private ChaCha20Cipher m_chacha20 = null; - - /// - /// Construct a new cryptographically secure random stream object. - /// - /// Algorithm to use. - /// Initialization key. Must not be null and - /// must contain at least 1 byte. - public CryptoRandomStream(CrsAlgorithm a, byte[] pbKey) - { - if(pbKey == null) { Debug.Assert(false); throw new ArgumentNullException("pbKey"); } - - int cbKey = pbKey.Length; - if(cbKey <= 0) - { - Debug.Assert(false); // Need at least one byte - throw new ArgumentOutOfRangeException("pbKey"); - } - - m_crsAlgorithm = a; - - if(a == CrsAlgorithm.ChaCha20) - { - byte[] pbKey32 = new byte[32]; - byte[] pbIV12 = new byte[12]; - - using(SHA512Managed h = new SHA512Managed()) - { - byte[] pbHash = h.ComputeHash(pbKey); - Array.Copy(pbHash, pbKey32, 32); - Array.Copy(pbHash, 32, pbIV12, 0, 12); - MemUtil.ZeroByteArray(pbHash); - } - - m_chacha20 = new ChaCha20Cipher(pbKey32, pbIV12, true); - } - else if(a == CrsAlgorithm.Salsa20) - { - byte[] pbKey32 = CryptoUtil.HashSha256(pbKey); - byte[] pbIV8 = new byte[8] { 0xE8, 0x30, 0x09, 0x4B, - 0x97, 0x20, 0x5D, 0x2A }; // Unique constant - - m_salsa20 = new Salsa20Cipher(pbKey32, pbIV8); - } - else if(a == CrsAlgorithm.ArcFourVariant) - { - // Fill the state linearly - m_pbState = new byte[256]; - for(int w = 0; w < 256; ++w) m_pbState[w] = (byte)w; - - unchecked - { - byte j = 0, t; - int inxKey = 0; - for(int w = 0; w < 256; ++w) // Key setup - { - j += (byte)(m_pbState[w] + pbKey[inxKey]); - - t = m_pbState[0]; // Swap entries - m_pbState[0] = m_pbState[j]; - m_pbState[j] = t; - - ++inxKey; - if(inxKey >= cbKey) inxKey = 0; - } - } - - GetRandomBytes(512); // Increases security, see cryptanalysis - } - else // Unknown algorithm - { - Debug.Assert(false); - throw new ArgumentOutOfRangeException("a"); - } - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - private void Dispose(bool disposing) - { - if(disposing) - { - if(m_crsAlgorithm == CrsAlgorithm.ChaCha20) - m_chacha20.Dispose(); - else if(m_crsAlgorithm == CrsAlgorithm.Salsa20) - m_salsa20.Dispose(); - else if(m_crsAlgorithm == CrsAlgorithm.ArcFourVariant) - { - MemUtil.ZeroByteArray(m_pbState); - m_i = 0; - m_j = 0; - } - else { Debug.Assert(false); } - - m_bDisposed = true; - } - } - - /// - /// Get random bytes. - /// - /// Number of random bytes to retrieve. - /// Returns random bytes. - public byte[] GetRandomBytes(uint uRequestedCount) - { - if(m_bDisposed) throw new ObjectDisposedException(null); - - if(uRequestedCount == 0) return MemUtil.EmptyByteArray; - if(uRequestedCount > (uint)int.MaxValue) - throw new ArgumentOutOfRangeException("uRequestedCount"); - int cb = (int)uRequestedCount; - - byte[] pbRet = new byte[cb]; - - if(m_crsAlgorithm == CrsAlgorithm.ChaCha20) - m_chacha20.Encrypt(pbRet, 0, cb); - else if(m_crsAlgorithm == CrsAlgorithm.Salsa20) - m_salsa20.Encrypt(pbRet, 0, cb); - else if(m_crsAlgorithm == CrsAlgorithm.ArcFourVariant) - { - unchecked - { - for(int w = 0; w < cb; ++w) - { - ++m_i; - m_j += m_pbState[m_i]; - - byte t = m_pbState[m_i]; // Swap entries - m_pbState[m_i] = m_pbState[m_j]; - m_pbState[m_j] = t; - - t = (byte)(m_pbState[m_i] + m_pbState[m_j]); - pbRet[w] = m_pbState[t]; - } - } - } - else { Debug.Assert(false); } - - return pbRet; - } - - public ulong GetRandomUInt64() - { - byte[] pb = GetRandomBytes(8); - return MemUtil.BytesToUInt64(pb); - } - -#if CRSBENCHMARK - public static string Benchmark() - { - int nRounds = 2000000; - - string str = "ArcFour small: " + BenchTime(CrsAlgorithm.ArcFourVariant, - nRounds, 16).ToString() + "\r\n"; - str += "ArcFour big: " + BenchTime(CrsAlgorithm.ArcFourVariant, - 32, 2 * 1024 * 1024).ToString() + "\r\n"; - str += "Salsa20 small: " + BenchTime(CrsAlgorithm.Salsa20, - nRounds, 16).ToString() + "\r\n"; - str += "Salsa20 big: " + BenchTime(CrsAlgorithm.Salsa20, - 32, 2 * 1024 * 1024).ToString(); - return str; - } - - private static int BenchTime(CrsAlgorithm cra, int nRounds, int nDataSize) - { - byte[] pbKey = new byte[4] { 0x00, 0x01, 0x02, 0x03 }; - - int nStart = Environment.TickCount; - for(int i = 0; i < nRounds; ++i) - { - using(CryptoRandomStream c = new CryptoRandomStream(cra, pbKey)) - { - c.GetRandomBytes((uint)nDataSize); - } - } - int nEnd = Environment.TickCount; - - return (nEnd - nStart); - } -#endif - } -} diff --git a/ModernKeePassLib/Cryptography/CryptoUtil.cs b/ModernKeePassLib/Cryptography/CryptoUtil.cs deleted file mode 100644 index 2d6b4a4..0000000 --- a/ModernKeePassLib/Cryptography/CryptoUtil.cs +++ /dev/null @@ -1,237 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Reflection; -using System.Text; -#if ModernKeePassLib -using ModernKeePassLib.Cryptography.Hash; -#elif !KeePassUAP -using System.Security.Cryptography; -#endif - -using ModernKeePassLib.Native; -using ModernKeePassLib.Utility; - -namespace ModernKeePassLib.Cryptography -{ - public static class CryptoUtil - { - private static bool? g_obProtData = null; - public static bool IsProtectedDataSupported - { - get - { - if(g_obProtData.HasValue) return g_obProtData.Value; - - bool b = false; - try - { - Random r = CryptoRandom.NewWeakRandom(); - - byte[] pbData = new byte[137]; - r.NextBytes(pbData); - - byte[] pbEnt = new byte[41]; - r.NextBytes(pbEnt); - - byte[] pbEnc = ProtectedData.Protect(pbData, pbEnt, - DataProtectionScope.CurrentUser); - if((pbEnc != null) && !MemUtil.ArraysEqual(pbEnc, pbData)) - { - byte[] pbDec = ProtectedData.Unprotect(pbEnc, pbEnt, - DataProtectionScope.CurrentUser); - if((pbDec != null) && MemUtil.ArraysEqual(pbDec, pbData)) - b = true; - } - } - catch(Exception) { Debug.Assert(false); } - - Debug.Assert(b); // Should be supported on all systems - g_obProtData = b; - return b; - } - } - - public static byte[] HashSha256(byte[] pbData) - { - if(pbData == null) throw new ArgumentNullException("pbData"); - - return HashSha256(pbData, 0, pbData.Length); - } - - public static byte[] HashSha256(byte[] pbData, int iOffset, int cbCount) - { - if(pbData == null) throw new ArgumentNullException("pbData"); - -#if DEBUG - byte[] pbCopy = new byte[pbData.Length]; - Array.Copy(pbData, pbCopy, pbData.Length); -#endif - - byte[] pbHash; - using(SHA256Managed h = new SHA256Managed()) - { - pbHash = h.ComputeHash(pbData, iOffset, cbCount); - } - -#if DEBUG - // Ensure the data has not been modified - Debug.Assert(MemUtil.ArraysEqual(pbData, pbCopy)); - - Debug.Assert((pbHash != null) && (pbHash.Length == 32)); - byte[] pbZero = new byte[32]; - Debug.Assert(!MemUtil.ArraysEqual(pbHash, pbZero)); -#endif - - return pbHash; - } - - /// - /// Create a cryptographic key of length - /// (in bytes) from . - /// - public static byte[] ResizeKey(byte[] pbIn, int iInOffset, - int cbIn, int cbOut) - { - if(pbIn == null) throw new ArgumentNullException("pbIn"); - if(cbOut < 0) throw new ArgumentOutOfRangeException("cbOut"); - - if(cbOut == 0) return MemUtil.EmptyByteArray; - - byte[] pbHash; - if(cbOut <= 32) pbHash = HashSha256(pbIn, iInOffset, cbIn); - else - { - using(SHA512Managed h = new SHA512Managed()) - { - pbHash = h.ComputeHash(pbIn, iInOffset, cbIn); - } - } - - if(cbOut == pbHash.Length) return pbHash; - - byte[] pbRet = new byte[cbOut]; - if(cbOut < pbHash.Length) - Array.Copy(pbHash, pbRet, cbOut); - else - { - int iPos = 0; - ulong r = 0; - while(iPos < cbOut) - { - Debug.Assert(pbHash.Length == 64); - using(HMACSHA256 h = new HMACSHA256(pbHash)) - { - byte[] pbR = MemUtil.UInt64ToBytes(r); - byte[] pbPart = h.ComputeHash(pbR); - - int cbCopy = Math.Min(cbOut - iPos, pbPart.Length); - Debug.Assert(cbCopy > 0); - - Array.Copy(pbPart, 0, pbRet, iPos, cbCopy); - iPos += cbCopy; - ++r; - - MemUtil.ZeroByteArray(pbPart); - } - } - Debug.Assert(iPos == cbOut); - } - -#if DEBUG - byte[] pbZero = new byte[pbHash.Length]; - Debug.Assert(!MemUtil.ArraysEqual(pbHash, pbZero)); -#endif - MemUtil.ZeroByteArray(pbHash); - return pbRet; - } - -#if !ModernKeePassLib - private static bool? g_obAesCsp = null; - internal static SymmetricAlgorithm CreateAes() - { - if(g_obAesCsp.HasValue) - return (g_obAesCsp.Value ? CreateAesCsp() : new RijndaelManaged()); - - SymmetricAlgorithm a = CreateAesCsp(); - g_obAesCsp = (a != null); - return (a ?? new RijndaelManaged()); - } - - private static SymmetricAlgorithm CreateAesCsp() - { - try - { - // On Windows, the CSP implementation is only minimally - // faster (and for key derivations it's not used anyway, - // as KeePass uses a native implementation based on - // CNG/BCrypt, which is much faster) - if(!NativeLib.IsUnix()) return null; - - string strFqn = Assembly.CreateQualifiedName( - "System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089", - "System.Security.Cryptography.AesCryptoServiceProvider"); - - Type t = Type.GetType(strFqn); - if(t == null) return null; - - return (Activator.CreateInstance(t) as SymmetricAlgorithm); - } - catch(Exception) { Debug.Assert(false); } - - return null; - } -#endif - public static byte[] ProtectData(byte[] pb, byte[] pbOptEntropy, - DataProtectionScope s) - { - return ProtectDataPriv(pb, true, pbOptEntropy, s); - } - - public static byte[] UnprotectData(byte[] pb, byte[] pbOptEntropy, - DataProtectionScope s) - { - return ProtectDataPriv(pb, false, pbOptEntropy, s); - } - - private static byte[] ProtectDataPriv(byte[] pb, bool bProtect, - byte[] pbOptEntropy, DataProtectionScope s) - { - if(pb == null) throw new ArgumentNullException("pb"); - - if((pbOptEntropy != null) && (pbOptEntropy.Length == 0)) - pbOptEntropy = null; - - if(CryptoUtil.IsProtectedDataSupported) - { - if(bProtect) - return ProtectedData.Protect(pb, pbOptEntropy, s); - return ProtectedData.Unprotect(pb, pbOptEntropy, s); - } - - Debug.Assert(false); - byte[] pbCopy = new byte[pb.Length]; - Array.Copy(pb, pbCopy, pb.Length); - return pbCopy; - } - } -} diff --git a/ModernKeePassLib/Cryptography/Hash/Blake2b.cs b/ModernKeePassLib/Cryptography/Hash/Blake2b.cs deleted file mode 100644 index d7732c4..0000000 --- a/ModernKeePassLib/Cryptography/Hash/Blake2b.cs +++ /dev/null @@ -1,394 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -// This implementation is based on the official reference C -// implementation by Samuel Neves (CC0 1.0 Universal). - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Text; - -#if ModernKeePassLib -#elif !KeePassUAP -using System.Security.Cryptography; -#endif - -using ModernKeePassLib.Utility; - -namespace ModernKeePassLib.Cryptography.Hash -{ - public sealed class Blake2b : IDisposable - { - protected int HashSizeValue; - protected internal byte[] HashValue; - protected int State = 0; - - private bool m_bDisposed = false; - - private const int NbRounds = 12; - private const int NbBlockBytes = 128; - private const int NbMaxOutBytes = 64; - - private static readonly ulong[] g_vIV = new ulong[8] { - 0x6A09E667F3BCC908UL, 0xBB67AE8584CAA73BUL, - 0x3C6EF372FE94F82BUL, 0xA54FF53A5F1D36F1UL, - 0x510E527FADE682D1UL, 0x9B05688C2B3E6C1FUL, - 0x1F83D9ABFB41BD6BUL, 0x5BE0CD19137E2179UL - }; - - private static readonly int[] g_vSigma = new int[NbRounds * 16] { - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, - 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3, - 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4, - 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8, - 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13, - 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9, - 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11, - 13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10, - 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5, - 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, - 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 - }; - - private readonly int m_cbHashLength; - - private ulong[] m_h = new ulong[8]; - private ulong[] m_t = new ulong[2]; - private ulong[] m_f = new ulong[2]; - private byte[] m_buf = new byte[NbBlockBytes]; - private int m_cbBuf = 0; - - private ulong[] m_m = new ulong[16]; - private ulong[] m_v = new ulong[16]; - - public int HashSize - { - get { return HashSizeValue; } - } - - public byte[] Hash - { - get - { - if (m_bDisposed) - throw new ObjectDisposedException(null); - if (State != 0) - throw new InvalidOperationException("Blak2B Cryptography Hash Not Yet Finalized"); - return (byte[])HashValue.Clone(); - } - } - - public Blake2b() - { - m_cbHashLength = NbMaxOutBytes; - this.HashSizeValue = NbMaxOutBytes * 8; // Bits - - Initialize(); - } - - public Blake2b(int cbHashLength) - { - if((cbHashLength < 0) || (cbHashLength > NbMaxOutBytes)) - throw new ArgumentOutOfRangeException("cbHashLength"); - - m_cbHashLength = cbHashLength; - this.HashSizeValue = cbHashLength * 8; // Bits - - Initialize(); - } - - public void Initialize() - { - Debug.Assert(m_h.Length == g_vIV.Length); - Array.Copy(g_vIV, m_h, m_h.Length); - - // Fan-out = 1, depth = 1 - m_h[0] ^= 0x0000000001010000UL ^ (ulong)m_cbHashLength; - - Array.Clear(m_t, 0, m_t.Length); - Array.Clear(m_f, 0, m_f.Length); - Array.Clear(m_buf, 0, m_buf.Length); - m_cbBuf = 0; - - Array.Clear(m_m, 0, m_m.Length); - Array.Clear(m_v, 0, m_v.Length); - } - - private static void G(ulong[] v, ulong[] m, int r16, int i, - int a, int b, int c, int d) - { - int p = r16 + i; - - v[a] += v[b] + m[g_vSigma[p]]; - v[d] = MemUtil.RotateRight64(v[d] ^ v[a], 32); - v[c] += v[d]; - v[b] = MemUtil.RotateRight64(v[b] ^ v[c], 24); - v[a] += v[b] + m[g_vSigma[p + 1]]; - v[d] = MemUtil.RotateRight64(v[d] ^ v[a], 16); - v[c] += v[d]; - v[b] = MemUtil.RotateRight64(v[b] ^ v[c], 63); - } - - private void Compress(byte[] pb, int iOffset) - { - ulong[] v = m_v; - ulong[] m = m_m; - ulong[] h = m_h; - - for(int i = 0; i < 16; ++i) - m[i] = MemUtil.BytesToUInt64(pb, iOffset + (i << 3)); - - Array.Copy(h, v, 8); - v[8] = g_vIV[0]; - v[9] = g_vIV[1]; - v[10] = g_vIV[2]; - v[11] = g_vIV[3]; - v[12] = g_vIV[4] ^ m_t[0]; - v[13] = g_vIV[5] ^ m_t[1]; - v[14] = g_vIV[6] ^ m_f[0]; - v[15] = g_vIV[7] ^ m_f[1]; - - for(int r = 0; r < NbRounds; ++r) - { - int r16 = r << 4; - - G(v, m, r16, 0, 0, 4, 8, 12); - G(v, m, r16, 2, 1, 5, 9, 13); - G(v, m, r16, 4, 2, 6, 10, 14); - G(v, m, r16, 6, 3, 7, 11, 15); - G(v, m, r16, 8, 0, 5, 10, 15); - G(v, m, r16, 10, 1, 6, 11, 12); - G(v, m, r16, 12, 2, 7, 8, 13); - G(v, m, r16, 14, 3, 4, 9, 14); - } - - for(int i = 0; i < 8; ++i) - h[i] ^= v[i] ^ v[i + 8]; - } - - private void IncrementCounter(ulong cb) - { - m_t[0] += cb; - if(m_t[0] < cb) ++m_t[1]; - } - - private void HashCore(byte[] array, int ibStart, int cbSize) - { - Debug.Assert(m_f[0] == 0); - - if((m_cbBuf + cbSize) > NbBlockBytes) // Not '>=' (buffer must not be empty) - { - int cbFill = NbBlockBytes - m_cbBuf; - if(cbFill > 0) Array.Copy(array, ibStart, m_buf, m_cbBuf, cbFill); - - IncrementCounter((ulong)NbBlockBytes); - Compress(m_buf, 0); - - m_cbBuf = 0; - cbSize -= cbFill; - ibStart += cbFill; - - while(cbSize > NbBlockBytes) // Not '>=' (buffer must not be empty) - { - IncrementCounter((ulong)NbBlockBytes); - Compress(array, ibStart); - - cbSize -= NbBlockBytes; - ibStart += NbBlockBytes; - } - } - - if(cbSize > 0) - { - Debug.Assert((m_cbBuf + cbSize) <= NbBlockBytes); - - Array.Copy(array, ibStart, m_buf, m_cbBuf, cbSize); - m_cbBuf += cbSize; - } - } - - private byte[] HashFinal() - { - if(m_f[0] != 0) { Debug.Assert(false); throw new InvalidOperationException(); } - Debug.Assert(((m_t[1] == 0) && (m_t[0] == 0)) || - (m_cbBuf > 0)); // Buffer must not be empty for last block processing - - m_f[0] = ulong.MaxValue; // Indicate last block - - int cbFill = NbBlockBytes - m_cbBuf; - if(cbFill > 0) Array.Clear(m_buf, m_cbBuf, cbFill); - - IncrementCounter((ulong)m_cbBuf); - Compress(m_buf, 0); - - byte[] pbHash = new byte[NbMaxOutBytes]; - for(int i = 0; i < m_h.Length; ++i) - MemUtil.UInt64ToBytesEx(m_h[i], pbHash, i << 3); - - if(m_cbHashLength == NbMaxOutBytes) return pbHash; - Debug.Assert(m_cbHashLength < NbMaxOutBytes); - - byte[] pbShort = new byte[m_cbHashLength]; - if(m_cbHashLength > 0) - Array.Copy(pbHash, pbShort, m_cbHashLength); - MemUtil.ZeroByteArray(pbHash); - return pbShort; - } - - public byte[] ComputeHash(Stream inputStream) - { - if (m_bDisposed) - throw new ObjectDisposedException(null); - - // Default the buffer size to 4K. - byte[] buffer = new byte[4096]; - int bytesRead; - do - { - bytesRead = inputStream.Read(buffer, 0, 4096); - if (bytesRead > 0) - { - HashCore(buffer, 0, bytesRead); - } - } while (bytesRead > 0); - - HashValue = HashFinal(); - byte[] Tmp = (byte[])HashValue.Clone(); - Initialize(); - return (Tmp); - } - - public byte[] ComputeHash(byte[] buffer) - { - if (m_bDisposed) - throw new ObjectDisposedException(null); - - // Do some validation - if (buffer == null) throw new ArgumentNullException("buffer"); - - HashCore(buffer, 0, buffer.Length); - HashValue = HashFinal(); - byte[] Tmp = (byte[])HashValue.Clone(); - Initialize(); - return (Tmp); - } - - public byte[] ComputeHash(byte[] buffer, int offset, int count) - { - // Do some validation - if (buffer == null) - throw new ArgumentNullException("buffer"); - if (offset < 0) - throw new ArgumentOutOfRangeException("offset", "ArgumentOutOfRange_NeedNonNegNum"); - if (count < 0 || (count > buffer.Length)) - throw new ArgumentException("Argument_InvalidValue"); - if ((buffer.Length - count) < offset) - throw new ArgumentException("Argument_InvalidOffLen"); - - if (m_bDisposed) - throw new ObjectDisposedException(null); - - HashCore(buffer, offset, count); - HashValue = HashFinal(); - byte[] Tmp = (byte[])HashValue.Clone(); - Initialize(); - return (Tmp); - } - - public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset) - { - // Do some validation, we let BlockCopy do the destination array validation - if (inputBuffer == null) - throw new ArgumentNullException("inputBuffer"); - if (inputOffset < 0) - throw new ArgumentOutOfRangeException("inputOffset", "ArgumentOutOfRange_NeedNonNegNum"); - if (inputCount < 0 || (inputCount > inputBuffer.Length)) - throw new ArgumentException("Argument_InvalidValue"); - if ((inputBuffer.Length - inputCount) < inputOffset) - throw new ArgumentException("Argument_InvalidOffLen"); - - if (m_bDisposed) - throw new ObjectDisposedException(null); - - // Change the State value - State = 1; - HashCore(inputBuffer, inputOffset, inputCount); - if ((outputBuffer != null) && ((inputBuffer != outputBuffer) || (inputOffset != outputOffset))) - Buffer.BlockCopy(inputBuffer, inputOffset, outputBuffer, outputOffset, inputCount); - return inputCount; - } - - public byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount) - { - // Do some validation - if (inputBuffer == null) - throw new ArgumentNullException("inputBuffer"); - if (inputOffset < 0) - throw new ArgumentOutOfRangeException("inputOffset", "ArgumentOutOfRange_NeedNonNegNum"); - if (inputCount < 0 || (inputCount > inputBuffer.Length)) - throw new ArgumentException("Argument_InvalidValue"); - if ((inputBuffer.Length - inputCount) < inputOffset) - throw new ArgumentException("Argument_InvalidOffLen"); - - if (m_bDisposed) - throw new ObjectDisposedException(null); - - HashCore(inputBuffer, inputOffset, inputCount); - HashValue = HashFinal(); - byte[] outputBytes; - if (inputCount != 0) - { - outputBytes = new byte[inputCount]; - Array.Copy(inputBuffer, inputOffset, outputBytes, 0, inputCount); - } - else - { - outputBytes = MemUtil.EmptyByteArray; - } - // reset the State value - State = 0; - return outputBytes; - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - public void Clear() - { - (this as IDisposable).Dispose(); - } - - private void Dispose(bool disposing) - { - if (disposing) - { - if (HashValue != null) - Array.Clear(HashValue, 0, HashValue.Length); - HashValue = null; - m_bDisposed = true; - } - } - } -} diff --git a/ModernKeePassLib/Cryptography/Hash/HMAC.cs b/ModernKeePassLib/Cryptography/Hash/HMAC.cs deleted file mode 100644 index 281baa9..0000000 --- a/ModernKeePassLib/Cryptography/Hash/HMAC.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System; -using Org.BouncyCastle.Crypto.Macs; - -namespace ModernKeePassLib.Cryptography.Hash -{ - public class HMAC : IDisposable - { - protected HMac Hmac; - - public byte[] Hash - { - get - { - var result = new byte[Hmac.GetMacSize()]; - Hmac.DoFinal(result, 0); - return result; - } - } - - public byte[] ComputeHash(byte[] value) - { - return ComputeHash(value, 0, value.Length); - } - - public byte[] ComputeHash(byte[] value, int offset, int length) - { - if (value == null) throw new ArgumentNullException(nameof(value)); - - byte[] resBuf = new byte[Hmac.GetMacSize()]; - Hmac.BlockUpdate(value, offset, length); - Hmac.DoFinal(resBuf, offset); - - return resBuf; - } - - public void TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset) - { - Hmac.BlockUpdate(inputBuffer, inputOffset, inputCount); - if ((outputBuffer != null) && ((inputBuffer != outputBuffer) || (inputOffset != outputOffset))) - Buffer.BlockCopy(inputBuffer, inputOffset, outputBuffer, outputOffset, inputCount); - } - - public byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount) - { - Hmac.BlockUpdate(inputBuffer, inputOffset, inputCount); - byte[] outputBytes = new byte[inputCount]; - if (inputCount != 0) - Buffer.BlockCopy(inputBuffer, inputOffset, outputBytes, 0, inputCount); - return outputBytes; - } - - public void Initialize() - { - Hmac.Reset(); - } - - public void Dispose() - { - Hmac.Reset(); - } - } -} diff --git a/ModernKeePassLib/Cryptography/Hash/HMACSHA1.cs b/ModernKeePassLib/Cryptography/Hash/HMACSHA1.cs deleted file mode 100644 index 1653a82..0000000 --- a/ModernKeePassLib/Cryptography/Hash/HMACSHA1.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Runtime.InteropServices.WindowsRuntime; -using Windows.Security.Cryptography.Core; - -namespace ModernKeePassLib.Cryptography.Hash -{ - public class HMACSHA1: HashAlgorithm - { - public HMACSHA1(byte[] key) : base (MacAlgorithmProvider.OpenAlgorithm(MacAlgorithmNames.HmacSha1).CreateHash(key.AsBuffer())) {} - } -} diff --git a/ModernKeePassLib/Cryptography/Hash/HMACSHA256.cs b/ModernKeePassLib/Cryptography/Hash/HMACSHA256.cs deleted file mode 100644 index 49280d8..0000000 --- a/ModernKeePassLib/Cryptography/Hash/HMACSHA256.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Runtime.InteropServices.WindowsRuntime; -using Windows.Security.Cryptography.Core; - -namespace ModernKeePassLib.Cryptography.Hash -{ - public class HMACSHA256: HashAlgorithm - { - public HMACSHA256(byte[] key) : base (MacAlgorithmProvider.OpenAlgorithm(MacAlgorithmNames.HmacSha256).CreateHash(key.AsBuffer())) {} - } -} diff --git a/ModernKeePassLib/Cryptography/Hash/HashAlgorithm.cs b/ModernKeePassLib/Cryptography/Hash/HashAlgorithm.cs deleted file mode 100644 index 8ff9821..0000000 --- a/ModernKeePassLib/Cryptography/Hash/HashAlgorithm.cs +++ /dev/null @@ -1,100 +0,0 @@ -using System; -using System.Runtime.InteropServices.WindowsRuntime; -using Windows.Security.Cryptography.Core; -using Validation; - -namespace ModernKeePassLib.Cryptography.Hash -{ - public abstract class HashAlgorithm: IDisposable - { - /// - /// The platform-specific hash object. - /// - private readonly CryptographicHash _hash; - - /// - /// Initializes a new instance of the class. - /// - /// The platform hash. - internal HashAlgorithm(CryptographicHash hash) - { - Requires.NotNull(hash, "Hash"); - _hash = hash; - } - - public bool CanReuseTransform => true; - public bool CanTransformMultipleBlocks => true; - - public byte[] Hash => _hash.GetValueAndReset().ToArray(); - - public void Append(byte[] data) - { - _hash.Append(data.AsBuffer()); - } - - public byte[] GetValueAndReset() - { - return _hash.GetValueAndReset().ToArray(); - } - - #region ICryptoTransform methods - - public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset) - { - byte[] buffer; - if (inputCount < inputBuffer.Length) - { - buffer = new byte[inputCount]; - Array.Copy(inputBuffer, inputOffset, buffer, 0, inputCount); - } - else - { - buffer = inputBuffer; - } - - Append(buffer); - if (outputBuffer != null) - { - Array.Copy(inputBuffer, inputOffset, outputBuffer, outputOffset, inputCount); - } - - return inputCount; - } - - public byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount) - { - this.TransformBlock(inputBuffer, inputOffset, inputCount, null, 0); - if (inputCount == inputBuffer.Length) - { - return inputBuffer; - } - var buffer = new byte[inputCount]; - Array.Copy(inputBuffer, inputOffset, buffer, 0, inputCount); - return buffer; - } - - public byte[] ComputeHash(byte[] value) - { - return ComputeHash(value, 0, value.Length); - } - - public byte[] ComputeHash(byte[] value, int offset, int length) - { - if (value == null) throw new ArgumentNullException(nameof(value)); - - TransformFinalBlock(value, offset, length); - var resBuf = GetValueAndReset(); - - return resBuf; - } - public void Initialize() - { - } - - public void Dispose() - { - } - - #endregion - } -} diff --git a/ModernKeePassLib/Cryptography/Hash/SHA256Managed.cs b/ModernKeePassLib/Cryptography/Hash/SHA256Managed.cs deleted file mode 100644 index 1996667..0000000 --- a/ModernKeePassLib/Cryptography/Hash/SHA256Managed.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Windows.Security.Cryptography.Core; - -namespace ModernKeePassLib.Cryptography.Hash -{ - public class SHA256Managed : HashAlgorithm - { - public SHA256Managed() : base(HashAlgorithmProvider.OpenAlgorithm(HashAlgorithmNames.Sha256).CreateHash()) {} - } -} diff --git a/ModernKeePassLib/Cryptography/Hash/SHA512Managed.cs b/ModernKeePassLib/Cryptography/Hash/SHA512Managed.cs deleted file mode 100644 index 26fd625..0000000 --- a/ModernKeePassLib/Cryptography/Hash/SHA512Managed.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Windows.Security.Cryptography.Core; - -namespace ModernKeePassLib.Cryptography.Hash -{ - public class SHA512Managed: HashAlgorithm - { - public SHA512Managed() : base(HashAlgorithmProvider.OpenAlgorithm(HashAlgorithmNames.Sha512).CreateHash()) {} - } -} diff --git a/ModernKeePassLib/Cryptography/HashingStreamEx.cs b/ModernKeePassLib/Cryptography/HashingStreamEx.cs deleted file mode 100644 index 7a4fde4..0000000 --- a/ModernKeePassLib/Cryptography/HashingStreamEx.cs +++ /dev/null @@ -1,186 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Text; -#if ModernKeePassLib -using ModernKeePassLib.Cryptography.Hash; -#elif !KeePassUAP -using System.Security.Cryptography; -#endif - -using ModernKeePassLib.Utility; - -namespace ModernKeePassLib.Cryptography -{ - public sealed class HashingStreamEx : Stream - { - private readonly Stream m_sBaseStream; - private readonly bool m_bWriting; - private HashAlgorithm m_hash; - - private byte[] m_pbFinalHash = null; - - public byte[] Hash - { - get { return m_pbFinalHash; } - } - - public override bool CanRead - { - get { return !m_bWriting; } - } - - public override bool CanSeek - { - get { return false; } - } - - public override bool CanWrite - { - get { return m_bWriting; } - } - - public override long Length - { - get { return m_sBaseStream.Length; } - } - - public override long Position - { - get { return m_sBaseStream.Position; } - set { Debug.Assert(false); throw new NotSupportedException(); } - } - - public HashingStreamEx(Stream sBaseStream, bool bWriting, HashAlgorithm hashAlgorithm) - { - if(sBaseStream == null) throw new ArgumentNullException("sBaseStream"); - - m_sBaseStream = sBaseStream; - m_bWriting = bWriting; - -#if !KeePassLibSD - m_hash = (hashAlgorithm ?? new SHA256Managed()); -#else // KeePassLibSD - m_hash = null; - - try { m_hash = HashAlgorithm.Create("SHA256"); } - catch(Exception) { } - try { if(m_hash == null) m_hash = HashAlgorithm.Create(); } - catch(Exception) { } -#endif - if(m_hash == null) { Debug.Assert(false); return; } - - // Validate hash algorithm - if(!m_hash.CanReuseTransform || !m_hash.CanTransformMultipleBlocks) - { - Debug.Assert(false); - m_hash = null; - } - } - - protected override void Dispose(bool disposing) - { - if(disposing) - { - if(m_hash != null) - { - try - { - m_hash.TransformFinalBlock(MemUtil.EmptyByteArray, 0, 0); - - m_pbFinalHash = m_hash.Hash; - } - catch(Exception) { Debug.Assert(false); } - - m_hash = null; - } - - m_sBaseStream.Dispose(); - } - - base.Dispose(disposing); - } - - public override void Flush() - { - m_sBaseStream.Flush(); - } - - public override long Seek(long lOffset, SeekOrigin soOrigin) - { - throw new NotSupportedException(); - } - - public override void SetLength(long lValue) - { - throw new NotSupportedException(); - } - - public override int Read(byte[] pbBuffer, int nOffset, int nCount) - { - if(m_bWriting) throw new InvalidOperationException(); - - int nRead = m_sBaseStream.Read(pbBuffer, nOffset, nCount); - int nPartialRead = nRead; - while((nRead < nCount) && (nPartialRead != 0)) - { - nPartialRead = m_sBaseStream.Read(pbBuffer, nOffset + nRead, - nCount - nRead); - nRead += nPartialRead; - } - -#if DEBUG - byte[] pbOrg = new byte[pbBuffer.Length]; - Array.Copy(pbBuffer, pbOrg, pbBuffer.Length); -#endif - - if((m_hash != null) && (nRead > 0)) - m_hash.TransformBlock(pbBuffer, nOffset, nRead, pbBuffer, nOffset); - -#if DEBUG - Debug.Assert(MemUtil.ArraysEqual(pbBuffer, pbOrg)); -#endif - - return nRead; - } - - public override void Write(byte[] pbBuffer, int nOffset, int nCount) - { - if(!m_bWriting) throw new InvalidOperationException(); - -#if DEBUG - byte[] pbOrg = new byte[pbBuffer.Length]; - Array.Copy(pbBuffer, pbOrg, pbBuffer.Length); -#endif - - if((m_hash != null) && (nCount > 0)) - m_hash.TransformBlock(pbBuffer, nOffset, nCount, pbBuffer, nOffset); - -#if DEBUG - Debug.Assert(MemUtil.ArraysEqual(pbBuffer, pbOrg)); -#endif - - m_sBaseStream.Write(pbBuffer, nOffset, nCount); - } - } -} diff --git a/ModernKeePassLib/Cryptography/HmacOtp.cs b/ModernKeePassLib/Cryptography/HmacOtp.cs deleted file mode 100644 index 0e11aab..0000000 --- a/ModernKeePassLib/Cryptography/HmacOtp.cs +++ /dev/null @@ -1,96 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Text; -#if ModernKeePassLib -using ModernKeePassLib.Cryptography.Hash; -#elif !KeePassUAP -using System.Security.Cryptography; -#endif - -using ModernKeePassLib.Utility; - -#if !KeePassLibSD -namespace ModernKeePassLib.Cryptography -{ - /// - /// Generate HMAC-based one-time passwords as specified in RFC 4226. - /// - public static class HmacOtp - { - private static readonly uint[] vDigitsPower = new uint[]{ 1, 10, 100, - 1000, 10000, 100000, 1000000, 10000000, 100000000 }; - - public static string Generate(byte[] pbSecret, ulong uFactor, - uint uCodeDigits, bool bAddChecksum, int iTruncationOffset) - { - byte[] pbText = MemUtil.UInt64ToBytes(uFactor); - Array.Reverse(pbText); // To big-endian - - HMACSHA1 hsha1 = new HMACSHA1(pbSecret); - byte[] pbHash = hsha1.ComputeHash(pbText); - - uint uOffset = (uint)(pbHash[pbHash.Length - 1] & 0xF); - if((iTruncationOffset >= 0) && (iTruncationOffset < (pbHash.Length - 4))) - uOffset = (uint)iTruncationOffset; - - uint uBinary = (uint)(((pbHash[uOffset] & 0x7F) << 24) | - ((pbHash[uOffset + 1] & 0xFF) << 16) | - ((pbHash[uOffset + 2] & 0xFF) << 8) | - (pbHash[uOffset + 3] & 0xFF)); - - uint uOtp = (uBinary % vDigitsPower[uCodeDigits]); - if(bAddChecksum) - uOtp = ((uOtp * 10) + CalculateChecksum(uOtp, uCodeDigits)); - - uint uDigits = (bAddChecksum ? (uCodeDigits + 1) : uCodeDigits); - return uOtp.ToString(NumberFormatInfo.InvariantInfo).PadLeft( - (int)uDigits, '0'); - } - - private static readonly uint[] vDoubleDigits = new uint[]{ 0, 2, 4, 6, 8, - 1, 3, 5, 7, 9 }; - - private static uint CalculateChecksum(uint uNum, uint uDigits) - { - bool bDoubleDigit = true; - uint uTotal = 0; - - while(0 < uDigits--) - { - uint uDigit = (uNum % 10); - uNum /= 10; - - if(bDoubleDigit) uDigit = vDoubleDigits[uDigit]; - - uTotal += uDigit; - bDoubleDigit = !bDoubleDigit; - } - - uint uResult = (uTotal % 10); - if(uResult != 0) uResult = 10 - uResult; - - return uResult; - } - } -} -#endif diff --git a/ModernKeePassLib/Cryptography/KeyDerivation/AesKdf.GCrypt.cs b/ModernKeePassLib/Cryptography/KeyDerivation/AesKdf.GCrypt.cs deleted file mode 100644 index 51b100d..0000000 --- a/ModernKeePassLib/Cryptography/KeyDerivation/AesKdf.GCrypt.cs +++ /dev/null @@ -1,399 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Runtime.InteropServices; -using System.Text; -using System.Threading; - -using ModernKeePassLib.Native; -using ModernKeePassLib.Utility; - -namespace ModernKeePassLib.Cryptography.KeyDerivation -{ - public sealed partial class AesKdf : KdfEngine - { - private static bool TransformKeyGCrypt(byte[] pbData32, byte[] pbSeed32, - ulong uRounds) - { - byte[] pbNewData32 = null; - try - { - if(GCryptInitLib()) - { - pbNewData32 = new byte[32]; - Array.Copy(pbData32, pbNewData32, 32); - - if(TransformKeyGCryptPriv(pbNewData32, pbSeed32, uRounds)) - { - Array.Copy(pbNewData32, pbData32, 32); - return true; - } - } - } - catch(Exception) { } - finally { if(pbNewData32 != null) MemUtil.ZeroByteArray(pbNewData32); } - - return false; - } - - private static bool TransformKeyBenchmarkGCrypt(uint uTimeMs, out ulong uRounds) - { - uRounds = 0; - - try - { - if(GCryptInitLib()) - return TransformKeyBenchmarkGCryptPriv(uTimeMs, ref uRounds); - } - catch(Exception) { } - - return false; - } - - private static bool GCryptInitLib() - { - if(!NativeLib.IsUnix()) return false; // Independent of workaround state - if(!MonoWorkarounds.IsRequired(1468)) return false; // Can be turned off - - // gcry_check_version initializes the library; - // throws when LibGCrypt is not available - NativeMethods.gcry_check_version(IntPtr.Zero); - return true; - } - - // ============================================================= - // Multi-threaded implementation - - // For some reason, the following multi-threaded implementation - // is slower than the single-threaded implementation below - // (threading overhead by Mono? LibGCrypt threading issues?) - /* private sealed class GCryptTransformInfo : IDisposable - { - public IntPtr Data16; - public IntPtr Seed32; - public ulong Rounds; - public uint TimeMs; - - public bool Success = false; - - public GCryptTransformInfo(byte[] pbData32, int iDataOffset, - byte[] pbSeed32, ulong uRounds, uint uTimeMs) - { - this.Data16 = Marshal.AllocCoTaskMem(16); - Marshal.Copy(pbData32, iDataOffset, this.Data16, 16); - - this.Seed32 = Marshal.AllocCoTaskMem(32); - Marshal.Copy(pbSeed32, 0, this.Seed32, 32); - - this.Rounds = uRounds; - this.TimeMs = uTimeMs; - } - - public void Dispose() - { - if(this.Data16 != IntPtr.Zero) - { - Marshal.WriteInt64(this.Data16, 0); - Marshal.WriteInt64(this.Data16, 8, 0); - Marshal.FreeCoTaskMem(this.Data16); - this.Data16 = IntPtr.Zero; - } - - if(this.Seed32 != IntPtr.Zero) - { - Marshal.FreeCoTaskMem(this.Seed32); - this.Seed32 = IntPtr.Zero; - } - } - } - - private static GCryptTransformInfo[] GCryptRun(byte[] pbData32, - byte[] pbSeed32, ulong uRounds, uint uTimeMs, ParameterizedThreadStart fL, - ParameterizedThreadStart fR) - { - GCryptTransformInfo tiL = new GCryptTransformInfo(pbData32, 0, - pbSeed32, uRounds, uTimeMs); - GCryptTransformInfo tiR = new GCryptTransformInfo(pbData32, 16, - pbSeed32, uRounds, uTimeMs); - - Thread th = new Thread(fL); - th.Start(tiL); - - fR(tiR); - - th.Join(); - - Marshal.Copy(tiL.Data16, pbData32, 0, 16); - Marshal.Copy(tiR.Data16, pbData32, 16, 16); - - tiL.Dispose(); - tiR.Dispose(); - - if(tiL.Success && tiR.Success) - return new GCryptTransformInfo[2] { tiL, tiR }; - return null; - } - - private static bool TransformKeyGCryptPriv(byte[] pbData32, byte[] pbSeed32, - ulong uRounds) - { - return (GCryptRun(pbData32, pbSeed32, uRounds, 0, - new ParameterizedThreadStart(AesKdf.GCryptTransformTh), - new ParameterizedThreadStart(AesKdf.GCryptTransformTh)) != null); - } - - private static bool GCryptInitCipher(ref IntPtr h, GCryptTransformInfo ti) - { - NativeMethods.gcry_cipher_open(ref h, NativeMethods.GCRY_CIPHER_AES256, - NativeMethods.GCRY_CIPHER_MODE_ECB, 0); - if(h == IntPtr.Zero) { Debug.Assert(false); return false; } - - IntPtr n32 = new IntPtr(32); - if(NativeMethods.gcry_cipher_setkey(h, ti.Seed32, n32) != 0) - { - Debug.Assert(false); - return false; - } - - return true; - } - - private static void GCryptTransformTh(object o) - { - IntPtr h = IntPtr.Zero; - try - { - GCryptTransformInfo ti = (o as GCryptTransformInfo); - if(ti == null) { Debug.Assert(false); return; } - - if(!GCryptInitCipher(ref h, ti)) return; - - IntPtr n16 = new IntPtr(16); - for(ulong u = 0; u < ti.Rounds; ++u) - { - if(NativeMethods.gcry_cipher_encrypt(h, ti.Data16, n16, - IntPtr.Zero, IntPtr.Zero) != 0) - { - Debug.Assert(false); - return; - } - } - - ti.Success = true; - } - catch(Exception) { Debug.Assert(false); } - finally - { - try { if(h != IntPtr.Zero) NativeMethods.gcry_cipher_close(h); } - catch(Exception) { Debug.Assert(false); } - } - } - - private static bool TransformKeyBenchmarkGCryptPriv(uint uTimeMs, ref ulong uRounds) - { - GCryptTransformInfo[] v = GCryptRun(new byte[32], new byte[32], - 0, uTimeMs, - new ParameterizedThreadStart(AesKdf.GCryptBenchmarkTh), - new ParameterizedThreadStart(AesKdf.GCryptBenchmarkTh)); - - if(v != null) - { - ulong uL = Math.Min(v[0].Rounds, ulong.MaxValue >> 1); - ulong uR = Math.Min(v[1].Rounds, ulong.MaxValue >> 1); - uRounds = (uL + uR) / 2; - - return true; - } - return false; - } - - private static void GCryptBenchmarkTh(object o) - { - IntPtr h = IntPtr.Zero; - try - { - GCryptTransformInfo ti = (o as GCryptTransformInfo); - if(ti == null) { Debug.Assert(false); return; } - - if(!GCryptInitCipher(ref h, ti)) return; - - ulong r = 0; - IntPtr n16 = new IntPtr(16); - int tStart = Environment.TickCount; - while(true) - { - for(ulong j = 0; j < BenchStep; ++j) - { - if(NativeMethods.gcry_cipher_encrypt(h, ti.Data16, n16, - IntPtr.Zero, IntPtr.Zero) != 0) - { - Debug.Assert(false); - return; - } - } - - r += BenchStep; - if(r < BenchStep) // Overflow check - { - r = ulong.MaxValue; - break; - } - - uint tElapsed = (uint)(Environment.TickCount - tStart); - if(tElapsed > ti.TimeMs) break; - } - - ti.Rounds = r; - ti.Success = true; - } - catch(Exception) { Debug.Assert(false); } - finally - { - try { if(h != IntPtr.Zero) NativeMethods.gcry_cipher_close(h); } - catch(Exception) { Debug.Assert(false); } - } - } */ - - // ============================================================= - // Single-threaded implementation - - private static bool GCryptInitCipher(ref IntPtr h, IntPtr pSeed32) - { - NativeMethods.gcry_cipher_open(ref h, NativeMethods.GCRY_CIPHER_AES256, - NativeMethods.GCRY_CIPHER_MODE_ECB, 0); - if(h == IntPtr.Zero) { Debug.Assert(false); return false; } - - IntPtr n32 = new IntPtr(32); - if(NativeMethods.gcry_cipher_setkey(h, pSeed32, n32) != 0) - { - Debug.Assert(false); - return false; - } - - return true; - } - - private static bool GCryptBegin(byte[] pbData32, byte[] pbSeed32, - ref IntPtr h, ref IntPtr pData32, ref IntPtr pSeed32) - { - pData32 = Marshal.AllocCoTaskMem(32); - pSeed32 = Marshal.AllocCoTaskMem(32); - - Marshal.Copy(pbData32, 0, pData32, 32); - Marshal.Copy(pbSeed32, 0, pSeed32, 32); - - return GCryptInitCipher(ref h, pSeed32); - } - - private static void GCryptEnd(IntPtr h, IntPtr pData32, IntPtr pSeed32) - { - NativeMethods.gcry_cipher_close(h); - - Marshal.WriteInt64(pData32, 0); - Marshal.WriteInt64(pData32, 8, 0); - Marshal.WriteInt64(pData32, 16, 0); - Marshal.WriteInt64(pData32, 24, 0); - - Marshal.FreeCoTaskMem(pData32); - Marshal.FreeCoTaskMem(pSeed32); - } - - private static bool TransformKeyGCryptPriv(byte[] pbData32, byte[] pbSeed32, - ulong uRounds) - { - IntPtr h = IntPtr.Zero, pData32 = IntPtr.Zero, pSeed32 = IntPtr.Zero; - if(!GCryptBegin(pbData32, pbSeed32, ref h, ref pData32, ref pSeed32)) - return false; - - try - { - IntPtr n32 = new IntPtr(32); - for(ulong i = 0; i < uRounds; ++i) - { - if(NativeMethods.gcry_cipher_encrypt(h, pData32, n32, - IntPtr.Zero, IntPtr.Zero) != 0) - { - Debug.Assert(false); - return false; - } - } - - Marshal.Copy(pData32, pbData32, 0, 32); - return true; - } - catch(Exception) { Debug.Assert(false); } - finally { GCryptEnd(h, pData32, pSeed32); } - - return false; - } - - private static bool TransformKeyBenchmarkGCryptPriv(uint uTimeMs, ref ulong uRounds) - { - byte[] pbData32 = new byte[32]; - byte[] pbSeed32 = new byte[32]; - - IntPtr h = IntPtr.Zero, pData32 = IntPtr.Zero, pSeed32 = IntPtr.Zero; - if(!GCryptBegin(pbData32, pbSeed32, ref h, ref pData32, ref pSeed32)) - return false; - - uint uMaxMs = uTimeMs; - ulong uDiv = 1; - if(uMaxMs <= (uint.MaxValue >> 1)) { uMaxMs *= 2U; uDiv = 2; } - - try - { - ulong r = 0; - IntPtr n32 = new IntPtr(32); - int tStart = Environment.TickCount; - while(true) - { - for(ulong j = 0; j < BenchStep; ++j) - { - if(NativeMethods.gcry_cipher_encrypt(h, pData32, n32, - IntPtr.Zero, IntPtr.Zero) != 0) - { - Debug.Assert(false); - return false; - } - } - - r += BenchStep; - if(r < BenchStep) // Overflow check - { - r = ulong.MaxValue; - break; - } - - uint tElapsed = (uint)(Environment.TickCount - tStart); - if(tElapsed > uMaxMs) break; - } - - uRounds = r / uDiv; - return true; - } - catch(Exception) { Debug.Assert(false); } - finally { GCryptEnd(h, pData32, pSeed32); } - - return false; - } - } -} diff --git a/ModernKeePassLib/Cryptography/KeyDerivation/AesKdf.cs b/ModernKeePassLib/Cryptography/KeyDerivation/AesKdf.cs deleted file mode 100644 index 16a4a3c..0000000 --- a/ModernKeePassLib/Cryptography/KeyDerivation/AesKdf.cs +++ /dev/null @@ -1,281 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Text; - -#if ModernKeePassLib || KeePassUAP -using Org.BouncyCastle.Crypto; -using Org.BouncyCastle.Crypto.Engines; -using Org.BouncyCastle.Crypto.Parameters; -#else -using System.Security.Cryptography; -#endif - -using ModernKeePassLib.Cryptography; -using ModernKeePassLib.Native; -using ModernKeePassLib.Utility; - -namespace ModernKeePassLib.Cryptography.KeyDerivation -{ - public sealed partial class AesKdf : KdfEngine - { - private static readonly PwUuid g_uuid = new PwUuid(new byte[] { - 0xC9, 0xD9, 0xF3, 0x9A, 0x62, 0x8A, 0x44, 0x60, - 0xBF, 0x74, 0x0D, 0x08, 0xC1, 0x8A, 0x4F, 0xEA }); - - public const string ParamRounds = "R"; // UInt64 - public const string ParamSeed = "S"; // Byte[32] - - private const ulong BenchStep = 3001; - - public override PwUuid Uuid - { - get { return g_uuid; } - } - - public override string Name - { - get { return "AES-KDF"; } - } - - public AesKdf() - { - } - - public override KdfParameters GetDefaultParameters() - { - KdfParameters p = base.GetDefaultParameters(); - p.SetUInt64(ParamRounds, PwDefs.DefaultKeyEncryptionRounds); - return p; - } - - public override void Randomize(KdfParameters p) - { - if(p == null) { Debug.Assert(false); return; } - Debug.Assert(g_uuid.Equals(p.KdfUuid)); - - byte[] pbSeed = CryptoRandom.Instance.GetRandomBytes(32); - p.SetByteArray(ParamSeed, pbSeed); - } - - public override byte[] Transform(byte[] pbMsg, KdfParameters p) - { - if(pbMsg == null) throw new ArgumentNullException("pbMsg"); - if(p == null) throw new ArgumentNullException("p"); - - Type tRounds = p.GetTypeOf(ParamRounds); - if(tRounds == null) throw new ArgumentNullException("p.Rounds"); - if(tRounds != typeof(ulong)) throw new ArgumentOutOfRangeException("p.Rounds"); - ulong uRounds = p.GetUInt64(ParamRounds, 0); - - byte[] pbSeed = p.GetByteArray(ParamSeed); - if(pbSeed == null) throw new ArgumentNullException("p.Seed"); - - if(pbMsg.Length != 32) - { - Debug.Assert(false); - pbMsg = CryptoUtil.HashSha256(pbMsg); - } - - if(pbSeed.Length != 32) - { - Debug.Assert(false); - pbSeed = CryptoUtil.HashSha256(pbSeed); - } - - return TransformKey(pbMsg, pbSeed, uRounds); - } - - private static byte[] TransformKey(byte[] pbOriginalKey32, byte[] pbKeySeed32, - ulong uNumRounds) - { - Debug.Assert((pbOriginalKey32 != null) && (pbOriginalKey32.Length == 32)); - if(pbOriginalKey32 == null) throw new ArgumentNullException("pbOriginalKey32"); - if(pbOriginalKey32.Length != 32) throw new ArgumentException(); - - Debug.Assert((pbKeySeed32 != null) && (pbKeySeed32.Length == 32)); - if(pbKeySeed32 == null) throw new ArgumentNullException("pbKeySeed32"); - if(pbKeySeed32.Length != 32) throw new ArgumentException(); - - byte[] pbNewKey = new byte[32]; - Array.Copy(pbOriginalKey32, pbNewKey, pbNewKey.Length); - - try - { -#if !ModernKeePassLib - // Try to use the native library first - if(NativeLib.TransformKey256(pbNewKey, pbKeySeed32, uNumRounds)) - return CryptoUtil.HashSha256(pbNewKey); -#endif - - if(TransformKeyGCrypt(pbNewKey, pbKeySeed32, uNumRounds)) - return CryptoUtil.HashSha256(pbNewKey); - - if(TransformKeyManaged(pbNewKey, pbKeySeed32, uNumRounds)) - return CryptoUtil.HashSha256(pbNewKey); - } - finally { MemUtil.ZeroByteArray(pbNewKey); } - - return null; - } - - public static bool TransformKeyManaged(byte[] pbNewKey32, byte[] pbKeySeed32, - ulong uNumRounds) - { -#if ModernKeePassLib || KeePassUAP - KeyParameter kp = new KeyParameter(pbKeySeed32); - AesEngine aes = new AesEngine(); - aes.Init(true, kp); - - for(ulong i = 0; i < uNumRounds; ++i) - { - aes.ProcessBlock(pbNewKey32, 0, pbNewKey32, 0); - aes.ProcessBlock(pbNewKey32, 16, pbNewKey32, 16); - } -#else - byte[] pbIV = new byte[16]; - Array.Clear(pbIV, 0, pbIV.Length); - - SymmetricAlgorithm a = CryptoUtil.CreateAes(); - if(a.BlockSize != 128) // AES block size - { - Debug.Assert(false); - a.BlockSize = 128; - } - - a.IV = pbIV; - a.Mode = CipherMode.ECB; - a.KeySize = 256; - a.Key = pbKeySeed32; - ICryptoTransform iCrypt = a.CreateEncryptor(); - - // !iCrypt.CanReuseTransform -- doesn't work with Mono - if((iCrypt == null) || (iCrypt.InputBlockSize != 16) || - (iCrypt.OutputBlockSize != 16)) - { - Debug.Assert(false, "Invalid ICryptoTransform."); - Debug.Assert((iCrypt.InputBlockSize == 16), "Invalid input block size!"); - Debug.Assert((iCrypt.OutputBlockSize == 16), "Invalid output block size!"); - return false; - } - - for(ulong i = 0; i < uNumRounds; ++i) - { - iCrypt.TransformBlock(pbNewKey32, 0, 16, pbNewKey32, 0); - iCrypt.TransformBlock(pbNewKey32, 16, 16, pbNewKey32, 16); - } -#endif - - return true; - } - - public override KdfParameters GetBestParameters(uint uMilliseconds) - { - KdfParameters p = GetDefaultParameters(); - ulong uRounds; -#if !ModernKeePassLib - // Try native method - if(NativeLib.TransformKeyBenchmark256(uMilliseconds, out uRounds)) - { - p.SetUInt64(ParamRounds, uRounds); - return p; - } -#endif - if(TransformKeyBenchmarkGCrypt(uMilliseconds, out uRounds)) - { - p.SetUInt64(ParamRounds, uRounds); - return p; - } - - byte[] pbKey = new byte[32]; - byte[] pbNewKey = new byte[32]; - for(int i = 0; i < pbKey.Length; ++i) - { - pbKey[i] = (byte)i; - pbNewKey[i] = (byte)i; - } - -#if ModernKeePassLib || KeePassUAP - KeyParameter kp = new KeyParameter(pbKey); - AesEngine aes = new AesEngine(); - aes.Init(true, kp); -#else - byte[] pbIV = new byte[16]; - Array.Clear(pbIV, 0, pbIV.Length); - - SymmetricAlgorithm a = CryptoUtil.CreateAes(); - if(a.BlockSize != 128) // AES block size - { - Debug.Assert(false); - a.BlockSize = 128; - } - - a.IV = pbIV; - a.Mode = CipherMode.ECB; - a.KeySize = 256; - a.Key = pbKey; - ICryptoTransform iCrypt = a.CreateEncryptor(); - - // !iCrypt.CanReuseTransform -- doesn't work with Mono - if((iCrypt == null) || (iCrypt.InputBlockSize != 16) || - (iCrypt.OutputBlockSize != 16)) - { - Debug.Assert(false, "Invalid ICryptoTransform."); - Debug.Assert(iCrypt.InputBlockSize == 16, "Invalid input block size!"); - Debug.Assert(iCrypt.OutputBlockSize == 16, "Invalid output block size!"); - - p.SetUInt64(ParamRounds, PwDefs.DefaultKeyEncryptionRounds); - return p; - } -#endif - - uRounds = 0; - int tStart = Environment.TickCount; - while(true) - { - for(ulong j = 0; j < BenchStep; ++j) - { -#if ModernKeePassLib || KeePassUAP - aes.ProcessBlock(pbNewKey, 0, pbNewKey, 0); - aes.ProcessBlock(pbNewKey, 16, pbNewKey, 16); -#else - iCrypt.TransformBlock(pbNewKey, 0, 16, pbNewKey, 0); - iCrypt.TransformBlock(pbNewKey, 16, 16, pbNewKey, 16); -#endif - } - - uRounds += BenchStep; - if(uRounds < BenchStep) // Overflow check - { - uRounds = ulong.MaxValue; - break; - } - - uint tElapsed = (uint)(Environment.TickCount - tStart); - if(tElapsed > uMilliseconds) break; - } - - p.SetUInt64(ParamRounds, uRounds); - return p; - } - } -} diff --git a/ModernKeePassLib/Cryptography/KeyDerivation/Argon2Kdf.Core.cs b/ModernKeePassLib/Cryptography/KeyDerivation/Argon2Kdf.Core.cs deleted file mode 100644 index c706c6e..0000000 --- a/ModernKeePassLib/Cryptography/KeyDerivation/Argon2Kdf.Core.cs +++ /dev/null @@ -1,637 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -// This implementation is based on the official reference C -// implementation by Daniel Dinu and Dmitry Khovratovich (CC0 1.0). - -// Relative iterations (* = B2ROUND_ARRAYS \\ G_INLINED): -// * | false true -// ------+----------- -// false | 8885 9618 -// true | 9009 9636 -#define ARGON2_B2ROUND_ARRAYS -#define ARGON2_G_INLINED - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Runtime.InteropServices; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using ModernKeePassLib.Cryptography.Hash; -using ModernKeePassLib.Utility; - -namespace ModernKeePassLib.Cryptography.KeyDerivation -{ - public sealed partial class Argon2Kdf : KdfEngine - { - private const ulong NbBlockSize = 1024; - private const ulong NbBlockSizeInQW = NbBlockSize / 8UL; - private const ulong NbSyncPoints = 4; - - private const int NbPreHashDigestLength = 64; - private const int NbPreHashSeedLength = NbPreHashDigestLength + 8; - -#if ARGON2_B2ROUND_ARRAYS - private static int[][] g_vFBCols = null; - private static int[][] g_vFBRows = null; -#endif - - private sealed class Argon2Ctx - { - public uint Version = 0; - - public ulong Lanes = 0; - public ulong TCost = 0; - public ulong MCost = 0; - public ulong MemoryBlocks = 0; - public ulong SegmentLength = 0; - public ulong LaneLength = 0; - - public ulong[] Mem = null; - } - - private sealed class Argon2ThreadInfo - { - public Argon2Ctx Context = null; - public ManualResetEvent Finished = new ManualResetEvent(false); - - public ulong Pass = 0; - public ulong Lane = 0; - public ulong Slice = 0; - public ulong Index = 0; - - public void Release() - { - if(this.Finished != null) - { - this.Finished.Dispose(); - this.Finished = null; - } - else { Debug.Assert(false); } - } - } - - private static byte[] Argon2d(byte[] pbMsg, byte[] pbSalt, uint uParallel, - ulong uMem, ulong uIt, int cbOut, uint uVersion, byte[] pbSecretKey, - byte[] pbAssocData) - { - pbSecretKey = (pbSecretKey ?? MemUtil.EmptyByteArray); - pbAssocData = (pbAssocData ?? MemUtil.EmptyByteArray); - -#if ARGON2_B2ROUND_ARRAYS - InitB2RoundIndexArrays(); -#endif - - Argon2Ctx ctx = new Argon2Ctx(); - ctx.Version = uVersion; - - ctx.Lanes = uParallel; - ctx.TCost = uIt; - ctx.MCost = uMem / NbBlockSize; - ctx.MemoryBlocks = Math.Max(ctx.MCost, 2UL * NbSyncPoints * ctx.Lanes); - - ctx.SegmentLength = ctx.MemoryBlocks / (ctx.Lanes * NbSyncPoints); - ctx.MemoryBlocks = ctx.SegmentLength * ctx.Lanes * NbSyncPoints; - - ctx.LaneLength = ctx.SegmentLength * NbSyncPoints; - - Debug.Assert(NbBlockSize == (NbBlockSizeInQW * -#if ModernKeePassLib || KeePassUAP - (ulong)Marshal.SizeOf() -#else - (ulong)Marshal.SizeOf(typeof(ulong)) -#endif - )); - ctx.Mem = new ulong[ctx.MemoryBlocks * NbBlockSizeInQW]; - - Blake2b h = new Blake2b(); - - // Initial hash - Debug.Assert(h.HashSize == (NbPreHashDigestLength * 8)); - byte[] pbBuf = new byte[4]; - MemUtil.UInt32ToBytesEx(uParallel, pbBuf, 0); - h.TransformBlock(pbBuf, 0, pbBuf.Length, pbBuf, 0); - MemUtil.UInt32ToBytesEx((uint)cbOut, pbBuf, 0); - h.TransformBlock(pbBuf, 0, pbBuf.Length, pbBuf, 0); - MemUtil.UInt32ToBytesEx((uint)ctx.MCost, pbBuf, 0); - h.TransformBlock(pbBuf, 0, pbBuf.Length, pbBuf, 0); - MemUtil.UInt32ToBytesEx((uint)uIt, pbBuf, 0); - h.TransformBlock(pbBuf, 0, pbBuf.Length, pbBuf, 0); - MemUtil.UInt32ToBytesEx(uVersion, pbBuf, 0); - h.TransformBlock(pbBuf, 0, pbBuf.Length, pbBuf, 0); - MemUtil.UInt32ToBytesEx(0, pbBuf, 0); // Argon2d type = 0 - h.TransformBlock(pbBuf, 0, pbBuf.Length, pbBuf, 0); - MemUtil.UInt32ToBytesEx((uint)pbMsg.Length, pbBuf, 0); - h.TransformBlock(pbBuf, 0, pbBuf.Length, pbBuf, 0); - h.TransformBlock(pbMsg, 0, pbMsg.Length, pbMsg, 0); - MemUtil.UInt32ToBytesEx((uint)pbSalt.Length, pbBuf, 0); - h.TransformBlock(pbBuf, 0, pbBuf.Length, pbBuf, 0); - h.TransformBlock(pbSalt, 0, pbSalt.Length, pbSalt, 0); - MemUtil.UInt32ToBytesEx((uint)pbSecretKey.Length, pbBuf, 0); - h.TransformBlock(pbBuf, 0, pbBuf.Length, pbBuf, 0); - h.TransformBlock(pbSecretKey, 0, pbSecretKey.Length, pbSecretKey, 0); - MemUtil.UInt32ToBytesEx((uint)pbAssocData.Length, pbBuf, 0); - h.TransformBlock(pbBuf, 0, pbBuf.Length, pbBuf, 0); - h.TransformBlock(pbAssocData, 0, pbAssocData.Length, pbAssocData, 0); - h.TransformFinalBlock(MemUtil.EmptyByteArray, 0, 0); - byte[] pbH0 = h.Hash; - Debug.Assert(pbH0.Length == 64); - - byte[] pbBlockHash = new byte[NbPreHashSeedLength]; - Array.Copy(pbH0, pbBlockHash, pbH0.Length); - MemUtil.ZeroByteArray(pbH0); - - FillFirstBlocks(ctx, pbBlockHash, h); - MemUtil.ZeroByteArray(pbBlockHash); - - FillMemoryBlocks(ctx); - - byte[] pbOut = FinalHash(ctx, cbOut, h); - - h.Clear(); - MemUtil.ZeroArray(ctx.Mem); - return pbOut; - } - - private static void LoadBlock(ulong[] pqDst, ulong uDstOffset, byte[] pbIn) - { - // for(ulong i = 0; i < NbBlockSizeInQW; ++i) - // pqDst[uDstOffset + i] = MemUtil.BytesToUInt64(pbIn, (int)(i << 3)); - - Debug.Assert((uDstOffset + NbBlockSizeInQW - 1UL) <= (ulong)int.MaxValue); - int iDstOffset = (int)uDstOffset; - for(int i = 0; i < (int)NbBlockSizeInQW; ++i) - pqDst[iDstOffset + i] = MemUtil.BytesToUInt64(pbIn, i << 3); - } - - private static void StoreBlock(byte[] pbDst, ulong[] pqSrc) - { - for(int i = 0; i < (int)NbBlockSizeInQW; ++i) - MemUtil.UInt64ToBytesEx(pqSrc[i], pbDst, i << 3); - } - - private static void CopyBlock(ulong[] vDst, ulong uDstOffset, ulong[] vSrc, - ulong uSrcOffset) - { - // for(ulong i = 0; i < NbBlockSizeInQW; ++i) - // vDst[uDstOffset + i] = vSrc[uSrcOffset + i]; - - // Debug.Assert((uDstOffset + NbBlockSizeInQW - 1UL) <= (ulong)int.MaxValue); - // Debug.Assert((uSrcOffset + NbBlockSizeInQW - 1UL) <= (ulong)int.MaxValue); - // int iDstOffset = (int)uDstOffset; - // int iSrcOffset = (int)uSrcOffset; - // for(int i = 0; i < (int)NbBlockSizeInQW; ++i) - // vDst[iDstOffset + i] = vSrc[iSrcOffset + i]; - -#if ModernKeePassLib || KeePassUAP - Array.Copy(vSrc, (int)uSrcOffset, vDst, (int)uDstOffset, - (int)NbBlockSizeInQW); -#else - Array.Copy(vSrc, (long)uSrcOffset, vDst, (long)uDstOffset, - (long)NbBlockSizeInQW); -#endif - } - - private static void XorBlock(ulong[] vDst, ulong uDstOffset, ulong[] vSrc, - ulong uSrcOffset) - { - // for(ulong i = 0; i < NbBlockSizeInQW; ++i) - // vDst[uDstOffset + i] ^= vSrc[uSrcOffset + i]; - - Debug.Assert((uDstOffset + NbBlockSizeInQW - 1UL) <= (ulong)int.MaxValue); - Debug.Assert((uSrcOffset + NbBlockSizeInQW - 1UL) <= (ulong)int.MaxValue); - int iDstOffset = (int)uDstOffset; - int iSrcOffset = (int)uSrcOffset; - for(int i = 0; i < (int)NbBlockSizeInQW; ++i) - vDst[iDstOffset + i] ^= vSrc[iSrcOffset + i]; - } - - private static void Blake2bLong(byte[] pbOut, int cbOut, - byte[] pbIn, int cbIn, Blake2b h) - { - Debug.Assert((h != null) && (h.HashSize == (64 * 8))); - - byte[] pbOutLen = new byte[4]; - MemUtil.UInt32ToBytesEx((uint)cbOut, pbOutLen, 0); - - if(cbOut <= 64) - { - Blake2b hOut = ((cbOut == 64) ? h : new Blake2b(cbOut)); - if(cbOut == 64) hOut.Initialize(); - - hOut.TransformBlock(pbOutLen, 0, pbOutLen.Length, pbOutLen, 0); - hOut.TransformBlock(pbIn, 0, cbIn, pbIn, 0); - hOut.TransformFinalBlock(MemUtil.EmptyByteArray, 0, 0); - - Array.Copy(hOut.Hash, pbOut, cbOut); - - if(cbOut < 64) hOut.Clear(); - return; - } - - h.Initialize(); - h.TransformBlock(pbOutLen, 0, pbOutLen.Length, pbOutLen, 0); - h.TransformBlock(pbIn, 0, cbIn, pbIn, 0); - h.TransformFinalBlock(MemUtil.EmptyByteArray, 0, 0); - - byte[] pbOutBuffer = new byte[64]; - Array.Copy(h.Hash, pbOutBuffer, pbOutBuffer.Length); - - int ibOut = 64 / 2; - Array.Copy(pbOutBuffer, pbOut, ibOut); - int cbToProduce = cbOut - ibOut; - - h.Initialize(); - while(cbToProduce > 64) - { - byte[] pbHash = h.ComputeHash(pbOutBuffer); - Array.Copy(pbHash, pbOutBuffer, 64); - - Array.Copy(pbHash, 0, pbOut, ibOut, 64 / 2); - ibOut += 64 / 2; - cbToProduce -= 64 / 2; - - MemUtil.ZeroByteArray(pbHash); - } - - using(Blake2b hOut = new Blake2b(cbToProduce)) - { - byte[] pbHash = hOut.ComputeHash(pbOutBuffer); - Array.Copy(pbHash, 0, pbOut, ibOut, cbToProduce); - - MemUtil.ZeroByteArray(pbHash); - } - - MemUtil.ZeroByteArray(pbOutBuffer); - } - -#if !ARGON2_G_INLINED - private static ulong BlaMka(ulong x, ulong y) - { - ulong xy = (x & 0xFFFFFFFFUL) * (y & 0xFFFFFFFFUL); - return (x + y + (xy << 1)); - } - - private static void G(ulong[] v, int a, int b, int c, int d) - { - ulong va = v[a], vb = v[b], vc = v[c], vd = v[d]; - - va = BlaMka(va, vb); - vd = MemUtil.RotateRight64(vd ^ va, 32); - vc = BlaMka(vc, vd); - vb = MemUtil.RotateRight64(vb ^ vc, 24); - va = BlaMka(va, vb); - vd = MemUtil.RotateRight64(vd ^ va, 16); - vc = BlaMka(vc, vd); - vb = MemUtil.RotateRight64(vb ^ vc, 63); - - v[a] = va; - v[b] = vb; - v[c] = vc; - v[d] = vd; - } -#else - private static void G(ulong[] v, int a, int b, int c, int d) - { - ulong va = v[a], vb = v[b], vc = v[c], vd = v[d]; - - ulong xy = (va & 0xFFFFFFFFUL) * (vb & 0xFFFFFFFFUL); - va += vb + (xy << 1); - - vd = MemUtil.RotateRight64(vd ^ va, 32); - - xy = (vc & 0xFFFFFFFFUL) * (vd & 0xFFFFFFFFUL); - vc += vd + (xy << 1); - - vb = MemUtil.RotateRight64(vb ^ vc, 24); - - xy = (va & 0xFFFFFFFFUL) * (vb & 0xFFFFFFFFUL); - va += vb + (xy << 1); - - vd = MemUtil.RotateRight64(vd ^ va, 16); - - xy = (vc & 0xFFFFFFFFUL) * (vd & 0xFFFFFFFFUL); - vc += vd + (xy << 1); - - vb = MemUtil.RotateRight64(vb ^ vc, 63); - - v[a] = va; - v[b] = vb; - v[c] = vc; - v[d] = vd; - } -#endif - -#if ARGON2_B2ROUND_ARRAYS - private static void Blake2RoundNoMsg(ulong[] pbR, int[] v) - { - G(pbR, v[0], v[4], v[8], v[12]); - G(pbR, v[1], v[5], v[9], v[13]); - G(pbR, v[2], v[6], v[10], v[14]); - G(pbR, v[3], v[7], v[11], v[15]); - G(pbR, v[0], v[5], v[10], v[15]); - G(pbR, v[1], v[6], v[11], v[12]); - G(pbR, v[2], v[7], v[8], v[13]); - G(pbR, v[3], v[4], v[9], v[14]); - } -#else - private static void Blake2RoundNoMsgCols16i(ulong[] pbR, int i) - { - G(pbR, i, i + 4, i + 8, i + 12); - G(pbR, i + 1, i + 5, i + 9, i + 13); - G(pbR, i + 2, i + 6, i + 10, i + 14); - G(pbR, i + 3, i + 7, i + 11, i + 15); - G(pbR, i, i + 5, i + 10, i + 15); - G(pbR, i + 1, i + 6, i + 11, i + 12); - G(pbR, i + 2, i + 7, i + 8, i + 13); - G(pbR, i + 3, i + 4, i + 9, i + 14); - } - - private static void Blake2RoundNoMsgRows2i(ulong[] pbR, int i) - { - G(pbR, i, i + 32, i + 64, i + 96); - G(pbR, i + 1, i + 33, i + 65, i + 97); - G(pbR, i + 16, i + 48, i + 80, i + 112); - G(pbR, i + 17, i + 49, i + 81, i + 113); - G(pbR, i, i + 33, i + 80, i + 113); - G(pbR, i + 1, i + 48, i + 81, i + 96); - G(pbR, i + 16, i + 49, i + 64, i + 97); - G(pbR, i + 17, i + 32, i + 65, i + 112); - } -#endif - - private static void FillFirstBlocks(Argon2Ctx ctx, byte[] pbBlockHash, - Blake2b h) - { - byte[] pbBlock = new byte[NbBlockSize]; - - for(ulong l = 0; l < ctx.Lanes; ++l) - { - MemUtil.UInt32ToBytesEx(0, pbBlockHash, NbPreHashDigestLength); - MemUtil.UInt32ToBytesEx((uint)l, pbBlockHash, NbPreHashDigestLength + 4); - - Blake2bLong(pbBlock, (int)NbBlockSize, pbBlockHash, - NbPreHashSeedLength, h); - LoadBlock(ctx.Mem, l * ctx.LaneLength * NbBlockSizeInQW, pbBlock); - - MemUtil.UInt32ToBytesEx(1, pbBlockHash, NbPreHashDigestLength); - - Blake2bLong(pbBlock, (int)NbBlockSize, pbBlockHash, - NbPreHashSeedLength, h); - LoadBlock(ctx.Mem, (l * ctx.LaneLength + 1UL) * NbBlockSizeInQW, pbBlock); - } - - MemUtil.ZeroByteArray(pbBlock); - } - - private static ulong IndexAlpha(Argon2Ctx ctx, Argon2ThreadInfo ti, - uint uPseudoRand, bool bSameLane) - { - ulong uRefAreaSize; - if(ti.Pass == 0) - { - if(ti.Slice == 0) - { - Debug.Assert(ti.Index > 0); - uRefAreaSize = ti.Index - 1UL; - } - else - { - if(bSameLane) - uRefAreaSize = ti.Slice * ctx.SegmentLength + - ti.Index - 1UL; - else - uRefAreaSize = ti.Slice * ctx.SegmentLength - - ((ti.Index == 0UL) ? 1UL : 0UL); - } - } - else - { - if(bSameLane) - uRefAreaSize = ctx.LaneLength - ctx.SegmentLength + - ti.Index - 1UL; - else - uRefAreaSize = ctx.LaneLength - ctx.SegmentLength - - ((ti.Index == 0) ? 1UL : 0UL); - } - Debug.Assert(uRefAreaSize <= (ulong)uint.MaxValue); - - ulong uRelPos = uPseudoRand; - uRelPos = (uRelPos * uRelPos) >> 32; - uRelPos = uRefAreaSize - 1UL - ((uRefAreaSize * uRelPos) >> 32); - - ulong uStart = 0; - if(ti.Pass != 0) - uStart = (((ti.Slice + 1UL) == NbSyncPoints) ? 0UL : - ((ti.Slice + 1UL) * ctx.SegmentLength)); - Debug.Assert(uStart <= (ulong)uint.MaxValue); - - Debug.Assert(ctx.LaneLength <= (ulong)uint.MaxValue); - return ((uStart + uRelPos) % ctx.LaneLength); - } - - private static void FillMemoryBlocks(Argon2Ctx ctx) - { - int np = (int)ctx.Lanes; - Argon2ThreadInfo[] v = new Argon2ThreadInfo[np]; - - for(ulong r = 0; r < ctx.TCost; ++r) - { - for(ulong s = 0; s < NbSyncPoints; ++s) - { - for(int l = 0; l < np; ++l) - { - Argon2ThreadInfo ti = new Argon2ThreadInfo(); - ti.Context = ctx; - - ti.Pass = r; - ti.Lane = (ulong)l; - ti.Slice = s; - -#if ModernKeePassLib - Task.Factory.StartNew(FillSegmentThr, ti); - //ThreadPool.RunAsync(a => FillSegmentThr(ti)); -#else - if(!ThreadPool.QueueUserWorkItem(FillSegmentThr, ti)) - { - Debug.Assert(false); - throw new OutOfMemoryException(); - } -#endif - v[l] = ti; - } - - for(int l = 0; l < np; ++l) - { - v[l].Finished.WaitOne(); - v[l].Release(); - } - } - } - } - - private static void FillSegmentThr(object o) - { - Argon2ThreadInfo ti = (o as Argon2ThreadInfo); - if(ti == null) { Debug.Assert(false); return; } - - try - { - Argon2Ctx ctx = ti.Context; - if(ctx == null) { Debug.Assert(false); return; } - - Debug.Assert(ctx.Version >= MinVersion); - bool bCanXor = (ctx.Version >= 0x13U); - - ulong uStart = 0; - if((ti.Pass == 0) && (ti.Slice == 0)) uStart = 2; - - ulong uCur = (ti.Lane * ctx.LaneLength) + (ti.Slice * - ctx.SegmentLength) + uStart; - - ulong uPrev = (((uCur % ctx.LaneLength) == 0) ? - (uCur + ctx.LaneLength - 1UL) : (uCur - 1UL)); - - ulong[] pbR = new ulong[NbBlockSizeInQW]; - ulong[] pbTmp = new ulong[NbBlockSizeInQW]; - - for(ulong i = uStart; i < ctx.SegmentLength; ++i) - { - if((uCur % ctx.LaneLength) == 1) - uPrev = uCur - 1UL; - - ulong uPseudoRand = ctx.Mem[uPrev * NbBlockSizeInQW]; - ulong uRefLane = (uPseudoRand >> 32) % ctx.Lanes; - if((ti.Pass == 0) && (ti.Slice == 0)) - uRefLane = ti.Lane; - - ti.Index = i; - ulong uRefIndex = IndexAlpha(ctx, ti, (uint)uPseudoRand, - (uRefLane == ti.Lane)); - - ulong uRefBlockIndex = (ctx.LaneLength * uRefLane + - uRefIndex) * NbBlockSizeInQW; - ulong uCurBlockIndex = uCur * NbBlockSizeInQW; - - FillBlock(ctx.Mem, uPrev * NbBlockSizeInQW, uRefBlockIndex, - uCurBlockIndex, ((ti.Pass != 0) && bCanXor), pbR, pbTmp); - - ++uCur; - ++uPrev; - } - - MemUtil.ZeroArray(pbR); - MemUtil.ZeroArray(pbTmp); - } - catch(Exception) { Debug.Assert(false); } - - try { ti.Finished.Set(); } - catch(Exception) { Debug.Assert(false); } - } - -#if ARGON2_B2ROUND_ARRAYS - private static void InitB2RoundIndexArrays() - { - int[][] vCols = g_vFBCols; - if(vCols == null) - { - vCols = new int[8][]; - Debug.Assert(vCols.Length == 8); - int e = 0; - for(int i = 0; i < 8; ++i) - { - vCols[i] = new int[16]; - for(int j = 0; j < 16; ++j) - { - vCols[i][j] = e; - ++e; - } - } - - g_vFBCols = vCols; - } - - int[][] vRows = g_vFBRows; - if(vRows == null) - { - vRows = new int[8][]; - for(int i = 0; i < 8; ++i) - { - vRows[i] = new int[16]; - for(int j = 0; j < 16; ++j) - { - int jh = j / 2; - vRows[i][j] = (2 * i) + (16 * jh) + (j & 1); - } - } - - g_vFBRows = vRows; - } - } -#endif - - private static void FillBlock(ulong[] pMem, ulong uPrev, ulong uRef, - ulong uNext, bool bXor, ulong[] pbR, ulong[] pbTmp) - { - CopyBlock(pbR, 0, pMem, uRef); - XorBlock(pbR, 0, pMem, uPrev); - CopyBlock(pbTmp, 0, pbR, 0); - if(bXor) XorBlock(pbTmp, 0, pMem, uNext); - -#if ARGON2_B2ROUND_ARRAYS - int[][] vCols = g_vFBCols; - int[][] vRows = g_vFBRows; - for(int i = 0; i < 8; ++i) - Blake2RoundNoMsg(pbR, vCols[i]); - for(int i = 0; i < 8; ++i) - Blake2RoundNoMsg(pbR, vRows[i]); -#else - for(int i = 0; i < (8 * 16); i += 16) - Blake2RoundNoMsgCols16i(pbR, i); - for(int i = 0; i < (8 * 2); i += 2) - Blake2RoundNoMsgRows2i(pbR, i); -#endif - - CopyBlock(pMem, uNext, pbTmp, 0); - XorBlock(pMem, uNext, pbR, 0); - } - - private static byte[] FinalHash(Argon2Ctx ctx, int cbOut, Blake2b h) - { - ulong[] pqBlockHash = new ulong[NbBlockSizeInQW]; - CopyBlock(pqBlockHash, 0, ctx.Mem, (ctx.LaneLength - 1UL) * - NbBlockSizeInQW); - for(ulong l = 1; l < ctx.Lanes; ++l) - XorBlock(pqBlockHash, 0, ctx.Mem, (l * ctx.LaneLength + - ctx.LaneLength - 1UL) * NbBlockSizeInQW); - - byte[] pbBlockHashBytes = new byte[NbBlockSize]; - StoreBlock(pbBlockHashBytes, pqBlockHash); - - byte[] pbOut = new byte[cbOut]; - Blake2bLong(pbOut, cbOut, pbBlockHashBytes, (int)NbBlockSize, h); - - MemUtil.ZeroArray(pqBlockHash); - MemUtil.ZeroByteArray(pbBlockHashBytes); - return pbOut; - } - } -} diff --git a/ModernKeePassLib/Cryptography/KeyDerivation/Argon2Kdf.cs b/ModernKeePassLib/Cryptography/KeyDerivation/Argon2Kdf.cs deleted file mode 100644 index a65b0e2..0000000 --- a/ModernKeePassLib/Cryptography/KeyDerivation/Argon2Kdf.cs +++ /dev/null @@ -1,144 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Text; - -namespace ModernKeePassLib.Cryptography.KeyDerivation -{ - public sealed partial class Argon2Kdf : KdfEngine - { - private static readonly PwUuid g_uuid = new PwUuid(new byte[] { - 0xEF, 0x63, 0x6D, 0xDF, 0x8C, 0x29, 0x44, 0x4B, - 0x91, 0xF7, 0xA9, 0xA4, 0x03, 0xE3, 0x0A, 0x0C }); - - public const string ParamSalt = "S"; // Byte[] - public const string ParamParallelism = "P"; // UInt32 - public const string ParamMemory = "M"; // UInt64 - public const string ParamIterations = "I"; // UInt64 - public const string ParamVersion = "V"; // UInt32 - public const string ParamSecretKey = "K"; // Byte[] - public const string ParamAssocData = "A"; // Byte[] - - private const uint MinVersion = 0x10; - private const uint MaxVersion = 0x13; - - private const int MinSalt = 8; - private const int MaxSalt = int.MaxValue; // .NET limit; 2^32 - 1 in spec - - internal const ulong MinIterations = 1; - internal const ulong MaxIterations = uint.MaxValue; - - internal const ulong MinMemory = 1024 * 8; // For parallelism = 1 - // internal const ulong MaxMemory = (ulong)uint.MaxValue * 1024UL; // Spec - internal const ulong MaxMemory = int.MaxValue; // .NET limit - - internal const uint MinParallelism = 1; - internal const uint MaxParallelism = (1 << 24) - 1; - - internal const ulong DefaultIterations = 2; - internal const ulong DefaultMemory = 1024 * 1024; // 1 MB - internal const uint DefaultParallelism = 2; - - public override PwUuid Uuid - { - get { return g_uuid; } - } - - public override string Name - { - get { return "Argon2"; } - } - - public Argon2Kdf() - { - } - - public override KdfParameters GetDefaultParameters() - { - KdfParameters p = base.GetDefaultParameters(); - - p.SetUInt32(ParamVersion, MaxVersion); - - p.SetUInt64(ParamIterations, DefaultIterations); - p.SetUInt64(ParamMemory, DefaultMemory); - p.SetUInt32(ParamParallelism, DefaultParallelism); - - return p; - } - - public override void Randomize(KdfParameters p) - { - if(p == null) { Debug.Assert(false); return; } - Debug.Assert(g_uuid.Equals(p.KdfUuid)); - - byte[] pb = CryptoRandom.Instance.GetRandomBytes(32); - p.SetByteArray(ParamSalt, pb); - } - - public override byte[] Transform(byte[] pbMsg, KdfParameters p) - { - if(pbMsg == null) throw new ArgumentNullException("pbMsg"); - if(p == null) throw new ArgumentNullException("p"); - - byte[] pbSalt = p.GetByteArray(ParamSalt); - if(pbSalt == null) - throw new ArgumentNullException("p.Salt"); - if((pbSalt.Length < MinSalt) || (pbSalt.Length > MaxSalt)) - throw new ArgumentOutOfRangeException("p.Salt"); - - uint uPar = p.GetUInt32(ParamParallelism, 0); - if((uPar < MinParallelism) || (uPar > MaxParallelism)) - throw new ArgumentOutOfRangeException("p.Parallelism"); - - ulong uMem = p.GetUInt64(ParamMemory, 0); - if((uMem < MinMemory) || (uMem > MaxMemory)) - throw new ArgumentOutOfRangeException("p.Memory"); - - ulong uIt = p.GetUInt64(ParamIterations, 0); - if((uIt < MinIterations) || (uIt > MaxIterations)) - throw new ArgumentOutOfRangeException("p.Iterations"); - - uint v = p.GetUInt32(ParamVersion, 0); - if((v < MinVersion) || (v > MaxVersion)) - throw new ArgumentOutOfRangeException("p.Version"); - - byte[] pbSecretKey = p.GetByteArray(ParamSecretKey); - byte[] pbAssocData = p.GetByteArray(ParamAssocData); - - byte[] pbRet = Argon2d(pbMsg, pbSalt, uPar, uMem, uIt, - 32, v, pbSecretKey, pbAssocData); - - if(uMem > (100UL * 1024UL * 1024UL)) GC.Collect(); - return pbRet; - } - - public override KdfParameters GetBestParameters(uint uMilliseconds) - { - KdfParameters p = GetDefaultParameters(); - Randomize(p); - - MaximizeParamUInt64(p, ParamIterations, MinIterations, - MaxIterations, uMilliseconds, true); - return p; - } - } -} diff --git a/ModernKeePassLib/Cryptography/KeyDerivation/KdfEngine.cs b/ModernKeePassLib/Cryptography/KeyDerivation/KdfEngine.cs deleted file mode 100644 index dc108ea..0000000 --- a/ModernKeePassLib/Cryptography/KeyDerivation/KdfEngine.cs +++ /dev/null @@ -1,142 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Text; - -namespace ModernKeePassLib.Cryptography.KeyDerivation -{ - public abstract class KdfEngine - { - public abstract PwUuid Uuid - { - get; - } - - public abstract string Name - { - get; - } - - public virtual KdfParameters GetDefaultParameters() - { - return new KdfParameters(this.Uuid); - } - - /// - /// Generate random seeds and store them in . - /// - public virtual void Randomize(KdfParameters p) - { - Debug.Assert(p != null); - Debug.Assert(p.KdfUuid.Equals(this.Uuid)); - } - - public abstract byte[] Transform(byte[] pbMsg, KdfParameters p); - - public virtual KdfParameters GetBestParameters(uint uMilliseconds) - { - throw new NotImplementedException(); - } - - protected void MaximizeParamUInt64(KdfParameters p, string strName, - ulong uMin, ulong uMax, uint uMilliseconds, bool bInterpSearch) - { - if(p == null) { Debug.Assert(false); return; } - if(string.IsNullOrEmpty(strName)) { Debug.Assert(false); return; } - if(uMin > uMax) { Debug.Assert(false); return; } - - if(uMax > (ulong.MaxValue >> 1)) - { - Debug.Assert(false); - uMax = ulong.MaxValue >> 1; - - if(uMin > uMax) { p.SetUInt64(strName, uMin); return; } - } - - byte[] pbMsg = new byte[32]; - for(int i = 0; i < pbMsg.Length; ++i) pbMsg[i] = (byte)i; - - ulong uLow = uMin; - ulong uHigh = uMin + 1UL; - long tLow = 0; - long tHigh = 0; - long tTarget = (long)uMilliseconds; - - // Determine range - while(uHigh <= uMax) - { - p.SetUInt64(strName, uHigh); - - // GC.Collect(); - Stopwatch sw = Stopwatch.StartNew(); - Transform(pbMsg, p); - sw.Stop(); - - tHigh = sw.ElapsedMilliseconds; - if(tHigh > tTarget) break; - - uLow = uHigh; - tLow = tHigh; - uHigh <<= 1; - } - if(uHigh > uMax) { uHigh = uMax; tHigh = 0; } - if(uLow > uHigh) uLow = uHigh; // Skips to end - - // Find optimal number of iterations - while((uHigh - uLow) >= 2UL) - { - ulong u = (uHigh + uLow) >> 1; // Binary search - // Interpolation search, if possible - if(bInterpSearch && (tLow > 0) && (tHigh > tTarget) && - (tLow <= tTarget)) - { - u = uLow + (((uHigh - uLow) * (ulong)(tTarget - tLow)) / - (ulong)(tHigh - tLow)); - if((u >= uLow) && (u <= uHigh)) - { - u = Math.Max(u, uLow + 1UL); - u = Math.Min(u, uHigh - 1UL); - } - else - { - Debug.Assert(false); - u = (uHigh + uLow) >> 1; - } - } - - p.SetUInt64(strName, u); - - // GC.Collect(); - Stopwatch sw = Stopwatch.StartNew(); - Transform(pbMsg, p); - sw.Stop(); - - long t = sw.ElapsedMilliseconds; - if(t == tTarget) { uLow = u; break; } - else if(t > tTarget) { uHigh = u; tHigh = t; } - else { uLow = u; tLow = t; } - } - - p.SetUInt64(strName, uLow); - } - } -} diff --git a/ModernKeePassLib/Cryptography/KeyDerivation/KdfParameters.cs b/ModernKeePassLib/Cryptography/KeyDerivation/KdfParameters.cs deleted file mode 100644 index 04de500..0000000 --- a/ModernKeePassLib/Cryptography/KeyDerivation/KdfParameters.cs +++ /dev/null @@ -1,80 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Text; - -using ModernKeePassLib.Collections; -using ModernKeePassLib.Utility; - -namespace ModernKeePassLib.Cryptography.KeyDerivation -{ - public sealed class KdfParameters : VariantDictionary - { - private const string ParamUuid = @"$UUID"; - - private readonly PwUuid m_puKdf; - public PwUuid KdfUuid - { - get { return m_puKdf; } - } - - public KdfParameters(PwUuid puKdf) - { - if(puKdf == null) throw new ArgumentNullException("puKdf"); - - m_puKdf = puKdf; - SetByteArray(ParamUuid, puKdf.UuidBytes); - } - - /// - /// Unsupported. - /// - public override object Clone() - { - throw new NotSupportedException(); - } - - public static byte[] SerializeExt(KdfParameters p) - { - return VariantDictionary.Serialize(p); - } - - public static KdfParameters DeserializeExt(byte[] pb) - { - VariantDictionary d = VariantDictionary.Deserialize(pb); - if(d == null) { Debug.Assert(false); return null; } - - byte[] pbUuid = d.GetByteArray(ParamUuid); - if((pbUuid == null) || (pbUuid.Length != (int)PwUuid.UuidSize)) - { - Debug.Assert(false); - return null; - } - - PwUuid pu = new PwUuid(pbUuid); - KdfParameters p = new KdfParameters(pu); - d.CopyTo(p); - return p; - } - } -} diff --git a/ModernKeePassLib/Cryptography/KeyDerivation/KdfPool.cs b/ModernKeePassLib/Cryptography/KeyDerivation/KdfPool.cs deleted file mode 100644 index 5b631b7..0000000 --- a/ModernKeePassLib/Cryptography/KeyDerivation/KdfPool.cs +++ /dev/null @@ -1,96 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Text; - -using ModernKeePassLib.Utility; - -namespace ModernKeePassLib.Cryptography.KeyDerivation -{ - public static class KdfPool - { - private static List g_l = new List(); - - public static IEnumerable Engines - { - get - { - EnsureInitialized(); - return g_l; - } - } - - private static void EnsureInitialized() - { - if(g_l.Count > 0) return; - - g_l.Add(new AesKdf()); - g_l.Add(new Argon2Kdf()); - } - - internal static KdfParameters GetDefaultParameters() - { - EnsureInitialized(); - return g_l[0].GetDefaultParameters(); - } - - public static KdfEngine Get(PwUuid pu) - { - if(pu == null) { Debug.Assert(false); return null; } - - EnsureInitialized(); - - foreach(KdfEngine kdf in g_l) - { - if(pu.Equals(kdf.Uuid)) return kdf; - } - - return null; - } - - public static KdfEngine Get(string strName) - { - if(string.IsNullOrEmpty(strName)) { Debug.Assert(false); return null; } - - EnsureInitialized(); - - foreach(KdfEngine kdf in g_l) - { - if(strName.Equals(kdf.Name, StrUtil.CaseIgnoreCmp)) return kdf; - } - - return null; - } - - public static void Add(KdfEngine kdf) - { - if(kdf == null) { Debug.Assert(false); return; } - - EnsureInitialized(); - - if(Get(kdf.Uuid) != null) { Debug.Assert(false); return; } - if(Get(kdf.Name) != null) { Debug.Assert(false); return; } - - g_l.Add(kdf); - } - } -} diff --git a/ModernKeePassLib/Cryptography/PasswordGenerator/CharSetBasedGenerator.cs b/ModernKeePassLib/Cryptography/PasswordGenerator/CharSetBasedGenerator.cs deleted file mode 100644 index 92d321d..0000000 --- a/ModernKeePassLib/Cryptography/PasswordGenerator/CharSetBasedGenerator.cs +++ /dev/null @@ -1,65 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; -using System.Text; -using System.Diagnostics; - -using ModernKeePassLib.Security; -using ModernKeePassLib.Utility; - -namespace ModernKeePassLib.Cryptography.PasswordGenerator -{ - internal static class CharSetBasedGenerator - { - internal static PwgError Generate(out ProtectedString psOut, - PwProfile pwProfile, CryptoRandomStream crsRandomSource) - { - psOut = ProtectedString.Empty; - if(pwProfile.Length == 0) return PwgError.Success; - - PwCharSet pcs = new PwCharSet(pwProfile.CharSet.ToString()); - char[] vGenerated = new char[pwProfile.Length]; - - PwGenerator.PrepareCharSet(pcs, pwProfile); - - for(int nIndex = 0; nIndex < (int)pwProfile.Length; ++nIndex) - { - char ch = PwGenerator.GenerateCharacter(pwProfile, pcs, - crsRandomSource); - - if(ch == char.MinValue) - { - MemUtil.ZeroArray(vGenerated); - return PwgError.TooFewCharacters; - } - - vGenerated[nIndex] = ch; - } - - byte[] pbUtf8 = StrUtil.Utf8.GetBytes(vGenerated); - psOut = new ProtectedString(true, pbUtf8); - MemUtil.ZeroByteArray(pbUtf8); - MemUtil.ZeroArray(vGenerated); - - return PwgError.Success; - } - } -} diff --git a/ModernKeePassLib/Cryptography/PasswordGenerator/CustomPwGenerator.cs b/ModernKeePassLib/Cryptography/PasswordGenerator/CustomPwGenerator.cs deleted file mode 100644 index 4ccaa03..0000000 --- a/ModernKeePassLib/Cryptography/PasswordGenerator/CustomPwGenerator.cs +++ /dev/null @@ -1,66 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; -using System.Text; - -using ModernKeePassLib; -using ModernKeePassLib.Security; - -namespace ModernKeePassLib.Cryptography.PasswordGenerator -{ - public abstract class CustomPwGenerator - { - /// - /// Each custom password generation algorithm must have - /// its own unique UUID. - /// - public abstract PwUuid Uuid { get; } - - /// - /// Displayable name of the password generation algorithm. - /// - public abstract string Name { get; } - - public virtual bool SupportsOptions - { - get { return false; } - } - - /// - /// Password generation function. - /// - /// Password generation options chosen - /// by the user. This may be null, if the default - /// options should be used. - /// Source that the algorithm - /// can use to generate random numbers. - /// Generated password or null in case - /// of failure. If returning null, the caller assumes - /// that an error message has already been shown to the user. - public abstract ProtectedString Generate(PwProfile prf, - CryptoRandomStream crsRandomSource); - - public virtual string GetOptions(string strCurrentOptions) - { - return string.Empty; - } - } -} diff --git a/ModernKeePassLib/Cryptography/PasswordGenerator/CustomPwGeneratorPool.cs b/ModernKeePassLib/Cryptography/PasswordGenerator/CustomPwGeneratorPool.cs deleted file mode 100644 index 952faad..0000000 --- a/ModernKeePassLib/Cryptography/PasswordGenerator/CustomPwGeneratorPool.cs +++ /dev/null @@ -1,110 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Text; - -namespace ModernKeePassLib.Cryptography.PasswordGenerator -{ - public sealed class CustomPwGeneratorPool : IEnumerable - { - private List m_vGens = new List(); - - public int Count - { - get { return m_vGens.Count; } - } - - public CustomPwGeneratorPool() - { - } - - IEnumerator IEnumerable.GetEnumerator() - { - return m_vGens.GetEnumerator(); - } - - public IEnumerator GetEnumerator() - { - return m_vGens.GetEnumerator(); - } - - public void Add(CustomPwGenerator pwg) - { - if(pwg == null) throw new ArgumentNullException("pwg"); - - PwUuid uuid = pwg.Uuid; - if(uuid == null) throw new ArgumentException(); - - int nIndex = FindIndex(uuid); - - if(nIndex >= 0) m_vGens[nIndex] = pwg; // Replace - else m_vGens.Add(pwg); - } - - public CustomPwGenerator Find(PwUuid uuid) - { - if(uuid == null) throw new ArgumentNullException("uuid"); - - foreach(CustomPwGenerator pwg in m_vGens) - { - if(uuid.Equals(pwg.Uuid)) return pwg; - } - - return null; - } - - public CustomPwGenerator Find(string strName) - { - if(strName == null) throw new ArgumentNullException("strName"); - - foreach(CustomPwGenerator pwg in m_vGens) - { - if(pwg.Name == strName) return pwg; - } - - return null; - } - - private int FindIndex(PwUuid uuid) - { - if(uuid == null) throw new ArgumentNullException("uuid"); - - for(int i = 0; i < m_vGens.Count; ++i) - { - if(uuid.Equals(m_vGens[i].Uuid)) return i; - } - - return -1; - } - - public bool Remove(PwUuid uuid) - { - if(uuid == null) throw new ArgumentNullException("uuid"); - - int nIndex = FindIndex(uuid); - if(nIndex < 0) return false; - - m_vGens.RemoveAt(nIndex); - return true; - } - } -} diff --git a/ModernKeePassLib/Cryptography/PasswordGenerator/PatternBasedGenerator.cs b/ModernKeePassLib/Cryptography/PasswordGenerator/PatternBasedGenerator.cs deleted file mode 100644 index b14c1d5..0000000 --- a/ModernKeePassLib/Cryptography/PasswordGenerator/PatternBasedGenerator.cs +++ /dev/null @@ -1,184 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; -using System.Text; -using System.Diagnostics; - -using ModernKeePassLib.Security; -using ModernKeePassLib.Utility; - -namespace ModernKeePassLib.Cryptography.PasswordGenerator -{ - internal static class PatternBasedGenerator - { - internal static PwgError Generate(out ProtectedString psOut, - PwProfile pwProfile, CryptoRandomStream crsRandomSource) - { - psOut = ProtectedString.Empty; - LinkedList vGenerated = new LinkedList(); - PwCharSet pcsCurrent = new PwCharSet(); - PwCharSet pcsCustom = new PwCharSet(); - PwCharSet pcsUsed = new PwCharSet(); - bool bInCharSetDef = false; - - string strPattern = ExpandPattern(pwProfile.Pattern); - if(strPattern.Length == 0) return PwgError.Success; - - CharStream csStream = new CharStream(strPattern); - char ch = csStream.ReadChar(); - - while(ch != char.MinValue) - { - pcsCurrent.Clear(); - - bool bGenerateChar = false; - - if(ch == '\\') - { - ch = csStream.ReadChar(); - if(ch == char.MinValue) // Backslash at the end - { - vGenerated.AddLast('\\'); - break; - } - - if(bInCharSetDef) pcsCustom.Add(ch); - else - { - vGenerated.AddLast(ch); - pcsUsed.Add(ch); - } - } - else if(ch == '^') - { - ch = csStream.ReadChar(); - if(ch == char.MinValue) // ^ at the end - { - vGenerated.AddLast('^'); - break; - } - - if(bInCharSetDef) pcsCustom.Remove(ch); - } - else if(ch == '[') - { - pcsCustom.Clear(); - bInCharSetDef = true; - } - else if(ch == ']') - { - pcsCurrent.Add(pcsCustom.ToString()); - - bInCharSetDef = false; - bGenerateChar = true; - } - else if(bInCharSetDef) - { - if(pcsCustom.AddCharSet(ch) == false) - pcsCustom.Add(ch); - } - else if(pcsCurrent.AddCharSet(ch) == false) - { - vGenerated.AddLast(ch); - pcsUsed.Add(ch); - } - else bGenerateChar = true; - - if(bGenerateChar) - { - PwGenerator.PrepareCharSet(pcsCurrent, pwProfile); - - if(pwProfile.NoRepeatingCharacters) - pcsCurrent.Remove(pcsUsed.ToString()); - - char chGen = PwGenerator.GenerateCharacter(pwProfile, - pcsCurrent, crsRandomSource); - - if(chGen == char.MinValue) return PwgError.TooFewCharacters; - - vGenerated.AddLast(chGen); - pcsUsed.Add(chGen); - } - - ch = csStream.ReadChar(); - } - - if(vGenerated.Count == 0) return PwgError.Success; - - char[] vArray = new char[vGenerated.Count]; - vGenerated.CopyTo(vArray, 0); - - if(pwProfile.PatternPermutePassword) - PwGenerator.ShufflePassword(vArray, crsRandomSource); - - byte[] pbUtf8 = StrUtil.Utf8.GetBytes(vArray); - psOut = new ProtectedString(true, pbUtf8); - MemUtil.ZeroByteArray(pbUtf8); - MemUtil.ZeroArray(vArray); - vGenerated.Clear(); - - return PwgError.Success; - } - - private static string ExpandPattern(string strPattern) - { - Debug.Assert(strPattern != null); if(strPattern == null) return string.Empty; - string str = strPattern; - - while(true) - { - int nOpen = FindFirstUnescapedChar(str, '{'); - int nClose = FindFirstUnescapedChar(str, '}'); - - if((nOpen >= 0) && (nOpen < nClose)) - { - string strCount = str.Substring(nOpen + 1, nClose - nOpen - 1); - str = str.Remove(nOpen, nClose - nOpen + 1); - - uint uRepeat; - if(StrUtil.TryParseUInt(strCount, out uRepeat) && (nOpen >= 1)) - { - if(uRepeat == 0) - str = str.Remove(nOpen - 1, 1); - else - str = str.Insert(nOpen, new string(str[nOpen - 1], (int)uRepeat - 1)); - } - } - else break; - } - - return str; - } - - private static int FindFirstUnescapedChar(string str, char ch) - { - for(int i = 0; i < str.Length; ++i) - { - char chCur = str[i]; - - if(chCur == '\\') ++i; // Next is escaped, skip it - else if(chCur == ch) return i; - } - - return -1; - } - } -} diff --git a/ModernKeePassLib/Cryptography/PasswordGenerator/PwCharSet.cs b/ModernKeePassLib/Cryptography/PasswordGenerator/PwCharSet.cs deleted file mode 100644 index 3047b75..0000000 --- a/ModernKeePassLib/Cryptography/PasswordGenerator/PwCharSet.cs +++ /dev/null @@ -1,351 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; -using System.Text; -using System.Diagnostics; - -namespace ModernKeePassLib.Cryptography.PasswordGenerator -{ - public sealed class PwCharSet - { - public const string UpperCase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - public const string LowerCase = "abcdefghijklmnopqrstuvwxyz"; - public const string Digits = "0123456789"; - - public const string UpperConsonants = "BCDFGHJKLMNPQRSTVWXYZ"; - public const string LowerConsonants = "bcdfghjklmnpqrstvwxyz"; - public const string UpperVowels = "AEIOU"; - public const string LowerVowels = "aeiou"; - - public const string Punctuation = @",.;:"; - public const string Brackets = @"[]{}()<>"; - - public const string PrintableAsciiSpecial = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"; - - public const string UpperHex = "0123456789ABCDEF"; - public const string LowerHex = "0123456789abcdef"; - - public const string Invalid = "\t\r\n"; - public const string LookAlike = @"O0l1I|"; - - internal const string MenuAccels = PwCharSet.LowerCase + PwCharSet.Digits; - - private const int CharTabSize = (0x10000 / 8); - - private List m_vChars = new List(); - private byte[] m_vTab = new byte[CharTabSize]; - - private static string m_strHighAnsi = null; - public static string HighAnsiChars - { - get - { - if(m_strHighAnsi == null) { new PwCharSet(); } // Create string - Debug.Assert(m_strHighAnsi != null); - return m_strHighAnsi; - } - } - - private static string m_strSpecial = null; - public static string SpecialChars - { - get - { - if(m_strSpecial == null) { new PwCharSet(); } // Create string - Debug.Assert(m_strSpecial != null); - return m_strSpecial; - } - } - - /// - /// Create a new, empty character set collection object. - /// - public PwCharSet() - { - Initialize(true); - } - - public PwCharSet(string strCharSet) - { - Initialize(true); - Add(strCharSet); - } - - private PwCharSet(bool bFullInitialize) - { - Initialize(bFullInitialize); - } - - private void Initialize(bool bFullInitialize) - { - Clear(); - - if(!bFullInitialize) return; - - if(m_strHighAnsi == null) - { - StringBuilder sbHighAnsi = new StringBuilder(); - // [U+0080, U+009F] are C1 control characters, - // U+00A0 is non-breaking space - for(char ch = '\u00A1'; ch <= '\u00AC'; ++ch) - sbHighAnsi.Append(ch); - // U+00AD is soft hyphen (format character) - for(char ch = '\u00AE'; ch < '\u00FF'; ++ch) - sbHighAnsi.Append(ch); - sbHighAnsi.Append('\u00FF'); - - m_strHighAnsi = sbHighAnsi.ToString(); - } - - if(m_strSpecial == null) - { - PwCharSet pcs = new PwCharSet(false); - pcs.AddRange('!', '/'); - pcs.AddRange(':', '@'); - pcs.AddRange('[', '`'); - pcs.Add(@"|~"); - pcs.Remove(@"-_ "); - pcs.Remove(PwCharSet.Brackets); - - m_strSpecial = pcs.ToString(); - } - } - - /// - /// Number of characters in this set. - /// - public uint Size - { - get { return (uint)m_vChars.Count; } - } - - /// - /// Get a character of the set using an index. - /// - /// Index of the character to get. - /// Character at the specified position. If the index is invalid, - /// an ArgumentOutOfRangeException is thrown. - public char this[uint uPos] - { - get - { - if(uPos >= (uint)m_vChars.Count) - throw new ArgumentOutOfRangeException("uPos"); - - return m_vChars[(int)uPos]; - } - } - - /// - /// Remove all characters from this set. - /// - public void Clear() - { - m_vChars.Clear(); - Array.Clear(m_vTab, 0, m_vTab.Length); - } - - public bool Contains(char ch) - { - return (((m_vTab[ch / 8] >> (ch % 8)) & 1) != char.MinValue); - } - - public bool Contains(string strCharacters) - { - Debug.Assert(strCharacters != null); - if(strCharacters == null) throw new ArgumentNullException("strCharacters"); - - foreach(char ch in strCharacters) - { - if(!Contains(ch)) return false; - } - - return true; - } - - /// - /// Add characters to the set. - /// - /// Character to add. - public void Add(char ch) - { - if(ch == char.MinValue) { Debug.Assert(false); return; } - - if(!Contains(ch)) - { - m_vChars.Add(ch); - m_vTab[ch / 8] |= (byte)(1 << (ch % 8)); - } - } - - /// - /// Add characters to the set. - /// - /// String containing characters to add. - public void Add(string strCharSet) - { - Debug.Assert(strCharSet != null); - if(strCharSet == null) throw new ArgumentNullException("strCharSet"); - - m_vChars.Capacity = m_vChars.Count + strCharSet.Length; - - foreach(char ch in strCharSet) - Add(ch); - } - - public void Add(string strCharSet1, string strCharSet2) - { - Add(strCharSet1); - Add(strCharSet2); - } - - public void Add(string strCharSet1, string strCharSet2, string strCharSet3) - { - Add(strCharSet1); - Add(strCharSet2); - Add(strCharSet3); - } - - public void AddRange(char chMin, char chMax) - { - m_vChars.Capacity = m_vChars.Count + (chMax - chMin) + 1; - - for(char ch = chMin; ch < chMax; ++ch) - Add(ch); - - Add(chMax); - } - - public bool AddCharSet(char chCharSetIdentifier) - { - bool bResult = true; - - switch(chCharSetIdentifier) - { - case 'a': Add(PwCharSet.LowerCase, PwCharSet.Digits); break; - case 'A': Add(PwCharSet.LowerCase, PwCharSet.UpperCase, - PwCharSet.Digits); break; - case 'U': Add(PwCharSet.UpperCase, PwCharSet.Digits); break; - case 'c': Add(PwCharSet.LowerConsonants); break; - case 'C': Add(PwCharSet.LowerConsonants, - PwCharSet.UpperConsonants); break; - case 'z': Add(PwCharSet.UpperConsonants); break; - case 'd': Add(PwCharSet.Digits); break; // Digit - case 'h': Add(PwCharSet.LowerHex); break; - case 'H': Add(PwCharSet.UpperHex); break; - case 'l': Add(PwCharSet.LowerCase); break; - case 'L': Add(PwCharSet.LowerCase, PwCharSet.UpperCase); break; - case 'u': Add(PwCharSet.UpperCase); break; - case 'p': Add(PwCharSet.Punctuation); break; - case 'b': Add(PwCharSet.Brackets); break; - case 's': Add(PwCharSet.PrintableAsciiSpecial); break; - case 'S': Add(PwCharSet.UpperCase, PwCharSet.LowerCase); - Add(PwCharSet.Digits, PwCharSet.PrintableAsciiSpecial); break; - case 'v': Add(PwCharSet.LowerVowels); break; - case 'V': Add(PwCharSet.LowerVowels, PwCharSet.UpperVowels); break; - case 'Z': Add(PwCharSet.UpperVowels); break; - case 'x': Add(m_strHighAnsi); break; - default: bResult = false; break; - } - - return bResult; - } - - public bool Remove(char ch) - { - m_vTab[ch / 8] &= (byte)(~(1 << (ch % 8))); - return m_vChars.Remove(ch); - } - - public bool Remove(string strCharacters) - { - Debug.Assert(strCharacters != null); - if(strCharacters == null) throw new ArgumentNullException("strCharacters"); - - bool bResult = true; - foreach(char ch in strCharacters) - { - if(!Remove(ch)) bResult = false; - } - - return bResult; - } - - public bool RemoveIfAllExist(string strCharacters) - { - Debug.Assert(strCharacters != null); - if(strCharacters == null) throw new ArgumentNullException("strCharacters"); - - if(!Contains(strCharacters)) - return false; - - return Remove(strCharacters); - } - - /// - /// Convert the character set to a string containing all its characters. - /// - /// String containing all character set characters. - public override string ToString() - { - StringBuilder sb = new StringBuilder(); - foreach(char ch in m_vChars) - sb.Append(ch); - - return sb.ToString(); - } - - public string PackAndRemoveCharRanges() - { - StringBuilder sb = new StringBuilder(); - - sb.Append(RemoveIfAllExist(PwCharSet.UpperCase) ? 'U' : '_'); - sb.Append(RemoveIfAllExist(PwCharSet.LowerCase) ? 'L' : '_'); - sb.Append(RemoveIfAllExist(PwCharSet.Digits) ? 'D' : '_'); - sb.Append(RemoveIfAllExist(m_strSpecial) ? 'S' : '_'); - sb.Append(RemoveIfAllExist(PwCharSet.Punctuation) ? 'P' : '_'); - sb.Append(RemoveIfAllExist(@"-") ? 'm' : '_'); - sb.Append(RemoveIfAllExist(@"_") ? 'u' : '_'); - sb.Append(RemoveIfAllExist(@" ") ? 's' : '_'); - sb.Append(RemoveIfAllExist(PwCharSet.Brackets) ? 'B' : '_'); - sb.Append(RemoveIfAllExist(m_strHighAnsi) ? 'H' : '_'); - - return sb.ToString(); - } - - public void UnpackCharRanges(string strRanges) - { - if(strRanges == null) { Debug.Assert(false); return; } - if(strRanges.Length < 10) { Debug.Assert(false); return; } - - if(strRanges[0] != '_') Add(PwCharSet.UpperCase); - if(strRanges[1] != '_') Add(PwCharSet.LowerCase); - if(strRanges[2] != '_') Add(PwCharSet.Digits); - if(strRanges[3] != '_') Add(m_strSpecial); - if(strRanges[4] != '_') Add(PwCharSet.Punctuation); - if(strRanges[5] != '_') Add('-'); - if(strRanges[6] != '_') Add('_'); - if(strRanges[7] != '_') Add(' '); - if(strRanges[8] != '_') Add(PwCharSet.Brackets); - if(strRanges[9] != '_') Add(m_strHighAnsi); - } - } -} diff --git a/ModernKeePassLib/Cryptography/PasswordGenerator/PwGenerator.cs b/ModernKeePassLib/Cryptography/PasswordGenerator/PwGenerator.cs deleted file mode 100644 index 7a5c136..0000000 --- a/ModernKeePassLib/Cryptography/PasswordGenerator/PwGenerator.cs +++ /dev/null @@ -1,167 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Text; -#if ModernKeePassLib -using ModernKeePassLib.Cryptography.Hash; -#elif !KeePassUAP -using System.Security.Cryptography; -#endif - -using ModernKeePassLib.Security; -using ModernKeePassLib.Utility; - -namespace ModernKeePassLib.Cryptography.PasswordGenerator -{ - public enum PwgError - { - Success = 0, - Unknown = 1, - TooFewCharacters = 2, - UnknownAlgorithm = 3 - } - - /// - /// Utility functions for generating random passwords. - /// - public static class PwGenerator - { - public static PwgError Generate(out ProtectedString psOut, - PwProfile pwProfile, byte[] pbUserEntropy, - CustomPwGeneratorPool pwAlgorithmPool) - { - Debug.Assert(pwProfile != null); - if(pwProfile == null) throw new ArgumentNullException("pwProfile"); - - PwgError e = PwgError.Unknown; - CryptoRandomStream crs = null; - byte[] pbKey = null; - try - { - crs = CreateRandomStream(pbUserEntropy, out pbKey); - - if(pwProfile.GeneratorType == PasswordGeneratorType.CharSet) - e = CharSetBasedGenerator.Generate(out psOut, pwProfile, crs); - else if(pwProfile.GeneratorType == PasswordGeneratorType.Pattern) - e = PatternBasedGenerator.Generate(out psOut, pwProfile, crs); - else if(pwProfile.GeneratorType == PasswordGeneratorType.Custom) - e = GenerateCustom(out psOut, pwProfile, crs, pwAlgorithmPool); - else { Debug.Assert(false); psOut = ProtectedString.Empty; } - } - finally - { - if(crs != null) crs.Dispose(); - if(pbKey != null) MemUtil.ZeroByteArray(pbKey); - } - - return e; - } - - private static CryptoRandomStream CreateRandomStream(byte[] pbAdditionalEntropy, - out byte[] pbKey) - { - pbKey = CryptoRandom.Instance.GetRandomBytes(128); - - // Mix in additional entropy - Debug.Assert(pbKey.Length >= 64); - if((pbAdditionalEntropy != null) && (pbAdditionalEntropy.Length > 0)) - { - using(SHA512Managed h = new SHA512Managed()) - { - byte[] pbHash = h.ComputeHash(pbAdditionalEntropy); - MemUtil.XorArray(pbHash, 0, pbKey, 0, pbHash.Length); - } - } - - return new CryptoRandomStream(CrsAlgorithm.ChaCha20, pbKey); - } - - internal static char GenerateCharacter(PwProfile pwProfile, - PwCharSet pwCharSet, CryptoRandomStream crsRandomSource) - { - if(pwCharSet.Size == 0) return char.MinValue; - - ulong uIndex = crsRandomSource.GetRandomUInt64(); - uIndex %= (ulong)pwCharSet.Size; - - char ch = pwCharSet[(uint)uIndex]; - - if(pwProfile.NoRepeatingCharacters) - pwCharSet.Remove(ch); - - return ch; - } - - internal static void PrepareCharSet(PwCharSet pwCharSet, PwProfile pwProfile) - { - pwCharSet.Remove(PwCharSet.Invalid); - - if(pwProfile.ExcludeLookAlike) pwCharSet.Remove(PwCharSet.LookAlike); - - if(pwProfile.ExcludeCharacters.Length > 0) - pwCharSet.Remove(pwProfile.ExcludeCharacters); - } - - internal static void ShufflePassword(char[] pPassword, - CryptoRandomStream crsRandomSource) - { - Debug.Assert(pPassword != null); if(pPassword == null) return; - Debug.Assert(crsRandomSource != null); if(crsRandomSource == null) return; - - if(pPassword.Length <= 1) return; // Nothing to shuffle - - for(int nSelect = 0; nSelect < pPassword.Length; ++nSelect) - { - ulong uRandomIndex = crsRandomSource.GetRandomUInt64(); - uRandomIndex %= (ulong)(pPassword.Length - nSelect); - - char chTemp = pPassword[nSelect]; - pPassword[nSelect] = pPassword[nSelect + (int)uRandomIndex]; - pPassword[nSelect + (int)uRandomIndex] = chTemp; - } - } - - private static PwgError GenerateCustom(out ProtectedString psOut, - PwProfile pwProfile, CryptoRandomStream crs, - CustomPwGeneratorPool pwAlgorithmPool) - { - psOut = ProtectedString.Empty; - - Debug.Assert(pwProfile.GeneratorType == PasswordGeneratorType.Custom); - if(pwAlgorithmPool == null) return PwgError.UnknownAlgorithm; - - string strID = pwProfile.CustomAlgorithmUuid; - if(string.IsNullOrEmpty(strID)) { Debug.Assert(false); return PwgError.UnknownAlgorithm; } - - byte[] pbUuid = Convert.FromBase64String(strID); - PwUuid uuid = new PwUuid(pbUuid); - CustomPwGenerator pwg = pwAlgorithmPool.Find(uuid); - if(pwg == null) { Debug.Assert(false); return PwgError.UnknownAlgorithm; } - - ProtectedString pwd = pwg.Generate(pwProfile.CloneDeep(), crs); - if(pwd == null) return PwgError.Unknown; - - psOut = pwd; - return PwgError.Success; - } - } -} diff --git a/ModernKeePassLib/Cryptography/PasswordGenerator/PwProfile.cs b/ModernKeePassLib/Cryptography/PasswordGenerator/PwProfile.cs deleted file mode 100644 index aa361e3..0000000 --- a/ModernKeePassLib/Cryptography/PasswordGenerator/PwProfile.cs +++ /dev/null @@ -1,276 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; -using System.Text; -using System.Xml.Serialization; -using System.ComponentModel; -using System.Diagnostics; - -using ModernKeePassLib.Interfaces; -using ModernKeePassLib.Security; -using ModernKeePassLib.Utility; - -namespace ModernKeePassLib.Cryptography.PasswordGenerator -{ - /// - /// Type of the password generator. Different types like generators - /// based on given patterns, based on character sets, etc. are - /// available. - /// - public enum PasswordGeneratorType - { - /// - /// Generator based on character spaces/sets, i.e. groups - /// of characters like lower-case, upper-case or numeric characters. - /// - CharSet = 0, - - /// - /// Password generation based on a pattern. The user has provided - /// a pattern, which describes how the generated password has to - /// look like. - /// - Pattern = 1, - - Custom = 2 - } - - public sealed class PwProfile : IDeepCloneable - { - private string m_strName = string.Empty; - [DefaultValue("")] - public string Name - { - get { return m_strName; } - set { m_strName = value; } - } - - private PasswordGeneratorType m_type = PasswordGeneratorType.CharSet; - public PasswordGeneratorType GeneratorType - { - get { return m_type; } - set { m_type = value; } - } - - private bool m_bUserEntropy = false; - [DefaultValue(false)] - public bool CollectUserEntropy - { - get { return m_bUserEntropy; } - set { m_bUserEntropy = value; } - } - - private uint m_uLength = 20; - public uint Length - { - get { return m_uLength; } - set { m_uLength = value; } - } - - private PwCharSet m_pwCharSet = new PwCharSet(PwCharSet.UpperCase + - PwCharSet.LowerCase + PwCharSet.Digits); - [XmlIgnore] - public PwCharSet CharSet - { - get { return m_pwCharSet; } - set - { - if(value == null) throw new ArgumentNullException("value"); - m_pwCharSet = value; - } - } - - private string m_strCharSetRanges = string.Empty; - [DefaultValue("")] - public string CharSetRanges - { - get { this.UpdateCharSet(true); return m_strCharSetRanges; } - set - { - if(value == null) throw new ArgumentNullException("value"); - m_strCharSetRanges = value; - this.UpdateCharSet(false); - } - } - - private string m_strCharSetAdditional = string.Empty; - [DefaultValue("")] - public string CharSetAdditional - { - get { this.UpdateCharSet(true); return m_strCharSetAdditional; } - set - { - if(value == null) throw new ArgumentNullException("value"); - m_strCharSetAdditional = value; - this.UpdateCharSet(false); - } - } - - private string m_strPattern = string.Empty; - [DefaultValue("")] - public string Pattern - { - get { return m_strPattern; } - set { m_strPattern = value; } - } - - private bool m_bPatternPermute = false; - [DefaultValue(false)] - public bool PatternPermutePassword - { - get { return m_bPatternPermute; } - set { m_bPatternPermute = value; } - } - - private bool m_bNoLookAlike = false; - [DefaultValue(false)] - public bool ExcludeLookAlike - { - get { return m_bNoLookAlike; } - set { m_bNoLookAlike = value; } - } - - private bool m_bNoRepeat = false; - [DefaultValue(false)] - public bool NoRepeatingCharacters - { - get { return m_bNoRepeat; } - set { m_bNoRepeat = value; } - } - - private string m_strExclude = string.Empty; - [DefaultValue("")] - public string ExcludeCharacters - { - get { return m_strExclude; } - set - { - if(value == null) throw new ArgumentNullException("value"); - m_strExclude = value; - } - } - - private string m_strCustomID = string.Empty; - [DefaultValue("")] - public string CustomAlgorithmUuid - { - get { return m_strCustomID; } - set - { - if(value == null) throw new ArgumentNullException("value"); - m_strCustomID = value; - } - } - - private string m_strCustomOpt = string.Empty; - [DefaultValue("")] - public string CustomAlgorithmOptions - { - get { return m_strCustomOpt; } - set - { - if(value == null) throw new ArgumentNullException("value"); - m_strCustomOpt = value; - } - } - - public PwProfile() - { - } - - public PwProfile CloneDeep() - { - PwProfile p = new PwProfile(); - - p.m_strName = m_strName; - p.m_type = m_type; - p.m_bUserEntropy = m_bUserEntropy; - p.m_uLength = m_uLength; - p.m_pwCharSet = new PwCharSet(m_pwCharSet.ToString()); - p.m_strCharSetRanges = m_strCharSetRanges; - p.m_strCharSetAdditional = m_strCharSetAdditional; - p.m_strPattern = m_strPattern; - p.m_bPatternPermute = m_bPatternPermute; - p.m_bNoLookAlike = m_bNoLookAlike; - p.m_bNoRepeat = m_bNoRepeat; - p.m_strExclude = m_strExclude; - p.m_strCustomID = m_strCustomID; - p.m_strCustomOpt = m_strCustomOpt; - - return p; - } - - private void UpdateCharSet(bool bSetXml) - { - if(bSetXml) - { - PwCharSet pcs = new PwCharSet(m_pwCharSet.ToString()); - m_strCharSetRanges = pcs.PackAndRemoveCharRanges(); - m_strCharSetAdditional = pcs.ToString(); - } - else - { - PwCharSet pcs = new PwCharSet(m_strCharSetAdditional); - pcs.UnpackCharRanges(m_strCharSetRanges); - m_pwCharSet = pcs; - } - } - - public static PwProfile DeriveFromPassword(ProtectedString psPassword) - { - PwProfile pp = new PwProfile(); - Debug.Assert(psPassword != null); if(psPassword == null) return pp; - - char[] vChars = psPassword.ReadChars(); - - pp.GeneratorType = PasswordGeneratorType.CharSet; - pp.Length = (uint)vChars.Length; - - PwCharSet pcs = pp.CharSet; - pcs.Clear(); - - foreach(char ch in vChars) - { - if((ch >= 'A') && (ch <= 'Z')) pcs.Add(PwCharSet.UpperCase); - else if((ch >= 'a') && (ch <= 'z')) pcs.Add(PwCharSet.LowerCase); - else if((ch >= '0') && (ch <= '9')) pcs.Add(PwCharSet.Digits); - else if(PwCharSet.SpecialChars.IndexOf(ch) >= 0) - pcs.Add(PwCharSet.SpecialChars); - else if(ch == ' ') pcs.Add(' '); - else if(ch == '-') pcs.Add('-'); - else if(ch == '_') pcs.Add('_'); - else if(PwCharSet.Brackets.IndexOf(ch) >= 0) - pcs.Add(PwCharSet.Brackets); - else if(PwCharSet.HighAnsiChars.IndexOf(ch) >= 0) - pcs.Add(PwCharSet.HighAnsiChars); - else pcs.Add(ch); - } - - MemUtil.ZeroArray(vChars); - return pp; - } - - public bool HasSecurityReducingOption() - { - return (m_bNoLookAlike || m_bNoRepeat || (m_strExclude.Length > 0)); - } - } -} diff --git a/ModernKeePassLib/Cryptography/PopularPasswords.cs b/ModernKeePassLib/Cryptography/PopularPasswords.cs deleted file mode 100644 index 905ae7c..0000000 --- a/ModernKeePassLib/Cryptography/PopularPasswords.cs +++ /dev/null @@ -1,140 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Text; - -using ModernKeePassLib.Utility; - -namespace ModernKeePassLib.Cryptography -{ - public static class PopularPasswords - { - private static Dictionary> m_dicts = - new Dictionary>(); - - internal static int MaxLength - { - get - { - Debug.Assert(m_dicts.Count > 0); // Should be initialized - - int iMaxLen = 0; - foreach(int iLen in m_dicts.Keys) - { - if(iLen > iMaxLen) iMaxLen = iLen; - } - - return iMaxLen; - } - } - - internal static bool ContainsLength(int nLength) - { - Dictionary dDummy; - return m_dicts.TryGetValue(nLength, out dDummy); - } - - public static bool IsPopularPassword(char[] vPassword) - { - ulong uDummy; - return IsPopularPassword(vPassword, out uDummy); - } - - public static bool IsPopularPassword(char[] vPassword, out ulong uDictSize) - { - if(vPassword == null) throw new ArgumentNullException("vPassword"); - if(vPassword.Length == 0) { uDictSize = 0; return false; } - -#if DEBUG -#if ModernKeePassLib - foreach (var ch in vPassword) - { - Debug.Assert(ch == char.ToLower(ch)); - } -#else - Array.ForEach(vPassword, ch => Debug.Assert(ch == char.ToLower(ch))); -#endif -#endif - - try { return IsPopularPasswordPriv(vPassword, out uDictSize); } - catch(Exception) { Debug.Assert(false); } - - uDictSize = 0; - return false; - } - - private static bool IsPopularPasswordPriv(char[] vPassword, out ulong uDictSize) - { - Debug.Assert(m_dicts.Count > 0); // Should be initialized with data - - Dictionary d; - if(!m_dicts.TryGetValue(vPassword.Length, out d)) - { - uDictSize = 0; - return false; - } - - uDictSize = (ulong)d.Count; - return d.ContainsKey(vPassword); - } - - public static void Add(byte[] pbData, bool bGZipped) - { - try - { - if(bGZipped) - pbData = MemUtil.Decompress(pbData); - - string strData = StrUtil.Utf8.GetString(pbData, 0, pbData.Length); - if(string.IsNullOrEmpty(strData)) { Debug.Assert(false); return; } - - StringBuilder sb = new StringBuilder(); - for(int i = 0; i <= strData.Length; ++i) - { - char ch = ((i == strData.Length) ? ' ' : strData[i]); - - if(char.IsWhiteSpace(ch)) - { - int cc = sb.Length; - if(cc > 0) - { - char[] vWord = new char[cc]; - sb.CopyTo(0, vWord, 0, cc); - - Dictionary d; - if(!m_dicts.TryGetValue(cc, out d)) - { - d = new Dictionary(MemUtil.ArrayHelperExOfChar); - m_dicts[cc] = d; - } - - d[vWord] = true; - sb.Remove(0, cc); - } - } - else sb.Append(char.ToLower(ch)); - } - } - catch(Exception) { Debug.Assert(false); } - } - } -} diff --git a/ModernKeePassLib/Cryptography/ProtectedData.cs b/ModernKeePassLib/Cryptography/ProtectedData.cs deleted file mode 100644 index c25562d..0000000 --- a/ModernKeePassLib/Cryptography/ProtectedData.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using ModernKeePassLib.Native; - -namespace ModernKeePassLib.Cryptography -{ - public static class ProtectedData - { - public static byte[] Unprotect(byte[] pbEnc, byte[] mPbOptEnt, DataProtectionScope currentUser) - { - throw new NotImplementedException(); - } - - public static byte[] Protect(byte[] pbPlain, byte[] mPbOptEnt, DataProtectionScope currentUser) - { - throw new NotImplementedException(); - } - } -} diff --git a/ModernKeePassLib/Cryptography/QualityEstimation.cs b/ModernKeePassLib/Cryptography/QualityEstimation.cs deleted file mode 100644 index a477a17..0000000 --- a/ModernKeePassLib/Cryptography/QualityEstimation.cs +++ /dev/null @@ -1,779 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Text; - -using ModernKeePassLib.Cryptography.PasswordGenerator; -using ModernKeePassLib.Utility; - -namespace ModernKeePassLib.Cryptography -{ - /// - /// A class that offers static functions to estimate the quality of - /// passwords. - /// - public static class QualityEstimation - { - private static class PatternID - { - public const char LowerAlpha = 'L'; - public const char UpperAlpha = 'U'; - public const char Digit = 'D'; - public const char Special = 'S'; - public const char High = 'H'; - public const char Other = 'X'; - - public const char Dictionary = 'W'; - public const char Repetition = 'R'; - public const char Number = 'N'; - public const char DiffSeq = 'C'; - - public const string All = "LUDSHXWRNC"; - } - - // private static class CharDistrib - // { - // public static readonly ulong[] LowerAlpha = new ulong[26] { - // 884, 211, 262, 249, 722, 98, 172, 234, 556, 124, 201, 447, 321, - // 483, 518, 167, 18, 458, 416, 344, 231, 105, 80, 48, 238, 76 - // }; - // public static readonly ulong[] UpperAlpha = new ulong[26] { - // 605, 188, 209, 200, 460, 81, 130, 163, 357, 122, 144, 332, 260, - // 317, 330, 132, 18, 320, 315, 250, 137, 76, 60, 36, 161, 54 - // }; - // public static readonly ulong[] Digit = new ulong[10] { - // 574, 673, 524, 377, 339, 336, 312, 310, 357, 386 - // }; - // } - - private sealed class QeCharType - { - private readonly char m_chTypeID; - public char TypeID { get { return m_chTypeID; } } - - private readonly string m_strAlph; - public string Alphabet { get { return m_strAlph; } } - - private readonly int m_nChars; - public int CharCount { get { return m_nChars; } } - - private readonly char m_chFirst; - private readonly char m_chLast; - - private readonly double m_dblCharSize; - public double CharSize { get { return m_dblCharSize; } } - - public QeCharType(char chTypeID, string strAlphabet, bool bIsConsecutive) - { - if(strAlphabet == null) throw new ArgumentNullException(); - if(strAlphabet.Length == 0) throw new ArgumentException(); - - m_chTypeID = chTypeID; - m_strAlph = strAlphabet; - m_nChars = m_strAlph.Length; - m_chFirst = (bIsConsecutive ? m_strAlph[0] : char.MinValue); - m_chLast = (bIsConsecutive ? m_strAlph[m_nChars - 1] : char.MinValue); - - m_dblCharSize = Log2(m_nChars); - - Debug.Assert(((int)(m_chLast - m_chFirst) == (m_nChars - 1)) || - !bIsConsecutive); - } - - public QeCharType(char chTypeID, int nChars) // Catch-none set - { - if(nChars <= 0) throw new ArgumentOutOfRangeException(); - - m_chTypeID = chTypeID; - m_strAlph = string.Empty; - m_nChars = nChars; - m_chFirst = char.MinValue; - m_chLast = char.MinValue; - - m_dblCharSize = Log2(m_nChars); - } - - public bool Contains(char ch) - { - if(m_chLast != char.MinValue) - return ((ch >= m_chFirst) && (ch <= m_chLast)); - - Debug.Assert(m_strAlph.Length > 0); // Don't call for catch-none set - return (m_strAlph.IndexOf(ch) >= 0); - } - } - - private sealed class EntropyEncoder - { - private readonly string m_strAlph; - private Dictionary m_dHisto = new Dictionary(); - private readonly ulong m_uBaseWeight; - private readonly ulong m_uCharWeight; - private readonly ulong m_uOccExclThreshold; - - public EntropyEncoder(string strAlphabet, ulong uBaseWeight, - ulong uCharWeight, ulong uOccExclThreshold) - { - if(strAlphabet == null) throw new ArgumentNullException(); - if(strAlphabet.Length == 0) throw new ArgumentException(); - - m_strAlph = strAlphabet; - m_uBaseWeight = uBaseWeight; - m_uCharWeight = uCharWeight; - m_uOccExclThreshold = uOccExclThreshold; - -#if DEBUG - Dictionary d = new Dictionary(); - foreach(char ch in m_strAlph) { d[ch] = true; } - Debug.Assert(d.Count == m_strAlph.Length); // No duplicates -#endif - } - - public void Reset() - { - m_dHisto.Clear(); - } - - public void Write(char ch) - { - Debug.Assert(m_strAlph.IndexOf(ch) >= 0); - - ulong uOcc; - m_dHisto.TryGetValue(ch, out uOcc); - Debug.Assert(m_dHisto.ContainsKey(ch) || (uOcc == 0)); - m_dHisto[ch] = uOcc + 1; - } - - public double GetOutputSize() - { - ulong uTotalWeight = m_uBaseWeight * (ulong)m_strAlph.Length; - foreach(ulong u in m_dHisto.Values) - { - Debug.Assert(u >= 1); - if(u > m_uOccExclThreshold) - uTotalWeight += (u - m_uOccExclThreshold) * m_uCharWeight; - } - - double dSize = 0.0, dTotalWeight = (double)uTotalWeight; - foreach(ulong u in m_dHisto.Values) - { - ulong uWeight = m_uBaseWeight; - if(u > m_uOccExclThreshold) - uWeight += (u - m_uOccExclThreshold) * m_uCharWeight; - - dSize -= (double)u * Log2((double)uWeight / dTotalWeight); - } - - return dSize; - } - } - - private sealed class MultiEntropyEncoder - { - private Dictionary m_dEncs = - new Dictionary(); - - public MultiEntropyEncoder() - { - } - - public void AddEncoder(char chTypeID, EntropyEncoder ec) - { - if(ec == null) { Debug.Assert(false); return; } - - Debug.Assert(!m_dEncs.ContainsKey(chTypeID)); - m_dEncs[chTypeID] = ec; - } - - public void Reset() - { - foreach(EntropyEncoder ec in m_dEncs.Values) { ec.Reset(); } - } - - public bool Write(char chTypeID, char chData) - { - EntropyEncoder ec; - if(!m_dEncs.TryGetValue(chTypeID, out ec)) - return false; - - ec.Write(chData); - return true; - } - - public double GetOutputSize() - { - double d = 0.0; - - foreach(EntropyEncoder ec in m_dEncs.Values) - { - d += ec.GetOutputSize(); - } - - return d; - } - } - - private sealed class QePatternInstance - { - private readonly int m_iPos; - public int Position { get { return m_iPos; } } - - private readonly int m_nLen; - public int Length { get { return m_nLen; } } - - private readonly char m_chPatternID; - public char PatternID { get { return m_chPatternID; } } - - private readonly double m_dblCost; - public double Cost { get { return m_dblCost; } } - - private readonly QeCharType m_ctSingle; - public QeCharType SingleCharType { get { return m_ctSingle; } } - - public QePatternInstance(int iPosition, int nLength, char chPatternID, - double dblCost) - { - m_iPos = iPosition; - m_nLen = nLength; - m_chPatternID = chPatternID; - m_dblCost = dblCost; - m_ctSingle = null; - } - - public QePatternInstance(int iPosition, int nLength, QeCharType ctSingle) - { - m_iPos = iPosition; - m_nLen = nLength; - m_chPatternID = ctSingle.TypeID; - m_dblCost = ctSingle.CharSize; - m_ctSingle = ctSingle; - } - } - - private sealed class QePathState - { - public readonly int Position; - public readonly List Path; - - public QePathState(int iPosition, List lPath) - { - this.Position = iPosition; - this.Path = lPath; - } - } - - private static readonly object m_objSyncInit = new object(); - private static List m_lCharTypes = null; - - private static void EnsureInitialized() - { - lock(m_objSyncInit) - { - if(m_lCharTypes == null) - { - string strSpecial = PwCharSet.PrintableAsciiSpecial; - if(strSpecial.IndexOf(' ') >= 0) { Debug.Assert(false); } - else strSpecial = strSpecial + " "; - - int nSp = strSpecial.Length; - int nHi = PwCharSet.HighAnsiChars.Length; - - m_lCharTypes = new List(); - - m_lCharTypes.Add(new QeCharType(PatternID.LowerAlpha, - PwCharSet.LowerCase, true)); - m_lCharTypes.Add(new QeCharType(PatternID.UpperAlpha, - PwCharSet.UpperCase, true)); - m_lCharTypes.Add(new QeCharType(PatternID.Digit, - PwCharSet.Digits, true)); - m_lCharTypes.Add(new QeCharType(PatternID.Special, - strSpecial, false)); - m_lCharTypes.Add(new QeCharType(PatternID.High, - PwCharSet.HighAnsiChars, false)); - m_lCharTypes.Add(new QeCharType(PatternID.Other, - 0x10000 - (2 * 26) - 10 - nSp - nHi)); - } - } - } - - /// - /// Estimate the quality of a password. - /// - /// Password to check. - /// Estimated bit-strength of the password. - public static uint EstimatePasswordBits(char[] vPassword) - { - if(vPassword == null) { Debug.Assert(false); return 0; } - if(vPassword.Length == 0) return 0; - - EnsureInitialized(); - - int n = vPassword.Length; - List[] vPatterns = new List[n]; - for(int i = 0; i < n; ++i) - { - vPatterns[i] = new List(); - - QePatternInstance piChar = new QePatternInstance(i, 1, - GetCharType(vPassword[i])); - vPatterns[i].Add(piChar); - } - - FindRepetitions(vPassword, vPatterns); - FindNumbers(vPassword, vPatterns); - FindDiffSeqs(vPassword, vPatterns); - FindPopularPasswords(vPassword, vPatterns); - - // Encoders must not be static, because the entropy estimation - // may run concurrently in multiple threads and the encoders are - // not read-only - EntropyEncoder ecPattern = new EntropyEncoder(PatternID.All, 0, 1, 0); - MultiEntropyEncoder mcData = new MultiEntropyEncoder(); - for(int i = 0; i < (m_lCharTypes.Count - 1); ++i) - { - // Let m be the alphabet size. In order to ensure that two same - // characters cost at least as much as a single character, for - // the probability p and weight w of the character it must hold: - // -log(1/m) >= -2*log(p) - // <=> log(1/m) <= log(p^2) <=> 1/m <= p^2 <=> p >= sqrt(1/m); - // sqrt(1/m) = (1+w)/(m+w) - // <=> m+w = (1+w)*sqrt(m) <=> m+w = sqrt(m) + w*sqrt(m) - // <=> w*(1-sqrt(m)) = sqrt(m) - m <=> w = (sqrt(m)-m)/(1-sqrt(m)) - // <=> w = (sqrt(m)-m)*(1+sqrt(m))/(1-m) - // <=> w = (sqrt(m)-m+m-m*sqrt(m))/(1-m) <=> w = sqrt(m) - ulong uw = (ulong)Math.Sqrt((double)m_lCharTypes[i].CharCount); - - mcData.AddEncoder(m_lCharTypes[i].TypeID, new EntropyEncoder( - m_lCharTypes[i].Alphabet, 1, uw, 1)); - } - - double dblMinCost = (double)int.MaxValue; - int tStart = Environment.TickCount; - - Stack sRec = new Stack(); - sRec.Push(new QePathState(0, new List())); - while(sRec.Count > 0) - { - int tDiff = Environment.TickCount - tStart; - if(tDiff > 500) break; - - QePathState s = sRec.Pop(); - - if(s.Position >= n) - { - Debug.Assert(s.Position == n); - - double dblCost = ComputePathCost(s.Path, vPassword, - ecPattern, mcData); - if(dblCost < dblMinCost) dblMinCost = dblCost; - } - else - { - List lSubs = vPatterns[s.Position]; - for(int i = lSubs.Count - 1; i >= 0; --i) - { - QePatternInstance pi = lSubs[i]; - Debug.Assert(pi.Position == s.Position); - Debug.Assert(pi.Length >= 1); - - List lNewPath = - new List(s.Path.Count + 1); - lNewPath.AddRange(s.Path); - lNewPath.Add(pi); - Debug.Assert(lNewPath.Capacity == (s.Path.Count + 1)); - - QePathState sNew = new QePathState(s.Position + - pi.Length, lNewPath); - sRec.Push(sNew); - } - } - } - - return (uint)Math.Ceiling(dblMinCost); - } - - /// - /// Estimate the quality of a password. - /// - /// Password to check, UTF-8 encoded. - /// Estimated bit-strength of the password. - public static uint EstimatePasswordBits(byte[] pbUnprotectedUtf8) - { - if(pbUnprotectedUtf8 == null) { Debug.Assert(false); return 0; } - - char[] v = StrUtil.Utf8.GetChars(pbUnprotectedUtf8); - uint r; - try { r = EstimatePasswordBits(v); } - finally { MemUtil.ZeroArray(v); } - - return r; - } - - private static QeCharType GetCharType(char ch) - { - int nTypes = m_lCharTypes.Count; - Debug.Assert((nTypes > 0) && (m_lCharTypes[nTypes - 1].CharCount > 256)); - - for(int i = 0; i < (nTypes - 1); ++i) - { - if(m_lCharTypes[i].Contains(ch)) - return m_lCharTypes[i]; - } - - return m_lCharTypes[nTypes - 1]; - } - - private static double ComputePathCost(List l, - char[] vPassword, EntropyEncoder ecPattern, MultiEntropyEncoder mcData) - { - ecPattern.Reset(); - for(int i = 0; i < l.Count; ++i) - ecPattern.Write(l[i].PatternID); - double dblPatternCost = ecPattern.GetOutputSize(); - - mcData.Reset(); - double dblDataCost = 0.0; - foreach(QePatternInstance pi in l) - { - QeCharType tChar = pi.SingleCharType; - if(tChar != null) - { - char ch = vPassword[pi.Position]; - if(!mcData.Write(tChar.TypeID, ch)) - dblDataCost += pi.Cost; - } - else dblDataCost += pi.Cost; - } - dblDataCost += mcData.GetOutputSize(); - - return (dblPatternCost + dblDataCost); - } - - private static void FindPopularPasswords(char[] vPassword, - List[] vPatterns) - { - int n = vPassword.Length; - - char[] vLower = new char[n]; - char[] vLeet = new char[n]; - for(int i = 0; i < n; ++i) - { - char ch = vPassword[i]; - - vLower[i] = char.ToLower(ch); - vLeet[i] = char.ToLower(DecodeLeetChar(ch)); - } - - char chErased = default(char); // The value that Array.Clear uses - Debug.Assert(chErased == char.MinValue); - - int nMaxLen = Math.Min(n, PopularPasswords.MaxLength); - for(int nSubLen = nMaxLen; nSubLen >= 3; --nSubLen) - { - if(!PopularPasswords.ContainsLength(nSubLen)) continue; - - char[] vSub = new char[nSubLen]; - - for(int i = 0; i <= (n - nSubLen); ++i) - { - if(Array.IndexOf(vLower, chErased, i, nSubLen) >= 0) - continue; - - Array.Copy(vLower, i, vSub, 0, nSubLen); - if(!EvalAddPopularPasswordPattern(vPatterns, vPassword, - i, vSub, 0.0)) - { - Array.Copy(vLeet, i, vSub, 0, nSubLen); - if(EvalAddPopularPasswordPattern(vPatterns, vPassword, - i, vSub, 1.5)) - { - Array.Clear(vLower, i, nSubLen); // Not vLeet - Debug.Assert(vLower[i] == chErased); - } - } - else - { - Array.Clear(vLower, i, nSubLen); - Debug.Assert(vLower[i] == chErased); - } - } - - MemUtil.ZeroArray(vSub); - } - - MemUtil.ZeroArray(vLower); - MemUtil.ZeroArray(vLeet); - } - - private static bool EvalAddPopularPasswordPattern(List[] vPatterns, - char[] vPassword, int i, char[] vSub, double dblCostPerMod) - { - ulong uDictSize; - if(!PopularPasswords.IsPopularPassword(vSub, out uDictSize)) - return false; - - int n = vSub.Length; - int d = HammingDist(vSub, 0, vPassword, i, n); - - double dblCost = Log2((double)uDictSize); - - // dblCost += log2(n binom d) - int k = Math.Min(d, n - d); - for(int j = n; j > (n - k); --j) - dblCost += Log2(j); - for(int j = k; j >= 2; --j) - dblCost -= Log2(j); - - dblCost += dblCostPerMod * (double)d; - - vPatterns[i].Add(new QePatternInstance(i, n, PatternID.Dictionary, - dblCost)); - return true; - } - - private static char DecodeLeetChar(char chLeet) - { - if((chLeet >= '\u00C0') && (chLeet <= '\u00C6')) return 'a'; - if((chLeet >= '\u00C8') && (chLeet <= '\u00CB')) return 'e'; - if((chLeet >= '\u00CC') && (chLeet <= '\u00CF')) return 'i'; - if((chLeet >= '\u00D2') && (chLeet <= '\u00D6')) return 'o'; - if((chLeet >= '\u00D9') && (chLeet <= '\u00DC')) return 'u'; - if((chLeet >= '\u00E0') && (chLeet <= '\u00E6')) return 'a'; - if((chLeet >= '\u00E8') && (chLeet <= '\u00EB')) return 'e'; - if((chLeet >= '\u00EC') && (chLeet <= '\u00EF')) return 'i'; - if((chLeet >= '\u00F2') && (chLeet <= '\u00F6')) return 'o'; - if((chLeet >= '\u00F9') && (chLeet <= '\u00FC')) return 'u'; - - char ch; - switch(chLeet) - { - case '4': - case '@': - case '?': - case '^': - case '\u00AA': ch = 'a'; break; - case '8': - case '\u00DF': ch = 'b'; break; - case '(': - case '{': - case '[': - case '<': - case '\u00A2': - case '\u00A9': - case '\u00C7': - case '\u00E7': ch = 'c'; break; - case '\u00D0': - case '\u00F0': ch = 'd'; break; - case '3': - case '\u20AC': - case '&': - case '\u00A3': ch = 'e'; break; - case '6': - case '9': ch = 'g'; break; - case '#': ch = 'h'; break; - case '1': - case '!': - case '|': - case '\u00A1': - case '\u00A6': ch = 'i'; break; - case '\u00D1': - case '\u00F1': ch = 'n'; break; - case '0': - case '*': - case '\u00A4': // Currency - case '\u00B0': // Degree - case '\u00D8': - case '\u00F8': ch = 'o'; break; - case '\u00AE': ch = 'r'; break; - case '$': - case '5': - case '\u00A7': ch = 's'; break; - case '+': - case '7': ch = 't'; break; - case '\u00B5': ch = 'u'; break; - case '%': - case '\u00D7': ch = 'x'; break; - case '\u00A5': - case '\u00DD': - case '\u00FD': - case '\u00FF': ch = 'y'; break; - case '2': ch = 'z'; break; - default: ch = chLeet; break; - } - - return ch; - } - - private static int HammingDist(char[] v1, int iOffset1, - char[] v2, int iOffset2, int nLength) - { - int nDist = 0; - for(int i = 0; i < nLength; ++i) - { - if(v1[iOffset1 + i] != v2[iOffset2 + i]) ++nDist; - } - - return nDist; - } - - private static void FindRepetitions(char[] vPassword, - List[] vPatterns) - { - int n = vPassword.Length; - char[] v = new char[n]; - Array.Copy(vPassword, v, n); - - char chErased = char.MaxValue; - for(int m = (n / 2); m >= 3; --m) - { - for(int x1 = 0; x1 <= (n - (2 * m)); ++x1) - { - bool bFoundRep = false; - - for(int x2 = (x1 + m); x2 <= (n - m); ++x2) - { - if(PartsEqual(v, x1, x2, m)) - { - double dblCost = Log2(x1 + 1) + Log2(m); - vPatterns[x2].Add(new QePatternInstance(x2, m, - PatternID.Repetition, dblCost)); - - ErasePart(v, x2, m, ref chErased); - bFoundRep = true; - } - } - - if(bFoundRep) ErasePart(v, x1, m, ref chErased); - } - } - - MemUtil.ZeroArray(v); - } - - private static bool PartsEqual(char[] v, int x1, int x2, int nLength) - { - for(int i = 0; i < nLength; ++i) - { - if(v[x1 + i] != v[x2 + i]) return false; - } - - return true; - } - - private static void ErasePart(char[] v, int i, int n, ref char chErased) - { - for(int j = 0; j < n; ++j) - { - v[i + j] = chErased; - --chErased; - } - } - - private static void FindNumbers(char[] vPassword, - List[] vPatterns) - { - int n = vPassword.Length; - StringBuilder sb = new StringBuilder(); - - for(int i = 0; i < n; ++i) - { - char ch = vPassword[i]; - if((ch >= '0') && (ch <= '9')) sb.Append(ch); - else - { - AddNumberPattern(vPatterns, sb, i - sb.Length); - sb.Remove(0, sb.Length); - } - } - AddNumberPattern(vPatterns, sb, n - sb.Length); - } - - private static void AddNumberPattern(List[] vPatterns, - StringBuilder sb, int i) - { - if(sb.Length <= 2) return; - string strNumber = sb.ToString(); - - int nZeros = 0; - for(int j = 0; j < strNumber.Length; ++j) - { - if(strNumber[j] != '0') break; - ++nZeros; - } - - double dblCost = Log2(nZeros + 1); - if(nZeros < strNumber.Length) - { - string strNonZero = strNumber.Substring(nZeros); - -#if KeePassLibSD - try { dblCost += Log2(double.Parse(strNonZero)); } - catch(Exception) { Debug.Assert(false); return; } -#else - double d; - if(double.TryParse(strNonZero, out d)) - dblCost += Log2(d); - else { Debug.Assert(false); return; } -#endif - } - - vPatterns[i].Add(new QePatternInstance(i, strNumber.Length, - PatternID.Number, dblCost)); - } - - private static void FindDiffSeqs(char[] vPassword, - List[] vPatterns) - { - int n = vPassword.Length; - int d = int.MaxValue, p = 0; - - for(int i = 1; i <= n; ++i) - { - int dCur = ((i == n) ? int.MinValue : - ((int)vPassword[i] - (int)vPassword[i - 1])); - if(dCur != d) - { - if((i - p) >= 3) // At least 3 chars involved - { - QeCharType ct = GetCharType(vPassword[p]); - double dblCost = ct.CharSize + Log2(i - p - 1); - - vPatterns[p].Add(new QePatternInstance(p, - i - p, PatternID.DiffSeq, dblCost)); - } - - d = dCur; - p = i - 1; - } - } - } - - private static double Log2(double dblValue) - { -#if KeePassLibSD - return (Math.Log(dblValue) / Math.Log(2.0)); -#else - return Math.Log(dblValue, 2.0); -#endif - } - } -} diff --git a/ModernKeePassLib/Cryptography/SelfTest.cs b/ModernKeePassLib/Cryptography/SelfTest.cs deleted file mode 100644 index d8ecc26..0000000 --- a/ModernKeePassLib/Cryptography/SelfTest.cs +++ /dev/null @@ -1,1141 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Globalization; -using System.IO; -using System.Runtime.InteropServices; -using System.Security; -using System.Text; - -#if ModernKeePassLib || KeePassUAP -using Org.BouncyCastle.Crypto; -using Org.BouncyCastle.Crypto.Engines; -using Org.BouncyCastle.Crypto.Parameters; -#else -using System.Security.Cryptography; -#endif - -using ModernKeePassLib.Cryptography.Cipher; -using ModernKeePassLib.Cryptography.Hash; -using ModernKeePassLib.Cryptography.KeyDerivation; -using ModernKeePassLib.Keys; -using ModernKeePassLib.Native; -using ModernKeePassLib.Resources; -using ModernKeePassLib.Security; -using ModernKeePassLib.Utility; - -namespace ModernKeePassLib.Cryptography -{ - /// - /// Class containing self-test methods. - /// - // TODO: move all this into the Unit Tests project - public static class SelfTest - { - /// - /// Perform a self-test. - /// - public static void Perform() - { -#if KeePassUAP - Debug.Assert(Marshal.SizeOf() == 4); - Debug.Assert(Marshal.SizeOf() == 4); - Debug.Assert(Marshal.SizeOf() == 8); - Debug.Assert(Marshal.SizeOf() == 8); - Debug.Assert(Marshal.SizeOf() == IntPtr.Size); -#else - Debug.Assert(Marshal.SizeOf(typeof(int)) == 4); - Debug.Assert(Marshal.SizeOf(typeof(uint)) == 4); - Debug.Assert(Marshal.SizeOf(typeof(long)) == 8); - Debug.Assert(Marshal.SizeOf(typeof(ulong)) == 8); - Debug.Assert(Marshal.SizeOf(typeof(IntPtr)) == IntPtr.Size); -#endif - Debug.Assert((IntPtr.Size == 4) || (IntPtr.Size == 8)); - - Debug.Assert((int)PwIcon.World == 1); - Debug.Assert((int)PwIcon.Warning == 2); - Debug.Assert((int)PwIcon.BlackBerry == 68); - - Random r = CryptoRandom.NewWeakRandom(); - - TestFipsComplianceProblems(); // Must be the first test - - TestAes(); - TestSalsa20(r); - TestChaCha20(r); - TestBlake2b(r); - TestArgon2(); - TestHmac(); - - TestKeyTransform(r); - TestNativeKeyTransform(r); - - TestHmacOtp(); - - TestProtectedObjects(r); - TestMemUtil(r); - TestStrUtil(); - TestUrlUtil(); - -#if KeePassUAP - SelfTestEx.Perform(); -#endif - } - - internal static void TestFipsComplianceProblems() - { -#if !ModernKeePassLib - try { using(RijndaelManaged r = new RijndaelManaged()) { } } - catch(Exception exAes) - { - throw new SecurityException("AES/Rijndael: " + exAes.Message); - } -#endif - - try { using(SHA256Managed h = new SHA256Managed()) { } } - catch(Exception exSha256) - { - throw new SecurityException("SHA-256: " + exSha256.Message); - } - } - - private static void TestAes() - { - // Test vector (official ECB test vector #356) - byte[] pbIV = new byte[16]; - byte[] pbTestKey = new byte[32]; - byte[] pbTestData = new byte[16]; - byte[] pbReferenceCT = new byte[16] { - 0x75, 0xD1, 0x1B, 0x0E, 0x3A, 0x68, 0xC4, 0x22, - 0x3D, 0x88, 0xDB, 0xF0, 0x17, 0x97, 0x7D, 0xD7 }; - int i; - - for(i = 0; i < 16; ++i) pbIV[i] = 0; - for(i = 0; i < 32; ++i) pbTestKey[i] = 0; - for(i = 0; i < 16; ++i) pbTestData[i] = 0; - pbTestData[0] = 0x04; - -#if ModernKeePassLib || KeePassUAP - AesEngine r = new AesEngine(); - r.Init(true, new KeyParameter(pbTestKey)); - if(r.GetBlockSize() != pbTestData.Length) - throw new SecurityException("AES (BC)"); - r.ProcessBlock(pbTestData, 0, pbTestData, 0); -#else - SymmetricAlgorithm a = CryptoUtil.CreateAes(); - if(a.BlockSize != 128) // AES block size - { - Debug.Assert(false); - a.BlockSize = 128; - } - - a.IV = pbIV; - a.KeySize = 256; - a.Key = pbTestKey; - a.Mode = CipherMode.ECB; - ICryptoTransform iCrypt = a.CreateEncryptor(); - - iCrypt.TransformBlock(pbTestData, 0, 16, pbTestData, 0); -#endif - - if(!MemUtil.ArraysEqual(pbTestData, pbReferenceCT)) - throw new SecurityException("AES"); - } - - private static void TestSalsa20(Random r) - { -#if DEBUG - // Test values from official set 6, vector 3 - byte[] pbKey = new byte[32] { - 0x0F, 0x62, 0xB5, 0x08, 0x5B, 0xAE, 0x01, 0x54, - 0xA7, 0xFA, 0x4D, 0xA0, 0xF3, 0x46, 0x99, 0xEC, - 0x3F, 0x92, 0xE5, 0x38, 0x8B, 0xDE, 0x31, 0x84, - 0xD7, 0x2A, 0x7D, 0xD0, 0x23, 0x76, 0xC9, 0x1C - }; - byte[] pbIV = new byte[8] { 0x28, 0x8F, 0xF6, 0x5D, - 0xC4, 0x2B, 0x92, 0xF9 }; - byte[] pbExpected = new byte[16] { - 0x5E, 0x5E, 0x71, 0xF9, 0x01, 0x99, 0x34, 0x03, - 0x04, 0xAB, 0xB2, 0x2A, 0x37, 0xB6, 0x62, 0x5B - }; - - byte[] pb = new byte[16]; - Salsa20Cipher c = new Salsa20Cipher(pbKey, pbIV); - c.Encrypt(pb, 0, pb.Length); - if(!MemUtil.ArraysEqual(pb, pbExpected)) - throw new SecurityException("Salsa20-1"); - - // Extended test - byte[] pbExpected2 = new byte[16] { - 0xAB, 0xF3, 0x9A, 0x21, 0x0E, 0xEE, 0x89, 0x59, - 0x8B, 0x71, 0x33, 0x37, 0x70, 0x56, 0xC2, 0xFE - }; - byte[] pbExpected3 = new byte[16] { - 0x1B, 0xA8, 0x9D, 0xBD, 0x3F, 0x98, 0x83, 0x97, - 0x28, 0xF5, 0x67, 0x91, 0xD5, 0xB7, 0xCE, 0x23 - }; - - int nPos = Salsa20ToPos(c, r, pb.Length, 65536); - Array.Clear(pb, 0, pb.Length); - c.Encrypt(pb, 0, pb.Length); - if(!MemUtil.ArraysEqual(pb, pbExpected2)) - throw new SecurityException("Salsa20-2"); - - nPos = Salsa20ToPos(c, r, nPos + pb.Length, 131008); - Array.Clear(pb, 0, pb.Length); - c.Encrypt(pb, 0, pb.Length); - if(!MemUtil.ArraysEqual(pb, pbExpected3)) - throw new SecurityException("Salsa20-3"); - - Dictionary d = new Dictionary(); - const int nRounds = 100; - for(int i = 0; i < nRounds; ++i) - { - byte[] z = new byte[32]; - c = new Salsa20Cipher(z, MemUtil.Int64ToBytes(i)); - c.Encrypt(z, 0, z.Length); - d[MemUtil.ByteArrayToHexString(z)] = true; - } - if(d.Count != nRounds) throw new SecurityException("Salsa20-4"); -#endif - } - -#if DEBUG - private static int Salsa20ToPos(Salsa20Cipher c, Random r, int nPos, - int nTargetPos) - { - byte[] pb = new byte[512]; - - while(nPos < nTargetPos) - { - int x = r.Next(1, 513); - int nGen = Math.Min(nTargetPos - nPos, x); - c.Encrypt(pb, 0, nGen); - nPos += nGen; - } - - return nTargetPos; - } -#endif - - private static void TestChaCha20(Random r) - { - // ====================================================== - // Test vector from RFC 7539, section 2.3.2 - - byte[] pbKey = new byte[32]; - for(int i = 0; i < 32; ++i) pbKey[i] = (byte)i; - - byte[] pbIV = new byte[12]; - pbIV[3] = 0x09; - pbIV[7] = 0x4A; - - byte[] pbExpc = new byte[64] { - 0x10, 0xF1, 0xE7, 0xE4, 0xD1, 0x3B, 0x59, 0x15, - 0x50, 0x0F, 0xDD, 0x1F, 0xA3, 0x20, 0x71, 0xC4, - 0xC7, 0xD1, 0xF4, 0xC7, 0x33, 0xC0, 0x68, 0x03, - 0x04, 0x22, 0xAA, 0x9A, 0xC3, 0xD4, 0x6C, 0x4E, - 0xD2, 0x82, 0x64, 0x46, 0x07, 0x9F, 0xAA, 0x09, - 0x14, 0xC2, 0xD7, 0x05, 0xD9, 0x8B, 0x02, 0xA2, - 0xB5, 0x12, 0x9C, 0xD1, 0xDE, 0x16, 0x4E, 0xB9, - 0xCB, 0xD0, 0x83, 0xE8, 0xA2, 0x50, 0x3C, 0x4E - }; - - byte[] pb = new byte[64]; - - using(ChaCha20Cipher c = new ChaCha20Cipher(pbKey, pbIV)) - { - c.Seek(64, SeekOrigin.Begin); // Skip first block - c.Encrypt(pb, 0, pb.Length); - - if(!MemUtil.ArraysEqual(pb, pbExpc)) - throw new SecurityException("ChaCha20-1"); - } - -#if DEBUG - // ====================================================== - // Test vector from RFC 7539, section 2.4.2 - - pbIV[3] = 0; - - pb = StrUtil.Utf8.GetBytes("Ladies and Gentlemen of the clas" + - @"s of '99: If I could offer you only one tip for " + - @"the future, sunscreen would be it."); - - pbExpc = new byte[] { - 0x6E, 0x2E, 0x35, 0x9A, 0x25, 0x68, 0xF9, 0x80, - 0x41, 0xBA, 0x07, 0x28, 0xDD, 0x0D, 0x69, 0x81, - 0xE9, 0x7E, 0x7A, 0xEC, 0x1D, 0x43, 0x60, 0xC2, - 0x0A, 0x27, 0xAF, 0xCC, 0xFD, 0x9F, 0xAE, 0x0B, - 0xF9, 0x1B, 0x65, 0xC5, 0x52, 0x47, 0x33, 0xAB, - 0x8F, 0x59, 0x3D, 0xAB, 0xCD, 0x62, 0xB3, 0x57, - 0x16, 0x39, 0xD6, 0x24, 0xE6, 0x51, 0x52, 0xAB, - 0x8F, 0x53, 0x0C, 0x35, 0x9F, 0x08, 0x61, 0xD8, - 0x07, 0xCA, 0x0D, 0xBF, 0x50, 0x0D, 0x6A, 0x61, - 0x56, 0xA3, 0x8E, 0x08, 0x8A, 0x22, 0xB6, 0x5E, - 0x52, 0xBC, 0x51, 0x4D, 0x16, 0xCC, 0xF8, 0x06, - 0x81, 0x8C, 0xE9, 0x1A, 0xB7, 0x79, 0x37, 0x36, - 0x5A, 0xF9, 0x0B, 0xBF, 0x74, 0xA3, 0x5B, 0xE6, - 0xB4, 0x0B, 0x8E, 0xED, 0xF2, 0x78, 0x5E, 0x42, - 0x87, 0x4D - }; - - byte[] pb64 = new byte[64]; - - using(ChaCha20Cipher c = new ChaCha20Cipher(pbKey, pbIV)) - { - c.Encrypt(pb64, 0, pb64.Length); // Skip first block - c.Encrypt(pb, 0, pb.Length); - - if(!MemUtil.ArraysEqual(pb, pbExpc)) - throw new SecurityException("ChaCha20-2"); - } - - // ====================================================== - // Test vector from RFC 7539, appendix A.2 #2 - - Array.Clear(pbKey, 0, pbKey.Length); - pbKey[31] = 1; - - Array.Clear(pbIV, 0, pbIV.Length); - pbIV[11] = 2; - - pb = StrUtil.Utf8.GetBytes("Any submission to the IETF inten" + - "ded by the Contributor for publication as all or" + - " part of an IETF Internet-Draft or RFC and any s" + - "tatement made within the context of an IETF acti" + - "vity is considered an \"IETF Contribution\". Such " + - "statements include oral statements in IETF sessi" + - "ons, as well as written and electronic communica" + - "tions made at any time or place, which are addressed to"); - - pbExpc = MemUtil.HexStringToByteArray( - "A3FBF07DF3FA2FDE4F376CA23E82737041605D9F4F4F57BD8CFF2C1D4B7955EC" + - "2A97948BD3722915C8F3D337F7D370050E9E96D647B7C39F56E031CA5EB6250D" + - "4042E02785ECECFA4B4BB5E8EAD0440E20B6E8DB09D881A7C6132F420E527950" + - "42BDFA7773D8A9051447B3291CE1411C680465552AA6C405B7764D5E87BEA85A" + - "D00F8449ED8F72D0D662AB052691CA66424BC86D2DF80EA41F43ABF937D3259D" + - "C4B2D0DFB48A6C9139DDD7F76966E928E635553BA76C5C879D7B35D49EB2E62B" + - "0871CDAC638939E25E8A1E0EF9D5280FA8CA328B351C3C765989CBCF3DAA8B6C" + - "CC3AAF9F3979C92B3720FC88DC95ED84A1BE059C6499B9FDA236E7E818B04B0B" + - "C39C1E876B193BFE5569753F88128CC08AAA9B63D1A16F80EF2554D7189C411F" + - "5869CA52C5B83FA36FF216B9C1D30062BEBCFD2DC5BCE0911934FDA79A86F6E6" + - "98CED759C3FF9B6477338F3DA4F9CD8514EA9982CCAFB341B2384DD902F3D1AB" + - "7AC61DD29C6F21BA5B862F3730E37CFDC4FD806C22F221"); - - using(MemoryStream msEnc = new MemoryStream()) - { - using(ChaCha20Stream c = new ChaCha20Stream(msEnc, true, pbKey, pbIV)) - { - r.NextBytes(pb64); - c.Write(pb64, 0, pb64.Length); // Skip first block - - int p = 0; - while(p < pb.Length) - { - int cb = r.Next(1, pb.Length - p + 1); - c.Write(pb, p, cb); - p += cb; - } - Debug.Assert(p == pb.Length); - } - - byte[] pbEnc0 = msEnc.ToArray(); - byte[] pbEnc = MemUtil.Mid(pbEnc0, 64, pbEnc0.Length - 64); - if(!MemUtil.ArraysEqual(pbEnc, pbExpc)) - throw new SecurityException("ChaCha20-3"); - - using(MemoryStream msCT = new MemoryStream(pbEnc0, false)) - { - using(ChaCha20Stream cDec = new ChaCha20Stream(msCT, false, - pbKey, pbIV)) - { - byte[] pbPT = MemUtil.Read(cDec, pbEnc0.Length); - if(cDec.ReadByte() >= 0) - throw new SecurityException("ChaCha20-4"); - if(!MemUtil.ArraysEqual(MemUtil.Mid(pbPT, 0, 64), pb64)) - throw new SecurityException("ChaCha20-5"); - if(!MemUtil.ArraysEqual(MemUtil.Mid(pbPT, 64, pbEnc.Length), pb)) - throw new SecurityException("ChaCha20-6"); - } - } - } - - // ====================================================== - // Test vector TC8 from RFC draft by J. Strombergson: - // https://tools.ietf.org/html/draft-strombergson-chacha-test-vectors-01 - - pbKey = new byte[32] { - 0xC4, 0x6E, 0xC1, 0xB1, 0x8C, 0xE8, 0xA8, 0x78, - 0x72, 0x5A, 0x37, 0xE7, 0x80, 0xDF, 0xB7, 0x35, - 0x1F, 0x68, 0xED, 0x2E, 0x19, 0x4C, 0x79, 0xFB, - 0xC6, 0xAE, 0xBE, 0xE1, 0xA6, 0x67, 0x97, 0x5D - }; - - // The first 4 bytes are set to zero and a large counter - // is used; this makes the RFC 7539 version of ChaCha20 - // compatible with the original specification by - // D. J. Bernstein. - pbIV = new byte[12] { 0x00, 0x00, 0x00, 0x00, - 0x1A, 0xDA, 0x31, 0xD5, 0xCF, 0x68, 0x82, 0x21 - }; - - pb = new byte[128]; - - pbExpc = new byte[128] { - 0xF6, 0x3A, 0x89, 0xB7, 0x5C, 0x22, 0x71, 0xF9, - 0x36, 0x88, 0x16, 0x54, 0x2B, 0xA5, 0x2F, 0x06, - 0xED, 0x49, 0x24, 0x17, 0x92, 0x30, 0x2B, 0x00, - 0xB5, 0xE8, 0xF8, 0x0A, 0xE9, 0xA4, 0x73, 0xAF, - 0xC2, 0x5B, 0x21, 0x8F, 0x51, 0x9A, 0xF0, 0xFD, - 0xD4, 0x06, 0x36, 0x2E, 0x8D, 0x69, 0xDE, 0x7F, - 0x54, 0xC6, 0x04, 0xA6, 0xE0, 0x0F, 0x35, 0x3F, - 0x11, 0x0F, 0x77, 0x1B, 0xDC, 0xA8, 0xAB, 0x92, - - 0xE5, 0xFB, 0xC3, 0x4E, 0x60, 0xA1, 0xD9, 0xA9, - 0xDB, 0x17, 0x34, 0x5B, 0x0A, 0x40, 0x27, 0x36, - 0x85, 0x3B, 0xF9, 0x10, 0xB0, 0x60, 0xBD, 0xF1, - 0xF8, 0x97, 0xB6, 0x29, 0x0F, 0x01, 0xD1, 0x38, - 0xAE, 0x2C, 0x4C, 0x90, 0x22, 0x5B, 0xA9, 0xEA, - 0x14, 0xD5, 0x18, 0xF5, 0x59, 0x29, 0xDE, 0xA0, - 0x98, 0xCA, 0x7A, 0x6C, 0xCF, 0xE6, 0x12, 0x27, - 0x05, 0x3C, 0x84, 0xE4, 0x9A, 0x4A, 0x33, 0x32 - }; - - using(ChaCha20Cipher c = new ChaCha20Cipher(pbKey, pbIV, true)) - { - c.Decrypt(pb, 0, pb.Length); - - if(!MemUtil.ArraysEqual(pb, pbExpc)) - throw new SecurityException("ChaCha20-7"); - } -#endif - } - - private static void TestBlake2b(Random r) - { -#if DEBUG - Blake2b h = new Blake2b(); - - // ====================================================== - // From https://tools.ietf.org/html/rfc7693 - - byte[] pbData = StrUtil.Utf8.GetBytes("abc"); - byte[] pbExpc = new byte[64] { - 0xBA, 0x80, 0xA5, 0x3F, 0x98, 0x1C, 0x4D, 0x0D, - 0x6A, 0x27, 0x97, 0xB6, 0x9F, 0x12, 0xF6, 0xE9, - 0x4C, 0x21, 0x2F, 0x14, 0x68, 0x5A, 0xC4, 0xB7, - 0x4B, 0x12, 0xBB, 0x6F, 0xDB, 0xFF, 0xA2, 0xD1, - 0x7D, 0x87, 0xC5, 0x39, 0x2A, 0xAB, 0x79, 0x2D, - 0xC2, 0x52, 0xD5, 0xDE, 0x45, 0x33, 0xCC, 0x95, - 0x18, 0xD3, 0x8A, 0xA8, 0xDB, 0xF1, 0x92, 0x5A, - 0xB9, 0x23, 0x86, 0xED, 0xD4, 0x00, 0x99, 0x23 - }; - - byte[] pbC = h.ComputeHash(pbData); - if(!MemUtil.ArraysEqual(pbC, pbExpc)) - throw new SecurityException("Blake2b-1"); - - // ====================================================== - // Computed using the official b2sum tool - - pbExpc = new byte[64] { - 0x78, 0x6A, 0x02, 0xF7, 0x42, 0x01, 0x59, 0x03, - 0xC6, 0xC6, 0xFD, 0x85, 0x25, 0x52, 0xD2, 0x72, - 0x91, 0x2F, 0x47, 0x40, 0xE1, 0x58, 0x47, 0x61, - 0x8A, 0x86, 0xE2, 0x17, 0xF7, 0x1F, 0x54, 0x19, - 0xD2, 0x5E, 0x10, 0x31, 0xAF, 0xEE, 0x58, 0x53, - 0x13, 0x89, 0x64, 0x44, 0x93, 0x4E, 0xB0, 0x4B, - 0x90, 0x3A, 0x68, 0x5B, 0x14, 0x48, 0xB7, 0x55, - 0xD5, 0x6F, 0x70, 0x1A, 0xFE, 0x9B, 0xE2, 0xCE - }; - - pbC = h.ComputeHash(MemUtil.EmptyByteArray); - if(!MemUtil.ArraysEqual(pbC, pbExpc)) - throw new SecurityException("Blake2b-2"); - - // ====================================================== - // Computed using the official b2sum tool - - string strS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.:,;_-\r\n"; - StringBuilder sb = new StringBuilder(); - for(int i = 0; i < 1000; ++i) sb.Append(strS); - pbData = StrUtil.Utf8.GetBytes(sb.ToString()); - - pbExpc = new byte[64] { - 0x59, 0x69, 0x8D, 0x3B, 0x83, 0xF4, 0x02, 0x4E, - 0xD8, 0x99, 0x26, 0x0E, 0xF4, 0xE5, 0x9F, 0x20, - 0xDC, 0x31, 0xEE, 0x5B, 0x45, 0xEA, 0xBB, 0xFC, - 0x1C, 0x0A, 0x8E, 0xED, 0xAA, 0x7A, 0xFF, 0x50, - 0x82, 0xA5, 0x8F, 0xBC, 0x4A, 0x46, 0xFC, 0xC5, - 0xEF, 0x44, 0x4E, 0x89, 0x80, 0x7D, 0x3F, 0x1C, - 0xC1, 0x94, 0x45, 0xBB, 0xC0, 0x2C, 0x95, 0xAA, - 0x3F, 0x08, 0x8A, 0x93, 0xF8, 0x75, 0x91, 0xB0 - }; - - int p = 0; - while(p < pbData.Length) - { - int cb = r.Next(1, pbData.Length - p + 1); - h.TransformBlock(pbData, p, cb, pbData, p); - p += cb; - } - Debug.Assert(p == pbData.Length); - - h.TransformFinalBlock(MemUtil.EmptyByteArray, 0, 0); - - if(!MemUtil.ArraysEqual(h.Hash, pbExpc)) - throw new SecurityException("Blake2b-3"); - - h.Clear(); -#endif - } - - private static void TestArgon2() - { -#if DEBUG - Argon2Kdf kdf = new Argon2Kdf(); - - // ====================================================== - // From the official Argon2 1.3 reference code package - // (test vector for Argon2d 1.3); also on - // https://tools.ietf.org/html/draft-irtf-cfrg-argon2-00 - - var p = kdf.GetDefaultParameters(); - kdf.Randomize(p); - - Debug.Assert(p.GetUInt32(Argon2Kdf.ParamVersion, 0) == 0x13U); - - byte[] pbMsg = new byte[32]; - for(int i = 0; i < pbMsg.Length; ++i) pbMsg[i] = 1; - - p.SetUInt64(Argon2Kdf.ParamMemory, 32 * 1024); - p.SetUInt64(Argon2Kdf.ParamIterations, 3); - p.SetUInt32(Argon2Kdf.ParamParallelism, 4); - - byte[] pbSalt = new byte[16]; - for(int i = 0; i < pbSalt.Length; ++i) pbSalt[i] = 2; - p.SetByteArray(Argon2Kdf.ParamSalt, pbSalt); - - byte[] pbKey = new byte[8]; - for(int i = 0; i < pbKey.Length; ++i) pbKey[i] = 3; - p.SetByteArray(Argon2Kdf.ParamSecretKey, pbKey); - - byte[] pbAssoc = new byte[12]; - for(int i = 0; i < pbAssoc.Length; ++i) pbAssoc[i] = 4; - p.SetByteArray(Argon2Kdf.ParamAssocData, pbAssoc); - - byte[] pbExpc = new byte[32] { - 0x51, 0x2B, 0x39, 0x1B, 0x6F, 0x11, 0x62, 0x97, - 0x53, 0x71, 0xD3, 0x09, 0x19, 0x73, 0x42, 0x94, - 0xF8, 0x68, 0xE3, 0xBE, 0x39, 0x84, 0xF3, 0xC1, - 0xA1, 0x3A, 0x4D, 0xB9, 0xFA, 0xBE, 0x4A, 0xCB - }; - - byte[] pb = kdf.Transform(pbMsg, p); - - if(!MemUtil.ArraysEqual(pb, pbExpc)) - throw new SecurityException("Argon2-1"); - - // ====================================================== - // From the official Argon2 1.3 reference code package - // (test vector for Argon2d 1.0) - - p.SetUInt32(Argon2Kdf.ParamVersion, 0x10); - - pbExpc = new byte[32] { - 0x96, 0xA9, 0xD4, 0xE5, 0xA1, 0x73, 0x40, 0x92, - 0xC8, 0x5E, 0x29, 0xF4, 0x10, 0xA4, 0x59, 0x14, - 0xA5, 0xDD, 0x1F, 0x5C, 0xBF, 0x08, 0xB2, 0x67, - 0x0D, 0xA6, 0x8A, 0x02, 0x85, 0xAB, 0xF3, 0x2B - }; - - pb = kdf.Transform(pbMsg, p); - - if(!MemUtil.ArraysEqual(pb, pbExpc)) - throw new SecurityException("Argon2-2"); - - // ====================================================== - // From the official 'phc-winner-argon2-20151206.zip' - // (test vector for Argon2d 1.0) - - p.SetUInt64(Argon2Kdf.ParamMemory, 16 * 1024); - - pbExpc = new byte[32] { - 0x57, 0xB0, 0x61, 0x3B, 0xFD, 0xD4, 0x13, 0x1A, - 0x0C, 0x34, 0x88, 0x34, 0xC6, 0x72, 0x9C, 0x2C, - 0x72, 0x29, 0x92, 0x1E, 0x6B, 0xBA, 0x37, 0x66, - 0x5D, 0x97, 0x8C, 0x4F, 0xE7, 0x17, 0x5E, 0xD2 - }; - - pb = kdf.Transform(pbMsg, p); - - if(!MemUtil.ArraysEqual(pb, pbExpc)) - throw new SecurityException("Argon2-3"); - -#if SELFTEST_ARGON2_LONG - // ====================================================== - // Computed using the official 'argon2' application - // (test vectors for Argon2d 1.3) - - p = kdf.GetDefaultParameters(); - - pbMsg = StrUtil.Utf8.GetBytes("ABC1234"); - - p.SetUInt64(Argon2Kdf.ParamMemory, (1 << 11) * 1024); // 2 MB - p.SetUInt64(Argon2Kdf.ParamIterations, 2); - p.SetUInt32(Argon2Kdf.ParamParallelism, 2); - - pbSalt = StrUtil.Utf8.GetBytes("somesalt"); - p.SetByteArray(Argon2Kdf.ParamSalt, pbSalt); - - pbExpc = new byte[32] { - 0x29, 0xCB, 0xD3, 0xA1, 0x93, 0x76, 0xF7, 0xA2, - 0xFC, 0xDF, 0xB0, 0x68, 0xAC, 0x0B, 0x99, 0xBA, - 0x40, 0xAC, 0x09, 0x01, 0x73, 0x42, 0xCE, 0xF1, - 0x29, 0xCC, 0xA1, 0x4F, 0xE1, 0xC1, 0xB7, 0xA3 - }; - - pb = kdf.Transform(pbMsg, p); - - if(!MemUtil.ArraysEqual(pb, pbExpc)) - throw new SecurityException("Argon2-4"); - - p.SetUInt64(Argon2Kdf.ParamMemory, (1 << 10) * 1024); // 1 MB - p.SetUInt64(Argon2Kdf.ParamIterations, 3); - - pbExpc = new byte[32] { - 0x7A, 0xBE, 0x1C, 0x1C, 0x8D, 0x7F, 0xD6, 0xDC, - 0x7C, 0x94, 0x06, 0x3E, 0xD8, 0xBC, 0xD8, 0x1C, - 0x2F, 0x87, 0x84, 0x99, 0x12, 0x83, 0xFE, 0x76, - 0x00, 0x64, 0xC4, 0x58, 0xA4, 0xDA, 0x35, 0x70 - }; - - pb = kdf.Transform(pbMsg, p); - - if(!MemUtil.ArraysEqual(pb, pbExpc)) - throw new SecurityException("Argon2-5"); - -#if SELFTEST_ARGON2_LONGER - p.SetUInt64(Argon2Kdf.ParamMemory, (1 << 20) * 1024); // 1 GB - p.SetUInt64(Argon2Kdf.ParamIterations, 2); - p.SetUInt32(Argon2Kdf.ParamParallelism, 3); - - pbExpc = new byte[32] { - 0xE6, 0xE7, 0xCB, 0xF5, 0x5A, 0x06, 0x93, 0x05, - 0x32, 0xBA, 0x86, 0xC6, 0x1F, 0x45, 0x17, 0x99, - 0x65, 0x41, 0x77, 0xF9, 0x30, 0x55, 0x9A, 0xE8, - 0x3D, 0x21, 0x48, 0xC6, 0x2D, 0x0C, 0x49, 0x11 - }; - - pb = kdf.Transform(pbMsg, p); - - if(!MemUtil.ArraysEqual(pb, pbExpc)) - throw new SecurityException("Argon2-6"); -#endif // SELFTEST_ARGON2_LONGER -#endif // SELFTEST_ARGON2_LONG -#endif // DEBUG - } - - private static void TestHmac() - { -#if DEBUG - // Test vectors from RFC 4231 - - byte[] pbKey = new byte[20]; - for(int i = 0; i < pbKey.Length; ++i) pbKey[i] = 0x0B; - byte[] pbMsg = StrUtil.Utf8.GetBytes("Hi There"); - byte[] pbExpc = new byte[32] { - 0xB0, 0x34, 0x4C, 0x61, 0xD8, 0xDB, 0x38, 0x53, - 0x5C, 0xA8, 0xAF, 0xCE, 0xAF, 0x0B, 0xF1, 0x2B, - 0x88, 0x1D, 0xC2, 0x00, 0xC9, 0x83, 0x3D, 0xA7, - 0x26, 0xE9, 0x37, 0x6C, 0x2E, 0x32, 0xCF, 0xF7 - }; - HmacEval(pbKey, pbMsg, pbExpc, "1"); - - pbKey = new byte[131]; - for(int i = 0; i < pbKey.Length; ++i) pbKey[i] = 0xAA; - pbMsg = StrUtil.Utf8.GetBytes( - "This is a test using a larger than block-size key and " + - "a larger than block-size data. The key needs to be " + - "hashed before being used by the HMAC algorithm."); - pbExpc = new byte[32] { - 0x9B, 0x09, 0xFF, 0xA7, 0x1B, 0x94, 0x2F, 0xCB, - 0x27, 0x63, 0x5F, 0xBC, 0xD5, 0xB0, 0xE9, 0x44, - 0xBF, 0xDC, 0x63, 0x64, 0x4F, 0x07, 0x13, 0x93, - 0x8A, 0x7F, 0x51, 0x53, 0x5C, 0x3A, 0x35, 0xE2 - }; - HmacEval(pbKey, pbMsg, pbExpc, "2"); -#endif - } - -#if DEBUG - private static void HmacEval(byte[] pbKey, byte[] pbMsg, - byte[] pbExpc, string strID) - { - using(HMACSHA256 h = new HMACSHA256(pbKey)) - { - h.TransformBlock(pbMsg, 0, pbMsg.Length, pbMsg, 0); - h.TransformFinalBlock(MemUtil.EmptyByteArray, 0, 0); - - byte[] pbHash = h.Hash; - if(!MemUtil.ArraysEqual(pbHash, pbExpc)) - throw new SecurityException("HMAC-SHA-256-" + strID); - - // Reuse the object - h.Initialize(); - h.TransformBlock(pbMsg, 0, pbMsg.Length, pbMsg, 0); - h.TransformFinalBlock(MemUtil.EmptyByteArray, 0, 0); - - pbHash = h.Hash; - if(!MemUtil.ArraysEqual(pbHash, pbExpc)) - throw new SecurityException("HMAC-SHA-256-" + strID + "-R"); - } - } -#endif - - private static void TestKeyTransform(Random r) - { -#if DEBUG - // Up to KeePass 2.34, the OtpKeyProv plugin used the public - // CompositeKey.TransformKeyManaged method (and a finalizing - // SHA-256 computation), which became an internal method of - // the AesKdf class in KeePass 2.35, thus OtpKeyProv now - // uses the AesKdf class; here we ensure that the results - // are the same - - byte[] pbKey = new byte[32]; - r.NextBytes(pbKey); - byte[] pbSeed = new byte[32]; - r.NextBytes(pbSeed); - ulong uRounds = (ulong)r.Next(1, 0x7FFF); - - byte[] pbMan = new byte[pbKey.Length]; - Array.Copy(pbKey, pbMan, pbKey.Length); - if(!AesKdf.TransformKeyManaged(pbMan, pbSeed, uRounds)) - throw new SecurityException("AES-KDF-1"); - pbMan = CryptoUtil.HashSha256(pbMan); - - AesKdf kdf = new AesKdf(); - var p = kdf.GetDefaultParameters(); - p.SetUInt64(AesKdf.ParamRounds, uRounds); - p.SetByteArray(AesKdf.ParamSeed, pbSeed); - byte[] pbKdf = kdf.Transform(pbKey, p); - - if(!MemUtil.ArraysEqual(pbMan, pbKdf)) - throw new SecurityException("AES-KDF-2"); -#endif - } - - private static void TestNativeKeyTransform(Random r) - { -#if !ModernKeePassLib && DEBUG - byte[] pbOrgKey = CryptoRandom.Instance.GetRandomBytes(32); - byte[] pbSeed = CryptoRandom.Instance.GetRandomBytes(32); - ulong uRounds = (ulong)r.Next(1, 0x3FFF); - - byte[] pbManaged = new byte[32]; - Array.Copy(pbOrgKey, pbManaged, 32); - if(!AesKdf.TransformKeyManaged(pbManaged, pbSeed, uRounds)) - throw new SecurityException("AES-KDF-1"); - - byte[] pbNative = new byte[32]; - Array.Copy(pbOrgKey, pbNative, 32); - if(!NativeLib.TransformKey256(pbNative, pbSeed, uRounds)) - return; // Native library not available ("success") - - if(!MemUtil.ArraysEqual(pbManaged, pbNative)) - throw new SecurityException("AES-KDF-2"); -#endif - } - - private static void TestMemUtil(Random r) - { -#if DEBUG - byte[] pb = CryptoRandom.Instance.GetRandomBytes((uint)r.Next( - 0, 0x2FFFF)); - - byte[] pbCompressed = MemUtil.Compress(pb); - if(!MemUtil.ArraysEqual(MemUtil.Decompress(pbCompressed), pb)) - throw new InvalidOperationException("GZip"); - - Encoding enc = StrUtil.Utf8; - pb = enc.GetBytes("012345678901234567890a"); - byte[] pbN = enc.GetBytes("9012"); - if(MemUtil.IndexOf(pb, pbN) != 9) - throw new InvalidOperationException("MemUtil-1"); - pbN = enc.GetBytes("01234567890123"); - if(MemUtil.IndexOf(pb, pbN) != 0) - throw new InvalidOperationException("MemUtil-2"); - pbN = enc.GetBytes("a"); - if(MemUtil.IndexOf(pb, pbN) != 21) - throw new InvalidOperationException("MemUtil-3"); - pbN = enc.GetBytes("0a"); - if(MemUtil.IndexOf(pb, pbN) != 20) - throw new InvalidOperationException("MemUtil-4"); - pbN = enc.GetBytes("1"); - if(MemUtil.IndexOf(pb, pbN) != 1) - throw new InvalidOperationException("MemUtil-5"); - pbN = enc.GetBytes("b"); - if(MemUtil.IndexOf(pb, pbN) >= 0) - throw new InvalidOperationException("MemUtil-6"); - pbN = enc.GetBytes("012b"); - if(MemUtil.IndexOf(pb, pbN) >= 0) - throw new InvalidOperationException("MemUtil-7"); - - byte[] pbRes = MemUtil.ParseBase32("MY======"); - byte[] pbExp = Encoding.UTF8.GetBytes("f"); - if(!MemUtil.ArraysEqual(pbRes, pbExp)) throw new Exception("Base32-1"); - - pbRes = MemUtil.ParseBase32("MZXQ===="); - pbExp = Encoding.UTF8.GetBytes("fo"); - if(!MemUtil.ArraysEqual(pbRes, pbExp)) throw new Exception("Base32-2"); - - pbRes = MemUtil.ParseBase32("MZXW6==="); - pbExp = Encoding.UTF8.GetBytes("foo"); - if(!MemUtil.ArraysEqual(pbRes, pbExp)) throw new Exception("Base32-3"); - - pbRes = MemUtil.ParseBase32("MZXW6YQ="); - pbExp = Encoding.UTF8.GetBytes("foob"); - if(!MemUtil.ArraysEqual(pbRes, pbExp)) throw new Exception("Base32-4"); - - pbRes = MemUtil.ParseBase32("MZXW6YTB"); - pbExp = Encoding.UTF8.GetBytes("fooba"); - if(!MemUtil.ArraysEqual(pbRes, pbExp)) throw new Exception("Base32-5"); - - pbRes = MemUtil.ParseBase32("MZXW6YTBOI======"); - pbExp = Encoding.UTF8.GetBytes("foobar"); - if(!MemUtil.ArraysEqual(pbRes, pbExp)) throw new Exception("Base32-6"); - - pbRes = MemUtil.ParseBase32("JNSXSIDQOJXXM2LEMVZCAYTBONSWIIDPNYQG63TFFV2GS3LFEBYGC43TO5XXEZDTFY======"); - pbExp = Encoding.UTF8.GetBytes("Key provider based on one-time passwords."); - if(!MemUtil.ArraysEqual(pbRes, pbExp)) throw new Exception("Base32-7"); - - int i = 0 - 0x10203040; - pbRes = MemUtil.Int32ToBytes(i); - if(MemUtil.ByteArrayToHexString(pbRes) != "C0CFDFEF") - throw new Exception("MemUtil-8"); // Must be little-endian - if(MemUtil.BytesToUInt32(pbRes) != (uint)i) - throw new Exception("MemUtil-9"); - if(MemUtil.BytesToInt32(pbRes) != i) - throw new Exception("MemUtil-10"); - - ArrayHelperEx ah = MemUtil.ArrayHelperExOfChar; - for(int j = 0; j < 30; ++j) - { - string strA = r.Next(30).ToString(); - string strB = r.Next(30).ToString(); - char[] vA = strA.ToCharArray(); - char[] vB = strB.ToCharArray(); - - if(ah.Equals(vA, vB) != string.Equals(strA, strB)) - throw new Exception("MemUtil-11"); - if((vA.Length == vB.Length) && (Math.Sign(ah.Compare(vA, vB)) != - Math.Sign(string.CompareOrdinal(strA, strB)))) - throw new Exception("MemUtil-12"); - } -#endif - } - - private static void TestHmacOtp() - { -#if (DEBUG && !KeePassLibSD) - byte[] pbSecret = StrUtil.Utf8.GetBytes("12345678901234567890"); - string[] vExp = new string[]{ "755224", "287082", "359152", - "969429", "338314", "254676", "287922", "162583", "399871", - "520489" }; - - for(int i = 0; i < vExp.Length; ++i) - { - if(HmacOtp.Generate(pbSecret, (ulong)i, 6, false, -1) != vExp[i]) - throw new InvalidOperationException("HmacOtp"); - } -#endif - } - - private static void TestProtectedObjects(Random r) - { -#if DEBUG - Encoding enc = StrUtil.Utf8; - - byte[] pbData = enc.GetBytes("Test Test Test Test"); - ProtectedBinary pb = new ProtectedBinary(true, pbData); - if(!pb.IsProtected) throw new SecurityException("ProtectedBinary-1"); - - byte[] pbDec = pb.ReadData(); - if(!MemUtil.ArraysEqual(pbData, pbDec)) - throw new SecurityException("ProtectedBinary-2"); - if(!pb.IsProtected) throw new SecurityException("ProtectedBinary-3"); - - byte[] pbData2 = enc.GetBytes("Test Test Test Test"); - byte[] pbData3 = enc.GetBytes("Test Test Test Test Test"); - ProtectedBinary pb2 = new ProtectedBinary(true, pbData2); - ProtectedBinary pb3 = new ProtectedBinary(true, pbData3); - if(!pb.Equals(pb2)) throw new SecurityException("ProtectedBinary-4"); - if(pb.Equals(pb3)) throw new SecurityException("ProtectedBinary-5"); - if(pb2.Equals(pb3)) throw new SecurityException("ProtectedBinary-6"); - - if(pb.GetHashCode() != pb2.GetHashCode()) - throw new SecurityException("ProtectedBinary-7"); - if(!((object)pb).Equals((object)pb2)) - throw new SecurityException("ProtectedBinary-8"); - if(((object)pb).Equals((object)pb3)) - throw new SecurityException("ProtectedBinary-9"); - if(((object)pb2).Equals((object)pb3)) - throw new SecurityException("ProtectedBinary-10"); - - ProtectedString ps = new ProtectedString(); - if(ps.Length != 0) throw new SecurityException("ProtectedString-1"); - if(!ps.IsEmpty) throw new SecurityException("ProtectedString-2"); - if(ps.ReadString().Length != 0) - throw new SecurityException("ProtectedString-3"); - - ps = new ProtectedString(true, "Test"); - ProtectedString ps2 = new ProtectedString(true, enc.GetBytes("Test")); - if(ps.IsEmpty) throw new SecurityException("ProtectedString-4"); - pbData = ps.ReadUtf8(); - pbData2 = ps2.ReadUtf8(); - if(!MemUtil.ArraysEqual(pbData, pbData2)) - throw new SecurityException("ProtectedString-5"); - if(pbData.Length != 4) - throw new SecurityException("ProtectedString-6"); - if(ps.ReadString() != ps2.ReadString()) - throw new SecurityException("ProtectedString-7"); - pbData = ps.ReadUtf8(); - pbData2 = ps2.ReadUtf8(); - if(!MemUtil.ArraysEqual(pbData, pbData2)) - throw new SecurityException("ProtectedString-8"); - if(!ps.IsProtected) throw new SecurityException("ProtectedString-9"); - if(!ps2.IsProtected) throw new SecurityException("ProtectedString-10"); - - string str = string.Empty; - ps = new ProtectedString(); - for(int i = 0; i < 100; ++i) - { - bool bProt = ((r.Next() % 4) != 0); - ps = ps.WithProtection(bProt); - - int x = r.Next(str.Length + 1); - int c = r.Next(20); - char ch = (char)r.Next(1, 256); - - string strIns = new string(ch, c); - str = str.Insert(x, strIns); - ps = ps.Insert(x, strIns); - - if(ps.IsProtected != bProt) - throw new SecurityException("ProtectedString-11"); - if(ps.ReadString() != str) - throw new SecurityException("ProtectedString-12"); - - ps = ps.WithProtection(bProt); - - x = r.Next(str.Length); - c = r.Next(str.Length - x + 1); - - str = str.Remove(x, c); - ps = ps.Remove(x, c); - - if(ps.IsProtected != bProt) - throw new SecurityException("ProtectedString-13"); - if(ps.ReadString() != str) - throw new SecurityException("ProtectedString-14"); - } - - ps = new ProtectedString(false, "ABCD"); - ps2 = new ProtectedString(true, "EFG"); - ps += (ps2 + "HI"); - if(!ps.Equals(new ProtectedString(true, "ABCDEFGHI"), true)) - throw new SecurityException("ProtectedString-15"); - if(!ps.Equals(new ProtectedString(false, "ABCDEFGHI"), false)) - throw new SecurityException("ProtectedString-16"); -#endif - } - - private static void TestStrUtil() - { -#if DEBUG - string[] vSeps = new string[]{ "ax", "b", "c" }; - const string str1 = "axbqrstcdeax"; - List v1 = StrUtil.SplitWithSep(str1, vSeps, true); - - if(v1.Count != 9) throw new InvalidOperationException("StrUtil-1"); - if(v1[0].Length > 0) throw new InvalidOperationException("StrUtil-2"); - if(!v1[1].Equals("ax")) throw new InvalidOperationException("StrUtil-3"); - if(v1[2].Length > 0) throw new InvalidOperationException("StrUtil-4"); - if(!v1[3].Equals("b")) throw new InvalidOperationException("StrUtil-5"); - if(!v1[4].Equals("qrst")) throw new InvalidOperationException("StrUtil-6"); - if(!v1[5].Equals("c")) throw new InvalidOperationException("StrUtil-7"); - if(!v1[6].Equals("de")) throw new InvalidOperationException("StrUtil-8"); - if(!v1[7].Equals("ax")) throw new InvalidOperationException("StrUtil-9"); - if(v1[8].Length > 0) throw new InvalidOperationException("StrUtil-10"); - - const string str2 = "12ab56"; - List v2 = StrUtil.SplitWithSep(str2, new string[]{ "AB" }, false); - if(v2.Count != 3) throw new InvalidOperationException("StrUtil-11"); - if(!v2[0].Equals("12")) throw new InvalidOperationException("StrUtil-12"); - if(!v2[1].Equals("AB")) throw new InvalidOperationException("StrUtil-13"); - if(!v2[2].Equals("56")) throw new InvalidOperationException("StrUtil-14"); - - List v3 = StrUtil.SplitWithSep("pqrs", vSeps, false); - if(v3.Count != 1) throw new InvalidOperationException("StrUtil-15"); - if(!v3[0].Equals("pqrs")) throw new InvalidOperationException("StrUtil-16"); - - if(StrUtil.VersionToString(0x000F000E000D000CUL) != "15.14.13.12") - throw new InvalidOperationException("StrUtil-V1"); - if(StrUtil.VersionToString(0x00FF000E00010000UL) != "255.14.1") - throw new InvalidOperationException("StrUtil-V2"); - if(StrUtil.VersionToString(0x000F00FF00000000UL) != "15.255") - throw new InvalidOperationException("StrUtil-V3"); - if(StrUtil.VersionToString(0x00FF000000000000UL) != "255") - throw new InvalidOperationException("StrUtil-V4"); - if(StrUtil.VersionToString(0x00FF000000000000UL, 2) != "255.0") - throw new InvalidOperationException("StrUtil-V5"); - if(StrUtil.VersionToString(0x0000000000070000UL) != "0.0.7") - throw new InvalidOperationException("StrUtil-V6"); - if(StrUtil.VersionToString(0x0000000000000000UL) != "0") - throw new InvalidOperationException("StrUtil-V7"); - if(StrUtil.VersionToString(0x00000000FFFF0000UL, 4) != "0.0.65535.0") - throw new InvalidOperationException("StrUtil-V8"); - if(StrUtil.VersionToString(0x0000000000000000UL, 4) != "0.0.0.0") - throw new InvalidOperationException("StrUtil-V9"); - - if(StrUtil.RtfEncodeChar('\u0000') != "\\u0?") - throw new InvalidOperationException("StrUtil-Rtf1"); - if(StrUtil.RtfEncodeChar('\u7FFF') != "\\u32767?") - throw new InvalidOperationException("StrUtil-Rtf2"); - if(StrUtil.RtfEncodeChar('\u8000') != "\\u-32768?") - throw new InvalidOperationException("StrUtil-Rtf3"); - if(StrUtil.RtfEncodeChar('\uFFFF') != "\\u-1?") - throw new InvalidOperationException("StrUtil-Rtf4"); - - if(!StrUtil.StringToBool(Boolean.TrueString)) - throw new InvalidOperationException("StrUtil-Bool1"); - if(StrUtil.StringToBool(Boolean.FalseString)) - throw new InvalidOperationException("StrUtil-Bool2"); - - if(StrUtil.Count("Abracadabra", "a") != 4) - throw new InvalidOperationException("StrUtil-Count1"); - if(StrUtil.Count("Bla", "U") != 0) - throw new InvalidOperationException("StrUtil-Count2"); - if(StrUtil.Count("AAAAA", "AA") != 4) - throw new InvalidOperationException("StrUtil-Count3"); - - const string sU = "data:mytype;base64,"; - if(!StrUtil.IsDataUri(sU)) - throw new InvalidOperationException("StrUtil-DataUri1"); - if(!StrUtil.IsDataUri(sU, "mytype")) - throw new InvalidOperationException("StrUtil-DataUri2"); - if(StrUtil.IsDataUri(sU, "notmytype")) - throw new InvalidOperationException("StrUtil-DataUri3"); - - uint u = 0x7FFFFFFFU; - if(u.ToString(NumberFormatInfo.InvariantInfo) != "2147483647") - throw new InvalidOperationException("StrUtil-Inv1"); - if(uint.MaxValue.ToString(NumberFormatInfo.InvariantInfo) != - "4294967295") - throw new InvalidOperationException("StrUtil-Inv2"); - if(long.MinValue.ToString(NumberFormatInfo.InvariantInfo) != - "-9223372036854775808") - throw new InvalidOperationException("StrUtil-Inv3"); - if(short.MinValue.ToString(NumberFormatInfo.InvariantInfo) != - "-32768") - throw new InvalidOperationException("StrUtil-Inv4"); - - if(!string.Equals("abcd", "aBcd", StrUtil.CaseIgnoreCmp)) - throw new InvalidOperationException("StrUtil-Case1"); - if(string.Equals(@"ab", StrUtil.CaseIgnoreCmp)) - throw new InvalidOperationException("StrUtil-Case2"); - - const string strNL = "\n01\r23\n45\r\n67\r"; - string strW = StrUtil.NormalizeNewLines(strNL, true); - string strL = StrUtil.NormalizeNewLines(strNL, false); - if(strW != "\r\n01\r\n23\r\n45\r\n67\r\n") - throw new InvalidOperationException("StrUtil-NewLine1"); - if(strL != "\n01\n23\n45\n67\n") - throw new InvalidOperationException("StrUtil-NewLine2"); - if(StrUtil.IsNewLineNormalized(strNL.ToCharArray(), true)) - throw new InvalidOperationException("StrUtil-NewLine3"); - if(StrUtil.IsNewLineNormalized(strNL.ToCharArray(), false)) - throw new InvalidOperationException("StrUtil-NewLine4"); - if(!StrUtil.IsNewLineNormalized(strW.ToCharArray(), true)) - throw new InvalidOperationException("StrUtil-NewLine5"); - if(StrUtil.IsNewLineNormalized(strW.ToCharArray(), false)) - throw new InvalidOperationException("StrUtil-NewLine6"); - if(StrUtil.IsNewLineNormalized(strL.ToCharArray(), true)) - throw new InvalidOperationException("StrUtil-NewLine7"); - if(!StrUtil.IsNewLineNormalized(strL.ToCharArray(), false)) - throw new InvalidOperationException("StrUtil-NewLine8"); - if(!StrUtil.IsNewLineNormalized(string.Empty.ToCharArray(), true)) - throw new InvalidOperationException("StrUtil-NewLine9"); - if(!StrUtil.IsNewLineNormalized(string.Empty.ToCharArray(), false)) - throw new InvalidOperationException("StrUtil-NewLine10"); -#endif - } - - private static void TestUrlUtil() - { -#if DEBUG -#if !ModernKeePassLib && !KeePassUAP - Debug.Assert(Uri.UriSchemeHttp.Equals("http", StrUtil.CaseIgnoreCmp)); - Debug.Assert(Uri.UriSchemeHttps.Equals("https", StrUtil.CaseIgnoreCmp)); -#endif - - if(UrlUtil.GetHost(@"scheme://domain:port/path?query_string#fragment_id") != - "domain") - throw new InvalidOperationException("UrlUtil-H1"); - if(UrlUtil.GetHost(@"https://example.org:443") != "example.org") - throw new InvalidOperationException("UrlUtil-H2"); - if(UrlUtil.GetHost(@"mailto:bob@example.com") != "example.com") - throw new InvalidOperationException("UrlUtil-H3"); - if(UrlUtil.GetHost(@"ftp://asmith@ftp.example.org") != "ftp.example.org") - throw new InvalidOperationException("UrlUtil-H4"); - if(UrlUtil.GetHost(@"scheme://username:password@domain:port/path?query_string#fragment_id") != - "domain") - throw new InvalidOperationException("UrlUtil-H5"); - if(UrlUtil.GetHost(@"bob@example.com") != "example.com") - throw new InvalidOperationException("UrlUtil-H6"); - if(UrlUtil.GetHost(@"s://u:p@d.tld:p/p?q#f") != "d.tld") - throw new InvalidOperationException("UrlUtil-H7"); - - if(NativeLib.IsUnix()) return; - - string strBase = "\\\\HOMESERVER\\Apps\\KeePass\\KeePass.exe"; - string strDoc = "\\\\HOMESERVER\\Documents\\KeePass\\NewDatabase.kdbx"; - string strRel = "..\\..\\Documents\\KeePass\\NewDatabase.kdbx"; - - string str = UrlUtil.MakeRelativePath(strBase, strDoc); - if(!str.Equals(strRel)) throw new InvalidOperationException("UrlUtil-R1"); - - str = UrlUtil.MakeAbsolutePath(strBase, strRel); - if(!str.Equals(strDoc)) throw new InvalidOperationException("UrlUtil-R2"); - - str = UrlUtil.GetQuotedAppPath(" \"Test\" \"%1\" "); - if(str != "Test") throw new InvalidOperationException("UrlUtil-Q1"); - str = UrlUtil.GetQuotedAppPath("C:\\Program Files\\Test.exe"); - if(str != "C:\\Program Files\\Test.exe") throw new InvalidOperationException("UrlUtil-Q2"); - str = UrlUtil.GetQuotedAppPath("Reg.exe \"Test\" \"Test 2\""); - if(str != "Reg.exe \"Test\" \"Test 2\"") throw new InvalidOperationException("UrlUtil-Q3"); -#endif - } - } -} diff --git a/ModernKeePassLib/Delegates/Handlers.cs b/ModernKeePassLib/Delegates/Handlers.cs deleted file mode 100644 index 5c3786b..0000000 --- a/ModernKeePassLib/Delegates/Handlers.cs +++ /dev/null @@ -1,66 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; - -namespace ModernKeePassLib.Delegates -{ - /// - /// Function definition of a method that performs an action on a group. - /// When traversing the internal tree, this function will be invoked - /// for all visited groups. - /// - /// Currently visited group. - /// You must return true if you want to continue the - /// traversal. If you want to immediately stop the whole traversal, - /// return false. - public delegate bool GroupHandler(PwGroup pg); - - /// - /// Function definition of a method that performs an action on an entry. - /// When traversing the internal tree, this function will be invoked - /// for all visited entries. - /// - /// Currently visited entry. - /// You must return true if you want to continue the - /// traversal. If you want to immediately stop the whole traversal, - /// return false. - public delegate bool EntryHandler(PwEntry pe); - - public delegate void VoidDelegate(); - public delegate string StrPwEntryDelegate(string str, PwEntry pe); - - // Action<...> with 0 or >= 2 parameters has been introduced in .NET 3.5 - public delegate void GAction(); - public delegate void GAction(T o); - public delegate void GAction(T1 o1, T2 o2); - public delegate void GAction(T1 o1, T2 o2, T3 o3); - public delegate void GAction(T1 o1, T2 o2, T3 o3, T4 o4); - public delegate void GAction(T1 o1, T2 o2, T3 o3, T4 o4, T5 o5); - public delegate void GAction(T1 o1, T2 o2, T3 o3, T4 o4, T5 o5, T6 o6); - - // Func<...> has been introduced in .NET 3.5 - public delegate TResult GFunc(); - public delegate TResult GFunc(T o); - public delegate TResult GFunc(T1 o1, T2 o2); - public delegate TResult GFunc(T1 o1, T2 o2, T3 o3); - public delegate TResult GFunc(T1 o1, T2 o2, T3 o3, T4 o4); - public delegate TResult GFunc(T1 o1, T2 o2, T3 o3, T4 o4, T5 o5); - public delegate TResult GFunc(T1 o1, T2 o2, T3 o3, T4 o4, T5 o5, T6 o6); -} diff --git a/ModernKeePassLib/Interfaces/IDeepCloneable.cs b/ModernKeePassLib/Interfaces/IDeepCloneable.cs deleted file mode 100644 index ffaad06..0000000 --- a/ModernKeePassLib/Interfaces/IDeepCloneable.cs +++ /dev/null @@ -1,37 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; - -namespace ModernKeePassLib.Interfaces -{ - /// - /// Interface for objects that are deeply cloneable. - /// - /// Reference type. - public interface IDeepCloneable where T : class - { - /// - /// Deeply clone the object. - /// - /// Cloned object. - T CloneDeep(); - } -} diff --git a/ModernKeePassLib/Interfaces/IStatusLogger.cs b/ModernKeePassLib/Interfaces/IStatusLogger.cs deleted file mode 100644 index 980ca3d..0000000 --- a/ModernKeePassLib/Interfaces/IStatusLogger.cs +++ /dev/null @@ -1,105 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; - -namespace ModernKeePassLib.Interfaces -{ - /// - /// Status message types. - /// - public enum LogStatusType - { - /// - /// Default type: simple information type. - /// - Info = 0, - - /// - /// Warning message. - /// - Warning, - - /// - /// Error message. - /// - Error, - - /// - /// Additional information. Depends on lines above. - /// - AdditionalInfo - } - - /// - /// Status logging interface. - /// - public interface IStatusLogger - { - /// - /// Function which needs to be called when logging is started. - /// - /// This string should roughly describe - /// the operation, of which the status is logged. - /// Specifies whether the - /// operation is written to the log or not. - void StartLogging(string strOperation, bool bWriteOperationToLog); - - /// - /// Function which needs to be called when logging is ended - /// (i.e. when no more messages will be logged and when the - /// percent value won't change any more). - /// - void EndLogging(); - - /// - /// Set the current progress in percent. - /// - /// Percent of work finished. - /// Returns true if the caller should continue - /// the current work. - bool SetProgress(uint uPercent); - - /// - /// Set the current status text. - /// - /// Status text. - /// Type of the message. - /// Returns true if the caller should continue - /// the current work. - bool SetText(string strNewText, LogStatusType lsType); - - /// - /// Check if the user cancelled the current work. - /// - /// Returns true if the caller should continue - /// the current work. - bool ContinueWork(); - } - - public sealed class NullStatusLogger : IStatusLogger - { - public void StartLogging(string strOperation, bool bWriteOperationToLog) { } - public void EndLogging() { } - public bool SetProgress(uint uPercent) { return true; } - public bool SetText(string strNewText, LogStatusType lsType) { return true; } - public bool ContinueWork() { return true; } - } -} diff --git a/ModernKeePassLib/Interfaces/IStructureItem.cs b/ModernKeePassLib/Interfaces/IStructureItem.cs deleted file mode 100644 index 9fc2eed..0000000 --- a/ModernKeePassLib/Interfaces/IStructureItem.cs +++ /dev/null @@ -1,37 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; - -namespace ModernKeePassLib.Interfaces -{ - public interface IStructureItem : ITimeLogger // Provides LocationChanged - { - PwUuid Uuid - { - get; - set; - } - - PwGroup ParentGroup - { - get; - } - } -} diff --git a/ModernKeePassLib/Interfaces/ITimeLogger.cs b/ModernKeePassLib/Interfaces/ITimeLogger.cs deleted file mode 100644 index 6611e3b..0000000 --- a/ModernKeePassLib/Interfaces/ITimeLogger.cs +++ /dev/null @@ -1,105 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; - -namespace ModernKeePassLib.Interfaces -{ - /// - /// Interface for objects that support various times (creation time, last - /// access time, last modification time and expiry time). Offers - /// several helper functions (for example a function to touch the current - /// object). - /// - public interface ITimeLogger - { - /// - /// The date/time when the object was created. - /// - DateTime CreationTime - { - get; - set; - } - - /// - /// The date/time when the object was last modified. - /// - DateTime LastModificationTime - { - get; - set; - } - - /// - /// The date/time when the object was last accessed. - /// - DateTime LastAccessTime - { - get; - set; - } - - /// - /// The date/time when the object expires. - /// - DateTime ExpiryTime - { - get; - set; - } - - /// - /// Flag that determines if the object does expire. - /// - bool Expires - { - get; - set; - } - - /// - /// Get or set the usage count of the object. To increase the usage - /// count by one, use the Touch function. - /// - ulong UsageCount - { - get; - set; - } - - /// - /// The date/time when the location of the object was last changed. - /// - DateTime LocationChanged - { - get; - set; - } - - /// - /// Touch the object. This function updates the internal last access - /// time. If the parameter is true, - /// the last modification time gets updated, too. Each time you call - /// Touch, the usage count of the object is increased by one. - /// - /// Update last modification time. - void Touch(bool bModified); - } -} diff --git a/ModernKeePassLib/Interfaces/IUIOperations.cs b/ModernKeePassLib/Interfaces/IUIOperations.cs deleted file mode 100644 index be8fee6..0000000 --- a/ModernKeePassLib/Interfaces/IUIOperations.cs +++ /dev/null @@ -1,37 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; -using System.Text; - -namespace ModernKeePassLib.Interfaces -{ - public interface IUIOperations - { - /// - /// Let the user interface save the current database. - /// - /// If true, the UI will not ask for - /// whether to synchronize or overwrite, it'll simply overwrite the - /// file. - /// Returns true if the file has been saved. - bool UIFileSave(bool bForceSave); - } -} diff --git a/ModernKeePassLib/Interfaces/IXmlSerializerEx.cs b/ModernKeePassLib/Interfaces/IXmlSerializerEx.cs deleted file mode 100644 index 8712e5e..0000000 --- a/ModernKeePassLib/Interfaces/IXmlSerializerEx.cs +++ /dev/null @@ -1,33 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; -using System.Text; -using System.IO; -using System.Xml; - -namespace ModernKeePassLib.Interfaces -{ - public interface IXmlSerializerEx - { - void Serialize(XmlWriter xmlWriter, object o); - object Deserialize(Stream s); - } -} diff --git a/ModernKeePassLib/KeePassLib.pfx b/ModernKeePassLib/KeePassLib.pfx deleted file mode 100644 index 19b0155f5b910f8d44dddd6910a2524028c1638e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1756 zcmY*Yc~sL^7XD>13!sMxCsBnV1IaGSK2 zO+?uuNkz5!+lLi2x9S{e{&g%H|8 z5JH;>MhK1n)8OMf;HYa3?q6GTq@GYHau_azUT(IyfktIji=M?;oa|T3?s|N<`(T~( zy*;KwIU{=?`Gu5M#P17Eg^FamMrV#D+zu$BrGNJ;uSXrCzUI?p?q~S4k3;fV&lAu6 z0kQa|XNKUh==tS+=4{D>MDO*dal2D4Lwin z&k)Mn193B#Nq@^yyBznHjZ*QGxm2u8LzBv2MUxvlS)9Jwk>~E98vn7|#O9d&nI5PK zH==7}zgZ46+D`f-BphG#2TwPBs^Bv@Dukxz3s6f2Suu2%jBW*P<1WtV@ z`>nJ{=c~$lHlp^YUZmQwxF}}glHIcuasQl^imon8a!GiFvU~3D!*avXGog`(%h0Nb zh81Mm2xh@GAgp*kAWk(f`Ev%}u~%fn^)c$}Vm*&)yf-I4pdP~Wa-uXL=5vZ^b4yap|#qFQpW#^Uth^ zZN9r6uI1@9_h1+58*knI{_75Wf z;&6o;Hu>JXD0rOGW#YU@TIk=NX{0rSJWKOkE(b9kPJ3CqYNCUnC58p zcB=B~qFqpN@7SiS*^oUII-~b+%&VGmVJPF&DgnPKwmadnRPCJ`z{Q;^n`gom>+F9! z#opU`twR(%CFuOI=4d@40uF}&06+-&lhh@88#94SzzrY(48Q@P10;Y9R$CAe0S_>2 zK}-TwD%jD%N&qNdR63|Tf;s~XN5Bbi2ZI1)6T%Q^JOToT2q_o{(bw1;I0D1~kw7w# z0HlC<65s+jz#lw6GpB(l5HY|$9Y_SZ1P~_!Y_O01GKB+tzue=2hy$Del0heS{qcJw z0yL)-r0Ss%04!C1Dv-a}74g}w|FOlsZNn@?ezt3l5Sqq44;x=wf>bCV9|i4U>>5>s z!$!JR-utCHu*wq~q2ZDYf7CY}=Z|R*=&=fdE$v76Tb;x!zs!&@%( z8ge=4jMA)jQ-YO-+tTz6ey%rNxl?9Z%CniZqO2ijbrq={U%j?KJu90Z|B%&Y5}$iC zSRU^wYmiNcIg1}qUwX_2&arOnUpI56i3|%7t)siojcX=b9zf&nMy>pEqjobAo7Ev_3)6P~`4X5l;SX*ZXrE1>2D|hOLum^SaS83^O z-Fp-C@@=r;#@n0()2gCOdPJwv(enk51!JxIp31har^S&p{vHouK1zMx_ z@{%PfMV;$PiDvJZY1Ca0a6y`LLz|uu-Ew^9^&flfK65A~b(O>^K5}K)?cs}9mf39& z{*w*%-vP(gbcRDaWB*6YE;6?@#@nL1lz43j!{h7)b= zsjjF*OM`zc(v~|yo>a(Dw2rfA(@-iS$f0@TTn#kI=m z8#Z-uhAI=iB;@z2q1r23h*wR)WXFrgk*ut5rJIpM^SEytM!3Wpf-}K{fJAJ=?|?xK z;JW# - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Text; - -using ModernKeePassLib.Cryptography; -using ModernKeePassLib.Cryptography.KeyDerivation; -using ModernKeePassLib.Resources; -using ModernKeePassLib.Security; -using ModernKeePassLib.Utility; - -namespace ModernKeePassLib.Keys -{ - /// - /// Represents a key. A key can be build up using several user key data sources - /// like a password, a key file, the currently logged on user credentials, - /// the current computer ID, etc. - /// - public sealed class CompositeKey - { - private List m_vUserKeys = new List(); - - /// - /// List of all user keys contained in the current composite key. - /// - public IEnumerable UserKeys - { - get { return m_vUserKeys; } - } - - public uint UserKeyCount - { - get { return (uint)m_vUserKeys.Count; } - } - - /// - /// Construct a new, empty key object. - /// - public CompositeKey() - { - } - - // /// - // /// Deconstructor, clears up the key. - // /// - // ~CompositeKey() - // { - // Clear(); - // } - - // /// - // /// Clears the key. This function also erases all previously stored - // /// user key data objects. - // /// - // public void Clear() - // { - // foreach(IUserKey pKey in m_vUserKeys) - // pKey.Clear(); - // m_vUserKeys.Clear(); - // } - - /// - /// Add a user key. - /// - /// User key to add. - public void AddUserKey(IUserKey pKey) - { - Debug.Assert(pKey != null); if(pKey == null) throw new ArgumentNullException("pKey"); - - m_vUserKeys.Add(pKey); - } - - /// - /// Remove a user key. - /// - /// User key to remove. - /// Returns true if the key was removed successfully. - public bool RemoveUserKey(IUserKey pKey) - { - Debug.Assert(pKey != null); if(pKey == null) throw new ArgumentNullException("pKey"); - - Debug.Assert(m_vUserKeys.IndexOf(pKey) >= 0); - return m_vUserKeys.Remove(pKey); - } - - /// - /// Test whether the composite key contains a specific type of - /// user keys (password, key file, ...). If at least one user - /// key of that type is present, the function returns true. - /// - /// User key type. - /// Returns true, if the composite key contains - /// a user key of the specified type. - public bool ContainsType(Type tUserKeyType) - { - Debug.Assert(tUserKeyType != null); - if(tUserKeyType == null) throw new ArgumentNullException("tUserKeyType"); - - foreach(IUserKey pKey in m_vUserKeys) - { - if(pKey == null) { Debug.Assert(false); continue; } - -#if ModernKeePassLib || KeePassUAP - if(pKey.GetType() == tUserKeyType) - return true; -#else - if(tUserKeyType.IsInstanceOfType(pKey)) - return true; -#endif - } - - return false; - } - - /// - /// Get the first user key of a specified type. - /// - /// Type of the user key to get. - /// Returns the first user key of the specified type - /// or null if no key of that type is found. - public IUserKey GetUserKey(Type tUserKeyType) - { - Debug.Assert(tUserKeyType != null); - if(tUserKeyType == null) throw new ArgumentNullException("tUserKeyType"); - - foreach(IUserKey pKey in m_vUserKeys) - { - if(pKey == null) { Debug.Assert(false); continue; } - -#if ModernKeePassLib || KeePassUAP - if(pKey.GetType() == tUserKeyType) - return pKey; -#else - if(tUserKeyType.IsInstanceOfType(pKey)) - return pKey; -#endif - } - - return null; - } - - /// - /// Creates the composite key from the supplied user key sources (password, - /// key file, user account, computer ID, etc.). - /// - private byte[] CreateRawCompositeKey32() - { - ValidateUserKeys(); - - // Concatenate user key data - List lData = new List(); - int cbData = 0; - foreach(IUserKey pKey in m_vUserKeys) - { - ProtectedBinary b = pKey.KeyData; - if(b != null) - { - byte[] pbKeyData = b.ReadData(); - lData.Add(pbKeyData); - cbData += pbKeyData.Length; - } - } - - byte[] pbAllData = new byte[cbData]; - int p = 0; - foreach(byte[] pbData in lData) - { - Array.Copy(pbData, 0, pbAllData, p, pbData.Length); - p += pbData.Length; - MemUtil.ZeroByteArray(pbData); - } - Debug.Assert(p == cbData); - - byte[] pbHash = CryptoUtil.HashSha256(pbAllData); - MemUtil.ZeroByteArray(pbAllData); - return pbHash; - } - - public bool EqualsValue(CompositeKey ckOther) - { - if(ckOther == null) throw new ArgumentNullException("ckOther"); - - byte[] pbThis = CreateRawCompositeKey32(); - byte[] pbOther = ckOther.CreateRawCompositeKey32(); - bool bResult = MemUtil.ArraysEqual(pbThis, pbOther); - MemUtil.ZeroByteArray(pbOther); - MemUtil.ZeroByteArray(pbThis); - - return bResult; - } - - [Obsolete] - public ProtectedBinary GenerateKey32(byte[] pbKeySeed32, ulong uNumRounds) - { - Debug.Assert(pbKeySeed32 != null); - if(pbKeySeed32 == null) throw new ArgumentNullException("pbKeySeed32"); - Debug.Assert(pbKeySeed32.Length == 32); - if(pbKeySeed32.Length != 32) throw new ArgumentException("pbKeySeed32"); - - AesKdf kdf = new AesKdf(); - KdfParameters p = kdf.GetDefaultParameters(); - p.SetUInt64(AesKdf.ParamRounds, uNumRounds); - p.SetByteArray(AesKdf.ParamSeed, pbKeySeed32); - - return GenerateKey32(p); - } - - /// - /// Generate a 32-byte (256-bit) key from the composite key. - /// - public ProtectedBinary GenerateKey32(KdfParameters p) - { - if(p == null) { Debug.Assert(false); throw new ArgumentNullException("p"); } - - byte[] pbRaw32 = CreateRawCompositeKey32(); - if((pbRaw32 == null) || (pbRaw32.Length != 32)) - { Debug.Assert(false); return null; } - - KdfEngine kdf = KdfPool.Get(p.KdfUuid); - if(kdf == null) // CryptographicExceptions are translated to "file corrupted" - throw new Exception(KLRes.UnknownKdf + MessageService.NewParagraph + - KLRes.FileNewVerOrPlgReq + MessageService.NewParagraph + - "UUID: " + p.KdfUuid.ToHexString() + "."); - - byte[] pbTrf32 = kdf.Transform(pbRaw32, p); - if(pbTrf32 == null) { Debug.Assert(false); return null; } - - if(pbTrf32.Length != 32) - { - Debug.Assert(false); - pbTrf32 = CryptoUtil.HashSha256(pbTrf32); - } - - ProtectedBinary pbRet = new ProtectedBinary(true, pbTrf32); - MemUtil.ZeroByteArray(pbTrf32); - MemUtil.ZeroByteArray(pbRaw32); - return pbRet; - } - - private void ValidateUserKeys() - { - int nAccounts = 0; - - foreach(IUserKey uKey in m_vUserKeys) - { - if(uKey is KcpUserAccount) - ++nAccounts; - } - - if(nAccounts >= 2) - { - Debug.Assert(false); - throw new InvalidOperationException(); - } - } - } - - public sealed class InvalidCompositeKeyException : Exception - { - public override string Message - { - get - { - return KLRes.InvalidCompositeKey + MessageService.NewParagraph + - KLRes.InvalidCompositeKeyHint; - } - } - - /// - /// Construct a new invalid composite key exception. - /// - public InvalidCompositeKeyException() - { - } - } -} diff --git a/ModernKeePassLib/Keys/IUserKey.cs b/ModernKeePassLib/Keys/IUserKey.cs deleted file mode 100644 index f3ae8bf..0000000 --- a/ModernKeePassLib/Keys/IUserKey.cs +++ /dev/null @@ -1,46 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; - -using ModernKeePassLib.Security; - -namespace ModernKeePassLib.Keys -{ - /// - /// Interface to a user key, like a password, key file data, etc. - /// - public interface IUserKey - { - /// - /// Get key data. Querying this property is fast (it returns a - /// reference to a cached ProtectedBinary object). - /// If no key data is available, null is returned. - /// - ProtectedBinary KeyData - { - get; - } - - // /// - // /// Clear the key and securely erase all security-critical information. - // /// - // void Clear(); - } -} diff --git a/ModernKeePassLib/Keys/KcpCustomKey.cs b/ModernKeePassLib/Keys/KcpCustomKey.cs deleted file mode 100644 index d263cc5..0000000 --- a/ModernKeePassLib/Keys/KcpCustomKey.cs +++ /dev/null @@ -1,68 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Text; - -using ModernKeePassLib.Cryptography; -using ModernKeePassLib.Security; - -namespace ModernKeePassLib.Keys -{ - public sealed class KcpCustomKey : IUserKey - { - private readonly string m_strName; - private ProtectedBinary m_pbKey; - - /// - /// Name of the provider that generated the custom key. - /// - public string Name - { - get { return m_strName; } - } - - public ProtectedBinary KeyData - { - get { return m_pbKey; } - } - - public KcpCustomKey(string strName, byte[] pbKeyData, bool bPerformHash) - { - Debug.Assert(strName != null); if(strName == null) throw new ArgumentNullException("strName"); - Debug.Assert(pbKeyData != null); if(pbKeyData == null) throw new ArgumentNullException("pbKeyData"); - - m_strName = strName; - - if(bPerformHash) - { - byte[] pbRaw = CryptoUtil.HashSha256(pbKeyData); - m_pbKey = new ProtectedBinary(true, pbRaw); - } - else m_pbKey = new ProtectedBinary(true, pbKeyData); - } - - // public void Clear() - // { - // m_pbKey = null; - // } - } -} diff --git a/ModernKeePassLib/Keys/KcpKeyFile.cs b/ModernKeePassLib/Keys/KcpKeyFile.cs deleted file mode 100644 index fde69b7..0000000 --- a/ModernKeePassLib/Keys/KcpKeyFile.cs +++ /dev/null @@ -1,350 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Diagnostics; -using System.IO; -using System.Security; -using System.Text; -using System.Xml; -#if ModernKeePassLib -using System.Collections.Generic; -using System.Linq; -using System.Xml.Linq; -using Windows.Security.Cryptography; -using Windows.Security.Cryptography.Core; -using Windows.Storage; -#else -using System.Security.Cryptography; -#endif - -using ModernKeePassLib.Cryptography; -using ModernKeePassLib.Resources; -using ModernKeePassLib.Security; -using ModernKeePassLib.Serialization; -using ModernKeePassLib.Utility; - -namespace ModernKeePassLib.Keys -{ - /// - /// Key files as provided by the user. - /// - public sealed class KcpKeyFile : IUserKey - { - private string m_strPath; - private ProtectedBinary m_pbKeyData; - - /// - /// Path to the key file. - /// - public string Path - { - get { return m_strPath; } - } - - /// - /// Get key data. Querying this property is fast (it returns a - /// reference to a cached ProtectedBinary object). - /// If no key data is available, null is returned. - /// - public ProtectedBinary KeyData - { - get { return m_pbKeyData; } - } - -#if ModernKeePassLib - public KcpKeyFile(StorageFile strKeyFile) - { - Construct(IOConnectionInfo.FromFile(strKeyFile), false); - } -#endif - - public KcpKeyFile(string strKeyFile) - { - Construct(IOConnectionInfo.FromPath(strKeyFile), false); - } - - public KcpKeyFile(string strKeyFile, bool bThrowIfDbFile) - { - Construct(IOConnectionInfo.FromPath(strKeyFile), bThrowIfDbFile); - } - - public KcpKeyFile(IOConnectionInfo iocKeyFile) - { - Construct(iocKeyFile, false); - } - - public KcpKeyFile(IOConnectionInfo iocKeyFile, bool bThrowIfDbFile) - { - Construct(iocKeyFile, bThrowIfDbFile); - } - - private void Construct(IOConnectionInfo iocFile, bool bThrowIfDbFile) - { - byte[] pbFileData = IOConnection.ReadFile(iocFile); - if(pbFileData == null) throw new FileNotFoundException(); - - if(bThrowIfDbFile && (pbFileData.Length >= 8)) - { - uint uSig1 = MemUtil.BytesToUInt32(MemUtil.Mid(pbFileData, 0, 4)); - uint uSig2 = MemUtil.BytesToUInt32(MemUtil.Mid(pbFileData, 4, 4)); - - if(((uSig1 == KdbxFile.FileSignature1) && - (uSig2 == KdbxFile.FileSignature2)) || - ((uSig1 == KdbxFile.FileSignaturePreRelease1) && - (uSig2 == KdbxFile.FileSignaturePreRelease2)) || - ((uSig1 == KdbxFile.FileSignatureOld1) && - (uSig2 == KdbxFile.FileSignatureOld2))) -#if KeePassLibSD - throw new Exception(KLRes.KeyFileDbSel); -#else - throw new InvalidDataException(KLRes.KeyFileDbSel); -#endif - } - - byte[] pbKey = LoadXmlKeyFile(pbFileData); - if(pbKey == null) pbKey = LoadKeyFile(pbFileData); - - if(pbKey == null) throw new InvalidOperationException(); - - m_strPath = iocFile.Path; - m_pbKeyData = new ProtectedBinary(true, pbKey); - - MemUtil.ZeroByteArray(pbKey); - } - - // public void Clear() - // { - // m_strPath = string.Empty; - // m_pbKeyData = null; - // } - - private static byte[] LoadKeyFile(byte[] pbFileData) - { - if(pbFileData == null) { Debug.Assert(false); return null; } - - int iLength = pbFileData.Length; - - byte[] pbKey = null; - if(iLength == 32) pbKey = LoadBinaryKey32(pbFileData); - else if(iLength == 64) pbKey = LoadHexKey32(pbFileData); - - if(pbKey == null) - pbKey = CryptoUtil.HashSha256(pbFileData); - - return pbKey; - } - - private static byte[] LoadBinaryKey32(byte[] pbFileData) - { - if(pbFileData == null) { Debug.Assert(false); return null; } - if(pbFileData.Length != 32) { Debug.Assert(false); return null; } - - return pbFileData; - } - - private static byte[] LoadHexKey32(byte[] pbFileData) - { - if(pbFileData == null) { Debug.Assert(false); return null; } - if(pbFileData.Length != 64) { Debug.Assert(false); return null; } - - try - { - if(!StrUtil.IsHexString(pbFileData, true)) return null; - - string strHex = StrUtil.Utf8.GetString(pbFileData, 0, pbFileData.Length); - byte[] pbKey = MemUtil.HexStringToByteArray(strHex); - if((pbKey == null) || (pbKey.Length != 32)) - { - Debug.Assert(false); - return null; - } - - return pbKey; - } - catch(Exception) { Debug.Assert(false); } - - return null; - } - - /// - /// Create a new, random key-file. - /// - /// Path where the key-file should be saved to. - /// If the file exists already, it will be overwritten. - /// Additional entropy used to generate - /// the random key. May be null (in this case only the KeePass-internal - /// random number generator is used). - /// Returns a FileSaveResult error code. -#if ModernKeePassLib - public static void Create(StorageFile strFilePath, byte[] pbAdditionalEntropy) -#else - public static void Create(string strFilePath, byte[] pbAdditionalEntropy) -#endif - { - byte[] pbKey32 = CryptoRandom.Instance.GetRandomBytes(32); - if(pbKey32 == null) throw new SecurityException(); - - byte[] pbFinalKey32; - if((pbAdditionalEntropy == null) || (pbAdditionalEntropy.Length == 0)) - pbFinalKey32 = pbKey32; - else - { - using(MemoryStream ms = new MemoryStream()) - { - MemUtil.Write(ms, pbAdditionalEntropy); - MemUtil.Write(ms, pbKey32); - - pbFinalKey32 = CryptoUtil.HashSha256(ms.ToArray()); - } - } - - CreateXmlKeyFile(strFilePath, pbFinalKey32); - } - - // ================================================================ - // XML Key Files - // ================================================================ - - // Sample XML file: - // - // - // - // 1.00 - // - // - // ySFoKuCcJblw8ie6RkMBdVCnAf4EedSch7ItujK6bmI= - // - // - - private const string RootElementName = "KeyFile"; - private const string MetaElementName = "Meta"; - private const string VersionElementName = "Version"; - private const string KeyElementName = "Key"; - private const string KeyDataElementName = "Data"; - - private static byte[] LoadXmlKeyFile(byte[] pbFileData) - { - if(pbFileData == null) { Debug.Assert(false); return null; } - - MemoryStream ms = new MemoryStream(pbFileData, false); - byte[] pbKeyData = null; - - try - { -#if ModernKeePassLib - - var doc = XDocument.Load(ms); - - var el = doc.Root; - - if((el == null) || !el.Name.LocalName.Equals(RootElementName)) - return null; - if(el.DescendantNodes().Count() < 2) - return null; - - foreach(var xmlChild in el.Descendants()) - { - if(xmlChild.Name == MetaElementName) { } // Ignore Meta - else if(xmlChild.Name == KeyElementName) - { - foreach(var xmlKeyChild in xmlChild.Descendants()) - { - if(xmlKeyChild.Name == KeyDataElementName) - { - if(pbKeyData == null) - pbKeyData = Convert.FromBase64String(xmlKeyChild.Value); - } - } - } - } -#else - XmlDocument doc = XmlUtilEx.CreateXmlDocument(); - doc.Load(ms); - - XmlElement el = doc.DocumentElement; - if((el == null) || !el.Name.Equals(RootElementName)) return null; - if(el.ChildNodes.Count < 2) return null; - - foreach(XmlNode xmlChild in el.ChildNodes) - { - if(xmlChild.Name.Equals(MetaElementName)) { } // Ignore Meta - else if(xmlChild.Name == KeyElementName) - { - foreach(XmlNode xmlKeyChild in xmlChild.ChildNodes) - { - if(xmlKeyChild.Name == KeyDataElementName) - { - if(pbKeyData == null) - pbKeyData = Convert.FromBase64String(xmlKeyChild.InnerText); - } - } - } - } -#endif - } - catch(Exception) { pbKeyData = null; } - finally { ms.Dispose(); } - - return pbKeyData; - } -#if ModernKeePassLib - private static void CreateXmlKeyFile(StorageFile strFile, byte[] pbKeyData) -#else - private static void CreateXmlKeyFile(string strFile, byte[] pbKeyData) -#endif - { - Debug.Assert(strFile != null); - if(strFile == null) throw new ArgumentNullException("strFile"); - Debug.Assert(pbKeyData != null); - if(pbKeyData == null) throw new ArgumentNullException("pbKeyData"); - -#if ModernKeePassLib - IOConnectionInfo ioc = IOConnectionInfo.FromFile(strFile); -#else - IOConnectionInfo ioc = IOConnectionInfo.FromPath(strFile); -#endif - using(Stream s = IOConnection.OpenWrite(ioc)) - { - using(XmlWriter xw = XmlUtilEx.CreateXmlWriter(s)) - { - xw.WriteStartDocument(); - xw.WriteStartElement(RootElementName); // - - xw.WriteStartElement(MetaElementName); // - xw.WriteStartElement(VersionElementName); // - xw.WriteString("1.00"); - xw.WriteEndElement(); // - xw.WriteEndElement(); // - - xw.WriteStartElement(KeyElementName); // - - xw.WriteStartElement(KeyDataElementName); // - xw.WriteString(Convert.ToBase64String(pbKeyData)); - xw.WriteEndElement(); // - - xw.WriteEndElement(); // - - xw.WriteEndElement(); // - xw.WriteEndDocument(); - } - } - } - } -} diff --git a/ModernKeePassLib/Keys/KcpPassword.cs b/ModernKeePassLib/Keys/KcpPassword.cs deleted file mode 100644 index 940515c..0000000 --- a/ModernKeePassLib/Keys/KcpPassword.cs +++ /dev/null @@ -1,106 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Diagnostics; -using System.Text; - -using ModernKeePassLib.Security; -using ModernKeePassLib.Utility; -using ModernKeePassLib.Cryptography; - -namespace ModernKeePassLib.Keys -{ - /// - /// Master password / passphrase as provided by the user. - /// - public sealed class KcpPassword : IUserKey - { - private ProtectedString m_psPassword; - private ProtectedBinary m_pbKeyData; - - /// - /// Get the password as protected string. - /// - public ProtectedString Password - { - get { return m_psPassword; } - } - - /// - /// Get key data. Querying this property is fast (it returns a - /// reference to a cached ProtectedBinary object). - /// If no key data is available, null is returned. - /// - public ProtectedBinary KeyData - { - get { return m_pbKeyData; } - } - - public KcpPassword(byte[] pbPasswordUtf8) - { - SetKey(pbPasswordUtf8); - } - - public KcpPassword(string strPassword) - { - SetKey(StrUtil.Utf8.GetBytes(strPassword)); - } - - private void SetKey(byte[] pbPasswordUtf8) - { - Debug.Assert(pbPasswordUtf8 != null); - if(pbPasswordUtf8 == null) throw new ArgumentNullException("pbPasswordUtf8"); - -#if (DEBUG && !KeePassLibSD) - Debug.Assert(ValidatePassword(pbPasswordUtf8)); -#endif - - byte[] pbRaw = CryptoUtil.HashSha256(pbPasswordUtf8); - - m_psPassword = new ProtectedString(true, pbPasswordUtf8); - m_pbKeyData = new ProtectedBinary(true, pbRaw); - } - - // public void Clear() - // { - // m_psPassword = null; - // m_pbKeyData = null; - // } - -#if (DEBUG && !KeePassLibSD) - private static bool ValidatePassword(byte[] pb) - { - try - { -#if ModernKeePassLib - // TODO: find a way to implement this - return true; -#else - string str = StrUtil.Utf8.GetString(pb); - return str.IsNormalized(NormalizationForm.FormC); -#endif - } - catch(Exception) { Debug.Assert(false); } - - return false; - } -#endif - } -} diff --git a/ModernKeePassLib/Keys/KcpUserAccount.cs b/ModernKeePassLib/Keys/KcpUserAccount.cs deleted file mode 100644 index 6e71bb0..0000000 --- a/ModernKeePassLib/Keys/KcpUserAccount.cs +++ /dev/null @@ -1,167 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Diagnostics; -using System.IO; -using System.Security; - -#if ModernKeePassLib -using Windows.Storage; -using Windows.Security.Cryptography; -#endif - -using ModernKeePassLib.Cryptography; -using ModernKeePassLib.Native; -using ModernKeePassLib.Resources; -using ModernKeePassLib.Security; -using ModernKeePassLib.Utility; - -namespace ModernKeePassLib.Keys -{ - /// - /// A user key depending on the currently logged on Windows user account. - /// - public sealed class KcpUserAccount : IUserKey - { - private ProtectedBinary m_pbKeyData = null; - - // Constant initialization vector (unique for KeePass) - private static readonly byte[] m_pbEntropy = new byte[] { - 0xDE, 0x13, 0x5B, 0x5F, 0x18, 0xA3, 0x46, 0x70, - 0xB2, 0x57, 0x24, 0x29, 0x69, 0x88, 0x98, 0xE6 - }; - - private const string UserKeyFileName = "ProtectedUserKey.bin"; - - /// - /// Get key data. Querying this property is fast (it returns a - /// reference to a cached ProtectedBinary object). - /// If no key data is available, null is returned. - /// - public ProtectedBinary KeyData - { - get { return m_pbKeyData; } - } - - /// - /// Construct a user account key. - /// - public KcpUserAccount() - { - if(!CryptoUtil.IsProtectedDataSupported) - throw new PlatformNotSupportedException(); // Windows 98/ME - - byte[] pbKey = LoadUserKey(false); - if(pbKey == null) pbKey = CreateUserKey(); - if(pbKey == null) // Should never happen - { - Debug.Assert(false); - throw new SecurityException(KLRes.UserAccountKeyError); - } - - m_pbKeyData = new ProtectedBinary(true, pbKey); - MemUtil.ZeroByteArray(pbKey); - } - - // public void Clear() - // { - // m_pbKeyData = null; - // } - - private static string GetUserKeyFilePath(bool bCreate) - { -#if ModernKeePassLib - string strUserDir = Windows.Storage.ApplicationData.Current.RoamingFolder.Path; -#else - string strUserDir = Environment.GetFolderPath( - Environment.SpecialFolder.ApplicationData); -#endif - - strUserDir = UrlUtil.EnsureTerminatingSeparator(strUserDir, false); - strUserDir += PwDefs.ShortProductName; - -#if !ModernKeePassLib - - if(bCreate && !Directory.Exists(strUserDir)) - Directory.CreateDirectory(strUserDir); -#endif - - strUserDir = UrlUtil.EnsureTerminatingSeparator(strUserDir, false); - return (strUserDir + UserKeyFileName); - } - - private static byte[] LoadUserKey(bool bThrow) - { - byte[] pbKey = null; - -#if !KeePassLibSD - try - { - string strFilePath = GetUserKeyFilePath(false); - -#if ModernKeePassLib - var fileStream = StorageFile.GetFileFromPathAsync(strFilePath).GetAwaiter().GetResult().OpenStreamForReadAsync().GetAwaiter().GetResult(); - var pbProtectedKey = new byte[(int)fileStream.Length]; - fileStream.Read(pbProtectedKey, 0, (int)fileStream.Length); - fileStream.Dispose(); -#else - byte[] pbProtectedKey = File.ReadAllBytes(strFilePath); -#endif - - pbKey = CryptoUtil.UnprotectData(pbProtectedKey, m_pbEntropy, - DataProtectionScope.CurrentUser); - } - catch(Exception) - { - if(bThrow) throw; - pbKey = null; - } -#endif - - return pbKey; - } - - private static byte[] CreateUserKey() - { -#if KeePassLibSD - return null; -#else - string strFilePath = GetUserKeyFilePath(true); - - byte[] pbRandomKey = CryptoRandom.Instance.GetRandomBytes(64); - byte[] pbProtectedKey = CryptoUtil.ProtectData(pbRandomKey, - m_pbEntropy, DataProtectionScope.CurrentUser); -#if ModernKeePassLib - var fileStream = StorageFile.GetFileFromPathAsync(strFilePath).GetAwaiter().GetResult().OpenStreamForWriteAsync().GetAwaiter().GetResult(); - fileStream.Write(pbProtectedKey, 0, (int)fileStream.Length); - fileStream.Dispose(); -#else - File.WriteAllBytes(strFilePath, pbProtectedKey); -#endif - - byte[] pbKey = LoadUserKey(true); - Debug.Assert(MemUtil.ArraysEqual(pbKey, pbRandomKey)); - - MemUtil.ZeroByteArray(pbRandomKey); - return pbKey; -#endif - } - } -} diff --git a/ModernKeePassLib/Keys/KeyProvider.cs b/ModernKeePassLib/Keys/KeyProvider.cs deleted file mode 100644 index 812c77b..0000000 --- a/ModernKeePassLib/Keys/KeyProvider.cs +++ /dev/null @@ -1,152 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; -using System.Text; - -using ModernKeePassLib.Serialization; - -namespace ModernKeePassLib.Keys -{ - public sealed class KeyProviderQueryContext - { - private IOConnectionInfo m_ioInfo; - public IOConnectionInfo DatabaseIOInfo - { - get { return m_ioInfo; } - } - - public string DatabasePath - { - get { return m_ioInfo.Path; } - } - - private bool m_bCreatingNewKey; - public bool CreatingNewKey - { - get { return m_bCreatingNewKey; } - } - - private bool m_bSecDesktop; - public bool IsOnSecureDesktop - { - get { return m_bSecDesktop; } - } - - public KeyProviderQueryContext(IOConnectionInfo ioInfo, bool bCreatingNewKey, - bool bOnSecDesktop) - { - if(ioInfo == null) throw new ArgumentNullException("ioInfo"); - - m_ioInfo = ioInfo.CloneDeep(); - m_bCreatingNewKey = bCreatingNewKey; - m_bSecDesktop = bOnSecDesktop; - } - } - - public abstract class KeyProvider - { - /// - /// Name of your key provider (should be unique). - /// - public abstract string Name - { - get; - } - - /// - /// Property indicating whether the provider is exclusive. - /// If the provider is exclusive, KeePass doesn't allow other - /// key sources (master password, Windows user account, ...) - /// to be combined with the provider. - /// Key providers typically should return false - /// (to allow non-exclusive use), i.e. don't override this - /// property. - /// - public virtual bool Exclusive - { - get { return false; } - } - - /// - /// Property that specifies whether the returned key data - /// gets hashed by KeePass first or is written directly to - /// the user key data stream. - /// Standard key provider plugins should return false - /// (i.e. don't overwrite this property). Returning true - /// may cause severe security problems and is highly - /// discouraged. - /// - public virtual bool DirectKey - { - get { return false; } - } - - // public virtual PwIcon ImageIndex - // { - // get { return PwIcon.UserKey; } - // } - - /// - /// This property specifies whether the GetKey method might - /// show a form or dialog. If there is any chance that the method shows - /// one, this property must return true. Only if it's guaranteed - /// that the GetKey method doesn't show any form or dialog, this - /// property should return false. - /// - public virtual bool GetKeyMightShowGui - { - get { return true; } - } - - /// - /// This property specifies whether the key provider is compatible - /// with the secure desktop mode. This almost never is the case, - /// so you usually won't override this property. - /// - public virtual bool SecureDesktopCompatible - { - get { return false; } - } - - public abstract byte[] GetKey(KeyProviderQueryContext ctx); - } - -#if DEBUG - public sealed class SampleKeyProvider : KeyProvider - { - public override string Name - { - get { return "Built-In Sample Key Provider"; } - } - - // Do not just copy this to your own key provider class! See above. - public override bool GetKeyMightShowGui - { - get { return false; } - } - - public override byte[] GetKey(KeyProviderQueryContext ctx) - { - return new byte[]{ 2, 3, 5, 7, 11, 13 }; - } - } -#endif -} diff --git a/ModernKeePassLib/Keys/KeyProviderPool.cs b/ModernKeePassLib/Keys/KeyProviderPool.cs deleted file mode 100644 index 06163a6..0000000 --- a/ModernKeePassLib/Keys/KeyProviderPool.cs +++ /dev/null @@ -1,105 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Text; -using System.Diagnostics; - -namespace ModernKeePassLib.Keys -{ - public sealed class KeyProviderPool : IEnumerable - { - private List m_vProviders = new List(); - - public int Count - { - get { return m_vProviders.Count; } - } - - IEnumerator IEnumerable.GetEnumerator() - { - return m_vProviders.GetEnumerator(); - } - - public IEnumerator GetEnumerator() - { - return m_vProviders.GetEnumerator(); - } - - public void Add(KeyProvider prov) - { - Debug.Assert(prov != null); if(prov == null) throw new ArgumentNullException("prov"); - - m_vProviders.Add(prov); - } - - public bool Remove(KeyProvider prov) - { - Debug.Assert(prov != null); if(prov == null) throw new ArgumentNullException("prov"); - - return m_vProviders.Remove(prov); - } - - public KeyProvider Get(string strProviderName) - { - if(strProviderName == null) throw new ArgumentNullException("strProviderName"); - - foreach(KeyProvider prov in m_vProviders) - { - if(prov.Name == strProviderName) return prov; - } - - return null; - } - - public bool IsKeyProvider(string strName) - { - Debug.Assert(strName != null); if(strName == null) throw new ArgumentNullException("strName"); - - foreach(KeyProvider prov in m_vProviders) - { - if(prov.Name == strName) return true; - } - - return false; - } - - internal byte[] GetKey(string strProviderName, KeyProviderQueryContext ctx, - out bool bPerformHash) - { - Debug.Assert(strProviderName != null); if(strProviderName == null) throw new ArgumentNullException("strProviderName"); - - bPerformHash = true; - - foreach(KeyProvider prov in m_vProviders) - { - if(prov.Name == strProviderName) - { - bPerformHash = !prov.DirectKey; - return prov.GetKey(ctx); - } - } - - Debug.Assert(false); - return null; - } - } -} diff --git a/ModernKeePassLib/Keys/KeyValidator.cs b/ModernKeePassLib/Keys/KeyValidator.cs deleted file mode 100644 index df7fbd6..0000000 --- a/ModernKeePassLib/Keys/KeyValidator.cs +++ /dev/null @@ -1,51 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; -using System.Text; - -namespace ModernKeePassLib.Keys -{ - public enum KeyValidationType - { - MasterPassword = 0 - } - - public abstract class KeyValidator - { - /// - /// Name of your key validator (should be unique). - /// - public abstract string Name - { - get; - } - - /// - /// Validate a key. - /// - /// Key to validate. - /// Type of the validation to perform. - /// Returns null, if the validation is successful. - /// If there's a problem with the key, the returned string describes - /// the problem. - public abstract string Validate(string strKey, KeyValidationType t); - } -} diff --git a/ModernKeePassLib/Keys/KeyValidatorPool.cs b/ModernKeePassLib/Keys/KeyValidatorPool.cs deleted file mode 100644 index 66a0cd3..0000000 --- a/ModernKeePassLib/Keys/KeyValidatorPool.cs +++ /dev/null @@ -1,86 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Text; -using System.Diagnostics; - -using ModernKeePassLib.Utility; - -namespace ModernKeePassLib.Keys -{ - public sealed class KeyValidatorPool : IEnumerable - { - private List m_vValidators = new List(); - - public int Count - { - get { return m_vValidators.Count; } - } - - IEnumerator IEnumerable.GetEnumerator() - { - return m_vValidators.GetEnumerator(); - } - - public IEnumerator GetEnumerator() - { - return m_vValidators.GetEnumerator(); - } - - public void Add(KeyValidator v) - { - Debug.Assert(v != null); if(v == null) throw new ArgumentNullException("v"); - - m_vValidators.Add(v); - } - - public bool Remove(KeyValidator v) - { - Debug.Assert(v != null); if(v == null) throw new ArgumentNullException("v"); - - return m_vValidators.Remove(v); - } - - public string Validate(string strKey, KeyValidationType t) - { - Debug.Assert(strKey != null); if(strKey == null) throw new ArgumentNullException("strKey"); - - foreach(KeyValidator v in m_vValidators) - { - string strResult = v.Validate(strKey, t); - if(strResult != null) return strResult; - } - - return null; - } - - public string Validate(byte[] pbKeyUtf8, KeyValidationType t) - { - Debug.Assert(pbKeyUtf8 != null); if(pbKeyUtf8 == null) throw new ArgumentNullException("pbKeyUtf8"); - - if(m_vValidators.Count == 0) return null; - - string strKey = StrUtil.Utf8.GetString(pbKeyUtf8, 0, pbKeyUtf8.Length); - return Validate(strKey, t); - } - } -} diff --git a/ModernKeePassLib/Keys/UserKeyType.cs b/ModernKeePassLib/Keys/UserKeyType.cs deleted file mode 100644 index ad8f84e..0000000 --- a/ModernKeePassLib/Keys/UserKeyType.cs +++ /dev/null @@ -1,33 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; - -namespace ModernKeePassLib.Keys -{ - [Flags] - public enum UserKeyType - { - None = 0, - Other = 1, - Password = 2, - KeyFile = 4, - UserAccount = 8 - } -} diff --git a/ModernKeePassLib/ModernKeePass.Lib.csproj b/ModernKeePassLib/ModernKeePass.Lib.csproj deleted file mode 100644 index 0865971..0000000 --- a/ModernKeePassLib/ModernKeePass.Lib.csproj +++ /dev/null @@ -1,188 +0,0 @@ - - - - Debug - AnyCPU - {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - {2E710089-9559-4967-846C-E763DD1F3ACB} - Library - Properties - ModernKeePassLib - ModernKeePassLib - false - - - 2.0 - - - - - v5.0 - KeePassLib.pfx - - - 14.0 - - - true - full - false - bin\Debug - DEBUG;ModernKeePassLib - prompt - 4 - AnyCPU - - - true - bin\Release - prompt - 4 - ModernKeePassLib - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - False - Libs\Windows.winmd - False - - - - \ No newline at end of file diff --git a/ModernKeePassLib/ModernKeePassLib.nuget.targets b/ModernKeePassLib/ModernKeePassLib.nuget.targets deleted file mode 100644 index a00610d..0000000 --- a/ModernKeePassLib/ModernKeePassLib.nuget.targets +++ /dev/null @@ -1,9 +0,0 @@ - - - - $(UserProfile)\.nuget\packages\ - - - - - \ No newline at end of file diff --git a/ModernKeePassLib/ModernKeePassLib.nuspec b/ModernKeePassLib/ModernKeePassLib.nuspec deleted file mode 100644 index e6e594b..0000000 --- a/ModernKeePassLib/ModernKeePassLib.nuspec +++ /dev/null @@ -1,36 +0,0 @@ - - - - ModernKeePassLib - 2.39.1 - ModernKeePassLib - Geoffroy Bonneville - Geoffroy Bonneville - https://www.gnu.org/licenses/gpl-3.0.en.html - https://github.com/wismna/ModernKeePass - false - Portable KeePass Password Management Library that targets .Net Standard and WinRT. Allows reading, editing and writing to KeePass 2.x databases. - Implementation of KeePass library version 2.39.1 - Copyright © 2018 Geoffroy Bonneville - KeePass KeePassLib Portable PCL NetStandard - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/ModernKeePassLib/ModernKeePassLibPCL.nuget.targets b/ModernKeePassLib/ModernKeePassLibPCL.nuget.targets deleted file mode 100644 index 8930d73..0000000 --- a/ModernKeePassLib/ModernKeePassLibPCL.nuget.targets +++ /dev/null @@ -1,9 +0,0 @@ - - - - $(UserProfile)\.nuget\packages\ - - - - - \ No newline at end of file diff --git a/ModernKeePassLib/Native/ClipboardU.cs b/ModernKeePassLib/Native/ClipboardU.cs deleted file mode 100644 index 87760b4..0000000 --- a/ModernKeePassLib/Native/ClipboardU.cs +++ /dev/null @@ -1,190 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Reflection; -using System.Text; - -namespace ModernKeePassLib.Native -{ - internal static class ClipboardU - { - private const string XSel = "xsel"; - private const string XSelV = "--version"; - private const string XSelR = "--output --clipboard"; - private const string XSelC = "--clear --clipboard"; - private const string XSelW = "--input --clipboard"; - private const string XSelND = " --nodetach"; - private const AppRunFlags XSelWF = AppRunFlags.WaitForExit; - - private static bool? g_obXSel = null; - - public static string GetText() - { - // System.Windows.Forms.Clipboard doesn't work properly, - // see Mono workaround 1530 - - // string str = GtkGetText(); - // if(str != null) return str; - - return XSelGetText(); - } - - public static bool SetText(string strText, bool bMayBlock) - { - string str = (strText ?? string.Empty); - - // System.Windows.Forms.Clipboard doesn't work properly, - // see Mono workaround 1530 - - // if(GtkSetText(str)) return true; - - return XSelSetText(str, bMayBlock); - } - - // ============================================================= - // LibGTK - - // Even though GTK+ 3 appears to be loaded already, performing a - // P/Invoke of LibGTK's gtk_init_check function terminates the - // process (!) with the following error message: - // "Gtk-ERROR **: GTK+ 2.x symbols detected. Using GTK+ 2.x and - // GTK+ 3 in the same process is not supported". - - /* private static bool GtkInit() - { - try - { - // GTK requires GLib; - // the following throws if and only if GLib is unavailable - NativeMethods.g_free(IntPtr.Zero); - - if(NativeMethods.gtk_init_check(IntPtr.Zero, IntPtr.Zero) != - NativeMethods.G_FALSE) - return true; - - Debug.Assert(false); - } - catch(Exception) { Debug.Assert(false); } - - return false; - } - - private static string GtkGetText() - { - IntPtr lpText = IntPtr.Zero; - try - { - if(GtkInit()) - { - IntPtr h = NativeMethods.gtk_clipboard_get( - NativeMethods.GDK_SELECTION_CLIPBOARD); - if(h != IntPtr.Zero) - { - lpText = NativeMethods.gtk_clipboard_wait_for_text(h); - if(lpText != IntPtr.Zero) - return NativeMethods.Utf8ZToString(lpText); - } - } - } - catch(Exception) { Debug.Assert(false); } - finally - { - try { NativeMethods.g_free(lpText); } - catch(Exception) { Debug.Assert(false); } - } - - return null; - } - - private static bool GtkSetText(string str) - { - IntPtr lpText = IntPtr.Zero; - try - { - if(GtkInit()) - { - lpText = NativeMethods.Utf8ZFromString(str ?? string.Empty); - if(lpText == IntPtr.Zero) { Debug.Assert(false); return false; } - - bool b = false; - for(int i = 0; i < 2; ++i) - { - IntPtr h = NativeMethods.gtk_clipboard_get((i == 0) ? - NativeMethods.GDK_SELECTION_PRIMARY : - NativeMethods.GDK_SELECTION_CLIPBOARD); - if(h != IntPtr.Zero) - { - NativeMethods.gtk_clipboard_clear(h); - NativeMethods.gtk_clipboard_set_text(h, lpText, -1); - NativeMethods.gtk_clipboard_store(h); - b = true; - } - } - - return b; - } - } - catch(Exception) { Debug.Assert(false); } - finally { NativeMethods.Utf8ZFree(lpText); } - - return false; - } */ - - // ============================================================= - // XSel - - private static bool XSelInit() - { - if(g_obXSel.HasValue) return g_obXSel.Value; - - string strTest = NativeLib.RunConsoleApp(XSel, XSelV); - - bool b = (strTest != null); - g_obXSel = b; - return b; - } - - private static string XSelGetText() - { - if(!XSelInit()) return null; - - return NativeLib.RunConsoleApp(XSel, XSelR); - } - - private static bool XSelSetText(string str, bool bMayBlock) - { - if(!XSelInit()) return false; - - string strOpt = (bMayBlock ? XSelND : string.Empty); - - // xsel with an empty input can hang, thus use --clear - if(str.Length == 0) - return (NativeLib.RunConsoleApp(XSel, XSelC + strOpt, - null, XSelWF) != null); - - // Use --nodetach to prevent clipboard corruption; - // https://sourceforge.net/p/keepass/bugs/1603/ - return (NativeLib.RunConsoleApp(XSel, XSelW + strOpt, - str, XSelWF) != null); - } - } -} diff --git a/ModernKeePassLib/Native/Native.PCL.cs b/ModernKeePassLib/Native/Native.PCL.cs deleted file mode 100644 index a9a777c..0000000 --- a/ModernKeePassLib/Native/Native.PCL.cs +++ /dev/null @@ -1,88 +0,0 @@ -using System; - -using PlatformID = System.UInt32; - -namespace ModernKeePassLib.Native -{ - internal static class NativeLib - { - public static ulong MonoVersion { - get { throw new NotImplementedException(); } - } - - public static bool IsUnix() - { - return true; - } - } - - internal static class NativeMethods - { - public static bool SupportsStrCmpNaturally => false; - - internal const int GCRY_CIPHER_AES256 = 9; - internal const int GCRY_CIPHER_MODE_ECB = 1; - - public static int StrCmpNaturally (string s1, string s2) - { - throw new NotImplementedException(); - } - - internal static void gcry_check_version(IntPtr zero) - { - throw new NotImplementedException(); - } - - public static void gcry_cipher_open(ref IntPtr intPtr, object gcryCipherAes256, object gcryCipherModeEcb, int i) - { - throw new NotImplementedException(); - } - - internal static int gcry_cipher_setkey(IntPtr h, IntPtr pSeed32, IntPtr n32) - { - throw new NotImplementedException(); - } - - internal static void gcry_cipher_close(IntPtr h) - { - throw new NotImplementedException(); - } - - internal static int gcry_cipher_encrypt(IntPtr h, IntPtr pData32, IntPtr n32, IntPtr zero1, IntPtr zero2) - { - throw new NotImplementedException(); - } - - public static string GetUserRuntimeDir() - { - throw new NotImplementedException(); - } - } - - public enum DataProtectionScope - { - CurrentUser, - LocalMachine - } - - internal enum MemoryProtectionScope - { - CrossProcess, - SameLogon, - SameProcess - } - - internal static class ProtectedMemory - { - public static byte[] Protect(byte[] userData, MemoryProtectionScope scope) - { - throw new NotImplementedException(); - } - - public static byte[] Unprotect(byte[] userData, MemoryProtectionScope scope) - { - throw new NotImplementedException(); - } - } -} - diff --git a/ModernKeePassLib/Native/NativeLib.cs b/ModernKeePassLib/Native/NativeLib.cs deleted file mode 100644 index bfaee1e..0000000 --- a/ModernKeePassLib/Native/NativeLib.cs +++ /dev/null @@ -1,449 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Reflection; -using System.Runtime.InteropServices; -using System.Text; -using System.Text.RegularExpressions; - -#if !KeePassUAP -using System.IO; -using System.Threading; -using System.Windows.Forms; -#endif - -using ModernKeePassLib.Utility; - -namespace ModernKeePassLib.Native -{ - /// - /// Interface to native library (library containing fast versions of - /// several cryptographic functions). - /// - public static class NativeLib - { - private static bool m_bAllowNative = true; - - /// - /// If this property is set to true, the native library is used. - /// If it is false, all calls to functions in this class will fail. - /// - public static bool AllowNative - { - get { return m_bAllowNative; } - set { m_bAllowNative = value; } - } - - private static ulong? m_ouMonoVersion = null; - public static ulong MonoVersion - { - get - { - if(m_ouMonoVersion.HasValue) return m_ouMonoVersion.Value; - - ulong uVersion = 0; - try - { - Type t = Type.GetType("Mono.Runtime"); - if(t != null) - { - MethodInfo mi = t.GetMethod("GetDisplayName", - BindingFlags.NonPublic | BindingFlags.Static); - if(mi != null) - { - string strName = (mi.Invoke(null, null) as string); - if(!string.IsNullOrEmpty(strName)) - { - Match m = Regex.Match(strName, "\\d+(\\.\\d+)+"); - if(m.Success) - uVersion = StrUtil.ParseVersion(m.Value); - else { Debug.Assert(false); } - } - else { Debug.Assert(false); } - } - else { Debug.Assert(false); } - } - } - catch(Exception) { Debug.Assert(false); } - - m_ouMonoVersion = uVersion; - return uVersion; - } - } - - /// - /// Determine if the native library is installed. - /// - /// Returns true, if the native library is installed. - public static bool IsLibraryInstalled() - { - byte[] pDummy0 = new byte[32]; - byte[] pDummy1 = new byte[32]; - - // Save the native state - bool bCachedNativeState = m_bAllowNative; - - // Temporarily allow native functions and try to load the library - m_bAllowNative = true; - bool bResult = TransformKey256(pDummy0, pDummy1, 16); - - // Pop native state and return result - m_bAllowNative = bCachedNativeState; - return bResult; - } - - private static bool? m_bIsUnix = null; - public static bool IsUnix() - { - if(m_bIsUnix.HasValue) return m_bIsUnix.Value; - - PlatformID p = GetPlatformID(); - - // Mono defines Unix as 128 in early .NET versions -#if !KeePassLibSD - m_bIsUnix = ((p == PlatformID.Unix) || (p == PlatformID.MacOSX) || - ((int)p == 128)); -#else - m_bIsUnix = (((int)p == 4) || ((int)p == 6) || ((int)p == 128)); -#endif - return m_bIsUnix.Value; - } - - private static PlatformID? m_platID = null; - public static PlatformID GetPlatformID() - { - if(m_platID.HasValue) return m_platID.Value; - -#if KeePassUAP - m_platID = EnvironmentExt.OSVersion.Platform; -#else - m_platID = Environment.OSVersion.Platform; -#endif - -#if (!KeePassLibSD && !KeePassUAP) - // Mono returns PlatformID.Unix on Mac OS X, workaround this - if(m_platID.Value == PlatformID.Unix) - { - if((RunConsoleApp("uname", null) ?? string.Empty).Trim().Equals( - "Darwin", StrUtil.CaseIgnoreCmp)) - m_platID = PlatformID.MacOSX; - } -#endif - - return m_platID.Value; - } - - private static DesktopType? m_tDesktop = null; - public static DesktopType GetDesktopType() - { - if(!m_tDesktop.HasValue) - { - DesktopType t = DesktopType.None; - if(!IsUnix()) t = DesktopType.Windows; - else - { - try - { - string strXdg = (Environment.GetEnvironmentVariable( - "XDG_CURRENT_DESKTOP") ?? string.Empty).Trim(); - string strGdm = (Environment.GetEnvironmentVariable( - "GDMSESSION") ?? string.Empty).Trim(); - StringComparison sc = StrUtil.CaseIgnoreCmp; - - if(strXdg.Equals("Unity", sc)) - t = DesktopType.Unity; - else if(strXdg.Equals("LXDE", sc)) - t = DesktopType.Lxde; - else if(strXdg.Equals("XFCE", sc)) - t = DesktopType.Xfce; - else if(strXdg.Equals("MATE", sc)) - t = DesktopType.Mate; - else if(strXdg.Equals("X-Cinnamon", sc)) // Mint 18.3 - t = DesktopType.Cinnamon; - else if(strXdg.Equals("Pantheon", sc)) // Elementary OS - t = DesktopType.Pantheon; - else if(strXdg.Equals("KDE", sc) || // Mint 16, Kubuntu 17.10 - strGdm.Equals("kde-plasma", sc)) // Ubuntu 12.04 - t = DesktopType.Kde; - else if(strXdg.Equals("GNOME", sc)) - { - if(strGdm.Equals("cinnamon", sc)) // Mint 13 - t = DesktopType.Cinnamon; - else t = DesktopType.Gnome; // Fedora 27 - } - else if(strXdg.Equals("ubuntu:GNOME", sc)) - t = DesktopType.Gnome; - } - catch(Exception) { Debug.Assert(false); } - } - - m_tDesktop = t; - } - - return m_tDesktop.Value; - } - -#if (!KeePassLibSD && !KeePassUAP) - public static string RunConsoleApp(string strAppPath, string strParams) - { - return RunConsoleApp(strAppPath, strParams, null); - } - - public static string RunConsoleApp(string strAppPath, string strParams, - string strStdInput) - { - return RunConsoleApp(strAppPath, strParams, strStdInput, - (AppRunFlags.GetStdOutput | AppRunFlags.WaitForExit)); - } - - private delegate string RunProcessDelegate(); - - public static string RunConsoleApp(string strAppPath, string strParams, - string strStdInput, AppRunFlags f) - { - if(strAppPath == null) throw new ArgumentNullException("strAppPath"); - if(strAppPath.Length == 0) throw new ArgumentException("strAppPath"); - - bool bStdOut = ((f & AppRunFlags.GetStdOutput) != AppRunFlags.None); - - RunProcessDelegate fnRun = delegate() - { - Process pToDispose = null; - try - { - ProcessStartInfo psi = new ProcessStartInfo(); - - psi.CreateNoWindow = true; - psi.FileName = strAppPath; - psi.WindowStyle = ProcessWindowStyle.Hidden; - psi.UseShellExecute = false; - psi.RedirectStandardOutput = bStdOut; - - if(strStdInput != null) psi.RedirectStandardInput = true; - - if(!string.IsNullOrEmpty(strParams)) psi.Arguments = strParams; - - Process p = Process.Start(psi); - pToDispose = p; - - if(strStdInput != null) - { - EnsureNoBom(p.StandardInput); - - p.StandardInput.Write(strStdInput); - p.StandardInput.Close(); - } - - string strOutput = string.Empty; - if(bStdOut) strOutput = p.StandardOutput.ReadToEnd(); - - if((f & AppRunFlags.WaitForExit) != AppRunFlags.None) - p.WaitForExit(); - else if((f & AppRunFlags.GCKeepAlive) != AppRunFlags.None) - { - pToDispose = null; // Thread disposes it - - Thread th = new Thread(delegate() - { - try { p.WaitForExit(); p.Dispose(); } - catch(Exception) { Debug.Assert(false); } - }); - th.Start(); - } - - return strOutput; - } -#if DEBUG - catch(Exception ex) { Debug.Assert(ex is ThreadAbortException); } -#else - catch(Exception) { } -#endif - finally - { - try { if(pToDispose != null) pToDispose.Dispose(); } - catch(Exception) { Debug.Assert(false); } - } - - return null; - }; - - if((f & AppRunFlags.DoEvents) != AppRunFlags.None) - { - List
lDisabledForms = new List(); - if((f & AppRunFlags.DisableForms) != AppRunFlags.None) - { - foreach(Form form in Application.OpenForms) - { - if(!form.Enabled) continue; - - lDisabledForms.Add(form); - form.Enabled = false; - } - } - - IAsyncResult ar = fnRun.BeginInvoke(null, null); - - while(!ar.AsyncWaitHandle.WaitOne(0)) - { - Application.DoEvents(); - Thread.Sleep(2); - } - - string strRet = fnRun.EndInvoke(ar); - - for(int i = lDisabledForms.Count - 1; i >= 0; --i) - lDisabledForms[i].Enabled = true; - - return strRet; - } - - return fnRun(); - } - - private static void EnsureNoBom(StreamWriter sw) - { - if(sw == null) { Debug.Assert(false); return; } - if(!MonoWorkarounds.IsRequired(1219)) return; - - try - { - Encoding enc = sw.Encoding; - if(enc == null) { Debug.Assert(false); return; } - byte[] pbBom = enc.GetPreamble(); - if((pbBom == null) || (pbBom.Length == 0)) return; - - // For Mono >= 4.0 (using Microsoft's reference source) - try - { - FieldInfo fi = typeof(StreamWriter).GetField("haveWrittenPreamble", - BindingFlags.Instance | BindingFlags.NonPublic); - if(fi != null) - { - fi.SetValue(sw, true); - return; - } - } - catch(Exception) { Debug.Assert(false); } - - // For Mono < 4.0 - FieldInfo fiPD = typeof(StreamWriter).GetField("preamble_done", - BindingFlags.Instance | BindingFlags.NonPublic); - if(fiPD != null) fiPD.SetValue(sw, true); - else { Debug.Assert(false); } - } - catch(Exception) { Debug.Assert(false); } - } -#endif - - /// - /// Transform a key. - /// - /// Source and destination buffer. - /// Key to use in the transformation. - /// Number of transformation rounds. - /// Returns true, if the key was transformed successfully. - public static bool TransformKey256(byte[] pBuf256, byte[] pKey256, - ulong uRounds) - { -#if KeePassUAP - return false; -#else - if(!m_bAllowNative) return false; - - KeyValuePair kvp = PrepareArrays256(pBuf256, pKey256); - bool bResult = false; - - try - { - bResult = NativeMethods.TransformKey(kvp.Key, kvp.Value, uRounds); - } - catch(Exception) { bResult = false; } - - if(bResult) GetBuffers256(kvp, pBuf256, pKey256); - - FreeArrays(kvp); - return bResult; -#endif - } - - /// - /// Benchmark key transformation. - /// - /// Number of milliseconds to perform the benchmark. - /// Number of transformations done. - /// Returns true, if the benchmark was successful. - public static bool TransformKeyBenchmark256(uint uTimeMs, out ulong puRounds) - { - puRounds = 0; - -#if KeePassUAP - return false; -#else - if(!m_bAllowNative) return false; - - try { puRounds = NativeMethods.TransformKeyBenchmark(uTimeMs); } - catch(Exception) { return false; } - - return true; -#endif - } - - private static KeyValuePair PrepareArrays256(byte[] pBuf256, - byte[] pKey256) - { - Debug.Assert((pBuf256 != null) && (pBuf256.Length == 32)); - if(pBuf256 == null) throw new ArgumentNullException("pBuf256"); - if(pBuf256.Length != 32) throw new ArgumentException(); - - Debug.Assert((pKey256 != null) && (pKey256.Length == 32)); - if(pKey256 == null) throw new ArgumentNullException("pKey256"); - if(pKey256.Length != 32) throw new ArgumentException(); - - IntPtr hBuf = Marshal.AllocHGlobal(pBuf256.Length); - Marshal.Copy(pBuf256, 0, hBuf, pBuf256.Length); - - IntPtr hKey = Marshal.AllocHGlobal(pKey256.Length); - Marshal.Copy(pKey256, 0, hKey, pKey256.Length); - - return new KeyValuePair(hBuf, hKey); - } - - private static void GetBuffers256(KeyValuePair kvpSource, - byte[] pbDestBuf, byte[] pbDestKey) - { - if(kvpSource.Key != IntPtr.Zero) - Marshal.Copy(kvpSource.Key, pbDestBuf, 0, pbDestBuf.Length); - - if(kvpSource.Value != IntPtr.Zero) - Marshal.Copy(kvpSource.Value, pbDestKey, 0, pbDestKey.Length); - } - - private static void FreeArrays(KeyValuePair kvpPointers) - { - if(kvpPointers.Key != IntPtr.Zero) - Marshal.FreeHGlobal(kvpPointers.Key); - - if(kvpPointers.Value != IntPtr.Zero) - Marshal.FreeHGlobal(kvpPointers.Value); - } - } -} diff --git a/ModernKeePassLib/Native/NativeMethods.Unix.cs b/ModernKeePassLib/Native/NativeMethods.Unix.cs deleted file mode 100644 index 04a8b99..0000000 --- a/ModernKeePassLib/Native/NativeMethods.Unix.cs +++ /dev/null @@ -1,216 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Reflection; -using System.Runtime.InteropServices; -using System.Text; - -#if !KeePassUAP -using System.Windows.Forms; -#endif - -namespace ModernKeePassLib.Native -{ - internal static partial class NativeMethods - { -#if (!KeePassLibSD && !KeePassUAP) - [StructLayout(LayoutKind.Sequential)] - private struct XClassHint - { - public IntPtr res_name; - public IntPtr res_class; - } - - [DllImport("libX11")] - private static extern int XSetClassHint(IntPtr display, IntPtr window, IntPtr class_hints); - - private static Type m_tXplatUIX11 = null; - private static Type GetXplatUIX11Type(bool bThrowOnError) - { - if(m_tXplatUIX11 == null) - { - // CheckState is in System.Windows.Forms - string strTypeCS = typeof(CheckState).AssemblyQualifiedName; - string strTypeX11 = strTypeCS.Replace("CheckState", "XplatUIX11"); - m_tXplatUIX11 = Type.GetType(strTypeX11, bThrowOnError, true); - } - - return m_tXplatUIX11; - } - - private static Type m_tHwnd = null; - private static Type GetHwndType(bool bThrowOnError) - { - if(m_tHwnd == null) - { - // CheckState is in System.Windows.Forms - string strTypeCS = typeof(CheckState).AssemblyQualifiedName; - string strTypeHwnd = strTypeCS.Replace("CheckState", "Hwnd"); - m_tHwnd = Type.GetType(strTypeHwnd, bThrowOnError, true); - } - - return m_tHwnd; - } - - internal static void SetWmClass(Form f, string strName, string strClass) - { - if(f == null) { Debug.Assert(false); return; } - - // The following crashes under Mac OS X (SIGSEGV in native code, - // not just an exception), thus skip it when we're on Mac OS X; - // https://sourceforge.net/projects/keepass/forums/forum/329221/topic/5860588 - if(NativeLib.GetPlatformID() == PlatformID.MacOSX) return; - - try - { - Type tXplatUIX11 = GetXplatUIX11Type(true); - FieldInfo fiDisplayHandle = tXplatUIX11.GetField("DisplayHandle", - BindingFlags.NonPublic | BindingFlags.Static); - IntPtr hDisplay = (IntPtr)fiDisplayHandle.GetValue(null); - - Type tHwnd = GetHwndType(true); - MethodInfo miObjectFromHandle = tHwnd.GetMethod("ObjectFromHandle", - BindingFlags.Public | BindingFlags.Static); - object oHwnd = miObjectFromHandle.Invoke(null, new object[] { f.Handle }); - - FieldInfo fiWholeWindow = tHwnd.GetField("whole_window", - BindingFlags.NonPublic | BindingFlags.Instance); - IntPtr hWindow = (IntPtr)fiWholeWindow.GetValue(oHwnd); - - XClassHint xch = new XClassHint(); - xch.res_name = Marshal.StringToCoTaskMemAnsi(strName ?? string.Empty); - xch.res_class = Marshal.StringToCoTaskMemAnsi(strClass ?? string.Empty); - IntPtr pXch = Marshal.AllocCoTaskMem(Marshal.SizeOf(xch)); - Marshal.StructureToPtr(xch, pXch, false); - - XSetClassHint(hDisplay, hWindow, pXch); - - Marshal.FreeCoTaskMem(pXch); - Marshal.FreeCoTaskMem(xch.res_name); - Marshal.FreeCoTaskMem(xch.res_class); - } - catch(Exception) { Debug.Assert(false); } - } -#endif - - // ============================================================= - // LibGCrypt 1.8.1 - - private const string LibGCrypt = "libgcrypt.so.20"; - - internal const int GCRY_CIPHER_AES256 = 9; - internal const int GCRY_CIPHER_MODE_ECB = 1; - - [DllImport(LibGCrypt)] - internal static extern IntPtr gcry_check_version(IntPtr lpReqVersion); - - [DllImport(LibGCrypt)] - internal static extern uint gcry_cipher_open(ref IntPtr ph, int nAlgo, - int nMode, uint uFlags); - - [DllImport(LibGCrypt)] - internal static extern void gcry_cipher_close(IntPtr h); - - [DllImport(LibGCrypt)] - internal static extern uint gcry_cipher_setkey(IntPtr h, IntPtr pbKey, - IntPtr cbKey); // cbKey is size_t - - [DllImport(LibGCrypt)] - internal static extern uint gcry_cipher_encrypt(IntPtr h, IntPtr pbOut, - IntPtr cbOut, IntPtr pbIn, IntPtr cbIn); // cb* are size_t - - /* internal static IntPtr Utf8ZFromString(string str) - { - byte[] pb = StrUtil.Utf8.GetBytes(str ?? string.Empty); - - IntPtr p = Marshal.AllocCoTaskMem(pb.Length + 1); - if(p != IntPtr.Zero) - { - Marshal.Copy(pb, 0, p, pb.Length); - Marshal.WriteByte(p, pb.Length, 0); - } - else { Debug.Assert(false); } - - return p; - } - - internal static string Utf8ZToString(IntPtr p) - { - if(p == IntPtr.Zero) { Debug.Assert(false); return null; } - - List l = new List(); - for(int i = 0; i < int.MaxValue; ++i) - { - byte bt = Marshal.ReadByte(p, i); - if(bt == 0) break; - - l.Add(bt); - } - - return StrUtil.Utf8.GetString(l.ToArray()); - } - - internal static void Utf8ZFree(IntPtr p) - { - if(p != IntPtr.Zero) Marshal.FreeCoTaskMem(p); - } */ - - /* // ============================================================= - // LibGLib 2 - - private const string LibGLib = "libglib-2.0.so.0"; - - internal const int G_FALSE = 0; - - // https://developer.gnome.org/glib/stable/glib-Memory-Allocation.html - [DllImport(LibGLib)] - internal static extern void g_free(IntPtr pMem); // pMem may be null - - // ============================================================= - // LibGTK 3 (3.22.11 / 3.22.24) - - private const string LibGtk = "libgtk-3.so.0"; - - internal static readonly IntPtr GDK_SELECTION_PRIMARY = new IntPtr(1); - internal static readonly IntPtr GDK_SELECTION_CLIPBOARD = new IntPtr(69); - - [DllImport(LibGtk)] - internal static extern int gtk_init_check(IntPtr pArgc, IntPtr pArgv); - - [DllImport(LibGtk)] - // The returned handle is owned by GTK and must not be freed - internal static extern IntPtr gtk_clipboard_get(IntPtr pSelection); - - [DllImport(LibGtk)] - internal static extern void gtk_clipboard_clear(IntPtr hClipboard); - - [DllImport(LibGtk)] - internal static extern IntPtr gtk_clipboard_wait_for_text(IntPtr hClipboard); - - [DllImport(LibGtk)] - internal static extern void gtk_clipboard_set_text(IntPtr hClipboard, - IntPtr lpText, int cbLen); - - [DllImport(LibGtk)] - internal static extern void gtk_clipboard_store(IntPtr hClipboard); */ - } -} diff --git a/ModernKeePassLib/Native/NativeMethods.cs b/ModernKeePassLib/Native/NativeMethods.cs deleted file mode 100644 index c6fbdeb..0000000 --- a/ModernKeePassLib/Native/NativeMethods.cs +++ /dev/null @@ -1,260 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Diagnostics; -using System.IO; -using System.Runtime.InteropServices; -using System.Text; - -using ModernKeePassLib.Utility; - -namespace ModernKeePassLib.Native -{ - internal static partial class NativeMethods - { - internal const int MAX_PATH = 260; - - internal const long INVALID_HANDLE_VALUE = -1; - - internal const uint MOVEFILE_REPLACE_EXISTING = 0x00000001; - internal const uint MOVEFILE_COPY_ALLOWED = 0x00000002; - - internal const uint FILE_SUPPORTS_TRANSACTIONS = 0x00200000; - internal const int MAX_TRANSACTION_DESCRIPTION_LENGTH = 64; - - // internal const uint TF_SFT_SHOWNORMAL = 0x00000001; - // internal const uint TF_SFT_HIDDEN = 0x00000008; - - /* [DllImport("KeePassNtv32.dll", EntryPoint = "TransformKey")] - [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool TransformKey32(IntPtr pBuf256, - IntPtr pKey256, UInt64 uRounds); - - [DllImport("KeePassNtv64.dll", EntryPoint = "TransformKey")] - [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool TransformKey64(IntPtr pBuf256, - IntPtr pKey256, UInt64 uRounds); - - internal static bool TransformKey(IntPtr pBuf256, IntPtr pKey256, - UInt64 uRounds) - { - if(IntPtr.Size == 4) - return TransformKey32(pBuf256, pKey256, uRounds); - return TransformKey64(pBuf256, pKey256, uRounds); - } - - [DllImport("KeePassNtv32.dll", EntryPoint = "TransformKeyTimed")] - [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool TransformKeyTimed32(IntPtr pBuf256, - IntPtr pKey256, ref UInt64 puRounds, UInt32 uSeconds); - - [DllImport("KeePassNtv64.dll", EntryPoint = "TransformKeyTimed")] - [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool TransformKeyTimed64(IntPtr pBuf256, - IntPtr pKey256, ref UInt64 puRounds, UInt32 uSeconds); - - internal static bool TransformKeyTimed(IntPtr pBuf256, IntPtr pKey256, - ref UInt64 puRounds, UInt32 uSeconds) - { - if(IntPtr.Size == 4) - return TransformKeyTimed32(pBuf256, pKey256, ref puRounds, uSeconds); - return TransformKeyTimed64(pBuf256, pKey256, ref puRounds, uSeconds); - } */ - -#if !KeePassUAP - [DllImport("KeePassLibC32.dll", EntryPoint = "TransformKey256")] - [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool TransformKey32(IntPtr pBuf256, - IntPtr pKey256, UInt64 uRounds); - - [DllImport("KeePassLibC64.dll", EntryPoint = "TransformKey256")] - [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool TransformKey64(IntPtr pBuf256, - IntPtr pKey256, UInt64 uRounds); - - internal static bool TransformKey(IntPtr pBuf256, IntPtr pKey256, - UInt64 uRounds) - { - if(IntPtr.Size == 4) - return TransformKey32(pBuf256, pKey256, uRounds); - return TransformKey64(pBuf256, pKey256, uRounds); - } - - [DllImport("KeePassLibC32.dll", EntryPoint = "TransformKeyBenchmark256")] - private static extern UInt64 TransformKeyBenchmark32(UInt32 uTimeMs); - - [DllImport("KeePassLibC64.dll", EntryPoint = "TransformKeyBenchmark256")] - private static extern UInt64 TransformKeyBenchmark64(UInt32 uTimeMs); - - internal static UInt64 TransformKeyBenchmark(UInt32 uTimeMs) - { - if(IntPtr.Size == 4) - return TransformKeyBenchmark32(uTimeMs); - return TransformKeyBenchmark64(uTimeMs); - } -#endif - - /* [DllImport("KeePassLibC32.dll", EntryPoint = "TF_ShowLangBar")] - [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool TF_ShowLangBar32(UInt32 dwFlags); - - [DllImport("KeePassLibC64.dll", EntryPoint = "TF_ShowLangBar")] - [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool TF_ShowLangBar64(UInt32 dwFlags); - - internal static bool TfShowLangBar(uint dwFlags) - { - if(IntPtr.Size == 4) return TF_ShowLangBar32(dwFlags); - return TF_ShowLangBar64(dwFlags); - } */ - - [DllImport("KeePassLibC32.dll", EntryPoint = "ProtectProcessWithDacl")] - private static extern void ProtectProcessWithDacl32(); - - [DllImport("KeePassLibC64.dll", EntryPoint = "ProtectProcessWithDacl")] - private static extern void ProtectProcessWithDacl64(); - - internal static void ProtectProcessWithDacl() - { - try - { - if(NativeLib.IsUnix()) return; - - if(IntPtr.Size == 4) ProtectProcessWithDacl32(); - else ProtectProcessWithDacl64(); - } - catch(Exception) { Debug.Assert(false); } - } - - [DllImport("Kernel32.dll", SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool CloseHandle(IntPtr hObject); - - [DllImport("Kernel32.dll", CharSet = CharSet.Auto, ExactSpelling = false, - SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool GetVolumeInformation(string lpRootPathName, - StringBuilder lpVolumeNameBuffer, UInt32 nVolumeNameSize, - ref UInt32 lpVolumeSerialNumber, ref UInt32 lpMaximumComponentLength, - ref UInt32 lpFileSystemFlags, StringBuilder lpFileSystemNameBuffer, - UInt32 nFileSystemNameSize); - - [DllImport("Kernel32.dll", CharSet = CharSet.Auto, ExactSpelling = false, - SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool MoveFileEx(string lpExistingFileName, - string lpNewFileName, UInt32 dwFlags); - - [DllImport("KtmW32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, - SetLastError = true)] - internal static extern IntPtr CreateTransaction(IntPtr lpTransactionAttributes, - IntPtr lpUOW, UInt32 dwCreateOptions, UInt32 dwIsolationLevel, - UInt32 dwIsolationFlags, UInt32 dwTimeout, string lpDescription); - - [DllImport("KtmW32.dll", SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool CommitTransaction(IntPtr hTransaction); - - [DllImport("Kernel32.dll", CharSet = CharSet.Auto, ExactSpelling = false, - SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool MoveFileTransacted(string lpExistingFileName, - string lpNewFileName, IntPtr lpProgressRoutine, IntPtr lpData, - UInt32 dwFlags, IntPtr hTransaction); - -#if (!KeePassLibSD && !KeePassUAP) - [DllImport("ShlWApi.dll", CharSet = CharSet.Auto)] - [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool PathRelativePathTo([Out] StringBuilder pszPath, - [In] string pszFrom, uint dwAttrFrom, [In] string pszTo, uint dwAttrTo); - - [DllImport("ShlWApi.dll", CharSet = CharSet.Unicode, ExactSpelling = true)] - private static extern int StrCmpLogicalW(string x, string y); - - private static bool? m_obSupportsLogicalCmp = null; - - private static void TestNaturalComparisonsSupport() - { - try - { - StrCmpLogicalW("0", "0"); // Throws exception if unsupported - m_obSupportsLogicalCmp = true; - } - catch(Exception) { m_obSupportsLogicalCmp = false; } - } -#endif - - internal static bool SupportsStrCmpNaturally - { - get - { -#if (!KeePassLibSD && !KeePassUAP) - if(!m_obSupportsLogicalCmp.HasValue) - TestNaturalComparisonsSupport(); - - return m_obSupportsLogicalCmp.Value; -#else - return false; -#endif - } - } - - internal static int StrCmpNaturally(string x, string y) - { -#if (!KeePassLibSD && !KeePassUAP) - if(!NativeMethods.SupportsStrCmpNaturally) - { - Debug.Assert(false); - return string.Compare(x, y, true); - } - - return StrCmpLogicalW(x, y); -#else - Debug.Assert(false); - return string.Compare(x, y, true); -#endif - } - - internal static string GetUserRuntimeDir() - { -#if KeePassLibSD - return Path.GetTempPath(); -#else -#if KeePassUAP - string strRtDir = EnvironmentExt.AppDataLocalFolderPath; -#else - string strRtDir = Environment.GetEnvironmentVariable("XDG_RUNTIME_DIR"); - if(string.IsNullOrEmpty(strRtDir)) - strRtDir = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); - if(string.IsNullOrEmpty(strRtDir)) - { - Debug.Assert(false); - return Path.GetTempPath(); // Not UrlUtil (otherwise cyclic) - } -#endif - - strRtDir = UrlUtil.EnsureTerminatingSeparator(strRtDir, false); - strRtDir += PwDefs.ShortProductName; - - return strRtDir; -#endif - } - } -} diff --git a/ModernKeePassLib/Properties/AssemblyInfo.cs b/ModernKeePassLib/Properties/AssemblyInfo.cs deleted file mode 100644 index 121fd60..0000000 --- a/ModernKeePassLib/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,44 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General assembly properties -[assembly: AssemblyTitle("ModernKeePassLib")] -[assembly: AssemblyDescription("KeePass Password Management Library for .Net Standard")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("wismna")] -[assembly: AssemblyProduct("ModernKeePassLib")] -[assembly: AssemblyCopyright("Copyright © 2018 Geoffroy Bonneville")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// COM settings -[assembly: ComVisible(false)] - -#if !ModernKeePassLib -// Assembly GUID -[assembly: Guid("395f6eec-a1e0-4438-aa82-b75099348134")] -#endif - -// Assembly version information -[assembly: AssemblyVersion("2.39.1.*")] -[assembly: AssemblyFileVersion("2.39.1.0")] diff --git a/ModernKeePassLib/PwCustomIcon.cs b/ModernKeePassLib/PwCustomIcon.cs deleted file mode 100644 index 103d3d4..0000000 --- a/ModernKeePassLib/PwCustomIcon.cs +++ /dev/null @@ -1,131 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; -using System.Diagnostics; -#if ModernKeePassLib -using Image = Splat.IBitmap; -#else -using System.Drawing; -#endif - -using ModernKeePassLib.Utility; - -namespace ModernKeePassLib -{ - /// - /// Custom icon. PwCustomIcon objects are immutable. - /// - public sealed class PwCustomIcon - { - private readonly PwUuid m_pwUuid; - private readonly byte[] m_pbImageDataPng; - - private readonly Image m_imgOrg; - private Dictionary m_dImageCache = new Dictionary(); - - // Recommended maximum sizes, not obligatory - internal const int MaxWidth = 128; - internal const int MaxHeight = 128; - - public PwUuid Uuid - { - get { return m_pwUuid; } - } - - public byte[] ImageDataPng - { - get { return m_pbImageDataPng; } - } - - [Obsolete("Use GetImage instead.")] - public Image Image - { -#if (!KeePassLibSD && !KeePassUAP) - get { return GetImage(16, 16); } // Backward compatibility -#else - get { return GetImage(); } // Backward compatibility -#endif - } - - public PwCustomIcon(PwUuid pwUuid, byte[] pbImageDataPng) - { - Debug.Assert(pwUuid != null); - if(pwUuid == null) throw new ArgumentNullException("pwUuid"); - Debug.Assert(!pwUuid.Equals(PwUuid.Zero)); - if(pwUuid.Equals(PwUuid.Zero)) throw new ArgumentException("pwUuid == 0."); - Debug.Assert(pbImageDataPng != null); - if(pbImageDataPng == null) throw new ArgumentNullException("pbImageDataPng"); - - m_pwUuid = pwUuid; - m_pbImageDataPng = pbImageDataPng; - - // MemoryStream ms = new MemoryStream(m_pbImageDataPng, false); - // m_imgOrg = Image.FromStream(ms); - // ms.Close(); - try { m_imgOrg = GfxUtil.LoadImage(m_pbImageDataPng); } - catch(Exception) { Debug.Assert(false); m_imgOrg = null; } - - if(m_imgOrg != null) - m_dImageCache[GetID((int)m_imgOrg.Width, (int)m_imgOrg.Height)] = - m_imgOrg; - } - - private static long GetID(int w, int h) - { - return (((long)w << 32) ^ (long)h); - } - - /// - /// Get the icon as an Image (original size). - /// - public Image GetImage() - { - return m_imgOrg; - } - -#if (!KeePassLibSD && !KeePassUAP) - /// - /// Get the icon as an Image (with the specified size). - /// - /// Width of the returned image. - /// Height of the returned image. - public Image GetImage(int w, int h) - { - if(w < 0) { Debug.Assert(false); return m_imgOrg; } - if(h < 0) { Debug.Assert(false); return m_imgOrg; } - if(m_imgOrg == null) return null; - - long lID = GetID(w, h); - - Image img; - if(m_dImageCache.TryGetValue(lID, out img)) return img; - -#if ModernKeePassLib - img = GfxUtil.ScaleImage(m_pbImageDataPng, w, h); -#else - img = GfxUtil.ScaleImage(m_imgOrg, w, h, ScaleTransformFlags.UIIcon); -#endif - m_dImageCache[lID] = img; - return img; - } -#endif - } -} diff --git a/ModernKeePassLib/PwDatabase.cs b/ModernKeePassLib/PwDatabase.cs deleted file mode 100644 index f903985..0000000 --- a/ModernKeePassLib/PwDatabase.cs +++ /dev/null @@ -1,2077 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Drawing; - -#if ModernKeePassLib -using Image = Splat.IBitmap; -#endif - -using ModernKeePassLib.Collections; -using ModernKeePassLib.Cryptography; -using ModernKeePassLib.Cryptography.Cipher; -using ModernKeePassLib.Cryptography.KeyDerivation; -using ModernKeePassLib.Delegates; -using ModernKeePassLib.Interfaces; -using ModernKeePassLib.Keys; -using ModernKeePassLib.Resources; -using ModernKeePassLib.Security; -using ModernKeePassLib.Serialization; -using ModernKeePassLib.Utility; - -namespace ModernKeePassLib -{ - /// - /// The core password manager class. It contains a number of groups, which - /// contain the actual entries. - /// - public sealed class PwDatabase - { - internal const int DefaultHistoryMaxItems = 10; // -1 = unlimited - internal const long DefaultHistoryMaxSize = 6 * 1024 * 1024; // -1 = unlimited - - private static bool m_bPrimaryCreated = false; - - // Initializations: see Clear() - private PwGroup m_pgRootGroup = null; - private PwObjectList m_vDeletedObjects = new PwObjectList(); - - private PwUuid m_uuidDataCipher = StandardAesEngine.AesUuid; - private PwCompressionAlgorithm m_caCompression = PwCompressionAlgorithm.GZip; - // private ulong m_uKeyEncryptionRounds = PwDefs.DefaultKeyEncryptionRounds; - private KdfParameters m_kdfParams = KdfPool.GetDefaultParameters(); - - private CompositeKey m_pwUserKey = null; - private MemoryProtectionConfig m_memProtConfig = new MemoryProtectionConfig(); - - private List m_vCustomIcons = new List(); - private bool m_bUINeedsIconUpdate = true; - - private DateTime m_dtSettingsChanged = PwDefs.DtDefaultNow; - private string m_strName = string.Empty; - private DateTime m_dtNameChanged = PwDefs.DtDefaultNow; - private string m_strDesc = string.Empty; - private DateTime m_dtDescChanged = PwDefs.DtDefaultNow; - private string m_strDefaultUserName = string.Empty; - private DateTime m_dtDefaultUserChanged = PwDefs.DtDefaultNow; - private uint m_uMntncHistoryDays = 365; - private Color m_clr = Color.Empty; - - private DateTime m_dtKeyLastChanged = PwDefs.DtDefaultNow; - private long m_lKeyChangeRecDays = -1; - private long m_lKeyChangeForceDays = -1; - private bool m_bKeyChangeForceOnce = false; - - private IOConnectionInfo m_ioSource = new IOConnectionInfo(); - private bool m_bDatabaseOpened = false; - private bool m_bModified = false; - - private PwUuid m_pwLastSelectedGroup = PwUuid.Zero; - private PwUuid m_pwLastTopVisibleGroup = PwUuid.Zero; - - private bool m_bUseRecycleBin = true; - private PwUuid m_pwRecycleBin = PwUuid.Zero; - private DateTime m_dtRecycleBinChanged = PwDefs.DtDefaultNow; - private PwUuid m_pwEntryTemplatesGroup = PwUuid.Zero; - private DateTime m_dtEntryTemplatesChanged = PwDefs.DtDefaultNow; - - private int m_nHistoryMaxItems = DefaultHistoryMaxItems; - private long m_lHistoryMaxSize = DefaultHistoryMaxSize; // In bytes - - private StringDictionaryEx m_dCustomData = new StringDictionaryEx(); - private VariantDictionary m_dPublicCustomData = new VariantDictionary(); - - private byte[] m_pbHashOfFileOnDisk = null; - private byte[] m_pbHashOfLastIO = null; - - private bool m_bUseFileTransactions = false; - private bool m_bUseFileLocks = false; - - private IStatusLogger m_slStatus = null; - - private static string m_strLocalizedAppName = string.Empty; - - // private const string StrBackupExtension = ".bak"; - - /// - /// Get the root group that contains all groups and entries stored in the - /// database. - /// - /// Root group. The return value is null, if no database - /// has been opened. - public PwGroup RootGroup - { - get { return m_pgRootGroup; } - set - { - Debug.Assert(value != null); - if(value == null) throw new ArgumentNullException("value"); - - m_pgRootGroup = value; - } - } - - /// - /// IOConnection of the currently opened database file. - /// Is never null. - /// - public IOConnectionInfo IOConnectionInfo - { - get { return m_ioSource; } - } - - /// - /// If this is true, a database is currently open. - /// - public bool IsOpen - { - get { return m_bDatabaseOpened; } - } - - /// - /// Modification flag. If true, the class has been modified and the - /// user interface should prompt the user to save the changes before - /// closing the database for example. - /// - public bool Modified - { - get { return m_bModified; } - set { m_bModified = value; } - } - - /// - /// The user key used for database encryption. This key must be created - /// and set before using any of the database load/save functions. - /// - public CompositeKey MasterKey - { - get { return m_pwUserKey; } - set - { - Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); - - m_pwUserKey = value; - } - } - - public DateTime SettingsChanged - { - get { return m_dtSettingsChanged; } - set { m_dtSettingsChanged = value; } - } - - /// - /// Name of the database. - /// - public string Name - { - get { return m_strName; } - set - { - Debug.Assert(value != null); - if(value != null) m_strName = value; - } - } - - public DateTime NameChanged - { - get { return m_dtNameChanged; } - set { m_dtNameChanged = value; } - } - - /// - /// Database description. - /// - public string Description - { - get { return m_strDesc; } - set - { - Debug.Assert(value != null); - if(value != null) m_strDesc = value; - } - } - - public DateTime DescriptionChanged - { - get { return m_dtDescChanged; } - set { m_dtDescChanged = value; } - } - - /// - /// Default user name used for new entries. - /// - public string DefaultUserName - { - get { return m_strDefaultUserName; } - set - { - Debug.Assert(value != null); - if(value != null) m_strDefaultUserName = value; - } - } - - public DateTime DefaultUserNameChanged - { - get { return m_dtDefaultUserChanged; } - set { m_dtDefaultUserChanged = value; } - } - - /// - /// Number of days until history entries are being deleted - /// in a database maintenance operation. - /// - public uint MaintenanceHistoryDays - { - get { return m_uMntncHistoryDays; } - set { m_uMntncHistoryDays = value; } - } - - public Color Color - { - get { return m_clr; } - set { m_clr = value; } - } - - public DateTime MasterKeyChanged - { - get { return m_dtKeyLastChanged; } - set { m_dtKeyLastChanged = value; } - } - - public long MasterKeyChangeRec - { - get { return m_lKeyChangeRecDays; } - set { m_lKeyChangeRecDays = value; } - } - - public long MasterKeyChangeForce - { - get { return m_lKeyChangeForceDays; } - set { m_lKeyChangeForceDays = value; } - } - - public bool MasterKeyChangeForceOnce - { - get { return m_bKeyChangeForceOnce; } - set { m_bKeyChangeForceOnce = value; } - } - - /// - /// The encryption algorithm used to encrypt the data part of the database. - /// - public PwUuid DataCipherUuid - { - get { return m_uuidDataCipher; } - set - { - Debug.Assert(value != null); - if(value != null) m_uuidDataCipher = value; - } - } - - /// - /// Compression algorithm used to encrypt the data part of the database. - /// - public PwCompressionAlgorithm Compression - { - get { return m_caCompression; } - set { m_caCompression = value; } - } - - // /// - // /// Number of key transformation rounds (KDF parameter). - // /// - // public ulong KeyEncryptionRounds - // { - // get { return m_uKeyEncryptionRounds; } - // set { m_uKeyEncryptionRounds = value; } - // } - - public KdfParameters KdfParameters - { - get { return m_kdfParams; } - set - { - if(value == null) throw new ArgumentNullException("value"); - m_kdfParams = value; - } - } - - /// - /// Memory protection configuration (for default fields). - /// - public MemoryProtectionConfig MemoryProtection - { - get { return m_memProtConfig; } - set - { - Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); - - m_memProtConfig = value; - } - } - - /// - /// Get a list of all deleted objects. - /// - public PwObjectList DeletedObjects - { - get { return m_vDeletedObjects; } - } - - /// - /// Get all custom icons stored in this database. - /// - public List CustomIcons - { - get { return m_vCustomIcons; } - } - - /// - /// This is a dirty-flag for the UI. It is used to indicate when an - /// icon list update is required. - /// - public bool UINeedsIconUpdate - { - get { return m_bUINeedsIconUpdate; } - set { m_bUINeedsIconUpdate = value; } - } - - public PwUuid LastSelectedGroup - { - get { return m_pwLastSelectedGroup; } - set - { - Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); - m_pwLastSelectedGroup = value; - } - } - - public PwUuid LastTopVisibleGroup - { - get { return m_pwLastTopVisibleGroup; } - set - { - Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); - m_pwLastTopVisibleGroup = value; - } - } - - public bool RecycleBinEnabled - { - get { return m_bUseRecycleBin; } - set { m_bUseRecycleBin = value; } - } - - public PwUuid RecycleBinUuid - { - get { return m_pwRecycleBin; } - set - { - Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); - m_pwRecycleBin = value; - } - } - - public DateTime RecycleBinChanged - { - get { return m_dtRecycleBinChanged; } - set { m_dtRecycleBinChanged = value; } - } - - /// - /// UUID of the group containing template entries. May be - /// PwUuid.Zero, if no entry templates group has been specified. - /// - public PwUuid EntryTemplatesGroup - { - get { return m_pwEntryTemplatesGroup; } - set - { - Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); - m_pwEntryTemplatesGroup = value; - } - } - - public DateTime EntryTemplatesGroupChanged - { - get { return m_dtEntryTemplatesChanged; } - set { m_dtEntryTemplatesChanged = value; } - } - - public int HistoryMaxItems - { - get { return m_nHistoryMaxItems; } - set { m_nHistoryMaxItems = value; } - } - - public long HistoryMaxSize - { - get { return m_lHistoryMaxSize; } - set { m_lHistoryMaxSize = value; } - } - - /// - /// Custom data container that can be used by plugins to store - /// own data in KeePass databases. - /// The data is stored in the encrypted part of encrypted - /// database files. - /// Use unique names for your items, e.g. "PluginName_ItemName". - /// - public StringDictionaryEx CustomData - { - get { return m_dCustomData; } - internal set - { - if(value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); } - m_dCustomData = value; - } - } - - /// - /// Custom data container that can be used by plugins to store - /// own data in KeePass databases. - /// The data is stored in the *unencrypted* part of database files, - /// and it is not supported by all file formats (e.g. supported by KDBX, - /// unsupported by XML). - /// It is highly recommended to use CustomData instead, - /// if possible. - /// Use unique names for your items, e.g. "PluginName_ItemName". - /// - public VariantDictionary PublicCustomData - { - get { return m_dPublicCustomData; } - internal set - { - if(value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); } - m_dPublicCustomData = value; - } - } - - /// - /// Hash value of the primary file on disk (last read or last write). - /// A call to SaveAs without making the saved file primary will - /// not change this hash. May be null. - /// - public byte[] HashOfFileOnDisk - { - get { return m_pbHashOfFileOnDisk; } - } - - public byte[] HashOfLastIO - { - get { return m_pbHashOfLastIO; } - } - - public bool UseFileTransactions - { - get { return m_bUseFileTransactions; } - set { m_bUseFileTransactions = value; } - } - - public bool UseFileLocks - { - get { return m_bUseFileLocks; } - set { m_bUseFileLocks = value; } - } - - private string m_strDetachBins = null; - /// - /// Detach binaries when opening a file. If this isn't null, - /// all binaries are saved to the specified path and are removed - /// from the database. - /// - public string DetachBinaries - { - get { return m_strDetachBins; } - set { m_strDetachBins = value; } - } - - /// - /// Localized application name. - /// - public static string LocalizedAppName - { - get { return m_strLocalizedAppName; } - set { Debug.Assert(value != null); m_strLocalizedAppName = value; } - } - - /// - /// Constructs an empty password manager object. - /// - public PwDatabase() - { - if(m_bPrimaryCreated == false) m_bPrimaryCreated = true; - - Clear(); - } - - private void Clear() - { - m_pgRootGroup = null; - m_vDeletedObjects = new PwObjectList(); - - m_uuidDataCipher = StandardAesEngine.AesUuid; - m_caCompression = PwCompressionAlgorithm.GZip; - // m_uKeyEncryptionRounds = PwDefs.DefaultKeyEncryptionRounds; - m_kdfParams = KdfPool.GetDefaultParameters(); - - m_pwUserKey = null; - m_memProtConfig = new MemoryProtectionConfig(); - - m_vCustomIcons = new List(); - m_bUINeedsIconUpdate = true; - - DateTime dtNow = DateTime.UtcNow; - - m_dtSettingsChanged = dtNow; - m_strName = string.Empty; - m_dtNameChanged = dtNow; - m_strDesc = string.Empty; - m_dtDescChanged = dtNow; - m_strDefaultUserName = string.Empty; - m_dtDefaultUserChanged = dtNow; - m_uMntncHistoryDays = 365; - m_clr = Color.Empty; - - m_dtKeyLastChanged = dtNow; - m_lKeyChangeRecDays = -1; - m_lKeyChangeForceDays = -1; - m_bKeyChangeForceOnce = false; - - m_ioSource = new IOConnectionInfo(); - m_bDatabaseOpened = false; - m_bModified = false; - - m_pwLastSelectedGroup = PwUuid.Zero; - m_pwLastTopVisibleGroup = PwUuid.Zero; - - m_bUseRecycleBin = true; - m_pwRecycleBin = PwUuid.Zero; - m_dtRecycleBinChanged = dtNow; - m_pwEntryTemplatesGroup = PwUuid.Zero; - m_dtEntryTemplatesChanged = dtNow; - - m_nHistoryMaxItems = DefaultHistoryMaxItems; - m_lHistoryMaxSize = DefaultHistoryMaxSize; - - m_dCustomData = new StringDictionaryEx(); - m_dPublicCustomData = new VariantDictionary(); - - m_pbHashOfFileOnDisk = null; - m_pbHashOfLastIO = null; - - m_bUseFileTransactions = false; - m_bUseFileLocks = false; - } - - /// - /// Initialize the class for managing a new database. Previously loaded - /// data is deleted. - /// - /// IO connection of the new database. - /// Key to open the database. - public void New(IOConnectionInfo ioConnection, CompositeKey pwKey) - { - Debug.Assert(ioConnection != null); - if(ioConnection == null) throw new ArgumentNullException("ioConnection"); - Debug.Assert(pwKey != null); - if(pwKey == null) throw new ArgumentNullException("pwKey"); - - Close(); - - m_ioSource = ioConnection; - m_pwUserKey = pwKey; - - m_bDatabaseOpened = true; - m_bModified = true; - - m_pgRootGroup = new PwGroup(true, true, UrlUtil.StripExtension( - UrlUtil.GetFileName(ioConnection.Path)), PwIcon.FolderOpen); - m_pgRootGroup.IsExpanded = true; - } - - /// - /// Open a database. The URL may point to any supported data source. - /// - /// IO connection to load the database from. - /// Key used to open the specified database. - /// Logger, which gets all status messages. - public void Open(IOConnectionInfo ioSource, CompositeKey pwKey, - IStatusLogger slLogger) - { - Debug.Assert(ioSource != null); - if(ioSource == null) throw new ArgumentNullException("ioSource"); - Debug.Assert(pwKey != null); - if(pwKey == null) throw new ArgumentNullException("pwKey"); - - Close(); - - try - { - m_pgRootGroup = new PwGroup(true, true, UrlUtil.StripExtension( - UrlUtil.GetFileName(ioSource.Path)), PwIcon.FolderOpen); - m_pgRootGroup.IsExpanded = true; - - m_pwUserKey = pwKey; - m_bModified = false; - - KdbxFile kdbx = new KdbxFile(this); - kdbx.DetachBinaries = m_strDetachBins; - - using(Stream s = IOConnection.OpenRead(ioSource)) - { - kdbx.Load(s, KdbxFormat.Default, slLogger); - } - - m_pbHashOfLastIO = kdbx.HashOfFileOnDisk; - m_pbHashOfFileOnDisk = kdbx.HashOfFileOnDisk; - Debug.Assert(m_pbHashOfFileOnDisk != null); - - m_bDatabaseOpened = true; - m_ioSource = ioSource; - } - catch(Exception) - { - Clear(); - throw; - } - } - - /// - /// Save the currently opened database. The file is written to the location - /// it has been opened from. - /// - /// Logger that recieves status information. - public void Save(IStatusLogger slLogger) - { - Debug.Assert(!HasDuplicateUuids()); - - FileLock fl = null; - if(m_bUseFileLocks) fl = new FileLock(m_ioSource); - try - { - KdbxFile kdbx = new KdbxFile(this); - - using(FileTransactionEx ft = new FileTransactionEx(m_ioSource, - m_bUseFileTransactions)) - { - using(Stream s = ft.OpenWrite()) - { - kdbx.Save(s, null, KdbxFormat.Default, slLogger); - } - - ft.CommitWrite(); - } - - m_pbHashOfLastIO = kdbx.HashOfFileOnDisk; - m_pbHashOfFileOnDisk = kdbx.HashOfFileOnDisk; - Debug.Assert(m_pbHashOfFileOnDisk != null); - } - finally { if(fl != null) fl.Dispose(); } - - m_bModified = false; - } - - /// - /// Save the currently opened database to a different location. If - /// is true, the specified - /// location is made the default location for future saves - /// using SaveDatabase. - /// - /// New location to serialize the database to. - /// If true, the new location is made the - /// standard location for the database. If false, a copy of the currently - /// opened database is saved to the specified location, but it isn't - /// made the default location (i.e. no lock files will be moved for - /// example). - /// Logger that recieves status information. - public void SaveAs(IOConnectionInfo ioConnection, bool bIsPrimaryNow, - IStatusLogger slLogger) - { - Debug.Assert(ioConnection != null); - if(ioConnection == null) throw new ArgumentNullException("ioConnection"); - - IOConnectionInfo ioCurrent = m_ioSource; // Remember current - m_ioSource = ioConnection; - - byte[] pbHashCopy = m_pbHashOfFileOnDisk; - - try { this.Save(slLogger); } - catch(Exception) - { - m_ioSource = ioCurrent; // Restore - m_pbHashOfFileOnDisk = pbHashCopy; - - m_pbHashOfLastIO = null; - throw; - } - - if(!bIsPrimaryNow) - { - m_ioSource = ioCurrent; // Restore - m_pbHashOfFileOnDisk = pbHashCopy; - } - } - - /// - /// Closes the currently opened database. No confirmation message is shown - /// before closing. Unsaved changes will be lost. - /// - public void Close() - { - Clear(); - } - - public void MergeIn(PwDatabase pdSource, PwMergeMethod mm) - { - MergeIn(pdSource, mm, null); - } - - public void MergeIn(PwDatabase pdSource, PwMergeMethod mm, - IStatusLogger slStatus) - { - if(pdSource == null) throw new ArgumentNullException("pdSource"); - - if(mm == PwMergeMethod.CreateNewUuids) - { - pdSource.RootGroup.Uuid = new PwUuid(true); - pdSource.RootGroup.CreateNewItemUuids(true, true, true); - } - - // PwGroup pgOrgStructure = m_pgRootGroup.CloneStructure(); - // PwGroup pgSrcStructure = pdSource.RootGroup.CloneStructure(); - // Later in case 'if(mm == PwMergeMethod.Synchronize)': - // PwObjectPoolEx ppOrg = PwObjectPoolEx.FromGroup(pgOrgStructure); - // PwObjectPoolEx ppSrc = PwObjectPoolEx.FromGroup(pgSrcStructure); - - PwObjectPoolEx ppOrg = PwObjectPoolEx.FromGroup(m_pgRootGroup); - PwObjectPoolEx ppSrc = PwObjectPoolEx.FromGroup(pdSource.RootGroup); - - GroupHandler ghSrc = delegate(PwGroup pg) - { - // if(pg == pdSource.m_pgRootGroup) return true; - - // Do not use ppOrg for finding the group, because new groups - // might have been added (which are not in the pool, and the - // pool should not be modified) - PwGroup pgLocal = m_pgRootGroup.FindGroup(pg.Uuid, true); - - if(pgLocal == null) - { - PwGroup pgSourceParent = pg.ParentGroup; - PwGroup pgLocalContainer; - if(pgSourceParent == null) - { - // pg is the root group of pdSource, and no corresponding - // local group was found; create the group within the - // local root group - Debug.Assert(pg == pdSource.m_pgRootGroup); - pgLocalContainer = m_pgRootGroup; - } - else if(pgSourceParent == pdSource.m_pgRootGroup) - pgLocalContainer = m_pgRootGroup; - else - pgLocalContainer = m_pgRootGroup.FindGroup(pgSourceParent.Uuid, true); - Debug.Assert(pgLocalContainer != null); - if(pgLocalContainer == null) pgLocalContainer = m_pgRootGroup; - - PwGroup pgNew = new PwGroup(false, false); - pgNew.Uuid = pg.Uuid; - pgNew.AssignProperties(pg, false, true); - - // pgLocalContainer.AddGroup(pgNew, true); - InsertObjectAtBestPos(pgLocalContainer.Groups, pgNew, ppSrc); - pgNew.ParentGroup = pgLocalContainer; - } - else // pgLocal != null - { - Debug.Assert(mm != PwMergeMethod.CreateNewUuids); - - if(mm == PwMergeMethod.OverwriteExisting) - pgLocal.AssignProperties(pg, false, false); - else if((mm == PwMergeMethod.OverwriteIfNewer) || - (mm == PwMergeMethod.Synchronize)) - { - pgLocal.AssignProperties(pg, true, false); - } - // else if(mm == PwMergeMethod.KeepExisting) ... - } - - return ((slStatus != null) ? slStatus.ContinueWork() : true); - }; - - EntryHandler ehSrc = delegate(PwEntry pe) - { - // PwEntry peLocal = m_pgRootGroup.FindEntry(pe.Uuid, true); - PwEntry peLocal = (ppOrg.GetItemByUuid(pe.Uuid) as PwEntry); - Debug.Assert(object.ReferenceEquals(peLocal, - m_pgRootGroup.FindEntry(pe.Uuid, true))); - - if(peLocal == null) - { - PwGroup pgSourceParent = pe.ParentGroup; - PwGroup pgLocalContainer; - if(pgSourceParent == pdSource.m_pgRootGroup) - pgLocalContainer = m_pgRootGroup; - else - pgLocalContainer = m_pgRootGroup.FindGroup(pgSourceParent.Uuid, true); - Debug.Assert(pgLocalContainer != null); - if(pgLocalContainer == null) pgLocalContainer = m_pgRootGroup; - - PwEntry peNew = new PwEntry(false, false); - peNew.Uuid = pe.Uuid; - peNew.AssignProperties(pe, false, true, true); - - // pgLocalContainer.AddEntry(peNew, true); - InsertObjectAtBestPos(pgLocalContainer.Entries, peNew, ppSrc); - peNew.ParentGroup = pgLocalContainer; - } - else // peLocal != null - { - Debug.Assert(mm != PwMergeMethod.CreateNewUuids); - - const PwCompareOptions cmpOpt = (PwCompareOptions.IgnoreParentGroup | - PwCompareOptions.IgnoreLastAccess | PwCompareOptions.IgnoreHistory | - PwCompareOptions.NullEmptyEquivStd); - bool bEquals = peLocal.EqualsEntry(pe, cmpOpt, MemProtCmpMode.None); - - bool bOrgBackup = !bEquals; - if(mm != PwMergeMethod.OverwriteExisting) - bOrgBackup &= (TimeUtil.CompareLastMod(pe, peLocal, true) > 0); - bOrgBackup &= !pe.HasBackupOfData(peLocal, false, true); - if(bOrgBackup) peLocal.CreateBackup(null); // Maintain at end - - bool bSrcBackup = !bEquals && (mm != PwMergeMethod.OverwriteExisting); - bSrcBackup &= (TimeUtil.CompareLastMod(peLocal, pe, true) > 0); - bSrcBackup &= !peLocal.HasBackupOfData(pe, false, true); - if(bSrcBackup) pe.CreateBackup(null); // Maintain at end - - if(mm == PwMergeMethod.OverwriteExisting) - peLocal.AssignProperties(pe, false, false, false); - else if((mm == PwMergeMethod.OverwriteIfNewer) || - (mm == PwMergeMethod.Synchronize)) - { - peLocal.AssignProperties(pe, true, false, false); - } - // else if(mm == PwMergeMethod.KeepExisting) ... - - MergeEntryHistory(peLocal, pe, mm); - } - - return ((slStatus != null) ? slStatus.ContinueWork() : true); - }; - - ghSrc(pdSource.RootGroup); - if(!pdSource.RootGroup.TraverseTree(TraversalMethod.PreOrder, ghSrc, ehSrc)) - throw new InvalidOperationException(); - - IStatusLogger slPrevStatus = m_slStatus; - m_slStatus = slStatus; - - if(mm == PwMergeMethod.Synchronize) - { - RelocateGroups(ppOrg, ppSrc); - RelocateEntries(ppOrg, ppSrc); - ReorderObjects(m_pgRootGroup, ppOrg, ppSrc); - - // After all relocations and reorderings - MergeInLocationChanged(m_pgRootGroup, ppOrg, ppSrc); - ppOrg = null; // Pools are now invalid, because the location - ppSrc = null; // changed times have been merged in - - // Delete *after* relocating, because relocating might - // empty some groups that are marked for deletion (and - // objects that weren't relocated yet might prevent the - // deletion) - Dictionary dOrgDel = CreateDeletedObjectsPool(); - MergeInDeletionInfo(pdSource.m_vDeletedObjects, dOrgDel); - ApplyDeletions(m_pgRootGroup, dOrgDel); - - // The list and the dictionary should be kept in sync - Debug.Assert(m_vDeletedObjects.UCount == (uint)dOrgDel.Count); - } - - // Must be called *after* merging groups, because group UUIDs - // are required for recycle bin and entry template UUIDs - MergeInDbProperties(pdSource, mm); - - MergeInCustomIcons(pdSource); - - MaintainBackups(); - - Debug.Assert(!HasDuplicateUuids()); - m_slStatus = slPrevStatus; - } - - private void MergeInCustomIcons(PwDatabase pdSource) - { - foreach(PwCustomIcon pwci in pdSource.CustomIcons) - { - if(GetCustomIconIndex(pwci.Uuid) >= 0) continue; - - m_vCustomIcons.Add(pwci); // PwCustomIcon is immutable - m_bUINeedsIconUpdate = true; - } - } - - private Dictionary CreateDeletedObjectsPool() - { - Dictionary d = - new Dictionary(); - - int n = (int)m_vDeletedObjects.UCount; - for(int i = n - 1; i >= 0; --i) - { - PwDeletedObject pdo = m_vDeletedObjects.GetAt((uint)i); - - PwDeletedObject pdoEx; - if(d.TryGetValue(pdo.Uuid, out pdoEx)) - { - Debug.Assert(false); // Found duplicate, which should not happen - - if(pdo.DeletionTime > pdoEx.DeletionTime) - pdoEx.DeletionTime = pdo.DeletionTime; - - m_vDeletedObjects.RemoveAt((uint)i); - } - else d[pdo.Uuid] = pdo; - } - - return d; - } - - private void MergeInDeletionInfo(PwObjectList lSrc, - Dictionary dOrgDel) - { - foreach(PwDeletedObject pdoSrc in lSrc) - { - PwDeletedObject pdoOrg; - if(dOrgDel.TryGetValue(pdoSrc.Uuid, out pdoOrg)) // Update - { - Debug.Assert(pdoOrg.Uuid.Equals(pdoSrc.Uuid)); - - if(pdoSrc.DeletionTime > pdoOrg.DeletionTime) - pdoOrg.DeletionTime = pdoSrc.DeletionTime; - } - else // Add - { - m_vDeletedObjects.Add(pdoSrc); - dOrgDel[pdoSrc.Uuid] = pdoSrc; - } - } - } - - private void ApplyDeletions(PwObjectList l, Predicate fCanDelete, - Dictionary dOrgDel) - where T : class, ITimeLogger, IStructureItem, IDeepCloneable - { - int n = (int)l.UCount; - for(int i = n - 1; i >= 0; --i) - { - if((m_slStatus != null) && !m_slStatus.ContinueWork()) break; - - T t = l.GetAt((uint)i); - - PwDeletedObject pdo; - if(dOrgDel.TryGetValue(t.Uuid, out pdo)) - { - Debug.Assert(t.Uuid.Equals(pdo.Uuid)); - - bool bDel = (TimeUtil.Compare(t.LastModificationTime, - pdo.DeletionTime, true) < 0); - bDel &= fCanDelete(t); - - if(bDel) l.RemoveAt((uint)i); - else - { - // Prevent future deletion attempts; this also prevents - // delayed deletions (emptying a group could cause a - // group to be deleted, if the deletion was prevented - // before due to the group not being empty) - if(!m_vDeletedObjects.Remove(pdo)) { Debug.Assert(false); } - if(!dOrgDel.Remove(pdo.Uuid)) { Debug.Assert(false); } - } - } - } - } - - private static bool SafeCanDeleteGroup(PwGroup pg) - { - if(pg == null) { Debug.Assert(false); return false; } - - if(pg.Groups.UCount > 0) return false; - if(pg.Entries.UCount > 0) return false; - return true; - } - - private static bool SafeCanDeleteEntry(PwEntry pe) - { - if(pe == null) { Debug.Assert(false); return false; } - - return true; - } - - // Apply deletions on all objects in the specified container - // (but not the container itself), using post-order traversal - // to avoid implicit deletions; - // https://sourceforge.net/p/keepass/bugs/1499/ - private void ApplyDeletions(PwGroup pgContainer, - Dictionary dOrgDel) - { - foreach(PwGroup pg in pgContainer.Groups) // Post-order traversal - { - ApplyDeletions(pg, dOrgDel); - } - - ApplyDeletions(pgContainer.Groups, PwDatabase.SafeCanDeleteGroup, dOrgDel); - ApplyDeletions(pgContainer.Entries, PwDatabase.SafeCanDeleteEntry, dOrgDel); - } - - private void RelocateGroups(PwObjectPoolEx ppOrg, PwObjectPoolEx ppSrc) - { - PwObjectList vGroups = m_pgRootGroup.GetGroups(true); - - foreach(PwGroup pg in vGroups) - { - if((m_slStatus != null) && !m_slStatus.ContinueWork()) break; - - // PwGroup pgOrg = pgOrgStructure.FindGroup(pg.Uuid, true); - IStructureItem ptOrg = ppOrg.GetItemByUuid(pg.Uuid); - if(ptOrg == null) continue; - // PwGroup pgSrc = pgSrcStructure.FindGroup(pg.Uuid, true); - IStructureItem ptSrc = ppSrc.GetItemByUuid(pg.Uuid); - if(ptSrc == null) continue; - - PwGroup pgOrgParent = ptOrg.ParentGroup; - // vGroups does not contain the root group, thus pgOrgParent - // should not be null - if(pgOrgParent == null) { Debug.Assert(false); continue; } - - PwGroup pgSrcParent = ptSrc.ParentGroup; - // pgSrcParent may be null (for the source root group) - if(pgSrcParent == null) continue; - - if(pgOrgParent.Uuid.Equals(pgSrcParent.Uuid)) - { - // pg.LocationChanged = ((ptSrc.LocationChanged > ptOrg.LocationChanged) ? - // ptSrc.LocationChanged : ptOrg.LocationChanged); - continue; - } - - if(ptSrc.LocationChanged > ptOrg.LocationChanged) - { - PwGroup pgLocal = m_pgRootGroup.FindGroup(pgSrcParent.Uuid, true); - if(pgLocal == null) { Debug.Assert(false); continue; } - - if(pgLocal.IsContainedIn(pg)) continue; - - pg.ParentGroup.Groups.Remove(pg); - - // pgLocal.AddGroup(pg, true); - InsertObjectAtBestPos(pgLocal.Groups, pg, ppSrc); - pg.ParentGroup = pgLocal; - - // pg.LocationChanged = ptSrc.LocationChanged; - } - else - { - Debug.Assert(pg.ParentGroup.Uuid.Equals(pgOrgParent.Uuid)); - Debug.Assert(pg.LocationChanged == ptOrg.LocationChanged); - } - } - - Debug.Assert(m_pgRootGroup.GetGroups(true).UCount == vGroups.UCount); - } - - private void RelocateEntries(PwObjectPoolEx ppOrg, PwObjectPoolEx ppSrc) - { - PwObjectList vEntries = m_pgRootGroup.GetEntries(true); - - foreach(PwEntry pe in vEntries) - { - if((m_slStatus != null) && !m_slStatus.ContinueWork()) break; - - // PwEntry peOrg = pgOrgStructure.FindEntry(pe.Uuid, true); - IStructureItem ptOrg = ppOrg.GetItemByUuid(pe.Uuid); - if(ptOrg == null) continue; - // PwEntry peSrc = pgSrcStructure.FindEntry(pe.Uuid, true); - IStructureItem ptSrc = ppSrc.GetItemByUuid(pe.Uuid); - if(ptSrc == null) continue; - - PwGroup pgOrg = ptOrg.ParentGroup; - PwGroup pgSrc = ptSrc.ParentGroup; - if(pgOrg.Uuid.Equals(pgSrc.Uuid)) - { - // pe.LocationChanged = ((ptSrc.LocationChanged > ptOrg.LocationChanged) ? - // ptSrc.LocationChanged : ptOrg.LocationChanged); - continue; - } - - if(ptSrc.LocationChanged > ptOrg.LocationChanged) - { - PwGroup pgLocal = m_pgRootGroup.FindGroup(pgSrc.Uuid, true); - if(pgLocal == null) { Debug.Assert(false); continue; } - - pe.ParentGroup.Entries.Remove(pe); - - // pgLocal.AddEntry(pe, true); - InsertObjectAtBestPos(pgLocal.Entries, pe, ppSrc); - pe.ParentGroup = pgLocal; - - // pe.LocationChanged = ptSrc.LocationChanged; - } - else - { - Debug.Assert(pe.ParentGroup.Uuid.Equals(pgOrg.Uuid)); - Debug.Assert(pe.LocationChanged == ptOrg.LocationChanged); - } - } - - Debug.Assert(m_pgRootGroup.GetEntries(true).UCount == vEntries.UCount); - } - - private void ReorderObjects(PwGroup pg, PwObjectPoolEx ppOrg, - PwObjectPoolEx ppSrc) - { - ReorderObjectList(pg.Groups, ppOrg, ppSrc); - ReorderObjectList(pg.Entries, ppOrg, ppSrc); - - foreach(PwGroup pgSub in pg.Groups) - { - ReorderObjects(pgSub, ppOrg, ppSrc); - } - } - - private void ReorderObjectList(PwObjectList lItems, - PwObjectPoolEx ppOrg, PwObjectPoolEx ppSrc) - where T : class, ITimeLogger, IStructureItem, IDeepCloneable - { - List> lBlocks = PartitionConsec(lItems, ppOrg, ppSrc); - if(lBlocks.Count <= 1) return; - -#if DEBUG - PwObjectList lOrgItems = lItems.CloneShallow(); -#endif - - Queue> qToDo = new Queue>(); - qToDo.Enqueue(new KeyValuePair(0, lBlocks.Count - 1)); - - while(qToDo.Count > 0) - { - if((m_slStatus != null) && !m_slStatus.ContinueWork()) break; - - KeyValuePair kvp = qToDo.Dequeue(); - if(kvp.Key >= kvp.Value) { Debug.Assert(false); continue; } - - PwObjectPoolEx pPool; - int iPivot = FindLocationChangedPivot(lBlocks, kvp, out pPool); - PwObjectBlock bPivot = lBlocks[iPivot]; - - T tPivotPrimary = bPivot.PrimaryItem; - if(tPivotPrimary == null) { Debug.Assert(false); continue; } - ulong idPivot = pPool.GetIdByUuid(tPivotPrimary.Uuid); - if(idPivot == 0) { Debug.Assert(false); continue; } - - Queue> qBefore = new Queue>(); - Queue> qAfter = new Queue>(); - bool bBefore = true; - - for(int i = kvp.Key; i <= kvp.Value; ++i) - { - if(i == iPivot) { bBefore = false; continue; } - - PwObjectBlock b = lBlocks[i]; - Debug.Assert(b.LocationChanged <= bPivot.LocationChanged); - - T t = b.PrimaryItem; - if(t != null) - { - ulong idBPri = pPool.GetIdByUuid(t.Uuid); - if(idBPri > 0) - { - if(idBPri < idPivot) qBefore.Enqueue(b); - else qAfter.Enqueue(b); - - continue; - } - } - else { Debug.Assert(false); } - - if(bBefore) qBefore.Enqueue(b); - else qAfter.Enqueue(b); - } - - int j = kvp.Key; - while(qBefore.Count > 0) { lBlocks[j] = qBefore.Dequeue(); ++j; } - int iNewPivot = j; - lBlocks[j] = bPivot; - ++j; - while(qAfter.Count > 0) { lBlocks[j] = qAfter.Dequeue(); ++j; } - Debug.Assert(j == (kvp.Value + 1)); - - if((iNewPivot - 1) > kvp.Key) - qToDo.Enqueue(new KeyValuePair(kvp.Key, iNewPivot - 1)); - if((iNewPivot + 1) < kvp.Value) - qToDo.Enqueue(new KeyValuePair(iNewPivot + 1, kvp.Value)); - } - - uint u = 0; - foreach(PwObjectBlock b in lBlocks) - { - foreach(T t in b) - { - lItems.SetAt(u, t); - ++u; - } - } - Debug.Assert(u == lItems.UCount); - -#if DEBUG - Debug.Assert(u == lOrgItems.UCount); - foreach(T ptItem in lOrgItems) - { - Debug.Assert(lItems.IndexOf(ptItem) >= 0); - } -#endif - } - - private static List> PartitionConsec(PwObjectList lItems, - PwObjectPoolEx ppOrg, PwObjectPoolEx ppSrc) - where T : class, ITimeLogger, IStructureItem, IDeepCloneable - { - List> lBlocks = new List>(); - - Dictionary dItemUuids = new Dictionary(); - foreach(T t in lItems) { dItemUuids[t.Uuid] = true; } - - uint n = lItems.UCount; - for(uint u = 0; u < n; ++u) - { - T t = lItems.GetAt(u); - - PwObjectBlock b = new PwObjectBlock(); - - DateTime dtLoc; - PwObjectPoolEx pPool = GetBestPool(t, ppOrg, ppSrc, out dtLoc); - b.Add(t, dtLoc, pPool); - - lBlocks.Add(b); - - ulong idOrg = ppOrg.GetIdByUuid(t.Uuid); - ulong idSrc = ppSrc.GetIdByUuid(t.Uuid); - if((idOrg == 0) || (idSrc == 0)) continue; - - for(uint x = u + 1; x < n; ++x) - { - T tNext = lItems.GetAt(x); - - ulong idOrgNext = idOrg + 1; - while(true) - { - IStructureItem ptOrg = ppOrg.GetItemById(idOrgNext); - if(ptOrg == null) { idOrgNext = 0; break; } - if(ptOrg.Uuid.Equals(tNext.Uuid)) break; // Found it - if(dItemUuids.ContainsKey(ptOrg.Uuid)) { idOrgNext = 0; break; } - ++idOrgNext; - } - if(idOrgNext == 0) break; - - ulong idSrcNext = idSrc + 1; - while(true) - { - IStructureItem ptSrc = ppSrc.GetItemById(idSrcNext); - if(ptSrc == null) { idSrcNext = 0; break; } - if(ptSrc.Uuid.Equals(tNext.Uuid)) break; // Found it - if(dItemUuids.ContainsKey(ptSrc.Uuid)) { idSrcNext = 0; break; } - ++idSrcNext; - } - if(idSrcNext == 0) break; - - pPool = GetBestPool(tNext, ppOrg, ppSrc, out dtLoc); - b.Add(tNext, dtLoc, pPool); - - ++u; - idOrg = idOrgNext; - idSrc = idSrcNext; - } - } - - return lBlocks; - } - - private static PwObjectPoolEx GetBestPool(T t, PwObjectPoolEx ppOrg, - PwObjectPoolEx ppSrc, out DateTime dtLoc) - where T : class, ITimeLogger, IStructureItem, IDeepCloneable - { - PwObjectPoolEx p = null; - dtLoc = TimeUtil.SafeMinValueUtc; - - IStructureItem ptOrg = ppOrg.GetItemByUuid(t.Uuid); - if(ptOrg != null) - { - dtLoc = ptOrg.LocationChanged; - p = ppOrg; - } - - IStructureItem ptSrc = ppSrc.GetItemByUuid(t.Uuid); - if((ptSrc != null) && (ptSrc.LocationChanged > dtLoc)) - { - dtLoc = ptSrc.LocationChanged; - p = ppSrc; - } - - Debug.Assert(p != null); - return p; - } - - private static int FindLocationChangedPivot(List> lBlocks, - KeyValuePair kvpRange, out PwObjectPoolEx pPool) - where T : class, ITimeLogger, IStructureItem, IDeepCloneable - { - pPool = null; - - int iPosMax = kvpRange.Key; - DateTime dtMax = TimeUtil.SafeMinValueUtc; - - for(int i = kvpRange.Key; i <= kvpRange.Value; ++i) - { - PwObjectBlock b = lBlocks[i]; - if(b.LocationChanged > dtMax) - { - iPosMax = i; - dtMax = b.LocationChanged; - pPool = b.PoolAssoc; - } - } - - return iPosMax; - } - - private static void MergeInLocationChanged(PwGroup pg, - PwObjectPoolEx ppOrg, PwObjectPoolEx ppSrc) - { - GroupHandler gh = delegate(PwGroup pgSub) - { - DateTime dt; - if(GetBestPool(pgSub, ppOrg, ppSrc, out dt) != null) - pgSub.LocationChanged = dt; - else { Debug.Assert(false); } - return true; - }; - - EntryHandler eh = delegate(PwEntry pe) - { - DateTime dt; - if(GetBestPool(pe, ppOrg, ppSrc, out dt) != null) - pe.LocationChanged = dt; - else { Debug.Assert(false); } - return true; - }; - - gh(pg); - pg.TraverseTree(TraversalMethod.PreOrder, gh, eh); - } - - private static void InsertObjectAtBestPos(PwObjectList lItems, - T tNew, PwObjectPoolEx ppSrc) - where T : class, ITimeLogger, IStructureItem, IDeepCloneable - { - if(tNew == null) { Debug.Assert(false); return; } - - ulong idSrc = ppSrc.GetIdByUuid(tNew.Uuid); - if(idSrc == 0) { Debug.Assert(false); lItems.Add(tNew); return; } - - const uint uIdOffset = 2; - Dictionary dOrg = new Dictionary(); - for(uint u = 0; u < lItems.UCount; ++u) - dOrg[lItems.GetAt(u).Uuid] = uIdOffset + u; - - ulong idSrcNext = idSrc + 1; - uint idOrgNext = 0; - while(true) - { - IStructureItem pNext = ppSrc.GetItemById(idSrcNext); - if(pNext == null) break; - if(dOrg.TryGetValue(pNext.Uuid, out idOrgNext)) break; - ++idSrcNext; - } - - if(idOrgNext != 0) - { - lItems.Insert(idOrgNext - uIdOffset, tNew); - return; - } - - ulong idSrcPrev = idSrc - 1; - uint idOrgPrev = 0; - while(true) - { - IStructureItem pPrev = ppSrc.GetItemById(idSrcPrev); - if(pPrev == null) break; - if(dOrg.TryGetValue(pPrev.Uuid, out idOrgPrev)) break; - --idSrcPrev; - } - - if(idOrgPrev != 0) - { - lItems.Insert(idOrgPrev + 1 - uIdOffset, tNew); - return; - } - - lItems.Add(tNew); - } - - private void MergeInDbProperties(PwDatabase pdSource, PwMergeMethod mm) - { - if(pdSource == null) { Debug.Assert(false); return; } - if((mm == PwMergeMethod.KeepExisting) || (mm == PwMergeMethod.None)) - return; - - bool bForce = (mm == PwMergeMethod.OverwriteExisting); - bool bSourceNewer = (pdSource.m_dtSettingsChanged > m_dtSettingsChanged); - - if(bForce || bSourceNewer) - { - m_dtSettingsChanged = pdSource.m_dtSettingsChanged; - - m_clr = pdSource.m_clr; - } - - if(bForce || (pdSource.m_dtNameChanged > m_dtNameChanged)) - { - m_strName = pdSource.m_strName; - m_dtNameChanged = pdSource.m_dtNameChanged; - } - - if(bForce || (pdSource.m_dtDescChanged > m_dtDescChanged)) - { - m_strDesc = pdSource.m_strDesc; - m_dtDescChanged = pdSource.m_dtDescChanged; - } - - if(bForce || (pdSource.m_dtDefaultUserChanged > m_dtDefaultUserChanged)) - { - m_strDefaultUserName = pdSource.m_strDefaultUserName; - m_dtDefaultUserChanged = pdSource.m_dtDefaultUserChanged; - } - - PwUuid pwPrefBin = m_pwRecycleBin, pwAltBin = pdSource.m_pwRecycleBin; - if(bForce || (pdSource.m_dtRecycleBinChanged > m_dtRecycleBinChanged)) - { - pwPrefBin = pdSource.m_pwRecycleBin; - pwAltBin = m_pwRecycleBin; - m_bUseRecycleBin = pdSource.m_bUseRecycleBin; - m_dtRecycleBinChanged = pdSource.m_dtRecycleBinChanged; - } - if(m_pgRootGroup.FindGroup(pwPrefBin, true) != null) - m_pwRecycleBin = pwPrefBin; - else if(m_pgRootGroup.FindGroup(pwAltBin, true) != null) - m_pwRecycleBin = pwAltBin; - else m_pwRecycleBin = PwUuid.Zero; // Debug.Assert(false); - - PwUuid pwPrefTmp = m_pwEntryTemplatesGroup, pwAltTmp = pdSource.m_pwEntryTemplatesGroup; - if(bForce || (pdSource.m_dtEntryTemplatesChanged > m_dtEntryTemplatesChanged)) - { - pwPrefTmp = pdSource.m_pwEntryTemplatesGroup; - pwAltTmp = m_pwEntryTemplatesGroup; - m_dtEntryTemplatesChanged = pdSource.m_dtEntryTemplatesChanged; - } - if(m_pgRootGroup.FindGroup(pwPrefTmp, true) != null) - m_pwEntryTemplatesGroup = pwPrefTmp; - else if(m_pgRootGroup.FindGroup(pwAltTmp, true) != null) - m_pwEntryTemplatesGroup = pwAltTmp; - else m_pwEntryTemplatesGroup = PwUuid.Zero; // Debug.Assert(false); - - foreach(KeyValuePair kvp in pdSource.m_dCustomData) - { - if(bSourceNewer || !m_dCustomData.Exists(kvp.Key)) - m_dCustomData.Set(kvp.Key, kvp.Value); - } - - VariantDictionary vdLocal = m_dPublicCustomData; // Backup - m_dPublicCustomData = (VariantDictionary)pdSource.m_dPublicCustomData.Clone(); - if(!bSourceNewer) vdLocal.CopyTo(m_dPublicCustomData); // Merge - } - - private void MergeEntryHistory(PwEntry pe, PwEntry peSource, - PwMergeMethod mm) - { - if(!pe.Uuid.Equals(peSource.Uuid)) { Debug.Assert(false); return; } - - if(pe.History.UCount == peSource.History.UCount) - { - bool bEqual = true; - for(uint uEnum = 0; uEnum < pe.History.UCount; ++uEnum) - { - if(pe.History.GetAt(uEnum).LastModificationTime != - peSource.History.GetAt(uEnum).LastModificationTime) - { - bEqual = false; - break; - } - } - - if(bEqual) return; - } - - if((m_slStatus != null) && !m_slStatus.ContinueWork()) return; - - IDictionary dict = -#if KeePassLibSD - new SortedList(); -#else - new SortedDictionary(); -#endif - foreach(PwEntry peOrg in pe.History) - { - dict[peOrg.LastModificationTime] = peOrg; - } - - foreach(PwEntry peSrc in peSource.History) - { - DateTime dt = peSrc.LastModificationTime; - if(dict.ContainsKey(dt)) - { - if(mm == PwMergeMethod.OverwriteExisting) - dict[dt] = peSrc.CloneDeep(); - } - else dict[dt] = peSrc.CloneDeep(); - } - - pe.History.Clear(); - foreach(KeyValuePair kvpCur in dict) - { - Debug.Assert(kvpCur.Value.Uuid.Equals(pe.Uuid)); - Debug.Assert(kvpCur.Value.History.UCount == 0); - pe.History.Add(kvpCur.Value); - } - } - - public bool MaintainBackups() - { - if(m_pgRootGroup == null) { Debug.Assert(false); return false; } - - bool bDeleted = false; - EntryHandler eh = delegate(PwEntry pe) - { - if(pe.MaintainBackups(this)) bDeleted = true; - return true; - }; - - m_pgRootGroup.TraverseTree(TraversalMethod.PreOrder, null, eh); - return bDeleted; - } - - /* /// - /// Synchronize current database with another one. - /// - /// Source file. - public void Synchronize(string strFile) - { - PwDatabase pdSource = new PwDatabase(); - - IOConnectionInfo ioc = IOConnectionInfo.FromPath(strFile); - pdSource.Open(ioc, m_pwUserKey, null); - - MergeIn(pdSource, PwMergeMethod.Synchronize); - } */ - - /// - /// Get the index of a custom icon. - /// - /// ID of the icon. - /// Index of the icon. - public int GetCustomIconIndex(PwUuid pwIconId) - { - for(int i = 0; i < m_vCustomIcons.Count; ++i) - { - PwCustomIcon pwci = m_vCustomIcons[i]; - if(pwci.Uuid.Equals(pwIconId)) - return i; - } - - // Debug.Assert(false); // Do not assert - return -1; - } - - public int GetCustomIconIndex(byte[] pbPngData) - { - if(pbPngData == null) { Debug.Assert(false); return -1; } - - for(int i = 0; i < m_vCustomIcons.Count; ++i) - { - PwCustomIcon pwci = m_vCustomIcons[i]; - byte[] pbEx = pwci.ImageDataPng; - if(pbEx == null) { Debug.Assert(false); continue; } - - if(MemUtil.ArraysEqual(pbEx, pbPngData)) - return i; - } - - return -1; - } - -#if KeePassUAP - public Image GetCustomIcon(PwUuid pwIconId) - { - int nIndex = GetCustomIconIndex(pwIconId); - if(nIndex >= 0) - return m_vCustomIcons[nIndex].GetImage(); - else { Debug.Assert(false); } - - return null; - } -#elif !KeePassLibSD - [Obsolete("Additionally specify the size.")] - public Image GetCustomIcon(PwUuid pwIconId) - { - return GetCustomIcon(pwIconId, 16, 16); // Backward compatibility - } - - /// - /// Get a custom icon. This method can return null, - /// e.g. if no cached image of the icon is available. - /// - /// ID of the icon. - /// Width of the returned image. If this is - /// negative, the image is returned in its original size. - /// Height of the returned image. If this is - /// negative, the image is returned in its original size. - public Image GetCustomIcon(PwUuid pwIconId, int w, int h) - { - int nIndex = GetCustomIconIndex(pwIconId); - if(nIndex >= 0) - { - if((w >= 0) && (h >= 0)) - return m_vCustomIcons[nIndex].GetImage(w, h); - else return m_vCustomIcons[nIndex].GetImage(); // No assert - } - else { Debug.Assert(false); } - - return null; - } -#endif - - public bool DeleteCustomIcons(List vUuidsToDelete) - { - Debug.Assert(vUuidsToDelete != null); - if(vUuidsToDelete == null) throw new ArgumentNullException("vUuidsToDelete"); - if(vUuidsToDelete.Count <= 0) return true; - - GroupHandler gh = delegate(PwGroup pg) - { - PwUuid uuidThis = pg.CustomIconUuid; - if(uuidThis.Equals(PwUuid.Zero)) return true; - - foreach(PwUuid uuidDelete in vUuidsToDelete) - { - if(uuidThis.Equals(uuidDelete)) - { - pg.CustomIconUuid = PwUuid.Zero; - break; - } - } - - return true; - }; - - EntryHandler eh = delegate(PwEntry pe) - { - RemoveCustomIconUuid(pe, vUuidsToDelete); - return true; - }; - - gh(m_pgRootGroup); - if(!m_pgRootGroup.TraverseTree(TraversalMethod.PreOrder, gh, eh)) - { - Debug.Assert(false); - return false; - } - - foreach(PwUuid pwUuid in vUuidsToDelete) - { - int nIndex = GetCustomIconIndex(pwUuid); - if(nIndex >= 0) m_vCustomIcons.RemoveAt(nIndex); - } - - return true; - } - - private static void RemoveCustomIconUuid(PwEntry pe, List vToDelete) - { - PwUuid uuidThis = pe.CustomIconUuid; - if(uuidThis.Equals(PwUuid.Zero)) return; - - foreach(PwUuid uuidDelete in vToDelete) - { - if(uuidThis.Equals(uuidDelete)) - { - pe.CustomIconUuid = PwUuid.Zero; - break; - } - } - - foreach(PwEntry peHistory in pe.History) - RemoveCustomIconUuid(peHistory, vToDelete); - } - - private int GetTotalObjectUuidCount() - { - uint uGroups, uEntries; - m_pgRootGroup.GetCounts(true, out uGroups, out uEntries); - - uint uTotal = uGroups + uEntries + 1; // 1 for root group - if(uTotal > 0x7FFFFFFFU) { Debug.Assert(false); return 0x7FFFFFFF; } - return (int)uTotal; - } - - internal bool HasDuplicateUuids() - { - int nTotal = GetTotalObjectUuidCount(); - Dictionary d = new Dictionary(nTotal); - bool bDupFound = false; - - GroupHandler gh = delegate(PwGroup pg) - { - PwUuid pu = pg.Uuid; - if(d.ContainsKey(pu)) - { - bDupFound = true; - return false; - } - - d.Add(pu, null); - Debug.Assert(d.ContainsKey(pu)); - return true; - }; - - EntryHandler eh = delegate(PwEntry pe) - { - PwUuid pu = pe.Uuid; - if(d.ContainsKey(pu)) - { - bDupFound = true; - return false; - } - - d.Add(pu, null); - Debug.Assert(d.ContainsKey(pu)); - return true; - }; - - gh(m_pgRootGroup); - m_pgRootGroup.TraverseTree(TraversalMethod.PreOrder, gh, eh); - - Debug.Assert(bDupFound || (d.Count == nTotal)); - return bDupFound; - } - - internal void FixDuplicateUuids() - { - int nTotal = GetTotalObjectUuidCount(); - Dictionary d = new Dictionary(nTotal); - - GroupHandler gh = delegate(PwGroup pg) - { - PwUuid pu = pg.Uuid; - if(d.ContainsKey(pu)) - { - pu = new PwUuid(true); - while(d.ContainsKey(pu)) { Debug.Assert(false); pu = new PwUuid(true); } - - pg.Uuid = pu; - } - - d.Add(pu, null); - return true; - }; - - EntryHandler eh = delegate(PwEntry pe) - { - PwUuid pu = pe.Uuid; - if(d.ContainsKey(pu)) - { - pu = new PwUuid(true); - while(d.ContainsKey(pu)) { Debug.Assert(false); pu = new PwUuid(true); } - - pe.SetUuid(pu, true); - } - - d.Add(pu, null); - return true; - }; - - gh(m_pgRootGroup); - m_pgRootGroup.TraverseTree(TraversalMethod.PreOrder, gh, eh); - - Debug.Assert(d.Count == nTotal); - Debug.Assert(!HasDuplicateUuids()); - } - - /* public void CreateBackupFile(IStatusLogger sl) - { - if(sl != null) sl.SetText(KLRes.CreatingBackupFile, LogStatusType.Info); - - IOConnectionInfo iocBk = m_ioSource.CloneDeep(); - iocBk.Path += StrBackupExtension; - - bool bMadeUnhidden = UrlUtil.UnhideFile(iocBk.Path); - - bool bFastCopySuccess = false; - if(m_ioSource.IsLocalFile() && (m_ioSource.UserName.Length == 0) && - (m_ioSource.Password.Length == 0)) - { - try - { - string strFile = m_ioSource.Path + StrBackupExtension; - File.Copy(m_ioSource.Path, strFile, true); - bFastCopySuccess = true; - } - catch(Exception) { Debug.Assert(false); } - } - - if(bFastCopySuccess == false) - { - using(Stream sIn = IOConnection.OpenRead(m_ioSource)) - { - using(Stream sOut = IOConnection.OpenWrite(iocBk)) - { - MemUtil.CopyStream(sIn, sOut); - } - } - } - - if(bMadeUnhidden) UrlUtil.HideFile(iocBk.Path, true); // Hide again - } */ - - /* private static void RemoveData(PwGroup pg) - { - EntryHandler eh = delegate(PwEntry pe) - { - pe.AutoType.Clear(); - pe.Binaries.Clear(); - pe.History.Clear(); - pe.Strings.Clear(); - return true; - }; - - pg.TraverseTree(TraversalMethod.PreOrder, null, eh); - } */ - - public uint DeleteDuplicateEntries(IStatusLogger sl) - { - uint uDeleted = 0; - - PwGroup pgRecycleBin = null; - if(m_bUseRecycleBin) - pgRecycleBin = m_pgRootGroup.FindGroup(m_pwRecycleBin, true); - - DateTime dtNow = DateTime.UtcNow; - PwObjectList l = m_pgRootGroup.GetEntries(true); - int i = 0; - while(true) - { - if(i >= ((int)l.UCount - 1)) break; - - if(sl != null) - { - long lCnt = (long)l.UCount, li = (long)i; - long nArTotal = (lCnt * lCnt) / 2L; - long nArCur = li * lCnt - ((li * li) / 2L); - long nArPct = (nArCur * 100L) / nArTotal; - if(nArPct < 0) nArPct = 0; - if(nArPct > 100) nArPct = 100; - if(!sl.SetProgress((uint)nArPct)) break; - } - - PwEntry peA = l.GetAt((uint)i); - - for(uint j = (uint)i + 1; j < l.UCount; ++j) - { - PwEntry peB = l.GetAt(j); - if(!DupEntriesEqual(peA, peB)) continue; - - bool bDeleteA = (TimeUtil.CompareLastMod(peA, peB, true) <= 0); - if(pgRecycleBin != null) - { - bool bAInBin = peA.IsContainedIn(pgRecycleBin); - bool bBInBin = peB.IsContainedIn(pgRecycleBin); - - if(bAInBin && !bBInBin) bDeleteA = true; - else if(bBInBin && !bAInBin) bDeleteA = false; - } - - if(bDeleteA) - { - peA.ParentGroup.Entries.Remove(peA); - m_vDeletedObjects.Add(new PwDeletedObject(peA.Uuid, dtNow)); - - l.RemoveAt((uint)i); - --i; - } - else - { - peB.ParentGroup.Entries.Remove(peB); - m_vDeletedObjects.Add(new PwDeletedObject(peB.Uuid, dtNow)); - - l.RemoveAt(j); - } - - ++uDeleted; - break; - } - - ++i; - } - - return uDeleted; - } - - private static List m_lStdFields = null; - private static bool DupEntriesEqual(PwEntry a, PwEntry b) - { - if(m_lStdFields == null) m_lStdFields = PwDefs.GetStandardFields(); - - foreach(string strStdKey in m_lStdFields) - { - string strA = a.Strings.ReadSafe(strStdKey); - string strB = b.Strings.ReadSafe(strStdKey); - if(!strA.Equals(strB)) return false; - } - - foreach(KeyValuePair kvpA in a.Strings) - { - if(PwDefs.IsStandardField(kvpA.Key)) continue; - - ProtectedString psB = b.Strings.Get(kvpA.Key); - if(psB == null) return false; - - // Ignore protection setting, compare values only - if(!psB.Equals(kvpA.Value, false)) return false; - } - - foreach(KeyValuePair kvpB in b.Strings) - { - if(PwDefs.IsStandardField(kvpB.Key)) continue; - - ProtectedString psA = a.Strings.Get(kvpB.Key); - if(psA == null) return false; - - // Must be equal by logic - Debug.Assert(psA.Equals(kvpB.Value, false)); - } - - if(a.Binaries.UCount != b.Binaries.UCount) return false; - foreach(KeyValuePair kvpBin in a.Binaries) - { - ProtectedBinary pbA = kvpBin.Value; - ProtectedBinary pbB = b.Binaries.Get(kvpBin.Key); - if(pbB == null) return false; - - // Ignore protection setting, compare values only - if(!pbB.Equals(pbA, false)) return false; - } - - return true; - } - - public uint DeleteEmptyGroups() - { - uint uDeleted = 0; - - PwObjectList l = m_pgRootGroup.GetGroups(true); - int iStart = (int)l.UCount - 1; - for(int i = iStart; i >= 0; --i) - { - PwGroup pg = l.GetAt((uint)i); - if((pg.Groups.UCount > 0) || (pg.Entries.UCount > 0)) continue; - - pg.ParentGroup.Groups.Remove(pg); - m_vDeletedObjects.Add(new PwDeletedObject(pg.Uuid, DateTime.UtcNow)); - - ++uDeleted; - } - - return uDeleted; - } - - public uint DeleteUnusedCustomIcons() - { - List lToDelete = new List(); - foreach(PwCustomIcon pwci in m_vCustomIcons) - lToDelete.Add(pwci.Uuid); - - GroupHandler gh = delegate(PwGroup pg) - { - PwUuid pwUuid = pg.CustomIconUuid; - if((pwUuid == null) || pwUuid.Equals(PwUuid.Zero)) return true; - - for(int i = 0; i < lToDelete.Count; ++i) - { - if(lToDelete[i].Equals(pwUuid)) - { - lToDelete.RemoveAt(i); - break; - } - } - - return true; - }; - - EntryHandler eh = delegate(PwEntry pe) - { - PwUuid pwUuid = pe.CustomIconUuid; - if((pwUuid == null) || pwUuid.Equals(PwUuid.Zero)) return true; - - for(int i = 0; i < lToDelete.Count; ++i) - { - if(lToDelete[i].Equals(pwUuid)) - { - lToDelete.RemoveAt(i); - break; - } - } - - return true; - }; - - gh(m_pgRootGroup); - m_pgRootGroup.TraverseTree(TraversalMethod.PreOrder, gh, eh); - - uint uDeleted = 0; - foreach(PwUuid pwDel in lToDelete) - { - int nIndex = GetCustomIconIndex(pwDel); - if(nIndex < 0) { Debug.Assert(false); continue; } - - m_vCustomIcons.RemoveAt(nIndex); - ++uDeleted; - } - - if(uDeleted > 0) m_bUINeedsIconUpdate = true; - return uDeleted; - } - } -} diff --git a/ModernKeePassLib/PwDefs.cs b/ModernKeePassLib/PwDefs.cs deleted file mode 100644 index f3aade6..0000000 --- a/ModernKeePassLib/PwDefs.cs +++ /dev/null @@ -1,505 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Diagnostics; -using System.Xml.Serialization; - -using ModernKeePassLib.Delegates; -using ModernKeePassLib.Interfaces; -using ModernKeePassLib.Serialization; - -namespace ModernKeePassLib -{ - /// - /// Contains KeePassLib-global definitions and enums. - /// - public static class PwDefs - { - /// - /// The product name. - /// - public const string ProductName = "KeePass Password Safe"; - - /// - /// A short, simple string representing the product name. The string - /// should contain no spaces, directory separator characters, etc. - /// - public const string ShortProductName = "KeePass"; - - internal const string UnixName = "keepass2"; - internal const string ResClass = "KeePass2"; // With initial capital - - /// - /// Version, encoded as 32-bit unsigned integer. - /// 2.00 = 0x02000000, 2.01 = 0x02000100, ..., 2.18 = 0x02010800. - /// As of 2.19, the version is encoded component-wise per byte, - /// e.g. 2.19 = 0x02130000. - /// It is highly recommended to use FileVersion64 instead. - /// - public const uint Version32 = 0x02270100; - - /// - /// Version, encoded as 64-bit unsigned integer - /// (component-wise, 16 bits per component). - /// - public const ulong FileVersion64 = 0x0002002700010000UL; - - /// - /// Version, encoded as string. - /// - public const string VersionString = "2.39.1"; - - public const string Copyright = @"Copyright © 2003-2018 Dominik Reichl"; - - /// - /// Product website URL. Terminated by a forward slash. - /// - public const string HomepageUrl = "https://keepass.info/"; - - /// - /// URL to the online translations page. - /// - public const string TranslationsUrl = "https://keepass.info/translations.html"; - - /// - /// URL to the online plugins page. - /// - public const string PluginsUrl = "https://keepass.info/plugins.html"; - - /// - /// Product donations URL. - /// - public const string DonationsUrl = "https://keepass.info/donate.html"; - - /// - /// URL to the root path of the online KeePass help. Terminated by - /// a forward slash. - /// - public const string HelpUrl = "https://keepass.info/help/"; - - /// - /// URL to a TXT file (eventually compressed) that contains information - /// about the latest KeePass version available on the website. - /// - public const string VersionUrl = "https://www.dominik-reichl.de/update/version2x.txt.gz"; - - /// - /// A DateTime object that represents the time when the assembly - /// was loaded. - /// - public static readonly DateTime DtDefaultNow = DateTime.UtcNow; - - /// - /// Default number of master key encryption/transformation rounds - /// (making dictionary attacks harder). - /// - public const ulong DefaultKeyEncryptionRounds = 60000; - - /// - /// Default identifier string for the title field. Should not contain - /// spaces, tabs or other whitespace. - /// - public const string TitleField = "Title"; - - /// - /// Default identifier string for the user name field. Should not contain - /// spaces, tabs or other whitespace. - /// - public const string UserNameField = "UserName"; - - /// - /// Default identifier string for the password field. Should not contain - /// spaces, tabs or other whitespace. - /// - public const string PasswordField = "Password"; - - /// - /// Default identifier string for the URL field. Should not contain - /// spaces, tabs or other whitespace. - /// - public const string UrlField = "URL"; - - /// - /// Default identifier string for the notes field. Should not contain - /// spaces, tabs or other whitespace. - /// - public const string NotesField = "Notes"; - - /// - /// Default identifier string for the field which will contain TAN indices. - /// - public const string TanIndexField = UserNameField; - - /// - /// Default title of an entry that is really a TAN entry. - /// - public const string TanTitle = @""; - - /// - /// Prefix of a custom auto-type string field. - /// - public const string AutoTypeStringPrefix = "S:"; - - /// - /// Default string representing a hidden password. - /// - public const string HiddenPassword = "********"; - - /// - /// Default auto-type keystroke sequence. If no custom sequence is - /// specified, this sequence is used. - /// - public const string DefaultAutoTypeSequence = @"{USERNAME}{TAB}{PASSWORD}{ENTER}"; - - /// - /// Default auto-type keystroke sequence for TAN entries. If no custom - /// sequence is specified, this sequence is used. - /// - public const string DefaultAutoTypeSequenceTan = @"{PASSWORD}"; - - /// - /// Check if a name is a standard field name. - /// - /// Input field name. - /// Returns true, if the field name is a standard - /// field name (title, user name, password, ...), otherwise false. - public static bool IsStandardField(string strFieldName) - { - Debug.Assert(strFieldName != null); if(strFieldName == null) return false; - - if(strFieldName.Equals(TitleField)) return true; - if(strFieldName.Equals(UserNameField)) return true; - if(strFieldName.Equals(PasswordField)) return true; - if(strFieldName.Equals(UrlField)) return true; - if(strFieldName.Equals(NotesField)) return true; - - return false; - } - - public static List GetStandardFields() - { - List l = new List(); - - l.Add(TitleField); - l.Add(UserNameField); - l.Add(PasswordField); - l.Add(UrlField); - l.Add(NotesField); - - return l; - } - - /// - /// Check if an entry is a TAN. - /// - /// Password entry. - /// Returns true if the entry is a TAN. - public static bool IsTanEntry(PwEntry pe) - { - Debug.Assert(pe != null); if(pe == null) return false; - - return (pe.Strings.ReadSafe(PwDefs.TitleField) == TanTitle); - } - - internal static string GetTranslationDisplayVersion(string strFileVersion) - { - if(strFileVersion == null) { Debug.Assert(false); return string.Empty; } - - if(strFileVersion == "2.39") return "2.39 / 2.39.1"; - - return strFileVersion; - } - } - - // #pragma warning disable 1591 // Missing XML comments warning - /// - /// Search parameters for group and entry searches. - /// - public sealed class SearchParameters - { - private string m_strText = string.Empty; - [DefaultValue("")] - public string SearchString - { - get { return m_strText; } - set - { - if(value == null) throw new ArgumentNullException("value"); - m_strText = value; - } - } - - private bool m_bRegex = false; - [DefaultValue(false)] - public bool RegularExpression - { - get { return m_bRegex; } - set { m_bRegex = value; } - } - - private bool m_bSearchInTitles = true; - [DefaultValue(true)] - public bool SearchInTitles - { - get { return m_bSearchInTitles; } - set { m_bSearchInTitles = value; } - } - - private bool m_bSearchInUserNames = true; - [DefaultValue(true)] - public bool SearchInUserNames - { - get { return m_bSearchInUserNames; } - set { m_bSearchInUserNames = value; } - } - - private bool m_bSearchInPasswords = false; - [DefaultValue(false)] - public bool SearchInPasswords - { - get { return m_bSearchInPasswords; } - set { m_bSearchInPasswords = value; } - } - - private bool m_bSearchInUrls = true; - [DefaultValue(true)] - public bool SearchInUrls - { - get { return m_bSearchInUrls; } - set { m_bSearchInUrls = value; } - } - - private bool m_bSearchInNotes = true; - [DefaultValue(true)] - public bool SearchInNotes - { - get { return m_bSearchInNotes; } - set { m_bSearchInNotes = value; } - } - - private bool m_bSearchInOther = true; - [DefaultValue(true)] - public bool SearchInOther - { - get { return m_bSearchInOther; } - set { m_bSearchInOther = value; } - } - - private bool m_bSearchInStringNames = false; - [DefaultValue(false)] - public bool SearchInStringNames - { - get { return m_bSearchInStringNames; } - set { m_bSearchInStringNames = value; } - } - - private bool m_bSearchInTags = true; - [DefaultValue(true)] - public bool SearchInTags - { - get { return m_bSearchInTags; } - set { m_bSearchInTags = value; } - } - - private bool m_bSearchInUuids = false; - [DefaultValue(false)] - public bool SearchInUuids - { - get { return m_bSearchInUuids; } - set { m_bSearchInUuids = value; } - } - - private bool m_bSearchInGroupNames = false; - [DefaultValue(false)] - public bool SearchInGroupNames - { - get { return m_bSearchInGroupNames; } - set { m_bSearchInGroupNames = value; } - } - -#if ModernKeePassLib || KeePassUAP - private StringComparison m_scType = StringComparison.OrdinalIgnoreCase; -#else - private StringComparison m_scType = StringComparison.InvariantCultureIgnoreCase; -#endif - /// - /// String comparison type. Specifies the condition when the specified - /// text matches a group/entry string. - /// - public StringComparison ComparisonMode - { - get { return m_scType; } - set { m_scType = value; } - } - - private bool m_bExcludeExpired = false; - [DefaultValue(false)] - public bool ExcludeExpired - { - get { return m_bExcludeExpired; } - set { m_bExcludeExpired = value; } - } - - private bool m_bRespectEntrySearchingDisabled = true; - [DefaultValue(true)] - public bool RespectEntrySearchingDisabled - { - get { return m_bRespectEntrySearchingDisabled; } - set { m_bRespectEntrySearchingDisabled = value; } - } - - private StrPwEntryDelegate m_fnDataTrf = null; - [XmlIgnore] - public StrPwEntryDelegate DataTransformationFn - { - get { return m_fnDataTrf; } - set { m_fnDataTrf = value; } - } - - private string m_strDataTrf = string.Empty; - /// - /// Only for serialization. - /// - [DefaultValue("")] - public string DataTransformation - { - get { return m_strDataTrf; } - set - { - if(value == null) throw new ArgumentNullException("value"); - m_strDataTrf = value; - } - } - - [XmlIgnore] - public static SearchParameters None - { - get - { - SearchParameters sp = new SearchParameters(); - - Debug.Assert(sp.m_strText.Length == 0); - Debug.Assert(!sp.m_bRegex); - sp.m_bSearchInTitles = false; - sp.m_bSearchInUserNames = false; - Debug.Assert(!sp.m_bSearchInPasswords); - sp.m_bSearchInUrls = false; - sp.m_bSearchInNotes = false; - sp.m_bSearchInOther = false; - Debug.Assert(!sp.m_bSearchInStringNames); - sp.m_bSearchInTags = false; - Debug.Assert(!sp.m_bSearchInUuids); - Debug.Assert(!sp.m_bSearchInGroupNames); - // Debug.Assert(sp.m_scType == StringComparison.InvariantCultureIgnoreCase); - Debug.Assert(!sp.m_bExcludeExpired); - Debug.Assert(sp.m_bRespectEntrySearchingDisabled); - - return sp; - } - } - - /// - /// Construct a new search parameters object. - /// - public SearchParameters() - { - } - - public SearchParameters Clone() - { - return (SearchParameters)this.MemberwiseClone(); - } - } - // #pragma warning restore 1591 // Missing XML comments warning - - // #pragma warning disable 1591 // Missing XML comments warning - /// - /// Memory protection configuration structure (for default fields). - /// - public sealed class MemoryProtectionConfig : IDeepCloneable - { - public bool ProtectTitle = false; - public bool ProtectUserName = false; - public bool ProtectPassword = true; - public bool ProtectUrl = false; - public bool ProtectNotes = false; - - // public bool AutoEnableVisualHiding = false; - - public MemoryProtectionConfig CloneDeep() - { - return (MemoryProtectionConfig)this.MemberwiseClone(); - } - - public bool GetProtection(string strField) - { - if(strField == PwDefs.TitleField) return this.ProtectTitle; - if(strField == PwDefs.UserNameField) return this.ProtectUserName; - if(strField == PwDefs.PasswordField) return this.ProtectPassword; - if(strField == PwDefs.UrlField) return this.ProtectUrl; - if(strField == PwDefs.NotesField) return this.ProtectNotes; - - return false; - } - } - // #pragma warning restore 1591 // Missing XML comments warning - - public sealed class ObjectTouchedEventArgs : EventArgs - { - private object m_o; - public object Object { get { return m_o; } } - - private bool m_bModified; - public bool Modified { get { return m_bModified; } } - - private bool m_bParentsTouched; - public bool ParentsTouched { get { return m_bParentsTouched; } } - - public ObjectTouchedEventArgs(object o, bool bModified, - bool bParentsTouched) - { - m_o = o; - m_bModified = bModified; - m_bParentsTouched = bParentsTouched; - } - } - - public sealed class IOAccessEventArgs : EventArgs - { - private IOConnectionInfo m_ioc; - public IOConnectionInfo IOConnectionInfo { get { return m_ioc; } } - - private IOConnectionInfo m_ioc2; - public IOConnectionInfo IOConnectionInfo2 { get { return m_ioc2; } } - - private IOAccessType m_t; - public IOAccessType Type { get { return m_t; } } - - public IOAccessEventArgs(IOConnectionInfo ioc, IOConnectionInfo ioc2, - IOAccessType t) - { - m_ioc = ioc; - m_ioc2 = ioc2; - m_t = t; - } - } -} diff --git a/ModernKeePassLib/PwDeletedObject.cs b/ModernKeePassLib/PwDeletedObject.cs deleted file mode 100644 index 3f10a09..0000000 --- a/ModernKeePassLib/PwDeletedObject.cs +++ /dev/null @@ -1,86 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; -using System.Diagnostics; - -using ModernKeePassLib.Interfaces; - -namespace ModernKeePassLib -{ - /// - /// Represents an object that has been deleted. - /// - public sealed class PwDeletedObject : IDeepCloneable - { - private PwUuid m_uuid = PwUuid.Zero; - /// - /// UUID of the entry that has been deleted. - /// - public PwUuid Uuid - { - get { return m_uuid; } - set - { - if(value == null) throw new ArgumentNullException("value"); - m_uuid = value; - } - } - - private DateTime m_dtDeletionTime = PwDefs.DtDefaultNow; - /// - /// The date/time when the entry has been deleted. - /// - public DateTime DeletionTime - { - get { return m_dtDeletionTime; } - set { m_dtDeletionTime = value; } - } - - /// - /// Construct a new PwDeletedObject object. - /// - public PwDeletedObject() - { - } - - public PwDeletedObject(PwUuid uuid, DateTime dtDeletionTime) - { - if(uuid == null) throw new ArgumentNullException("uuid"); - - m_uuid = uuid; - m_dtDeletionTime = dtDeletionTime; - } - - /// - /// Clone the object. - /// - /// Value copy of the current object. - public PwDeletedObject CloneDeep() - { - PwDeletedObject pdo = new PwDeletedObject(); - - pdo.m_uuid = m_uuid; // PwUuid objects are immutable - pdo.m_dtDeletionTime = m_dtDeletionTime; - - return pdo; - } - } -} diff --git a/ModernKeePassLib/PwEntry.cs b/ModernKeePassLib/PwEntry.cs deleted file mode 100644 index 2f91d92..0000000 --- a/ModernKeePassLib/PwEntry.cs +++ /dev/null @@ -1,947 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; -using System.Diagnostics; - -using System.Drawing; - -using ModernKeePassLib.Collections; -using ModernKeePassLib.Interfaces; -using ModernKeePassLib.Security; -using ModernKeePassLib.Utility; - -namespace ModernKeePassLib -{ - /// - /// A class representing a password entry. A password entry consists of several - /// fields like title, user name, password, etc. Each password entry has a - /// unique ID (UUID). - /// - public sealed class PwEntry : ITimeLogger, IStructureItem, IDeepCloneable - { - private PwUuid m_uuid = PwUuid.Zero; - private PwGroup m_pParentGroup = null; - private DateTime m_tParentGroupLastMod = PwDefs.DtDefaultNow; - - private ProtectedStringDictionary m_listStrings = new ProtectedStringDictionary(); - private ProtectedBinaryDictionary m_listBinaries = new ProtectedBinaryDictionary(); - private AutoTypeConfig m_listAutoType = new AutoTypeConfig(); - private PwObjectList m_listHistory = new PwObjectList(); - - private PwIcon m_pwIcon = PwIcon.Key; - private PwUuid m_pwCustomIconID = PwUuid.Zero; - - private Color m_clrForeground = Color.Empty; - private Color m_clrBackground = Color.Empty; - - private DateTime m_tCreation = PwDefs.DtDefaultNow; - private DateTime m_tLastMod = PwDefs.DtDefaultNow; - private DateTime m_tLastAccess = PwDefs.DtDefaultNow; - private DateTime m_tExpire = PwDefs.DtDefaultNow; - private bool m_bExpires = false; - private ulong m_uUsageCount = 0; - - private string m_strOverrideUrl = string.Empty; - - private List m_vTags = new List(); - - private StringDictionaryEx m_dCustomData = new StringDictionaryEx(); - - /// - /// UUID of this entry. - /// - public PwUuid Uuid - { - get { return m_uuid; } - set - { - Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); - m_uuid = value; - } - } - - /// - /// Reference to a group which contains the current entry. - /// - public PwGroup ParentGroup - { - get { return m_pParentGroup; } - - // Plugins: use PwGroup.AddEntry instead. - internal set { m_pParentGroup = value; } - } - - /// - /// The date/time when the location of the object was last changed. - /// - public DateTime LocationChanged - { - get { return m_tParentGroupLastMod; } - set { m_tParentGroupLastMod = value; } - } - - /// - /// Get or set all entry strings. - /// - public ProtectedStringDictionary Strings - { - get { return m_listStrings; } - set - { - Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); - m_listStrings = value; - } - } - - /// - /// Get or set all entry binaries. - /// - public ProtectedBinaryDictionary Binaries - { - get { return m_listBinaries; } - set - { - Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); - m_listBinaries = value; - } - } - - /// - /// Get or set all auto-type window/keystroke sequence associations. - /// - public AutoTypeConfig AutoType - { - get { return m_listAutoType; } - set - { - Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); - m_listAutoType = value; - } - } - - /// - /// Get all previous versions of this entry (backups). - /// - public PwObjectList History - { - get { return m_listHistory; } - set - { - Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); - m_listHistory = value; - } - } - - /// - /// Image ID specifying the icon that will be used for this entry. - /// - public PwIcon IconId - { - get { return m_pwIcon; } - set { m_pwIcon = value; } - } - - /// - /// Get the custom icon ID. This value is 0, if no custom icon is - /// being used (i.e. the icon specified by the IconID property - /// should be displayed). - /// - public PwUuid CustomIconUuid - { - get { return m_pwCustomIconID; } - set - { - Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); - m_pwCustomIconID = value; - } - } - - /// - /// Get or set the foreground color of this entry. - /// - public Color ForegroundColor - { - get { return m_clrForeground; } - set { m_clrForeground = value; } - } - - /// - /// Get or set the background color of this entry. - /// - public Color BackgroundColor - { - get { return m_clrBackground; } - set { m_clrBackground = value; } - } - - /// - /// The date/time when this entry was created. - /// - public DateTime CreationTime - { - get { return m_tCreation; } - set { m_tCreation = value; } - } - - /// - /// The date/time when this entry was last modified. - /// - public DateTime LastModificationTime - { - get { return m_tLastMod; } - set { m_tLastMod = value; } - } - - /// - /// The date/time when this entry was last accessed (read). - /// - public DateTime LastAccessTime - { - get { return m_tLastAccess; } - set { m_tLastAccess = value; } - } - - /// - /// The date/time when this entry expires. Use the Expires property - /// to specify if the entry does actually expire or not. - /// - public DateTime ExpiryTime - { - get { return m_tExpire; } - set { m_tExpire = value; } - } - - /// - /// Specifies whether the entry expires or not. - /// - public bool Expires - { - get { return m_bExpires; } - set { m_bExpires = value; } - } - - /// - /// Get or set the usage count of the entry. To increase the usage - /// count by one, use the Touch function. - /// - public ulong UsageCount - { - get { return m_uUsageCount; } - set { m_uUsageCount = value; } - } - - /// - /// Entry-specific override URL. If this string is non-empty, - /// - public string OverrideUrl - { - get { return m_strOverrideUrl; } - set - { - if(value == null) throw new ArgumentNullException("value"); - m_strOverrideUrl = value; - } - } - - /// - /// List of tags associated with this entry. - /// - public List Tags - { - get { return m_vTags; } - set - { - if(value == null) throw new ArgumentNullException("value"); - m_vTags = value; - } - } - - /// - /// Custom data container that can be used by plugins to store - /// own data in KeePass entries. - /// The data is stored in the encrypted part of encrypted - /// database files. - /// Use unique names for your items, e.g. "PluginName_ItemName". - /// - public StringDictionaryEx CustomData - { - get { return m_dCustomData; } - internal set - { - if(value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); } - m_dCustomData = value; - } - } - - public static EventHandler EntryTouched; - public EventHandler Touched; - - /// - /// Construct a new, empty password entry. Member variables will be initialized - /// to their default values. - /// - /// If true, a new UUID will be created - /// for this entry. If false, the UUID is zero and you must set it - /// manually later. - /// If true, the creation, last modification - /// and last access times will be set to the current system time. - public PwEntry(bool bCreateNewUuid, bool bSetTimes) - { - if(bCreateNewUuid) m_uuid = new PwUuid(true); - - if(bSetTimes) - { - DateTime dtNow = DateTime.UtcNow; - m_tCreation = dtNow; - m_tLastMod = dtNow; - m_tLastAccess = dtNow; - m_tParentGroupLastMod = dtNow; - } - } - - /// - /// Construct a new, empty password entry. Member variables will be initialized - /// to their default values. - /// - /// Reference to the containing group, this - /// parameter may be null and set later manually. - /// If true, a new UUID will be created - /// for this entry. If false, the UUID is zero and you must set it - /// manually later. - /// If true, the creation, last modification - /// and last access times will be set to the current system time. - [Obsolete("Use a different constructor. To add an entry to a group, use AddEntry of PwGroup.")] - public PwEntry(PwGroup pwParentGroup, bool bCreateNewUuid, bool bSetTimes) - { - m_pParentGroup = pwParentGroup; - - if(bCreateNewUuid) m_uuid = new PwUuid(true); - - if(bSetTimes) - { - DateTime dtNow = DateTime.UtcNow; - m_tCreation = dtNow; - m_tLastMod = dtNow; - m_tLastAccess = dtNow; - m_tParentGroupLastMod = dtNow; - } - } - -#if DEBUG - // For display in debugger - public override string ToString() - { - return (@"PwEntry '" + m_listStrings.ReadSafe(PwDefs.TitleField) + @"'"); - } -#endif - - /// - /// Clone the current entry. The returned entry is an exact value copy - /// of the current entry (including UUID and parent group reference). - /// All mutable members are cloned. - /// - /// Exact value clone. All references to mutable values changed. - public PwEntry CloneDeep() - { - PwEntry peNew = new PwEntry(false, false); - - peNew.m_uuid = m_uuid; // PwUuid is immutable - peNew.m_pParentGroup = m_pParentGroup; - peNew.m_tParentGroupLastMod = m_tParentGroupLastMod; - - peNew.m_listStrings = m_listStrings.CloneDeep(); - peNew.m_listBinaries = m_listBinaries.CloneDeep(); - peNew.m_listAutoType = m_listAutoType.CloneDeep(); - peNew.m_listHistory = m_listHistory.CloneDeep(); - - peNew.m_pwIcon = m_pwIcon; - peNew.m_pwCustomIconID = m_pwCustomIconID; - - peNew.m_clrForeground = m_clrForeground; - peNew.m_clrBackground = m_clrBackground; - - peNew.m_tCreation = m_tCreation; - peNew.m_tLastMod = m_tLastMod; - peNew.m_tLastAccess = m_tLastAccess; - peNew.m_tExpire = m_tExpire; - peNew.m_bExpires = m_bExpires; - peNew.m_uUsageCount = m_uUsageCount; - - peNew.m_strOverrideUrl = m_strOverrideUrl; - - peNew.m_vTags = new List(m_vTags); - - peNew.m_dCustomData = m_dCustomData.CloneDeep(); - - return peNew; - } - - public PwEntry CloneStructure() - { - PwEntry peNew = new PwEntry(false, false); - - peNew.m_uuid = m_uuid; // PwUuid is immutable - peNew.m_tParentGroupLastMod = m_tParentGroupLastMod; - // Do not assign m_pParentGroup - - return peNew; - } - - private static PwCompareOptions BuildCmpOpt(bool bIgnoreParentGroup, - bool bIgnoreLastMod, bool bIgnoreLastAccess, bool bIgnoreHistory, - bool bIgnoreThisLastBackup) - { - PwCompareOptions pwOpt = PwCompareOptions.None; - if(bIgnoreParentGroup) pwOpt |= PwCompareOptions.IgnoreParentGroup; - if(bIgnoreLastMod) pwOpt |= PwCompareOptions.IgnoreLastMod; - if(bIgnoreLastAccess) pwOpt |= PwCompareOptions.IgnoreLastAccess; - if(bIgnoreHistory) pwOpt |= PwCompareOptions.IgnoreHistory; - if(bIgnoreThisLastBackup) pwOpt |= PwCompareOptions.IgnoreLastBackup; - return pwOpt; - } - - [Obsolete] - public bool EqualsEntry(PwEntry pe, bool bIgnoreParentGroup, bool bIgnoreLastMod, - bool bIgnoreLastAccess, bool bIgnoreHistory, bool bIgnoreThisLastBackup) - { - return EqualsEntry(pe, BuildCmpOpt(bIgnoreParentGroup, bIgnoreLastMod, - bIgnoreLastAccess, bIgnoreHistory, bIgnoreThisLastBackup), - MemProtCmpMode.None); - } - - [Obsolete] - public bool EqualsEntry(PwEntry pe, bool bIgnoreParentGroup, bool bIgnoreLastMod, - bool bIgnoreLastAccess, bool bIgnoreHistory, bool bIgnoreThisLastBackup, - MemProtCmpMode mpCmpStr) - { - return EqualsEntry(pe, BuildCmpOpt(bIgnoreParentGroup, bIgnoreLastMod, - bIgnoreLastAccess, bIgnoreHistory, bIgnoreThisLastBackup), mpCmpStr); - } - - public bool EqualsEntry(PwEntry pe, PwCompareOptions pwOpt, - MemProtCmpMode mpCmpStr) - { - if(pe == null) { Debug.Assert(false); return false; } - - bool bNeEqStd = ((pwOpt & PwCompareOptions.NullEmptyEquivStd) != - PwCompareOptions.None); - bool bIgnoreLastAccess = ((pwOpt & PwCompareOptions.IgnoreLastAccess) != - PwCompareOptions.None); - bool bIgnoreLastMod = ((pwOpt & PwCompareOptions.IgnoreLastMod) != - PwCompareOptions.None); - - if(!m_uuid.Equals(pe.m_uuid)) return false; - if((pwOpt & PwCompareOptions.IgnoreParentGroup) == PwCompareOptions.None) - { - if(m_pParentGroup != pe.m_pParentGroup) return false; - if(!bIgnoreLastMod && (m_tParentGroupLastMod != pe.m_tParentGroupLastMod)) - return false; - } - - if(!m_listStrings.EqualsDictionary(pe.m_listStrings, pwOpt, mpCmpStr)) - return false; - if(!m_listBinaries.EqualsDictionary(pe.m_listBinaries)) return false; - - if(!m_listAutoType.Equals(pe.m_listAutoType)) return false; - - if((pwOpt & PwCompareOptions.IgnoreHistory) == PwCompareOptions.None) - { - bool bIgnoreLastBackup = ((pwOpt & PwCompareOptions.IgnoreLastBackup) != - PwCompareOptions.None); - - if(!bIgnoreLastBackup && (m_listHistory.UCount != pe.m_listHistory.UCount)) - return false; - if(bIgnoreLastBackup && (m_listHistory.UCount == 0)) - { - Debug.Assert(false); - return false; - } - if(bIgnoreLastBackup && ((m_listHistory.UCount - 1) != pe.m_listHistory.UCount)) - return false; - - PwCompareOptions cmpSub = PwCompareOptions.IgnoreParentGroup; - if(bNeEqStd) cmpSub |= PwCompareOptions.NullEmptyEquivStd; - if(bIgnoreLastMod) cmpSub |= PwCompareOptions.IgnoreLastMod; - if(bIgnoreLastAccess) cmpSub |= PwCompareOptions.IgnoreLastAccess; - - for(uint uHist = 0; uHist < pe.m_listHistory.UCount; ++uHist) - { - if(!m_listHistory.GetAt(uHist).EqualsEntry(pe.m_listHistory.GetAt( - uHist), cmpSub, MemProtCmpMode.None)) - return false; - } - } - - if(m_pwIcon != pe.m_pwIcon) return false; - if(!m_pwCustomIconID.Equals(pe.m_pwCustomIconID)) return false; - - if(m_clrForeground != pe.m_clrForeground) return false; - if(m_clrBackground != pe.m_clrBackground) return false; - - if(m_tCreation != pe.m_tCreation) return false; - if(!bIgnoreLastMod && (m_tLastMod != pe.m_tLastMod)) return false; - if(!bIgnoreLastAccess && (m_tLastAccess != pe.m_tLastAccess)) return false; - if(m_tExpire != pe.m_tExpire) return false; - if(m_bExpires != pe.m_bExpires) return false; - if(!bIgnoreLastAccess && (m_uUsageCount != pe.m_uUsageCount)) return false; - - if(m_strOverrideUrl != pe.m_strOverrideUrl) return false; - - if(m_vTags.Count != pe.m_vTags.Count) return false; - for(int iTag = 0; iTag < m_vTags.Count; ++iTag) - { - if(m_vTags[iTag] != pe.m_vTags[iTag]) return false; - } - - if(!m_dCustomData.Equals(pe.m_dCustomData)) return false; - - return true; - } - - /// - /// Assign properties to the current entry based on a template entry. - /// - /// Template entry. Must not be null. - /// Only set the properties of the template entry - /// if it is newer than the current one. - /// If true, the history will be - /// copied, too. - /// If true, the - /// LocationChanged property is copied, otherwise not. - public void AssignProperties(PwEntry peTemplate, bool bOnlyIfNewer, - bool bIncludeHistory, bool bAssignLocationChanged) - { - if(peTemplate == null) { Debug.Assert(false); throw new ArgumentNullException("peTemplate"); } - - if(bOnlyIfNewer && (TimeUtil.Compare(peTemplate.m_tLastMod, - m_tLastMod, true) < 0)) - return; - - // Template UUID should be the same as the current one - Debug.Assert(m_uuid.Equals(peTemplate.m_uuid)); - m_uuid = peTemplate.m_uuid; - - if(bAssignLocationChanged) - m_tParentGroupLastMod = peTemplate.m_tParentGroupLastMod; - - m_listStrings = peTemplate.m_listStrings.CloneDeep(); - m_listBinaries = peTemplate.m_listBinaries.CloneDeep(); - m_listAutoType = peTemplate.m_listAutoType.CloneDeep(); - if(bIncludeHistory) - m_listHistory = peTemplate.m_listHistory.CloneDeep(); - - m_pwIcon = peTemplate.m_pwIcon; - m_pwCustomIconID = peTemplate.m_pwCustomIconID; // Immutable - - m_clrForeground = peTemplate.m_clrForeground; - m_clrBackground = peTemplate.m_clrBackground; - - m_tCreation = peTemplate.m_tCreation; - m_tLastMod = peTemplate.m_tLastMod; - m_tLastAccess = peTemplate.m_tLastAccess; - m_tExpire = peTemplate.m_tExpire; - m_bExpires = peTemplate.m_bExpires; - m_uUsageCount = peTemplate.m_uUsageCount; - - m_strOverrideUrl = peTemplate.m_strOverrideUrl; - - m_vTags = new List(peTemplate.m_vTags); - - m_dCustomData = peTemplate.m_dCustomData.CloneDeep(); - } - - /// - /// Touch the entry. This function updates the internal last access - /// time. If the parameter is true, - /// the last modification time gets updated, too. - /// - /// Modify last modification time. - public void Touch(bool bModified) - { - Touch(bModified, true); - } - - /// - /// Touch the entry. This function updates the internal last access - /// time. If the parameter is true, - /// the last modification time gets updated, too. - /// - /// Modify last modification time. - /// If true, all parent objects - /// get touched, too. - public void Touch(bool bModified, bool bTouchParents) - { - m_tLastAccess = DateTime.UtcNow; - ++m_uUsageCount; - - if(bModified) m_tLastMod = m_tLastAccess; - - if(this.Touched != null) - this.Touched(this, new ObjectTouchedEventArgs(this, - bModified, bTouchParents)); - if(PwEntry.EntryTouched != null) - PwEntry.EntryTouched(this, new ObjectTouchedEventArgs(this, - bModified, bTouchParents)); - - if(bTouchParents && (m_pParentGroup != null)) - m_pParentGroup.Touch(bModified, true); - } - - /// - /// Create a backup of this entry. The backup item doesn't contain any - /// history items. - /// - [Obsolete] - public void CreateBackup() - { - CreateBackup(null); - } - - /// - /// Create a backup of this entry. The backup item doesn't contain any - /// history items. - /// If this parameter isn't null, - /// the history list is maintained automatically (i.e. old backups are - /// deleted if there are too many or the history size is too large). - /// This parameter may be null (no maintenance then). - /// - public void CreateBackup(PwDatabase pwHistMntcSettings) - { - PwEntry peCopy = CloneDeep(); - peCopy.History = new PwObjectList(); // Remove history - - m_listHistory.Add(peCopy); // Must be added at end, see EqualsEntry - - if(pwHistMntcSettings != null) MaintainBackups(pwHistMntcSettings); - } - - /// - /// Restore an entry snapshot from backups. - /// - /// Index of the backup item, to which - /// should be reverted. - [Obsolete] - public void RestoreFromBackup(uint uBackupIndex) - { - RestoreFromBackup(uBackupIndex, null); - } - - /// - /// Restore an entry snapshot from backups. - /// - /// Index of the backup item, to which - /// should be reverted. - /// If this parameter isn't null, - /// the history list is maintained automatically (i.e. old backups are - /// deleted if there are too many or the history size is too large). - /// This parameter may be null (no maintenance then). - public void RestoreFromBackup(uint uBackupIndex, PwDatabase pwHistMntcSettings) - { - Debug.Assert(uBackupIndex < m_listHistory.UCount); - if(uBackupIndex >= m_listHistory.UCount) - throw new ArgumentOutOfRangeException("uBackupIndex"); - - PwEntry pe = m_listHistory.GetAt(uBackupIndex); - Debug.Assert(pe != null); if(pe == null) throw new InvalidOperationException(); - - CreateBackup(pwHistMntcSettings); // Backup current data before restoring - AssignProperties(pe, false, false, false); - } - - public bool HasBackupOfData(PwEntry peData, bool bIgnoreLastMod, - bool bIgnoreLastAccess) - { - if(peData == null) { Debug.Assert(false); return false; } - - PwCompareOptions cmpOpt = (PwCompareOptions.IgnoreParentGroup | - PwCompareOptions.IgnoreHistory | PwCompareOptions.NullEmptyEquivStd); - if(bIgnoreLastMod) cmpOpt |= PwCompareOptions.IgnoreLastMod; - if(bIgnoreLastAccess) cmpOpt |= PwCompareOptions.IgnoreLastAccess; - - foreach(PwEntry pe in m_listHistory) - { - if(pe.EqualsEntry(peData, cmpOpt, MemProtCmpMode.None)) return true; - } - - return false; - } - - /// - /// Delete old history items if there are too many or the history - /// size is too large. - /// If one or more history items have been deleted, true - /// is returned. Otherwise false. - /// - public bool MaintainBackups(PwDatabase pwSettings) - { - if(pwSettings == null) { Debug.Assert(false); return false; } - - bool bDeleted = false; - - int nMaxItems = pwSettings.HistoryMaxItems; - if(nMaxItems >= 0) - { - while(m_listHistory.UCount > (uint)nMaxItems) - { - RemoveOldestBackup(); - bDeleted = true; - } - } - - long lMaxSize = pwSettings.HistoryMaxSize; - if(lMaxSize >= 0) - { - while(true) - { - ulong uHistSize = 0; - foreach(PwEntry pe in m_listHistory) { uHistSize += pe.GetSize(); } - - if(uHistSize > (ulong)lMaxSize) - { - RemoveOldestBackup(); - bDeleted = true; - } - else break; - } - } - - return bDeleted; - } - - private void RemoveOldestBackup() - { - DateTime dtMin = TimeUtil.SafeMaxValueUtc; - uint idxRemove = uint.MaxValue; - - for(uint u = 0; u < m_listHistory.UCount; ++u) - { - PwEntry pe = m_listHistory.GetAt(u); - if(TimeUtil.Compare(pe.LastModificationTime, dtMin, true) < 0) - { - idxRemove = u; - dtMin = pe.LastModificationTime; - } - } - - if(idxRemove != uint.MaxValue) m_listHistory.RemoveAt(idxRemove); - } - - public bool GetAutoTypeEnabled() - { - if(!m_listAutoType.Enabled) return false; - - if(m_pParentGroup != null) - return m_pParentGroup.GetAutoTypeEnabledInherited(); - - return PwGroup.DefaultAutoTypeEnabled; - } - - public string GetAutoTypeSequence() - { - string strSeq = m_listAutoType.DefaultSequence; - - PwGroup pg = m_pParentGroup; - while(pg != null) - { - if(strSeq.Length != 0) break; - - strSeq = pg.DefaultAutoTypeSequence; - pg = pg.ParentGroup; - } - - if(strSeq.Length != 0) return strSeq; - - if(PwDefs.IsTanEntry(this)) return PwDefs.DefaultAutoTypeSequenceTan; - return PwDefs.DefaultAutoTypeSequence; - } - - public bool GetSearchingEnabled() - { - if(m_pParentGroup != null) - return m_pParentGroup.GetSearchingEnabledInherited(); - - return PwGroup.DefaultSearchingEnabled; - } - - /// - /// Approximate the total size (in process memory) of this entry - /// in bytes (including strings, binaries and history entries). - /// - /// Size in bytes. - public ulong GetSize() - { - // This method assumes 64-bit pointers/references and Unicode - // strings (i.e. 2 bytes per character) - - ulong cb = 248; // Number of bytes; approx. fixed length data - ulong cc = 0; // Number of characters - - cb += (ulong)m_listStrings.UCount * 40; - foreach(KeyValuePair kvpStr in m_listStrings) - cc += (ulong)kvpStr.Key.Length + (ulong)kvpStr.Value.Length; - - cb += (ulong)m_listBinaries.UCount * 65; - foreach(KeyValuePair kvpBin in m_listBinaries) - { - cc += (ulong)kvpBin.Key.Length; - cb += (ulong)kvpBin.Value.Length; - } - - cc += (ulong)m_listAutoType.DefaultSequence.Length; - cb += (ulong)m_listAutoType.AssociationsCount * 24; - foreach(AutoTypeAssociation a in m_listAutoType.Associations) - cc += (ulong)a.WindowName.Length + (ulong)a.Sequence.Length; - - cb += (ulong)m_listHistory.UCount * 8; - foreach(PwEntry peHistory in m_listHistory) - cb += peHistory.GetSize(); - - cc += (ulong)m_strOverrideUrl.Length; - - cb += (ulong)m_vTags.Count * 8; - foreach(string strTag in m_vTags) - cc += (ulong)strTag.Length; - - cb += (ulong)m_dCustomData.Count * 16; - foreach(KeyValuePair kvp in m_dCustomData) - cc += (ulong)kvp.Key.Length + (ulong)kvp.Value.Length; - - return (cb + (cc << 1)); - } - - public bool HasTag(string strTag) - { - if(string.IsNullOrEmpty(strTag)) { Debug.Assert(false); return false; } - - for(int i = 0; i < m_vTags.Count; ++i) - { - if(m_vTags[i].Equals(strTag, StrUtil.CaseIgnoreCmp)) return true; - } - - return false; - } - - public bool AddTag(string strTag) - { - if(string.IsNullOrEmpty(strTag)) { Debug.Assert(false); return false; } - - for(int i = 0; i < m_vTags.Count; ++i) - { - if(m_vTags[i].Equals(strTag, StrUtil.CaseIgnoreCmp)) return false; - } - - m_vTags.Add(strTag); - return true; - } - - public bool RemoveTag(string strTag) - { - if(string.IsNullOrEmpty(strTag)) { Debug.Assert(false); return false; } - - for(int i = 0; i < m_vTags.Count; ++i) - { - if(m_vTags[i].Equals(strTag, StrUtil.CaseIgnoreCmp)) - { - m_vTags.RemoveAt(i); - return true; - } - } - - return false; - } - - public bool IsContainedIn(PwGroup pgContainer) - { - PwGroup pgCur = m_pParentGroup; - while(pgCur != null) - { - if(pgCur == pgContainer) return true; - - pgCur = pgCur.ParentGroup; - } - - return false; - } - - public void SetUuid(PwUuid pwNewUuid, bool bAlsoChangeHistoryUuids) - { - this.Uuid = pwNewUuid; - - if(bAlsoChangeHistoryUuids) - { - foreach(PwEntry peHist in m_listHistory) - { - peHist.Uuid = pwNewUuid; - } - } - } - - public void SetCreatedNow() - { - DateTime dt = DateTime.UtcNow; - - m_tCreation = dt; - m_tLastAccess = dt; - } - - public PwEntry Duplicate() - { - PwEntry pe = CloneDeep(); - - pe.SetUuid(new PwUuid(true), true); - pe.SetCreatedNow(); - - return pe; - } - } - - public sealed class PwEntryComparer : IComparer - { - private string m_strFieldName; - private bool m_bCaseInsensitive; - private bool m_bCompareNaturally; - - public PwEntryComparer(string strFieldName, bool bCaseInsensitive, - bool bCompareNaturally) - { - if(strFieldName == null) throw new ArgumentNullException("strFieldName"); - - m_strFieldName = strFieldName; - m_bCaseInsensitive = bCaseInsensitive; - m_bCompareNaturally = bCompareNaturally; - } - - public int Compare(PwEntry a, PwEntry b) - { - string strA = a.Strings.ReadSafe(m_strFieldName); - string strB = b.Strings.ReadSafe(m_strFieldName); - - if(m_bCompareNaturally) return StrUtil.CompareNaturally(strA, strB); - -#if ModernKeePassLib || KeePassRT - return string.Compare(strA, strB, m_bCaseInsensitive ? - StringComparison.CurrentCultureIgnoreCase : StringComparison.CurrentCulture); -#else - return string.Compare(strA, strB, m_bCaseInsensitive); -#endif - } - } -} diff --git a/ModernKeePassLib/PwEnums.cs b/ModernKeePassLib/PwEnums.cs deleted file mode 100644 index 9befce9..0000000 --- a/ModernKeePassLib/PwEnums.cs +++ /dev/null @@ -1,319 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; - -namespace ModernKeePassLib -{ - /// - /// Compression algorithm specifiers. - /// - public enum PwCompressionAlgorithm - { - /// - /// No compression. - /// - None = 0, - - /// - /// GZip compression. - /// - GZip = 1, - - /// - /// Virtual field: currently known number of algorithms. Should not be used - /// by plugins or libraries -- it's used internally only. - /// - Count = 2 - } - - /// - /// Tree traversal methods. - /// - public enum TraversalMethod - { - /// - /// Don't traverse the tree. - /// - None = 0, - - /// - /// Traverse the tree in pre-order mode, i.e. first visit all items - /// in the current node, then visit all subnodes. - /// - PreOrder = 1 - } - - /// - /// Methods for merging password databases/entries. - /// - public enum PwMergeMethod - { - // Do not change the explicitly assigned values, otherwise - // serialization (e.g. of Ecas triggers) breaks - None = 0, - OverwriteExisting = 1, - KeepExisting = 2, - OverwriteIfNewer = 3, - CreateNewUuids = 4, - Synchronize = 5 - } - - /// - /// Icon identifiers for groups and password entries. - /// - public enum PwIcon - { - Key = 0, - World, - Warning, - NetworkServer, - MarkedDirectory, - UserCommunication, - Parts, - Notepad, - WorldSocket, - Identity, - PaperReady, - Digicam, - IRCommunication, - MultiKeys, - Energy, - Scanner, - WorldStar, - CDRom, - Monitor, - EMail, - Configuration, - ClipboardReady, - PaperNew, - Screen, - EnergyCareful, - EMailBox, - Disk, - Drive, - PaperQ, - TerminalEncrypted, - Console, - Printer, - ProgramIcons, - Run, - Settings, - WorldComputer, - Archive, - Homebanking, - DriveWindows, - Clock, - EMailSearch, - PaperFlag, - Memory, - TrashBin, - Note, - Expired, - Info, - Package, - Folder, - FolderOpen, - FolderPackage, - LockOpen, - PaperLocked, - Checked, - Pen, - Thumbnail, - Book, - List, - UserKey, - Tool, - Home, - Star, - Tux, - Feather, - Apple, - Wiki, - Money, - Certificate, - BlackBerry, - - /// - /// Virtual identifier -- represents the number of icons. - /// - Count - } - - public enum ProxyServerType - { - None = 0, - System = 1, - Manual = 2 - } - - public enum ProxyAuthType - { - None = 0, - - /// - /// Use default user credentials (provided by the system). - /// - Default = 1, - - Manual = 2, - - /// - /// Default or Manual, depending on whether - /// manual credentials are available. - /// This type exists for supporting upgrading from KeePass - /// 2.28 to 2.29; the user cannot select this type. - /// - Auto = 3 - } - - /// - /// Comparison modes for in-memory protected objects. - /// - public enum MemProtCmpMode - { - /// - /// Ignore the in-memory protection states. - /// - None = 0, - - /// - /// Ignore the in-memory protection states of standard - /// objects; do compare in-memory protection states of - /// custom objects. - /// - CustomOnly, - - /// - /// Compare in-memory protection states. - /// - Full - } - - [Flags] - public enum PwCompareOptions - { - None = 0x0, - - /// - /// Empty standard string fields are considered to be the - /// same as non-existing standard string fields. - /// This doesn't affect custom string comparisons. - /// - NullEmptyEquivStd = 0x1, - - IgnoreParentGroup = 0x2, - IgnoreLastAccess = 0x4, - IgnoreLastMod = 0x8, - IgnoreHistory = 0x10, - IgnoreLastBackup = 0x20, - - // For groups: - PropertiesOnly = 0x40, - - IgnoreTimes = (IgnoreLastAccess | IgnoreLastMod) - } - - public enum IOAccessType - { - None = 0, - - /// - /// The IO connection is being opened for reading. - /// - Read = 1, - - /// - /// The IO connection is being opened for writing. - /// - Write = 2, - - /// - /// The IO connection is being opened for testing - /// whether a file/object exists. - /// - Exists = 3, - - /// - /// The IO connection is being opened for deleting a file/object. - /// - Delete = 4, - - /// - /// The IO connection is being opened for renaming/moving a file/object. - /// - Move = 5 - } - - // public enum PwLogicalOp - // { - // None = 0, - // Or = 1, - // And = 2, - // NOr = 3, - // NAnd = 4 - // } - - [Flags] - public enum AppRunFlags - { - None = 0, - GetStdOutput = 1, - WaitForExit = 2, - - // https://sourceforge.net/p/keepass/patches/84/ - /// - /// This flag prevents any handles being garbage-collected - /// before the started process has terminated, without - /// blocking the current thread. - /// - GCKeepAlive = 4, - - // https://sourceforge.net/p/keepass/patches/85/ - DoEvents = 8, - DisableForms = 16 - } - - [Flags] - public enum ScaleTransformFlags - { - None = 0, - - /// - /// UIIcon indicates that the returned image is going - /// to be displayed as icon in the UI and that it is not - /// subject to future changes in size. - /// - UIIcon = 1 - } - - public enum DesktopType - { - None = 0, - Windows, - Gnome, - Kde, - Unity, - Lxde, - Xfce, - Mate, - Cinnamon, - Pantheon - } -} diff --git a/ModernKeePassLib/PwGroup.cs b/ModernKeePassLib/PwGroup.cs deleted file mode 100644 index 0649a6e..0000000 --- a/ModernKeePassLib/PwGroup.cs +++ /dev/null @@ -1,1664 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Text.RegularExpressions; - -using ModernKeePassLib.Collections; -using ModernKeePassLib.Delegates; -using ModernKeePassLib.Interfaces; -using ModernKeePassLib.Security; -using ModernKeePassLib.Utility; - -namespace ModernKeePassLib -{ - /// - /// A group containing several password entries. - /// - public sealed class PwGroup : ITimeLogger, IStructureItem, IDeepCloneable - { - public const bool DefaultAutoTypeEnabled = true; - public const bool DefaultSearchingEnabled = true; - - private PwObjectList m_listGroups = new PwObjectList(); - private PwObjectList m_listEntries = new PwObjectList(); - private PwGroup m_pParentGroup = null; - private DateTime m_tParentGroupLastMod = PwDefs.DtDefaultNow; - - private PwUuid m_uuid = PwUuid.Zero; - private string m_strName = string.Empty; - private string m_strNotes = string.Empty; - - private PwIcon m_pwIcon = PwIcon.Folder; - private PwUuid m_pwCustomIconID = PwUuid.Zero; - - private DateTime m_tCreation = PwDefs.DtDefaultNow; - private DateTime m_tLastMod = PwDefs.DtDefaultNow; - private DateTime m_tLastAccess = PwDefs.DtDefaultNow; - private DateTime m_tExpire = PwDefs.DtDefaultNow; - private bool m_bExpires = false; - private ulong m_uUsageCount = 0; - - private bool m_bIsExpanded = true; - private bool m_bVirtual = false; - - private string m_strDefaultAutoTypeSequence = string.Empty; - - private bool? m_bEnableAutoType = null; - private bool? m_bEnableSearching = null; - - private PwUuid m_pwLastTopVisibleEntry = PwUuid.Zero; - - private StringDictionaryEx m_dCustomData = new StringDictionaryEx(); - - /// - /// UUID of this group. - /// - public PwUuid Uuid - { - get { return m_uuid; } - set - { - Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); - m_uuid = value; - } - } - - /// - /// The name of this group. Cannot be null. - /// - public string Name - { - get { return m_strName; } - set - { - Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); - m_strName = value; - } - } - - /// - /// Comments about this group. Cannot be null. - /// - public string Notes - { - get { return m_strNotes; } - set - { - Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); - m_strNotes = value; - } - } - - /// - /// Icon of the group. - /// - public PwIcon IconId - { - get { return m_pwIcon; } - set { m_pwIcon = value; } - } - - /// - /// Get the custom icon ID. This value is 0, if no custom icon is - /// being used (i.e. the icon specified by the IconID property - /// should be displayed). - /// - public PwUuid CustomIconUuid - { - get { return m_pwCustomIconID; } - set - { - Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); - m_pwCustomIconID = value; - } - } - - /// - /// Reference to the group to which this group belongs. May be null. - /// - public PwGroup ParentGroup - { - get { return m_pParentGroup; } - - // Plugins: use PwGroup.AddGroup instead. - internal set { Debug.Assert(value != this); m_pParentGroup = value; } - } - - /// - /// The date/time when the location of the object was last changed. - /// - public DateTime LocationChanged - { - get { return m_tParentGroupLastMod; } - set { m_tParentGroupLastMod = value; } - } - - /// - /// A flag that specifies if the group is shown as expanded or - /// collapsed in the user interface. - /// - public bool IsExpanded - { - get { return m_bIsExpanded; } - set { m_bIsExpanded = value; } - } - - /// - /// The date/time when this group was created. - /// - public DateTime CreationTime - { - get { return m_tCreation; } - set { m_tCreation = value; } - } - - /// - /// The date/time when this group was last modified. - /// - public DateTime LastModificationTime - { - get { return m_tLastMod; } - set { m_tLastMod = value; } - } - - /// - /// The date/time when this group was last accessed (read). - /// - public DateTime LastAccessTime - { - get { return m_tLastAccess; } - set { m_tLastAccess = value; } - } - - /// - /// The date/time when this group expires. - /// - public DateTime ExpiryTime - { - get { return m_tExpire; } - set { m_tExpire = value; } - } - - /// - /// Flag that determines if the group expires. - /// - public bool Expires - { - get { return m_bExpires; } - set { m_bExpires = value; } - } - - /// - /// Get or set the usage count of the group. To increase the usage - /// count by one, use the Touch function. - /// - public ulong UsageCount - { - get { return m_uUsageCount; } - set { m_uUsageCount = value; } - } - - /// - /// Get a list of subgroups in this group. - /// - public PwObjectList Groups - { - get { return m_listGroups; } - } - - /// - /// Get a list of entries in this group. - /// - public PwObjectList Entries - { - get { return m_listEntries; } - } - - /// - /// A flag specifying whether this group is virtual or not. Virtual - /// groups can contain links to entries stored in other groups. - /// Note that this flag has to be interpreted and set by the calling - /// code; it won't prevent you from accessing and modifying the list - /// of entries in this group in any way. - /// - public bool IsVirtual - { - get { return m_bVirtual; } - set { m_bVirtual = value; } - } - - /// - /// Default auto-type keystroke sequence for all entries in - /// this group. This property can be an empty string, which - /// means that the value should be inherited from the parent. - /// - public string DefaultAutoTypeSequence - { - get { return m_strDefaultAutoTypeSequence; } - set - { - Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); - m_strDefaultAutoTypeSequence = value; - } - } - - public bool? EnableAutoType - { - get { return m_bEnableAutoType; } - set { m_bEnableAutoType = value; } - } - - public bool? EnableSearching - { - get { return m_bEnableSearching; } - set { m_bEnableSearching = value; } - } - - public PwUuid LastTopVisibleEntry - { - get { return m_pwLastTopVisibleEntry; } - set - { - Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); - m_pwLastTopVisibleEntry = value; - } - } - - /// - /// Custom data container that can be used by plugins to store - /// own data in KeePass groups. - /// The data is stored in the encrypted part of encrypted - /// database files. - /// Use unique names for your items, e.g. "PluginName_ItemName". - /// - public StringDictionaryEx CustomData - { - get { return m_dCustomData; } - internal set - { - if(value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); } - m_dCustomData = value; - } - } - - public static EventHandler GroupTouched; - public EventHandler Touched; - - /// - /// Construct a new, empty group. - /// - public PwGroup() - { - } - - /// - /// Construct a new, empty group. - /// - /// Create a new UUID for this group. - /// Set creation, last access and last modification times to the current time. - public PwGroup(bool bCreateNewUuid, bool bSetTimes) - { - if(bCreateNewUuid) m_uuid = new PwUuid(true); - - if(bSetTimes) - { - DateTime dtNow = DateTime.UtcNow; - m_tCreation = dtNow; - m_tLastMod = dtNow; - m_tLastAccess = dtNow; - m_tParentGroupLastMod = dtNow; - } - } - - /// - /// Construct a new group. - /// - /// Create a new UUID for this group. - /// Set creation, last access and last modification times to the current time. - /// Name of the new group. - /// Icon of the new group. - public PwGroup(bool bCreateNewUuid, bool bSetTimes, string strName, PwIcon pwIcon) - { - if(bCreateNewUuid) m_uuid = new PwUuid(true); - - if(bSetTimes) - { - DateTime dtNow = DateTime.UtcNow; - m_tCreation = dtNow; - m_tLastMod = dtNow; - m_tLastAccess = dtNow; - m_tParentGroupLastMod = dtNow; - } - - if(strName != null) m_strName = strName; - - m_pwIcon = pwIcon; - } - -#if DEBUG - // For display in debugger - public override string ToString() - { - return (@"PwGroup '" + m_strName + @"'"); - } -#endif - - /// - /// Deeply clone the current group. The returned group will be an exact - /// value copy of the current object (including UUID, etc.). - /// - /// Exact value copy of the current PwGroup object. - public PwGroup CloneDeep() - { - PwGroup pg = new PwGroup(false, false); - - pg.m_uuid = m_uuid; // PwUuid is immutable - - pg.m_listGroups = m_listGroups.CloneDeep(); - pg.m_listEntries = m_listEntries.CloneDeep(); - pg.m_pParentGroup = m_pParentGroup; - pg.m_tParentGroupLastMod = m_tParentGroupLastMod; - - pg.m_strName = m_strName; - pg.m_strNotes = m_strNotes; - - pg.m_pwIcon = m_pwIcon; - pg.m_pwCustomIconID = m_pwCustomIconID; - - pg.m_tCreation = m_tCreation; - pg.m_tLastMod = m_tLastMod; - pg.m_tLastAccess = m_tLastAccess; - pg.m_tExpire = m_tExpire; - pg.m_bExpires = m_bExpires; - pg.m_uUsageCount = m_uUsageCount; - - pg.m_bIsExpanded = m_bIsExpanded; - pg.m_bVirtual = m_bVirtual; - - pg.m_strDefaultAutoTypeSequence = m_strDefaultAutoTypeSequence; - - pg.m_bEnableAutoType = m_bEnableAutoType; - pg.m_bEnableSearching = m_bEnableSearching; - - pg.m_pwLastTopVisibleEntry = m_pwLastTopVisibleEntry; - - pg.m_dCustomData = m_dCustomData.CloneDeep(); - - return pg; - } - - public PwGroup CloneStructure() - { - PwGroup pg = new PwGroup(false, false); - - pg.m_uuid = m_uuid; // PwUuid is immutable - pg.m_tParentGroupLastMod = m_tParentGroupLastMod; - // Do not assign m_pParentGroup - - foreach(PwGroup pgSub in m_listGroups) - pg.AddGroup(pgSub.CloneStructure(), true); - - foreach(PwEntry peSub in m_listEntries) - pg.AddEntry(peSub.CloneStructure(), true); - - return pg; - } - - public bool EqualsGroup(PwGroup pg, PwCompareOptions pwOpt, - MemProtCmpMode mpCmpStr) - { - if(pg == null) { Debug.Assert(false); return false; } - - bool bIgnoreLastAccess = ((pwOpt & PwCompareOptions.IgnoreLastAccess) != - PwCompareOptions.None); - bool bIgnoreLastMod = ((pwOpt & PwCompareOptions.IgnoreLastMod) != - PwCompareOptions.None); - - if(!m_uuid.Equals(pg.m_uuid)) return false; - if((pwOpt & PwCompareOptions.IgnoreParentGroup) == PwCompareOptions.None) - { - if(m_pParentGroup != pg.m_pParentGroup) return false; - if(!bIgnoreLastMod && (m_tParentGroupLastMod != pg.m_tParentGroupLastMod)) - return false; - } - - if(m_strName != pg.m_strName) return false; - if(m_strNotes != pg.m_strNotes) return false; - - if(m_pwIcon != pg.m_pwIcon) return false; - if(!m_pwCustomIconID.Equals(pg.m_pwCustomIconID)) return false; - - if(m_tCreation != pg.m_tCreation) return false; - if(!bIgnoreLastMod && (m_tLastMod != pg.m_tLastMod)) return false; - if(!bIgnoreLastAccess && (m_tLastAccess != pg.m_tLastAccess)) return false; - if(m_tExpire != pg.m_tExpire) return false; - if(m_bExpires != pg.m_bExpires) return false; - if(!bIgnoreLastAccess && (m_uUsageCount != pg.m_uUsageCount)) return false; - - // if(m_bIsExpanded != pg.m_bIsExpanded) return false; - - if(m_strDefaultAutoTypeSequence != pg.m_strDefaultAutoTypeSequence) return false; - - if(m_bEnableAutoType.HasValue != pg.m_bEnableAutoType.HasValue) return false; - if(m_bEnableAutoType.HasValue) - { - if(m_bEnableAutoType.Value != pg.m_bEnableAutoType.Value) return false; - } - if(m_bEnableSearching.HasValue != pg.m_bEnableSearching.HasValue) return false; - if(m_bEnableSearching.HasValue) - { - if(m_bEnableSearching.Value != pg.m_bEnableSearching.Value) return false; - } - - if(!m_pwLastTopVisibleEntry.Equals(pg.m_pwLastTopVisibleEntry)) return false; - - if(!m_dCustomData.Equals(pg.m_dCustomData)) return false; - - if((pwOpt & PwCompareOptions.PropertiesOnly) == PwCompareOptions.None) - { - if(m_listEntries.UCount != pg.m_listEntries.UCount) return false; - for(uint u = 0; u < m_listEntries.UCount; ++u) - { - PwEntry peA = m_listEntries.GetAt(u); - PwEntry peB = pg.m_listEntries.GetAt(u); - if(!peA.EqualsEntry(peB, pwOpt, mpCmpStr)) return false; - } - - if(m_listGroups.UCount != pg.m_listGroups.UCount) return false; - for(uint u = 0; u < m_listGroups.UCount; ++u) - { - PwGroup pgA = m_listGroups.GetAt(u); - PwGroup pgB = pg.m_listGroups.GetAt(u); - if(!pgA.EqualsGroup(pgB, pwOpt, mpCmpStr)) return false; - } - } - - return true; - } - - /// - /// Assign properties to the current group based on a template group. - /// - /// Template group. Must not be null. - /// Only set the properties of the template group - /// if it is newer than the current one. - /// If true, the - /// LocationChanged property is copied, otherwise not. - public void AssignProperties(PwGroup pgTemplate, bool bOnlyIfNewer, - bool bAssignLocationChanged) - { - Debug.Assert(pgTemplate != null); if(pgTemplate == null) throw new ArgumentNullException("pgTemplate"); - - if(bOnlyIfNewer && (TimeUtil.Compare(pgTemplate.m_tLastMod, m_tLastMod, - true) < 0)) - return; - - // Template UUID should be the same as the current one - Debug.Assert(m_uuid.Equals(pgTemplate.m_uuid)); - m_uuid = pgTemplate.m_uuid; - - if(bAssignLocationChanged) - m_tParentGroupLastMod = pgTemplate.m_tParentGroupLastMod; - - m_strName = pgTemplate.m_strName; - m_strNotes = pgTemplate.m_strNotes; - - m_pwIcon = pgTemplate.m_pwIcon; - m_pwCustomIconID = pgTemplate.m_pwCustomIconID; - - m_tCreation = pgTemplate.m_tCreation; - m_tLastMod = pgTemplate.m_tLastMod; - m_tLastAccess = pgTemplate.m_tLastAccess; - m_tExpire = pgTemplate.m_tExpire; - m_bExpires = pgTemplate.m_bExpires; - m_uUsageCount = pgTemplate.m_uUsageCount; - - m_strDefaultAutoTypeSequence = pgTemplate.m_strDefaultAutoTypeSequence; - - m_bEnableAutoType = pgTemplate.m_bEnableAutoType; - m_bEnableSearching = pgTemplate.m_bEnableSearching; - - m_pwLastTopVisibleEntry = pgTemplate.m_pwLastTopVisibleEntry; - - m_dCustomData = pgTemplate.m_dCustomData.CloneDeep(); - } - - /// - /// Touch the group. This function updates the internal last access - /// time. If the parameter is true, - /// the last modification time gets updated, too. - /// - /// Modify last modification time. - public void Touch(bool bModified) - { - Touch(bModified, true); - } - - /// - /// Touch the group. This function updates the internal last access - /// time. If the parameter is true, - /// the last modification time gets updated, too. - /// - /// Modify last modification time. - /// If true, all parent objects - /// get touched, too. - public void Touch(bool bModified, bool bTouchParents) - { - m_tLastAccess = DateTime.UtcNow; - ++m_uUsageCount; - - if(bModified) m_tLastMod = m_tLastAccess; - - if(this.Touched != null) - this.Touched(this, new ObjectTouchedEventArgs(this, - bModified, bTouchParents)); - if(PwGroup.GroupTouched != null) - PwGroup.GroupTouched(this, new ObjectTouchedEventArgs(this, - bModified, bTouchParents)); - - if(bTouchParents && (m_pParentGroup != null)) - m_pParentGroup.Touch(bModified, true); - } - - /// - /// Get number of groups and entries in the current group. This function - /// can also traverse through all subgroups and accumulate their counts - /// (recursive mode). - /// - /// If this parameter is true, all - /// subgroups and entries in subgroups will be counted and added to - /// the returned value. If it is false, only the number of - /// subgroups and entries of the current group is returned. - /// Number of subgroups. - /// Number of entries. - public void GetCounts(bool bRecursive, out uint uNumGroups, out uint uNumEntries) - { - if(bRecursive) - { - uint uTotalGroups = m_listGroups.UCount; - uint uTotalEntries = m_listEntries.UCount; - uint uSubGroupCount, uSubEntryCount; - - foreach(PwGroup pg in m_listGroups) - { - pg.GetCounts(true, out uSubGroupCount, out uSubEntryCount); - - uTotalGroups += uSubGroupCount; - uTotalEntries += uSubEntryCount; - } - - uNumGroups = uTotalGroups; - uNumEntries = uTotalEntries; - } - else // !bRecursive - { - uNumGroups = m_listGroups.UCount; - uNumEntries = m_listEntries.UCount; - } - } - - public uint GetEntriesCount(bool bRecursive) - { - uint uGroups, uEntries; - GetCounts(bRecursive, out uGroups, out uEntries); - return uEntries; - } - - /// - /// Traverse the group/entry tree in the current group. Various traversal - /// methods are available. - /// - /// Specifies the traversal method. - /// Function that performs an action on - /// the currently visited group (see GroupHandler for more). - /// This parameter may be null, in this case the tree is traversed but - /// you don't get notifications for each visited group. - /// Function that performs an action on - /// the currently visited entry (see EntryHandler for more). - /// This parameter may be null. - /// Returns true if all entries and groups have been - /// traversed. If the traversal has been canceled by one of the two - /// handlers, the return value is false. - public bool TraverseTree(TraversalMethod tm, GroupHandler groupHandler, EntryHandler entryHandler) - { - bool bRet = false; - - switch(tm) - { - case TraversalMethod.None: - bRet = true; - break; - case TraversalMethod.PreOrder: - bRet = PreOrderTraverseTree(groupHandler, entryHandler); - break; - default: - Debug.Assert(false); - break; - } - - return bRet; - } - - private bool PreOrderTraverseTree(GroupHandler groupHandler, EntryHandler entryHandler) - { - if(entryHandler != null) - { - foreach(PwEntry pe in m_listEntries) - { - if(!entryHandler(pe)) return false; - } - } - - foreach(PwGroup pg in m_listGroups) - { - if(groupHandler != null) - { - if(!groupHandler(pg)) return false; - } - - if(!pg.PreOrderTraverseTree(groupHandler, entryHandler)) - return false; - } - - return true; - } - - /// - /// Pack all groups into one flat linked list of references (recursively). - /// - /// Flat list of all groups. - public LinkedList GetFlatGroupList() - { - LinkedList list = new LinkedList(); - - foreach(PwGroup pg in m_listGroups) - { - list.AddLast(pg); - - if(pg.Groups.UCount != 0) - LinearizeGroupRecursive(list, pg, 1); - } - - return list; - } - - private void LinearizeGroupRecursive(LinkedList list, PwGroup pg, ushort uLevel) - { - Debug.Assert(pg != null); if(pg == null) return; - - foreach(PwGroup pwg in pg.Groups) - { - list.AddLast(pwg); - - if(pwg.Groups.UCount != 0) - LinearizeGroupRecursive(list, pwg, (ushort)(uLevel + 1)); - } - } - - /// - /// Pack all entries into one flat linked list of references. Temporary - /// group IDs are assigned automatically. - /// - /// A flat group list created by - /// GetFlatGroupList. - /// Flat list of all entries. - public static LinkedList GetFlatEntryList(LinkedList flatGroupList) - { - Debug.Assert(flatGroupList != null); if(flatGroupList == null) return null; - - LinkedList list = new LinkedList(); - foreach(PwGroup pg in flatGroupList) - { - foreach(PwEntry pe in pg.Entries) - list.AddLast(pe); - } - - return list; - } - - /// - /// Enable protection of a specific string field type. - /// - /// Name of the string field to protect or unprotect. - /// Enable protection or not. - /// Returns true, if the operation completed successfully, - /// otherwise false. - public bool EnableStringFieldProtection(string strFieldName, bool bEnable) - { - Debug.Assert(strFieldName != null); - - EntryHandler eh = delegate(PwEntry pe) - { - // Enable protection of current string - pe.Strings.EnableProtection(strFieldName, bEnable); - - // Do the same for all history items - foreach(PwEntry peHistory in pe.History) - { - peHistory.Strings.EnableProtection(strFieldName, bEnable); - } - - return true; - }; - - return PreOrderTraverseTree(null, eh); - } - - /// - /// Search this group and all subgroups for entries. - /// - /// Specifies the search parameters. - /// Entry list in which the search results - /// will be stored. - public void SearchEntries(SearchParameters sp, PwObjectList lResults) - { - SearchEntries(sp, lResults, null); - } - - /// - /// Search this group and all subgroups for entries. - /// - /// Specifies the search parameters. - /// Entry list in which the search results - /// will be stored. - /// Optional status reporting object. - public void SearchEntries(SearchParameters sp, PwObjectList lResults, - IStatusLogger slStatus) - { - if(sp == null) { Debug.Assert(false); return; } - if(lResults == null) { Debug.Assert(false); return; } - - PwObjectList lCand = GetEntries(true); - DateTime dtNow = DateTime.UtcNow; - - PwObjectList l = new PwObjectList(); - foreach(PwEntry pe in lCand) - { - if(sp.RespectEntrySearchingDisabled && !pe.GetSearchingEnabled()) - continue; - if(sp.ExcludeExpired && pe.Expires && (pe.ExpiryTime <= dtNow)) - continue; - - l.Add(pe); - } - lCand = l; - - List lTerms; - if(sp.RegularExpression) - { - lTerms = new List(); - lTerms.Add((sp.SearchString ?? string.Empty).Trim()); - } - else lTerms = StrUtil.SplitSearchTerms(sp.SearchString); - - // Search longer strings first (for improved performance) - lTerms.Sort(StrUtil.CompareLengthGt); - - ulong uPrcEntries = 0, uTotalEntries = lCand.UCount; - SearchParameters spSub = sp.Clone(); - - for(int iTerm = 0; iTerm < lTerms.Count; ++iTerm) - { - // Update counters for a better state guess - if(slStatus != null) - { - ulong uRemRounds = (ulong)(lTerms.Count - iTerm); - uTotalEntries = uPrcEntries + (uRemRounds * - lCand.UCount); - } - - spSub.SearchString = lTerms[iTerm]; // No trim - // spSub.RespectEntrySearchingDisabled = false; // Ignored by sub - // spSub.ExcludeExpired = false; // Ignored by sub - - bool bNegate = false; - if(spSub.SearchString.StartsWith(@"-") && - (spSub.SearchString.Length >= 2)) - { - spSub.SearchString = spSub.SearchString.Substring(1); - bNegate = true; - } - - l = new PwObjectList(); - if(!SearchEntriesSingle(lCand, spSub, l, slStatus, - ref uPrcEntries, uTotalEntries)) - { - lCand.Clear(); - break; - } - - if(bNegate) - { - PwObjectList lRem = new PwObjectList(); - foreach(PwEntry pe in lCand) - { - if(l.IndexOf(pe) < 0) lRem.Add(pe); - } - - lCand = lRem; - } - else lCand = l; - } - - Debug.Assert(lResults.UCount == 0); - lResults.Clear(); - lResults.Add(lCand); - } - - private static bool SearchEntriesSingle(PwObjectList lSource, - SearchParameters sp, PwObjectList lResults, - IStatusLogger slStatus, ref ulong uPrcEntries, ulong uTotalEntries) - { - if(lSource == null) { Debug.Assert(false); return true; } - if(sp == null) { Debug.Assert(false); return true; } - if(lResults == null) { Debug.Assert(false); return true; } - Debug.Assert(lResults.UCount == 0); - - bool bTitle = sp.SearchInTitles; - bool bUserName = sp.SearchInUserNames; - bool bPassword = sp.SearchInPasswords; - bool bUrl = sp.SearchInUrls; - bool bNotes = sp.SearchInNotes; - bool bOther = sp.SearchInOther; - bool bStringName = sp.SearchInStringNames; - bool bTags = sp.SearchInTags; - bool bUuids = sp.SearchInUuids; - bool bGroupName = sp.SearchInGroupNames; - // bool bExcludeExpired = sp.ExcludeExpired; - // bool bRespectEntrySearchingDisabled = sp.RespectEntrySearchingDisabled; - - Regex rx = null; - if(sp.RegularExpression) - { - RegexOptions ro = RegexOptions.None; // RegexOptions.Compiled - if((sp.ComparisonMode == StringComparison.CurrentCultureIgnoreCase) || -#if !ModernKeePassLib && !KeePassRT - (sp.ComparisonMode == StringComparison.InvariantCultureIgnoreCase) || -#endif - (sp.ComparisonMode == StringComparison.OrdinalIgnoreCase)) - { - ro |= RegexOptions.IgnoreCase; - } - - rx = new Regex(sp.SearchString, ro); - } - - ulong uLocalPrcEntries = uPrcEntries; - - if(sp.SearchString.Length == 0) lResults.Add(lSource); - else - { - foreach(PwEntry pe in lSource) - { - if(slStatus != null) - { - if(!slStatus.SetProgress((uint)((uLocalPrcEntries * - 100UL) / uTotalEntries))) return false; - ++uLocalPrcEntries; - } - - // if(bRespectEntrySearchingDisabled && !pe.GetSearchingEnabled()) - // continue; - // if(bExcludeExpired && pe.Expires && (pe.ExpiryTime <= dtNow)) - // continue; - - uint uInitialResults = lResults.UCount; - - foreach(KeyValuePair kvp in pe.Strings) - { - string strKey = kvp.Key; - - if(strKey == PwDefs.TitleField) - { - if(bTitle) SearchEvalAdd(sp, kvp.Value.ReadString(), - rx, pe, lResults); - } - else if(strKey == PwDefs.UserNameField) - { - if(bUserName) SearchEvalAdd(sp, kvp.Value.ReadString(), - rx, pe, lResults); - } - else if(strKey == PwDefs.PasswordField) - { - if(bPassword) SearchEvalAdd(sp, kvp.Value.ReadString(), - rx, pe, lResults); - } - else if(strKey == PwDefs.UrlField) - { - if(bUrl) SearchEvalAdd(sp, kvp.Value.ReadString(), - rx, pe, lResults); - } - else if(strKey == PwDefs.NotesField) - { - if(bNotes) SearchEvalAdd(sp, kvp.Value.ReadString(), - rx, pe, lResults); - } - else if(bOther) - SearchEvalAdd(sp, kvp.Value.ReadString(), - rx, pe, lResults); - - // An entry can match only once => break if we have added it - if(lResults.UCount != uInitialResults) break; - } - - if(bStringName) - { - foreach(KeyValuePair kvp in pe.Strings) - { - if(lResults.UCount != uInitialResults) break; - - SearchEvalAdd(sp, kvp.Key, rx, pe, lResults); - } - } - - if(bTags) - { - foreach(string strTag in pe.Tags) - { - if(lResults.UCount != uInitialResults) break; - - SearchEvalAdd(sp, strTag, rx, pe, lResults); - } - } - - if(bUuids && (lResults.UCount == uInitialResults)) - SearchEvalAdd(sp, pe.Uuid.ToHexString(), rx, pe, lResults); - - if(bGroupName && (lResults.UCount == uInitialResults) && - (pe.ParentGroup != null)) - SearchEvalAdd(sp, pe.ParentGroup.Name, rx, pe, lResults); - } - } - - uPrcEntries = uLocalPrcEntries; - return true; - } - - private static void SearchEvalAdd(SearchParameters sp, string strData, - Regex rx, PwEntry pe, PwObjectList lResults) - { - if(sp == null) { Debug.Assert(false); return; } - if(strData == null) { Debug.Assert(false); return; } - if(pe == null) { Debug.Assert(false); return; } - if(lResults == null) { Debug.Assert(false); return; } - - bool bMatch; - if(rx == null) - bMatch = (strData.IndexOf(sp.SearchString, - sp.ComparisonMode) >= 0); - else bMatch = rx.IsMatch(strData); - - if(!bMatch && (sp.DataTransformationFn != null)) - { - string strCmp = sp.DataTransformationFn(strData, pe); - if(!object.ReferenceEquals(strCmp, strData)) - { - if(rx == null) - bMatch = (strCmp.IndexOf(sp.SearchString, - sp.ComparisonMode) >= 0); - else bMatch = rx.IsMatch(strCmp); - } - } - - if(bMatch) lResults.Add(pe); - } - - public List BuildEntryTagsList() - { - return BuildEntryTagsList(false); - } - - public List BuildEntryTagsList(bool bSort) - { - List vTags = new List(); - - EntryHandler eh = delegate(PwEntry pe) - { - foreach(string strTag in pe.Tags) - { - bool bFound = false; - for(int i = 0; i < vTags.Count; ++i) - { - if(vTags[i].Equals(strTag, StrUtil.CaseIgnoreCmp)) - { - bFound = true; - break; - } - } - - if(!bFound) vTags.Add(strTag); - } - - return true; - }; - - TraverseTree(TraversalMethod.PreOrder, null, eh); - if(bSort) vTags.Sort(StrUtil.CaseIgnoreComparer); - return vTags; - } - -#if !KeePassLibSD - public IDictionary BuildEntryTagsDict(bool bSort) - { - IDictionary d; - if(!bSort) d = new Dictionary(StrUtil.CaseIgnoreComparer); - else d = new SortedDictionary(StrUtil.CaseIgnoreComparer); - - EntryHandler eh = delegate(PwEntry pe) - { - foreach(string strTag in pe.Tags) - { - uint u; - if(d.TryGetValue(strTag, out u)) d[strTag] = u + 1; - else d[strTag] = 1; - } - - return true; - }; - - TraverseTree(TraversalMethod.PreOrder, null, eh); - return d; - } -#endif - - public void FindEntriesByTag(string strTag, PwObjectList listStorage, - bool bSearchRecursive) - { - if(strTag == null) throw new ArgumentNullException("strTag"); - if(strTag.Length == 0) return; - - foreach(PwEntry pe in m_listEntries) - { - foreach(string strEntryTag in pe.Tags) - { - if(strEntryTag.Equals(strTag, StrUtil.CaseIgnoreCmp)) - { - listStorage.Add(pe); - break; - } - } - } - - if(bSearchRecursive) - { - foreach(PwGroup pg in m_listGroups) - pg.FindEntriesByTag(strTag, listStorage, true); - } - } - - /// - /// Find a group. - /// - /// UUID identifying the group the caller is looking for. - /// If true, the search is recursive. - /// Returns reference to found group, otherwise null. - public PwGroup FindGroup(PwUuid uuid, bool bSearchRecursive) - { - // Do not assert on PwUuid.Zero - if(m_uuid.Equals(uuid)) return this; - - if(bSearchRecursive) - { - PwGroup pgRec; - foreach(PwGroup pg in m_listGroups) - { - pgRec = pg.FindGroup(uuid, true); - if(pgRec != null) return pgRec; - } - } - else // Not recursive - { - foreach(PwGroup pg in m_listGroups) - { - if(pg.m_uuid.Equals(uuid)) - return pg; - } - } - - return null; - } - - /// - /// Find an object. - /// - /// UUID of the object to find. - /// Specifies whether to search recursively. - /// If null, groups and entries are - /// searched. If true, only entries are searched. If false, - /// only groups are searched. - /// Reference to the object, if found. Otherwise null. - public IStructureItem FindObject(PwUuid uuid, bool bRecursive, - bool? bEntries) - { - if(bEntries.HasValue) - { - if(bEntries.Value) return FindEntry(uuid, bRecursive); - else return FindGroup(uuid, bRecursive); - } - - PwGroup pg = FindGroup(uuid, bRecursive); - if(pg != null) return pg; - return FindEntry(uuid, bRecursive); - } - - /// - /// Try to find a subgroup and create it, if it doesn't exist yet. - /// - /// Name of the subgroup. - /// If the group isn't found: create it. - /// Returns a reference to the requested group or null if - /// it doesn't exist and shouldn't be created. - public PwGroup FindCreateGroup(string strName, bool bCreateIfNotFound) - { - Debug.Assert(strName != null); if(strName == null) throw new ArgumentNullException("strName"); - - foreach(PwGroup pg in m_listGroups) - { - if(pg.Name == strName) return pg; - } - - if(!bCreateIfNotFound) return null; - - PwGroup pgNew = new PwGroup(true, true, strName, PwIcon.Folder); - AddGroup(pgNew, true); - return pgNew; - } - - /// - /// Find an entry. - /// - /// UUID identifying the entry the caller is looking for. - /// If true, the search is recursive. - /// Returns reference to found entry, otherwise null. - public PwEntry FindEntry(PwUuid uuid, bool bSearchRecursive) - { - foreach(PwEntry pe in m_listEntries) - { - if(pe.Uuid.Equals(uuid)) return pe; - } - - if(bSearchRecursive) - { - PwEntry peSub; - foreach(PwGroup pg in m_listGroups) - { - peSub = pg.FindEntry(uuid, true); - if(peSub != null) return peSub; - } - } - - return null; - } - - /// - /// Get the full path of a group. - /// - /// Full path of the group. - public string GetFullPath() - { - return GetFullPath(".", false); - } - - /// - /// Get the full path of a group. - /// - /// String that separates the group - /// names. - /// Specifies whether the returned - /// path starts with the topmost group. - /// Full path of the group. - public string GetFullPath(string strSeparator, bool bIncludeTopMostGroup) - { - Debug.Assert(strSeparator != null); - if(strSeparator == null) throw new ArgumentNullException("strSeparator"); - - string strPath = m_strName; - - PwGroup pg = m_pParentGroup; - while(pg != null) - { - if((!bIncludeTopMostGroup) && (pg.m_pParentGroup == null)) - break; - - strPath = pg.Name + strSeparator + strPath; - - pg = pg.m_pParentGroup; - } - - return strPath; - } - - /// - /// Assign new UUIDs to groups and entries. - /// - /// Create new UUIDs for subgroups. - /// Create new UUIDs for entries. - /// Recursive tree traversal. - public void CreateNewItemUuids(bool bNewGroups, bool bNewEntries, bool bRecursive) - { - if(bNewGroups) - { - foreach(PwGroup pg in m_listGroups) - pg.Uuid = new PwUuid(true); - } - - if(bNewEntries) - { - foreach(PwEntry pe in m_listEntries) - pe.SetUuid(new PwUuid(true), true); - } - - if(bRecursive) - { - foreach(PwGroup pg in m_listGroups) - pg.CreateNewItemUuids(bNewGroups, bNewEntries, true); - } - } - - public void TakeOwnership(bool bTakeSubGroups, bool bTakeEntries, bool bRecursive) - { - if(bTakeSubGroups) - { - foreach(PwGroup pg in m_listGroups) - pg.ParentGroup = this; - } - - if(bTakeEntries) - { - foreach(PwEntry pe in m_listEntries) - pe.ParentGroup = this; - } - - if(bRecursive) - { - foreach(PwGroup pg in m_listGroups) - pg.TakeOwnership(bTakeSubGroups, bTakeEntries, true); - } - } - -#if !KeePassLibSD - /// - /// Find/create a subtree of groups. - /// - /// Tree string. - /// Separators that delimit groups in the - /// strTree parameter. - public PwGroup FindCreateSubTree(string strTree, char[] vSeparators) - { - return FindCreateSubTree(strTree, vSeparators, true); - } - - public PwGroup FindCreateSubTree(string strTree, char[] vSeparators, - bool bAllowCreate) - { - if(vSeparators == null) { Debug.Assert(false); vSeparators = new char[0]; } - - string[] v = new string[vSeparators.Length]; - for(int i = 0; i < vSeparators.Length; ++i) - v[i] = new string(vSeparators[i], 1); - - return FindCreateSubTree(strTree, v, bAllowCreate); - } - - public PwGroup FindCreateSubTree(string strTree, string[] vSeparators, - bool bAllowCreate) - { - Debug.Assert(strTree != null); if(strTree == null) return this; - if(strTree.Length == 0) return this; - - string[] vGroups = strTree.Split(vSeparators, StringSplitOptions.None); - if((vGroups == null) || (vGroups.Length == 0)) return this; - - PwGroup pgContainer = this; - for(int nGroup = 0; nGroup < vGroups.Length; ++nGroup) - { - if(string.IsNullOrEmpty(vGroups[nGroup])) continue; - - bool bFound = false; - foreach(PwGroup pg in pgContainer.Groups) - { - if(pg.Name == vGroups[nGroup]) - { - pgContainer = pg; - bFound = true; - break; - } - } - - if(!bFound) - { - if(!bAllowCreate) return null; - - PwGroup pg = new PwGroup(true, true, vGroups[nGroup], PwIcon.Folder); - pgContainer.AddGroup(pg, true); - pgContainer = pg; - } - } - - return pgContainer; - } -#endif - - /// - /// Get the level of the group (i.e. the number of parent groups). - /// - /// Number of parent groups. - public uint GetLevel() - { - PwGroup pg = m_pParentGroup; - uint uLevel = 0; - - while(pg != null) - { - pg = pg.ParentGroup; - ++uLevel; - } - - return uLevel; - } - - public string GetAutoTypeSequenceInherited() - { - if(m_strDefaultAutoTypeSequence.Length > 0) - return m_strDefaultAutoTypeSequence; - - if(m_pParentGroup != null) - return m_pParentGroup.GetAutoTypeSequenceInherited(); - - return string.Empty; - } - - public bool GetAutoTypeEnabledInherited() - { - if(m_bEnableAutoType.HasValue) return m_bEnableAutoType.Value; - - if(m_pParentGroup != null) - return m_pParentGroup.GetAutoTypeEnabledInherited(); - - return DefaultAutoTypeEnabled; - } - - public bool GetSearchingEnabledInherited() - { - if(m_bEnableSearching.HasValue) return m_bEnableSearching.Value; - - if(m_pParentGroup != null) - return m_pParentGroup.GetSearchingEnabledInherited(); - - return DefaultSearchingEnabled; - } - - /// - /// Get a list of subgroups (not including this one). - /// - /// If true, subgroups are added - /// recursively, i.e. all child groups are returned, too. - /// List of subgroups. If is - /// true, it is guaranteed that subsubgroups appear after - /// subgroups. - public PwObjectList GetGroups(bool bRecursive) - { - if(bRecursive == false) return m_listGroups; - - PwObjectList list = m_listGroups.CloneShallow(); - foreach(PwGroup pgSub in m_listGroups) - { - list.Add(pgSub.GetGroups(true)); - } - - return list; - } - - public PwObjectList GetEntries(bool bIncludeSubGroupEntries) - { - PwObjectList l = new PwObjectList(); - - GroupHandler gh = delegate(PwGroup pg) - { - l.Add(pg.Entries); - return true; - }; - - gh(this); - if(bIncludeSubGroupEntries) - PreOrderTraverseTree(gh, null); - - Debug.Assert(l.UCount == GetEntriesCount(bIncludeSubGroupEntries)); - return l; - } - - /// - /// Get objects contained in this group. - /// - /// Specifies whether to search recursively. - /// If null, the returned list contains - /// groups and entries. If true, the returned list contains only - /// entries. If false, the returned list contains only groups. - /// List of objects. - public List GetObjects(bool bRecursive, bool? bEntries) - { - List list = new List(); - - if(!bEntries.HasValue || !bEntries.Value) - { - PwObjectList lGroups = GetGroups(bRecursive); - foreach(PwGroup pg in lGroups) list.Add(pg); - } - - if(!bEntries.HasValue || bEntries.Value) - { - PwObjectList lEntries = GetEntries(bRecursive); - foreach(PwEntry pe in lEntries) list.Add(pe); - } - - return list; - } - - public bool IsContainedIn(PwGroup pgContainer) - { - PwGroup pgCur = m_pParentGroup; - while(pgCur != null) - { - if(pgCur == pgContainer) return true; - - pgCur = pgCur.m_pParentGroup; - } - - return false; - } - - /// - /// Add a subgroup to this group. - /// - /// Group to be added. Must not be null. - /// If this parameter is true, the - /// parent group reference of the subgroup will be set to the current - /// group (i.e. the current group takes ownership of the subgroup). - public void AddGroup(PwGroup subGroup, bool bTakeOwnership) - { - AddGroup(subGroup, bTakeOwnership, false); - } - - /// - /// Add a subgroup to this group. - /// - /// Group to be added. Must not be null. - /// If this parameter is true, the - /// parent group reference of the subgroup will be set to the current - /// group (i.e. the current group takes ownership of the subgroup). - /// If true, the - /// LocationChanged property of the subgroup is updated. - public void AddGroup(PwGroup subGroup, bool bTakeOwnership, - bool bUpdateLocationChangedOfSub) - { - if(subGroup == null) throw new ArgumentNullException("subGroup"); - - m_listGroups.Add(subGroup); - - if(bTakeOwnership) subGroup.m_pParentGroup = this; - - if(bUpdateLocationChangedOfSub) subGroup.LocationChanged = DateTime.UtcNow; - } - - /// - /// Add an entry to this group. - /// - /// Entry to be added. Must not be null. - /// If this parameter is true, the - /// parent group reference of the entry will be set to the current - /// group (i.e. the current group takes ownership of the entry). - public void AddEntry(PwEntry pe, bool bTakeOwnership) - { - AddEntry(pe, bTakeOwnership, false); - } - - /// - /// Add an entry to this group. - /// - /// Entry to be added. Must not be null. - /// If this parameter is true, the - /// parent group reference of the entry will be set to the current - /// group (i.e. the current group takes ownership of the entry). - /// If true, the - /// LocationChanged property of the entry is updated. - public void AddEntry(PwEntry pe, bool bTakeOwnership, - bool bUpdateLocationChangedOfEntry) - { - if(pe == null) throw new ArgumentNullException("pe"); - - m_listEntries.Add(pe); - - // Do not remove the entry from its previous parent group, - // only assign it to the new one - if(bTakeOwnership) pe.ParentGroup = this; - - if(bUpdateLocationChangedOfEntry) pe.LocationChanged = DateTime.UtcNow; - } - - public void SortSubGroups(bool bRecursive) - { - m_listGroups.Sort(new PwGroupComparer()); - - if(bRecursive) - { - foreach(PwGroup pgSub in m_listGroups) - pgSub.SortSubGroups(true); - } - } - - public void DeleteAllObjects(PwDatabase pdContext) - { - DateTime dtNow = DateTime.UtcNow; - - foreach(PwEntry pe in m_listEntries) - { - PwDeletedObject pdo = new PwDeletedObject(pe.Uuid, dtNow); - pdContext.DeletedObjects.Add(pdo); - } - m_listEntries.Clear(); - - foreach(PwGroup pg in m_listGroups) - { - pg.DeleteAllObjects(pdContext); - - PwDeletedObject pdo = new PwDeletedObject(pg.Uuid, dtNow); - pdContext.DeletedObjects.Add(pdo); - } - m_listGroups.Clear(); - } - - internal List GetTopSearchSkippedGroups() - { - List l = new List(); - - if(!GetSearchingEnabledInherited()) l.Add(this); - else GetTopSearchSkippedGroupsRec(l); - - return l; - } - - private void GetTopSearchSkippedGroupsRec(List l) - { - if(m_bEnableSearching.HasValue && !m_bEnableSearching.Value) - { - l.Add(this); - return; - } - else { Debug.Assert(GetSearchingEnabledInherited()); } - - foreach(PwGroup pgSub in m_listGroups) - pgSub.GetTopSearchSkippedGroupsRec(l); - } - - public void SetCreatedNow(bool bRecursive) - { - DateTime dt = DateTime.UtcNow; - - m_tCreation = dt; - m_tLastAccess = dt; - - if(!bRecursive) return; - - GroupHandler gh = delegate(PwGroup pg) - { - pg.m_tCreation = dt; - pg.m_tLastAccess = dt; - return true; - }; - - EntryHandler eh = delegate(PwEntry pe) - { - pe.CreationTime = dt; - pe.LastAccessTime = dt; - return true; - }; - - TraverseTree(TraversalMethod.PreOrder, gh, eh); - } - - public PwGroup Duplicate() - { - PwGroup pg = CloneDeep(); - - pg.Uuid = new PwUuid(true); - pg.CreateNewItemUuids(true, true, true); - - pg.SetCreatedNow(true); - - pg.TakeOwnership(true, true, true); - - return pg; - } - } - - public sealed class PwGroupComparer : IComparer - { - public PwGroupComparer() - { - } - - public int Compare(PwGroup a, PwGroup b) - { - return StrUtil.CompareNaturally(a.Name, b.Name); - } - } -} diff --git a/ModernKeePassLib/PwUuid.cs b/ModernKeePassLib/PwUuid.cs deleted file mode 100644 index 05f3e05..0000000 --- a/ModernKeePassLib/PwUuid.cs +++ /dev/null @@ -1,215 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; -using System.Xml; -using System.Diagnostics; - -using ModernKeePassLib.Utility; - -namespace ModernKeePassLib -{ - // [ImmutableObject(true)] - /// - /// Represents an UUID of a password entry or group. Once created, - /// PwUuid objects aren't modifyable anymore (immutable). - /// - public sealed class PwUuid : IComparable, IEquatable - { - /// - /// Standard size in bytes of a UUID. - /// - public const uint UuidSize = 16; - - /// - /// Zero UUID (all bytes are zero). - /// - public static readonly PwUuid Zero = new PwUuid(false); - - private byte[] m_pbUuid = null; // Never null after constructor - - /// - /// Get the 16 UUID bytes. - /// - public byte[] UuidBytes - { - get { return m_pbUuid; } - } - - /// - /// Construct a new UUID object. - /// - /// If this parameter is true, a new - /// UUID is generated. If it is false, the UUID is initialized - /// to zero. - public PwUuid(bool bCreateNew) - { - if(bCreateNew) CreateNew(); - else SetZero(); - } - - /// - /// Construct a new UUID object. - /// - /// Initial value of the PwUuid object. - public PwUuid(byte[] uuidBytes) - { - SetValue(uuidBytes); - } - - /// - /// Create a new, random UUID. - /// - /// Returns true if a random UUID has been generated, - /// otherwise it returns false. - private void CreateNew() - { - Debug.Assert(m_pbUuid == null); // Only call from constructor - while(true) - { - m_pbUuid = Guid.NewGuid().ToByteArray(); - - if((m_pbUuid == null) || (m_pbUuid.Length != (int)UuidSize)) - { - Debug.Assert(false); - throw new InvalidOperationException(); - } - - // Zero is a reserved value -- do not generate Zero - if(!Equals(PwUuid.Zero)) break; - Debug.Assert(false); - } - } - - private void SetValue(byte[] uuidBytes) - { - Debug.Assert((uuidBytes != null) && (uuidBytes.Length == (int)UuidSize)); - if(uuidBytes == null) throw new ArgumentNullException("uuidBytes"); - if(uuidBytes.Length != (int)UuidSize) throw new ArgumentException(); - - Debug.Assert(m_pbUuid == null); // Only call from constructor - m_pbUuid = new byte[UuidSize]; - - Array.Copy(uuidBytes, m_pbUuid, (int)UuidSize); - } - - private void SetZero() - { - Debug.Assert(m_pbUuid == null); // Only call from constructor - m_pbUuid = new byte[UuidSize]; - - // Array.Clear(m_pbUuid, 0, (int)UuidSize); -#if DEBUG - List l = new List(m_pbUuid); - Debug.Assert(l.TrueForAll(bt => (bt == 0))); -#endif - } - - [Obsolete] - public bool EqualsValue(PwUuid uuid) - { - return Equals(uuid); - } - - public override bool Equals(object obj) - { - return Equals(obj as PwUuid); - } - - public bool Equals(PwUuid other) - { - if(other == null) { Debug.Assert(false); return false; } - - for(int i = 0; i < (int)UuidSize; ++i) - { - if(m_pbUuid[i] != other.m_pbUuid[i]) return false; - } - - return true; - } - - private int m_h = 0; - public override int GetHashCode() - { - if(m_h == 0) - m_h = (int)MemUtil.Hash32(m_pbUuid, 0, m_pbUuid.Length); - return m_h; - } - - public int CompareTo(PwUuid other) - { - if(other == null) - { - Debug.Assert(false); - throw new ArgumentNullException("other"); - } - - for(int i = 0; i < (int)UuidSize; ++i) - { - if(m_pbUuid[i] < other.m_pbUuid[i]) return -1; - if(m_pbUuid[i] > other.m_pbUuid[i]) return 1; - } - - return 0; - } - - /// - /// Convert the UUID to its string representation. - /// - /// String containing the UUID value. - public string ToHexString() - { - return MemUtil.ByteArrayToHexString(m_pbUuid); - } - -#if DEBUG - public override string ToString() - { - return ToHexString(); - } -#endif - } - - [Obsolete] - public sealed class PwUuidComparable : IComparable - { - private byte[] m_pbUuid = new byte[PwUuid.UuidSize]; - - public PwUuidComparable(PwUuid pwUuid) - { - if(pwUuid == null) throw new ArgumentNullException("pwUuid"); - - Array.Copy(pwUuid.UuidBytes, m_pbUuid, (int)PwUuid.UuidSize); - } - - public int CompareTo(PwUuidComparable other) - { - if(other == null) throw new ArgumentNullException("other"); - - for(int i = 0; i < (int)PwUuid.UuidSize; ++i) - { - if(m_pbUuid[i] < other.m_pbUuid[i]) return -1; - if(m_pbUuid[i] > other.m_pbUuid[i]) return 1; - } - - return 0; - } - } -} diff --git a/ModernKeePassLib/README.md b/ModernKeePassLib/README.md deleted file mode 100644 index debe24f..0000000 --- a/ModernKeePassLib/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# ModernKeePassLib - -This is my adaptation of the KeePassLib (KeePass library) for the Universal Windows Platform and Windows Runtime (WinRT). -It aims at introducing as little change as possible to the original library: overall, except for namespace changes and some added classes (see below), there is almost no change. - -Download the Nuget package [here](https://www.nuget.org/packages/ModernKeePassLib) - -# Features -- Custom implementation of the System.Security.Cryptography.HashAlgoritm class by using WinRT equivalents -- Use of BouncyCastle PCL to implement AES key derivation features -- Lots of small changes in .NET methods (UTF8 instead of ASCII, string.) -- Disabled native functions (because not compatible with WinRT) -- Use of Splat for GfxUtil \ No newline at end of file diff --git a/ModernKeePassLib/Resources/KLRes.Generated.cs b/ModernKeePassLib/Resources/KLRes.Generated.cs deleted file mode 100644 index 007d875..0000000 --- a/ModernKeePassLib/Resources/KLRes.Generated.cs +++ /dev/null @@ -1,546 +0,0 @@ -// This is a generated file! -// Do not edit manually, changes will be overwritten. - -using System; -using System.Collections.Generic; - -namespace ModernKeePassLib.Resources -{ - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - public static class KLRes - { - private static string TryGetEx(Dictionary dictNew, - string strName, string strDefault) - { - string strTemp; - - if(dictNew.TryGetValue(strName, out strTemp)) - return strTemp; - - return strDefault; - } - - public static void SetTranslatedStrings(Dictionary dictNew) - { - if(dictNew == null) throw new ArgumentNullException("dictNew"); - - m_strCryptoStreamFailed = TryGetEx(dictNew, "CryptoStreamFailed", m_strCryptoStreamFailed); - m_strEncDataTooLarge = TryGetEx(dictNew, "EncDataTooLarge", m_strEncDataTooLarge); - m_strErrorInClipboard = TryGetEx(dictNew, "ErrorInClipboard", m_strErrorInClipboard); - m_strExpect100Continue = TryGetEx(dictNew, "Expect100Continue", m_strExpect100Continue); - m_strFatalError = TryGetEx(dictNew, "FatalError", m_strFatalError); - m_strFatalErrorText = TryGetEx(dictNew, "FatalErrorText", m_strFatalErrorText); - m_strFileCorrupted = TryGetEx(dictNew, "FileCorrupted", m_strFileCorrupted); - m_strFileHeaderCorrupted = TryGetEx(dictNew, "FileHeaderCorrupted", m_strFileHeaderCorrupted); - m_strFileIncomplete = TryGetEx(dictNew, "FileIncomplete", m_strFileIncomplete); - m_strFileIncompleteExpc = TryGetEx(dictNew, "FileIncompleteExpc", m_strFileIncompleteExpc); - m_strFileLoadFailed = TryGetEx(dictNew, "FileLoadFailed", m_strFileLoadFailed); - m_strFileLockedWrite = TryGetEx(dictNew, "FileLockedWrite", m_strFileLockedWrite); - m_strFileNewVerOrPlgReq = TryGetEx(dictNew, "FileNewVerOrPlgReq", m_strFileNewVerOrPlgReq); - m_strFileNewVerReq = TryGetEx(dictNew, "FileNewVerReq", m_strFileNewVerReq); - m_strFileSaveCorruptionWarning = TryGetEx(dictNew, "FileSaveCorruptionWarning", m_strFileSaveCorruptionWarning); - m_strFileSaveFailed = TryGetEx(dictNew, "FileSaveFailed", m_strFileSaveFailed); - m_strFileSigInvalid = TryGetEx(dictNew, "FileSigInvalid", m_strFileSigInvalid); - m_strFileUnknownCipher = TryGetEx(dictNew, "FileUnknownCipher", m_strFileUnknownCipher); - m_strFileUnknownCompression = TryGetEx(dictNew, "FileUnknownCompression", m_strFileUnknownCompression); - m_strFileVersionUnsupported = TryGetEx(dictNew, "FileVersionUnsupported", m_strFileVersionUnsupported); - m_strFinalKeyCreationFailed = TryGetEx(dictNew, "FinalKeyCreationFailed", m_strFinalKeyCreationFailed); - m_strFrameworkNotImplExcp = TryGetEx(dictNew, "FrameworkNotImplExcp", m_strFrameworkNotImplExcp); - m_strGeneral = TryGetEx(dictNew, "General", m_strGeneral); - m_strInvalidCompositeKey = TryGetEx(dictNew, "InvalidCompositeKey", m_strInvalidCompositeKey); - m_strInvalidCompositeKeyHint = TryGetEx(dictNew, "InvalidCompositeKeyHint", m_strInvalidCompositeKeyHint); - m_strInvalidDataWhileDecoding = TryGetEx(dictNew, "InvalidDataWhileDecoding", m_strInvalidDataWhileDecoding); - m_strKeePass1xHint = TryGetEx(dictNew, "KeePass1xHint", m_strKeePass1xHint); - m_strKeyBits = TryGetEx(dictNew, "KeyBits", m_strKeyBits); - m_strKeyFileDbSel = TryGetEx(dictNew, "KeyFileDbSel", m_strKeyFileDbSel); - m_strMasterSeedLengthInvalid = TryGetEx(dictNew, "MasterSeedLengthInvalid", m_strMasterSeedLengthInvalid); - m_strOldFormat = TryGetEx(dictNew, "OldFormat", m_strOldFormat); - m_strPassive = TryGetEx(dictNew, "Passive", m_strPassive); - m_strPreAuth = TryGetEx(dictNew, "PreAuth", m_strPreAuth); - m_strTimeout = TryGetEx(dictNew, "Timeout", m_strTimeout); - m_strTryAgainSecs = TryGetEx(dictNew, "TryAgainSecs", m_strTryAgainSecs); - m_strUnknownHeaderId = TryGetEx(dictNew, "UnknownHeaderId", m_strUnknownHeaderId); - m_strUnknownKdf = TryGetEx(dictNew, "UnknownKdf", m_strUnknownKdf); - m_strUserAccountKeyError = TryGetEx(dictNew, "UserAccountKeyError", m_strUserAccountKeyError); - m_strUserAgent = TryGetEx(dictNew, "UserAgent", m_strUserAgent); - } - - private static readonly string[] m_vKeyNames = { - "CryptoStreamFailed", - "EncDataTooLarge", - "ErrorInClipboard", - "Expect100Continue", - "FatalError", - "FatalErrorText", - "FileCorrupted", - "FileHeaderCorrupted", - "FileIncomplete", - "FileIncompleteExpc", - "FileLoadFailed", - "FileLockedWrite", - "FileNewVerOrPlgReq", - "FileNewVerReq", - "FileSaveCorruptionWarning", - "FileSaveFailed", - "FileSigInvalid", - "FileUnknownCipher", - "FileUnknownCompression", - "FileVersionUnsupported", - "FinalKeyCreationFailed", - "FrameworkNotImplExcp", - "General", - "InvalidCompositeKey", - "InvalidCompositeKeyHint", - "InvalidDataWhileDecoding", - "KeePass1xHint", - "KeyBits", - "KeyFileDbSel", - "MasterSeedLengthInvalid", - "OldFormat", - "Passive", - "PreAuth", - "Timeout", - "TryAgainSecs", - "UnknownHeaderId", - "UnknownKdf", - "UserAccountKeyError", - "UserAgent" - }; - - public static string[] GetKeyNames() - { - return m_vKeyNames; - } - - private static string m_strCryptoStreamFailed = - @"Failed to initialize encryption/decryption stream!"; - /// - /// Look up a localized string similar to - /// 'Failed to initialize encryption/decryption stream!'. - /// - public static string CryptoStreamFailed - { - get { return m_strCryptoStreamFailed; } - } - - private static string m_strEncDataTooLarge = - @"The data is too large to be encrypted/decrypted securely using {PARAM}."; - /// - /// Look up a localized string similar to - /// 'The data is too large to be encrypted/decrypted securely using {PARAM}.'. - /// - public static string EncDataTooLarge - { - get { return m_strEncDataTooLarge; } - } - - private static string m_strErrorInClipboard = - @"An extended error report has been copied to the clipboard."; - /// - /// Look up a localized string similar to - /// 'An extended error report has been copied to the clipboard.'. - /// - public static string ErrorInClipboard - { - get { return m_strErrorInClipboard; } - } - - private static string m_strExpect100Continue = - @"Expect 100-Continue responses"; - /// - /// Look up a localized string similar to - /// 'Expect 100-Continue responses'. - /// - public static string Expect100Continue - { - get { return m_strExpect100Continue; } - } - - private static string m_strFatalError = - @"Fatal Error"; - /// - /// Look up a localized string similar to - /// 'Fatal Error'. - /// - public static string FatalError - { - get { return m_strFatalError; } - } - - private static string m_strFatalErrorText = - @"A fatal error has occurred!"; - /// - /// Look up a localized string similar to - /// 'A fatal error has occurred!'. - /// - public static string FatalErrorText - { - get { return m_strFatalErrorText; } - } - - private static string m_strFileCorrupted = - @"The file is corrupted."; - /// - /// Look up a localized string similar to - /// 'The file is corrupted.'. - /// - public static string FileCorrupted - { - get { return m_strFileCorrupted; } - } - - private static string m_strFileHeaderCorrupted = - @"The file header is corrupted."; - /// - /// Look up a localized string similar to - /// 'The file header is corrupted.'. - /// - public static string FileHeaderCorrupted - { - get { return m_strFileHeaderCorrupted; } - } - - private static string m_strFileIncomplete = - @"Data is missing at the end of the file, i.e. the file is incomplete."; - /// - /// Look up a localized string similar to - /// 'Data is missing at the end of the file, i.e. the file is incomplete.'. - /// - public static string FileIncomplete - { - get { return m_strFileIncomplete; } - } - - private static string m_strFileIncompleteExpc = - @"Less data than expected could be read from the file."; - /// - /// Look up a localized string similar to - /// 'Less data than expected could be read from the file.'. - /// - public static string FileIncompleteExpc - { - get { return m_strFileIncompleteExpc; } - } - - private static string m_strFileLoadFailed = - @"Failed to load the specified file!"; - /// - /// Look up a localized string similar to - /// 'Failed to load the specified file!'. - /// - public static string FileLoadFailed - { - get { return m_strFileLoadFailed; } - } - - private static string m_strFileLockedWrite = - @"The file is locked, because the following user is currently writing to it:"; - /// - /// Look up a localized string similar to - /// 'The file is locked, because the following user is currently writing to it:'. - /// - public static string FileLockedWrite - { - get { return m_strFileLockedWrite; } - } - - private static string m_strFileNewVerOrPlgReq = - @"A newer KeePass version or a plugin is required to open this file."; - /// - /// Look up a localized string similar to - /// 'A newer KeePass version or a plugin is required to open this file.'. - /// - public static string FileNewVerOrPlgReq - { - get { return m_strFileNewVerOrPlgReq; } - } - - private static string m_strFileNewVerReq = - @"A newer KeePass version is required to open this file."; - /// - /// Look up a localized string similar to - /// 'A newer KeePass version is required to open this file.'. - /// - public static string FileNewVerReq - { - get { return m_strFileNewVerReq; } - } - - private static string m_strFileSaveCorruptionWarning = - @"The target file might be corrupted. Please try saving again. If that fails, save the database to a different location."; - /// - /// Look up a localized string similar to - /// 'The target file might be corrupted. Please try saving again. If that fails, save the database to a different location.'. - /// - public static string FileSaveCorruptionWarning - { - get { return m_strFileSaveCorruptionWarning; } - } - - private static string m_strFileSaveFailed = - @"Failed to save the current database to the specified location!"; - /// - /// Look up a localized string similar to - /// 'Failed to save the current database to the specified location!'. - /// - public static string FileSaveFailed - { - get { return m_strFileSaveFailed; } - } - - private static string m_strFileSigInvalid = - @"The file signature is invalid. Either the file isn't a KeePass database file at all or it is corrupted."; - /// - /// Look up a localized string similar to - /// 'The file signature is invalid. Either the file isn't a KeePass database file at all or it is corrupted.'. - /// - public static string FileSigInvalid - { - get { return m_strFileSigInvalid; } - } - - private static string m_strFileUnknownCipher = - @"The file is encrypted using an unknown encryption algorithm!"; - /// - /// Look up a localized string similar to - /// 'The file is encrypted using an unknown encryption algorithm!'. - /// - public static string FileUnknownCipher - { - get { return m_strFileUnknownCipher; } - } - - private static string m_strFileUnknownCompression = - @"The file is compressed using an unknown compression algorithm!"; - /// - /// Look up a localized string similar to - /// 'The file is compressed using an unknown compression algorithm!'. - /// - public static string FileUnknownCompression - { - get { return m_strFileUnknownCompression; } - } - - private static string m_strFileVersionUnsupported = - @"The file version is unsupported."; - /// - /// Look up a localized string similar to - /// 'The file version is unsupported.'. - /// - public static string FileVersionUnsupported - { - get { return m_strFileVersionUnsupported; } - } - - private static string m_strFinalKeyCreationFailed = - @"Failed to create the final encryption/decryption key!"; - /// - /// Look up a localized string similar to - /// 'Failed to create the final encryption/decryption key!'. - /// - public static string FinalKeyCreationFailed - { - get { return m_strFinalKeyCreationFailed; } - } - - private static string m_strFrameworkNotImplExcp = - @"The .NET framework/runtime under which KeePass is currently running does not support this operation."; - /// - /// Look up a localized string similar to - /// 'The .NET framework/runtime under which KeePass is currently running does not support this operation.'. - /// - public static string FrameworkNotImplExcp - { - get { return m_strFrameworkNotImplExcp; } - } - - private static string m_strGeneral = - @"General"; - /// - /// Look up a localized string similar to - /// 'General'. - /// - public static string General - { - get { return m_strGeneral; } - } - - private static string m_strInvalidCompositeKey = - @"The composite key is invalid!"; - /// - /// Look up a localized string similar to - /// 'The composite key is invalid!'. - /// - public static string InvalidCompositeKey - { - get { return m_strInvalidCompositeKey; } - } - - private static string m_strInvalidCompositeKeyHint = - @"Make sure the composite key is correct and try again."; - /// - /// Look up a localized string similar to - /// 'Make sure the composite key is correct and try again.'. - /// - public static string InvalidCompositeKeyHint - { - get { return m_strInvalidCompositeKeyHint; } - } - - private static string m_strInvalidDataWhileDecoding = - @"Found invalid data while decoding."; - /// - /// Look up a localized string similar to - /// 'Found invalid data while decoding.'. - /// - public static string InvalidDataWhileDecoding - { - get { return m_strInvalidDataWhileDecoding; } - } - - private static string m_strKeePass1xHint = - @"In order to import KeePass 1.x KDB files, create a new 2.x database file and click 'File' -> 'Import' in the main menu. In the import dialog, choose 'KeePass KDB (1.x)' as file format."; - /// - /// Look up a localized string similar to - /// 'In order to import KeePass 1.x KDB files, create a new 2.x database file and click 'File' -> 'Import' in the main menu. In the import dialog, choose 'KeePass KDB (1.x)' as file format.'. - /// - public static string KeePass1xHint - { - get { return m_strKeePass1xHint; } - } - - private static string m_strKeyBits = - @"{PARAM}-bit key"; - /// - /// Look up a localized string similar to - /// '{PARAM}-bit key'. - /// - public static string KeyBits - { - get { return m_strKeyBits; } - } - - private static string m_strKeyFileDbSel = - @"Database files cannot be used as key files."; - /// - /// Look up a localized string similar to - /// 'Database files cannot be used as key files.'. - /// - public static string KeyFileDbSel - { - get { return m_strKeyFileDbSel; } - } - - private static string m_strMasterSeedLengthInvalid = - @"The length of the master key seed is invalid!"; - /// - /// Look up a localized string similar to - /// 'The length of the master key seed is invalid!'. - /// - public static string MasterSeedLengthInvalid - { - get { return m_strMasterSeedLengthInvalid; } - } - - private static string m_strOldFormat = - @"The selected file appears to be an old format"; - /// - /// Look up a localized string similar to - /// 'The selected file appears to be an old format'. - /// - public static string OldFormat - { - get { return m_strOldFormat; } - } - - private static string m_strPassive = - @"Passive"; - /// - /// Look up a localized string similar to - /// 'Passive'. - /// - public static string Passive - { - get { return m_strPassive; } - } - - private static string m_strPreAuth = - @"Pre-authenticate"; - /// - /// Look up a localized string similar to - /// 'Pre-authenticate'. - /// - public static string PreAuth - { - get { return m_strPreAuth; } - } - - private static string m_strTimeout = - @"Timeout"; - /// - /// Look up a localized string similar to - /// 'Timeout'. - /// - public static string Timeout - { - get { return m_strTimeout; } - } - - private static string m_strTryAgainSecs = - @"Please try it again in a few seconds."; - /// - /// Look up a localized string similar to - /// 'Please try it again in a few seconds.'. - /// - public static string TryAgainSecs - { - get { return m_strTryAgainSecs; } - } - - private static string m_strUnknownHeaderId = - @"Unknown header ID!"; - /// - /// Look up a localized string similar to - /// 'Unknown header ID!'. - /// - public static string UnknownHeaderId - { - get { return m_strUnknownHeaderId; } - } - - private static string m_strUnknownKdf = - @"Unknown key derivation function!"; - /// - /// Look up a localized string similar to - /// 'Unknown key derivation function!'. - /// - public static string UnknownKdf - { - get { return m_strUnknownKdf; } - } - - private static string m_strUserAccountKeyError = - @"The operating system did not grant KeePass read/write access to the user profile folder, where the protected user key is stored."; - /// - /// Look up a localized string similar to - /// 'The operating system did not grant KeePass read/write access to the user profile folder, where the protected user key is stored.'. - /// - public static string UserAccountKeyError - { - get { return m_strUserAccountKeyError; } - } - - private static string m_strUserAgent = - @"User agent"; - /// - /// Look up a localized string similar to - /// 'User agent'. - /// - public static string UserAgent - { - get { return m_strUserAgent; } - } - } -} diff --git a/ModernKeePassLib/Resources/KSRes.Generated.cs b/ModernKeePassLib/Resources/KSRes.Generated.cs deleted file mode 100644 index 7b49547..0000000 --- a/ModernKeePassLib/Resources/KSRes.Generated.cs +++ /dev/null @@ -1,52 +0,0 @@ -// This is a generated file! -// Do not edit manually, changes will be overwritten. - -using System; -using System.Collections.Generic; - -namespace ModernKeePassLib.Resources -{ - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - public static class KSRes - { - private static string TryGetEx(Dictionary dictNew, - string strName, string strDefault) - { - string strTemp; - - if(dictNew.TryGetValue(strName, out strTemp)) - return strTemp; - - return strDefault; - } - - public static void SetTranslatedStrings(Dictionary dictNew) - { - if(dictNew == null) throw new ArgumentNullException("dictNew"); - - m_strTest = TryGetEx(dictNew, "Test", m_strTest); - } - - private static readonly string[] m_vKeyNames = { - "Test" - }; - - public static string[] GetKeyNames() - { - return m_vKeyNames; - } - - private static string m_strTest = - @"Test"; - /// - /// Look up a localized string similar to - /// 'Test'. - /// - public static string Test - { - get { return m_strTest; } - } - } -} diff --git a/ModernKeePassLib/Security/ProtectedBinary.cs b/ModernKeePassLib/Security/ProtectedBinary.cs deleted file mode 100644 index eff079a..0000000 --- a/ModernKeePassLib/Security/ProtectedBinary.cs +++ /dev/null @@ -1,418 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Diagnostics; -using System.Threading; - -using ModernKeePassLib.Cryptography; -using ModernKeePassLib.Cryptography.Cipher; -using ModernKeePassLib.Native; -using ModernKeePassLib.Utility; - -#if KeePassLibSD -using KeePassLibSD; -#endif - -namespace ModernKeePassLib.Security -{ - [Flags] - public enum PbCryptFlags - { - None = 0, - Encrypt = 1, - Decrypt = 2 - } - - public delegate void PbCryptDelegate(byte[] pbData, PbCryptFlags cf, - long lID); - - /// - /// A protected binary, i.e. a byte array that is encrypted in memory. - /// A ProtectedBinary object is immutable and thread-safe. - /// - public sealed class ProtectedBinary : IEquatable - { - private const int BlockSize = 16; - - private static PbCryptDelegate g_fExtCrypt = null; - /// - /// A plugin can provide a custom memory protection method - /// by assigning a non-null delegate to this property. - /// - public static PbCryptDelegate ExtCrypt - { - get { return g_fExtCrypt; } - set { g_fExtCrypt = value; } - } - - // Local copy of the delegate that was used for encryption, - // in order to allow correct decryption even when the global - // delegate changes - private PbCryptDelegate m_fExtCrypt = null; - - private enum PbMemProt - { - None = 0, - ProtectedMemory, // DPAPI on Windows - ChaCha20, - ExtCrypt - } - - // ProtectedMemory is supported only on Windows 2000 SP3 and higher -#if !KeePassLibSD - private static bool? g_obProtectedMemorySupported = null; -#endif - private static bool ProtectedMemorySupported - { - get - { -#if KeePassLibSD - return false; -#else - bool? ob = g_obProtectedMemorySupported; - if(ob.HasValue) return ob.Value; - - // Mono does not implement any encryption for ProtectedMemory - // on Linux (Mono uses DPAPI on Windows); - // https://sourceforge.net/p/keepass/feature-requests/1907/ - if(NativeLib.IsUnix()) - { - g_obProtectedMemorySupported = false; - return false; - } - - ob = false; - try // Test whether ProtectedMemory is supported - { - // BlockSize * 3 in order to test encryption for multiple - // blocks, but not introduce a power of 2 as factor - byte[] pb = new byte[ProtectedBinary.BlockSize * 3]; - for(int i = 0; i < pb.Length; ++i) pb[i] = (byte)i; - - ProtectedMemory.Protect(pb, MemoryProtectionScope.SameProcess); - - for(int i = 0; i < pb.Length; ++i) - { - if(pb[i] != (byte)i) { ob = true; break; } - } - } - catch(Exception) { } // Windows 98 / ME - - g_obProtectedMemorySupported = ob; - return ob.Value; -#endif - } - } - - private static long g_lCurID = 0; - private long m_lID; - - private byte[] m_pbData; // Never null - - // The real length of the data; this value can be different from - // m_pbData.Length, as the length of m_pbData always is a multiple - // of BlockSize (required for ProtectedMemory) - private uint m_uDataLen; - - private bool m_bProtected; // Protection requested by the caller - - private PbMemProt m_mp = PbMemProt.None; // Actual protection - - private readonly object m_objSync = new object(); - - private static byte[] g_pbKey32 = null; - - /// - /// A flag specifying whether the ProtectedBinary object has - /// turned on memory protection or not. - /// - public bool IsProtected - { - get { return m_bProtected; } - } - - /// - /// Length of the stored data. - /// - public uint Length - { - get { return m_uDataLen; } - } - - /// - /// Construct a new, empty protected binary data object. - /// Protection is disabled. - /// - public ProtectedBinary() - { - Init(false, MemUtil.EmptyByteArray, 0, 0); - } - - /// - /// Construct a new protected binary data object. - /// - /// If this paremeter is true, - /// the data will be encrypted in memory. If it is false, the - /// data is stored in plain-text in the process memory. - /// Value of the protected object. - /// The input parameter is not modified and - /// ProtectedBinary doesn't take ownership of the data, - /// i.e. the caller is responsible for clearing it. - public ProtectedBinary(bool bEnableProtection, byte[] pbData) - { - if(pbData == null) throw new ArgumentNullException("pbData"); // For .Length - - Init(bEnableProtection, pbData, 0, pbData.Length); - } - - /// - /// Construct a new protected binary data object. - /// - /// If this paremeter is true, - /// the data will be encrypted in memory. If it is false, the - /// data is stored in plain-text in the process memory. - /// Value of the protected object. - /// The input parameter is not modified and - /// ProtectedBinary doesn't take ownership of the data, - /// i.e. the caller is responsible for clearing it. - /// Offset for . - /// Size for . - public ProtectedBinary(bool bEnableProtection, byte[] pbData, - int iOffset, int cbSize) - { - Init(bEnableProtection, pbData, iOffset, cbSize); - } - - /// - /// Construct a new protected binary data object. Copy the data from - /// a XorredBuffer object. - /// - /// Enable protection or not. - /// XorredBuffer object used to - /// initialize the ProtectedBinary object. - public ProtectedBinary(bool bEnableProtection, XorredBuffer xbProtected) - { - Debug.Assert(xbProtected != null); - if(xbProtected == null) throw new ArgumentNullException("xbProtected"); - - byte[] pb = xbProtected.ReadPlainText(); - try { Init(bEnableProtection, pb, 0, pb.Length); } - finally { if(bEnableProtection) MemUtil.ZeroByteArray(pb); } - } - - private void Init(bool bEnableProtection, byte[] pbData, int iOffset, - int cbSize) - { - if(pbData == null) throw new ArgumentNullException("pbData"); - if(iOffset < 0) throw new ArgumentOutOfRangeException("iOffset"); - if(cbSize < 0) throw new ArgumentOutOfRangeException("cbSize"); - if(iOffset > (pbData.Length - cbSize)) - throw new ArgumentOutOfRangeException("cbSize"); - -#if KeePassLibSD - m_lID = ++g_lCurID; -#else - m_lID = Interlocked.Increment(ref g_lCurID); -#endif - - m_bProtected = bEnableProtection; - m_uDataLen = (uint)cbSize; - - const int bs = ProtectedBinary.BlockSize; - int nBlocks = cbSize / bs; - if((nBlocks * bs) < cbSize) ++nBlocks; - Debug.Assert((nBlocks * bs) >= cbSize); - - m_pbData = new byte[nBlocks * bs]; - Array.Copy(pbData, iOffset, m_pbData, 0, cbSize); - - Encrypt(); - } - - private void Encrypt() - { - Debug.Assert(m_mp == PbMemProt.None); - - // Nothing to do if caller didn't request protection - if(!m_bProtected) return; - - // ProtectedMemory.Protect throws for data size == 0 - if(m_pbData.Length == 0) return; - - PbCryptDelegate f = g_fExtCrypt; - if(f != null) - { - f(m_pbData, PbCryptFlags.Encrypt, m_lID); - - m_fExtCrypt = f; - m_mp = PbMemProt.ExtCrypt; - return; - } - - if(ProtectedBinary.ProtectedMemorySupported) - { - ProtectedMemory.Protect(m_pbData, MemoryProtectionScope.SameProcess); - - m_mp = PbMemProt.ProtectedMemory; - return; - } - - byte[] pbKey32 = g_pbKey32; - if(pbKey32 == null) - { - pbKey32 = CryptoRandom.Instance.GetRandomBytes(32); - - byte[] pbUpd = Interlocked.Exchange(ref g_pbKey32, pbKey32); - if(pbUpd != null) pbKey32 = pbUpd; - } - - byte[] pbIV = new byte[12]; - MemUtil.UInt64ToBytesEx((ulong)m_lID, pbIV, 4); - using(ChaCha20Cipher c = new ChaCha20Cipher(pbKey32, pbIV, true)) - { - c.Encrypt(m_pbData, 0, m_pbData.Length); - } - m_mp = PbMemProt.ChaCha20; - } - - private void Decrypt() - { - if(m_pbData.Length == 0) return; - - if(m_mp == PbMemProt.ProtectedMemory) - ProtectedMemory.Unprotect(m_pbData, MemoryProtectionScope.SameProcess); - else if(m_mp == PbMemProt.ChaCha20) - { - byte[] pbIV = new byte[12]; - MemUtil.UInt64ToBytesEx((ulong)m_lID, pbIV, 4); - using(ChaCha20Cipher c = new ChaCha20Cipher(g_pbKey32, pbIV, true)) - { - c.Decrypt(m_pbData, 0, m_pbData.Length); - } - } - else if(m_mp == PbMemProt.ExtCrypt) - m_fExtCrypt(m_pbData, PbCryptFlags.Decrypt, m_lID); - else { Debug.Assert(m_mp == PbMemProt.None); } - - m_mp = PbMemProt.None; - } - - /// - /// Get a copy of the protected data as a byte array. - /// Please note that the returned byte array is not protected and - /// can therefore been read by any other application. - /// Make sure that your clear it properly after usage. - /// - /// Unprotected byte array. This is always a copy of the internal - /// protected data and can therefore be cleared safely. - public byte[] ReadData() - { - if(m_uDataLen == 0) return MemUtil.EmptyByteArray; - - byte[] pbReturn = new byte[m_uDataLen]; - - lock(m_objSync) - { - Decrypt(); - Array.Copy(m_pbData, pbReturn, (int)m_uDataLen); - Encrypt(); - } - - return pbReturn; - } - - /// - /// Read the protected data and return it protected with a sequence - /// of bytes generated by a random stream. - /// - /// Random number source. - public byte[] ReadXorredData(CryptoRandomStream crsRandomSource) - { - Debug.Assert(crsRandomSource != null); - if(crsRandomSource == null) throw new ArgumentNullException("crsRandomSource"); - - byte[] pbData = ReadData(); - uint uLen = (uint)pbData.Length; - - byte[] randomPad = crsRandomSource.GetRandomBytes(uLen); - Debug.Assert(randomPad.Length == pbData.Length); - - for(uint i = 0; i < uLen; ++i) - pbData[i] ^= randomPad[i]; - - return pbData; - } - - private int? m_hash = null; - public override int GetHashCode() - { - if(m_hash.HasValue) return m_hash.Value; - - int h = (m_bProtected ? 0x7B11D289 : 0); - - byte[] pb = ReadData(); - unchecked - { - for(int i = 0; i < pb.Length; ++i) - h = (h << 3) + h + (int)pb[i]; - } - if(m_bProtected) MemUtil.ZeroByteArray(pb); - - m_hash = h; - return h; - } - - public override bool Equals(object obj) - { - return this.Equals(obj as ProtectedBinary, true); - } - - public bool Equals(ProtectedBinary other) - { - return this.Equals(other, true); - } - - public bool Equals(ProtectedBinary other, bool bCheckProtEqual) - { - if(other == null) return false; // No assert - if(object.ReferenceEquals(this, other)) return true; // Perf. opt. - - if(bCheckProtEqual && (m_bProtected != other.m_bProtected)) - return false; - - if(m_uDataLen != other.m_uDataLen) return false; - - byte[] pbL = ReadData(), pbR = null; - bool bEq; - try - { - pbR = other.ReadData(); - bEq = MemUtil.ArraysEqual(pbL, pbR); - } - finally - { - if(m_bProtected) MemUtil.ZeroByteArray(pbL); - if(other.m_bProtected && (pbR != null)) MemUtil.ZeroByteArray(pbR); - } - - return bEq; - } - } -} diff --git a/ModernKeePassLib/Security/ProtectedString.cs b/ModernKeePassLib/Security/ProtectedString.cs deleted file mode 100644 index eb62841..0000000 --- a/ModernKeePassLib/Security/ProtectedString.cs +++ /dev/null @@ -1,436 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Diagnostics; -using System.Text; - -using ModernKeePassLib.Cryptography; -using ModernKeePassLib.Utility; - -#if KeePassLibSD -using KeePassLibSD; -#endif - -// SecureString objects are limited to 65536 characters, don't use - -namespace ModernKeePassLib.Security -{ - /// - /// A string that is protected in process memory. - /// ProtectedString objects are immutable and thread-safe. - /// -#if (DEBUG && !KeePassLibSD) - [DebuggerDisplay("{ReadString()}")] -#endif - public sealed class ProtectedString - { - // Exactly one of the following will be non-null - private ProtectedBinary m_pbUtf8 = null; - private string m_strPlainText = null; - - private bool m_bIsProtected; - - private static readonly ProtectedString m_psEmpty = new ProtectedString(); - /// - /// Get an empty ProtectedString object, without protection. - /// - public static ProtectedString Empty - { - get { return m_psEmpty; } - } - - private static readonly ProtectedString m_psEmptyEx = new ProtectedString( - true, new byte[0]); - /// - /// Get an empty ProtectedString object, with protection turned on. - /// - public static ProtectedString EmptyEx - { - get { return m_psEmptyEx; } - } - - /// - /// A flag specifying whether the ProtectedString object - /// has turned on memory protection or not. - /// - public bool IsProtected - { - get { return m_bIsProtected; } - } - - public bool IsEmpty - { - get - { - ProtectedBinary p = m_pbUtf8; // Local ref for thread-safety - if(p != null) return (p.Length == 0); - - Debug.Assert(m_strPlainText != null); - return (m_strPlainText.Length == 0); - } - } - - private int m_nCachedLength = -1; - /// - /// Length of the protected string, in characters. - /// - public int Length - { - get - { - if(m_nCachedLength >= 0) return m_nCachedLength; - - ProtectedBinary p = m_pbUtf8; // Local ref for thread-safety - if(p != null) - { - byte[] pbPlain = p.ReadData(); - try { m_nCachedLength = StrUtil.Utf8.GetCharCount(pbPlain); } - finally { MemUtil.ZeroByteArray(pbPlain); } - } - else - { - Debug.Assert(m_strPlainText != null); - m_nCachedLength = m_strPlainText.Length; - } - - return m_nCachedLength; - } - } - - /// - /// Construct a new protected string object. Protection is - /// disabled. - /// - public ProtectedString() - { - Init(false, string.Empty); - } - - /// - /// Construct a new protected string. The string is initialized - /// to the value supplied in the parameters. - /// - /// If this parameter is true, - /// the string will be protected in memory (encrypted). If it - /// is false, the string will be stored as plain-text. - /// The initial string value. - public ProtectedString(bool bEnableProtection, string strValue) - { - Init(bEnableProtection, strValue); - } - - /// - /// Construct a new protected string. The string is initialized - /// to the value supplied in the parameters (UTF-8 encoded string). - /// - /// If this parameter is true, - /// the string will be protected in memory (encrypted). If it - /// is false, the string will be stored as plain-text. - /// The initial string value, encoded as - /// UTF-8 byte array. This parameter won't be modified; the caller - /// is responsible for clearing it. - public ProtectedString(bool bEnableProtection, byte[] vUtf8Value) - { - Init(bEnableProtection, vUtf8Value); - } - - /// - /// Construct a new protected string. The string is initialized - /// to the value passed in the XorredBuffer object. - /// - /// Enable protection or not. - /// XorredBuffer object containing the - /// string in UTF-8 representation. The UTF-8 string must not - /// be null-terminated. - public ProtectedString(bool bEnableProtection, XorredBuffer xbProtected) - { - Debug.Assert(xbProtected != null); - if(xbProtected == null) throw new ArgumentNullException("xbProtected"); - - byte[] pb = xbProtected.ReadPlainText(); - try { Init(bEnableProtection, pb); } - finally { if(bEnableProtection) MemUtil.ZeroByteArray(pb); } - } - - private void Init(bool bEnableProtection, string str) - { - if(str == null) throw new ArgumentNullException("str"); - - m_bIsProtected = bEnableProtection; - - // As the string already is in memory and immutable, - // protection would be useless - m_strPlainText = str; - } - - private void Init(bool bEnableProtection, byte[] pbUtf8) - { - if(pbUtf8 == null) throw new ArgumentNullException("pbUtf8"); - - m_bIsProtected = bEnableProtection; - - if(bEnableProtection) - m_pbUtf8 = new ProtectedBinary(true, pbUtf8); - else - m_strPlainText = StrUtil.Utf8.GetString(pbUtf8, 0, pbUtf8.Length); - } - - /// - /// Convert the protected string to a standard string object. - /// Be careful with this function, as the returned string object - /// isn't protected anymore and stored in plain-text in the - /// process memory. - /// - /// Plain-text string. Is never null. - public string ReadString() - { - if(m_strPlainText != null) return m_strPlainText; - - byte[] pb = ReadUtf8(); - string str = ((pb.Length == 0) ? string.Empty : - StrUtil.Utf8.GetString(pb, 0, pb.Length)); - // No need to clear pb - - // As the text is now visible in process memory anyway, - // there's no need to protect it anymore (strings are - // immutable and thus cannot be overwritten) - m_strPlainText = str; - m_pbUtf8 = null; // Thread-safe order - - return str; - } - - /// - /// Read out the string and return it as a char array. - /// The returned array is not protected and should be cleared by - /// the caller. - /// - /// Plain-text char array. - public char[] ReadChars() - { - if(m_strPlainText != null) return m_strPlainText.ToCharArray(); - - byte[] pb = ReadUtf8(); - char[] v; - try { v = StrUtil.Utf8.GetChars(pb); } - finally { MemUtil.ZeroByteArray(pb); } - return v; - } - - /// - /// Read out the string and return a byte array that contains the - /// string encoded using UTF-8. - /// The returned array is not protected and should be cleared by - /// the caller. - /// - /// Plain-text UTF-8 byte array. - public byte[] ReadUtf8() - { - ProtectedBinary p = m_pbUtf8; // Local ref for thread-safety - if(p != null) return p.ReadData(); - - return StrUtil.Utf8.GetBytes(m_strPlainText); - } - - /// - /// Read the protected string and return it protected with a sequence - /// of bytes generated by a random stream. - /// - /// Random number source. - /// Protected string. - public byte[] ReadXorredString(CryptoRandomStream crsRandomSource) - { - if(crsRandomSource == null) { Debug.Assert(false); throw new ArgumentNullException("crsRandomSource"); } - - byte[] pbData = ReadUtf8(); - uint uLen = (uint)pbData.Length; - - byte[] randomPad = crsRandomSource.GetRandomBytes(uLen); - Debug.Assert(randomPad.Length == pbData.Length); - - for(uint i = 0; i < uLen; ++i) - pbData[i] ^= randomPad[i]; - - return pbData; - } - - public ProtectedString WithProtection(bool bProtect) - { - if(bProtect == m_bIsProtected) return this; - - byte[] pb = ReadUtf8(); - - // No need to clear pb; either the current or the new object is unprotected - return new ProtectedString(bProtect, pb); - } - - public bool Equals(ProtectedString ps, bool bCheckProtEqual) - { - if(ps == null) throw new ArgumentNullException("ps"); - if(object.ReferenceEquals(this, ps)) return true; // Perf. opt. - - bool bPA = m_bIsProtected, bPB = ps.m_bIsProtected; - if(bCheckProtEqual && (bPA != bPB)) return false; - if(!bPA && !bPB) return (ReadString() == ps.ReadString()); - - byte[] pbA = ReadUtf8(), pbB = null; - bool bEq; - try - { - pbB = ps.ReadUtf8(); - bEq = MemUtil.ArraysEqual(pbA, pbB); - } - finally - { - if(bPA) MemUtil.ZeroByteArray(pbA); - if(bPB && (pbB != null)) MemUtil.ZeroByteArray(pbB); - } - - return bEq; - } - - public ProtectedString Insert(int iStart, string strInsert) - { - if(iStart < 0) throw new ArgumentOutOfRangeException("iStart"); - if(strInsert == null) throw new ArgumentNullException("strInsert"); - if(strInsert.Length == 0) return this; - - if(!m_bIsProtected) - return new ProtectedString(false, ReadString().Insert( - iStart, strInsert)); - - UTF8Encoding utf8 = StrUtil.Utf8; - char[] v = ReadChars(), vNew = null; - byte[] pbNew = null; - ProtectedString ps; - - try - { - if(iStart > v.Length) - throw new ArgumentOutOfRangeException("iStart"); - - char[] vIns = strInsert.ToCharArray(); - - vNew = new char[v.Length + vIns.Length]; - Array.Copy(v, 0, vNew, 0, iStart); - Array.Copy(vIns, 0, vNew, iStart, vIns.Length); - Array.Copy(v, iStart, vNew, iStart + vIns.Length, - v.Length - iStart); - - pbNew = utf8.GetBytes(vNew); - ps = new ProtectedString(true, pbNew); - - Debug.Assert(utf8.GetString(pbNew, 0, pbNew.Length) == - ReadString().Insert(iStart, strInsert)); - } - finally - { - MemUtil.ZeroArray(v); - if(vNew != null) MemUtil.ZeroArray(vNew); - if(pbNew != null) MemUtil.ZeroByteArray(pbNew); - } - - return ps; - } - - public ProtectedString Remove(int iStart, int nCount) - { - if(iStart < 0) throw new ArgumentOutOfRangeException("iStart"); - if(nCount < 0) throw new ArgumentOutOfRangeException("nCount"); - if(nCount == 0) return this; - - if(!m_bIsProtected) - return new ProtectedString(false, ReadString().Remove( - iStart, nCount)); - - UTF8Encoding utf8 = StrUtil.Utf8; - char[] v = ReadChars(), vNew = null; - byte[] pbNew = null; - ProtectedString ps; - - try - { - if((iStart + nCount) > v.Length) - throw new ArgumentException("(iStart + nCount) > v.Length"); - - vNew = new char[v.Length - nCount]; - Array.Copy(v, 0, vNew, 0, iStart); - Array.Copy(v, iStart + nCount, vNew, iStart, v.Length - - (iStart + nCount)); - - pbNew = utf8.GetBytes(vNew); - ps = new ProtectedString(true, pbNew); - - Debug.Assert(utf8.GetString(pbNew, 0, pbNew.Length) == - ReadString().Remove(iStart, nCount)); - } - finally - { - MemUtil.ZeroArray(v); - if(vNew != null) MemUtil.ZeroArray(vNew); - if(pbNew != null) MemUtil.ZeroByteArray(pbNew); - } - - return ps; - } - - public static ProtectedString operator +(ProtectedString a, ProtectedString b) - { - if(a == null) throw new ArgumentNullException("a"); - if(b == null) throw new ArgumentNullException("b"); - - if(b.IsEmpty) return a; - if(a.IsEmpty) return b; - if(!a.IsProtected && !b.IsProtected) - return new ProtectedString(false, a.ReadString() + b.ReadString()); - - char[] vA = a.ReadChars(), vB = null, vNew = null; - byte[] pbNew = null; - ProtectedString ps; - - try - { - vB = b.ReadChars(); - - vNew = new char[vA.Length + vB.Length]; - Array.Copy(vA, vNew, vA.Length); - Array.Copy(vB, 0, vNew, vA.Length, vB.Length); - - pbNew = StrUtil.Utf8.GetBytes(vNew); - ps = new ProtectedString(true, pbNew); - } - finally - { - MemUtil.ZeroArray(vA); - if(vB != null) MemUtil.ZeroArray(vB); - if(vNew != null) MemUtil.ZeroArray(vNew); - if(pbNew != null) MemUtil.ZeroByteArray(pbNew); - } - - return ps; - } - - public static ProtectedString operator +(ProtectedString a, string b) - { - ProtectedString psB = new ProtectedString(false, b); - return (a + psB); - } - } -} diff --git a/ModernKeePassLib/Security/XorredBuffer.cs b/ModernKeePassLib/Security/XorredBuffer.cs deleted file mode 100644 index ec9cf64..0000000 --- a/ModernKeePassLib/Security/XorredBuffer.cs +++ /dev/null @@ -1,116 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Diagnostics; - -namespace ModernKeePassLib.Security -{ - /// - /// Represents an object that is encrypted using a XOR pad until - /// it is read. XorredBuffer objects are immutable and - /// thread-safe. - /// - public sealed class XorredBuffer - { - private byte[] m_pbData; // Never null - private byte[] m_pbXorPad; // Always valid for m_pbData - - /// - /// Length of the protected data in bytes. - /// - public uint Length - { - get { return (uint)m_pbData.Length; } - } - - /// - /// Construct a new XOR-protected object using a protected byte array - /// and a XOR pad that decrypts the protected data. The - /// byte array must have the same size - /// as the byte array. - /// The XorredBuffer object takes ownership of the two byte - /// arrays, i.e. the caller must not use or modify them afterwards. - /// - /// Protected data (XOR pad applied). - /// XOR pad that can be used to decrypt the - /// parameter. - /// Thrown if one of the input - /// parameters is null. - /// Thrown if the byte arrays are - /// of different size. - public XorredBuffer(byte[] pbProtectedData, byte[] pbXorPad) - { - if(pbProtectedData == null) { Debug.Assert(false); throw new ArgumentNullException("pbProtectedData"); } - if(pbXorPad == null) { Debug.Assert(false); throw new ArgumentNullException("pbXorPad"); } - - Debug.Assert(pbProtectedData.Length == pbXorPad.Length); - if(pbProtectedData.Length != pbXorPad.Length) throw new ArgumentException(); - - m_pbData = pbProtectedData; - m_pbXorPad = pbXorPad; - } - - /// - /// Get a copy of the plain-text. The caller is responsible - /// for clearing the byte array safely after using it. - /// - /// Unprotected plain-text byte array. - public byte[] ReadPlainText() - { - byte[] pbPlain = new byte[m_pbData.Length]; - - for(int i = 0; i < pbPlain.Length; ++i) - pbPlain[i] = (byte)(m_pbData[i] ^ m_pbXorPad[i]); - - return pbPlain; - } - - /* public bool EqualsValue(XorredBuffer xb) - { - if(xb == null) { Debug.Assert(false); throw new ArgumentNullException("xb"); } - - if(xb.m_pbData.Length != m_pbData.Length) return false; - - for(int i = 0; i < m_pbData.Length; ++i) - { - byte bt1 = (byte)(m_pbData[i] ^ m_pbXorPad[i]); - byte bt2 = (byte)(xb.m_pbData[i] ^ xb.m_pbXorPad[i]); - - if(bt1 != bt2) return false; - } - - return true; - } - - public bool EqualsValue(byte[] pb) - { - if(pb == null) { Debug.Assert(false); throw new ArgumentNullException("pb"); } - - if(pb.Length != m_pbData.Length) return false; - - for(int i = 0; i < m_pbData.Length; ++i) - { - if((byte)(m_pbData[i] ^ m_pbXorPad[i]) != pb[i]) return false; - } - - return true; - } */ - } -} diff --git a/ModernKeePassLib/Serialization/BinaryReaderEx.cs b/ModernKeePassLib/Serialization/BinaryReaderEx.cs deleted file mode 100644 index 4445ef4..0000000 --- a/ModernKeePassLib/Serialization/BinaryReaderEx.cs +++ /dev/null @@ -1,92 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; - -using ModernKeePassLib.Utility; - -namespace ModernKeePassLib.Serialization -{ - public sealed class BinaryReaderEx - { - private Stream m_s; - // private Encoding m_enc; // See constructor - - private string m_strReadExcp; // May be null - public string ReadExceptionText - { - get { return m_strReadExcp; } - set { m_strReadExcp = value; } - } - - private Stream m_sCopyTo = null; - /// - /// If this property is set to a non-null stream, all data that - /// is read from the input stream is automatically written to - /// the copy stream (before returning the read data). - /// - public Stream CopyDataTo - { - get { return m_sCopyTo; } - set { m_sCopyTo = value; } - } - - public BinaryReaderEx(Stream input, Encoding encoding, - string strReadExceptionText) - { - if(input == null) throw new ArgumentNullException("input"); - - m_s = input; - // m_enc = encoding; // Not used yet - m_strReadExcp = strReadExceptionText; - } - - public byte[] ReadBytes(int nCount) - { - try - { - byte[] pb = MemUtil.Read(m_s, nCount); - if((pb == null) || (pb.Length != nCount)) - { - if(!string.IsNullOrEmpty(m_strReadExcp)) - throw new EndOfStreamException(m_strReadExcp); - else throw new EndOfStreamException(); - } - - if(m_sCopyTo != null) m_sCopyTo.Write(pb, 0, pb.Length); - return pb; - } - catch(Exception) - { - if(!string.IsNullOrEmpty(m_strReadExcp)) - throw new IOException(m_strReadExcp); - else throw; - } - } - - public byte ReadByte() - { - byte[] pb = ReadBytes(1); - return pb[0]; - } - } -} diff --git a/ModernKeePassLib/Serialization/FileLock.cs b/ModernKeePassLib/Serialization/FileLock.cs deleted file mode 100644 index e476acb..0000000 --- a/ModernKeePassLib/Serialization/FileLock.cs +++ /dev/null @@ -1,274 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Text; -using System.Threading; -#if ModernKeePassLib -using System.Runtime.InteropServices.WindowsRuntime; -using System.Threading.Tasks; -using Windows.Storage.Streams; -#endif -using ModernKeePassLib.Cryptography; -using ModernKeePassLib.Resources; -using ModernKeePassLib.Utility; - -namespace ModernKeePassLib.Serialization -{ - public sealed class FileLockException : Exception - { - private readonly string m_strMsg; - - public override string Message - { - get { return m_strMsg; } - } - - public FileLockException(string strBaseFile, string strUser) - { - StringBuilder sb = new StringBuilder(); - - if(!string.IsNullOrEmpty(strBaseFile)) - { - sb.Append(strBaseFile); - sb.Append(Environment.NewLine + Environment.NewLine); - } - - sb.Append(KLRes.FileLockedWrite); - sb.Append(Environment.NewLine); - - if(!string.IsNullOrEmpty(strUser)) sb.Append(strUser); - else sb.Append("?"); - - sb.Append(Environment.NewLine + Environment.NewLine); - sb.Append(KLRes.TryAgainSecs); - - m_strMsg = sb.ToString(); - } - } - - public sealed class FileLock : IDisposable - { - private const string LockFileExt = ".lock"; - private const string LockFileHeader = "KeePass Lock File"; - - private IOConnectionInfo m_iocLockFile; - - private sealed class LockFileInfo - { - public readonly string ID; - public readonly DateTime Time; - public readonly string UserName; - public readonly string Machine; - public readonly string Domain; - - private LockFileInfo(string strID, string strTime, string strUserName, - string strMachine, string strDomain) - { - this.ID = (strID ?? string.Empty).Trim(); - - DateTime dt; - if(TimeUtil.TryDeserializeUtc(strTime.Trim(), out dt)) - this.Time = dt; - else - { - Debug.Assert(false); - this.Time = DateTime.UtcNow; - } - - this.UserName = (strUserName ?? string.Empty).Trim(); - this.Machine = (strMachine ?? string.Empty).Trim(); - this.Domain = (strDomain ?? string.Empty).Trim(); - - if(this.Domain.Equals(this.Machine, StrUtil.CaseIgnoreCmp)) - this.Domain = string.Empty; - } - - public string GetOwner() - { - StringBuilder sb = new StringBuilder(); - sb.Append((this.UserName.Length > 0) ? this.UserName : "?"); - - bool bMachine = (this.Machine.Length > 0); - bool bDomain = (this.Domain.Length > 0); - if(bMachine || bDomain) - { - sb.Append(" ("); - sb.Append(this.Machine); - if(bMachine && bDomain) sb.Append(" @ "); - sb.Append(this.Domain); - sb.Append(")"); - } - - return sb.ToString(); - } - - public static LockFileInfo Load(IOConnectionInfo iocLockFile) - { - Stream s = null; - try - { - s = IOConnection.OpenRead(iocLockFile); - if(s == null) return null; - - string str = null; - using(StreamReader sr = new StreamReader(s, StrUtil.Utf8)) - { - str = sr.ReadToEnd(); - } - if(str == null) { Debug.Assert(false); return null; } - - str = StrUtil.NormalizeNewLines(str, false); - string[] v = str.Split('\n'); - if((v == null) || (v.Length < 6)) { Debug.Assert(false); return null; } - - if(!v[0].StartsWith(LockFileHeader)) { Debug.Assert(false); return null; } - return new LockFileInfo(v[1], v[2], v[3], v[4], v[5]); - } - catch(FileNotFoundException) { } - catch(Exception) { Debug.Assert(false); } - finally { if(s != null) s.Dispose(); } - - return null; - } - - // Throws on error - public static LockFileInfo Create(IOConnectionInfo iocLockFile) - { - LockFileInfo lfi; - Stream s = null; - try - { - byte[] pbID = CryptoRandom.Instance.GetRandomBytes(16); - string strTime = TimeUtil.SerializeUtc(DateTime.UtcNow); - - lfi = new LockFileInfo(Convert.ToBase64String(pbID), strTime, -#if KeePassUAP - EnvironmentExt.UserName, EnvironmentExt.MachineName, - EnvironmentExt.UserDomainName); -#elif ModernKeePassLib|| KeePassLibSD - string.Empty, string.Empty, string.Empty); -#else - Environment.UserName, Environment.MachineName, - Environment.UserDomainName); -#endif - - StringBuilder sb = new StringBuilder(); -#if !KeePassLibSD - sb.AppendLine(LockFileHeader); - sb.AppendLine(lfi.ID); - sb.AppendLine(strTime); - sb.AppendLine(lfi.UserName); - sb.AppendLine(lfi.Machine); - sb.AppendLine(lfi.Domain); -#else - sb.Append(LockFileHeader + Environment.NewLine); - sb.Append(lfi.ID + Environment.NewLine); - sb.Append(strTime + Environment.NewLine); - sb.Append(lfi.UserName + Environment.NewLine); - sb.Append(lfi.Machine + Environment.NewLine); - sb.Append(lfi.Domain + Environment.NewLine); -#endif - - byte[] pbFile = StrUtil.Utf8.GetBytes(sb.ToString()); - - s = IOConnection.OpenWrite(iocLockFile); - if(s == null) throw new IOException(iocLockFile.GetDisplayName()); - s.Write(pbFile, 0, pbFile.Length); - } - finally { if(s != null) s.Dispose(); } - - return lfi; - } - } - - public FileLock(IOConnectionInfo iocBaseFile) - { - if(iocBaseFile == null) throw new ArgumentNullException("strBaseFile"); - - m_iocLockFile = iocBaseFile.CloneDeep(); - m_iocLockFile.Path += LockFileExt; - - LockFileInfo lfiEx = LockFileInfo.Load(m_iocLockFile); - if(lfiEx != null) - { - m_iocLockFile = null; // Otherwise Dispose deletes the existing one - throw new FileLockException(iocBaseFile.GetDisplayName(), - lfiEx.GetOwner()); - } - - LockFileInfo.Create(m_iocLockFile); - } - - ~FileLock() - { - Dispose(false); - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - private void Dispose(bool bDisposing) - { - if(m_iocLockFile == null) return; - - bool bFileDeleted = false; - for(int r = 0; r < 5; ++r) - { - // if(!OwnLockFile()) { bFileDeleted = true; break; } - - try - { - IOConnection.DeleteFile(m_iocLockFile); - bFileDeleted = true; - } - catch(Exception) { Debug.Assert(false); } - - if(bFileDeleted) break; - -#if ModernKeePassLib - if(bDisposing) - Task.Delay(50).Wait(); -#else - if(bDisposing) Thread.Sleep(50); -#endif - } - - // if(bDisposing && !bFileDeleted) - // IOConnection.DeleteFile(m_iocLockFile); // Possibly with exception - - m_iocLockFile = null; - } - - // private bool OwnLockFile() - // { - // if(m_iocLockFile == null) { Debug.Assert(false); return false; } - // if(m_strLockID == null) { Debug.Assert(false); return false; } - // LockFileInfo lfi = LockFileInfo.Load(m_iocLockFile); - // if(lfi == null) return false; - // return m_strLockID.Equals(lfi.ID); - // } - } -} diff --git a/ModernKeePassLib/Serialization/FileTransactionEx.cs b/ModernKeePassLib/Serialization/FileTransactionEx.cs deleted file mode 100644 index 9cd871f..0000000 --- a/ModernKeePassLib/Serialization/FileTransactionEx.cs +++ /dev/null @@ -1,473 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Diagnostics; -using System.IO; -using System.Text; - -#if (!ModernKeePassLib && !KeePassLibSD && !KeePassRT) -using System.Security.AccessControl; -#endif - -using ModernKeePassLib.Native; -using ModernKeePassLib.Utility; -using System.Threading.Tasks; -using Windows.Storage; -using Windows.Storage.Streams; -using ModernKeePassLib.Cryptography; -using ModernKeePassLib.Resources; - -namespace ModernKeePassLib.Serialization -{ - public sealed class FileTransactionEx : IDisposable - { - private bool m_bTransacted; - private IOConnectionInfo m_iocBase; // Null means disposed - private IOConnectionInfo m_iocTemp; - private IOConnectionInfo m_iocTxfMidFallback = null; // Null <=> TxF not used - - private bool m_bMadeUnhidden = false; - - private List m_lToDelete = new List(); - - private const string StrTempSuffix = ".tmp"; - private const string StrTxfTempPrefix = PwDefs.ShortProductName + "_TxF_"; - private const string StrTxfTempSuffix = ".tmp"; - - private static Dictionary g_dEnabled = - new Dictionary(StrUtil.CaseIgnoreComparer); - - private static bool g_bExtraSafe = false; - internal static bool ExtraSafe - { - get { return g_bExtraSafe; } - set { g_bExtraSafe = value; } - } - - public FileTransactionEx(IOConnectionInfo iocBaseFile) : - this(iocBaseFile, true) - { - } - - public FileTransactionEx(IOConnectionInfo iocBaseFile, bool bTransacted) - { - if(iocBaseFile == null) throw new ArgumentNullException("iocBaseFile"); - - m_bTransacted = bTransacted; - - m_iocBase = iocBaseFile.CloneDeep(); - if(m_iocBase.IsLocalFile()) - m_iocBase.Path = UrlUtil.GetShortestAbsolutePath(m_iocBase.Path); - - string strPath = m_iocBase.Path; - -#if !ModernKeePassLib - if(m_iocBase.IsLocalFile()) - { - try - { - if(File.Exists(strPath)) - { - // Symbolic links are realized via reparse points; - // https://msdn.microsoft.com/en-us/library/windows/desktop/aa365503.aspx - // https://msdn.microsoft.com/en-us/library/windows/desktop/aa365680.aspx - // https://msdn.microsoft.com/en-us/library/windows/desktop/aa365006.aspx - // Performing a file transaction on a symbolic link - // would delete/replace the symbolic link instead of - // writing to its target - FileAttributes fa = File.GetAttributes(strPath); - if((long)(fa & FileAttributes.ReparsePoint) != 0) - m_bTransacted = false; - } - else - { - // If the base and the temporary file are in different - // folders and the base file doesn't exist (i.e. we can't - // backup the ACL), a transaction would cause the new file - // to have the default ACL of the temporary folder instead - // of the one of the base folder; therefore, we don't use - // a transaction when the base file doesn't exist (this - // also results in other applications monitoring the folder - // to see one file creation only) - m_bTransacted = false; - } - } - catch(Exception) { Debug.Assert(false); } - } -#endif - -#if !ModernKeePassLib - // Prevent transactions for FTP URLs under .NET 4.0 in order to - // avoid/workaround .NET bug 621450: - // https://connect.microsoft.com/VisualStudio/feedback/details/621450/problem-renaming-file-on-ftp-server-using-ftpwebrequest-in-net-framework-4-0-vs2010-only - if(strPath.StartsWith("ftp:", StrUtil.CaseIgnoreCmp) && - (Environment.Version.Major >= 4) && !NativeLib.IsUnix()) - m_bTransacted = false; -#endif - - foreach(KeyValuePair kvp in g_dEnabled) - { - if(strPath.StartsWith(kvp.Key, StrUtil.CaseIgnoreCmp)) - { - m_bTransacted = kvp.Value; - break; - } - } - - if(m_bTransacted) - { - m_iocTemp = m_iocBase.CloneDeep(); - m_iocTemp.Path += StrTempSuffix; - - TxfPrepare(); // Adjusts m_iocTemp - } - else m_iocTemp = m_iocBase; - } - - ~FileTransactionEx() - { - Dispose(false); - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - private void Dispose(bool bDisposing) - { - m_iocBase = null; - if(!bDisposing) return; - - try - { - foreach(IOConnectionInfo ioc in m_lToDelete) - { - if(IOConnection.FileExists(ioc, false)) - IOConnection.DeleteFile(ioc); - } - - m_lToDelete.Clear(); - } - catch(Exception) { Debug.Assert(false); } - } - - public Stream OpenWrite() - { - if(m_iocBase == null) { Debug.Assert(false); throw new ObjectDisposedException(null); } - - if(!m_bTransacted) m_bMadeUnhidden |= UrlUtil.UnhideFile(m_iocTemp.Path); - - return IOConnection.OpenWrite(m_iocTemp); - } - - public void CommitWrite() - { - if(m_iocBase == null) { Debug.Assert(false); throw new ObjectDisposedException(null); } - - if(!m_bTransacted) - { - if(m_bMadeUnhidden) UrlUtil.HideFile(m_iocTemp.Path, true); - } - else CommitWriteTransaction(); - - m_iocBase = null; // Dispose - } - - private void CommitWriteTransaction() - { - if(g_bExtraSafe) - { - if(!IOConnection.FileExists(m_iocTemp)) - throw new FileNotFoundException(m_iocTemp.Path + - MessageService.NewLine + KLRes.FileSaveFailed); - } - - bool bMadeUnhidden = UrlUtil.UnhideFile(m_iocBase.Path); - -#if !ModernKeePassLib - // 'All' includes 'Audit' (SACL), which requires SeSecurityPrivilege, - // which we usually don't have and therefore get an exception; - // trying to set 'Owner' or 'Group' can result in an - // UnauthorizedAccessException; thus we restore 'Access' (DACL) only - const AccessControlSections acs = AccessControlSections.Access; - -#endif - bool bEfsEncrypted = false; - byte[] pbSec = null; - DateTime? otCreation = null; - - bool bBaseExists = IOConnection.FileExists(m_iocBase); - if(bBaseExists && m_iocBase.IsLocalFile()) - { - // FileAttributes faBase = FileAttributes.Normal; - try - { -#if !ModernKeePassLib - FileAttributes faBase = File.GetAttributes(m_iocBase.Path); - bEfsEncrypted = ((long)(faBase & FileAttributes.Encrypted) != 0); - try { if(bEfsEncrypted) File.Decrypt(m_iocBase.Path); } // For TxF - catch(Exception) { Debug.Assert(false); } -#endif -#if ModernKeePassLib - otCreation = m_iocBase.StorageFile.DateCreated.UtcDateTime; -#else - otCreation = File.GetCreationTimeUtc(m_iocBase.Path); -#endif -#if !ModernKeePassLib - // May throw with Mono - FileSecurity sec = File.GetAccessControl(m_iocBase.Path, acs); - if(sec != null) pbSec = sec.GetSecurityDescriptorBinaryForm(); -#endif - } - catch(Exception) { Debug.Assert(NativeLib.IsUnix()); } - - // if((long)(faBase & FileAttributes.ReadOnly) != 0) - // throw new UnauthorizedAccessException(); - } - - if(!TxfMove()) - { - if(bBaseExists) IOConnection.DeleteFile(m_iocBase); - IOConnection.RenameFile(m_iocTemp, m_iocBase); - } - else { Debug.Assert(pbSec != null); } // TxF success => NTFS => has ACL - - try - { - // If File.GetCreationTimeUtc fails, it may return a - // date with year 1601, and Unix times start in 1970, - // so testing for 1971 should ensure validity; - // https://msdn.microsoft.com/en-us/library/system.io.file.getcreationtimeutc.aspx -#if !ModernKeePassLib - if(otCreation.HasValue && (otCreation.Value.Year >= 1971)) - File.SetCreationTimeUtc(m_iocBase.Path, otCreation.Value); -#endif - -#if !ModernKeePassLib - if(bEfsEncrypted) - { - try { File.Encrypt(m_iocBase.Path); } - catch(Exception) { Debug.Assert(false); } - } - - // File.SetAccessControl(m_iocBase.Path, secPrev); - // Directly calling File.SetAccessControl with the previous - // FileSecurity object does not work; the binary form - // indirection is required; - // https://sourceforge.net/p/keepass/bugs/1738/ - // https://msdn.microsoft.com/en-us/library/system.io.file.setaccesscontrol.aspx - if((pbSec != null) && (pbSec.Length != 0)) - { - FileSecurity sec = new FileSecurity(); - sec.SetSecurityDescriptorBinaryForm(pbSec, acs); - - File.SetAccessControl(m_iocBase.Path, sec); - } -#endif - } - catch (Exception) { Debug.Assert(false); } - - if(bMadeUnhidden) UrlUtil.HideFile(m_iocBase.Path, true); - } - - // For plugins - public static void Configure(string strPrefix, bool? obTransacted) - { - if(string.IsNullOrEmpty(strPrefix)) { Debug.Assert(false); return; } - - if(obTransacted.HasValue) - g_dEnabled[strPrefix] = obTransacted.Value; - else g_dEnabled.Remove(strPrefix); - } - - private static bool TxfIsSupported(char chDriveLetter) - { - if(chDriveLetter == '\0') return false; - -#if ModernKeePassLib - return true; -#else - try - { - string strRoot = (new string(chDriveLetter, 1)) + ":\\"; - - const int cch = NativeMethods.MAX_PATH + 1; - StringBuilder sbName = new StringBuilder(cch + 1); - uint uSerial = 0, cchMaxComp = 0, uFlags = 0; - StringBuilder sbFileSystem = new StringBuilder(cch + 1); - - if(!NativeMethods.GetVolumeInformation(strRoot, sbName, (uint)cch, - ref uSerial, ref cchMaxComp, ref uFlags, sbFileSystem, (uint)cch)) - { - Debug.Assert(false, (new Win32Exception()).Message); - return false; - } - - return ((uFlags & NativeMethods.FILE_SUPPORTS_TRANSACTIONS) != 0); - } - catch(Exception) { Debug.Assert(false); } - return false; -#endif - } - - private void TxfPrepare() - { - try - { - if(NativeLib.IsUnix()) return; - if(!m_iocBase.IsLocalFile()) return; - - string strID = StrUtil.AlphaNumericOnly(Convert.ToBase64String( - CryptoRandom.Instance.GetRandomBytes(16))); - string strTempDir = UrlUtil.GetTempPath(); - // See also ClearOld method - string strTemp = UrlUtil.EnsureTerminatingSeparator(strTempDir, - false) + StrTxfTempPrefix + strID + StrTxfTempSuffix; - - char chB = UrlUtil.GetDriveLetter(m_iocBase.Path); - char chT = UrlUtil.GetDriveLetter(strTemp); - if(!TxfIsSupported(chB)) return; - if((chT != chB) && !TxfIsSupported(chT)) return; - - m_iocTxfMidFallback = m_iocTemp; -#if ModernKeePassLib - var tempFile = ApplicationData.Current.TemporaryFolder.CreateFileAsync(m_iocTemp.Path).GetAwaiter() - .GetResult(); - m_iocTemp = IOConnectionInfo.FromFile(tempFile); -#else - m_iocTemp = IOConnectionInfo.FromPath(strTemp); -#endif - - m_lToDelete.Add(m_iocTemp); - } - catch(Exception) { Debug.Assert(false); m_iocTxfMidFallback = null; } - } - - private bool TxfMove() - { - if(m_iocTxfMidFallback == null) return false; - - if(TxfMoveWithTx()) return true; - - // Move the temporary file onto the base file's drive first, - // such that it cannot happen that both the base file and - // the temporary file are deleted/corrupted -#if ModernKeePassLib - m_iocTemp.StorageFile = ApplicationData.Current.TemporaryFolder.CreateFileAsync(m_iocTemp.Path).GetAwaiter() - .GetResult(); -#else - const uint f = (NativeMethods.MOVEFILE_COPY_ALLOWED | - NativeMethods.MOVEFILE_REPLACE_EXISTING); - bool b = NativeMethods.MoveFileEx(m_iocTemp.Path, m_iocTxfMidFallback.Path, f); - if(b) b = NativeMethods.MoveFileEx(m_iocTxfMidFallback.Path, m_iocBase.Path, f); - if(!b) throw new Win32Exception(); - - Debug.Assert(!File.Exists(m_iocTemp.Path)); - Debug.Assert(!File.Exists(m_iocTxfMidFallback.Path)); -#endif - return true; - } - - private bool TxfMoveWithTx() - { -#if ModernKeePassLib - return true; -#else - IntPtr hTx = new IntPtr((int)NativeMethods.INVALID_HANDLE_VALUE); - Debug.Assert(hTx.ToInt64() == NativeMethods.INVALID_HANDLE_VALUE); - try - { - string strTx = PwDefs.ShortProductName + " TxF - " + - StrUtil.AlphaNumericOnly(Convert.ToBase64String( - CryptoRandom.Instance.GetRandomBytes(16))); - const int mchTx = NativeMethods.MAX_TRANSACTION_DESCRIPTION_LENGTH; - if(strTx.Length >= mchTx) strTx = strTx.Substring(0, mchTx - 1); - - hTx = NativeMethods.CreateTransaction(IntPtr.Zero, - IntPtr.Zero, 0, 0, 0, 0, strTx); - if(hTx.ToInt64() == NativeMethods.INVALID_HANDLE_VALUE) - { - Debug.Assert(false, (new Win32Exception()).Message); - return false; - } - - if(!NativeMethods.MoveFileTransacted(m_iocTemp.Path, m_iocBase.Path, - IntPtr.Zero, IntPtr.Zero, (NativeMethods.MOVEFILE_COPY_ALLOWED | - NativeMethods.MOVEFILE_REPLACE_EXISTING), hTx)) - { - Debug.Assert(false, (new Win32Exception()).Message); - return false; - } - - if(!NativeMethods.CommitTransaction(hTx)) - { - Debug.Assert(false, (new Win32Exception()).Message); - return false; - } - - Debug.Assert(!File.Exists(m_iocTemp.Path)); - return true; - } - catch(Exception) { Debug.Assert(false); } - finally - { - if(hTx.ToInt64() != NativeMethods.INVALID_HANDLE_VALUE) - { - try { if(!NativeMethods.CloseHandle(hTx)) { Debug.Assert(false); } } - catch(Exception) { Debug.Assert(false); } - } - } - return false; -#endif - } - - internal static void ClearOld() - { - try - { -#if ModernKeePassLib - ApplicationData.Current.TemporaryFolder.GetFileAsync(UrlUtil.GetTempPath()).GetAwaiter() - .GetResult().DeleteAsync().GetAwaiter().GetResult(); -#else -// See also TxfPrepare method - DirectoryInfo di = new DirectoryInfo(UrlUtil.GetTempPath()); - List l = UrlUtil.GetFileInfos(di, StrTxfTempPrefix + - "*" + StrTxfTempSuffix, SearchOption.TopDirectoryOnly); - - foreach(FileInfo fi in l) - { - if(fi == null) { Debug.Assert(false); continue; } - if(!fi.Name.StartsWith(StrTxfTempPrefix, StrUtil.CaseIgnoreCmp) || - !fi.Name.EndsWith(StrTxfTempSuffix, StrUtil.CaseIgnoreCmp)) - continue; - - if((DateTime.UtcNow - fi.LastWriteTimeUtc).TotalDays > 1.0) - fi.Delete(); - } -#endif - } - catch(Exception) { Debug.Assert(false); } - } - } -} diff --git a/ModernKeePassLib/Serialization/HashedBlockStream.cs b/ModernKeePassLib/Serialization/HashedBlockStream.cs deleted file mode 100644 index 8454ae9..0000000 --- a/ModernKeePassLib/Serialization/HashedBlockStream.cs +++ /dev/null @@ -1,312 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Diagnostics; -using System.IO; -using System.Text; - -using ModernKeePassLib.Cryptography; -using ModernKeePassLib.Native; -using ModernKeePassLib.Utility; - -#if KeePassLibSD -using KeePassLibSD; -#endif - -namespace ModernKeePassLib.Serialization -{ - public sealed class HashedBlockStream : Stream - { - private const int NbDefaultBufferSize = 1024 * 1024; // 1 MB - - private Stream m_sBaseStream; - private bool m_bWriting; - private bool m_bVerify; - private bool m_bEos = false; - - private BinaryReader m_brInput; - private BinaryWriter m_bwOutput; - - private byte[] m_pbBuffer; - private int m_nBufferPos = 0; - - private uint m_uBlockIndex = 0; - - public override bool CanRead - { - get { return !m_bWriting; } - } - - public override bool CanSeek - { - get { return false; } - } - - public override bool CanWrite - { - get { return m_bWriting; } - } - - public override long Length - { - get { Debug.Assert(false); throw new NotSupportedException(); } - } - - public override long Position - { - get { Debug.Assert(false); throw new NotSupportedException(); } - set { Debug.Assert(false); throw new NotSupportedException(); } - } - - public HashedBlockStream(Stream sBaseStream, bool bWriting) - { - Initialize(sBaseStream, bWriting, 0, true); - } - - public HashedBlockStream(Stream sBaseStream, bool bWriting, int nBufferSize) - { - Initialize(sBaseStream, bWriting, nBufferSize, true); - } - - public HashedBlockStream(Stream sBaseStream, bool bWriting, int nBufferSize, - bool bVerify) - { - Initialize(sBaseStream, bWriting, nBufferSize, bVerify); - } - - private void Initialize(Stream sBaseStream, bool bWriting, int nBufferSize, - bool bVerify) - { - if(sBaseStream == null) throw new ArgumentNullException("sBaseStream"); - if(nBufferSize < 0) throw new ArgumentOutOfRangeException("nBufferSize"); - - if(nBufferSize == 0) nBufferSize = NbDefaultBufferSize; - - m_sBaseStream = sBaseStream; - m_bWriting = bWriting; - m_bVerify = bVerify; - - UTF8Encoding utf8 = StrUtil.Utf8; - if(!m_bWriting) // Reading mode - { - if(!m_sBaseStream.CanRead) - throw new InvalidOperationException(); - - m_brInput = new BinaryReader(sBaseStream, utf8); - - m_pbBuffer = MemUtil.EmptyByteArray; - } - else // Writing mode - { - if(!m_sBaseStream.CanWrite) - throw new InvalidOperationException(); - - m_bwOutput = new BinaryWriter(sBaseStream, utf8); - - m_pbBuffer = new byte[nBufferSize]; - } - } - - protected override void Dispose(bool disposing) - { - if(disposing && (m_sBaseStream != null)) - { - if(!m_bWriting) // Reading mode - { - m_brInput.Dispose(); - m_brInput = null; - } - else // Writing mode - { - if(m_nBufferPos == 0) // No data left in buffer - WriteHashedBlock(); // Write terminating block - else - { - WriteHashedBlock(); // Write remaining buffered data - WriteHashedBlock(); // Write terminating block - } - - Flush(); - m_bwOutput.Dispose(); - m_bwOutput = null; - } - - m_sBaseStream.Dispose(); - m_sBaseStream = null; - } - - base.Dispose(disposing); - } - - public override void Flush() - { - if(m_bWriting) m_bwOutput.Flush(); - } - - public override long Seek(long lOffset, SeekOrigin soOrigin) - { - throw new NotSupportedException(); - } - - public override void SetLength(long lValue) - { - throw new NotSupportedException(); - } - - public override int Read(byte[] pbBuffer, int nOffset, int nCount) - { - if(m_bWriting) throw new InvalidOperationException(); - - int nRemaining = nCount; - while(nRemaining > 0) - { - if(m_nBufferPos == m_pbBuffer.Length) - { - if(ReadHashedBlock() == false) - return (nCount - nRemaining); // Bytes actually read - } - - int nCopy = Math.Min(m_pbBuffer.Length - m_nBufferPos, nRemaining); - - Array.Copy(m_pbBuffer, m_nBufferPos, pbBuffer, nOffset, nCopy); - - nOffset += nCopy; - m_nBufferPos += nCopy; - - nRemaining -= nCopy; - } - - return nCount; - } - - private bool ReadHashedBlock() - { - if(m_bEos) return false; // End of stream reached already - - m_nBufferPos = 0; - - if(m_brInput.ReadUInt32() != m_uBlockIndex) - throw new InvalidDataException(); - ++m_uBlockIndex; - - byte[] pbStoredHash = m_brInput.ReadBytes(32); - if((pbStoredHash == null) || (pbStoredHash.Length != 32)) - throw new InvalidDataException(); - - int nBufferSize = 0; - try { nBufferSize = m_brInput.ReadInt32(); } - catch(NullReferenceException) // Mono bug workaround (LaunchPad 783268) - { - if(!NativeLib.IsUnix()) throw; - } - - if(nBufferSize < 0) - throw new InvalidDataException(); - - if(nBufferSize == 0) - { - for(int iHash = 0; iHash < 32; ++iHash) - { - if(pbStoredHash[iHash] != 0) - throw new InvalidDataException(); - } - - m_bEos = true; - m_pbBuffer = MemUtil.EmptyByteArray; - return false; - } - - m_pbBuffer = m_brInput.ReadBytes(nBufferSize); - if((m_pbBuffer == null) || ((m_pbBuffer.Length != nBufferSize) && m_bVerify)) - throw new InvalidDataException(); - - if(m_bVerify) - { - byte[] pbComputedHash = CryptoUtil.HashSha256(m_pbBuffer); - if((pbComputedHash == null) || (pbComputedHash.Length != 32)) - throw new InvalidOperationException(); - - if(!MemUtil.ArraysEqual(pbStoredHash, pbComputedHash)) - throw new InvalidDataException(); - } - - return true; - } - - public override void Write(byte[] pbBuffer, int nOffset, int nCount) - { - if(!m_bWriting) throw new InvalidOperationException(); - - while(nCount > 0) - { - if(m_nBufferPos == m_pbBuffer.Length) - WriteHashedBlock(); - - int nCopy = Math.Min(m_pbBuffer.Length - m_nBufferPos, nCount); - - Array.Copy(pbBuffer, nOffset, m_pbBuffer, m_nBufferPos, nCopy); - - nOffset += nCopy; - m_nBufferPos += nCopy; - - nCount -= nCopy; - } - } - - private void WriteHashedBlock() - { - m_bwOutput.Write(m_uBlockIndex); - ++m_uBlockIndex; - - if(m_nBufferPos > 0) - { - byte[] pbHash = CryptoUtil.HashSha256(m_pbBuffer, 0, m_nBufferPos); - - // For KeePassLibSD: - // SHA256Managed sha256 = new SHA256Managed(); - // byte[] pbHash; - // if(m_nBufferPos == m_pbBuffer.Length) - // pbHash = sha256.ComputeHash(m_pbBuffer); - // else - // { - // byte[] pbData = new byte[m_nBufferPos]; - // Array.Copy(m_pbBuffer, 0, pbData, 0, m_nBufferPos); - // pbHash = sha256.ComputeHash(pbData); - // } - - m_bwOutput.Write(pbHash); - } - else - { - m_bwOutput.Write((ulong)0); // Zero hash - m_bwOutput.Write((ulong)0); - m_bwOutput.Write((ulong)0); - m_bwOutput.Write((ulong)0); - } - - m_bwOutput.Write(m_nBufferPos); - - if(m_nBufferPos > 0) - m_bwOutput.Write(m_pbBuffer, 0, m_nBufferPos); - - m_nBufferPos = 0; - } - } -} diff --git a/ModernKeePassLib/Serialization/HmacBlockStream.cs b/ModernKeePassLib/Serialization/HmacBlockStream.cs deleted file mode 100644 index 96be7e8..0000000 --- a/ModernKeePassLib/Serialization/HmacBlockStream.cs +++ /dev/null @@ -1,326 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Text; - -#if ModernKeePassLib -using ModernKeePassLib.Cryptography.Hash; -#elif !KeePassUAP -using System.Security.Cryptography; -#endif - -using ModernKeePassLib.Resources; -using ModernKeePassLib.Utility; - -namespace ModernKeePassLib.Serialization -{ - public sealed class HmacBlockStream : Stream - { - private const int NbDefaultBufferSize = 1024 * 1024; // 1 MB - - private Stream m_sBase; - private readonly bool m_bWriting; - private readonly bool m_bVerify; - private byte[] m_pbKey; - - private bool m_bEos = false; - private byte[] m_pbBuffer; - private int m_iBufferPos = 0; - - private ulong m_uBlockIndex = 0; - - public override bool CanRead - { - get { return !m_bWriting; } - } - - public override bool CanSeek - { - get { return false; } - } - - public override bool CanWrite - { - get { return m_bWriting; } - } - - public override long Length - { - get { Debug.Assert(false); throw new NotSupportedException(); } - } - - public override long Position - { - get { Debug.Assert(false); throw new NotSupportedException(); } - set { Debug.Assert(false); throw new NotSupportedException(); } - } - - public HmacBlockStream(Stream sBase, bool bWriting, bool bVerify, - byte[] pbKey) - { - if(sBase == null) throw new ArgumentNullException("sBase"); - if(pbKey == null) throw new ArgumentNullException("pbKey"); - - m_sBase = sBase; - m_bWriting = bWriting; - m_bVerify = bVerify; - m_pbKey = pbKey; - - if(!m_bWriting) // Reading mode - { - if(!m_sBase.CanRead) throw new InvalidOperationException(); - - m_pbBuffer = MemUtil.EmptyByteArray; - } - else // Writing mode - { - if(!m_sBase.CanWrite) throw new InvalidOperationException(); - - m_pbBuffer = new byte[NbDefaultBufferSize]; - } - } - - protected override void Dispose(bool disposing) - { - if(disposing && (m_sBase != null)) - { - if(m_bWriting) - { - if(m_iBufferPos == 0) // No data left in buffer - WriteSafeBlock(); // Write terminating block - else - { - WriteSafeBlock(); // Write remaining buffered data - WriteSafeBlock(); // Write terminating block - } - - Flush(); - } - - m_sBase.Dispose(); - m_sBase = null; - } - - base.Dispose(disposing); - } - - public override void Flush() - { - Debug.Assert(m_sBase != null); // Object should not be disposed - if(m_bWriting && (m_sBase != null)) m_sBase.Flush(); - } - - public override long Seek(long lOffset, SeekOrigin soOrigin) - { - Debug.Assert(false); - throw new NotSupportedException(); - } - - public override void SetLength(long lValue) - { - Debug.Assert(false); - throw new NotSupportedException(); - } - - internal static byte[] GetHmacKey64(byte[] pbKey, ulong uBlockIndex) - { - if(pbKey == null) throw new ArgumentNullException("pbKey"); - Debug.Assert(pbKey.Length == 64); - - // We are computing the HMAC using SHA-256, whose internal - // block size is 512 bits; thus create a key that is 512 - // bits long (using SHA-512) - - byte[] pbBlockKey; - using(SHA512Managed h = new SHA512Managed()) - { - byte[] pbIndex = MemUtil.UInt64ToBytes(uBlockIndex); - - h.TransformBlock(pbIndex, 0, pbIndex.Length, pbIndex, 0); - h.TransformBlock(pbKey, 0, pbKey.Length, pbKey, 0); - h.TransformFinalBlock(MemUtil.EmptyByteArray, 0, 0); - - pbBlockKey = h.Hash; - } - -#if DEBUG - byte[] pbZero = new byte[64]; - Debug.Assert((pbBlockKey.Length == 64) && !MemUtil.ArraysEqual( - pbBlockKey, pbZero)); // Ensure we own pbBlockKey -#endif - return pbBlockKey; - } - - public override int Read(byte[] pbBuffer, int iOffset, int nCount) - { - if(m_bWriting) throw new InvalidOperationException(); - - int nRemaining = nCount; - while(nRemaining > 0) - { - if(m_iBufferPos == m_pbBuffer.Length) - { - if(!ReadSafeBlock()) - return (nCount - nRemaining); // Bytes actually read - } - - int nCopy = Math.Min(m_pbBuffer.Length - m_iBufferPos, nRemaining); - Debug.Assert(nCopy > 0); - - Array.Copy(m_pbBuffer, m_iBufferPos, pbBuffer, iOffset, nCopy); - - iOffset += nCopy; - m_iBufferPos += nCopy; - - nRemaining -= nCopy; - } - - return nCount; - } - - private bool ReadSafeBlock() - { - if(m_bEos) return false; // End of stream reached already - - byte[] pbStoredHmac = MemUtil.Read(m_sBase, 32); - if((pbStoredHmac == null) || (pbStoredHmac.Length != 32)) - throw new EndOfStreamException(KLRes.FileCorrupted + " " + - KLRes.FileIncomplete); - - // Block index is implicit: it's used in the HMAC computation, - // but does not need to be stored - // byte[] pbBlockIndex = MemUtil.Read(m_sBase, 8); - // if((pbBlockIndex == null) || (pbBlockIndex.Length != 8)) - // throw new EndOfStreamException(); - // ulong uBlockIndex = MemUtil.BytesToUInt64(pbBlockIndex); - // if((uBlockIndex != m_uBlockIndex) && m_bVerify) - // throw new InvalidDataException(); - byte[] pbBlockIndex = MemUtil.UInt64ToBytes(m_uBlockIndex); - - byte[] pbBlockSize = MemUtil.Read(m_sBase, 4); - if((pbBlockSize == null) || (pbBlockSize.Length != 4)) - throw new EndOfStreamException(KLRes.FileCorrupted + " " + - KLRes.FileIncomplete); - int nBlockSize = MemUtil.BytesToInt32(pbBlockSize); - if(nBlockSize < 0) - throw new InvalidDataException(KLRes.FileCorrupted); - - m_iBufferPos = 0; - - m_pbBuffer = MemUtil.Read(m_sBase, nBlockSize); - if((m_pbBuffer == null) || ((m_pbBuffer.Length != nBlockSize) && m_bVerify)) - throw new EndOfStreamException(KLRes.FileCorrupted + " " + - KLRes.FileIncompleteExpc); - - if(m_bVerify) - { - byte[] pbCmpHmac; - byte[] pbBlockKey = GetHmacKey64(m_pbKey, m_uBlockIndex); - using(HMACSHA256 h = new HMACSHA256(pbBlockKey)) - { - h.TransformBlock(pbBlockIndex, 0, pbBlockIndex.Length, - pbBlockIndex, 0); - h.TransformBlock(pbBlockSize, 0, pbBlockSize.Length, - pbBlockSize, 0); - - if(m_pbBuffer.Length > 0) - h.TransformBlock(m_pbBuffer, 0, m_pbBuffer.Length, - m_pbBuffer, 0); - - h.TransformFinalBlock(MemUtil.EmptyByteArray, 0, 0); - - pbCmpHmac = h.Hash; - } - MemUtil.ZeroByteArray(pbBlockKey); - - if(!MemUtil.ArraysEqual(pbCmpHmac, pbStoredHmac)) - throw new InvalidDataException(KLRes.FileCorrupted); - } - - ++m_uBlockIndex; - - if(nBlockSize == 0) - { - m_bEos = true; - return false; // No further data available - } - return true; - } - - public override void Write(byte[] pbBuffer, int iOffset, int nCount) - { - if(!m_bWriting) throw new InvalidOperationException(); - - while(nCount > 0) - { - if(m_iBufferPos == m_pbBuffer.Length) - WriteSafeBlock(); - - int nCopy = Math.Min(m_pbBuffer.Length - m_iBufferPos, nCount); - Debug.Assert(nCopy > 0); - - Array.Copy(pbBuffer, iOffset, m_pbBuffer, m_iBufferPos, nCopy); - - iOffset += nCopy; - m_iBufferPos += nCopy; - - nCount -= nCopy; - } - } - - private void WriteSafeBlock() - { - byte[] pbBlockIndex = MemUtil.UInt64ToBytes(m_uBlockIndex); - - int cbBlockSize = m_iBufferPos; - byte[] pbBlockSize = MemUtil.Int32ToBytes(cbBlockSize); - - byte[] pbBlockHmac; - byte[] pbBlockKey = GetHmacKey64(m_pbKey, m_uBlockIndex); - using(HMACSHA256 h = new HMACSHA256(pbBlockKey)) - { - h.TransformBlock(pbBlockIndex, 0, pbBlockIndex.Length, - pbBlockIndex, 0); - h.TransformBlock(pbBlockSize, 0, pbBlockSize.Length, - pbBlockSize, 0); - - if(cbBlockSize > 0) - h.TransformBlock(m_pbBuffer, 0, cbBlockSize, m_pbBuffer, 0); - - h.TransformFinalBlock(MemUtil.EmptyByteArray, 0, 0); - - pbBlockHmac = h.Hash; - } - MemUtil.ZeroByteArray(pbBlockKey); - - MemUtil.Write(m_sBase, pbBlockHmac); - // MemUtil.Write(m_sBase, pbBlockIndex); // Implicit - MemUtil.Write(m_sBase, pbBlockSize); - if(cbBlockSize > 0) - m_sBase.Write(m_pbBuffer, 0, cbBlockSize); - - ++m_uBlockIndex; - m_iBufferPos = 0; - } - } -} diff --git a/ModernKeePassLib/Serialization/IOConnection.cs b/ModernKeePassLib/Serialization/IOConnection.cs deleted file mode 100644 index a3a9161..0000000 --- a/ModernKeePassLib/Serialization/IOConnection.cs +++ /dev/null @@ -1,926 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Net; -using System.Reflection; -using System.Text; - -#if (!ModernKeePassLib && !KeePassLibSD && !KeePassUAP) -using System.Net.Cache; -using System.Net.Security; -#endif - -#if !ModernKeePassLib && !KeePassUAP -using System.Security.Cryptography.X509Certificates; -#endif - -#if ModernKeePassLib -using Windows.Storage; -using Windows.Storage.Streams; -#endif -using ModernKeePassLib.Native; -using ModernKeePassLib.Utility; - -namespace ModernKeePassLib.Serialization -{ -#if (!ModernKeePassLib && !KeePassLibSD && !KeePassRT) - internal sealed class IOWebClient : WebClient - { - private IOConnectionInfo m_ioc; - - public IOWebClient(IOConnectionInfo ioc) : base() - { - m_ioc = ioc; - } - - protected override WebRequest GetWebRequest(Uri address) - { - WebRequest request = base.GetWebRequest(address); - IOConnection.ConfigureWebRequest(request, m_ioc); - return request; - } - } -#endif - -#if !ModernKeePassLib - internal abstract class WrapperStream : Stream - { - private readonly Stream m_s; - protected Stream BaseStream - { - get { return m_s; } - } - - public override bool CanRead - { - get { return m_s.CanRead; } - } - - public override bool CanSeek - { - get { return m_s.CanSeek; } - } - - public override bool CanTimeout - { - get { return m_s.CanTimeout; } - } - - public override bool CanWrite - { - get { return m_s.CanWrite; } - } - - public override long Length - { - get { return m_s.Length; } - } - - public override long Position - { - get { return m_s.Position; } - set { m_s.Position = value; } - } - - public override int ReadTimeout - { - get { return m_s.ReadTimeout; } - set { m_s.ReadTimeout = value; } - } - - public override int WriteTimeout - { - get { return m_s.WriteTimeout; } - set { m_s.WriteTimeout = value; } - } - - public WrapperStream(Stream sBase) : base() - { - if(sBase == null) throw new ArgumentNullException("sBase"); - - m_s = sBase; - } - -#if !KeePassUAP - public override IAsyncResult BeginRead(byte[] buffer, int offset, - int count, AsyncCallback callback, object state) - { - return m_s.BeginRead(buffer, offset, count, callback, state); - } - - public override IAsyncResult BeginWrite(byte[] buffer, int offset, - int count, AsyncCallback callback, object state) - { - return BeginWrite(buffer, offset, count, callback, state); - } -#endif - - protected override void Dispose(bool disposing) - { - if(disposing) m_s.Dispose(); - - base.Dispose(disposing); - } - -#if !KeePassUAP - public override int EndRead(IAsyncResult asyncResult) - { - return m_s.EndRead(asyncResult); - } - - public override void EndWrite(IAsyncResult asyncResult) - { - m_s.EndWrite(asyncResult); - } -#endif - - public override void Flush() - { - m_s.Flush(); - } - - public override int Read(byte[] buffer, int offset, int count) - { - return m_s.Read(buffer, offset, count); - } - - public override int ReadByte() - { - return m_s.ReadByte(); - } - - public override long Seek(long offset, SeekOrigin origin) - { - return m_s.Seek(offset, origin); - } - - public override void SetLength(long value) - { - m_s.SetLength(value); - } - - public override void Write(byte[] buffer, int offset, int count) - { - m_s.Write(buffer, offset, count); - } - - public override void WriteByte(byte value) - { - m_s.WriteByte(value); - } - } - - internal sealed class IocStream : WrapperStream - { - private readonly bool m_bWrite; // Initially opened for writing - private bool m_bDisposed = false; - - public IocStream(Stream sBase) : base(sBase) - { - m_bWrite = sBase.CanWrite; - } - - protected override void Dispose(bool disposing) - { - base.Dispose(disposing); - - if(disposing && MonoWorkarounds.IsRequired(10163) && m_bWrite && - !m_bDisposed) - { - try - { - Stream s = this.BaseStream; - Type t = s.GetType(); - if(t.Name == "WebConnectionStream") - { - PropertyInfo pi = t.GetProperty("Request", - BindingFlags.Instance | BindingFlags.NonPublic); - if(pi != null) - { - WebRequest wr = (pi.GetValue(s, null) as WebRequest); - if(wr != null) - IOConnection.DisposeResponse(wr.GetResponse(), false); - else { Debug.Assert(false); } - } - else { Debug.Assert(false); } - } - } - catch(Exception) { Debug.Assert(false); } - } - - m_bDisposed = true; - } - - public static Stream WrapIfRequired(Stream s) - { - if(s == null) { Debug.Assert(false); return null; } - - if(MonoWorkarounds.IsRequired(10163) && s.CanWrite) - return new IocStream(s); - - return s; - } - } -#endif - - public static class IOConnection - { -#if (!ModernKeePassLib && !KeePassLibSD && !KeePassRT) - private static ProxyServerType m_pstProxyType = ProxyServerType.System; - private static string m_strProxyAddr = string.Empty; - private static string m_strProxyPort = string.Empty; - private static ProxyAuthType m_patProxyAuthType = ProxyAuthType.Auto; - private static string m_strProxyUserName = string.Empty; - private static string m_strProxyPassword = string.Empty; - -#if !KeePassUAP - private static bool? m_obDefaultExpect100Continue = null; - - private static bool m_bSslCertsAcceptInvalid = false; - internal static bool SslCertsAcceptInvalid - { - // get { return m_bSslCertsAcceptInvalid; } - set { m_bSslCertsAcceptInvalid = value; } - } -#endif -#endif - - // Web request methods - public const string WrmDeleteFile = "DELETEFILE"; - public const string WrmMoveFile = "MOVEFILE"; - - // Web request headers - public const string WrhMoveFileTo = "MoveFileTo"; - - public static event EventHandler IOAccessPre; - -#if (!ModernKeePassLib && !KeePassLibSD && !KeePassRT) - // Allow self-signed certificates, expired certificates, etc. - private static bool AcceptCertificate(object sender, - X509Certificate certificate, X509Chain chain, - SslPolicyErrors sslPolicyErrors) - { - return true; - } - - internal static void SetProxy(ProxyServerType pst, string strAddr, - string strPort, ProxyAuthType pat, string strUserName, - string strPassword) - { - m_pstProxyType = pst; - m_strProxyAddr = (strAddr ?? string.Empty); - m_strProxyPort = (strPort ?? string.Empty); - m_patProxyAuthType = pat; - m_strProxyUserName = (strUserName ?? string.Empty); - m_strProxyPassword = (strPassword ?? string.Empty); - } - - internal static void ConfigureWebRequest(WebRequest request, - IOConnectionInfo ioc) - { - if(request == null) { Debug.Assert(false); return; } // No throw - - IocProperties p = ((ioc != null) ? ioc.Properties : null); - if(p == null) { Debug.Assert(false); p = new IocProperties(); } - - IHasIocProperties ihpReq = (request as IHasIocProperties); - if(ihpReq != null) - { - IocProperties pEx = ihpReq.IOConnectionProperties; - if(pEx != null) p.CopyTo(pEx); - else ihpReq.IOConnectionProperties = p.CloneDeep(); - } - - if(IsHttpWebRequest(request)) - { - // WebDAV support -#if !KeePassUAP - request.PreAuthenticate = true; // Also auth GET -#endif - if(string.Equals(request.Method, WebRequestMethods.Http.Post, - StrUtil.CaseIgnoreCmp)) - request.Method = WebRequestMethods.Http.Put; - -#if !KeePassUAP - HttpWebRequest hwr = (request as HttpWebRequest); - if(hwr != null) - { - string strUA = p.Get(IocKnownProperties.UserAgent); - if(!string.IsNullOrEmpty(strUA)) hwr.UserAgent = strUA; - } - else { Debug.Assert(false); } -#endif - } -#if !KeePassUAP - else if(IsFtpWebRequest(request)) - { - FtpWebRequest fwr = (request as FtpWebRequest); - if(fwr != null) - { - bool? obPassive = p.GetBool(IocKnownProperties.Passive); - if(obPassive.HasValue) fwr.UsePassive = obPassive.Value; - } - else { Debug.Assert(false); } - } -#endif - -#if !KeePassUAP - // Not implemented and ignored in Mono < 2.10 - try - { - request.CachePolicy = new RequestCachePolicy(RequestCacheLevel.NoCacheNoStore); - } - catch(NotImplementedException) { } - catch(Exception) { Debug.Assert(false); } -#endif - - try - { - IWebProxy prx; - if(GetWebProxy(out prx)) request.Proxy = prx; - } - catch(Exception) { Debug.Assert(false); } - -#if !KeePassUAP - long? olTimeout = p.GetLong(IocKnownProperties.Timeout); - if(olTimeout.HasValue && (olTimeout.Value >= 0)) - request.Timeout = (int)Math.Min(olTimeout.Value, (long)int.MaxValue); - - bool? ob = p.GetBool(IocKnownProperties.PreAuth); - if(ob.HasValue) request.PreAuthenticate = ob.Value; -#endif - } - - internal static void ConfigureWebClient(WebClient wc) - { -#if !KeePassUAP - // Not implemented and ignored in Mono < 2.10 - try - { - wc.CachePolicy = new RequestCachePolicy(RequestCacheLevel.NoCacheNoStore); - } - catch(NotImplementedException) { } - catch(Exception) { Debug.Assert(false); } -#endif - - try - { - IWebProxy prx; - if(GetWebProxy(out prx)) wc.Proxy = prx; - } - catch(Exception) { Debug.Assert(false); } - } - - private static bool GetWebProxy(out IWebProxy prx) - { - bool b = GetWebProxyServer(out prx); - if(b) AssignCredentials(prx); - return b; - } - - private static bool GetWebProxyServer(out IWebProxy prx) - { - prx = null; - - if(m_pstProxyType == ProxyServerType.None) - return true; // Use null proxy - - if(m_pstProxyType == ProxyServerType.Manual) - { - try - { - if(m_strProxyAddr.Length == 0) - { - // First try default (from config), then system - prx = WebRequest.DefaultWebProxy; -#if !KeePassUAP - if(prx == null) prx = WebRequest.GetSystemWebProxy(); -#endif - } - else if(m_strProxyPort.Length > 0) - prx = new WebProxy(m_strProxyAddr, int.Parse(m_strProxyPort)); - else prx = new WebProxy(m_strProxyAddr); - - return (prx != null); - } -#if KeePassUAP - catch(Exception) { Debug.Assert(false); } -#else - catch(Exception ex) - { - string strInfo = m_strProxyAddr; - if(m_strProxyPort.Length > 0) - strInfo += ":" + m_strProxyPort; - MessageService.ShowWarning(strInfo, ex); - } -#endif - - return false; // Use default - } - - Debug.Assert(m_pstProxyType == ProxyServerType.System); - try - { - // First try system, then default (from config) -#if !KeePassUAP - prx = WebRequest.GetSystemWebProxy(); -#endif - if(prx == null) prx = WebRequest.DefaultWebProxy; - - return (prx != null); - } - catch(Exception) { Debug.Assert(false); } - - return false; - } - - private static void AssignCredentials(IWebProxy prx) - { - if(prx == null) return; // No assert - - string strUserName = m_strProxyUserName; - string strPassword = m_strProxyPassword; - - ProxyAuthType pat = m_patProxyAuthType; - if(pat == ProxyAuthType.Auto) - { - if((strUserName.Length > 0) || (strPassword.Length > 0)) - pat = ProxyAuthType.Manual; - else pat = ProxyAuthType.Default; - } - - try - { - if(pat == ProxyAuthType.None) - prx.Credentials = null; - else if(pat == ProxyAuthType.Default) - prx.Credentials = CredentialCache.DefaultCredentials; - else if(pat == ProxyAuthType.Manual) - { - if((strUserName.Length > 0) || (strPassword.Length > 0)) - prx.Credentials = new NetworkCredential( - strUserName, strPassword); - } - else { Debug.Assert(false); } - } - catch(Exception) { Debug.Assert(false); } - } - - private static void PrepareWebAccess(IOConnectionInfo ioc) - { -#if !KeePassUAP - IocProperties p = ((ioc != null) ? ioc.Properties : null); - if(p == null) { Debug.Assert(false); p = new IocProperties(); } - - try - { - if(m_bSslCertsAcceptInvalid) - ServicePointManager.ServerCertificateValidationCallback = - IOConnection.AcceptCertificate; - else - ServicePointManager.ServerCertificateValidationCallback = null; - } - catch(Exception) { Debug.Assert(false); } - - try - { - SecurityProtocolType spt = (SecurityProtocolType.Ssl3 | - SecurityProtocolType.Tls); - - // The flags Tls11 and Tls12 in SecurityProtocolType have been - // introduced in .NET 4.5 and must not be set when running under - // older .NET versions (otherwise an exception is thrown) - Type tSpt = typeof(SecurityProtocolType); - string[] vSpt = Enum.GetNames(tSpt); - foreach(string strSpt in vSpt) - { - if(strSpt.Equals("Tls11", StrUtil.CaseIgnoreCmp)) - spt |= (SecurityProtocolType)Enum.Parse(tSpt, "Tls11", true); - else if(strSpt.Equals("Tls12", StrUtil.CaseIgnoreCmp)) - spt |= (SecurityProtocolType)Enum.Parse(tSpt, "Tls12", true); - } - - ServicePointManager.SecurityProtocol = spt; - } - catch(Exception) { Debug.Assert(false); } - - try - { - bool bCurCont = ServicePointManager.Expect100Continue; - if(!m_obDefaultExpect100Continue.HasValue) - { - Debug.Assert(bCurCont); // Default should be true - m_obDefaultExpect100Continue = bCurCont; - } - - bool bNewCont = m_obDefaultExpect100Continue.Value; - bool? ob = p.GetBool(IocKnownProperties.Expect100Continue); - if(ob.HasValue) bNewCont = ob.Value; - - if(bNewCont != bCurCont) - ServicePointManager.Expect100Continue = bNewCont; - } - catch(Exception) { Debug.Assert(false); } -#endif - } - - private static IOWebClient CreateWebClient(IOConnectionInfo ioc) - { - PrepareWebAccess(ioc); - - IOWebClient wc = new IOWebClient(ioc); - ConfigureWebClient(wc); - - if((ioc.UserName.Length > 0) || (ioc.Password.Length > 0)) - wc.Credentials = new NetworkCredential(ioc.UserName, ioc.Password); - else if(NativeLib.IsUnix()) // Mono requires credentials - wc.Credentials = new NetworkCredential("anonymous", string.Empty); - - return wc; - } - - private static WebRequest CreateWebRequest(IOConnectionInfo ioc) - { - PrepareWebAccess(ioc); - - WebRequest req = WebRequest.Create(ioc.Path); - ConfigureWebRequest(req, ioc); - - if((ioc.UserName.Length > 0) || (ioc.Password.Length > 0)) - req.Credentials = new NetworkCredential(ioc.UserName, ioc.Password); - else if(NativeLib.IsUnix()) // Mono requires credentials - req.Credentials = new NetworkCredential("anonymous", string.Empty); - - return req; - } - - public static Stream OpenRead(IOConnectionInfo ioc) - { - RaiseIOAccessPreEvent(ioc, IOAccessType.Read); - - if(StrUtil.IsDataUri(ioc.Path)) - { - byte[] pbData = StrUtil.DataUriToData(ioc.Path); - if(pbData != null) return new MemoryStream(pbData, false); - } - - if(ioc.IsLocalFile()) return OpenReadLocal(ioc); - - return IocStream.WrapIfRequired(CreateWebClient(ioc).OpenRead( - new Uri(ioc.Path))); - } -#else - public static Stream OpenRead(IOConnectionInfo ioc) - { - RaiseIOAccessPreEvent(ioc, IOAccessType.Read); - - return OpenReadLocal(ioc); - } -#endif - - private static Stream OpenReadLocal(IOConnectionInfo ioc) - { - return ioc.StorageFile.OpenAsync(FileAccessMode.Read).GetAwaiter().GetResult().AsStream(); - } - -#if (!ModernKeePassLib && !KeePassLibSD && !KeePassRT) - public static Stream OpenWrite(IOConnectionInfo ioc) - { - if(ioc == null) { Debug.Assert(false); return null; } - - RaiseIOAccessPreEvent(ioc, IOAccessType.Write); - - if(ioc.IsLocalFile()) return OpenWriteLocal(ioc); - - Uri uri = new Uri(ioc.Path); - Stream s; - - // Mono does not set HttpWebRequest.Method to POST for writes, - // so one needs to set the method to PUT explicitly - if(NativeLib.IsUnix() && IsHttpWebRequest(uri)) - s = CreateWebClient(ioc).OpenWrite(uri, WebRequestMethods.Http.Put); - else s = CreateWebClient(ioc).OpenWrite(uri); - - return IocStream.WrapIfRequired(s); - } -#else - public static Stream OpenWrite(IOConnectionInfo ioc) - { - RaiseIOAccessPreEvent(ioc, IOAccessType.Write); - - return OpenWriteLocal(ioc); - } -#endif - - private static Stream OpenWriteLocal(IOConnectionInfo ioc) - { -#if ModernKeePassLib - return ioc.StorageFile.OpenAsync(FileAccessMode.ReadWrite).GetAwaiter().GetResult().AsStream(); -#else - return new FileStream(ioc.Path, FileMode.Create, FileAccess.Write, - FileShare.None); -#endif - } - - public static bool FileExists(IOConnectionInfo ioc) - { - return FileExists(ioc, false); - } - - public static bool FileExists(IOConnectionInfo ioc, bool bThrowErrors) - { - if(ioc == null) { Debug.Assert(false); return false; } - - RaiseIOAccessPreEvent(ioc, IOAccessType.Exists); - -#if ModernKeePassLib - return ioc.StorageFile.IsAvailable; -#else - if(ioc.IsLocalFile()) return File.Exists(ioc.Path); - -#if !KeePassLibSD - if(ioc.Path.StartsWith("ftp://", StrUtil.CaseIgnoreCmp)) - { - bool b = SendCommand(ioc, WebRequestMethods.Ftp.GetDateTimestamp); - if(!b && bThrowErrors) throw new InvalidOperationException(); - return b; - } -#endif - - try - { - Stream s = OpenRead(ioc); - if(s == null) throw new FileNotFoundException(); - - try { s.ReadByte(); } - catch(Exception) { } - - // We didn't download the file completely; close may throw - // an exception -- that's okay - try { s.Close(); } - catch(Exception) { } - } - catch(Exception) - { - if(bThrowErrors) throw; - return false; - } - - return true; -#endif - } - - public static void DeleteFile(IOConnectionInfo ioc) - { - RaiseIOAccessPreEvent(ioc, IOAccessType.Delete); - -#if ModernKeePassLib - if (!ioc.IsLocalFile()) return; - ioc.StorageFile?.DeleteAsync().GetAwaiter().GetResult(); -#else - if(ioc.IsLocalFile()) { File.Delete(ioc.Path); return; } - -#if !KeePassLibSD - WebRequest req = CreateWebRequest(ioc); - if(req != null) - { - if(IsHttpWebRequest(req)) req.Method = "DELETE"; - else if(IsFtpWebRequest(req)) - req.Method = WebRequestMethods.Ftp.DeleteFile; - else if(IsFileWebRequest(req)) - { - File.Delete(UrlUtil.FileUrlToPath(ioc.Path)); - return; - } - else req.Method = WrmDeleteFile; - - DisposeResponse(req.GetResponse(), true); - } -#endif -#endif - } - - /// - /// Rename/move a file. For local file system and WebDAV, the - /// specified file is moved, i.e. the file destination can be - /// in a different directory/path. In contrast, for FTP the - /// file is renamed, i.e. its destination must be in the same - /// directory/path. - /// - /// Source file path. - /// Target file path. - public static void RenameFile(IOConnectionInfo iocFrom, IOConnectionInfo iocTo) - { - RaiseIOAccessPreEvent(iocFrom, iocTo, IOAccessType.Move); - -#if ModernKeePassLib - if (!iocFrom.IsLocalFile()) return; - iocFrom.StorageFile?.RenameAsync(iocTo.Path).GetAwaiter().GetResult(); -#else - if(iocFrom.IsLocalFile()) { File.Move(iocFrom.Path, iocTo.Path); return; } - -#if !KeePassLibSD - WebRequest req = CreateWebRequest(iocFrom); - if(req != null) - { - if(IsHttpWebRequest(req)) - { -#if KeePassUAP - throw new NotSupportedException(); -#else - req.Method = "MOVE"; - req.Headers.Set("Destination", iocTo.Path); // Full URL supported -#endif - } - else if(IsFtpWebRequest(req)) - { -#if KeePassUAP - throw new NotSupportedException(); -#else - req.Method = WebRequestMethods.Ftp.Rename; - string strTo = UrlUtil.GetFileName(iocTo.Path); - - // We're affected by .NET bug 621450: - // https://connect.microsoft.com/VisualStudio/feedback/details/621450/problem-renaming-file-on-ftp-server-using-ftpwebrequest-in-net-framework-4-0-vs2010-only - // Prepending "./", "%2E/" or "Dummy/../" doesn't work. - - ((FtpWebRequest)req).RenameTo = strTo; -#endif - } - else if(IsFileWebRequest(req)) - { - File.Move(UrlUtil.FileUrlToPath(iocFrom.Path), - UrlUtil.FileUrlToPath(iocTo.Path)); - return; - } - else - { -#if KeePassUAP - throw new NotSupportedException(); -#else - req.Method = WrmMoveFile; - req.Headers.Set(WrhMoveFileTo, iocTo.Path); -#endif - } - -#if !KeePassUAP // Unreachable code - DisposeResponse(req.GetResponse(), true); -#endif - } -#endif - - // using(Stream sIn = IOConnection.OpenRead(iocFrom)) - // { - // using(Stream sOut = IOConnection.OpenWrite(iocTo)) - // { - // MemUtil.CopyStream(sIn, sOut); - // sOut.Close(); - // } - // - // sIn.Close(); - // } - // DeleteFile(iocFrom); -#endif - } - -#if (!ModernKeePassLib && !KeePassLibSD && !KeePassRT) - private static bool SendCommand(IOConnectionInfo ioc, string strMethod) - { - try - { - WebRequest req = CreateWebRequest(ioc); - req.Method = strMethod; - DisposeResponse(req.GetResponse(), true); - } - catch(Exception) { return false; } - - return true; - } -#endif -#if !ModernKeePassLib - internal static void DisposeResponse(WebResponse wr, bool bGetStream) - { - if(wr == null) return; - - try - { - if(bGetStream) - { - Stream s = wr.GetResponseStream(); - if(s != null) s.Close(); - } - } - catch(Exception) { Debug.Assert(false); } - - try { wr.Close(); } - catch(Exception) { Debug.Assert(false); } - } -#endif - public static byte[] ReadFile(IOConnectionInfo ioc) - { - Stream sIn = null; - MemoryStream ms = null; - try - { - sIn = IOConnection.OpenRead(ioc); - if(sIn == null) return null; - - ms = new MemoryStream(); - MemUtil.CopyStream(sIn, ms); - - return ms.ToArray(); - } - catch(Exception) { } - finally - { - if(sIn != null) sIn.Dispose(); - if(ms != null) ms.Dispose(); - } - - return null; - } - - private static void RaiseIOAccessPreEvent(IOConnectionInfo ioc, IOAccessType t) - { - RaiseIOAccessPreEvent(ioc, null, t); - } - - private static void RaiseIOAccessPreEvent(IOConnectionInfo ioc, - IOConnectionInfo ioc2, IOAccessType t) - { - if(ioc == null) { Debug.Assert(false); return; } - // ioc2 may be null - - if(IOConnection.IOAccessPre != null) - { - IOConnectionInfo ioc2Lcl = ((ioc2 != null) ? ioc2.CloneDeep() : null); - IOAccessEventArgs e = new IOAccessEventArgs(ioc.CloneDeep(), ioc2Lcl, t); - IOConnection.IOAccessPre(null, e); - } - } -#if !ModernKeePassLib - private static bool IsHttpWebRequest(Uri uri) - { - if(uri == null) { Debug.Assert(false); return false; } - - string sch = uri.Scheme; - if(sch == null) { Debug.Assert(false); return false; } - return (sch.Equals("http", StrUtil.CaseIgnoreCmp) || // Uri.UriSchemeHttp - sch.Equals("https", StrUtil.CaseIgnoreCmp)); // Uri.UriSchemeHttps - } - - internal static bool IsHttpWebRequest(WebRequest wr) - { - if(wr == null) { Debug.Assert(false); return false; } - -#if KeePassUAP - return IsHttpWebRequest(wr.RequestUri); -#else - return (wr is HttpWebRequest); -#endif - } - - internal static bool IsFtpWebRequest(WebRequest wr) - { - if(wr == null) { Debug.Assert(false); return false; } - -#if KeePassUAP - return string.Equals(wr.RequestUri.Scheme, "ftp", StrUtil.CaseIgnoreCmp); -#else - return (wr is FtpWebRequest); -#endif - } - - private static bool IsFileWebRequest(WebRequest wr) - { - if(wr == null) { Debug.Assert(false); return false; } - -#if KeePassUAP - return string.Equals(wr.RequestUri.Scheme, "file", StrUtil.CaseIgnoreCmp); -#else - return (wr is FileWebRequest); -#endif - } -#endif // ModernKeePass - } -} diff --git a/ModernKeePassLib/Serialization/IOConnectionInfo.cs b/ModernKeePassLib/Serialization/IOConnectionInfo.cs deleted file mode 100644 index fd37f02..0000000 --- a/ModernKeePassLib/Serialization/IOConnectionInfo.cs +++ /dev/null @@ -1,392 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Diagnostics; -using System.IO; -using System.Text; -using System.Xml.Serialization; -#if ModernKeePassLib -using Windows.Storage; -//using PCLStorage; -#endif - -using ModernKeePassLib.Interfaces; -using ModernKeePassLib.Utility; -using System.Threading.Tasks; - -namespace ModernKeePassLib.Serialization -{ - public enum IOCredSaveMode - { - /// - /// Do not remember user name or password. - /// - NoSave = 0, - - /// - /// Remember the user name only, not the password. - /// - UserNameOnly, - - /// - /// Save both user name and password. - /// - SaveCred - } - - public enum IOCredProtMode - { - None = 0, - Obf - } - - /* public enum IOFileFormatHint - { - None = 0, - Deprecated - } */ - - public sealed class IOConnectionInfo : IDeepCloneable - { - // private IOFileFormatHint m_ioHint = IOFileFormatHint.None; - - private string m_strUrl = string.Empty; - public string Path - { - get { return m_strUrl; } - set - { - Debug.Assert(value != null); - if(value == null) throw new ArgumentNullException("value"); - - m_strUrl = value; - } - } - - private string m_strUser = string.Empty; - [DefaultValue("")] - public string UserName - { - get { return m_strUser; } - set - { - Debug.Assert(value != null); - if(value == null) throw new ArgumentNullException("value"); - - m_strUser = value; - } - } - - private string m_strPassword = string.Empty; - [DefaultValue("")] - public string Password - { - get { return m_strPassword; } - set - { - Debug.Assert(value != null); - if(value == null) throw new ArgumentNullException("value"); - - m_strPassword = value; - } - } - - private IOCredProtMode m_ioCredProtMode = IOCredProtMode.None; - public IOCredProtMode CredProtMode - { - get { return m_ioCredProtMode; } - set { m_ioCredProtMode = value; } - } - - private IOCredSaveMode m_ioCredSaveMode = IOCredSaveMode.NoSave; - public IOCredSaveMode CredSaveMode - { - get { return m_ioCredSaveMode; } - set { m_ioCredSaveMode = value; } - } - - private bool m_bComplete = false; - [XmlIgnore] - public bool IsComplete // Credentials etc. fully specified - { - get { return m_bComplete; } - set { m_bComplete = value; } - } - - /* public IOFileFormatHint FileFormatHint - { - get { return m_ioHint; } - set { m_ioHint = value; } - } */ - - private IocProperties m_props = new IocProperties(); - [XmlIgnore] - public IocProperties Properties - { - get { return m_props; } - set - { - if(value == null) throw new ArgumentNullException("value"); - m_props = value; - } - } - - /// - /// For serialization only; use Properties in code. - /// - [DefaultValue("")] - public string PropertiesEx - { - get { return m_props.Serialize(); } - set - { - if(value == null) throw new ArgumentNullException("value"); - - IocProperties p = IocProperties.Deserialize(value); - Debug.Assert(p != null); - m_props = (p ?? new IocProperties()); - } - } - - public IOConnectionInfo CloneDeep() - { - IOConnectionInfo ioc = (IOConnectionInfo)this.MemberwiseClone(); - ioc.m_props = m_props.CloneDeep(); - return ioc; - } - -#if DEBUG // For debugger display only - public override string ToString() - { - return GetDisplayName(); - } -#endif - - /* - /// - /// Serialize the current connection info to a string. Credentials - /// are serialized based on the CredSaveMode property. - /// - /// Input object to be serialized. - /// Serialized object as string. - public static string SerializeToString(IOConnectionInfo iocToCompile) - { - Debug.Assert(iocToCompile != null); - if(iocToCompile == null) throw new ArgumentNullException("iocToCompile"); - - string strUrl = iocToCompile.Path; - string strUser = TransformUnreadable(iocToCompile.UserName, true); - string strPassword = TransformUnreadable(iocToCompile.Password, true); - - string strAll = strUrl + strUser + strPassword + "CUN"; - char chSep = StrUtil.GetUnusedChar(strAll); - if(chSep == char.MinValue) throw new FormatException(); - - StringBuilder sb = new StringBuilder(); - sb.Append(chSep); - sb.Append(strUrl); - sb.Append(chSep); - - if(iocToCompile.CredSaveMode == IOCredSaveMode.SaveCred) - { - sb.Append('C'); - sb.Append(chSep); - sb.Append(strUser); - sb.Append(chSep); - sb.Append(strPassword); - } - else if(iocToCompile.CredSaveMode == IOCredSaveMode.UserNameOnly) - { - sb.Append('U'); - sb.Append(chSep); - sb.Append(strUser); - sb.Append(chSep); - } - else // Don't remember credentials - { - sb.Append('N'); - sb.Append(chSep); - sb.Append(chSep); - } - - return sb.ToString(); - } - - public static IOConnectionInfo UnserializeFromString(string strToDecompile) - { - Debug.Assert(strToDecompile != null); - if(strToDecompile == null) throw new ArgumentNullException("strToDecompile"); - if(strToDecompile.Length <= 1) throw new ArgumentException(); - - char chSep = strToDecompile[0]; - string[] vParts = strToDecompile.Substring(1, strToDecompile.Length - - 1).Split(new char[]{ chSep }); - if(vParts.Length < 4) throw new ArgumentException(); - - IOConnectionInfo s = new IOConnectionInfo(); - s.Path = vParts[0]; - - if(vParts[1] == "C") - s.CredSaveMode = IOCredSaveMode.SaveCred; - else if(vParts[1] == "U") - s.CredSaveMode = IOCredSaveMode.UserNameOnly; - else - s.CredSaveMode = IOCredSaveMode.NoSave; - - s.UserName = TransformUnreadable(vParts[2], false); - s.Password = TransformUnreadable(vParts[3], false); - return s; - } - */ - - /* - /// - /// Very simple string protection. Doesn't really encrypt the input - /// string, only encodes it that it's not readable on the first glance. - /// - /// The string to encode/decode. - /// If true, the string will be encoded, - /// otherwise it'll be decoded. - /// Encoded/decoded string. - private static string TransformUnreadable(string strToEncode, bool bEncode) - { - Debug.Assert(strToEncode != null); - if(strToEncode == null) throw new ArgumentNullException("strToEncode"); - - if(bEncode) - { - byte[] pbUtf8 = StrUtil.Utf8.GetBytes(strToEncode); - - unchecked - { - for(int iPos = 0; iPos < pbUtf8.Length; ++iPos) - pbUtf8[iPos] += (byte)(iPos * 11); - } - - return Convert.ToBase64String(pbUtf8); - } - else // Decode - { - byte[] pbBase = Convert.FromBase64String(strToEncode); - - unchecked - { - for(int iPos = 0; iPos < pbBase.Length; ++iPos) - pbBase[iPos] -= (byte)(iPos * 11); - } - - return StrUtil.Utf8.GetString(pbBase, 0, pbBase.Length); - } - } - */ - - public string GetDisplayName() - { - string str = m_strUrl; - - if(m_strUser.Length > 0) - str += (" (" + m_strUser + ")"); - - return str; - } - - public bool IsEmpty() - { - return (m_strUrl.Length == 0); - } - - public static IOConnectionInfo FromPath(string strPath) - { - IOConnectionInfo ioc = new IOConnectionInfo(); - - ioc.Path = strPath; - ioc.CredSaveMode = IOCredSaveMode.NoSave; - - return ioc; - } - - public static IOConnectionInfo FromFile(StorageFile file) - { - IOConnectionInfo ioc = new IOConnectionInfo(); - - ioc.StorageFile = file; - ioc.Path = file.Path; - ioc.CredSaveMode = IOCredSaveMode.NoSave; - - return ioc; - } - - public StorageFile StorageFile { get; set; } - - public bool CanProbablyAccess() - { -#if ModernKeePassLib - if (IsLocalFile()) - { - //return (FileSystem.Current.GetFileFromPathAsync(m_strUrl).Result != null); - var file = StorageFile.GetFileFromPathAsync(m_strUrl).GetAwaiter().GetResult(); - return file != null; - } -#else - if(IsLocalFile()) return File.Exists(m_strUrl); -#endif - - return true; - } - - public bool IsLocalFile() - { - // Not just ":/", see e.g. AppConfigEx.ChangePathRelAbs - return (m_strUrl.IndexOf("://") < 0); - } - - public void ClearCredentials(bool bDependingOnRememberMode) - { - if((bDependingOnRememberMode == false) || - (m_ioCredSaveMode == IOCredSaveMode.NoSave)) - { - m_strUser = string.Empty; - } - - if((bDependingOnRememberMode == false) || - (m_ioCredSaveMode == IOCredSaveMode.NoSave) || - (m_ioCredSaveMode == IOCredSaveMode.UserNameOnly)) - { - m_strPassword = string.Empty; - } - } - - public void Obfuscate(bool bObf) - { - if(bObf && (m_ioCredProtMode == IOCredProtMode.None)) - { - m_strPassword = StrUtil.Obfuscate(m_strPassword); - m_ioCredProtMode = IOCredProtMode.Obf; - } - else if(!bObf && (m_ioCredProtMode == IOCredProtMode.Obf)) - { - m_strPassword = StrUtil.Deobfuscate(m_strPassword); - m_ioCredProtMode = IOCredProtMode.None; - } - } - } -} diff --git a/ModernKeePassLib/Serialization/IocProperties.cs b/ModernKeePassLib/Serialization/IocProperties.cs deleted file mode 100644 index 01c5a94..0000000 --- a/ModernKeePassLib/Serialization/IocProperties.cs +++ /dev/null @@ -1,192 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Globalization; -using System.Text; -using System.Xml; - -using ModernKeePassLib.Interfaces; -using ModernKeePassLib.Utility; - -using StrDict = System.Collections.Generic.Dictionary; - -namespace ModernKeePassLib.Serialization -{ - public interface IHasIocProperties - { - IocProperties IOConnectionProperties { get; set; } - } - - public sealed class IocProperties : IDeepCloneable - { - private StrDict m_dict = new StrDict(); - - public IocProperties() - { - } - - public IocProperties CloneDeep() - { - IocProperties p = new IocProperties(); - p.m_dict = new StrDict(m_dict); - return p; - } - - public string Get(string strKey) - { - if(string.IsNullOrEmpty(strKey)) return null; - - foreach(KeyValuePair kvp in m_dict) - { - if(kvp.Key.Equals(strKey, StrUtil.CaseIgnoreCmp)) - return kvp.Value; - } - - return null; - } - - public void Set(string strKey, string strValue) - { - if(string.IsNullOrEmpty(strKey)) { Debug.Assert(false); return; } - - foreach(KeyValuePair kvp in m_dict) - { - if(kvp.Key.Equals(strKey, StrUtil.CaseIgnoreCmp)) - { - if(string.IsNullOrEmpty(strValue)) m_dict.Remove(kvp.Key); - else m_dict[kvp.Key] = strValue; - return; - } - } - - if(!string.IsNullOrEmpty(strValue)) m_dict[strKey] = strValue; - } - - public bool? GetBool(string strKey) - { - string str = Get(strKey); - if(string.IsNullOrEmpty(str)) return null; - - return StrUtil.StringToBool(str); - } - - public void SetBool(string strKey, bool? ob) - { - if(ob.HasValue) Set(strKey, (ob.Value ? "1" : "0")); - else Set(strKey, null); - } - - public long? GetLong(string strKey) - { - string str = Get(strKey); - if(string.IsNullOrEmpty(str)) return null; - - long l; - if(StrUtil.TryParseLongInvariant(str, out l)) return l; - Debug.Assert(false); - return null; - } - - public void SetLong(string strKey, long? ol) - { - if(ol.HasValue) - Set(strKey, ol.Value.ToString(NumberFormatInfo.InvariantInfo)); - else Set(strKey, null); - } - - public string Serialize() - { - if(m_dict.Count == 0) return string.Empty; - - StringBuilder sbAll = new StringBuilder(); - foreach(KeyValuePair kvp in m_dict) - { - sbAll.Append(kvp.Key); - sbAll.Append(kvp.Value); - } - - string strAll = sbAll.ToString(); - char chSepOuter = ';'; - if(strAll.IndexOf(chSepOuter) >= 0) - chSepOuter = StrUtil.GetUnusedChar(strAll); - - strAll += chSepOuter; - char chSepInner = '='; - if(strAll.IndexOf(chSepInner) >= 0) - chSepInner = StrUtil.GetUnusedChar(strAll); - - StringBuilder sb = new StringBuilder(); - sb.Append(chSepOuter); - sb.Append(chSepInner); - - foreach(KeyValuePair kvp in m_dict) - { - sb.Append(chSepOuter); - sb.Append(kvp.Key); - sb.Append(chSepInner); - sb.Append(kvp.Value); - } - - return sb.ToString(); - } - - public static IocProperties Deserialize(string strSerialized) - { - IocProperties p = new IocProperties(); - if(string.IsNullOrEmpty(strSerialized)) return p; // No assert - - char chSepOuter = strSerialized[0]; - string[] v = strSerialized.Substring(1).Split(new char[] { chSepOuter }); - if((v == null) || (v.Length < 2)) { Debug.Assert(false); return p; } - - string strMeta = v[0]; - if(string.IsNullOrEmpty(strMeta)) { Debug.Assert(false); return p; } - - char chSepInner = strMeta[0]; - char[] vSepInner = new char[] { chSepInner }; - - for(int i = 1; i < v.Length; ++i) - { - string strProp = v[i]; - if(string.IsNullOrEmpty(strProp)) { Debug.Assert(false); continue; } - - string[] vProp = strProp.Split(vSepInner); - if((vProp == null) || (vProp.Length < 2)) { Debug.Assert(false); continue; } - Debug.Assert(vProp.Length == 2); - - p.Set(vProp[0], vProp[1]); - } - - return p; - } - - public void CopyTo(IocProperties p) - { - if(p == null) { Debug.Assert(false); return; } - - foreach(KeyValuePair kvp in m_dict) - { - p.m_dict[kvp.Key] = kvp.Value; - } - } - } -} diff --git a/ModernKeePassLib/Serialization/IocPropertyInfo.cs b/ModernKeePassLib/Serialization/IocPropertyInfo.cs deleted file mode 100644 index c86c7c0..0000000 --- a/ModernKeePassLib/Serialization/IocPropertyInfo.cs +++ /dev/null @@ -1,99 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Text; - -using ModernKeePassLib.Utility; - -namespace ModernKeePassLib.Serialization -{ - public sealed class IocPropertyInfo - { - private readonly string m_strName; - public string Name - { - get { return m_strName; } - } - - private readonly Type m_t; - public Type Type - { - get { return m_t; } - } - - private string m_strDisplayName; - public string DisplayName - { - get { return m_strDisplayName; } - set - { - if(value == null) throw new ArgumentNullException("value"); - m_strDisplayName = value; - } - } - - private List m_lProtocols = new List(); - public IEnumerable Protocols - { - get { return m_lProtocols; } - } - - public IocPropertyInfo(string strName, Type t, string strDisplayName, - string[] vProtocols) - { - if(strName == null) throw new ArgumentNullException("strName"); - if(t == null) throw new ArgumentNullException("t"); - if(strDisplayName == null) throw new ArgumentNullException("strDisplayName"); - - m_strName = strName; - m_t = t; - m_strDisplayName = strDisplayName; - - AddProtocols(vProtocols); - } - - public void AddProtocols(string[] v) - { - if(v == null) { Debug.Assert(false); return; } - - foreach(string strProtocol in v) - { - if(strProtocol == null) continue; - - string str = strProtocol.Trim(); - if(str.Length == 0) continue; - - bool bFound = false; - foreach(string strEx in m_lProtocols) - { - if(strEx.Equals(str, StrUtil.CaseIgnoreCmp)) - { - bFound = true; - break; - } - } - - if(!bFound) m_lProtocols.Add(str); - } - } - } -} diff --git a/ModernKeePassLib/Serialization/IocPropertyInfoPool.cs b/ModernKeePassLib/Serialization/IocPropertyInfoPool.cs deleted file mode 100644 index cfe39be..0000000 --- a/ModernKeePassLib/Serialization/IocPropertyInfoPool.cs +++ /dev/null @@ -1,123 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Text; - -using ModernKeePassLib.Resources; -using ModernKeePassLib.Utility; - -namespace ModernKeePassLib.Serialization -{ - public static class IocKnownProtocols - { - public const string Http = "HTTP"; - public const string Https = "HTTPS"; - public const string WebDav = "WebDAV"; - public const string Ftp = "FTP"; - } - - public static class IocKnownProperties - { - public const string Timeout = "Timeout"; - public const string PreAuth = "PreAuth"; - - public const string UserAgent = "UserAgent"; - public const string Expect100Continue = "Expect100Continue"; - - public const string Passive = "Passive"; - } - - public static class IocPropertyInfoPool - { - private static List m_l = null; - public static IEnumerable PropertyInfos - { - get { EnsureInitialized(); return m_l; } - } - - private static void EnsureInitialized() - { - if(m_l != null) return; - - string strGen = KLRes.General; - string strHttp = IocKnownProtocols.Http; - string strHttps = IocKnownProtocols.Https; - string strWebDav = IocKnownProtocols.WebDav; - string strFtp = IocKnownProtocols.Ftp; - - string[] vGen = new string[] { strGen }; - string[] vHttp = new string[] { strHttp, strHttps, strWebDav }; - string[] vFtp = new string[] { strFtp }; - - List l = new List(); - - l.Add(new IocPropertyInfo(IocKnownProperties.Timeout, - typeof(long), KLRes.Timeout + " [ms]", vGen)); - l.Add(new IocPropertyInfo(IocKnownProperties.PreAuth, - typeof(bool), KLRes.PreAuth, vGen)); - - l.Add(new IocPropertyInfo(IocKnownProperties.UserAgent, - typeof(string), KLRes.UserAgent, vHttp)); - l.Add(new IocPropertyInfo(IocKnownProperties.Expect100Continue, - typeof(bool), KLRes.Expect100Continue, vHttp)); - - l.Add(new IocPropertyInfo(IocKnownProperties.Passive, - typeof(bool), KLRes.Passive, vFtp)); - - // l.Add(new IocPropertyInfo("Test", typeof(bool), - // "Long long long long long long long long long long long long long long long long long long long long", - // new string[] { "Proto 1/9", "Proto 2/9", "Proto 3/9", "Proto 4/9", "Proto 5/9", - // "Proto 6/9", "Proto 7/9", "Proto 8/9", "Proto 9/9" })); - - m_l = l; - } - - public static IocPropertyInfo Get(string strName) - { - if(string.IsNullOrEmpty(strName)) { Debug.Assert(false); return null; } - - EnsureInitialized(); - foreach(IocPropertyInfo pi in m_l) - { - if(pi.Name.Equals(strName, StrUtil.CaseIgnoreCmp)) - return pi; - } - - return null; - } - - public static bool Add(IocPropertyInfo pi) - { - if(pi == null) { Debug.Assert(false); return false; } - - // Name must be non-empty - string strName = pi.Name; - if(string.IsNullOrEmpty(strName)) { Debug.Assert(false); return false; } - - IocPropertyInfo piEx = Get(strName); // Ensures initialized - if(piEx != null) { Debug.Assert(false); return false; } // Exists already - - m_l.Add(pi); - return true; - } - } -} diff --git a/ModernKeePassLib/Serialization/KdbxFile.Read.Streamed.cs b/ModernKeePassLib/Serialization/KdbxFile.Read.Streamed.cs deleted file mode 100644 index 7966c79..0000000 --- a/ModernKeePassLib/Serialization/KdbxFile.Read.Streamed.cs +++ /dev/null @@ -1,1077 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Text; -using System.Xml; - -#if !ModernKeePassLib && !KeePassUAP -using System.Drawing; -#endif - -using ModernKeePassLib; -using ModernKeePassLib.Collections; -using ModernKeePassLib.Cryptography; -using ModernKeePassLib.Cryptography.Cipher; -using ModernKeePassLib.Interfaces; -using ModernKeePassLib.Resources; -using ModernKeePassLib.Security; -using ModernKeePassLib.Utility; - -namespace ModernKeePassLib.Serialization -{ - /// - /// Serialization to KeePass KDBX files. - /// - public sealed partial class KdbxFile - { - private enum KdbContext - { - Null, - KeePassFile, - Meta, - Root, - MemoryProtection, - CustomIcons, - CustomIcon, - Binaries, - CustomData, - CustomDataItem, - RootDeletedObjects, - DeletedObject, - Group, - GroupTimes, - GroupCustomData, - GroupCustomDataItem, - Entry, - EntryTimes, - EntryString, - EntryBinary, - EntryAutoType, - EntryAutoTypeItem, - EntryHistory, - EntryCustomData, - EntryCustomDataItem - } - - private bool m_bReadNextNode = true; - private Stack m_ctxGroups = new Stack(); - private PwGroup m_ctxGroup = null; - private PwEntry m_ctxEntry = null; - private string m_ctxStringName = null; - private ProtectedString m_ctxStringValue = null; - private string m_ctxBinaryName = null; - private ProtectedBinary m_ctxBinaryValue = null; - private string m_ctxATName = null; - private string m_ctxATSeq = null; - private bool m_bEntryInHistory = false; - private PwEntry m_ctxHistoryBase = null; - private PwDeletedObject m_ctxDeletedObject = null; - private PwUuid m_uuidCustomIconID = PwUuid.Zero; - private byte[] m_pbCustomIconData = null; - private string m_strCustomDataKey = null; - private string m_strCustomDataValue = null; - private string m_strGroupCustomDataKey = null; - private string m_strGroupCustomDataValue = null; - private string m_strEntryCustomDataKey = null; - private string m_strEntryCustomDataValue = null; - - private void ReadXmlStreamed(Stream sXml, Stream sParent) - { - ReadDocumentStreamed(CreateXmlReader(sXml), sParent); - } - - internal static XmlReaderSettings CreateStdXmlReaderSettings() - { - XmlReaderSettings xrs = new XmlReaderSettings(); - - xrs.CloseInput = true; - xrs.IgnoreComments = true; - xrs.IgnoreProcessingInstructions = true; - xrs.IgnoreWhitespace = true; - -#if ModernKeePassLib || KeePassUAP - xrs.DtdProcessing = DtdProcessing.Prohibit; -#else -#if !KeePassLibSD - // Also see PrepMonoDev.sh script - xrs.ProhibitDtd = true; // Obsolete in .NET 4, but still there - // xrs.DtdProcessing = DtdProcessing.Prohibit; // .NET 4 only -#endif - xrs.ValidationType = ValidationType.None; -#endif - - return xrs; - } - - private static XmlReader CreateXmlReader(Stream readerStream) - { - XmlReaderSettings xrs = CreateStdXmlReaderSettings(); - return XmlReader.Create(readerStream, xrs); - } - - private void ReadDocumentStreamed(XmlReader xr, Stream sParentStream) - { - Debug.Assert(xr != null); - if(xr == null) throw new ArgumentNullException("xr"); - - m_ctxGroups.Clear(); - - KdbContext ctx = KdbContext.Null; - - uint uTagCounter = 0; - - bool bSupportsStatus = (m_slLogger != null); - long lStreamLength = 1; - try - { - sParentStream.Position.ToString(); // Test Position support - lStreamLength = sParentStream.Length; - } - catch(Exception) { bSupportsStatus = false; } - if(lStreamLength <= 0) { Debug.Assert(false); lStreamLength = 1; } - - m_bReadNextNode = true; - - while(true) - { - if(m_bReadNextNode) - { - if(!xr.Read()) break; - } - else m_bReadNextNode = true; - - switch(xr.NodeType) - { - case XmlNodeType.Element: - ctx = ReadXmlElement(ctx, xr); - break; - - case XmlNodeType.EndElement: - ctx = EndXmlElement(ctx, xr); - break; - - case XmlNodeType.XmlDeclaration: - break; // Ignore - - default: - Debug.Assert(false); - break; - } - - ++uTagCounter; - if(((uTagCounter % 256) == 0) && bSupportsStatus) - { - Debug.Assert(lStreamLength == sParentStream.Length); - uint uPct = (uint)((sParentStream.Position * 100) / - lStreamLength); - - // Clip percent value in case the stream reports incorrect - // position/length values (M120413) - if(uPct > 100) { Debug.Assert(false); uPct = 100; } - - m_slLogger.SetProgress(uPct); - } - } - - Debug.Assert(ctx == KdbContext.Null); - if(ctx != KdbContext.Null) throw new FormatException(); - - Debug.Assert(m_ctxGroups.Count == 0); - if(m_ctxGroups.Count != 0) throw new FormatException(); - } - - private KdbContext ReadXmlElement(KdbContext ctx, XmlReader xr) - { - Debug.Assert(xr.NodeType == XmlNodeType.Element); - - switch(ctx) - { - case KdbContext.Null: - if(xr.Name == ElemDocNode) - return SwitchContext(ctx, KdbContext.KeePassFile, xr); - else ReadUnknown(xr); - break; - - case KdbContext.KeePassFile: - if(xr.Name == ElemMeta) - return SwitchContext(ctx, KdbContext.Meta, xr); - else if(xr.Name == ElemRoot) - return SwitchContext(ctx, KdbContext.Root, xr); - else ReadUnknown(xr); - break; - - case KdbContext.Meta: - if(xr.Name == ElemGenerator) - ReadString(xr); // Ignore - else if(xr.Name == ElemHeaderHash) - { - // The header hash is typically only stored in - // KDBX <= 3.1 files, not in KDBX >= 4 files - // (here, the header is verified via a HMAC), - // but we also support it for KDBX >= 4 files - // (i.e. if it's present, we check it) - - string strHash = ReadString(xr); - if(!string.IsNullOrEmpty(strHash) && (m_pbHashOfHeader != null) && - !m_bRepairMode) - { - Debug.Assert(m_uFileVersion < FileVersion32_4); - - byte[] pbHash = Convert.FromBase64String(strHash); - if(!MemUtil.ArraysEqual(pbHash, m_pbHashOfHeader)) - throw new InvalidDataException(KLRes.FileCorrupted); - } - } - else if(xr.Name == ElemSettingsChanged) - m_pwDatabase.SettingsChanged = ReadTime(xr); - else if(xr.Name == ElemDbName) - m_pwDatabase.Name = ReadString(xr); - else if(xr.Name == ElemDbNameChanged) - m_pwDatabase.NameChanged = ReadTime(xr); - else if(xr.Name == ElemDbDesc) - m_pwDatabase.Description = ReadString(xr); - else if(xr.Name == ElemDbDescChanged) - m_pwDatabase.DescriptionChanged = ReadTime(xr); - else if(xr.Name == ElemDbDefaultUser) - m_pwDatabase.DefaultUserName = ReadString(xr); - else if(xr.Name == ElemDbDefaultUserChanged) - m_pwDatabase.DefaultUserNameChanged = ReadTime(xr); - else if(xr.Name == ElemDbMntncHistoryDays) - m_pwDatabase.MaintenanceHistoryDays = ReadUInt(xr, 365); - else if(xr.Name == ElemDbColor) - { - string strColor = ReadString(xr); - if(!string.IsNullOrEmpty(strColor)) - m_pwDatabase.Color = ColorTranslator.FromHtml(strColor); - } - else if(xr.Name == ElemDbKeyChanged) - m_pwDatabase.MasterKeyChanged = ReadTime(xr); - else if(xr.Name == ElemDbKeyChangeRec) - m_pwDatabase.MasterKeyChangeRec = ReadLong(xr, -1); - else if(xr.Name == ElemDbKeyChangeForce) - m_pwDatabase.MasterKeyChangeForce = ReadLong(xr, -1); - else if(xr.Name == ElemDbKeyChangeForceOnce) - m_pwDatabase.MasterKeyChangeForceOnce = ReadBool(xr, false); - else if(xr.Name == ElemMemoryProt) - return SwitchContext(ctx, KdbContext.MemoryProtection, xr); - else if(xr.Name == ElemCustomIcons) - return SwitchContext(ctx, KdbContext.CustomIcons, xr); - else if(xr.Name == ElemRecycleBinEnabled) - m_pwDatabase.RecycleBinEnabled = ReadBool(xr, true); - else if(xr.Name == ElemRecycleBinUuid) - m_pwDatabase.RecycleBinUuid = ReadUuid(xr); - else if(xr.Name == ElemRecycleBinChanged) - m_pwDatabase.RecycleBinChanged = ReadTime(xr); - else if(xr.Name == ElemEntryTemplatesGroup) - m_pwDatabase.EntryTemplatesGroup = ReadUuid(xr); - else if(xr.Name == ElemEntryTemplatesGroupChanged) - m_pwDatabase.EntryTemplatesGroupChanged = ReadTime(xr); - else if(xr.Name == ElemHistoryMaxItems) - m_pwDatabase.HistoryMaxItems = ReadInt(xr, -1); - else if(xr.Name == ElemHistoryMaxSize) - m_pwDatabase.HistoryMaxSize = ReadLong(xr, -1); - else if(xr.Name == ElemLastSelectedGroup) - m_pwDatabase.LastSelectedGroup = ReadUuid(xr); - else if(xr.Name == ElemLastTopVisibleGroup) - m_pwDatabase.LastTopVisibleGroup = ReadUuid(xr); - else if(xr.Name == ElemBinaries) - return SwitchContext(ctx, KdbContext.Binaries, xr); - else if(xr.Name == ElemCustomData) - return SwitchContext(ctx, KdbContext.CustomData, xr); - else ReadUnknown(xr); - break; - - case KdbContext.MemoryProtection: - if(xr.Name == ElemProtTitle) - m_pwDatabase.MemoryProtection.ProtectTitle = ReadBool(xr, false); - else if(xr.Name == ElemProtUserName) - m_pwDatabase.MemoryProtection.ProtectUserName = ReadBool(xr, false); - else if(xr.Name == ElemProtPassword) - m_pwDatabase.MemoryProtection.ProtectPassword = ReadBool(xr, true); - else if(xr.Name == ElemProtUrl) - m_pwDatabase.MemoryProtection.ProtectUrl = ReadBool(xr, false); - else if(xr.Name == ElemProtNotes) - m_pwDatabase.MemoryProtection.ProtectNotes = ReadBool(xr, false); - // else if(xr.Name == ElemProtAutoHide) - // m_pwDatabase.MemoryProtection.AutoEnableVisualHiding = ReadBool(xr, true); - else ReadUnknown(xr); - break; - - case KdbContext.CustomIcons: - if(xr.Name == ElemCustomIconItem) - return SwitchContext(ctx, KdbContext.CustomIcon, xr); - else ReadUnknown(xr); - break; - - case KdbContext.CustomIcon: - if(xr.Name == ElemCustomIconItemID) - m_uuidCustomIconID = ReadUuid(xr); - else if(xr.Name == ElemCustomIconItemData) - { - string strData = ReadString(xr); - if(!string.IsNullOrEmpty(strData)) - m_pbCustomIconData = Convert.FromBase64String(strData); - else { Debug.Assert(false); } - } - else ReadUnknown(xr); - break; - - case KdbContext.Binaries: - if(xr.Name == ElemBinary) - { - if(xr.MoveToAttribute(AttrId)) - { - string strKey = xr.Value; - ProtectedBinary pbData = ReadProtectedBinary(xr); - - int iKey; - if(!StrUtil.TryParseIntInvariant(strKey, out iKey)) - throw new FormatException(); - if(iKey < 0) throw new FormatException(); - - Debug.Assert(m_pbsBinaries.Get(iKey) == null); - Debug.Assert(m_pbsBinaries.Find(pbData) < 0); - m_pbsBinaries.Set(iKey, pbData); - } - else ReadUnknown(xr); - } - else ReadUnknown(xr); - break; - - case KdbContext.CustomData: - if(xr.Name == ElemStringDictExItem) - return SwitchContext(ctx, KdbContext.CustomDataItem, xr); - else ReadUnknown(xr); - break; - - case KdbContext.CustomDataItem: - if(xr.Name == ElemKey) - m_strCustomDataKey = ReadString(xr); - else if(xr.Name == ElemValue) - m_strCustomDataValue = ReadString(xr); - else ReadUnknown(xr); - break; - - case KdbContext.Root: - if(xr.Name == ElemGroup) - { - Debug.Assert(m_ctxGroups.Count == 0); - if(m_ctxGroups.Count != 0) throw new FormatException(); - - m_pwDatabase.RootGroup = new PwGroup(false, false); - m_ctxGroups.Push(m_pwDatabase.RootGroup); - m_ctxGroup = m_ctxGroups.Peek(); - - return SwitchContext(ctx, KdbContext.Group, xr); - } - else if(xr.Name == ElemDeletedObjects) - return SwitchContext(ctx, KdbContext.RootDeletedObjects, xr); - else ReadUnknown(xr); - break; - - case KdbContext.Group: - if(xr.Name == ElemUuid) - m_ctxGroup.Uuid = ReadUuid(xr); - else if(xr.Name == ElemName) - m_ctxGroup.Name = ReadString(xr); - else if(xr.Name == ElemNotes) - m_ctxGroup.Notes = ReadString(xr); - else if(xr.Name == ElemIcon) - m_ctxGroup.IconId = ReadIconId(xr, PwIcon.Folder); - else if(xr.Name == ElemCustomIconID) - m_ctxGroup.CustomIconUuid = ReadUuid(xr); - else if(xr.Name == ElemTimes) - return SwitchContext(ctx, KdbContext.GroupTimes, xr); - else if(xr.Name == ElemIsExpanded) - m_ctxGroup.IsExpanded = ReadBool(xr, true); - else if(xr.Name == ElemGroupDefaultAutoTypeSeq) - m_ctxGroup.DefaultAutoTypeSequence = ReadString(xr); - else if(xr.Name == ElemEnableAutoType) - m_ctxGroup.EnableAutoType = StrUtil.StringToBoolEx(ReadString(xr)); - else if(xr.Name == ElemEnableSearching) - m_ctxGroup.EnableSearching = StrUtil.StringToBoolEx(ReadString(xr)); - else if(xr.Name == ElemLastTopVisibleEntry) - m_ctxGroup.LastTopVisibleEntry = ReadUuid(xr); - else if(xr.Name == ElemCustomData) - return SwitchContext(ctx, KdbContext.GroupCustomData, xr); - else if(xr.Name == ElemGroup) - { - m_ctxGroup = new PwGroup(false, false); - m_ctxGroups.Peek().AddGroup(m_ctxGroup, true); - - m_ctxGroups.Push(m_ctxGroup); - - return SwitchContext(ctx, KdbContext.Group, xr); - } - else if(xr.Name == ElemEntry) - { - m_ctxEntry = new PwEntry(false, false); - m_ctxGroup.AddEntry(m_ctxEntry, true); - - m_bEntryInHistory = false; - return SwitchContext(ctx, KdbContext.Entry, xr); - } - else ReadUnknown(xr); - break; - - case KdbContext.GroupCustomData: - if(xr.Name == ElemStringDictExItem) - return SwitchContext(ctx, KdbContext.GroupCustomDataItem, xr); - else ReadUnknown(xr); - break; - - case KdbContext.GroupCustomDataItem: - if(xr.Name == ElemKey) - m_strGroupCustomDataKey = ReadString(xr); - else if(xr.Name == ElemValue) - m_strGroupCustomDataValue = ReadString(xr); - else ReadUnknown(xr); - break; - - case KdbContext.Entry: - if(xr.Name == ElemUuid) - m_ctxEntry.Uuid = ReadUuid(xr); - else if(xr.Name == ElemIcon) - m_ctxEntry.IconId = ReadIconId(xr, PwIcon.Key); - else if(xr.Name == ElemCustomIconID) - m_ctxEntry.CustomIconUuid = ReadUuid(xr); - else if(xr.Name == ElemFgColor) - { - string strColor = ReadString(xr); - if(!string.IsNullOrEmpty(strColor)) - m_ctxEntry.ForegroundColor = ColorTranslator.FromHtml(strColor); - } - else if(xr.Name == ElemBgColor) - { - string strColor = ReadString(xr); - if(!string.IsNullOrEmpty(strColor)) - m_ctxEntry.BackgroundColor = ColorTranslator.FromHtml(strColor); - } - else if(xr.Name == ElemOverrideUrl) - m_ctxEntry.OverrideUrl = ReadString(xr); - else if(xr.Name == ElemTags) - m_ctxEntry.Tags = StrUtil.StringToTags(ReadString(xr)); - else if(xr.Name == ElemTimes) - return SwitchContext(ctx, KdbContext.EntryTimes, xr); - else if(xr.Name == ElemString) - return SwitchContext(ctx, KdbContext.EntryString, xr); - else if(xr.Name == ElemBinary) - return SwitchContext(ctx, KdbContext.EntryBinary, xr); - else if(xr.Name == ElemAutoType) - return SwitchContext(ctx, KdbContext.EntryAutoType, xr); - else if(xr.Name == ElemCustomData) - return SwitchContext(ctx, KdbContext.EntryCustomData, xr); - else if(xr.Name == ElemHistory) - { - Debug.Assert(m_bEntryInHistory == false); - - if(m_bEntryInHistory == false) - { - m_ctxHistoryBase = m_ctxEntry; - return SwitchContext(ctx, KdbContext.EntryHistory, xr); - } - else ReadUnknown(xr); - } - else ReadUnknown(xr); - break; - - case KdbContext.GroupTimes: - case KdbContext.EntryTimes: - ITimeLogger tl = ((ctx == KdbContext.GroupTimes) ? - (ITimeLogger)m_ctxGroup : (ITimeLogger)m_ctxEntry); - Debug.Assert(tl != null); - - if(xr.Name == ElemCreationTime) - tl.CreationTime = ReadTime(xr); - else if(xr.Name == ElemLastModTime) - tl.LastModificationTime = ReadTime(xr); - else if(xr.Name == ElemLastAccessTime) - tl.LastAccessTime = ReadTime(xr); - else if(xr.Name == ElemExpiryTime) - tl.ExpiryTime = ReadTime(xr); - else if(xr.Name == ElemExpires) - tl.Expires = ReadBool(xr, false); - else if(xr.Name == ElemUsageCount) - tl.UsageCount = ReadULong(xr, 0); - else if(xr.Name == ElemLocationChanged) - tl.LocationChanged = ReadTime(xr); - else ReadUnknown(xr); - break; - - case KdbContext.EntryString: - if(xr.Name == ElemKey) - m_ctxStringName = ReadString(xr); - else if(xr.Name == ElemValue) - m_ctxStringValue = ReadProtectedString(xr); - else ReadUnknown(xr); - break; - - case KdbContext.EntryBinary: - if(xr.Name == ElemKey) - m_ctxBinaryName = ReadString(xr); - else if(xr.Name == ElemValue) - m_ctxBinaryValue = ReadProtectedBinary(xr); - else ReadUnknown(xr); - break; - - case KdbContext.EntryAutoType: - if(xr.Name == ElemAutoTypeEnabled) - m_ctxEntry.AutoType.Enabled = ReadBool(xr, true); - else if(xr.Name == ElemAutoTypeObfuscation) - m_ctxEntry.AutoType.ObfuscationOptions = - (AutoTypeObfuscationOptions)ReadInt(xr, 0); - else if(xr.Name == ElemAutoTypeDefaultSeq) - m_ctxEntry.AutoType.DefaultSequence = ReadString(xr); - else if(xr.Name == ElemAutoTypeItem) - return SwitchContext(ctx, KdbContext.EntryAutoTypeItem, xr); - else ReadUnknown(xr); - break; - - case KdbContext.EntryAutoTypeItem: - if(xr.Name == ElemWindow) - m_ctxATName = ReadString(xr); - else if(xr.Name == ElemKeystrokeSequence) - m_ctxATSeq = ReadString(xr); - else ReadUnknown(xr); - break; - - case KdbContext.EntryCustomData: - if(xr.Name == ElemStringDictExItem) - return SwitchContext(ctx, KdbContext.EntryCustomDataItem, xr); - else ReadUnknown(xr); - break; - - case KdbContext.EntryCustomDataItem: - if(xr.Name == ElemKey) - m_strEntryCustomDataKey = ReadString(xr); - else if(xr.Name == ElemValue) - m_strEntryCustomDataValue = ReadString(xr); - else ReadUnknown(xr); - break; - - case KdbContext.EntryHistory: - if(xr.Name == ElemEntry) - { - m_ctxEntry = new PwEntry(false, false); - m_ctxHistoryBase.History.Add(m_ctxEntry); - - m_bEntryInHistory = true; - return SwitchContext(ctx, KdbContext.Entry, xr); - } - else ReadUnknown(xr); - break; - - case KdbContext.RootDeletedObjects: - if(xr.Name == ElemDeletedObject) - { - m_ctxDeletedObject = new PwDeletedObject(); - m_pwDatabase.DeletedObjects.Add(m_ctxDeletedObject); - - return SwitchContext(ctx, KdbContext.DeletedObject, xr); - } - else ReadUnknown(xr); - break; - - case KdbContext.DeletedObject: - if(xr.Name == ElemUuid) - m_ctxDeletedObject.Uuid = ReadUuid(xr); - else if(xr.Name == ElemDeletionTime) - m_ctxDeletedObject.DeletionTime = ReadTime(xr); - else ReadUnknown(xr); - break; - - default: - ReadUnknown(xr); - break; - } - - return ctx; - } - - private KdbContext EndXmlElement(KdbContext ctx, XmlReader xr) - { - Debug.Assert(xr.NodeType == XmlNodeType.EndElement); - - if((ctx == KdbContext.KeePassFile) && (xr.Name == ElemDocNode)) - return KdbContext.Null; - else if((ctx == KdbContext.Meta) && (xr.Name == ElemMeta)) - return KdbContext.KeePassFile; - else if((ctx == KdbContext.Root) && (xr.Name == ElemRoot)) - return KdbContext.KeePassFile; - else if((ctx == KdbContext.MemoryProtection) && (xr.Name == ElemMemoryProt)) - return KdbContext.Meta; - else if((ctx == KdbContext.CustomIcons) && (xr.Name == ElemCustomIcons)) - return KdbContext.Meta; - else if((ctx == KdbContext.CustomIcon) && (xr.Name == ElemCustomIconItem)) - { - if(!m_uuidCustomIconID.Equals(PwUuid.Zero) && - (m_pbCustomIconData != null)) - m_pwDatabase.CustomIcons.Add(new PwCustomIcon( - m_uuidCustomIconID, m_pbCustomIconData)); - else { Debug.Assert(false); } - - m_uuidCustomIconID = PwUuid.Zero; - m_pbCustomIconData = null; - - return KdbContext.CustomIcons; - } - else if((ctx == KdbContext.Binaries) && (xr.Name == ElemBinaries)) - return KdbContext.Meta; - else if((ctx == KdbContext.CustomData) && (xr.Name == ElemCustomData)) - return KdbContext.Meta; - else if((ctx == KdbContext.CustomDataItem) && (xr.Name == ElemStringDictExItem)) - { - if((m_strCustomDataKey != null) && (m_strCustomDataValue != null)) - m_pwDatabase.CustomData.Set(m_strCustomDataKey, m_strCustomDataValue); - else { Debug.Assert(false); } - - m_strCustomDataKey = null; - m_strCustomDataValue = null; - - return KdbContext.CustomData; - } - else if((ctx == KdbContext.Group) && (xr.Name == ElemGroup)) - { - if(PwUuid.Zero.Equals(m_ctxGroup.Uuid)) - m_ctxGroup.Uuid = new PwUuid(true); // No assert (import) - - m_ctxGroups.Pop(); - - if(m_ctxGroups.Count == 0) - { - m_ctxGroup = null; - return KdbContext.Root; - } - else - { - m_ctxGroup = m_ctxGroups.Peek(); - return KdbContext.Group; - } - } - else if((ctx == KdbContext.GroupTimes) && (xr.Name == ElemTimes)) - return KdbContext.Group; - else if((ctx == KdbContext.GroupCustomData) && (xr.Name == ElemCustomData)) - return KdbContext.Group; - else if((ctx == KdbContext.GroupCustomDataItem) && (xr.Name == ElemStringDictExItem)) - { - if((m_strGroupCustomDataKey != null) && (m_strGroupCustomDataValue != null)) - m_ctxGroup.CustomData.Set(m_strGroupCustomDataKey, m_strGroupCustomDataValue); - else { Debug.Assert(false); } - - m_strGroupCustomDataKey = null; - m_strGroupCustomDataValue = null; - - return KdbContext.GroupCustomData; - } - else if((ctx == KdbContext.Entry) && (xr.Name == ElemEntry)) - { - // Create new UUID if absent - if(PwUuid.Zero.Equals(m_ctxEntry.Uuid)) - m_ctxEntry.Uuid = new PwUuid(true); // No assert (import) - - if(m_bEntryInHistory) - { - m_ctxEntry = m_ctxHistoryBase; - return KdbContext.EntryHistory; - } - - return KdbContext.Group; - } - else if((ctx == KdbContext.EntryTimes) && (xr.Name == ElemTimes)) - return KdbContext.Entry; - else if((ctx == KdbContext.EntryString) && (xr.Name == ElemString)) - { - m_ctxEntry.Strings.Set(m_ctxStringName, m_ctxStringValue); - m_ctxStringName = null; - m_ctxStringValue = null; - return KdbContext.Entry; - } - else if((ctx == KdbContext.EntryBinary) && (xr.Name == ElemBinary)) - { - if(string.IsNullOrEmpty(m_strDetachBins)) - m_ctxEntry.Binaries.Set(m_ctxBinaryName, m_ctxBinaryValue); - else - { - SaveBinary(m_ctxBinaryName, m_ctxBinaryValue, m_strDetachBins); - - m_ctxBinaryValue = null; - GC.Collect(); - } - - m_ctxBinaryName = null; - m_ctxBinaryValue = null; - return KdbContext.Entry; - } - else if((ctx == KdbContext.EntryAutoType) && (xr.Name == ElemAutoType)) - return KdbContext.Entry; - else if((ctx == KdbContext.EntryAutoTypeItem) && (xr.Name == ElemAutoTypeItem)) - { - AutoTypeAssociation atAssoc = new AutoTypeAssociation(m_ctxATName, - m_ctxATSeq); - m_ctxEntry.AutoType.Add(atAssoc); - m_ctxATName = null; - m_ctxATSeq = null; - return KdbContext.EntryAutoType; - } - else if((ctx == KdbContext.EntryCustomData) && (xr.Name == ElemCustomData)) - return KdbContext.Entry; - else if((ctx == KdbContext.EntryCustomDataItem) && (xr.Name == ElemStringDictExItem)) - { - if((m_strEntryCustomDataKey != null) && (m_strEntryCustomDataValue != null)) - m_ctxEntry.CustomData.Set(m_strEntryCustomDataKey, m_strEntryCustomDataValue); - else { Debug.Assert(false); } - - m_strEntryCustomDataKey = null; - m_strEntryCustomDataValue = null; - - return KdbContext.EntryCustomData; - } - else if((ctx == KdbContext.EntryHistory) && (xr.Name == ElemHistory)) - { - m_bEntryInHistory = false; - return KdbContext.Entry; - } - else if((ctx == KdbContext.RootDeletedObjects) && (xr.Name == ElemDeletedObjects)) - return KdbContext.Root; - else if((ctx == KdbContext.DeletedObject) && (xr.Name == ElemDeletedObject)) - { - m_ctxDeletedObject = null; - return KdbContext.RootDeletedObjects; - } - else - { - Debug.Assert(false); - throw new FormatException(); - } - } - - private string ReadString(XmlReader xr) - { - XorredBuffer xb = ProcessNode(xr); - if(xb != null) - { - byte[] pb = xb.ReadPlainText(); - if(pb.Length == 0) return string.Empty; - return StrUtil.Utf8.GetString(pb, 0, pb.Length); - } - - m_bReadNextNode = false; // ReadElementString skips end tag -#if ModernKeePassLib - return xr.ReadElementContentAsString(); -#else - return xr.ReadElementString(); -#endif - } - - private string ReadStringRaw(XmlReader xr) - { - m_bReadNextNode = false; // ReadElementString skips end tag -#if ModernKeePassLib - return xr.ReadElementContentAsString(); -#else - return xr.ReadElementString(); -#endif - } - - private byte[] ReadBase64(XmlReader xr, bool bRaw) - { - // if(bRaw) return ReadBase64RawInChunks(xr); - - string str = (bRaw ? ReadStringRaw(xr) : ReadString(xr)); - if(string.IsNullOrEmpty(str)) return MemUtil.EmptyByteArray; - - return Convert.FromBase64String(str); - } - - /* private byte[] m_pbBase64ReadBuf = new byte[1024 * 1024 * 3]; - private byte[] ReadBase64RawInChunks(XmlReader xr) - { - xr.MoveToContent(); - - List lParts = new List(); - byte[] pbBuf = m_pbBase64ReadBuf; - while(true) - { - int cb = xr.ReadElementContentAsBase64(pbBuf, 0, pbBuf.Length); - if(cb == 0) break; - - byte[] pb = new byte[cb]; - Array.Copy(pbBuf, 0, pb, 0, cb); - lParts.Add(pb); - - // No break when cb < pbBuf.Length, because ReadElementContentAsBase64 - // moves to the next XML node only when returning 0 - } - m_bReadNextNode = false; - - if(lParts.Count == 0) return MemUtil.EmptyByteArray; - if(lParts.Count == 1) return lParts[0]; - - long cbRes = 0; - for(int i = 0; i < lParts.Count; ++i) - cbRes += lParts[i].Length; - - byte[] pbRes = new byte[cbRes]; - int cbCur = 0; - for(int i = 0; i < lParts.Count; ++i) - { - Array.Copy(lParts[i], 0, pbRes, cbCur, lParts[i].Length); - cbCur += lParts[i].Length; - } - - return pbRes; - } */ - - private bool ReadBool(XmlReader xr, bool bDefault) - { - string str = ReadString(xr); - if(str == ValTrue) return true; - else if(str == ValFalse) return false; - - Debug.Assert(false); - return bDefault; - } - - private PwUuid ReadUuid(XmlReader xr) - { - byte[] pb = ReadBase64(xr, false); - if(pb.Length == 0) return PwUuid.Zero; - return new PwUuid(pb); - } - - private int ReadInt(XmlReader xr, int nDefault) - { - string str = ReadString(xr); - - int n; - if(StrUtil.TryParseIntInvariant(str, out n)) return n; - - // Backward compatibility - if(StrUtil.TryParseInt(str, out n)) return n; - - Debug.Assert(false); - return nDefault; - } - - private uint ReadUInt(XmlReader xr, uint uDefault) - { - string str = ReadString(xr); - - uint u; - if(StrUtil.TryParseUIntInvariant(str, out u)) return u; - - // Backward compatibility - if(StrUtil.TryParseUInt(str, out u)) return u; - - Debug.Assert(false); - return uDefault; - } - - private long ReadLong(XmlReader xr, long lDefault) - { - string str = ReadString(xr); - - long l; - if(StrUtil.TryParseLongInvariant(str, out l)) return l; - - // Backward compatibility - if(StrUtil.TryParseLong(str, out l)) return l; - - Debug.Assert(false); - return lDefault; - } - - private ulong ReadULong(XmlReader xr, ulong uDefault) - { - string str = ReadString(xr); - - ulong u; - if(StrUtil.TryParseULongInvariant(str, out u)) return u; - - // Backward compatibility - if(StrUtil.TryParseULong(str, out u)) return u; - - Debug.Assert(false); - return uDefault; - } - - private DateTime ReadTime(XmlReader xr) - { - // Cf. WriteObject(string, DateTime) - if((m_format == KdbxFormat.Default) && (m_uFileVersion >= FileVersion32_4)) - { - // long l = ReadLong(xr, -1); - // if(l != -1) return DateTime.FromBinary(l); - - byte[] pb = ReadBase64(xr, false); - if(pb.Length != 8) - { - Debug.Assert(false); - byte[] pb8 = new byte[8]; - Array.Copy(pb, pb8, Math.Min(pb.Length, 8)); // Little-endian - pb = pb8; - } - long lSec = MemUtil.BytesToInt64(pb); - return new DateTime(lSec * TimeSpan.TicksPerSecond, DateTimeKind.Utc); - } - else - { - string str = ReadString(xr); - - DateTime dt; - if(TimeUtil.TryDeserializeUtc(str, out dt)) return dt; - } - - Debug.Assert(false); - return m_dtNow; - } - - private PwIcon ReadIconId(XmlReader xr, PwIcon icDefault) - { - int i = ReadInt(xr, (int)icDefault); - if((i >= 0) && (i < (int)PwIcon.Count)) return (PwIcon)i; - - Debug.Assert(false); - return icDefault; - } - - private ProtectedString ReadProtectedString(XmlReader xr) - { - XorredBuffer xb = ProcessNode(xr); - if(xb != null) return new ProtectedString(true, xb); - - bool bProtect = false; - if(m_format == KdbxFormat.PlainXml) - { - if(xr.MoveToAttribute(AttrProtectedInMemPlainXml)) - { - string strProtect = xr.Value; - bProtect = ((strProtect != null) && (strProtect == ValTrue)); - } - } - - ProtectedString ps = new ProtectedString(bProtect, ReadString(xr)); - return ps; - } - - private ProtectedBinary ReadProtectedBinary(XmlReader xr) - { - if(xr.MoveToAttribute(AttrRef)) - { - string strRef = xr.Value; - if(!string.IsNullOrEmpty(strRef)) - { - int iRef; - if(StrUtil.TryParseIntInvariant(strRef, out iRef)) - { - ProtectedBinary pb = m_pbsBinaries.Get(iRef); - if(pb != null) - { - // https://sourceforge.net/p/keepass/feature-requests/2023/ - xr.MoveToElement(); -#if DEBUG - string strInner = ReadStringRaw(xr); - Debug.Assert(string.IsNullOrEmpty(strInner)); -#else - ReadStringRaw(xr); -#endif - - return pb; - } - else { Debug.Assert(false); } - } - else { Debug.Assert(false); } - } - else { Debug.Assert(false); } - } - - bool bCompressed = false; - if(xr.MoveToAttribute(AttrCompressed)) - bCompressed = (xr.Value == ValTrue); - - XorredBuffer xb = ProcessNode(xr); - if(xb != null) - { - Debug.Assert(!bCompressed); // See SubWriteValue(ProtectedBinary value) - return new ProtectedBinary(true, xb); - } - - byte[] pbData = ReadBase64(xr, true); - if(pbData.Length == 0) return new ProtectedBinary(); - - if(bCompressed) pbData = MemUtil.Decompress(pbData); - return new ProtectedBinary(false, pbData); - } - - private void ReadUnknown(XmlReader xr) - { - Debug.Assert(false); // Unknown node! - - if(xr.IsEmptyElement) return; - - string strUnknownName = xr.Name; - ProcessNode(xr); - - while(xr.Read()) - { - if(xr.NodeType == XmlNodeType.EndElement) break; - if(xr.NodeType != XmlNodeType.Element) continue; - - ReadUnknown(xr); - } - - Debug.Assert(xr.Name == strUnknownName); - } - - private XorredBuffer ProcessNode(XmlReader xr) - { - // Debug.Assert(xr.NodeType == XmlNodeType.Element); - - XorredBuffer xb = null; - if(xr.HasAttributes) - { - if(xr.MoveToAttribute(AttrProtected)) - { - if(xr.Value == ValTrue) - { - xr.MoveToElement(); - - byte[] pbEncrypted = ReadBase64(xr, true); - byte[] pbPad = m_randomStream.GetRandomBytes((uint)pbEncrypted.Length); - - xb = new XorredBuffer(pbEncrypted, pbPad); - } - } - } - - return xb; - } - - private static KdbContext SwitchContext(KdbContext ctxCurrent, - KdbContext ctxNew, XmlReader xr) - { - if(xr.IsEmptyElement) return ctxCurrent; - return ctxNew; - } - } -} diff --git a/ModernKeePassLib/Serialization/KdbxFile.Read.cs b/ModernKeePassLib/Serialization/KdbxFile.Read.cs deleted file mode 100644 index 058977a..0000000 --- a/ModernKeePassLib/Serialization/KdbxFile.Read.cs +++ /dev/null @@ -1,592 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -// #define KDBX_BENCHMARK - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Security; -using System.Text; -using System.Xml; - -#if !ModernKeePassLib && !KeePassUAP -using System.Security.Cryptography; -#endif - -#if !KeePassLibSD -using System.IO.Compression; -#else -using KeePassLibSD; -#endif - -using ModernKeePassLib.Collections; -using ModernKeePassLib.Cryptography; -using ModernKeePassLib.Cryptography.Cipher; -using ModernKeePassLib.Cryptography.KeyDerivation; -using ModernKeePassLib.Interfaces; -using ModernKeePassLib.Keys; -using ModernKeePassLib.Resources; -using ModernKeePassLib.Security; -using ModernKeePassLib.Utility; - -namespace ModernKeePassLib.Serialization -{ - /// - /// Serialization to KeePass KDBX files. - /// - public sealed partial class KdbxFile - { - /// - /// Load a KDBX file. - /// - /// File to load. - /// Format. - /// Status logger (optional). - public void Load(string strFilePath, KdbxFormat fmt, IStatusLogger slLogger) - { - IOConnectionInfo ioc = IOConnectionInfo.FromPath(strFilePath); - Load(IOConnection.OpenRead(ioc), fmt, slLogger); - } - - /// - /// Load a KDBX file from a stream. - /// - /// Stream to read the data from. Must contain - /// a KDBX stream. - /// Format. - /// Status logger (optional). - public void Load(Stream sSource, KdbxFormat fmt, IStatusLogger slLogger) - { - Debug.Assert(sSource != null); - if(sSource == null) throw new ArgumentNullException("sSource"); - - if(m_bUsedOnce) - throw new InvalidOperationException("Do not reuse KdbxFile objects!"); - m_bUsedOnce = true; - -#if KDBX_BENCHMARK - Stopwatch swTime = Stopwatch.StartNew(); -#endif - - m_format = fmt; - m_slLogger = slLogger; - - m_pbsBinaries.Clear(); - - UTF8Encoding encNoBom = StrUtil.Utf8; - byte[] pbCipherKey = null; - byte[] pbHmacKey64 = null; - - List lStreams = new List(); - lStreams.Add(sSource); - - HashingStreamEx sHashing = new HashingStreamEx(sSource, false, null); - lStreams.Add(sHashing); - - try - { - Stream sXml; - if(fmt == KdbxFormat.Default) - { - BinaryReaderEx br = new BinaryReaderEx(sHashing, - encNoBom, KLRes.FileCorrupted); - byte[] pbHeader = LoadHeader(br); - m_pbHashOfHeader = CryptoUtil.HashSha256(pbHeader); - - int cbEncKey, cbEncIV; - ICipherEngine iCipher = GetCipher(out cbEncKey, out cbEncIV); - - ComputeKeys(out pbCipherKey, cbEncKey, out pbHmacKey64); - - string strIncomplete = KLRes.FileHeaderCorrupted + " " + - KLRes.FileIncomplete; - - Stream sPlain; - if(m_uFileVersion < FileVersion32_4) - { - Stream sDecrypted = EncryptStream(sHashing, iCipher, - pbCipherKey, cbEncIV, false); - if((sDecrypted == null) || (sDecrypted == sHashing)) - throw new SecurityException(KLRes.CryptoStreamFailed); - lStreams.Add(sDecrypted); - - BinaryReaderEx brDecrypted = new BinaryReaderEx(sDecrypted, - encNoBom, strIncomplete); - byte[] pbStoredStartBytes = brDecrypted.ReadBytes(32); - - if((m_pbStreamStartBytes == null) || (m_pbStreamStartBytes.Length != 32)) - throw new EndOfStreamException(strIncomplete); - if(!MemUtil.ArraysEqual(pbStoredStartBytes, m_pbStreamStartBytes)) - throw new InvalidCompositeKeyException(); - - sPlain = new HashedBlockStream(sDecrypted, false, 0, !m_bRepairMode); - } - else // KDBX >= 4 - { - byte[] pbStoredHash = MemUtil.Read(sHashing, 32); - if((pbStoredHash == null) || (pbStoredHash.Length != 32)) - throw new EndOfStreamException(strIncomplete); - if(!MemUtil.ArraysEqual(m_pbHashOfHeader, pbStoredHash)) - throw new InvalidDataException(KLRes.FileHeaderCorrupted); - - byte[] pbHeaderHmac = ComputeHeaderHmac(pbHeader, pbHmacKey64); - byte[] pbStoredHmac = MemUtil.Read(sHashing, 32); - if((pbStoredHmac == null) || (pbStoredHmac.Length != 32)) - throw new EndOfStreamException(strIncomplete); - if(!MemUtil.ArraysEqual(pbHeaderHmac, pbStoredHmac)) - throw new InvalidCompositeKeyException(); - - HmacBlockStream sBlocks = new HmacBlockStream(sHashing, - false, !m_bRepairMode, pbHmacKey64); - lStreams.Add(sBlocks); - - sPlain = EncryptStream(sBlocks, iCipher, pbCipherKey, - cbEncIV, false); - if((sPlain == null) || (sPlain == sBlocks)) - throw new SecurityException(KLRes.CryptoStreamFailed); - } - lStreams.Add(sPlain); - - if(m_pwDatabase.Compression == PwCompressionAlgorithm.GZip) - { - sXml = new GZipStream(sPlain, CompressionMode.Decompress); - lStreams.Add(sXml); - } - else sXml = sPlain; - - if(m_uFileVersion >= FileVersion32_4) - LoadInnerHeader(sXml); // Binary header before XML - } - else if(fmt == KdbxFormat.PlainXml) - sXml = sHashing; - else { Debug.Assert(false); throw new ArgumentOutOfRangeException("fmt"); } - - if(fmt == KdbxFormat.Default) - { - if(m_pbInnerRandomStreamKey == null) - { - Debug.Assert(false); - throw new SecurityException("Invalid inner random stream key!"); - } - - m_randomStream = new CryptoRandomStream(m_craInnerRandomStream, - m_pbInnerRandomStreamKey); - } - -#if KeePassDebug_WriteXml - // FileStream fsOut = new FileStream("Raw.xml", FileMode.Create, - // FileAccess.Write, FileShare.None); - // try - // { - // while(true) - // { - // int b = sXml.ReadByte(); - // if(b == -1) break; - // fsOut.WriteByte((byte)b); - // } - // } - // catch(Exception) { } - // fsOut.Close(); -#endif - - ReadXmlStreamed(sXml, sHashing); - // ReadXmlDom(sXml); - } -#if !ModernKeePassLib - catch(CryptographicException) // Thrown on invalid padding - { - throw new CryptographicException(KLRes.FileCorrupted); - } -#endif - finally - { - if(pbCipherKey != null) MemUtil.ZeroByteArray(pbCipherKey); - if(pbHmacKey64 != null) MemUtil.ZeroByteArray(pbHmacKey64); - - CommonCleanUpRead(lStreams, sHashing); - } - -#if KDBX_BENCHMARK - swTime.Stop(); - MessageService.ShowInfo("Loading KDBX took " + - swTime.ElapsedMilliseconds.ToString() + " ms."); -#endif - } - - private void CommonCleanUpRead(List lStreams, HashingStreamEx sHashing) - { - CloseStreams(lStreams); - - Debug.Assert(lStreams.Contains(sHashing)); // sHashing must be closed - m_pbHashOfFileOnDisk = sHashing.Hash; - Debug.Assert(m_pbHashOfFileOnDisk != null); - - CleanUpInnerRandomStream(); - - // Reset memory protection settings (to always use reasonable - // defaults) - m_pwDatabase.MemoryProtection = new MemoryProtectionConfig(); - - // Remove old backups (this call is required here in order to apply - // the default history maintenance settings for people upgrading from - // KeePass <= 2.14 to >= 2.15; also it ensures history integrity in - // case a different application has created the KDBX file and ignored - // the history maintenance settings) - m_pwDatabase.MaintainBackups(); // Don't mark database as modified - - // Expand the root group, such that in case the user accidently - // collapses the root group he can simply reopen the database - PwGroup pgRoot = m_pwDatabase.RootGroup; - if(pgRoot != null) pgRoot.IsExpanded = true; - else { Debug.Assert(false); } - - m_pbHashOfHeader = null; - } - - private byte[] LoadHeader(BinaryReaderEx br) - { - string strPrevExcpText = br.ReadExceptionText; - br.ReadExceptionText = KLRes.FileHeaderCorrupted + " " + - KLRes.FileIncompleteExpc; - - MemoryStream msHeader = new MemoryStream(); - Debug.Assert(br.CopyDataTo == null); - br.CopyDataTo = msHeader; - - byte[] pbSig1 = br.ReadBytes(4); - uint uSig1 = MemUtil.BytesToUInt32(pbSig1); - byte[] pbSig2 = br.ReadBytes(4); - uint uSig2 = MemUtil.BytesToUInt32(pbSig2); - - if((uSig1 == FileSignatureOld1) && (uSig2 == FileSignatureOld2)) - throw new OldFormatException(PwDefs.ShortProductName + @" 1.x", - OldFormatException.OldFormatType.KeePass1x); - - if((uSig1 == FileSignature1) && (uSig2 == FileSignature2)) { } - else if((uSig1 == FileSignaturePreRelease1) && (uSig2 == - FileSignaturePreRelease2)) { } - else throw new FormatException(KLRes.FileSigInvalid); - - byte[] pb = br.ReadBytes(4); - uint uVersion = MemUtil.BytesToUInt32(pb); - if((uVersion & FileVersionCriticalMask) > (FileVersion32 & FileVersionCriticalMask)) - throw new FormatException(KLRes.FileVersionUnsupported + - Environment.NewLine + KLRes.FileNewVerReq); - m_uFileVersion = uVersion; - - while(true) - { - if(!ReadHeaderField(br)) break; - } - - br.CopyDataTo = null; - byte[] pbHeader = msHeader.ToArray(); - msHeader.Dispose(); - - br.ReadExceptionText = strPrevExcpText; - return pbHeader; - } - - private bool ReadHeaderField(BinaryReaderEx brSource) - { - Debug.Assert(brSource != null); - if(brSource == null) throw new ArgumentNullException("brSource"); - - byte btFieldID = brSource.ReadByte(); - - int cbSize; - Debug.Assert(m_uFileVersion > 0); - if(m_uFileVersion < FileVersion32_4) - cbSize = (int)MemUtil.BytesToUInt16(brSource.ReadBytes(2)); - else cbSize = MemUtil.BytesToInt32(brSource.ReadBytes(4)); - if(cbSize < 0) throw new FormatException(KLRes.FileCorrupted); - - byte[] pbData = MemUtil.EmptyByteArray; - if(cbSize > 0) pbData = brSource.ReadBytes(cbSize); - - bool bResult = true; - KdbxHeaderFieldID kdbID = (KdbxHeaderFieldID)btFieldID; - switch(kdbID) - { - case KdbxHeaderFieldID.EndOfHeader: - bResult = false; // Returning false indicates end of header - break; - - case KdbxHeaderFieldID.CipherID: - SetCipher(pbData); - break; - - case KdbxHeaderFieldID.CompressionFlags: - SetCompressionFlags(pbData); - break; - - case KdbxHeaderFieldID.MasterSeed: - m_pbMasterSeed = pbData; - CryptoRandom.Instance.AddEntropy(pbData); - break; - - // Obsolete; for backward compatibility only - case KdbxHeaderFieldID.TransformSeed: - Debug.Assert(m_uFileVersion < FileVersion32_4); - - AesKdf kdfS = new AesKdf(); - if(!m_pwDatabase.KdfParameters.KdfUuid.Equals(kdfS.Uuid)) - m_pwDatabase.KdfParameters = kdfS.GetDefaultParameters(); - - // m_pbTransformSeed = pbData; - m_pwDatabase.KdfParameters.SetByteArray(AesKdf.ParamSeed, pbData); - - CryptoRandom.Instance.AddEntropy(pbData); - break; - - // Obsolete; for backward compatibility only - case KdbxHeaderFieldID.TransformRounds: - Debug.Assert(m_uFileVersion < FileVersion32_4); - - AesKdf kdfR = new AesKdf(); - if(!m_pwDatabase.KdfParameters.KdfUuid.Equals(kdfR.Uuid)) - m_pwDatabase.KdfParameters = kdfR.GetDefaultParameters(); - - // m_pwDatabase.KeyEncryptionRounds = MemUtil.BytesToUInt64(pbData); - m_pwDatabase.KdfParameters.SetUInt64(AesKdf.ParamRounds, - MemUtil.BytesToUInt64(pbData)); - break; - - case KdbxHeaderFieldID.EncryptionIV: - m_pbEncryptionIV = pbData; - break; - - case KdbxHeaderFieldID.InnerRandomStreamKey: - Debug.Assert(m_uFileVersion < FileVersion32_4); - Debug.Assert(m_pbInnerRandomStreamKey == null); - m_pbInnerRandomStreamKey = pbData; - CryptoRandom.Instance.AddEntropy(pbData); - break; - - case KdbxHeaderFieldID.StreamStartBytes: - Debug.Assert(m_uFileVersion < FileVersion32_4); - m_pbStreamStartBytes = pbData; - break; - - case KdbxHeaderFieldID.InnerRandomStreamID: - Debug.Assert(m_uFileVersion < FileVersion32_4); - SetInnerRandomStreamID(pbData); - break; - - case KdbxHeaderFieldID.KdfParameters: - m_pwDatabase.KdfParameters = KdfParameters.DeserializeExt(pbData); - break; - - case KdbxHeaderFieldID.PublicCustomData: - Debug.Assert(m_pwDatabase.PublicCustomData.Count == 0); - m_pwDatabase.PublicCustomData = VariantDictionary.Deserialize(pbData); - break; - - default: - Debug.Assert(false); - if(m_slLogger != null) - m_slLogger.SetText(KLRes.UnknownHeaderId + @": " + - kdbID.ToString() + "!", LogStatusType.Warning); - break; - } - - return bResult; - } - - private void LoadInnerHeader(Stream s) - { - BinaryReaderEx br = new BinaryReaderEx(s, StrUtil.Utf8, - KLRes.FileCorrupted + " " + KLRes.FileIncompleteExpc); - - while(true) - { - if(!ReadInnerHeaderField(br)) break; - } - } - - private bool ReadInnerHeaderField(BinaryReaderEx br) - { - Debug.Assert(br != null); - if(br == null) throw new ArgumentNullException("br"); - - byte btFieldID = br.ReadByte(); - - int cbSize = MemUtil.BytesToInt32(br.ReadBytes(4)); - if(cbSize < 0) throw new FormatException(KLRes.FileCorrupted); - - byte[] pbData = MemUtil.EmptyByteArray; - if(cbSize > 0) pbData = br.ReadBytes(cbSize); - - bool bResult = true; - KdbxInnerHeaderFieldID kdbID = (KdbxInnerHeaderFieldID)btFieldID; - switch(kdbID) - { - case KdbxInnerHeaderFieldID.EndOfHeader: - bResult = false; // Returning false indicates end of header - break; - - case KdbxInnerHeaderFieldID.InnerRandomStreamID: - SetInnerRandomStreamID(pbData); - break; - - case KdbxInnerHeaderFieldID.InnerRandomStreamKey: - Debug.Assert(m_pbInnerRandomStreamKey == null); - m_pbInnerRandomStreamKey = pbData; - CryptoRandom.Instance.AddEntropy(pbData); - break; - - case KdbxInnerHeaderFieldID.Binary: - if(pbData.Length < 1) throw new FormatException(); - KdbxBinaryFlags f = (KdbxBinaryFlags)pbData[0]; - bool bProt = ((f & KdbxBinaryFlags.Protected) != KdbxBinaryFlags.None); - - ProtectedBinary pb = new ProtectedBinary(bProt, pbData, - 1, pbData.Length - 1); - m_pbsBinaries.Add(pb); - - if(bProt) MemUtil.ZeroByteArray(pbData); - break; - - default: - Debug.Assert(false); - break; - } - - return bResult; - } - - private void SetCipher(byte[] pbID) - { - if((pbID == null) || (pbID.Length != (int)PwUuid.UuidSize)) - throw new FormatException(KLRes.FileUnknownCipher); - - m_pwDatabase.DataCipherUuid = new PwUuid(pbID); - } - - private void SetCompressionFlags(byte[] pbFlags) - { - int nID = (int)MemUtil.BytesToUInt32(pbFlags); - if((nID < 0) || (nID >= (int)PwCompressionAlgorithm.Count)) - throw new FormatException(KLRes.FileUnknownCompression); - - m_pwDatabase.Compression = (PwCompressionAlgorithm)nID; - } - - private void SetInnerRandomStreamID(byte[] pbID) - { - uint uID = MemUtil.BytesToUInt32(pbID); - if(uID >= (uint)CrsAlgorithm.Count) - throw new FormatException(KLRes.FileUnknownCipher); - - m_craInnerRandomStream = (CrsAlgorithm)uID; - } - - [Obsolete] - public static List ReadEntries(Stream msData) - { - return ReadEntries(msData, null, false); - } - - [Obsolete] - public static List ReadEntries(PwDatabase pdContext, Stream msData) - { - return ReadEntries(msData, pdContext, true); - } - - /// - /// Read entries from a stream. - /// - /// Input stream to read the entries from. - /// Context database (e.g. for storing icons). - /// If true, custom icons required by - /// the loaded entries are copied to the context database. - /// Loaded entries. - public static List ReadEntries(Stream msData, PwDatabase pdContext, - bool bCopyIcons) - { - List lEntries = new List(); - - if(msData == null) { Debug.Assert(false); return lEntries; } - // pdContext may be null - - /* KdbxFile f = new KdbxFile(pwDatabase); - f.m_format = KdbxFormat.PlainXml; - - XmlDocument doc = XmlUtilEx.CreateXmlDocument(); - doc.Load(msData); - - XmlElement el = doc.DocumentElement; - if(el.Name != ElemRoot) throw new FormatException(); - - List vEntries = new List(); - - foreach(XmlNode xmlChild in el.ChildNodes) - { - if(xmlChild.Name == ElemEntry) - { - PwEntry pe = f.ReadEntry(xmlChild); - pe.Uuid = new PwUuid(true); - - foreach(PwEntry peHistory in pe.History) - peHistory.Uuid = pe.Uuid; - - vEntries.Add(pe); - } - else { Debug.Assert(false); } - } - - return vEntries; */ - - PwDatabase pd = new PwDatabase(); - pd.New(new IOConnectionInfo(), new CompositeKey()); - - KdbxFile f = new KdbxFile(pd); - f.Load(msData, KdbxFormat.PlainXml, null); - - foreach(PwEntry pe in pd.RootGroup.Entries) - { - pe.SetUuid(new PwUuid(true), true); - lEntries.Add(pe); - - if(bCopyIcons && (pdContext != null)) - { - PwUuid pu = pe.CustomIconUuid; - if(!pu.Equals(PwUuid.Zero)) - { - int iSrc = pd.GetCustomIconIndex(pu); - int iDst = pdContext.GetCustomIconIndex(pu); - - if(iSrc < 0) { Debug.Assert(false); } - else if(iDst < 0) - { - pdContext.CustomIcons.Add(pd.CustomIcons[iSrc]); - - pdContext.Modified = true; - pdContext.UINeedsIconUpdate = true; - } - } - } - } - - return lEntries; - } - } -} diff --git a/ModernKeePassLib/Serialization/KdbxFile.Write.cs b/ModernKeePassLib/Serialization/KdbxFile.Write.cs deleted file mode 100644 index 90ed336..0000000 --- a/ModernKeePassLib/Serialization/KdbxFile.Write.cs +++ /dev/null @@ -1,1051 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Globalization; -using System.IO; -using System.Security; -using System.Text; -using System.Xml; - -#if !ModernKeePassLib && !KeePassUAP -using System.Drawing; -using System.Security.Cryptography; -#endif - -#if KeePassLibSD -using KeePassLibSD; -#else -using System.IO.Compression; -#endif - -using ModernKeePassLib.Collections; -using ModernKeePassLib.Cryptography; -using ModernKeePassLib.Cryptography.Cipher; -using ModernKeePassLib.Cryptography.KeyDerivation; -using ModernKeePassLib.Delegates; -using ModernKeePassLib.Interfaces; -using ModernKeePassLib.Keys; -using ModernKeePassLib.Resources; -using ModernKeePassLib.Security; -using ModernKeePassLib.Utility; - -namespace ModernKeePassLib.Serialization -{ - /// - /// Serialization to KeePass KDBX files. - /// - public sealed partial class KdbxFile - { - // public void Save(string strFile, PwGroup pgDataSource, KdbxFormat fmt, - // IStatusLogger slLogger) - // { - // bool bMadeUnhidden = UrlUtil.UnhideFile(strFile); - // - // IOConnectionInfo ioc = IOConnectionInfo.FromPath(strFile); - // this.Save(IOConnection.OpenWrite(ioc), pgDataSource, format, slLogger); - // - // if(bMadeUnhidden) UrlUtil.HideFile(strFile, true); // Hide again - // } - - /// - /// Save the contents of the current PwDatabase to a KDBX file. - /// - /// Stream to write the KDBX file into. - /// Group containing all groups and - /// entries to write. If null, the complete database will - /// be written. - /// Format of the file to create. - /// Logger that recieves status information. - public void Save(Stream sSaveTo, PwGroup pgDataSource, KdbxFormat fmt, - IStatusLogger slLogger) - { - Debug.Assert(sSaveTo != null); - if(sSaveTo == null) throw new ArgumentNullException("sSaveTo"); - - if(m_bUsedOnce) - throw new InvalidOperationException("Do not reuse KdbxFile objects!"); - m_bUsedOnce = true; - - m_format = fmt; - m_slLogger = slLogger; - m_xmlWriter = null; - - PwGroup pgRoot = (pgDataSource ?? m_pwDatabase.RootGroup); - UTF8Encoding encNoBom = StrUtil.Utf8; - CryptoRandom cr = CryptoRandom.Instance; - byte[] pbCipherKey = null; - byte[] pbHmacKey64 = null; - - m_pbsBinaries.Clear(); - m_pbsBinaries.AddFrom(pgRoot); - - List lStreams = new List(); - lStreams.Add(sSaveTo); - - HashingStreamEx sHashing = new HashingStreamEx(sSaveTo, true, null); - lStreams.Add(sHashing); - - try - { - m_uFileVersion = GetMinKdbxVersion(); - - int cbEncKey, cbEncIV; - ICipherEngine iCipher = GetCipher(out cbEncKey, out cbEncIV); - - m_pbMasterSeed = cr.GetRandomBytes(32); - m_pbEncryptionIV = cr.GetRandomBytes((uint)cbEncIV); - - // m_pbTransformSeed = cr.GetRandomBytes(32); - PwUuid puKdf = m_pwDatabase.KdfParameters.KdfUuid; - KdfEngine kdf = KdfPool.Get(puKdf); - if(kdf == null) - throw new Exception(KLRes.UnknownKdf + Environment.NewLine + - // KLRes.FileNewVerOrPlgReq + Environment.NewLine + - "UUID: " + puKdf.ToHexString() + "."); - kdf.Randomize(m_pwDatabase.KdfParameters); - - if(m_format == KdbxFormat.Default) - { - if(m_uFileVersion < FileVersion32_4) - { - m_craInnerRandomStream = CrsAlgorithm.Salsa20; - m_pbInnerRandomStreamKey = cr.GetRandomBytes(32); - } - else // KDBX >= 4 - { - m_craInnerRandomStream = CrsAlgorithm.ChaCha20; - m_pbInnerRandomStreamKey = cr.GetRandomBytes(64); - } - - m_randomStream = new CryptoRandomStream(m_craInnerRandomStream, - m_pbInnerRandomStreamKey); - } - - if(m_uFileVersion < FileVersion32_4) - m_pbStreamStartBytes = cr.GetRandomBytes(32); - - Stream sXml; - if(m_format == KdbxFormat.Default) - { - byte[] pbHeader = GenerateHeader(); - m_pbHashOfHeader = CryptoUtil.HashSha256(pbHeader); - - MemUtil.Write(sHashing, pbHeader); - sHashing.Flush(); - - ComputeKeys(out pbCipherKey, cbEncKey, out pbHmacKey64); - - Stream sPlain; - if(m_uFileVersion < FileVersion32_4) - { - Stream sEncrypted = EncryptStream(sHashing, iCipher, - pbCipherKey, cbEncIV, true); - if((sEncrypted == null) || (sEncrypted == sHashing)) - throw new SecurityException(KLRes.CryptoStreamFailed); - lStreams.Add(sEncrypted); - - MemUtil.Write(sEncrypted, m_pbStreamStartBytes); - - sPlain = new HashedBlockStream(sEncrypted, true); - } - else // KDBX >= 4 - { - // For integrity checking (without knowing the master key) - MemUtil.Write(sHashing, m_pbHashOfHeader); - - byte[] pbHeaderHmac = ComputeHeaderHmac(pbHeader, pbHmacKey64); - MemUtil.Write(sHashing, pbHeaderHmac); - - Stream sBlocks = new HmacBlockStream(sHashing, true, - true, pbHmacKey64); - lStreams.Add(sBlocks); - - sPlain = EncryptStream(sBlocks, iCipher, pbCipherKey, - cbEncIV, true); - if((sPlain == null) || (sPlain == sBlocks)) - throw new SecurityException(KLRes.CryptoStreamFailed); - } - lStreams.Add(sPlain); - - if(m_pwDatabase.Compression == PwCompressionAlgorithm.GZip) - { - sXml = new GZipStream(sPlain, CompressionMode.Compress); - lStreams.Add(sXml); - } - else sXml = sPlain; - - if(m_uFileVersion >= FileVersion32_4) - WriteInnerHeader(sXml); // Binary header before XML - } - else if(m_format == KdbxFormat.PlainXml) - sXml = sHashing; - else - { - Debug.Assert(false); - throw new ArgumentOutOfRangeException("fmt"); - } - - m_xmlWriter = XmlUtilEx.CreateXmlWriter(sXml, m_uFileVersion >= FileVersion32_4); - - WriteDocument(pgRoot); - - m_xmlWriter.Flush(); - } - finally - { - CommonCleanUpWrite(lStreams, sHashing); - - if(pbCipherKey != null) MemUtil.ZeroByteArray(pbCipherKey); - if(pbHmacKey64 != null) MemUtil.ZeroByteArray(pbHmacKey64); - } - } - - private void CommonCleanUpWrite(List lStreams, HashingStreamEx sHashing) - { - if(m_xmlWriter != null) { m_xmlWriter.Dispose(); m_xmlWriter = null; } - - CloseStreams(lStreams); - - Debug.Assert(lStreams.Contains(sHashing)); // sHashing must be closed - m_pbHashOfFileOnDisk = sHashing.Hash; - Debug.Assert(m_pbHashOfFileOnDisk != null); - - CleanUpInnerRandomStream(); - - m_pbHashOfHeader = null; - } - - private byte[] GenerateHeader() - { - byte[] pbHeader; - using(MemoryStream ms = new MemoryStream()) - { - MemUtil.Write(ms, MemUtil.UInt32ToBytes(FileSignature1)); - MemUtil.Write(ms, MemUtil.UInt32ToBytes(FileSignature2)); - MemUtil.Write(ms, MemUtil.UInt32ToBytes(m_uFileVersion)); - - WriteHeaderField(ms, KdbxHeaderFieldID.CipherID, - m_pwDatabase.DataCipherUuid.UuidBytes); - - int nCprID = (int)m_pwDatabase.Compression; - WriteHeaderField(ms, KdbxHeaderFieldID.CompressionFlags, - MemUtil.UInt32ToBytes((uint)nCprID)); - - WriteHeaderField(ms, KdbxHeaderFieldID.MasterSeed, m_pbMasterSeed); - - if(m_uFileVersion < FileVersion32_4) - { - Debug.Assert(m_pwDatabase.KdfParameters.KdfUuid.Equals( - (new AesKdf()).Uuid)); - WriteHeaderField(ms, KdbxHeaderFieldID.TransformSeed, - m_pwDatabase.KdfParameters.GetByteArray(AesKdf.ParamSeed)); - WriteHeaderField(ms, KdbxHeaderFieldID.TransformRounds, - MemUtil.UInt64ToBytes(m_pwDatabase.KdfParameters.GetUInt64( - AesKdf.ParamRounds, PwDefs.DefaultKeyEncryptionRounds))); - } - else - WriteHeaderField(ms, KdbxHeaderFieldID.KdfParameters, - KdfParameters.SerializeExt(m_pwDatabase.KdfParameters)); - - if(m_pbEncryptionIV.Length > 0) - WriteHeaderField(ms, KdbxHeaderFieldID.EncryptionIV, m_pbEncryptionIV); - - if(m_uFileVersion < FileVersion32_4) - { - WriteHeaderField(ms, KdbxHeaderFieldID.InnerRandomStreamKey, - m_pbInnerRandomStreamKey); - - WriteHeaderField(ms, KdbxHeaderFieldID.StreamStartBytes, - m_pbStreamStartBytes); - - int nIrsID = (int)m_craInnerRandomStream; - WriteHeaderField(ms, KdbxHeaderFieldID.InnerRandomStreamID, - MemUtil.Int32ToBytes(nIrsID)); - } - - // Write public custom data only when there is at least one item, - // because KDBX 3.1 didn't support this field yet - if(m_pwDatabase.PublicCustomData.Count > 0) - WriteHeaderField(ms, KdbxHeaderFieldID.PublicCustomData, - VariantDictionary.Serialize(m_pwDatabase.PublicCustomData)); - - WriteHeaderField(ms, KdbxHeaderFieldID.EndOfHeader, new byte[] { - (byte)'\r', (byte)'\n', (byte)'\r', (byte)'\n' }); - - pbHeader = ms.ToArray(); - } - - return pbHeader; - } - - private void WriteHeaderField(Stream s, KdbxHeaderFieldID kdbID, - byte[] pbData) - { - s.WriteByte((byte)kdbID); - - byte[] pb = (pbData ?? MemUtil.EmptyByteArray); - int cb = pb.Length; - if(cb < 0) { Debug.Assert(false); throw new OutOfMemoryException(); } - - Debug.Assert(m_uFileVersion > 0); - if(m_uFileVersion < FileVersion32_4) - { - if(cb > (int)ushort.MaxValue) - { - Debug.Assert(false); - throw new ArgumentOutOfRangeException("pbData"); - } - - MemUtil.Write(s, MemUtil.UInt16ToBytes((ushort)cb)); - } - else MemUtil.Write(s, MemUtil.Int32ToBytes(cb)); - - MemUtil.Write(s, pb); - } - - private void WriteInnerHeader(Stream s) - { - int nIrsID = (int)m_craInnerRandomStream; - WriteInnerHeaderField(s, KdbxInnerHeaderFieldID.InnerRandomStreamID, - MemUtil.Int32ToBytes(nIrsID), null); - - WriteInnerHeaderField(s, KdbxInnerHeaderFieldID.InnerRandomStreamKey, - m_pbInnerRandomStreamKey, null); - - ProtectedBinary[] vBin = m_pbsBinaries.ToArray(); - for(int i = 0; i < vBin.Length; ++i) - { - ProtectedBinary pb = vBin[i]; - if(pb == null) throw new InvalidOperationException(); - - KdbxBinaryFlags f = KdbxBinaryFlags.None; - if(pb.IsProtected) f |= KdbxBinaryFlags.Protected; - - byte[] pbFlags = new byte[1] { (byte)f }; - byte[] pbData = pb.ReadData(); - - WriteInnerHeaderField(s, KdbxInnerHeaderFieldID.Binary, - pbFlags, pbData); - - if(pb.IsProtected) MemUtil.ZeroByteArray(pbData); - } - - WriteInnerHeaderField(s, KdbxInnerHeaderFieldID.EndOfHeader, - null, null); - } - - private void WriteInnerHeaderField(Stream s, KdbxInnerHeaderFieldID kdbID, - byte[] pbData1, byte[] pbData2) - { - s.WriteByte((byte)kdbID); - - byte[] pb1 = (pbData1 ?? MemUtil.EmptyByteArray); - byte[] pb2 = (pbData2 ?? MemUtil.EmptyByteArray); - - int cb = pb1.Length + pb2.Length; - if(cb < 0) { Debug.Assert(false); throw new OutOfMemoryException(); } - - MemUtil.Write(s, MemUtil.Int32ToBytes(cb)); - MemUtil.Write(s, pb1); - MemUtil.Write(s, pb2); - } - - private void WriteDocument(PwGroup pgRoot) - { - Debug.Assert(m_xmlWriter != null); - if(m_xmlWriter == null) throw new InvalidOperationException(); - - uint uNumGroups, uNumEntries, uCurEntry = 0; - pgRoot.GetCounts(true, out uNumGroups, out uNumEntries); - - m_xmlWriter.WriteStartDocument(true); - m_xmlWriter.WriteStartElement(ElemDocNode); - - WriteMeta(); - - m_xmlWriter.WriteStartElement(ElemRoot); - StartGroup(pgRoot); - - Stack groupStack = new Stack(); - groupStack.Push(pgRoot); - - GroupHandler gh = delegate(PwGroup pg) - { - Debug.Assert(pg != null); - if(pg == null) throw new ArgumentNullException("pg"); - - while(true) - { - if(pg.ParentGroup == groupStack.Peek()) - { - groupStack.Push(pg); - StartGroup(pg); - break; - } - else - { - groupStack.Pop(); - if(groupStack.Count <= 0) return false; - - EndGroup(); - } - } - - return true; - }; - - EntryHandler eh = delegate(PwEntry pe) - { - Debug.Assert(pe != null); - WriteEntry(pe, false); - - ++uCurEntry; - if(m_slLogger != null) - if(!m_slLogger.SetProgress((100 * uCurEntry) / uNumEntries)) - return false; - - return true; - }; - - if(!pgRoot.TraverseTree(TraversalMethod.PreOrder, gh, eh)) - throw new InvalidOperationException(); - - while(groupStack.Count > 1) - { - m_xmlWriter.WriteEndElement(); - groupStack.Pop(); - } - - EndGroup(); - - WriteList(ElemDeletedObjects, m_pwDatabase.DeletedObjects); - m_xmlWriter.WriteEndElement(); // Root - - m_xmlWriter.WriteEndElement(); // ElemDocNode - m_xmlWriter.WriteEndDocument(); - } - - private void WriteMeta() - { - m_xmlWriter.WriteStartElement(ElemMeta); - - WriteObject(ElemGenerator, PwDatabase.LocalizedAppName, false); - - if((m_pbHashOfHeader != null) && (m_uFileVersion < FileVersion32_4)) - WriteObject(ElemHeaderHash, Convert.ToBase64String( - m_pbHashOfHeader), false); - - if(m_uFileVersion >= FileVersion32_4) - WriteObject(ElemSettingsChanged, m_pwDatabase.SettingsChanged); - - WriteObject(ElemDbName, m_pwDatabase.Name, true); - WriteObject(ElemDbNameChanged, m_pwDatabase.NameChanged); - WriteObject(ElemDbDesc, m_pwDatabase.Description, true); - WriteObject(ElemDbDescChanged, m_pwDatabase.DescriptionChanged); - WriteObject(ElemDbDefaultUser, m_pwDatabase.DefaultUserName, true); - WriteObject(ElemDbDefaultUserChanged, m_pwDatabase.DefaultUserNameChanged); - WriteObject(ElemDbMntncHistoryDays, m_pwDatabase.MaintenanceHistoryDays); - WriteObject(ElemDbColor, StrUtil.ColorToUnnamedHtml(m_pwDatabase.Color, true), false); - WriteObject(ElemDbKeyChanged, m_pwDatabase.MasterKeyChanged); - WriteObject(ElemDbKeyChangeRec, m_pwDatabase.MasterKeyChangeRec); - WriteObject(ElemDbKeyChangeForce, m_pwDatabase.MasterKeyChangeForce); - if(m_pwDatabase.MasterKeyChangeForceOnce) - WriteObject(ElemDbKeyChangeForceOnce, true); - - WriteList(ElemMemoryProt, m_pwDatabase.MemoryProtection); - - WriteCustomIconList(); - - WriteObject(ElemRecycleBinEnabled, m_pwDatabase.RecycleBinEnabled); - WriteObject(ElemRecycleBinUuid, m_pwDatabase.RecycleBinUuid); - WriteObject(ElemRecycleBinChanged, m_pwDatabase.RecycleBinChanged); - WriteObject(ElemEntryTemplatesGroup, m_pwDatabase.EntryTemplatesGroup); - WriteObject(ElemEntryTemplatesGroupChanged, m_pwDatabase.EntryTemplatesGroupChanged); - WriteObject(ElemHistoryMaxItems, m_pwDatabase.HistoryMaxItems); - WriteObject(ElemHistoryMaxSize, m_pwDatabase.HistoryMaxSize); - - WriteObject(ElemLastSelectedGroup, m_pwDatabase.LastSelectedGroup); - WriteObject(ElemLastTopVisibleGroup, m_pwDatabase.LastTopVisibleGroup); - - if((m_format != KdbxFormat.Default) || (m_uFileVersion < FileVersion32_4)) - WriteBinPool(); - - WriteList(ElemCustomData, m_pwDatabase.CustomData); - - m_xmlWriter.WriteEndElement(); - } - - private void StartGroup(PwGroup pg) - { - m_xmlWriter.WriteStartElement(ElemGroup); - WriteObject(ElemUuid, pg.Uuid); - WriteObject(ElemName, pg.Name, true); - WriteObject(ElemNotes, pg.Notes, true); - WriteObject(ElemIcon, (int)pg.IconId); - - if(!pg.CustomIconUuid.Equals(PwUuid.Zero)) - WriteObject(ElemCustomIconID, pg.CustomIconUuid); - - WriteList(ElemTimes, pg); - WriteObject(ElemIsExpanded, pg.IsExpanded); - WriteObject(ElemGroupDefaultAutoTypeSeq, pg.DefaultAutoTypeSequence, true); - WriteObject(ElemEnableAutoType, StrUtil.BoolToStringEx(pg.EnableAutoType), false); - WriteObject(ElemEnableSearching, StrUtil.BoolToStringEx(pg.EnableSearching), false); - WriteObject(ElemLastTopVisibleEntry, pg.LastTopVisibleEntry); - - if(pg.CustomData.Count > 0) - WriteList(ElemCustomData, pg.CustomData); - } - - private void EndGroup() - { - m_xmlWriter.WriteEndElement(); // Close group element - } - - private void WriteEntry(PwEntry pe, bool bIsHistory) - { - Debug.Assert(pe != null); if(pe == null) throw new ArgumentNullException("pe"); - - m_xmlWriter.WriteStartElement(ElemEntry); - - WriteObject(ElemUuid, pe.Uuid); - WriteObject(ElemIcon, (int)pe.IconId); - - if(!pe.CustomIconUuid.Equals(PwUuid.Zero)) - WriteObject(ElemCustomIconID, pe.CustomIconUuid); - - WriteObject(ElemFgColor, StrUtil.ColorToUnnamedHtml(pe.ForegroundColor, true), false); - WriteObject(ElemBgColor, StrUtil.ColorToUnnamedHtml(pe.BackgroundColor, true), false); - WriteObject(ElemOverrideUrl, pe.OverrideUrl, true); - WriteObject(ElemTags, StrUtil.TagsToString(pe.Tags, false), true); - - WriteList(ElemTimes, pe); - - WriteList(pe.Strings, true); - WriteList(pe.Binaries); - WriteList(ElemAutoType, pe.AutoType); - - if(pe.CustomData.Count > 0) - WriteList(ElemCustomData, pe.CustomData); - - if(!bIsHistory) WriteList(ElemHistory, pe.History, true); - else { Debug.Assert(pe.History.UCount == 0); } - - m_xmlWriter.WriteEndElement(); - } - - private void WriteList(ProtectedStringDictionary dictStrings, bool bEntryStrings) - { - Debug.Assert(dictStrings != null); - if(dictStrings == null) throw new ArgumentNullException("dictStrings"); - - foreach(KeyValuePair kvp in dictStrings) - WriteObject(kvp.Key, kvp.Value, bEntryStrings); - } - - private void WriteList(ProtectedBinaryDictionary dictBinaries) - { - Debug.Assert(dictBinaries != null); - if(dictBinaries == null) throw new ArgumentNullException("dictBinaries"); - - foreach(KeyValuePair kvp in dictBinaries) - WriteObject(kvp.Key, kvp.Value, true); - } - - private void WriteList(string name, AutoTypeConfig cfgAutoType) - { - Debug.Assert(name != null); - Debug.Assert(cfgAutoType != null); - if(cfgAutoType == null) throw new ArgumentNullException("cfgAutoType"); - - m_xmlWriter.WriteStartElement(name); - - WriteObject(ElemAutoTypeEnabled, cfgAutoType.Enabled); - WriteObject(ElemAutoTypeObfuscation, (int)cfgAutoType.ObfuscationOptions); - - if(cfgAutoType.DefaultSequence.Length > 0) - WriteObject(ElemAutoTypeDefaultSeq, cfgAutoType.DefaultSequence, true); - - foreach(AutoTypeAssociation a in cfgAutoType.Associations) - WriteObject(ElemAutoTypeItem, ElemWindow, ElemKeystrokeSequence, - new KeyValuePair(a.WindowName, a.Sequence)); - - m_xmlWriter.WriteEndElement(); - } - - private void WriteList(string name, ITimeLogger times) - { - Debug.Assert(name != null); - Debug.Assert(times != null); if(times == null) throw new ArgumentNullException("times"); - - m_xmlWriter.WriteStartElement(name); - - WriteObject(ElemCreationTime, times.CreationTime); - WriteObject(ElemLastModTime, times.LastModificationTime); - WriteObject(ElemLastAccessTime, times.LastAccessTime); - WriteObject(ElemExpiryTime, times.ExpiryTime); - WriteObject(ElemExpires, times.Expires); - WriteObject(ElemUsageCount, times.UsageCount); - WriteObject(ElemLocationChanged, times.LocationChanged); - - m_xmlWriter.WriteEndElement(); // Name - } - - private void WriteList(string name, PwObjectList value, bool bIsHistory) - { - Debug.Assert(name != null); - Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); - - m_xmlWriter.WriteStartElement(name); - - foreach(PwEntry pe in value) - WriteEntry(pe, bIsHistory); - - m_xmlWriter.WriteEndElement(); - } - - private void WriteList(string name, PwObjectList value) - { - Debug.Assert(name != null); - Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); - - m_xmlWriter.WriteStartElement(name); - - foreach(PwDeletedObject pdo in value) - WriteObject(ElemDeletedObject, pdo); - - m_xmlWriter.WriteEndElement(); - } - - private void WriteList(string name, MemoryProtectionConfig value) - { - Debug.Assert(name != null); - Debug.Assert(value != null); - - m_xmlWriter.WriteStartElement(name); - - WriteObject(ElemProtTitle, value.ProtectTitle); - WriteObject(ElemProtUserName, value.ProtectUserName); - WriteObject(ElemProtPassword, value.ProtectPassword); - WriteObject(ElemProtUrl, value.ProtectUrl); - WriteObject(ElemProtNotes, value.ProtectNotes); - // WriteObject(ElemProtAutoHide, value.AutoEnableVisualHiding); - - m_xmlWriter.WriteEndElement(); - } - - private void WriteList(string name, StringDictionaryEx value) - { - Debug.Assert(name != null); - Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); - - m_xmlWriter.WriteStartElement(name); - - foreach(KeyValuePair kvp in value) - WriteObject(ElemStringDictExItem, ElemKey, ElemValue, kvp); - - m_xmlWriter.WriteEndElement(); - } - - private void WriteCustomIconList() - { - if(m_pwDatabase.CustomIcons.Count == 0) return; - - m_xmlWriter.WriteStartElement(ElemCustomIcons); - - foreach(PwCustomIcon pwci in m_pwDatabase.CustomIcons) - { - m_xmlWriter.WriteStartElement(ElemCustomIconItem); - - WriteObject(ElemCustomIconItemID, pwci.Uuid); - - string strData = Convert.ToBase64String(pwci.ImageDataPng); - WriteObject(ElemCustomIconItemData, strData, false); - - m_xmlWriter.WriteEndElement(); - } - - m_xmlWriter.WriteEndElement(); - } - - private void WriteObject(string name, string value, - bool bFilterValueXmlChars) - { - Debug.Assert(name != null); - Debug.Assert(value != null); - - m_xmlWriter.WriteStartElement(name); - - if(bFilterValueXmlChars) - m_xmlWriter.WriteString(StrUtil.SafeXmlString(value)); - else m_xmlWriter.WriteString(value); - - m_xmlWriter.WriteEndElement(); - } - - private void WriteObject(string name, bool value) - { - Debug.Assert(name != null); - - WriteObject(name, value ? ValTrue : ValFalse, false); - } - - private void WriteObject(string name, PwUuid value) - { - Debug.Assert(name != null); - Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); - - WriteObject(name, Convert.ToBase64String(value.UuidBytes), false); - } - - private void WriteObject(string name, int value) - { - Debug.Assert(name != null); - - m_xmlWriter.WriteStartElement(name); - m_xmlWriter.WriteString(value.ToString(NumberFormatInfo.InvariantInfo)); - m_xmlWriter.WriteEndElement(); - } - - private void WriteObject(string name, uint value) - { - Debug.Assert(name != null); - - m_xmlWriter.WriteStartElement(name); - m_xmlWriter.WriteString(value.ToString(NumberFormatInfo.InvariantInfo)); - m_xmlWriter.WriteEndElement(); - } - - private void WriteObject(string name, long value) - { - Debug.Assert(name != null); - - m_xmlWriter.WriteStartElement(name); - m_xmlWriter.WriteString(value.ToString(NumberFormatInfo.InvariantInfo)); - m_xmlWriter.WriteEndElement(); - } - - private void WriteObject(string name, ulong value) - { - Debug.Assert(name != null); - - m_xmlWriter.WriteStartElement(name); - m_xmlWriter.WriteString(value.ToString(NumberFormatInfo.InvariantInfo)); - m_xmlWriter.WriteEndElement(); - } - - private void WriteObject(string name, DateTime value) - { - Debug.Assert(name != null); - Debug.Assert(value.Kind == DateTimeKind.Utc); - - // Cf. ReadTime - if((m_format == KdbxFormat.Default) && (m_uFileVersion >= FileVersion32_4)) - { - DateTime dt = TimeUtil.ToUtc(value, false); - - // DateTime dtBase = new DateTime(2000, 1, 1, 0, 0, 0, DateTimeKind.Utc); - // dt -= new TimeSpan(dtBase.Ticks); - - // WriteObject(name, dt.ToBinary()); - - // dt = TimeUtil.RoundToMultOf2PowLess1s(dt); - // long lBin = dt.ToBinary(); - - long lSec = dt.Ticks / TimeSpan.TicksPerSecond; - // WriteObject(name, lSec); - - byte[] pb = MemUtil.Int64ToBytes(lSec); - WriteObject(name, Convert.ToBase64String(pb), false); - } - else WriteObject(name, TimeUtil.SerializeUtc(value), false); - } - - private void WriteObject(string name, string strKeyName, - string strValueName, KeyValuePair kvp) - { - m_xmlWriter.WriteStartElement(name); - - m_xmlWriter.WriteStartElement(strKeyName); - m_xmlWriter.WriteString(StrUtil.SafeXmlString(kvp.Key)); - m_xmlWriter.WriteEndElement(); - m_xmlWriter.WriteStartElement(strValueName); - m_xmlWriter.WriteString(StrUtil.SafeXmlString(kvp.Value)); - m_xmlWriter.WriteEndElement(); - - m_xmlWriter.WriteEndElement(); - } - - private void WriteObject(string name, ProtectedString value, bool bIsEntryString) - { - Debug.Assert(name != null); - Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); - - m_xmlWriter.WriteStartElement(ElemString); - m_xmlWriter.WriteStartElement(ElemKey); - m_xmlWriter.WriteString(StrUtil.SafeXmlString(name)); - m_xmlWriter.WriteEndElement(); - m_xmlWriter.WriteStartElement(ElemValue); - - bool bProtected = value.IsProtected; - if(bIsEntryString) - { - // Adjust memory protection setting (which might be different - // from the database default, e.g. due to an import which - // didn't specify the correct setting) - if(name == PwDefs.TitleField) - bProtected = m_pwDatabase.MemoryProtection.ProtectTitle; - else if(name == PwDefs.UserNameField) - bProtected = m_pwDatabase.MemoryProtection.ProtectUserName; - else if(name == PwDefs.PasswordField) - bProtected = m_pwDatabase.MemoryProtection.ProtectPassword; - else if(name == PwDefs.UrlField) - bProtected = m_pwDatabase.MemoryProtection.ProtectUrl; - else if(name == PwDefs.NotesField) - bProtected = m_pwDatabase.MemoryProtection.ProtectNotes; - } - - if(bProtected && (m_format == KdbxFormat.Default)) - { - m_xmlWriter.WriteAttributeString(AttrProtected, ValTrue); - - byte[] pbEncoded = value.ReadXorredString(m_randomStream); - if(pbEncoded.Length > 0) - m_xmlWriter.WriteBase64(pbEncoded, 0, pbEncoded.Length); - } - else - { - string strValue = value.ReadString(); - - // If names should be localized, we need to apply the language-dependent - // string transformation here. By default, language-dependent conversions - // should be applied, otherwise characters could be rendered incorrectly - // (code page problems). - if(m_bLocalizedNames) - { - StringBuilder sb = new StringBuilder(); - foreach(char ch in strValue) - { - char chMapped = ch; - - // Symbols and surrogates must be moved into the correct code - // page area - if(char.IsSymbol(ch) || char.IsSurrogate(ch)) - { - System.Globalization.UnicodeCategory cat = - CharUnicodeInfo.GetUnicodeCategory(ch); - // Map character to correct position in code page - chMapped = (char)((int)cat * 32 + ch); - } - else if(char.IsControl(ch)) - { - if(ch >= 256) // Control character in high ANSI code page - { - // Some of the control characters map to corresponding ones - // in the low ANSI range (up to 255) when calling - // ToLower on them with invariant culture (see - // http://lists.ximian.com/pipermail/mono-patches/2002-February/086106.html ) -#if !KeePassLibSD - chMapped = char.ToLowerInvariant(ch); -#else - chMapped = char.ToLower(ch); -#endif - } - } - - sb.Append(chMapped); - } - - strValue = sb.ToString(); // Correct string for current code page - } - - if((m_format == KdbxFormat.PlainXml) && bProtected) - m_xmlWriter.WriteAttributeString(AttrProtectedInMemPlainXml, ValTrue); - - m_xmlWriter.WriteString(StrUtil.SafeXmlString(strValue)); - } - - m_xmlWriter.WriteEndElement(); // ElemValue - m_xmlWriter.WriteEndElement(); // ElemString - } - - private void WriteObject(string name, ProtectedBinary value, bool bAllowRef) - { - Debug.Assert(name != null); - Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); - - m_xmlWriter.WriteStartElement(ElemBinary); - m_xmlWriter.WriteStartElement(ElemKey); - m_xmlWriter.WriteString(StrUtil.SafeXmlString(name)); - m_xmlWriter.WriteEndElement(); - m_xmlWriter.WriteStartElement(ElemValue); - - string strRef = null; - if(bAllowRef) - { - int iRef = m_pbsBinaries.Find(value); - if(iRef >= 0) strRef = iRef.ToString(NumberFormatInfo.InvariantInfo); - else { Debug.Assert(false); } - } - if(strRef != null) - m_xmlWriter.WriteAttributeString(AttrRef, strRef); - else SubWriteValue(value); - - m_xmlWriter.WriteEndElement(); // ElemValue - m_xmlWriter.WriteEndElement(); // ElemBinary - } - - private void SubWriteValue(ProtectedBinary value) - { - if(value.IsProtected && (m_format == KdbxFormat.Default)) - { - m_xmlWriter.WriteAttributeString(AttrProtected, ValTrue); - - byte[] pbEncoded = value.ReadXorredData(m_randomStream); - if(pbEncoded.Length > 0) - m_xmlWriter.WriteBase64(pbEncoded, 0, pbEncoded.Length); - } - else - { - if(m_pwDatabase.Compression != PwCompressionAlgorithm.None) - { - m_xmlWriter.WriteAttributeString(AttrCompressed, ValTrue); - - byte[] pbRaw = value.ReadData(); - byte[] pbCmp = MemUtil.Compress(pbRaw); - m_xmlWriter.WriteBase64(pbCmp, 0, pbCmp.Length); - - if(value.IsProtected) - { - MemUtil.ZeroByteArray(pbRaw); - MemUtil.ZeroByteArray(pbCmp); - } - } - else - { - byte[] pbRaw = value.ReadData(); - m_xmlWriter.WriteBase64(pbRaw, 0, pbRaw.Length); - - if(value.IsProtected) MemUtil.ZeroByteArray(pbRaw); - } - } - } - - private void WriteObject(string name, PwDeletedObject value) - { - Debug.Assert(name != null); - Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); - - m_xmlWriter.WriteStartElement(name); - WriteObject(ElemUuid, value.Uuid); - WriteObject(ElemDeletionTime, value.DeletionTime); - m_xmlWriter.WriteEndElement(); - } - - private void WriteBinPool() - { - m_xmlWriter.WriteStartElement(ElemBinaries); - - ProtectedBinary[] v = m_pbsBinaries.ToArray(); - for(int i = 0; i < v.Length; ++i) - { - m_xmlWriter.WriteStartElement(ElemBinary); - m_xmlWriter.WriteAttributeString(AttrId, - i.ToString(NumberFormatInfo.InvariantInfo)); - SubWriteValue(v[i]); - m_xmlWriter.WriteEndElement(); - } - - m_xmlWriter.WriteEndElement(); - } - - [Obsolete] - public static bool WriteEntries(Stream msOutput, PwEntry[] vEntries) - { - return WriteEntries(msOutput, null, vEntries); - } - - public static bool WriteEntries(Stream msOutput, PwDatabase pdContext, - PwEntry[] vEntries) - { - if(msOutput == null) { Debug.Assert(false); return false; } - // pdContext may be null - if(vEntries == null) { Debug.Assert(false); return false; } - - /* KdbxFile f = new KdbxFile(pwDatabase); - f.m_format = KdbxFormat.PlainXml; - - XmlTextWriter xtw = null; - try { xtw = new XmlTextWriter(msOutput, StrUtil.Utf8); } - catch(Exception) { Debug.Assert(false); return false; } - if(xtw == null) { Debug.Assert(false); return false; } - - f.m_xmlWriter = xtw; - - xtw.Formatting = Formatting.Indented; - xtw.IndentChar = '\t'; - xtw.Indentation = 1; - - xtw.WriteStartDocument(true); - xtw.WriteStartElement(ElemRoot); - - foreach(PwEntry pe in vEntries) - f.WriteEntry(pe, false); - - xtw.WriteEndElement(); - xtw.WriteEndDocument(); - - xtw.Flush(); - xtw.Close(); - return true; */ - - PwDatabase pd = new PwDatabase(); - pd.New(new IOConnectionInfo(), new CompositeKey()); - - PwGroup pg = pd.RootGroup; - if(pg == null) { Debug.Assert(false); return false; } - - foreach(PwEntry pe in vEntries) - { - PwUuid pu = pe.CustomIconUuid; - if(!pu.Equals(PwUuid.Zero) && (pd.GetCustomIconIndex(pu) < 0)) - { - int i = -1; - if(pdContext != null) i = pdContext.GetCustomIconIndex(pu); - if(i >= 0) - { - PwCustomIcon ci = pdContext.CustomIcons[i]; - pd.CustomIcons.Add(ci); - } - else { Debug.Assert(pdContext == null); } - } - - PwEntry peCopy = pe.CloneDeep(); - pg.AddEntry(peCopy, true); - } - - KdbxFile f = new KdbxFile(pd); - f.Save(msOutput, null, KdbxFormat.PlainXml, null); - return true; - } - } -} diff --git a/ModernKeePassLib/Serialization/KdbxFile.cs b/ModernKeePassLib/Serialization/KdbxFile.cs deleted file mode 100644 index 82248a0..0000000 --- a/ModernKeePassLib/Serialization/KdbxFile.cs +++ /dev/null @@ -1,547 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Globalization; -using System.IO; -using System.Security; -using System.Text; -using System.Xml; - -#if ModernKeePassLib -using Windows.Storage; -using ModernKeePassLib.Cryptography.Hash; -#endif - -using ModernKeePassLib.Collections; -using ModernKeePassLib.Cryptography; -using ModernKeePassLib.Cryptography.Cipher; -using ModernKeePassLib.Cryptography.KeyDerivation; -using ModernKeePassLib.Delegates; -using ModernKeePassLib.Interfaces; -using ModernKeePassLib.Resources; -using ModernKeePassLib.Security; -using ModernKeePassLib.Utility; - -namespace ModernKeePassLib.Serialization -{ - /// - /// The KdbxFile class supports saving the data to various - /// formats. - /// - public enum KdbxFormat - { - /// - /// The default, encrypted file format. - /// - Default = 0, - - /// - /// Use this flag when exporting data to a plain-text XML file. - /// - PlainXml - } - - /// - /// Serialization to KeePass KDBX files. - /// - public sealed partial class KdbxFile - { - /// - /// File identifier, first 32-bit value. - /// - internal const uint FileSignature1 = 0x9AA2D903; - - /// - /// File identifier, second 32-bit value. - /// - internal const uint FileSignature2 = 0xB54BFB67; - - /// - /// File version of files saved by the current KdbxFile class. - /// KeePass 2.07 has version 1.01, 2.08 has 1.02, 2.09 has 2.00, - /// 2.10 has 2.02, 2.11 has 2.04, 2.15 has 3.00, 2.20 has 3.01. - /// The first 2 bytes are critical (i.e. loading will fail, if the - /// file version is too high), the last 2 bytes are informational. - /// - private const uint FileVersion32 = 0x00040000; - - internal const uint FileVersion32_4 = 0x00040000; // First of 4.x series - internal const uint FileVersion32_3 = 0x00030001; // Old format 3.1 - - private const uint FileVersionCriticalMask = 0xFFFF0000; - - // KeePass 1.x signature - internal const uint FileSignatureOld1 = 0x9AA2D903; - internal const uint FileSignatureOld2 = 0xB54BFB65; - // KeePass 2.x pre-release (alpha and beta) signature - internal const uint FileSignaturePreRelease1 = 0x9AA2D903; - internal const uint FileSignaturePreRelease2 = 0xB54BFB66; - - private const string ElemDocNode = "KeePassFile"; - private const string ElemMeta = "Meta"; - private const string ElemRoot = "Root"; - private const string ElemGroup = "Group"; - private const string ElemEntry = "Entry"; - - private const string ElemGenerator = "Generator"; - private const string ElemHeaderHash = "HeaderHash"; - private const string ElemSettingsChanged = "SettingsChanged"; - private const string ElemDbName = "DatabaseName"; - private const string ElemDbNameChanged = "DatabaseNameChanged"; - private const string ElemDbDesc = "DatabaseDescription"; - private const string ElemDbDescChanged = "DatabaseDescriptionChanged"; - private const string ElemDbDefaultUser = "DefaultUserName"; - private const string ElemDbDefaultUserChanged = "DefaultUserNameChanged"; - private const string ElemDbMntncHistoryDays = "MaintenanceHistoryDays"; - private const string ElemDbColor = "Color"; - private const string ElemDbKeyChanged = "MasterKeyChanged"; - private const string ElemDbKeyChangeRec = "MasterKeyChangeRec"; - private const string ElemDbKeyChangeForce = "MasterKeyChangeForce"; - private const string ElemDbKeyChangeForceOnce = "MasterKeyChangeForceOnce"; - private const string ElemRecycleBinEnabled = "RecycleBinEnabled"; - private const string ElemRecycleBinUuid = "RecycleBinUUID"; - private const string ElemRecycleBinChanged = "RecycleBinChanged"; - private const string ElemEntryTemplatesGroup = "EntryTemplatesGroup"; - private const string ElemEntryTemplatesGroupChanged = "EntryTemplatesGroupChanged"; - private const string ElemHistoryMaxItems = "HistoryMaxItems"; - private const string ElemHistoryMaxSize = "HistoryMaxSize"; - private const string ElemLastSelectedGroup = "LastSelectedGroup"; - private const string ElemLastTopVisibleGroup = "LastTopVisibleGroup"; - - private const string ElemMemoryProt = "MemoryProtection"; - private const string ElemProtTitle = "ProtectTitle"; - private const string ElemProtUserName = "ProtectUserName"; - private const string ElemProtPassword = "ProtectPassword"; - private const string ElemProtUrl = "ProtectURL"; - private const string ElemProtNotes = "ProtectNotes"; - // private const string ElemProtAutoHide = "AutoEnableVisualHiding"; - - private const string ElemCustomIcons = "CustomIcons"; - private const string ElemCustomIconItem = "Icon"; - private const string ElemCustomIconItemID = "UUID"; - private const string ElemCustomIconItemData = "Data"; - - private const string ElemAutoType = "AutoType"; - private const string ElemHistory = "History"; - - private const string ElemName = "Name"; - private const string ElemNotes = "Notes"; - private const string ElemUuid = "UUID"; - private const string ElemIcon = "IconID"; - private const string ElemCustomIconID = "CustomIconUUID"; - private const string ElemFgColor = "ForegroundColor"; - private const string ElemBgColor = "BackgroundColor"; - private const string ElemOverrideUrl = "OverrideURL"; - private const string ElemTimes = "Times"; - private const string ElemTags = "Tags"; - - private const string ElemCreationTime = "CreationTime"; - private const string ElemLastModTime = "LastModificationTime"; - private const string ElemLastAccessTime = "LastAccessTime"; - private const string ElemExpiryTime = "ExpiryTime"; - private const string ElemExpires = "Expires"; - private const string ElemUsageCount = "UsageCount"; - private const string ElemLocationChanged = "LocationChanged"; - - private const string ElemGroupDefaultAutoTypeSeq = "DefaultAutoTypeSequence"; - private const string ElemEnableAutoType = "EnableAutoType"; - private const string ElemEnableSearching = "EnableSearching"; - - private const string ElemString = "String"; - private const string ElemBinary = "Binary"; - private const string ElemKey = "Key"; - private const string ElemValue = "Value"; - - private const string ElemAutoTypeEnabled = "Enabled"; - private const string ElemAutoTypeObfuscation = "DataTransferObfuscation"; - private const string ElemAutoTypeDefaultSeq = "DefaultSequence"; - private const string ElemAutoTypeItem = "Association"; - private const string ElemWindow = "Window"; - private const string ElemKeystrokeSequence = "KeystrokeSequence"; - - private const string ElemBinaries = "Binaries"; - - private const string AttrId = "ID"; - private const string AttrRef = "Ref"; - private const string AttrProtected = "Protected"; - private const string AttrProtectedInMemPlainXml = "ProtectInMemory"; - private const string AttrCompressed = "Compressed"; - - private const string ElemIsExpanded = "IsExpanded"; - private const string ElemLastTopVisibleEntry = "LastTopVisibleEntry"; - - private const string ElemDeletedObjects = "DeletedObjects"; - private const string ElemDeletedObject = "DeletedObject"; - private const string ElemDeletionTime = "DeletionTime"; - - private const string ValFalse = "False"; - private const string ValTrue = "True"; - - private const string ElemCustomData = "CustomData"; - private const string ElemStringDictExItem = "Item"; - - private PwDatabase m_pwDatabase; // Not null, see constructor - private bool m_bUsedOnce = false; - - private XmlWriter m_xmlWriter = null; - private CryptoRandomStream m_randomStream = null; - private KdbxFormat m_format = KdbxFormat.Default; - private IStatusLogger m_slLogger = null; - - private uint m_uFileVersion = 0; - private byte[] m_pbMasterSeed = null; - // private byte[] m_pbTransformSeed = null; - private byte[] m_pbEncryptionIV = null; - private byte[] m_pbStreamStartBytes = null; - - // ArcFourVariant only for backward compatibility; KeePass defaults - // to a more secure algorithm when *writing* databases - private CrsAlgorithm m_craInnerRandomStream = CrsAlgorithm.ArcFourVariant; - private byte[] m_pbInnerRandomStreamKey = null; - - private ProtectedBinarySet m_pbsBinaries = new ProtectedBinarySet(); - - private byte[] m_pbHashOfHeader = null; - private byte[] m_pbHashOfFileOnDisk = null; - - private readonly DateTime m_dtNow = DateTime.UtcNow; // Cache current time - - private const uint NeutralLanguageOffset = 0x100000; // 2^20, see 32-bit Unicode specs - private const uint NeutralLanguageIDSec = 0x7DC5C; // See 32-bit Unicode specs - private const uint NeutralLanguageID = NeutralLanguageOffset + NeutralLanguageIDSec; - private static bool m_bLocalizedNames = false; - - private enum KdbxHeaderFieldID : byte - { - EndOfHeader = 0, - Comment = 1, - CipherID = 2, - CompressionFlags = 3, - MasterSeed = 4, - TransformSeed = 5, // KDBX 3.1, for backward compatibility only - TransformRounds = 6, // KDBX 3.1, for backward compatibility only - EncryptionIV = 7, - InnerRandomStreamKey = 8, // KDBX 3.1, for backward compatibility only - StreamStartBytes = 9, // KDBX 3.1, for backward compatibility only - InnerRandomStreamID = 10, // KDBX 3.1, for backward compatibility only - KdfParameters = 11, // KDBX 4, superseding Transform* - PublicCustomData = 12 // KDBX 4 - } - - // Inner header in KDBX >= 4 files - private enum KdbxInnerHeaderFieldID : byte - { - EndOfHeader = 0, - InnerRandomStreamID = 1, // Supersedes KdbxHeaderFieldID.InnerRandomStreamID - InnerRandomStreamKey = 2, // Supersedes KdbxHeaderFieldID.InnerRandomStreamKey - Binary = 3 - } - - [Flags] - private enum KdbxBinaryFlags : byte - { - None = 0, - Protected = 1 - } - - public byte[] HashOfFileOnDisk - { - get { return m_pbHashOfFileOnDisk; } - } - - private bool m_bRepairMode = false; - public bool RepairMode - { - get { return m_bRepairMode; } - set { m_bRepairMode = value; } - } - - private uint m_uForceVersion = 0; - internal uint ForceVersion - { - get { return m_uForceVersion; } - set { m_uForceVersion = value; } - } - - private string m_strDetachBins = null; - /// - /// Detach binaries when opening a file. If this isn't null, - /// all binaries are saved to the specified path and are removed - /// from the database. - /// - public string DetachBinaries - { - get { return m_strDetachBins; } - set { m_strDetachBins = value; } - } - - /// - /// Default constructor. - /// - /// The PwDatabase instance that the - /// class will load file data into or use to create a KDBX file. - public KdbxFile(PwDatabase pwDataStore) - { - Debug.Assert(pwDataStore != null); - if(pwDataStore == null) throw new ArgumentNullException("pwDataStore"); - - m_pwDatabase = pwDataStore; - } - - /// - /// Call this once to determine the current localization settings. - /// - public static void DetermineLanguageId() - { - // Test if localized names should be used. If localized names are used, - // the m_bLocalizedNames value must be set to true. By default, localized - // names should be used! (Otherwise characters could be corrupted - // because of different code pages). - unchecked - { - uint uTest = 0; - foreach(char ch in PwDatabase.LocalizedAppName) - uTest = uTest * 5 + ch; - - m_bLocalizedNames = (uTest != NeutralLanguageID); - } - } - - private uint GetMinKdbxVersion() - { - if(m_uForceVersion != 0) return m_uForceVersion; - - // See also KeePassKdb2x3.Export (KDBX 3.1 export module) - - AesKdf kdfAes = new AesKdf(); - if(!kdfAes.Uuid.Equals(m_pwDatabase.KdfParameters.KdfUuid)) - return FileVersion32; - - if(m_pwDatabase.PublicCustomData.Count > 0) - return FileVersion32; - - bool bCustomData = false; - GroupHandler gh = delegate(PwGroup pg) - { - if(pg == null) { Debug.Assert(false); return true; } - if(pg.CustomData.Count > 0) { bCustomData = true; return false; } - return true; - }; - EntryHandler eh = delegate(PwEntry pe) - { - if(pe == null) { Debug.Assert(false); return true; } - if(pe.CustomData.Count > 0) { bCustomData = true; return false; } - return true; - }; - gh(m_pwDatabase.RootGroup); - m_pwDatabase.RootGroup.TraverseTree(TraversalMethod.PreOrder, gh, eh); - if(bCustomData) return FileVersion32; - - return FileVersion32_3; // KDBX 3.1 is sufficient - } - - private void ComputeKeys(out byte[] pbCipherKey, int cbCipherKey, - out byte[] pbHmacKey64) - { - byte[] pbCmp = new byte[32 + 32 + 1]; - try - { - Debug.Assert(m_pbMasterSeed != null); - if(m_pbMasterSeed == null) - throw new ArgumentNullException("m_pbMasterSeed"); - Debug.Assert(m_pbMasterSeed.Length == 32); - if(m_pbMasterSeed.Length != 32) - throw new FormatException(KLRes.MasterSeedLengthInvalid); - Array.Copy(m_pbMasterSeed, 0, pbCmp, 0, 32); - - Debug.Assert(m_pwDatabase != null); - Debug.Assert(m_pwDatabase.MasterKey != null); - ProtectedBinary pbinUser = m_pwDatabase.MasterKey.GenerateKey32( - m_pwDatabase.KdfParameters); - Debug.Assert(pbinUser != null); - if(pbinUser == null) - throw new SecurityException(KLRes.InvalidCompositeKey); - byte[] pUserKey32 = pbinUser.ReadData(); - if((pUserKey32 == null) || (pUserKey32.Length != 32)) - throw new SecurityException(KLRes.InvalidCompositeKey); - Array.Copy(pUserKey32, 0, pbCmp, 32, 32); - MemUtil.ZeroByteArray(pUserKey32); - - pbCipherKey = CryptoUtil.ResizeKey(pbCmp, 0, 64, cbCipherKey); - - pbCmp[64] = 1; - using(SHA512Managed h = new SHA512Managed()) - { - pbHmacKey64 = h.ComputeHash(pbCmp); - } - } - finally { MemUtil.ZeroByteArray(pbCmp); } - } - - private ICipherEngine GetCipher(out int cbEncKey, out int cbEncIV) - { - PwUuid pu = m_pwDatabase.DataCipherUuid; - ICipherEngine iCipher = CipherPool.GlobalPool.GetCipher(pu); - if(iCipher == null) // CryptographicExceptions are translated to "file corrupted" - throw new Exception(KLRes.FileUnknownCipher + - MessageService.NewParagraph + KLRes.FileNewVerOrPlgReq + - MessageService.NewParagraph + "UUID: " + pu.ToHexString() + "."); - - ICipherEngine2 iCipher2 = (iCipher as ICipherEngine2); - if(iCipher2 != null) - { - cbEncKey = iCipher2.KeyLength; - if(cbEncKey < 0) throw new InvalidOperationException("EncKey.Length"); - - cbEncIV = iCipher2.IVLength; - if(cbEncIV < 0) throw new InvalidOperationException("EncIV.Length"); - } - else - { - cbEncKey = 32; - cbEncIV = 16; - } - - return iCipher; - } - - private Stream EncryptStream(Stream s, ICipherEngine iCipher, - byte[] pbKey, int cbIV, bool bEncrypt) - { - byte[] pbIV = (m_pbEncryptionIV ?? MemUtil.EmptyByteArray); - if(pbIV.Length != cbIV) - { - Debug.Assert(false); - throw new Exception(KLRes.FileCorrupted); - } - - if(bEncrypt) - return iCipher.EncryptStream(s, pbKey, pbIV); - return iCipher.DecryptStream(s, pbKey, pbIV); - } - - private byte[] ComputeHeaderHmac(byte[] pbHeader, byte[] pbKey) - { - byte[] pbHeaderHmac; - byte[] pbBlockKey = HmacBlockStream.GetHmacKey64( - pbKey, ulong.MaxValue); - using(HMACSHA256 h = new HMACSHA256(pbBlockKey)) - { - pbHeaderHmac = h.ComputeHash(pbHeader); - } - MemUtil.ZeroByteArray(pbBlockKey); - - return pbHeaderHmac; - } - - private void CloseStreams(List lStreams) - { - if(lStreams == null) { Debug.Assert(false); return; } - - // Typically, closing a stream also closes its base - // stream; however, there may be streams that do not - // do this (e.g. some cipher plugin), thus for safety - // we close all streams manually, from the innermost - // to the outermost - - for(int i = lStreams.Count - 1; i >= 0; --i) - { - // Check for duplicates - Debug.Assert((lStreams.IndexOf(lStreams[i]) == i) && - (lStreams.LastIndexOf(lStreams[i]) == i)); - - try { lStreams[i].Dispose(); } - // Unnecessary exception from CryptoStream with - // RijndaelManagedTransform when a stream hasn't been - // read completely (e.g. incorrect master key) -#if !ModernKeePassLib - catch(CryptographicException) { } -#endif - catch(Exception) { Debug.Assert(false); } - } - - // Do not clear the list - } - - private void CleanUpInnerRandomStream() - { - if(m_randomStream != null) m_randomStream.Dispose(); - - if(m_pbInnerRandomStreamKey != null) - MemUtil.ZeroByteArray(m_pbInnerRandomStreamKey); - } - - private static void SaveBinary(string strName, ProtectedBinary pb, - string strSaveDir) - { - if(pb == null) { Debug.Assert(false); return; } - - if(string.IsNullOrEmpty(strName)) strName = "File.bin"; - - string strPath; - int iTry = 1; - do - { - strPath = UrlUtil.EnsureTerminatingSeparator(strSaveDir, false); - - string strExt = UrlUtil.GetExtension(strName); - string strDesc = UrlUtil.StripExtension(strName); - - strPath += strDesc; - if(iTry > 1) - strPath += " (" + iTry.ToString(NumberFormatInfo.InvariantInfo) + - ")"; - - if(!string.IsNullOrEmpty(strExt)) strPath += "." + strExt; - - ++iTry; - } -#if ModernKeePassLib - while (StorageFile.GetFileFromPathAsync(strPath).GetResults() != null); -#else - while(File.Exists(strPath)); -#endif - -#if ModernKeePassLib - byte[] pbData = pb.ReadData(); - /*var file = FileSystem.Current.GetFileFromPathAsync(strPath).Result; - using (var stream = file.OpenAsync(FileAccess.ReadAndWrite).Result) {*/ - var file = StorageFile.GetFileFromPathAsync(strPath).GetAwaiter().GetResult(); - using (var stream = file.OpenAsync(FileAccessMode.ReadWrite).GetAwaiter().GetResult().AsStream()) - { - stream.Write (pbData, 0, pbData.Length); - } - MemUtil.ZeroByteArray(pbData); -#elif !KeePassLibSD - byte[] pbData = pb.ReadData(); - File.WriteAllBytes(strPath, pbData); - MemUtil.ZeroByteArray(pbData); -#else - FileStream fs = new FileStream(strPath, FileMode.Create, - FileAccess.Write, FileShare.None); - byte[] pbData = pb.ReadData(); - try { File.WriteAllBytes(strPath, pbData); } - finally { if(pb.IsProtected) MemUtil.ZeroByteArray(pbData); } -#endif - } - } -} diff --git a/ModernKeePassLib/Serialization/OldFormatException.cs b/ModernKeePassLib/Serialization/OldFormatException.cs deleted file mode 100644 index fcf660a..0000000 --- a/ModernKeePassLib/Serialization/OldFormatException.cs +++ /dev/null @@ -1,66 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; -using System.Text; - -using ModernKeePassLib.Resources; -using ModernKeePassLib.Utility; - -namespace ModernKeePassLib.Serialization -{ - public sealed class OldFormatException : Exception - { - private string m_strFormat = string.Empty; - private OldFormatType m_type = OldFormatType.Unknown; - - public enum OldFormatType - { - Unknown = 0, - KeePass1x = 1 - } - - public override string Message - { - get - { - string str = KLRes.OldFormat + ((m_strFormat.Length > 0) ? - (@" (" + m_strFormat + @")") : string.Empty) + "."; - - if(m_type == OldFormatType.KeePass1x) - str += Environment.NewLine + KLRes.KeePass1xHint; - - return str; - } - } - - public OldFormatException(string strFormatName) - { - if(strFormatName != null) m_strFormat = strFormatName; - } - - public OldFormatException(string strFormatName, OldFormatType t) - { - if(strFormatName != null) m_strFormat = strFormatName; - - m_type = t; - } - } -} diff --git a/ModernKeePassLib/Translation/KPControlCustomization.cs b/ModernKeePassLib/Translation/KPControlCustomization.cs deleted file mode 100644 index 5edcefc..0000000 --- a/ModernKeePassLib/Translation/KPControlCustomization.cs +++ /dev/null @@ -1,400 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Diagnostics; -using System.Globalization; -using System.Text; -using System.Xml.Serialization; - -#if !KeePassUAP -using System.Drawing; -using System.Windows.Forms; -#endif - -using ModernKeePassLib.Cryptography; -using ModernKeePassLib.Utility; - -namespace ModernKeePassLib.Translation -{ - public sealed class KpccLayout - { - public enum LayoutParameterEx - { - X, Y, Width, Height - } - - private const string m_strControlRelative = @"%c"; - - internal const NumberStyles m_nsParser = (NumberStyles.AllowLeadingSign | - NumberStyles.AllowDecimalPoint); - internal static readonly CultureInfo m_lclInv = CultureInfo.InvariantCulture; - - private string m_strPosX = string.Empty; - [XmlAttribute] - [DefaultValue("")] - public string X - { - get { return m_strPosX; } - set - { - if(value == null) throw new ArgumentNullException("value"); - m_strPosX = value; - } - } - - private string m_strPosY = string.Empty; - [XmlAttribute] - [DefaultValue("")] - public string Y - { - get { return m_strPosY; } - set - { - if(value == null) throw new ArgumentNullException("value"); - m_strPosY = value; - } - } - - private string m_strSizeW = string.Empty; - [XmlAttribute] - [DefaultValue("")] - public string Width - { - get { return m_strSizeW; } - set - { - if(value == null) throw new ArgumentNullException("value"); - m_strSizeW = value; - } - } - - private string m_strSizeH = string.Empty; - [XmlAttribute] - [DefaultValue("")] - public string Height - { - get { return m_strSizeH; } - set - { - if(value == null) throw new ArgumentNullException("value"); - m_strSizeH = value; - } - } - - public void SetControlRelativeValue(LayoutParameterEx lp, string strValue) - { - Debug.Assert(strValue != null); - if(strValue == null) throw new ArgumentNullException("strValue"); - - if(strValue.Length > 0) strValue += m_strControlRelative; - - if(lp == LayoutParameterEx.X) m_strPosX = strValue; - else if(lp == LayoutParameterEx.Y) m_strPosY = strValue; - else if(lp == LayoutParameterEx.Width) m_strSizeW = strValue; - else if(lp == LayoutParameterEx.Height) m_strSizeH = strValue; - else { Debug.Assert(false); } - } - -#if (!KeePassLibSD && !KeePassUAP) - internal void ApplyTo(Control c) - { - Debug.Assert(c != null); if(c == null) return; - - int? v; - v = GetModControlParameter(c, LayoutParameterEx.X, m_strPosX); - if(v.HasValue) c.Left = v.Value; - v = GetModControlParameter(c, LayoutParameterEx.Y, m_strPosY); - if(v.HasValue) c.Top = v.Value; - v = GetModControlParameter(c, LayoutParameterEx.Width, m_strSizeW); - if(v.HasValue) c.Width = v.Value; - v = GetModControlParameter(c, LayoutParameterEx.Height, m_strSizeH); - if(v.HasValue) c.Height = v.Value; - } - - private static int? GetModControlParameter(Control c, LayoutParameterEx p, - string strModParam) - { - if(strModParam.Length == 0) return null; - - Debug.Assert(c.Left == c.Location.X); - Debug.Assert(c.Top == c.Location.Y); - Debug.Assert(c.Width == c.Size.Width); - Debug.Assert(c.Height == c.Size.Height); - - int iPrev; - if(p == LayoutParameterEx.X) iPrev = c.Left; - else if(p == LayoutParameterEx.Y) iPrev = c.Top; - else if(p == LayoutParameterEx.Width) iPrev = c.Width; - else if(p == LayoutParameterEx.Height) iPrev = c.Height; - else { Debug.Assert(false); return null; } - - double? dRel = ToControlRelativePercent(strModParam); - if(dRel.HasValue) - return (iPrev + (int)((dRel.Value * (double)iPrev) / 100.0)); - - Debug.Assert(false); - return null; - } - - public static double? ToControlRelativePercent(string strEncoded) - { - Debug.Assert(strEncoded != null); - if(strEncoded == null) throw new ArgumentNullException("strEncoded"); - - if(strEncoded.Length == 0) return null; - - if(strEncoded.EndsWith(m_strControlRelative)) - { - string strValue = strEncoded.Substring(0, strEncoded.Length - - m_strControlRelative.Length); - if((strValue.Length == 1) && (strValue == "-")) - strValue = "0"; - - double dRel; - if(double.TryParse(strValue, m_nsParser, m_lclInv, out dRel)) - { - return dRel; - } - else - { - Debug.Assert(false); - return null; - } - } - - Debug.Assert(false); - return null; - } -#endif - - public static string ToControlRelativeString(string strEncoded) - { - Debug.Assert(strEncoded != null); - if(strEncoded == null) throw new ArgumentNullException("strEncoded"); - - if(strEncoded.Length == 0) return string.Empty; - - if(strEncoded.EndsWith(m_strControlRelative)) - return strEncoded.Substring(0, strEncoded.Length - - m_strControlRelative.Length); - - Debug.Assert(false); - return string.Empty; - } - } - - public sealed class KPControlCustomization : IComparable - { - private string m_strMemberName = string.Empty; - /// - /// Member variable name of the control to be translated. - /// - [XmlAttribute] - public string Name - { - get { return m_strMemberName; } - set - { - if(value == null) throw new ArgumentNullException("value"); - m_strMemberName = value; - } - } - - private string m_strHash = string.Empty; - [XmlAttribute] - public string BaseHash - { - get { return m_strHash; } - set - { - if(value == null) throw new ArgumentNullException("value"); - m_strHash = value; - } - } - - private string m_strText = string.Empty; - [DefaultValue("")] - public string Text - { - get { return m_strText; } - set - { - if(value == null) throw new ArgumentNullException("value"); - m_strText = value; - } - } - - private string m_strEngText = string.Empty; - [XmlIgnore] - public string TextEnglish - { - get { return m_strEngText; } - set { m_strEngText = value; } - } - - private KpccLayout m_layout = new KpccLayout(); - public KpccLayout Layout - { - get { return m_layout; } - set - { - if(value == null) throw new ArgumentNullException("value"); - m_layout = value; - } - } - - public int CompareTo(KPControlCustomization kpOther) - { - if(kpOther == null) { Debug.Assert(false); return 1; } - - return m_strMemberName.CompareTo(kpOther.Name); - } - -#if (!KeePassLibSD && !KeePassUAP) - private static readonly Type[] m_vTextControls = new Type[] { - typeof(MenuStrip), typeof(PictureBox), typeof(ListView), - typeof(TreeView), typeof(ToolStrip), typeof(WebBrowser), - typeof(Panel), typeof(StatusStrip), typeof(ProgressBar), - typeof(NumericUpDown), typeof(TabControl) - }; - - public static bool ControlSupportsText(object oControl) - { - if(oControl == null) return false; - - Type t = oControl.GetType(); - for(int i = 0; i < m_vTextControls.Length; ++i) - { - if(t == m_vTextControls[i]) return false; - } - - return true; - } - - // Name-unchecked (!) property application method - internal void ApplyTo(Control c) - { - if((m_strText.Length > 0) && ControlSupportsText(c) && - (c.Text.Length > 0)) - { - c.Text = m_strText; - } - - m_layout.ApplyTo(c); - } - - public static string HashControl(Control c) - { - if(c == null) { Debug.Assert(false); return string.Empty; } - - StringBuilder sb = new StringBuilder(); - WriteCpiParam(sb, c.Text); - - if(c is Form) - { - WriteCpiParam(sb, c.ClientSize.Width.ToString(KpccLayout.m_lclInv)); - WriteCpiParam(sb, c.ClientSize.Height.ToString(KpccLayout.m_lclInv)); - } - else // Normal control - { - WriteCpiParam(sb, c.Left.ToString(KpccLayout.m_lclInv)); - WriteCpiParam(sb, c.Top.ToString(KpccLayout.m_lclInv)); - WriteCpiParam(sb, c.Width.ToString(KpccLayout.m_lclInv)); - WriteCpiParam(sb, c.Height.ToString(KpccLayout.m_lclInv)); - WriteCpiParam(sb, c.Dock.ToString()); - } - - WriteCpiParam(sb, c.Font.Name); - WriteCpiParam(sb, c.Font.SizeInPoints.ToString(KpccLayout.m_lclInv)); - WriteCpiParam(sb, c.Font.Bold ? "B" : "N"); - WriteCpiParam(sb, c.Font.Italic ? "I" : "N"); - WriteCpiParam(sb, c.Font.Underline ? "U" : "N"); - WriteCpiParam(sb, c.Font.Strikeout ? "S" : "N"); - - WriteControlDependentParams(sb, c); - - byte[] pb = StrUtil.Utf8.GetBytes(sb.ToString()); - byte[] pbSha = CryptoUtil.HashSha256(pb); - - // See also MatchHash - return "v1:" + Convert.ToBase64String(pbSha, 0, 3, - Base64FormattingOptions.None); - } - - private static void WriteControlDependentParams(StringBuilder sb, Control c) - { - CheckBox cb = (c as CheckBox); - RadioButton rb = (c as RadioButton); - Button btn = (c as Button); - Label l = (c as Label); - LinkLabel ll = (c as LinkLabel); - - if(cb != null) - { - WriteCpiParam(sb, cb.AutoSize ? "A" : "F"); - WriteCpiParam(sb, cb.TextAlign.ToString()); - WriteCpiParam(sb, cb.TextImageRelation.ToString()); - WriteCpiParam(sb, cb.Appearance.ToString()); - WriteCpiParam(sb, cb.CheckAlign.ToString()); - } - else if(rb != null) - { - WriteCpiParam(sb, rb.AutoSize ? "A" : "F"); - WriteCpiParam(sb, rb.TextAlign.ToString()); - WriteCpiParam(sb, rb.TextImageRelation.ToString()); - WriteCpiParam(sb, rb.Appearance.ToString()); - WriteCpiParam(sb, rb.CheckAlign.ToString()); - } - else if(btn != null) - { - WriteCpiParam(sb, btn.AutoSize ? "A" : "F"); - WriteCpiParam(sb, btn.TextAlign.ToString()); - WriteCpiParam(sb, btn.TextImageRelation.ToString()); - } - else if(l != null) - { - WriteCpiParam(sb, l.AutoSize ? "A" : "F"); - WriteCpiParam(sb, l.TextAlign.ToString()); - } - else if(ll != null) - { - WriteCpiParam(sb, ll.AutoSize ? "A" : "F"); - WriteCpiParam(sb, ll.TextAlign.ToString()); - } - } - - private static void WriteCpiParam(StringBuilder sb, string strProp) - { - sb.Append('/'); - sb.Append(strProp); - } - - public bool MatchHash(string strHash) - { - if(strHash == null) throw new ArgumentNullException("strHash"); - - // Currently only v1: is supported, see HashControl - return (m_strHash == strHash); - } -#endif - } -} diff --git a/ModernKeePassLib/Translation/KPFormCustomization.cs b/ModernKeePassLib/Translation/KPFormCustomization.cs deleted file mode 100644 index 2f3c5ad..0000000 --- a/ModernKeePassLib/Translation/KPFormCustomization.cs +++ /dev/null @@ -1,108 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Text; -using System.Xml.Serialization; - -#if !KeePassUAP -using System.Windows.Forms; -#endif - -namespace ModernKeePassLib.Translation -{ - public sealed class KPFormCustomization - { - private string m_strFQName = string.Empty; - /// - /// The fully qualified name of the form. - /// - [XmlAttribute] - public string FullName - { - get { return m_strFQName; } - set - { - if(value == null) throw new ArgumentNullException("value"); - m_strFQName = value; - } - } - - private KPControlCustomization m_ccWindow = new KPControlCustomization(); - public KPControlCustomization Window - { - get { return m_ccWindow; } - set { m_ccWindow = value; } - } - - private List m_vControls = - new List(); - [XmlArray("ChildControls")] - [XmlArrayItem("Control")] - public List Controls - { - get { return m_vControls; } - set - { - if(value == null) throw new ArgumentNullException("value"); - - m_vControls = value; - } - } - -#if (!KeePassLibSD && !KeePassUAP) - private Form m_formEnglish = null; - [XmlIgnore] - public Form FormEnglish - { - get { return m_formEnglish; } - set { m_formEnglish = value; } - } - - public void ApplyTo(Form form) - { - Debug.Assert(form != null); if(form == null) throw new ArgumentNullException("form"); - - // Not supported by TrlUtil (preview form): - // Debug.Assert(form.GetType().FullName == m_strFQName); - - m_ccWindow.ApplyTo(form); - - if(m_vControls.Count == 0) return; - foreach(Control c in form.Controls) ApplyToControl(c); - } - - private void ApplyToControl(Control c) - { - foreach(KPControlCustomization cc in m_vControls) - { - if(c.Name == cc.Name) - { - cc.ApplyTo(c); - break; - } - } - - foreach(Control cSub in c.Controls) ApplyToControl(cSub); - } -#endif - } -} diff --git a/ModernKeePassLib/Translation/KPStringTable.cs b/ModernKeePassLib/Translation/KPStringTable.cs deleted file mode 100644 index ed4af18..0000000 --- a/ModernKeePassLib/Translation/KPStringTable.cs +++ /dev/null @@ -1,102 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Text; -using System.Xml.Serialization; - -#if !KeePassUAP -using System.Windows.Forms; -#endif - -namespace ModernKeePassLib.Translation -{ - public sealed class KPStringTable - { - private string m_strName = string.Empty; - [XmlAttribute] - public string Name - { - get { return m_strName; } - set - { - if(value == null) throw new ArgumentNullException("value"); - m_strName = value; - } - } - - private List m_vItems = new List(); - - [XmlArrayItem("Data")] - public List Strings - { - get { return m_vItems; } - set - { - if(value == null) throw new ArgumentNullException("value"); - m_vItems = value; - } - } - - public Dictionary ToDictionary() - { - Dictionary dict = new Dictionary(); - - foreach(KPStringTableItem kpstItem in m_vItems) - { - if(kpstItem.Value.Length > 0) - dict[kpstItem.Name] = kpstItem.Value; - } - - return dict; - } - -#if (!KeePassLibSD && !KeePassUAP) - public void ApplyTo(ToolStripItemCollection tsic) - { - if(tsic == null) throw new ArgumentNullException("tsic"); - - Dictionary dict = this.ToDictionary(); - if(dict.Count == 0) return; - - this.ApplyTo(tsic, dict); - } - - private void ApplyTo(ToolStripItemCollection tsic, Dictionary dict) - { - if(tsic == null) return; - - foreach(ToolStripItem tsi in tsic) - { - if(tsi.Text.Length == 0) continue; - - string strTrl; - if(dict.TryGetValue(tsi.Name, out strTrl)) - tsi.Text = strTrl; - - ToolStripMenuItem tsmi = tsi as ToolStripMenuItem; - if((tsmi != null) && (tsmi.DropDownItems != null)) - this.ApplyTo(tsmi.DropDownItems); - } - } -#endif - } -} diff --git a/ModernKeePassLib/Translation/KPStringTableItem.cs b/ModernKeePassLib/Translation/KPStringTableItem.cs deleted file mode 100644 index b57ec0b..0000000 --- a/ModernKeePassLib/Translation/KPStringTableItem.cs +++ /dev/null @@ -1,51 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; -using System.Text; -using System.Xml.Serialization; - -namespace ModernKeePassLib.Translation -{ - public sealed class KPStringTableItem - { - private string m_strName = string.Empty; - public string Name - { - get { return m_strName; } - set { m_strName = value; } - } - - private string m_strValue = string.Empty; - public string Value - { - get { return m_strValue; } - set { m_strValue = value; } - } - - private string m_strEnglish = string.Empty; - [XmlIgnore] - public string ValueEnglish - { - get { return m_strEnglish; } - set { m_strEnglish = value; } - } - } -} diff --git a/ModernKeePassLib/Translation/KPTranslation.cs b/ModernKeePassLib/Translation/KPTranslation.cs deleted file mode 100644 index 9678e09..0000000 --- a/ModernKeePassLib/Translation/KPTranslation.cs +++ /dev/null @@ -1,312 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Diagnostics; -using System.IO; -using System.Text; -using System.Xml; -using System.Xml.Serialization; - -#if !KeePassUAP -using System.Drawing; -using System.Windows.Forms; -#endif - -#if KeePassLibSD -using ICSharpCode.SharpZipLib.GZip; -#else -using System.IO.Compression; -#endif - -using ModernKeePassLib.Interfaces; -using ModernKeePassLib.Utility; - -namespace ModernKeePassLib.Translation -{ - [XmlRoot("Translation")] - public sealed class KPTranslation - { - public const string FileExtension = "lngx"; - - private KPTranslationProperties m_props = new KPTranslationProperties(); - public KPTranslationProperties Properties - { - get { return m_props; } - set { m_props = value; } - } - - private List m_vStringTables = new List(); - - [XmlArrayItem("StringTable")] - public List StringTables - { - get { return m_vStringTables; } - set - { - if(value == null) throw new ArgumentNullException("value"); - - m_vStringTables = value; - } - } - - private List m_vForms = new List(); - - [XmlArrayItem("Form")] - public List Forms - { - get { return m_vForms; } - set - { - if(value == null) throw new ArgumentNullException("value"); - - m_vForms = value; - } - } - - private string m_strUnusedText = string.Empty; - [DefaultValue("")] - public string UnusedText - { - get { return m_strUnusedText; } - set - { - if(value == null) throw new ArgumentNullException("value"); - - m_strUnusedText = value; - } - } - - public static void Save(KPTranslation kpTrl, string strFileName, - IXmlSerializerEx xs) - { - using(FileStream fs = new FileStream(strFileName, FileMode.Create, - FileAccess.Write, FileShare.None)) - { - Save(kpTrl, fs, xs); - } - } - - public static void Save(KPTranslation kpTrl, Stream sOut, - IXmlSerializerEx xs) - { - if(xs == null) throw new ArgumentNullException("xs"); - -#if !KeePassLibSD - using(GZipStream gz = new GZipStream(sOut, CompressionMode.Compress)) -#else - using(GZipOutputStream gz = new GZipOutputStream(sOut)) -#endif - { - using(XmlWriter xw = XmlUtilEx.CreateXmlWriter(gz)) - { - xs.Serialize(xw, kpTrl); - } - } - - sOut.Close(); - } - - public static KPTranslation Load(string strFile, IXmlSerializerEx xs) - { - KPTranslation kpTrl = null; - - using(FileStream fs = new FileStream(strFile, FileMode.Open, - FileAccess.Read, FileShare.Read)) - { - kpTrl = Load(fs, xs); - } - - return kpTrl; - } - - public static KPTranslation Load(Stream s, IXmlSerializerEx xs) - { - if(xs == null) throw new ArgumentNullException("xs"); - - KPTranslation kpTrl = null; - -#if !KeePassLibSD - using(GZipStream gz = new GZipStream(s, CompressionMode.Decompress)) -#else - using(GZipInputStream gz = new GZipInputStream(s)) -#endif - { - kpTrl = (xs.Deserialize(gz) as KPTranslation); - } - - s.Close(); - return kpTrl; - } - - public Dictionary SafeGetStringTableDictionary( - string strTableName) - { - foreach(KPStringTable kpst in m_vStringTables) - { - if(kpst.Name == strTableName) return kpst.ToDictionary(); - } - - return new Dictionary(); - } - -#if (!KeePassLibSD && !KeePassRT) - public void ApplyTo(Form form) - { - if(form == null) throw new ArgumentNullException("form"); - - if(m_props.RightToLeft) - { - try - { - form.RightToLeft = RightToLeft.Yes; - form.RightToLeftLayout = true; - } - catch(Exception) { Debug.Assert(false); } - } - - string strTypeName = form.GetType().FullName; - foreach(KPFormCustomization kpfc in m_vForms) - { - if(kpfc.FullName == strTypeName) - { - kpfc.ApplyTo(form); - break; - } - } - - if(m_props.RightToLeft) - { - try { RtlApplyToControls(form.Controls); } - catch(Exception) { Debug.Assert(false); } - } - } - - private static void RtlApplyToControls(Control.ControlCollection cc) - { - foreach(Control c in cc) - { - if(c.Controls.Count > 0) RtlApplyToControls(c.Controls); - - if(c is DateTimePicker) - ((DateTimePicker)c).RightToLeftLayout = true; - else if(c is ListView) - ((ListView)c).RightToLeftLayout = true; - else if(c is MonthCalendar) - ((MonthCalendar)c).RightToLeftLayout = true; - else if(c is ProgressBar) - ((ProgressBar)c).RightToLeftLayout = true; - else if(c is TabControl) - ((TabControl)c).RightToLeftLayout = true; - else if(c is TrackBar) - ((TrackBar)c).RightToLeftLayout = true; - else if(c is TreeView) - ((TreeView)c).RightToLeftLayout = true; - // else if(c is ToolStrip) - // RtlApplyToToolStripItems(((ToolStrip)c).Items); - /* else if(c is Button) // Also see Label - { - Button btn = (c as Button); - Image img = btn.Image; - if(img != null) - { - Image imgNew = (Image)img.Clone(); - imgNew.RotateFlip(RotateFlipType.RotateNoneFlipX); - btn.Image = imgNew; - } - } - else if(c is Label) // Also see Button - { - Label lbl = (c as Label); - Image img = lbl.Image; - if(img != null) - { - Image imgNew = (Image)img.Clone(); - imgNew.RotateFlip(RotateFlipType.RotateNoneFlipX); - lbl.Image = imgNew; - } - } */ - - if(IsRtlMoveChildsRequired(c)) RtlMoveChildControls(c); - } - } - - internal static bool IsRtlMoveChildsRequired(Control c) - { - if(c == null) { Debug.Assert(false); return false; } - - return ((c is GroupBox) || (c is Panel)); - } - - private static void RtlMoveChildControls(Control cParent) - { - int nParentWidth = cParent.Size.Width; - - foreach(Control c in cParent.Controls) - { - DockStyle ds = c.Dock; - if(ds == DockStyle.Left) - c.Dock = DockStyle.Right; - else if(ds == DockStyle.Right) - c.Dock = DockStyle.Left; - else - { - Point ptCur = c.Location; - c.Location = new Point(nParentWidth - c.Size.Width - ptCur.X, ptCur.Y); - } - } - } - - /* private static readonly string[] g_vRtlMirrorItemNames = new string[] { }; - private static void RtlApplyToToolStripItems(ToolStripItemCollection tsic) - { - foreach(ToolStripItem tsi in tsic) - { - if(tsi == null) { Debug.Assert(false); continue; } - - if(Array.IndexOf(g_vRtlMirrorItemNames, tsi.Name) >= 0) - tsi.RightToLeftAutoMirrorImage = true; - - ToolStripDropDownItem tsdd = (tsi as ToolStripDropDownItem); - if(tsdd != null) - RtlApplyToToolStripItems(tsdd.DropDownItems); - } - } */ - - public void ApplyTo(string strTableName, ToolStripItemCollection tsic) - { - if(tsic == null) throw new ArgumentNullException("tsic"); - - KPStringTable kpst = null; - foreach(KPStringTable kpstEnum in m_vStringTables) - { - if(kpstEnum.Name == strTableName) - { - kpst = kpstEnum; - break; - } - } - - if(kpst != null) kpst.ApplyTo(tsic); - } -#endif - } -} diff --git a/ModernKeePassLib/Translation/KPTranslationProperties.cs b/ModernKeePassLib/Translation/KPTranslationProperties.cs deleted file mode 100644 index eb124a2..0000000 --- a/ModernKeePassLib/Translation/KPTranslationProperties.cs +++ /dev/null @@ -1,105 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; -using System.Text; - -namespace ModernKeePassLib.Translation -{ - public sealed class KPTranslationProperties - { - private string m_strApp = string.Empty; - public string Application - { - get { return m_strApp; } - set { m_strApp = value; } - } - - private string m_strForVersion = string.Empty; - public string ApplicationVersion - { - get { return m_strForVersion; } - set { m_strForVersion = value; } - } - - private string m_strNameEnglish = string.Empty; - public string NameEnglish - { - get { return m_strNameEnglish; } - set { m_strNameEnglish = value; } - } - - private string m_strNameNative = string.Empty; - public string NameNative - { - get { return m_strNameNative; } - set { m_strNameNative = value; } - } - - private string m_strIso6391Code = string.Empty; - public string Iso6391Code - { - get { return m_strIso6391Code; } - set { m_strIso6391Code = value; } - } - - private bool m_bRtl = false; - public bool RightToLeft - { - get { return m_bRtl; } - set { m_bRtl = value; } - } - - private string m_strAuthorName = string.Empty; - public string AuthorName - { - get { return m_strAuthorName; } - set { m_strAuthorName = value; } - } - - private string m_strAuthorContact = string.Empty; - public string AuthorContact - { - get { return m_strAuthorContact; } - set { m_strAuthorContact = value; } - } - - private string m_strGen = string.Empty; - public string Generator - { - get { return m_strGen; } - set { m_strGen = value; } - } - - private string m_strUuid = string.Empty; - public string FileUuid - { - get { return m_strUuid; } - set { m_strUuid = value; } - } - - private string m_strLastModified = string.Empty; - public string LastModified - { - get { return m_strLastModified; } - set { m_strLastModified = value; } - } - } -} diff --git a/ModernKeePassLib/Utility/AppLogEx.cs b/ModernKeePassLib/Utility/AppLogEx.cs deleted file mode 100644 index 78eb02b..0000000 --- a/ModernKeePassLib/Utility/AppLogEx.cs +++ /dev/null @@ -1,103 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Text; - -#if !KeePassLibSD -using System.IO.Compression; -#endif - -namespace ModernKeePassLib.Utility -{ - /// - /// Application-wide logging services. - /// - public static class AppLogEx - { - private static StreamWriter m_swOut = null; - - public static void Open(string strPrefix) - { - // Logging is not enabled in normal builds of KeePass! - /* - AppLogEx.Close(); - - Debug.Assert(strPrefix != null); - if(strPrefix == null) strPrefix = "Log"; - - try - { - string strDirSep = string.Empty; - strDirSep += UrlUtil.LocalDirSepChar; - - string strTemp = UrlUtil.GetTempPath(); - if(!strTemp.EndsWith(strDirSep)) - strTemp += strDirSep; - - string strPath = strTemp + strPrefix + "-"; - Debug.Assert(strPath.IndexOf('/') < 0); - - DateTime dtNow = DateTime.UtcNow; - string strTime = dtNow.ToString("s"); - strTime = strTime.Replace('T', '-'); - strTime = strTime.Replace(':', '-'); - - strPath += strTime + "-" + Environment.TickCount.ToString( - NumberFormatInfo.InvariantInfo) + ".log.gz"; - - FileStream fsOut = new FileStream(strPath, FileMode.Create, - FileAccess.Write, FileShare.None); - GZipStream gz = new GZipStream(fsOut, CompressionMode.Compress); - m_swOut = new StreamWriter(gz); - - AppLogEx.Log("Started logging on " + dtNow.ToString("s") + "."); - } - catch(Exception) { Debug.Assert(false); } - */ - } - - public static void Close() - { - if(m_swOut == null) return; - - m_swOut.Dispose(); - m_swOut = null; - } - - public static void Log(string strText) - { - if(m_swOut == null) return; - - if(strText == null) m_swOut.WriteLine(); - else m_swOut.WriteLine(strText); - } - - public static void Log(Exception ex) - { - if(m_swOut == null) return; - - if(ex == null) m_swOut.WriteLine(); - else m_swOut.WriteLine(ex.ToString()); - } - } -} diff --git a/ModernKeePassLib/Utility/ColorTranslator.cs b/ModernKeePassLib/Utility/ColorTranslator.cs deleted file mode 100644 index 842345c..0000000 --- a/ModernKeePassLib/Utility/ColorTranslator.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System; -using System.Drawing; -using System.Globalization; -using System.Text.RegularExpressions; - -namespace ModernKeePassLib.Utility -{ - /// - /// Replacement for System.Drawing.ColorTranslator. - /// - /// - /// Colors are stored in the kdbx database file in HTML format (#XXXXXX). - /// - public static class ColorTranslator - { - static Regex longForm = new Regex("^#([0-9A-Fa-f]{2})([0-9A-Fa-f]{2})([0-9A-Fa-f]{2})$"); - - /// - /// Converts an HTML color value to a Color. - /// - /// The Color. - /// HTML color code. - /// If htmlColor is null. - /// If htmlColor did not match the pattern "#XXXXXX". - /// - /// Currently only understands "#XXXXXX". "#XXX" or named colors will - /// throw and exception. - /// - public static Color FromHtml(string htmlColor) - { - if (htmlColor == null) - throw new ArgumentNullException("htmlColor"); - Match match = longForm.Match(htmlColor); - if (match.Success) { - var r = int.Parse(match.Groups[1].Value, NumberStyles.HexNumber); - var g = int.Parse(match.Groups[2].Value, NumberStyles.HexNumber); - var b = int.Parse(match.Groups[3].Value, NumberStyles.HexNumber); - return Color.FromArgb(r, g, b); - } - throw new ArgumentException(string.Format("Could not parse HTML color '{0}'.", htmlColor), "htmlColor"); - } - - /// - /// Converts a color to an HTML color code. - /// - /// String containing the color code. - /// The Color to convert - /// - /// The string is in the format "#XXXXXX" - /// - public static string ToHtml(Color htmlColor) - { - return string.Format("#{0:x2}{1:x2}{2:x2}", htmlColor.R, htmlColor.G, htmlColor.B); - } - } -} - diff --git a/ModernKeePassLib/Utility/GfxUtil.PCL.cs b/ModernKeePassLib/Utility/GfxUtil.PCL.cs deleted file mode 100644 index 72f10fc..0000000 --- a/ModernKeePassLib/Utility/GfxUtil.PCL.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.IO; -using Splat; - -namespace ModernKeePassLib.Utility -{ - public class GfxUtil - { - public static IBitmap LoadImage(byte[] pb) - { - return null; - //return ScaleImage(pb, null, null); - } - - public static IBitmap ScaleImage(byte[] pb, int? w, int? h) - { - return null; - /*using (var ms = new MemoryStream(pb, false)) - { - return BitmapLoader.Current.Load(ms, w, h).Result; - }*/ - } - } -} diff --git a/ModernKeePassLib/Utility/GfxUtil.cs b/ModernKeePassLib/Utility/GfxUtil.cs deleted file mode 100644 index d97a799..0000000 --- a/ModernKeePassLib/Utility/GfxUtil.cs +++ /dev/null @@ -1,441 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Text; - -#if !KeePassUAP -using System.Drawing; -using System.Drawing.Drawing2D; -using System.Drawing.Imaging; -#endif - -namespace ModernKeePassLib.Utility -{ - public static class GfxUtil - { -#if (!KeePassLibSD && !KeePassUAP) - private sealed class GfxImage - { - public byte[] Data; - - public int Width; - public int Height; - - public GfxImage(byte[] pbData, int w, int h) - { - this.Data = pbData; - this.Width = w; - this.Height = h; - } - -#if DEBUG - // For debugger display - public override string ToString() - { - return (this.Width.ToString() + "x" + this.Height.ToString()); - } -#endif - } -#endif - -#if KeePassUAP - public static Image LoadImage(byte[] pb) - { - if(pb == null) throw new ArgumentNullException("pb"); - - MemoryStream ms = new MemoryStream(pb, false); - try { return Image.FromStream(ms); } - finally { ms.Close(); } - } -#else - public static Image LoadImage(byte[] pb) - { - if(pb == null) throw new ArgumentNullException("pb"); - -#if !KeePassLibSD - // First try to load the data as ICO and afterwards as - // normal image, because trying to load an ICO using - // the normal image loading methods can result in a - // low resolution image - try - { - Image imgIco = ExtractBestImageFromIco(pb); - if(imgIco != null) return imgIco; - } - catch(Exception) { Debug.Assert(false); } -#endif - - MemoryStream ms = new MemoryStream(pb, false); - try { return LoadImagePriv(ms); } - finally { ms.Close(); } - } - - private static Image LoadImagePriv(Stream s) - { - // Image.FromStream wants the stream to be open during - // the whole lifetime of the image; as we can't guarantee - // this, we make a copy of the image - Image imgSrc = null; - try - { -#if !KeePassLibSD - imgSrc = Image.FromStream(s); - Bitmap bmp = new Bitmap(imgSrc.Width, imgSrc.Height, - PixelFormat.Format32bppArgb); - - try - { - bmp.SetResolution(imgSrc.HorizontalResolution, - imgSrc.VerticalResolution); - Debug.Assert(bmp.Size == imgSrc.Size); - } - catch(Exception) { Debug.Assert(false); } -#else - imgSrc = new Bitmap(s); - Bitmap bmp = new Bitmap(imgSrc.Width, imgSrc.Height); -#endif - - using(Graphics g = Graphics.FromImage(bmp)) - { - g.Clear(Color.Transparent); - -#if !KeePassLibSD - g.DrawImageUnscaled(imgSrc, 0, 0); -#else - g.DrawImage(imgSrc, 0, 0); -#endif - } - - return bmp; - } - finally { if(imgSrc != null) imgSrc.Dispose(); } - } - -#if !KeePassLibSD - private static Image ExtractBestImageFromIco(byte[] pb) - { - List l = UnpackIco(pb); - if((l == null) || (l.Count == 0)) return null; - - long qMax = 0; - foreach(GfxImage gi in l) - { - if(gi.Width == 0) gi.Width = 256; - if(gi.Height == 0) gi.Height = 256; - - qMax = Math.Max(qMax, (long)gi.Width * (long)gi.Height); - } - - byte[] pbHdrPng = new byte[] { - 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A - }; - byte[] pbHdrJpeg = new byte[] { 0xFF, 0xD8, 0xFF }; - - Image imgBest = null; - int bppBest = -1; - - foreach(GfxImage gi in l) - { - if(((long)gi.Width * (long)gi.Height) < qMax) continue; - - byte[] pbImg = gi.Data; - Image img = null; - try - { - if((pbImg.Length > pbHdrPng.Length) && - MemUtil.ArraysEqual(pbHdrPng, - MemUtil.Mid(pbImg, 0, pbHdrPng.Length))) - img = GfxUtil.LoadImage(pbImg); - else if((pbImg.Length > pbHdrJpeg.Length) && - MemUtil.ArraysEqual(pbHdrJpeg, - MemUtil.Mid(pbImg, 0, pbHdrJpeg.Length))) - img = GfxUtil.LoadImage(pbImg); - else - { - using(MemoryStream ms = new MemoryStream(pb, false)) - { - using(Icon ico = new Icon(ms, gi.Width, gi.Height)) - { - img = ico.ToBitmap(); - } - } - } - } - catch(Exception) { Debug.Assert(false); } - - if(img == null) continue; - - if((img.Width < gi.Width) || (img.Height < gi.Height)) - { - Debug.Assert(false); - img.Dispose(); - continue; - } - - int bpp = GetBitsPerPixel(img.PixelFormat); - if(bpp > bppBest) - { - if(imgBest != null) imgBest.Dispose(); - - imgBest = img; - bppBest = bpp; - } - else img.Dispose(); - } - - return imgBest; - } - - private static List UnpackIco(byte[] pb) - { - if(pb == null) { Debug.Assert(false); return null; } - - const int SizeICONDIR = 6; - const int SizeICONDIRENTRY = 16; - - if(pb.Length < SizeICONDIR) return null; - if(MemUtil.BytesToUInt16(pb, 0) != 0) return null; // Reserved, 0 - if(MemUtil.BytesToUInt16(pb, 2) != 1) return null; // ICO type, 1 - - int n = MemUtil.BytesToUInt16(pb, 4); - if(n < 0) { Debug.Assert(false); return null; } - - int cbDir = SizeICONDIR + (n * SizeICONDIRENTRY); - if(pb.Length < cbDir) return null; - - List l = new List(); - int iOffset = SizeICONDIR; - for(int i = 0; i < n; ++i) - { - int w = pb[iOffset]; - int h = pb[iOffset + 1]; - if((w < 0) || (h < 0)) { Debug.Assert(false); return null; } - - int cb = MemUtil.BytesToInt32(pb, iOffset + 8); - if(cb <= 0) return null; // Data must have header (even BMP) - - int p = MemUtil.BytesToInt32(pb, iOffset + 12); - if(p < cbDir) return null; - if((p + cb) > pb.Length) return null; - - try - { - byte[] pbImage = MemUtil.Mid(pb, p, cb); - GfxImage img = new GfxImage(pbImage, w, h); - l.Add(img); - } - catch(Exception) { Debug.Assert(false); return null; } - - iOffset += SizeICONDIRENTRY; - } - - return l; - } - - private static int GetBitsPerPixel(PixelFormat f) - { - int bpp = 0; - switch(f) - { - case PixelFormat.Format1bppIndexed: - bpp = 1; - break; - - case PixelFormat.Format4bppIndexed: - bpp = 4; - break; - - case PixelFormat.Format8bppIndexed: - bpp = 8; - break; - - case PixelFormat.Format16bppArgb1555: - case PixelFormat.Format16bppGrayScale: - case PixelFormat.Format16bppRgb555: - case PixelFormat.Format16bppRgb565: - bpp = 16; - break; - - case PixelFormat.Format24bppRgb: - bpp = 24; - break; - - case PixelFormat.Format32bppArgb: - case PixelFormat.Format32bppPArgb: - case PixelFormat.Format32bppRgb: - bpp = 32; - break; - - case PixelFormat.Format48bppRgb: - bpp = 48; - break; - - case PixelFormat.Format64bppArgb: - case PixelFormat.Format64bppPArgb: - bpp = 64; - break; - - default: - Debug.Assert(false); - break; - } - - return bpp; - } - - public static Image ScaleImage(Image img, int w, int h) - { - return ScaleImage(img, w, h, ScaleTransformFlags.None); - } - - /// - /// Resize an image. - /// - /// Image to resize. - /// Width of the returned image. - /// Height of the returned image. - /// Flags to customize scaling behavior. - /// Resized image. This object is always different - /// from (i.e. they can be - /// disposed separately). - public static Image ScaleImage(Image img, int w, int h, - ScaleTransformFlags f) - { - if(img == null) throw new ArgumentNullException("img"); - if(w < 0) throw new ArgumentOutOfRangeException("w"); - if(h < 0) throw new ArgumentOutOfRangeException("h"); - - bool bUIIcon = ((f & ScaleTransformFlags.UIIcon) != - ScaleTransformFlags.None); - - // We must return a Bitmap object for UIUtil.CreateScaledImage - Bitmap bmp = new Bitmap(w, h, PixelFormat.Format32bppArgb); - using(Graphics g = Graphics.FromImage(bmp)) - { - g.Clear(Color.Transparent); - - g.SmoothingMode = SmoothingMode.HighQuality; - g.CompositingQuality = CompositingQuality.HighQuality; - - int wSrc = img.Width; - int hSrc = img.Height; - - InterpolationMode im = InterpolationMode.HighQualityBicubic; - if((wSrc > 0) && (hSrc > 0)) - { - if(bUIIcon && ((w % wSrc) == 0) && ((h % hSrc) == 0)) - im = InterpolationMode.NearestNeighbor; - // else if((w < wSrc) && (h < hSrc)) - // im = InterpolationMode.HighQualityBilinear; - } - else { Debug.Assert(false); } - g.InterpolationMode = im; - - RectangleF rSource = new RectangleF(0.0f, 0.0f, wSrc, hSrc); - RectangleF rDest = new RectangleF(0.0f, 0.0f, w, h); - AdjustScaleRects(ref rSource, ref rDest); - - g.DrawImage(img, rDest, rSource, GraphicsUnit.Pixel); - } - - return bmp; - } - - internal static void AdjustScaleRects(ref RectangleF rSource, - ref RectangleF rDest) - { - // When enlarging images, apply a -0.5 offset to avoid - // the scaled image being cropped on the top/left side; - // when shrinking images, do not apply a -0.5 offset, - // otherwise the image is cropped on the bottom/right - // side; this applies to all interpolation modes - if(rDest.Width > rSource.Width) - rSource.X = rSource.X - 0.5f; - if(rDest.Height > rSource.Height) - rSource.Y = rSource.Y - 0.5f; - - // When shrinking, apply a +0.5 offset, such that the - // scaled image is less cropped on the bottom/right side - if(rDest.Width < rSource.Width) - rSource.X = rSource.X + 0.5f; - if(rDest.Height < rSource.Height) - rSource.Y = rSource.Y + 0.5f; - } - -#if DEBUG - public static Image ScaleTest(Image[] vIcons) - { - Bitmap bmp = new Bitmap(1024, vIcons.Length * (256 + 12), - PixelFormat.Format32bppArgb); - - using(Graphics g = Graphics.FromImage(bmp)) - { - g.Clear(Color.White); - - int[] v = new int[] { 16, 24, 32, 48, 64, 128, 256 }; - - int x; - int y = 8; - - foreach(Image imgIcon in vIcons) - { - if(imgIcon == null) { Debug.Assert(false); continue; } - - x = 128; - - foreach(int q in v) - { - using(Image img = ScaleImage(imgIcon, q, q, - ScaleTransformFlags.UIIcon)) - { - g.DrawImageUnscaled(img, x, y); - } - - x += q + 8; - } - - y += v[v.Length - 1] + 8; - } - } - - return bmp; - } -#endif // DEBUG -#endif // !KeePassLibSD -#endif // KeePassUAP - - internal static string ImageToDataUri(Image img) - { - if(img == null) { Debug.Assert(false); return string.Empty; } - - byte[] pb = null; - using(MemoryStream ms = new MemoryStream()) - { - img.Save(ms, ImageFormat.Png); - pb = ms.ToArray(); - } - - return StrUtil.DataToDataUri(pb, "image/png"); - } - } -} diff --git a/ModernKeePassLib/Utility/MemUtil.cs b/ModernKeePassLib/Utility/MemUtil.cs deleted file mode 100644 index fc3261b..0000000 --- a/ModernKeePassLib/Utility/MemUtil.cs +++ /dev/null @@ -1,872 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Runtime.CompilerServices; -using System.Text; - -#if KeePassLibSD -using KeePassLibSD; -#else -using System.IO.Compression; -#endif - -namespace ModernKeePassLib.Utility -{ - /// - /// Contains static buffer manipulation and string conversion routines. - /// - public static class MemUtil - { - internal static readonly byte[] EmptyByteArray = new byte[0]; - - internal static readonly ArrayHelperEx ArrayHelperExOfChar = - new ArrayHelperEx(); - - private static readonly uint[] m_vSBox = new uint[256] { - 0xCD2FACB3, 0xE78A7F5C, 0x6F0803FC, 0xBCF6E230, - 0x3A321712, 0x06403DB1, 0xD2F84B95, 0xDF22A6E4, - 0x07CE9E5B, 0x31788A0C, 0xF683F6F4, 0xEA061F49, - 0xFA5C2ACA, 0x4B9E494E, 0xB0AB25BA, 0x767731FC, - 0x261893A7, 0x2B09F2CE, 0x046261E4, 0x41367B4B, - 0x18A7F225, 0x8F923C0E, 0x5EF3A325, 0x28D0435E, - 0x84C22919, 0xED66873C, 0x8CEDE444, 0x7FC47C24, - 0xFCFC6BA3, 0x676F928D, 0xB4147187, 0xD8FB126E, - 0x7D798D17, 0xFF82E424, 0x1712FA5B, 0xABB09DD5, - 0x8156BA63, 0x84E4D969, 0xC937FB9A, 0x2F1E5BFC, - 0x178ECA11, 0x0E71CD5F, 0x52AAC6F4, 0x71EEFC8F, - 0x7090D749, 0x21CACA31, 0x92996378, 0x0939A8A8, - 0xE9EE1934, 0xD2718616, 0xF2500543, 0xB911873C, - 0xD3CB3EEC, 0x2BA0DBEB, 0xB42D0A27, 0xECE67C0F, - 0x302925F0, 0x6114F839, 0xD39E6307, 0xE28970D6, - 0xEB982F99, 0x941B4CDF, 0xC540E550, 0x8124FC45, - 0x98B025C7, 0xE2BF90EA, 0x4F57C976, 0xCF546FE4, - 0x59566DC8, 0xE3F4360D, 0xF5F9D231, 0xD6180B22, - 0xB54E088A, 0xB5DFE6A6, 0x3637A36F, 0x056E9284, - 0xAFF8FBC5, 0x19E01648, 0x8611F043, 0xDAE44337, - 0xF61B6A1C, 0x257ACD9E, 0xDD35F507, 0xEF05CAFA, - 0x05EB4A83, 0xFC25CA92, 0x0A4728E6, 0x9CF150EF, - 0xAEEF67DE, 0xA9472337, 0x57C81EFE, 0x3E5E009F, - 0x02CB03BB, 0x2BA85674, 0xF21DC251, 0x78C34A34, - 0xABB1F5BF, 0xB95A2FBD, 0x1FB47777, 0x9A96E8AC, - 0x5D2D2838, 0x55AAC92A, 0x99EE324E, 0x10F6214B, - 0x58ABDFB1, 0x2008794D, 0xBEC880F0, 0xE75E5341, - 0x88015C34, 0x352D8FBF, 0x622B7F6C, 0xF5C59EA2, - 0x1F759D8E, 0xADE56159, 0xCC7B4C25, 0x5B8BC48C, - 0xB6BD15AF, 0x3C5B5110, 0xE74A7C3D, 0xEE613161, - 0x156A1C67, 0x72C06817, 0xEA0A6F69, 0x4CECF993, - 0xCA9D554C, 0x8E20361F, 0x42D396B9, 0x595DE578, - 0x749D7955, 0xFD1BA5FD, 0x81FC160E, 0xDB97E28C, - 0x7CF148F7, 0x0B0B3CF5, 0x534DE605, 0x46421066, - 0xD4B68DD1, 0x9E479CE6, 0xAE667A9D, 0xBC082082, - 0xB06DD6EF, 0x20F0F23F, 0xB99E1551, 0xF47A2E3A, - 0x71DA50C6, 0x67B65779, 0x2A8CB376, 0x1EA71EEE, - 0x29ABCD50, 0xB6EB0C6B, 0x23C10511, 0x6F3F2144, - 0x6AF23012, 0xF696BD9E, 0xB94099D8, 0xAD5A9C81, - 0x7A0794FA, 0x7EDF59D6, 0x1E72E574, 0x8561913C, - 0x4E4D568F, 0xEECB9928, 0x9C124D2E, 0x0848B82C, - 0xF1CA395F, 0x9DAF43DC, 0xF77EC323, 0x394E9B59, - 0x7E200946, 0x8B811D68, 0x16DA3305, 0xAB8DE2C3, - 0xE6C53B64, 0x98C2D321, 0x88A97D81, 0xA7106419, - 0x8E52F7BF, 0x8ED262AF, 0x7CCA974E, 0xF0933241, - 0x040DD437, 0xE143B3D4, 0x3019F56F, 0xB741521D, - 0xF1745362, 0x4C435F9F, 0xB4214D0D, 0x0B0C348B, - 0x5051D189, 0x4C30447E, 0x7393D722, 0x95CEDD0B, - 0xDD994E80, 0xC3D22ED9, 0x739CD900, 0x131EB9C4, - 0xEF1062B2, 0x4F0DE436, 0x52920073, 0x9A7F3D80, - 0x896E7B1B, 0x2C8BBE5A, 0xBD304F8A, 0xA993E22C, - 0x134C41A0, 0xFA989E00, 0x39CE9726, 0xFB89FCCF, - 0xE8FBAC97, 0xD4063FFC, 0x935A2B5A, 0x44C8EE83, - 0xCB2BC7B6, 0x02989E92, 0x75478BEA, 0x144378D0, - 0xD853C087, 0x8897A34E, 0xDD23629D, 0xBDE2A2A2, - 0x581D8ECC, 0x5DA8AEE8, 0xFF8AAFD0, 0xBA2BCF6E, - 0x4BD98DAC, 0xF2EDB9E4, 0xFA2DC868, 0x47E84661, - 0xECEB1C7D, 0x41705CA4, 0x5982E4D4, 0xEB5204A1, - 0xD196CAFB, 0x6414804D, 0x3ABD4B46, 0x8B494C26, - 0xB432D52B, 0x39C5356B, 0x6EC80BF7, 0x71BE5483, - 0xCEC4A509, 0xE9411D61, 0x52F341E5, 0xD2E6197B, - 0x4F02826C, 0xA9E48838, 0xD1F8F247, 0xE4957FB3, - 0x586CCA99, 0x9A8B6A5B, 0x4998FBEA, 0xF762BE4C, - 0x90DFE33C, 0x9731511E, 0x88C6A82F, 0xDD65A4D4 - }; - - /// - /// Convert a hexadecimal string to a byte array. The input string must be - /// even (i.e. its length is a multiple of 2). - /// - /// String containing hexadecimal characters. - /// Returns a byte array. Returns null if the string parameter - /// was null or is an uneven string (i.e. if its length isn't a - /// multiple of 2). - /// Thrown if - /// is null. - public static byte[] HexStringToByteArray(string strHex) - { - if(strHex == null) { Debug.Assert(false); throw new ArgumentNullException("strHex"); } - - int nStrLen = strHex.Length; - if((nStrLen & 1) != 0) { Debug.Assert(false); return null; } - - byte[] pb = new byte[nStrLen / 2]; - byte bt; - char ch; - - for(int i = 0; i < nStrLen; i += 2) - { - ch = strHex[i]; - - if((ch >= '0') && (ch <= '9')) - bt = (byte)(ch - '0'); - else if((ch >= 'a') && (ch <= 'f')) - bt = (byte)(ch - 'a' + 10); - else if((ch >= 'A') && (ch <= 'F')) - bt = (byte)(ch - 'A' + 10); - else { Debug.Assert(false); bt = 0; } - - bt <<= 4; - - ch = strHex[i + 1]; - if((ch >= '0') && (ch <= '9')) - bt += (byte)(ch - '0'); - else if((ch >= 'a') && (ch <= 'f')) - bt += (byte)(ch - 'a' + 10); - else if((ch >= 'A') && (ch <= 'F')) - bt += (byte)(ch - 'A' + 10); - else { Debug.Assert(false); } - - pb[i >> 1] = bt; - } - - return pb; - } - - /// - /// Convert a byte array to a hexadecimal string. - /// - /// Input byte array. - /// Returns the hexadecimal string representing the byte - /// array. Returns null, if the input byte array was null. Returns - /// an empty string, if the input byte array has length 0. - public static string ByteArrayToHexString(byte[] pbArray) - { - if(pbArray == null) return null; - - int nLen = pbArray.Length; - if(nLen == 0) return string.Empty; - - StringBuilder sb = new StringBuilder(); - - byte bt, btHigh, btLow; - for(int i = 0; i < nLen; ++i) - { - bt = pbArray[i]; - btHigh = bt; btHigh >>= 4; - btLow = (byte)(bt & 0x0F); - - if(btHigh >= 10) sb.Append((char)('A' + btHigh - 10)); - else sb.Append((char)('0' + btHigh)); - - if(btLow >= 10) sb.Append((char)('A' + btLow - 10)); - else sb.Append((char)('0' + btLow)); - } - - return sb.ToString(); - } - - /// - /// Decode Base32 strings according to RFC 4648. - /// - public static byte[] ParseBase32(string str) - { - if((str == null) || ((str.Length % 8) != 0)) - { - Debug.Assert(false); - return null; - } - - ulong uMaxBits = (ulong)str.Length * 5UL; - List l = new List((int)(uMaxBits / 8UL) + 1); - Debug.Assert(l.Count == 0); - - for(int i = 0; i < str.Length; i += 8) - { - ulong u = 0; - int nBits = 0; - - for(int j = 0; j < 8; ++j) - { - char ch = str[i + j]; - if(ch == '=') break; - - ulong uValue; - if((ch >= 'A') && (ch <= 'Z')) - uValue = (ulong)(ch - 'A'); - else if((ch >= 'a') && (ch <= 'z')) - uValue = (ulong)(ch - 'a'); - else if((ch >= '2') && (ch <= '7')) - uValue = (ulong)(ch - '2') + 26UL; - else { Debug.Assert(false); return null; } - - u <<= 5; - u += uValue; - nBits += 5; - } - - int nBitsTooMany = (nBits % 8); - u >>= nBitsTooMany; - nBits -= nBitsTooMany; - Debug.Assert((nBits % 8) == 0); - - int idxNewBytes = l.Count; - while(nBits > 0) - { - l.Add((byte)(u & 0xFF)); - u >>= 8; - nBits -= 8; - } - l.Reverse(idxNewBytes, l.Count - idxNewBytes); - } - - return l.ToArray(); - } - - /// - /// Set all bytes in a byte array to zero. - /// - /// Input array. All bytes of this array - /// will be set to zero. - public static void ZeroByteArray(byte[] pbArray) - { - Debug.Assert(pbArray != null); - if(pbArray == null) throw new ArgumentNullException("pbArray"); - - Array.Clear(pbArray, 0, pbArray.Length); - } - - /// - /// Set all elements of an array to the default value. - /// - /// Input array. -#if KeePassLibSD - [MethodImpl(MethodImplOptions.NoInlining)] -#else - [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] -#endif - public static void ZeroArray(T[] v) - { - if(v == null) { Debug.Assert(false); throw new ArgumentNullException("v"); } - - Array.Clear(v, 0, v.Length); - } - - /// - /// Convert 2 bytes to a 16-bit unsigned integer (little-endian). - /// - public static ushort BytesToUInt16(byte[] pb) - { - Debug.Assert((pb != null) && (pb.Length == 2)); - if(pb == null) throw new ArgumentNullException("pb"); - if(pb.Length != 2) throw new ArgumentOutOfRangeException("pb"); - - return (ushort)((ushort)pb[0] | ((ushort)pb[1] << 8)); - } - - /// - /// Convert 2 bytes to a 16-bit unsigned integer (little-endian). - /// - public static ushort BytesToUInt16(byte[] pb, int iOffset) - { - if(pb == null) { Debug.Assert(false); throw new ArgumentNullException("pb"); } - if((iOffset < 0) || ((iOffset + 1) >= pb.Length)) - { - Debug.Assert(false); - throw new ArgumentOutOfRangeException("iOffset"); - } - - return (ushort)((ushort)pb[iOffset] | ((ushort)pb[iOffset + 1] << 8)); - } - - /// - /// Convert 4 bytes to a 32-bit unsigned integer (little-endian). - /// - public static uint BytesToUInt32(byte[] pb) - { - Debug.Assert((pb != null) && (pb.Length == 4)); - if(pb == null) throw new ArgumentNullException("pb"); - if(pb.Length != 4) throw new ArgumentOutOfRangeException("pb"); - - return ((uint)pb[0] | ((uint)pb[1] << 8) | ((uint)pb[2] << 16) | - ((uint)pb[3] << 24)); - } - - /// - /// Convert 4 bytes to a 32-bit unsigned integer (little-endian). - /// - public static uint BytesToUInt32(byte[] pb, int iOffset) - { - if(pb == null) { Debug.Assert(false); throw new ArgumentNullException("pb"); } - if((iOffset < 0) || ((iOffset + 3) >= pb.Length)) - { - Debug.Assert(false); - throw new ArgumentOutOfRangeException("iOffset"); - } - - return ((uint)pb[iOffset] | ((uint)pb[iOffset + 1] << 8) | - ((uint)pb[iOffset + 2] << 16) | ((uint)pb[iOffset + 3] << 24)); - } - - /// - /// Convert 8 bytes to a 64-bit unsigned integer (little-endian). - /// - public static ulong BytesToUInt64(byte[] pb) - { - Debug.Assert((pb != null) && (pb.Length == 8)); - if(pb == null) throw new ArgumentNullException("pb"); - if(pb.Length != 8) throw new ArgumentOutOfRangeException("pb"); - - return ((ulong)pb[0] | ((ulong)pb[1] << 8) | ((ulong)pb[2] << 16) | - ((ulong)pb[3] << 24) | ((ulong)pb[4] << 32) | ((ulong)pb[5] << 40) | - ((ulong)pb[6] << 48) | ((ulong)pb[7] << 56)); - } - - /// - /// Convert 8 bytes to a 64-bit unsigned integer (little-endian). - /// - public static ulong BytesToUInt64(byte[] pb, int iOffset) - { - if(pb == null) { Debug.Assert(false); throw new ArgumentNullException("pb"); } - if((iOffset < 0) || ((iOffset + 7) >= pb.Length)) - { - Debug.Assert(false); - throw new ArgumentOutOfRangeException("iOffset"); - } - - // if(BitConverter.IsLittleEndian) - // return BitConverter.ToUInt64(pb, iOffset); - - return ((ulong)pb[iOffset] | ((ulong)pb[iOffset + 1] << 8) | - ((ulong)pb[iOffset + 2] << 16) | ((ulong)pb[iOffset + 3] << 24) | - ((ulong)pb[iOffset + 4] << 32) | ((ulong)pb[iOffset + 5] << 40) | - ((ulong)pb[iOffset + 6] << 48) | ((ulong)pb[iOffset + 7] << 56)); - } - - public static int BytesToInt32(byte[] pb) - { - return (int)BytesToUInt32(pb); - } - - public static int BytesToInt32(byte[] pb, int iOffset) - { - return (int)BytesToUInt32(pb, iOffset); - } - - public static long BytesToInt64(byte[] pb) - { - return (long)BytesToUInt64(pb); - } - - public static long BytesToInt64(byte[] pb, int iOffset) - { - return (long)BytesToUInt64(pb, iOffset); - } - - /// - /// Convert a 16-bit unsigned integer to 2 bytes (little-endian). - /// - public static byte[] UInt16ToBytes(ushort uValue) - { - byte[] pb = new byte[2]; - - unchecked - { - pb[0] = (byte)uValue; - pb[1] = (byte)(uValue >> 8); - } - - return pb; - } - - /// - /// Convert a 32-bit unsigned integer to 4 bytes (little-endian). - /// - public static byte[] UInt32ToBytes(uint uValue) - { - byte[] pb = new byte[4]; - - unchecked - { - pb[0] = (byte)uValue; - pb[1] = (byte)(uValue >> 8); - pb[2] = (byte)(uValue >> 16); - pb[3] = (byte)(uValue >> 24); - } - - return pb; - } - - /// - /// Convert a 32-bit unsigned integer to 4 bytes (little-endian). - /// - public static void UInt32ToBytesEx(uint uValue, byte[] pb, int iOffset) - { - if(pb == null) { Debug.Assert(false); throw new ArgumentNullException("pb"); } - if((iOffset < 0) || ((iOffset + 3) >= pb.Length)) - { - Debug.Assert(false); - throw new ArgumentOutOfRangeException("iOffset"); - } - - unchecked - { - pb[iOffset] = (byte)uValue; - pb[iOffset + 1] = (byte)(uValue >> 8); - pb[iOffset + 2] = (byte)(uValue >> 16); - pb[iOffset + 3] = (byte)(uValue >> 24); - } - } - - /// - /// Convert a 64-bit unsigned integer to 8 bytes (little-endian). - /// - public static byte[] UInt64ToBytes(ulong uValue) - { - byte[] pb = new byte[8]; - - unchecked - { - pb[0] = (byte)uValue; - pb[1] = (byte)(uValue >> 8); - pb[2] = (byte)(uValue >> 16); - pb[3] = (byte)(uValue >> 24); - pb[4] = (byte)(uValue >> 32); - pb[5] = (byte)(uValue >> 40); - pb[6] = (byte)(uValue >> 48); - pb[7] = (byte)(uValue >> 56); - } - - return pb; - } - - /// - /// Convert a 64-bit unsigned integer to 8 bytes (little-endian). - /// - public static void UInt64ToBytesEx(ulong uValue, byte[] pb, int iOffset) - { - if(pb == null) { Debug.Assert(false); throw new ArgumentNullException("pb"); } - if((iOffset < 0) || ((iOffset + 7) >= pb.Length)) - { - Debug.Assert(false); - throw new ArgumentOutOfRangeException("iOffset"); - } - - unchecked - { - pb[iOffset] = (byte)uValue; - pb[iOffset + 1] = (byte)(uValue >> 8); - pb[iOffset + 2] = (byte)(uValue >> 16); - pb[iOffset + 3] = (byte)(uValue >> 24); - pb[iOffset + 4] = (byte)(uValue >> 32); - pb[iOffset + 5] = (byte)(uValue >> 40); - pb[iOffset + 6] = (byte)(uValue >> 48); - pb[iOffset + 7] = (byte)(uValue >> 56); - } - } - - public static byte[] Int32ToBytes(int iValue) - { - return UInt32ToBytes((uint)iValue); - } - - public static void Int32ToBytesEx(int iValue, byte[] pb, int iOffset) - { - UInt32ToBytesEx((uint)iValue, pb, iOffset); - } - - public static byte[] Int64ToBytes(long lValue) - { - return UInt64ToBytes((ulong)lValue); - } - - public static void Int64ToBytesEx(long lValue, byte[] pb, int iOffset) - { - UInt64ToBytesEx((ulong)lValue, pb, iOffset); - } - - public static uint RotateLeft32(uint u, int nBits) - { - return ((u << nBits) | (u >> (32 - nBits))); - } - - public static uint RotateRight32(uint u, int nBits) - { - return ((u >> nBits) | (u << (32 - nBits))); - } - - public static ulong RotateLeft64(ulong u, int nBits) - { - return ((u << nBits) | (u >> (64 - nBits))); - } - - public static ulong RotateRight64(ulong u, int nBits) - { - return ((u >> nBits) | (u << (64 - nBits))); - } - - public static bool ArraysEqual(byte[] x, byte[] y) - { - // Return false if one of them is null (not comparable)! - if((x == null) || (y == null)) { Debug.Assert(false); return false; } - - if(x.Length != y.Length) return false; - - for(int i = 0; i < x.Length; ++i) - { - if(x[i] != y[i]) return false; - } - - return true; - } - - public static void XorArray(byte[] pbSource, int iSourceOffset, - byte[] pbBuffer, int iBufferOffset, int cb) - { - if(pbSource == null) throw new ArgumentNullException("pbSource"); - if(iSourceOffset < 0) throw new ArgumentOutOfRangeException("iSourceOffset"); - if(pbBuffer == null) throw new ArgumentNullException("pbBuffer"); - if(iBufferOffset < 0) throw new ArgumentOutOfRangeException("iBufferOffset"); - if(cb < 0) throw new ArgumentOutOfRangeException("cb"); - if(iSourceOffset > (pbSource.Length - cb)) - throw new ArgumentOutOfRangeException("cb"); - if(iBufferOffset > (pbBuffer.Length - cb)) - throw new ArgumentOutOfRangeException("cb"); - - for(int i = 0; i < cb; ++i) - pbBuffer[iBufferOffset + i] ^= pbSource[iSourceOffset + i]; - } - - /// - /// Fast hash that can be used e.g. for hash tables. - /// The algorithm might change in the future; do not store - /// the hashes for later use. - /// - public static uint Hash32(byte[] v, int iStart, int iLength) - { - uint u = 0x326F637B; - - if(v == null) { Debug.Assert(false); return u; } - if(iStart < 0) { Debug.Assert(false); return u; } - if(iLength < 0) { Debug.Assert(false); return u; } - - int m = iStart + iLength; - if(m > v.Length) { Debug.Assert(false); return u; } - - for(int i = iStart; i < m; ++i) - { - u ^= m_vSBox[v[i]]; - u *= 3; - } - - return u; - } - - public static void CopyStream(Stream sSource, Stream sTarget) - { - Debug.Assert((sSource != null) && (sTarget != null)); - if(sSource == null) throw new ArgumentNullException("sSource"); - if(sTarget == null) throw new ArgumentNullException("sTarget"); - - const int nBufSize = 4096; - byte[] pbBuf = new byte[nBufSize]; - - while(true) - { - int nRead = sSource.Read(pbBuf, 0, nBufSize); - if(nRead == 0) break; - - sTarget.Write(pbBuf, 0, nRead); - } - - // Do not close any of the streams - } - - public static byte[] Read(Stream s, int nCount) - { - if(s == null) throw new ArgumentNullException("s"); - if(nCount < 0) throw new ArgumentOutOfRangeException("nCount"); - - byte[] pb = new byte[nCount]; - int iOffset = 0; - while(nCount > 0) - { - int iRead = s.Read(pb, iOffset, nCount); - if(iRead == 0) break; - - iOffset += iRead; - nCount -= iRead; - } - - if(iOffset != pb.Length) - { - byte[] pbPart = new byte[iOffset]; - Array.Copy(pb, pbPart, iOffset); - return pbPart; - } - - return pb; - } - - public static void Write(Stream s, byte[] pbData) - { - if(s == null) { Debug.Assert(false); return; } - if(pbData == null) { Debug.Assert(false); return; } - - Debug.Assert(pbData.Length >= 0); - if(pbData.Length > 0) s.Write(pbData, 0, pbData.Length); - } - - public static byte[] Compress(byte[] pbData) - { - if(pbData == null) throw new ArgumentNullException("pbData"); - if(pbData.Length == 0) return pbData; - - byte[] pbCompressed; - using(MemoryStream msSource = new MemoryStream(pbData, false)) - { - using(MemoryStream msCompressed = new MemoryStream()) - { - using(GZipStream gz = new GZipStream(msCompressed, - CompressionMode.Compress)) - { - MemUtil.CopyStream(msSource, gz); - } - - pbCompressed = msCompressed.ToArray(); - } - } - - return pbCompressed; - } - - public static byte[] Decompress(byte[] pbCompressed) - { - if(pbCompressed == null) throw new ArgumentNullException("pbCompressed"); - if(pbCompressed.Length == 0) return pbCompressed; - - byte[] pbData; - using(MemoryStream msData = new MemoryStream()) - { - using(MemoryStream msCompressed = new MemoryStream(pbCompressed, false)) - { - using(GZipStream gz = new GZipStream(msCompressed, - CompressionMode.Decompress)) - { - MemUtil.CopyStream(gz, msData); - } - } - - pbData = msData.ToArray(); - } - - return pbData; - } - - public static int IndexOf(T[] vHaystack, T[] vNeedle) - where T : IEquatable - { - if(vHaystack == null) throw new ArgumentNullException("vHaystack"); - if(vNeedle == null) throw new ArgumentNullException("vNeedle"); - if(vNeedle.Length == 0) return 0; - - for(int i = 0; i <= (vHaystack.Length - vNeedle.Length); ++i) - { - bool bFound = true; - for(int m = 0; m < vNeedle.Length; ++m) - { - if(!vHaystack[i + m].Equals(vNeedle[m])) - { - bFound = false; - break; - } - } - if(bFound) return i; - } - - return -1; - } - - public static T[] Mid(T[] v, int iOffset, int iLength) - { - if(v == null) throw new ArgumentNullException("v"); - if(iOffset < 0) throw new ArgumentOutOfRangeException("iOffset"); - if(iLength < 0) throw new ArgumentOutOfRangeException("iLength"); - if((iOffset + iLength) > v.Length) throw new ArgumentException(); - - T[] r = new T[iLength]; - Array.Copy(v, iOffset, r, 0, iLength); - return r; - } - - public static IEnumerable Union(IEnumerable a, IEnumerable b, - IEqualityComparer cmp) - { - if(a == null) throw new ArgumentNullException("a"); - if(b == null) throw new ArgumentNullException("b"); - - Dictionary d = ((cmp != null) ? - (new Dictionary(cmp)) : (new Dictionary())); - - foreach(T ta in a) - { - if(d.ContainsKey(ta)) continue; // Prevent duplicates - - d[ta] = true; - yield return ta; - } - - foreach(T tb in b) - { - if(d.ContainsKey(tb)) continue; // Prevent duplicates - - d[tb] = true; - yield return tb; - } - - yield break; - } - - public static IEnumerable Intersect(IEnumerable a, IEnumerable b, - IEqualityComparer cmp) - { - if(a == null) throw new ArgumentNullException("a"); - if(b == null) throw new ArgumentNullException("b"); - - Dictionary d = ((cmp != null) ? - (new Dictionary(cmp)) : (new Dictionary())); - - foreach(T tb in b) { d[tb] = true; } - - foreach(T ta in a) - { - if(d.Remove(ta)) // Prevent duplicates - yield return ta; - } - - yield break; - } - - public static IEnumerable Except(IEnumerable a, IEnumerable b, - IEqualityComparer cmp) - { - if(a == null) throw new ArgumentNullException("a"); - if(b == null) throw new ArgumentNullException("b"); - - Dictionary d = ((cmp != null) ? - (new Dictionary(cmp)) : (new Dictionary())); - - foreach(T tb in b) { d[tb] = true; } - - foreach(T ta in a) - { - if(d.ContainsKey(ta)) continue; - - d[ta] = true; // Prevent duplicates - yield return ta; - } - - yield break; - } - } - - internal sealed class ArrayHelperEx : IEqualityComparer, IComparer - where T : IEquatable, IComparable - { - public int GetHashCode(T[] obj) - { - if(obj == null) throw new ArgumentNullException("obj"); - - uint h = 0xC17962B7U; - unchecked - { - int n = obj.Length; - for(int i = 0; i < n; ++i) - { - h += (uint)obj[i].GetHashCode(); - h = MemUtil.RotateLeft32(h * 0x5FC34C67U, 13); - } - } - - return (int)h; - } - - /* internal ulong GetHashCodeEx(T[] obj) - { - if(obj == null) throw new ArgumentNullException("obj"); - - ulong h = 0x207CAC8E509A3FC9UL; - unchecked - { - int n = obj.Length; - for(int i = 0; i < n; ++i) - { - h += (uint)obj[i].GetHashCode(); - h = MemUtil.RotateLeft64(h * 0x54724D3EA2860CBBUL, 29); - } - } - - return h; - } */ - - public bool Equals(T[] x, T[] y) - { - if(object.ReferenceEquals(x, y)) return true; - if((x == null) || (y == null)) return false; - - int n = x.Length; - if(n != y.Length) return false; - - for(int i = 0; i < n; ++i) - { - if(!x[i].Equals(y[i])) return false; - } - - return true; - } - - public int Compare(T[] x, T[] y) - { - if(object.ReferenceEquals(x, y)) return 0; - if(x == null) return -1; - if(y == null) return 1; - - int n = x.Length, m = y.Length; - if(n != m) return ((n < m) ? -1 : 1); - - for(int i = 0; i < n; ++i) - { - T tX = x[i], tY = y[i]; - if(!tX.Equals(tY)) return tX.CompareTo(tY); - } - - return 0; - } - } -} diff --git a/ModernKeePassLib/Utility/MessageService.cs b/ModernKeePassLib/Utility/MessageService.cs deleted file mode 100644 index a8a0d4c..0000000 --- a/ModernKeePassLib/Utility/MessageService.cs +++ /dev/null @@ -1,459 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; -using System.Collections.Specialized; -using System.Diagnostics; -using System.Text; - -#if !ModernKeePassLib -using System.Windows.Forms; -#endif - -using ModernKeePassLib.Resources; -using ModernKeePassLib.Serialization; - -namespace ModernKeePassLib.Utility -{ - public sealed class MessageServiceEventArgs : EventArgs - { -#if !ModernKeePassLib - private string m_strTitle = string.Empty; - private string m_strText = string.Empty; - private MessageBoxButtons m_msgButtons = MessageBoxButtons.OK; - private MessageBoxIcon m_msgIcon = MessageBoxIcon.None; - - public string Title { get { return m_strTitle; } } - public string Text { get { return m_strText; } } - public MessageBoxButtons Buttons { get { return m_msgButtons; } } - public MessageBoxIcon Icon { get { return m_msgIcon; } } - - public MessageServiceEventArgs() { } - - public MessageServiceEventArgs(string strTitle, string strText, - MessageBoxButtons msgButtons, MessageBoxIcon msgIcon) - { - m_strTitle = (strTitle ?? string.Empty); - m_strText = (strText ?? string.Empty); - m_msgButtons = msgButtons; - m_msgIcon = msgIcon; - } -#endif - } - - public static class MessageService - { - private static volatile uint m_uCurrentMessageCount = 0; - -#if !ModernKeePassLib -#if !KeePassLibSD - private const MessageBoxIcon m_mbiInfo = MessageBoxIcon.Information; - private const MessageBoxIcon m_mbiWarning = MessageBoxIcon.Warning; - private const MessageBoxIcon m_mbiFatal = MessageBoxIcon.Error; - - private const MessageBoxOptions m_mboRtl = (MessageBoxOptions.RtlReading | - MessageBoxOptions.RightAlign); -#else - private const MessageBoxIcon m_mbiInfo = MessageBoxIcon.Asterisk; - private const MessageBoxIcon m_mbiWarning = MessageBoxIcon.Exclamation; - private const MessageBoxIcon m_mbiFatal = MessageBoxIcon.Hand; -#endif - private const MessageBoxIcon m_mbiQuestion = MessageBoxIcon.Question; -#endif - public static string NewLine - { -#if !KeePassLibSD - get { return Environment.NewLine; } -#else - get { return "\r\n"; } -#endif - } - - public static string NewParagraph - { -#if !KeePassLibSD - get { return Environment.NewLine + Environment.NewLine; } -#else - get { return "\r\n\r\n"; } -#endif - } - - public static uint CurrentMessageCount - { - get { return m_uCurrentMessageCount; } - } - -#if !KeePassUAP - public static event EventHandler MessageShowing; -#endif - - private static string ObjectsToMessage(object[] vLines) - { - return ObjectsToMessage(vLines, false); - } - - private static string ObjectsToMessage(object[] vLines, bool bFullExceptions) - { - if(vLines == null) return string.Empty; - - string strNewPara = Environment.NewLine; - - StringBuilder sbText = new StringBuilder(); - bool bSeparator = false; - - foreach(object obj in vLines) - { - if(obj == null) continue; - - string strAppend = null; - - Exception exObj = (obj as Exception); - string strObj = (obj as string); -#if (!KeePassLibSD && !ModernKeePassLib) - StringCollection scObj = (obj as StringCollection); -#endif - - if(exObj != null) - { - if(bFullExceptions) - strAppend = StrUtil.FormatException(exObj); - else if(!string.IsNullOrEmpty(exObj.Message)) - strAppend = exObj.Message; - } -#if (!KeePassLibSD && !ModernKeePassLib) - else if(scObj != null) - { - StringBuilder sb = new StringBuilder(); - foreach(string strCollLine in scObj) - { - if(sb.Length > 0) sb.AppendLine(); - sb.Append(strCollLine.TrimEnd()); - } - strAppend = sb.ToString(); - } -#endif - else if (strObj != null) - strAppend = strObj; - else - strAppend = obj.ToString(); - - if(!string.IsNullOrEmpty(strAppend)) - { - if(bSeparator) sbText.Append(strNewPara); - else bSeparator = true; - - sbText.Append(strAppend); - } - } - - return sbText.ToString(); - } - -#if (!KeePassLibSD && !ModernKeePassLib) - internal static Form GetTopForm() - { - FormCollection fc = Application.OpenForms; - if((fc == null) || (fc.Count == 0)) return null; - - return fc[fc.Count - 1]; - } -#endif - -#if !ModernKeePassLib - internal static DialogResult SafeShowMessageBox(string strText, string strTitle, - MessageBoxButtons mb, MessageBoxIcon mi, MessageBoxDefaultButton mdb) - { -#if (KeePassLibSD || KeePassRT) - return MessageBox.Show(strText, strTitle, mb, mi, mdb); -#else - IWin32Window wnd = null; - try - { - Form f = GetTopForm(); - if((f != null) && f.InvokeRequired) - return (DialogResult)f.Invoke(new SafeShowMessageBoxInternalDelegate( - SafeShowMessageBoxInternal), f, strText, strTitle, mb, mi, mdb); - else wnd = f; - } - catch(Exception) { Debug.Assert(false); } - - if(wnd == null) - { - if(StrUtil.RightToLeft) - return MessageBox.Show(strText, strTitle, mb, mi, mdb, m_mboRtl); - return MessageBox.Show(strText, strTitle, mb, mi, mdb); - } - - try - { - if(StrUtil.RightToLeft) - return MessageBox.Show(wnd, strText, strTitle, mb, mi, mdb, m_mboRtl); - return MessageBox.Show(wnd, strText, strTitle, mb, mi, mdb); - } - catch(Exception) { Debug.Assert(false); } - - if(StrUtil.RightToLeft) - return MessageBox.Show(strText, strTitle, mb, mi, mdb, m_mboRtl); - return MessageBox.Show(strText, strTitle, mb, mi, mdb); -#endif - } - -#if (!KeePassLibSD && !KeePassRT) - internal delegate DialogResult SafeShowMessageBoxInternalDelegate(IWin32Window iParent, - string strText, string strTitle, MessageBoxButtons mb, MessageBoxIcon mi, - MessageBoxDefaultButton mdb); - - internal static DialogResult SafeShowMessageBoxInternal(IWin32Window iParent, - string strText, string strTitle, MessageBoxButtons mb, MessageBoxIcon mi, - MessageBoxDefaultButton mdb) - { - if(StrUtil.RightToLeft) - return MessageBox.Show(iParent, strText, strTitle, mb, mi, mdb, m_mboRtl); - return MessageBox.Show(iParent, strText, strTitle, mb, mi, mdb); - } -#endif - - public static void ShowInfo(params object[] vLines) - { - ShowInfoEx(null, vLines); - } - - public static void ShowInfoEx(string strTitle, params object[] vLines) - { - ++m_uCurrentMessageCount; - - strTitle = (strTitle ?? PwDefs.ShortProductName); - string strText = ObjectsToMessage(vLines); - - if(MessageService.MessageShowing != null) - MessageService.MessageShowing(null, new MessageServiceEventArgs( - strTitle, strText, MessageBoxButtons.OK, m_mbiInfo)); - - SafeShowMessageBox(strText, strTitle, MessageBoxButtons.OK, m_mbiInfo, - MessageBoxDefaultButton.Button1); - - --m_uCurrentMessageCount; - } - - public static void ShowWarning(params object[] vLines) - { - ShowWarningPriv(vLines, false); - } - - internal static void ShowWarningExcp(params object[] vLines) - { - ShowWarningPriv(vLines, true); - } - - private static void ShowWarningPriv(object[] vLines, bool bFullExceptions) - { - ++m_uCurrentMessageCount; - - string strTitle = PwDefs.ShortProductName; - string strText = ObjectsToMessage(vLines, bFullExceptions); - - if(MessageService.MessageShowing != null) - MessageService.MessageShowing(null, new MessageServiceEventArgs( - strTitle, strText, MessageBoxButtons.OK, m_mbiWarning)); - - SafeShowMessageBox(strText, strTitle, MessageBoxButtons.OK, m_mbiWarning, - MessageBoxDefaultButton.Button1); - - --m_uCurrentMessageCount; - } - - public static void ShowFatal(params object[] vLines) - { - ++m_uCurrentMessageCount; - - string strTitle = PwDefs.ShortProductName + @" - " + KLRes.FatalError; - string strText = KLRes.FatalErrorText + Environment.NewLine + - KLRes.ErrorInClipboard + Environment.NewLine + - // Please send it to the KeePass developers. - // KLRes.ErrorFeedbackRequest + Environment.NewLine + - ObjectsToMessage(vLines); - - try - { - string strDetails = ObjectsToMessage(vLines, true); - -#if KeePassLibSD - Clipboard.SetDataObject(strDetails); -#else - Clipboard.Clear(); - Clipboard.SetText(strDetails); -#endif - } - catch(Exception) { Debug.Assert(false); } - - if(MessageService.MessageShowing != null) - MessageService.MessageShowing(null, new MessageServiceEventArgs( - strTitle, strText, MessageBoxButtons.OK, m_mbiFatal)); - - SafeShowMessageBox(strText, strTitle, MessageBoxButtons.OK, m_mbiFatal, - MessageBoxDefaultButton.Button1); - - --m_uCurrentMessageCount; - } - - public static DialogResult Ask(string strText, string strTitle, - MessageBoxButtons mbb) - { - ++m_uCurrentMessageCount; - - string strTextEx = (strText ?? string.Empty); - string strTitleEx = (strTitle ?? PwDefs.ShortProductName); - - if(MessageService.MessageShowing != null) - MessageService.MessageShowing(null, new MessageServiceEventArgs( - strTitleEx, strTextEx, mbb, m_mbiQuestion)); - - DialogResult dr = SafeShowMessageBox(strTextEx, strTitleEx, mbb, - m_mbiQuestion, MessageBoxDefaultButton.Button1); - - --m_uCurrentMessageCount; - return dr; - } - - public static bool AskYesNo(string strText, string strTitle, bool bDefaultToYes, - MessageBoxIcon mbi) - { - ++m_uCurrentMessageCount; - - string strTextEx = (strText ?? string.Empty); - string strTitleEx = (strTitle ?? PwDefs.ShortProductName); - - if(MessageService.MessageShowing != null) - MessageService.MessageShowing(null, new MessageServiceEventArgs( - strTitleEx, strTextEx, MessageBoxButtons.YesNo, mbi)); - - DialogResult dr = SafeShowMessageBox(strTextEx, strTitleEx, - MessageBoxButtons.YesNo, mbi, bDefaultToYes ? - MessageBoxDefaultButton.Button1 : MessageBoxDefaultButton.Button2); - - --m_uCurrentMessageCount; - return (dr == DialogResult.Yes); - } - - public static bool AskYesNo(string strText, string strTitle, bool bDefaultToYes) - { - return AskYesNo(strText, strTitle, bDefaultToYes, m_mbiQuestion); - } - - public static bool AskYesNo(string strText, string strTitle) - { - return AskYesNo(strText, strTitle, true, m_mbiQuestion); - } - - public static bool AskYesNo(string strText) - { - return AskYesNo(strText, null, true, m_mbiQuestion); - } - - public static void ShowLoadWarning(string strFilePath, Exception ex) - { - ShowLoadWarning(strFilePath, ex, false); - } - - public static void ShowLoadWarning(string strFilePath, Exception ex, - bool bFullException) - { - ShowWarning(GetLoadWarningMessage(strFilePath, ex, bFullException)); - } - - public static void ShowLoadWarning(IOConnectionInfo ioConnection, Exception ex) - { - if(ioConnection != null) - ShowLoadWarning(ioConnection.GetDisplayName(), ex, false); - else ShowWarning(ex); - } - - public static void ShowSaveWarning(string strFilePath, Exception ex, - bool bCorruptionWarning) - { - FileLockException fl = (ex as FileLockException); - if(fl != null) - { - ShowWarning(fl.Message); - return; - } - - string str = GetSaveWarningMessage(strFilePath, ex, bCorruptionWarning); - ShowWarning(str); - } - - public static void ShowSaveWarning(IOConnectionInfo ioConnection, Exception ex, - bool bCorruptionWarning) - { - if(ioConnection != null) - ShowSaveWarning(ioConnection.GetDisplayName(), ex, bCorruptionWarning); - else ShowWarning(ex); - } -#endif // !KeePassUAP - - internal static string GetLoadWarningMessage(string strFilePath, - Exception ex, bool bFullException) - { - string str = string.Empty; - - if(!string.IsNullOrEmpty(strFilePath)) - str += strFilePath + Environment.NewLine; - - str += KLRes.FileLoadFailed; - - if((ex != null) && !string.IsNullOrEmpty(ex.Message)) - { - str += Environment.NewLine; - if(!bFullException) str += ex.Message; - else str += ObjectsToMessage(new object[] { ex }, true); - } - - return str; - } - - internal static string GetSaveWarningMessage(string strFilePath, - Exception ex, bool bCorruptionWarning) - { - string str = string.Empty; - if(!string.IsNullOrEmpty(strFilePath)) - str += strFilePath + Environment.NewLine; - - str += KLRes.FileSaveFailed; - - if((ex != null) && !string.IsNullOrEmpty(ex.Message)) - str += Environment.NewLine + ex.Message; - - if(bCorruptionWarning) - str += Environment.NewLine + KLRes.FileSaveCorruptionWarning; - - return str; - } - - public static void ExternalIncrementMessageCount() - { - ++m_uCurrentMessageCount; - } - - public static void ExternalDecrementMessageCount() - { - --m_uCurrentMessageCount; - } - } -} diff --git a/ModernKeePassLib/Utility/MonoWorkaround.PCL.cs b/ModernKeePassLib/Utility/MonoWorkaround.PCL.cs deleted file mode 100644 index f47b062..0000000 --- a/ModernKeePassLib/Utility/MonoWorkaround.PCL.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace ModernKeePassLib.Utility -{ - public static class MonoWorkarounds - { - public static bool IsRequired(int i) - { - return false; - } - } -} diff --git a/ModernKeePassLib/Utility/MonoWorkarounds.cs b/ModernKeePassLib/Utility/MonoWorkarounds.cs deleted file mode 100644 index 97b4dfd..0000000 --- a/ModernKeePassLib/Utility/MonoWorkarounds.cs +++ /dev/null @@ -1,597 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -#if DEBUG -// #define DEBUG_BREAKONFAIL -#endif - -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Diagnostics; -using System.IO; -using System.Reflection; -using System.Text; -using System.Threading; -using System.Xml; - -#if !KeePassUAP -using System.Windows.Forms; -#endif - -using ModernKeePassLib.Native; - -namespace ModernKeePassLib.Utility -{ - public static class MonoWorkarounds - { - private const string AppXDoTool = "xdotool"; - - private static Dictionary g_dForceReq = new Dictionary(); - private static Thread g_thFixClip = null; - // private static Predicate g_fOwnWindow = null; - -#if DEBUG_BREAKONFAIL - private static DebugBreakTraceListener g_tlBreak = null; -#endif - - private static bool? g_bReq = null; - public static bool IsRequired() - { - if(!g_bReq.HasValue) g_bReq = NativeLib.IsUnix(); - return g_bReq.Value; - } - - // 1219: - // Mono prepends byte order mark (BOM) to StdIn. - // https://sourceforge.net/p/keepass/bugs/1219/ - // 1245: - // Key events not raised while Alt is down, and nav keys out of order. - // https://sourceforge.net/p/keepass/bugs/1245/ - // 1254: - // NumericUpDown bug: text is drawn below up/down buttons. - // https://sourceforge.net/p/keepass/bugs/1254/ - // 1354: - // Finalizer of NotifyIcon throws on Unity. - // See also 1574. - // https://sourceforge.net/p/keepass/bugs/1354/ - // 1358: - // FileDialog crashes when ~/.recently-used is invalid. - // https://sourceforge.net/p/keepass/bugs/1358/ - // 1366: - // Drawing bug when scrolling a RichTextBox. - // https://sourceforge.net/p/keepass/bugs/1366/ - // 1378: - // Mono doesn't implement Microsoft.Win32.SystemEvents events. - // https://sourceforge.net/p/keepass/bugs/1378/ - // https://github.com/mono/mono/blob/master/mcs/class/System/Microsoft.Win32/SystemEvents.cs - // 1418: - // Minimizing a form while loading it doesn't work. - // https://sourceforge.net/p/keepass/bugs/1418/ - // 1468: - // Use LibGCrypt for AES-KDF, because Mono's implementations - // of RijndaelManaged and AesCryptoServiceProvider are slow. - // https://sourceforge.net/p/keepass/bugs/1468/ - // 1527: - // Timer causes 100% CPU load. - // https://sourceforge.net/p/keepass/bugs/1527/ - // 1530: - // Mono's clipboard functions don't work properly. - // https://sourceforge.net/p/keepass/bugs/1530/ - // 1574: - // Finalizer of NotifyIcon throws on Mac OS X. - // See also 1354. - // https://sourceforge.net/p/keepass/bugs/1574/ - // 1632: - // RichTextBox rendering bug for bold/italic text. - // https://sourceforge.net/p/keepass/bugs/1632/ - // 1690: - // Removing items from a list view doesn't work properly. - // https://sourceforge.net/p/keepass/bugs/1690/ - // 1716: - // 'Always on Top' doesn't work properly on the Cinnamon desktop. - // https://sourceforge.net/p/keepass/bugs/1716/ - // 2139: - // Shortcut keys are ignored. - // https://sourceforge.net/p/keepass/feature-requests/2139/ - // 2140: - // Explicit control focusing is ignored. - // https://sourceforge.net/p/keepass/feature-requests/2140/ - // 5795: - // Text in input field is incomplete. - // https://bugzilla.xamarin.com/show_bug.cgi?id=5795 - // https://sourceforge.net/p/keepass/discussion/329220/thread/d23dc88b/ - // 10163: - // WebRequest GetResponse call missing, breaks WebDAV due to no PUT. - // https://bugzilla.xamarin.com/show_bug.cgi?id=10163 - // https://sourceforge.net/p/keepass/bugs/1117/ - // https://sourceforge.net/p/keepass/discussion/329221/thread/9422258c/ - // https://github.com/mono/mono/commit/8e67b8c2fc7cb66bff7816ebf7c1039fb8cfc43b - // https://bugzilla.xamarin.com/show_bug.cgi?id=1512 - // https://sourceforge.net/p/keepass/patches/89/ - // 12525: - // PictureBox not rendered when bitmap height >= control height. - // https://bugzilla.xamarin.com/show_bug.cgi?id=12525 - // https://sourceforge.net/p/keepass/discussion/329220/thread/54f61e9a/ - // 100001: - // Control locations/sizes are invalid/unexpected. - // [NoRef] - // 373134: - // Control.InvokeRequired doesn't always return the correct value. - // https://bugzilla.novell.com/show_bug.cgi?id=373134 - // 586901: - // RichTextBox doesn't handle Unicode string correctly. - // https://bugzilla.novell.com/show_bug.cgi?id=586901 - // 620618: - // ListView column headers not drawn. - // https://bugzilla.novell.com/show_bug.cgi?id=620618 - // 649266: - // Calling Control.Hide doesn't remove the application from taskbar. - // https://bugzilla.novell.com/show_bug.cgi?id=649266 - // 686017: - // Minimum sizes must be enforced. - // https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=686017 - // 801414: - // Mono recreates the main window incorrectly. - // https://bugs.launchpad.net/ubuntu/+source/keepass2/+bug/801414 - // 891029: - // Increase tab control height and don't use images on tabs. - // https://sourceforge.net/projects/keepass/forums/forum/329221/topic/4519750 - // https://bugs.launchpad.net/ubuntu/+source/keepass2/+bug/891029 - // https://sourceforge.net/p/keepass/bugs/1256/ - // https://sourceforge.net/p/keepass/bugs/1566/ - // https://sourceforge.net/p/keepass/bugs/1634/ - // 836428016: - // ListView group header selection unsupported. - // https://sourceforge.net/p/keepass/discussion/329221/thread/31dae0f0/ - // 2449941153: - // RichTextBox doesn't properly escape '}' when generating RTF data. - // https://sourceforge.net/p/keepass/discussion/329221/thread/920722a1/ - // 3471228285: - // Mono requires command line arguments to be encoded differently. - // https://sourceforge.net/p/keepass/discussion/329221/thread/cee6bd7d/ - // 3574233558: - // Problems with minimizing windows, no content rendered. - // https://sourceforge.net/p/keepass/discussion/329220/thread/d50a79d6/ - public static bool IsRequired(uint uBugID) - { - if(!MonoWorkarounds.IsRequired()) return false; - - bool bForce; - if(g_dForceReq.TryGetValue(uBugID, out bForce)) return bForce; - - ulong v = NativeLib.MonoVersion; - if(v != 0) - { - if(uBugID == 10163) - return (v >= 0x0002000B00000000UL); // >= 2.11 - } - - return true; - } - - internal static void SetEnabled(string strIDs, bool bEnabled) - { - if(string.IsNullOrEmpty(strIDs)) return; - - string[] vIDs = strIDs.Split(new char[] { ',' }); - foreach(string strID in vIDs) - { - if(string.IsNullOrEmpty(strID)) continue; - - uint uID; - if(StrUtil.TryParseUInt(strID.Trim(), out uID)) - g_dForceReq[uID] = bEnabled; - } - } - - internal static void Initialize() - { - Terminate(); - - // g_fOwnWindow = fOwnWindow; - - if(IsRequired(1530)) - { - try - { - ThreadStart ts = new ThreadStart(MonoWorkarounds.FixClipThread); - g_thFixClip = new Thread(ts); - g_thFixClip.Start(); - } - catch(Exception) { Debug.Assert(false); } - } - -#if DEBUG_BREAKONFAIL - if(IsRequired() && (g_tlBreak == null)) - { - g_tlBreak = new DebugBreakTraceListener(); - Debug.Listeners.Add(g_tlBreak); - } -#endif - } - - internal static void Terminate() - { - if(g_thFixClip != null) - { - try { g_thFixClip.Abort(); } - catch(Exception) { Debug.Assert(false); } - - g_thFixClip = null; - } - } - - private static void FixClipThread() - { - try - { -#if !KeePassUAP - const int msDelay = 250; - - string strTest = ClipboardU.GetText(); - if(strTest == null) return; // No clipboard support - - // Without XDoTool, the workaround would be applied to - // all applications, which may corrupt the clipboard - // when it doesn't contain simple text only; - // https://sourceforge.net/p/keepass/bugs/1603/#a113 - strTest = (NativeLib.RunConsoleApp(AppXDoTool, - "help") ?? string.Empty).Trim(); - if(strTest.Length == 0) return; - - Thread.Sleep(msDelay); - - string strLast = null; - while(true) - { - string str = ClipboardU.GetText(); - if(str == null) { Debug.Assert(false); } - else if(str != strLast) - { - if(NeedClipboardWorkaround()) - ClipboardU.SetText(str, true); - - strLast = str; - } - - Thread.Sleep(msDelay); - } -#endif - } - catch(ThreadAbortException) - { - try { Thread.ResetAbort(); } - catch(Exception) { Debug.Assert(false); } - } - catch(Exception) { Debug.Assert(false); } - finally { g_thFixClip = null; } - } - -#if !KeePassUAP - private static bool NeedClipboardWorkaround() - { - try - { - string strHandle = (NativeLib.RunConsoleApp(AppXDoTool, - "getactivewindow") ?? string.Empty).Trim(); - if(strHandle.Length == 0) { Debug.Assert(false); return false; } - - // IntPtr h = new IntPtr(long.Parse(strHandle)); - long.Parse(strHandle); // Validate - - // Detection of own windows based on Form.Handle - // comparisons doesn't work reliably (Mono's handles - // are usually off by 1) - // Predicate fOwnWindow = g_fOwnWindow; - // if(fOwnWindow != null) - // { - // if(fOwnWindow(h)) return true; - // } - // else { Debug.Assert(false); } - - string strWmClass = (NativeLib.RunConsoleApp("xprop", - "-id " + strHandle + " WM_CLASS") ?? string.Empty); - - if(strWmClass.IndexOf("\"" + PwDefs.ResClass + "\"", - StrUtil.CaseIgnoreCmp) >= 0) return true; - if(strWmClass.IndexOf("\"Remmina\"", - StrUtil.CaseIgnoreCmp) >= 0) return true; - } - catch(ThreadAbortException) { throw; } - catch(Exception) { Debug.Assert(false); } - - return false; - } - - public static void ApplyTo(Form f) - { - if(!MonoWorkarounds.IsRequired()) return; - if(f == null) { Debug.Assert(false); return; } - -#if (!KeePassLibSD && !KeePassRT) - f.HandleCreated += MonoWorkarounds.OnFormHandleCreated; - SetWmClass(f); - - ApplyToControlsRec(f.Controls, f, MonoWorkarounds.ApplyToControl); -#endif - } - - public static void Release(Form f) - { - if(!MonoWorkarounds.IsRequired()) return; - if(f == null) { Debug.Assert(false); return; } - -#if (!KeePassLibSD && !KeePassRT) - f.HandleCreated -= MonoWorkarounds.OnFormHandleCreated; - - ApplyToControlsRec(f.Controls, f, MonoWorkarounds.ReleaseControl); -#endif - } - -#if (!KeePassLibSD && !KeePassRT) - private delegate void MwaControlHandler(Control c, Form fContext); - - private static void ApplyToControlsRec(Control.ControlCollection cc, - Form fContext, MwaControlHandler fn) - { - if(cc == null) { Debug.Assert(false); return; } - - foreach(Control c in cc) - { - fn(c, fContext); - ApplyToControlsRec(c.Controls, fContext, fn); - } - } - - private static void ApplyToControl(Control c, Form fContext) - { - Button btn = (c as Button); - if(btn != null) ApplyToButton(btn, fContext); - - NumericUpDown nc = (c as NumericUpDown); - if((nc != null) && MonoWorkarounds.IsRequired(1254)) - { - if(nc.TextAlign == HorizontalAlignment.Right) - nc.TextAlign = HorizontalAlignment.Left; - } - } - - private sealed class MwaHandlerInfo - { - private readonly Delegate m_fnOrg; // May be null - public Delegate FunctionOriginal - { - get { return m_fnOrg; } - } - - private readonly Delegate m_fnOvr; - public Delegate FunctionOverride - { - get { return m_fnOvr; } - } - - private readonly DialogResult m_dr; - public DialogResult Result - { - get { return m_dr; } - } - - private readonly Form m_fContext; - public Form FormContext - { - get { return m_fContext; } - } - - public MwaHandlerInfo(Delegate fnOrg, Delegate fnOvr, DialogResult dr, - Form fContext) - { - m_fnOrg = fnOrg; - m_fnOvr = fnOvr; - m_dr = dr; - m_fContext = fContext; - } - } - - private static EventHandlerList GetEventHandlers(Component c, - out object objClickEvent) - { - FieldInfo fi = typeof(Control).GetField("ClickEvent", // Mono - BindingFlags.Static | BindingFlags.NonPublic); - if(fi == null) - fi = typeof(Control).GetField("EventClick", // .NET - BindingFlags.Static | BindingFlags.NonPublic); - if(fi == null) { Debug.Assert(false); objClickEvent = null; return null; } - - objClickEvent = fi.GetValue(null); - if(objClickEvent == null) { Debug.Assert(false); return null; } - - PropertyInfo pi = typeof(Component).GetProperty("Events", - BindingFlags.Instance | BindingFlags.NonPublic); - return (pi.GetValue(c, null) as EventHandlerList); - } - - private static Dictionary m_dictHandlers = - new Dictionary(); - private static void ApplyToButton(Button btn, Form fContext) - { - DialogResult dr = btn.DialogResult; - if(dr == DialogResult.None) return; // No workaround required - - object objClickEvent; - EventHandlerList ehl = GetEventHandlers(btn, out objClickEvent); - if(ehl == null) { Debug.Assert(false); return; } - Delegate fnClick = ehl[objClickEvent]; // May be null - - EventHandler fnOvr = new EventHandler(MonoWorkarounds.OnButtonClick); - m_dictHandlers[btn] = new MwaHandlerInfo(fnClick, fnOvr, dr, fContext); - - btn.DialogResult = DialogResult.None; - if(fnClick != null) ehl.RemoveHandler(objClickEvent, fnClick); - ehl[objClickEvent] = fnOvr; - } - - private static void ReleaseControl(Control c, Form fContext) - { - Button btn = (c as Button); - if(btn != null) ReleaseButton(btn, fContext); - } - - private static void ReleaseButton(Button btn, Form fContext) - { - MwaHandlerInfo hi; - m_dictHandlers.TryGetValue(btn, out hi); - if(hi == null) return; - - object objClickEvent; - EventHandlerList ehl = GetEventHandlers(btn, out objClickEvent); - if(ehl == null) { Debug.Assert(false); return; } - - ehl.RemoveHandler(objClickEvent, hi.FunctionOverride); - if(hi.FunctionOriginal != null) - ehl[objClickEvent] = hi.FunctionOriginal; - - btn.DialogResult = hi.Result; - m_dictHandlers.Remove(btn); - } - - private static void OnButtonClick(object sender, EventArgs e) - { - Button btn = (sender as Button); - if(btn == null) { Debug.Assert(false); return; } - - MwaHandlerInfo hi; - m_dictHandlers.TryGetValue(btn, out hi); - if(hi == null) { Debug.Assert(false); return; } - - Form f = hi.FormContext; - - // Set current dialog result by setting the form's private - // variable; the DialogResult property can't be used, - // because it raises close events - FieldInfo fiRes = typeof(Form).GetField("dialog_result", - BindingFlags.Instance | BindingFlags.NonPublic); - if(fiRes == null) { Debug.Assert(false); return; } - if(f != null) fiRes.SetValue(f, hi.Result); - - if(hi.FunctionOriginal != null) - hi.FunctionOriginal.Method.Invoke(hi.FunctionOriginal.Target, - new object[] { btn, e }); - - // Raise close events, if the click event handler hasn't - // reset the dialog result - if((f != null) && (f.DialogResult == hi.Result)) - f.DialogResult = hi.Result; // Raises close events - } - - private static void SetWmClass(Form f) - { - NativeMethods.SetWmClass(f, PwDefs.UnixName, PwDefs.ResClass); - } - - private static void OnFormHandleCreated(object sender, EventArgs e) - { - Form f = (sender as Form); - if(f == null) { Debug.Assert(false); return; } - - if(!f.IsHandleCreated) return; // Prevent infinite loop - - SetWmClass(f); - } - - /// - /// Set the value of the private shown_raised member - /// variable of a form. - /// - /// Previous shown_raised value. - internal static bool ExchangeFormShownRaised(Form f, bool bNewValue) - { - if(f == null) { Debug.Assert(false); return true; } - - try - { - FieldInfo fi = typeof(Form).GetField("shown_raised", - BindingFlags.Instance | BindingFlags.NonPublic); - if(fi == null) { Debug.Assert(false); return true; } - - bool bPrevious = (bool)fi.GetValue(f); - - fi.SetValue(f, bNewValue); - - return bPrevious; - } - catch(Exception) { Debug.Assert(false); } - - return true; - } -#endif - - /// - /// Ensure that the file ~/.recently-used is valid (in order to - /// prevent Mono's FileDialog from crashing). - /// - internal static void EnsureRecentlyUsedValid() - { - if(!MonoWorkarounds.IsRequired(1358)) return; - - try - { - string strFile = Environment.GetFolderPath( - Environment.SpecialFolder.Personal); - strFile = UrlUtil.EnsureTerminatingSeparator(strFile, false); - strFile += ".recently-used"; - - if(File.Exists(strFile)) - { - try - { - // Mono's WriteRecentlyUsedFiles method also loads the - // XML file using XmlDocument - XmlDocument xd = XmlUtilEx.CreateXmlDocument(); - xd.Load(strFile); - } - catch(Exception) // The XML file is invalid - { - File.Delete(strFile); - } - } - } - catch(Exception) { Debug.Assert(false); } - } -#endif // !KeePassUAP - -#if DEBUG_BREAKONFAIL - private sealed class DebugBreakTraceListener : TraceListener - { - public override void Fail(string message) - { - Debugger.Break(); - } - - public override void Fail(string message, string detailMessage) - { - Debugger.Break(); - } - - public override void Write(string message) { } - public override void WriteLine(string message) { } - } -#endif - } -} diff --git a/ModernKeePassLib/Utility/StrUtil.cs b/ModernKeePassLib/Utility/StrUtil.cs deleted file mode 100644 index b40dca0..0000000 --- a/ModernKeePassLib/Utility/StrUtil.cs +++ /dev/null @@ -1,1818 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics; -using System.Globalization; -using System.IO; -using System.Text; -using System.Text.RegularExpressions; - -using System.Drawing; -#if ModernKeePassLib -using ModernKeePassLib.Cryptography; -#else -using System.Security.Cryptography; -#endif - -using ModernKeePassLib.Collections; -using ModernKeePassLib.Cryptography.PasswordGenerator; -using ModernKeePassLib.Native; -using ModernKeePassLib.Security; - -namespace ModernKeePassLib.Utility -{ - /// - /// Character stream class. - /// - public sealed class CharStream - { - private string m_strString = string.Empty; - private int m_nPos = 0; - - public CharStream(string str) - { - Debug.Assert(str != null); - if(str == null) throw new ArgumentNullException("str"); - - m_strString = str; - } - - public void Seek(SeekOrigin org, int nSeek) - { - if(org == SeekOrigin.Begin) - m_nPos = nSeek; - else if(org == SeekOrigin.Current) - m_nPos += nSeek; - else if(org == SeekOrigin.End) - m_nPos = m_strString.Length + nSeek; - } - - public char ReadChar() - { - if(m_nPos < 0) return char.MinValue; - if(m_nPos >= m_strString.Length) return char.MinValue; - - char chRet = m_strString[m_nPos]; - ++m_nPos; - return chRet; - } - - public char ReadChar(bool bSkipWhiteSpace) - { - if(bSkipWhiteSpace == false) return ReadChar(); - - while(true) - { - char ch = ReadChar(); - - if((ch != ' ') && (ch != '\t') && (ch != '\r') && (ch != '\n')) - return ch; - } - } - - public char PeekChar() - { - if(m_nPos < 0) return char.MinValue; - if(m_nPos >= m_strString.Length) return char.MinValue; - - return m_strString[m_nPos]; - } - - public char PeekChar(bool bSkipWhiteSpace) - { - if(bSkipWhiteSpace == false) return PeekChar(); - - int iIndex = m_nPos; - while(true) - { - if(iIndex < 0) return char.MinValue; - if(iIndex >= m_strString.Length) return char.MinValue; - - char ch = m_strString[iIndex]; - - if((ch != ' ') && (ch != '\t') && (ch != '\r') && (ch != '\n')) - return ch; - - ++iIndex; - } - } - } - - public enum StrEncodingType - { - Unknown = 0, - Default, - Ascii, - Utf7, - Utf8, - Utf16LE, - Utf16BE, - Utf32LE, - Utf32BE - } - - public sealed class StrEncodingInfo - { - private readonly StrEncodingType m_type; - public StrEncodingType Type - { - get { return m_type; } - } - - private readonly string m_strName; - public string Name - { - get { return m_strName; } - } - - private readonly Encoding m_enc; - public Encoding Encoding - { - get { return m_enc; } - } - - private readonly uint m_cbCodePoint; - /// - /// Size of a character in bytes. - /// - public uint CodePointSize - { - get { return m_cbCodePoint; } - } - - private readonly byte[] m_vSig; - /// - /// Start signature of the text (byte order mark). - /// May be null or empty, if no signature is known. - /// - public byte[] StartSignature - { - get { return m_vSig; } - } - - public StrEncodingInfo(StrEncodingType t, string strName, Encoding enc, - uint cbCodePoint, byte[] vStartSig) - { - if(strName == null) throw new ArgumentNullException("strName"); - if(enc == null) throw new ArgumentNullException("enc"); - if(cbCodePoint <= 0) throw new ArgumentOutOfRangeException("cbCodePoint"); - - m_type = t; - m_strName = strName; - m_enc = enc; - m_cbCodePoint = cbCodePoint; - m_vSig = vStartSig; - } - } - - /// - /// A class containing various string helper methods. - /// - public static class StrUtil - { - public const StringComparison CaseIgnoreCmp = StringComparison.OrdinalIgnoreCase; - - public static StringComparer CaseIgnoreComparer - { - get { return StringComparer.OrdinalIgnoreCase; } - } - - private static bool m_bRtl = false; - public static bool RightToLeft - { - get { return m_bRtl; } - set { m_bRtl = value; } - } - - private static UTF8Encoding m_encUtf8 = null; - public static UTF8Encoding Utf8 - { - get - { - if(m_encUtf8 == null) m_encUtf8 = new UTF8Encoding(false, false); - return m_encUtf8; - } - } - - private static List m_lEncs = null; - public static IEnumerable Encodings - { - get - { - if(m_lEncs != null) return m_lEncs; - - List l = new List(); - - l.Add(new StrEncodingInfo(StrEncodingType.Default, -#if ModernKeePassLib ||KeePassUAP - "Unicode (UTF-8)", StrUtil.Utf8, 1, new byte[] { 0xEF, 0xBB, 0xBF })); -#else -#if !KeePassLibSD - Encoding.Default.EncodingName, -#else - Encoding.Default.WebName, -#endif - Encoding.Default, - (uint)Encoding.Default.GetBytes("a").Length, null)); -#endif -#if !ModernKeePassLib && !KeePassRT - l.Add(new StrEncodingInfo(StrEncodingType.Ascii, - "ASCII", Encoding.ASCII, 1, null)); - l.Add(new StrEncodingInfo(StrEncodingType.Utf7, - "Unicode (UTF-7)", Encoding.UTF7, 1, null)); -#endif - l.Add(new StrEncodingInfo(StrEncodingType.Utf8, - "Unicode (UTF-8)", StrUtil.Utf8, 1, new byte[] { 0xEF, 0xBB, 0xBF })); - l.Add(new StrEncodingInfo(StrEncodingType.Utf16LE, - "Unicode (UTF-16 LE)", new UnicodeEncoding(false, false), - 2, new byte[] { 0xFF, 0xFE })); - l.Add(new StrEncodingInfo(StrEncodingType.Utf16BE, - "Unicode (UTF-16 BE)", new UnicodeEncoding(true, false), - 2, new byte[] { 0xFE, 0xFF })); -#if (!ModernKeePassLib && !KeePassLibSD && !KeePassRT) - l.Add(new StrEncodingInfo(StrEncodingType.Utf32LE, - "Unicode (UTF-32 LE)", new UTF32Encoding(false, false), - 4, new byte[] { 0xFF, 0xFE, 0x0, 0x0 })); - l.Add(new StrEncodingInfo(StrEncodingType.Utf32BE, - "Unicode (UTF-32 BE)", new UTF32Encoding(true, false), - 4, new byte[] { 0x0, 0x0, 0xFE, 0xFF })); -#endif - - m_lEncs = l; - return l; - } - } - - // public static string RtfPar - // { - // // get { return (m_bRtl ? "\\rtlpar " : "\\par "); } - // get { return "\\par "; } - // } - - // /// - // /// Convert a string into a valid RTF string. - // /// - // /// Any string. - // /// RTF-encoded string. - // public static string MakeRtfString(string str) - // { - // Debug.Assert(str != null); if(str == null) throw new ArgumentNullException("str"); - // str = str.Replace("\\", "\\\\"); - // str = str.Replace("\r", string.Empty); - // str = str.Replace("{", "\\{"); - // str = str.Replace("}", "\\}"); - // str = str.Replace("\n", StrUtil.RtfPar); - // StringBuilder sbEncoded = new StringBuilder(); - // for(int i = 0; i < str.Length; ++i) - // { - // char ch = str[i]; - // if((int)ch >= 256) - // sbEncoded.Append(StrUtil.RtfEncodeChar(ch)); - // else sbEncoded.Append(ch); - // } - // return sbEncoded.ToString(); - // } - - public static string RtfEncodeChar(char ch) - { - // Unicode character values must be encoded using - // 16-bit numbers (decimal); Unicode values greater - // than 32767 must be expressed as negative numbers - short sh = (short)ch; - return ("\\u" + sh.ToString(NumberFormatInfo.InvariantInfo) + "?"); - } - - public static string RtfFix(string strRtf) - { - if(strRtf == null) { Debug.Assert(false); return string.Empty; } - - string str = strRtf; - - // Workaround for .NET bug: the Rtf property of a RichTextBox - // can return an RTF text starting with "{\\urtf", but - // setting such an RTF text throws an exception (the setter - // checks for the RTF text to start with "{\\rtf"); - // https://sourceforge.net/p/keepass/discussion/329221/thread/7788872f/ - // https://www.microsoft.com/en-us/download/details.aspx?id=10725 - // https://msdn.microsoft.com/en-us/library/windows/desktop/bb774284.aspx - // https://referencesource.microsoft.com/#System.Windows.Forms/winforms/Managed/System/WinForms/RichTextBox.cs - const string p = "{\\urtf"; // Typically "{\\urtf1\\ansi\\ansicpg65001" - if(str.StartsWith(p) && (str.Length > p.Length) && - char.IsDigit(str[p.Length])) - str = str.Remove(2, 1); // Remove the 'u' - - return str; - } - - /// - /// Convert a string to a HTML sequence representing that string. - /// - /// String to convert. - /// String, HTML-encoded. - public static string StringToHtml(string str) - { - return StringToHtml(str, false); - } - - internal static string StringToHtml(string str, bool bNbsp) - { - Debug.Assert(str != null); if(str == null) throw new ArgumentNullException("str"); - - str = str.Replace(@"&", @"&"); // Must be first - str = str.Replace(@"<", @"<"); - str = str.Replace(@">", @">"); - str = str.Replace("\"", @"""); - str = str.Replace("\'", @"'"); - - if(bNbsp) str = str.Replace(" ", @" "); // Before
- - str = NormalizeNewLines(str, false); - str = str.Replace("\n", @"
" + MessageService.NewLine); - - return str; - } - - public static string XmlToString(string str) - { - Debug.Assert(str != null); if(str == null) throw new ArgumentNullException("str"); - - str = str.Replace(@"&", @"&"); - str = str.Replace(@"<", @"<"); - str = str.Replace(@">", @">"); - str = str.Replace(@""", "\""); - str = str.Replace(@"'", "\'"); - - return str; - } - - public static string ReplaceCaseInsensitive(string strString, string strFind, - string strNew) - { - Debug.Assert(strString != null); if(strString == null) return strString; - Debug.Assert(strFind != null); if(strFind == null) return strString; - Debug.Assert(strNew != null); if(strNew == null) return strString; - - string str = strString; - - int nPos = 0; - while(nPos < str.Length) - { - nPos = str.IndexOf(strFind, nPos, StringComparison.OrdinalIgnoreCase); - if(nPos < 0) break; - - str = str.Remove(nPos, strFind.Length); - str = str.Insert(nPos, strNew); - - nPos += strNew.Length; - } - - return str; - } - - /// - /// Split up a command line into application and argument. - /// - /// Command line to split. - /// Application path. - /// Arguments. - public static void SplitCommandLine(string strCmdLine, out string strApp, out string strArgs) - { - Debug.Assert(strCmdLine != null); if(strCmdLine == null) throw new ArgumentNullException("strCmdLine"); - - string str = strCmdLine.Trim(); - - strApp = null; strArgs = null; - - if(str.StartsWith("\"")) - { - int nSecond = str.IndexOf('\"', 1); - if(nSecond >= 1) - { - strApp = str.Substring(1, nSecond - 1).Trim(); - strArgs = str.Remove(0, nSecond + 1).Trim(); - } - } - - if(strApp == null) - { - int nSpace = str.IndexOf(' '); - - if(nSpace >= 0) - { - strApp = str.Substring(0, nSpace); - strArgs = str.Remove(0, nSpace).Trim(); - } - else strApp = strCmdLine; - } - - if(strApp == null) strApp = string.Empty; - if(strArgs == null) strArgs = string.Empty; - } - - // /// - // /// Initialize an RTF document based on given font face and size. - // /// - // /// StringBuilder to put the generated RTF into. - // /// Face name of the font to use. - // /// Size of the font to use. - // public static void InitRtf(StringBuilder sb, string strFontFace, float fFontSize) - // { - // Debug.Assert(sb != null); if(sb == null) throw new ArgumentNullException("sb"); - // Debug.Assert(strFontFace != null); if(strFontFace == null) throw new ArgumentNullException("strFontFace"); - // sb.Append("{\\rtf1"); - // if(m_bRtl) sb.Append("\\fbidis"); - // sb.Append("\\ansi\\ansicpg"); - // sb.Append(Encoding.Default.CodePage); - // sb.Append("\\deff0{\\fonttbl{\\f0\\fswiss MS Sans Serif;}{\\f1\\froman\\fcharset2 Symbol;}{\\f2\\fswiss "); - // sb.Append(strFontFace); - // sb.Append(";}{\\f3\\fswiss Arial;}}"); - // sb.Append("{\\colortbl\\red0\\green0\\blue0;}"); - // if(m_bRtl) sb.Append("\\rtldoc"); - // sb.Append("\\deflang1031\\pard\\plain\\f2\\cf0 "); - // sb.Append("\\fs"); - // sb.Append((int)(fFontSize * 2)); - // if(m_bRtl) sb.Append("\\rtlpar\\qr\\rtlch "); - // } - - // /// - // /// Convert a simple HTML string to an RTF string. - // /// - // /// Input HTML string. - // /// RTF string representing the HTML input string. - // public static string SimpleHtmlToRtf(string strHtmlString) - // { - // StringBuilder sb = new StringBuilder(); - // StrUtil.InitRtf(sb, "Microsoft Sans Serif", 8.25f); - // sb.Append(" "); - // string str = MakeRtfString(strHtmlString); - // str = str.Replace("", "\\b "); - // str = str.Replace("", "\\b0 "); - // str = str.Replace("", "\\i "); - // str = str.Replace("", "\\i0 "); - // str = str.Replace("", "\\ul "); - // str = str.Replace("", "\\ul0 "); - // str = str.Replace("
", StrUtil.RtfPar); - // sb.Append(str); - // return sb.ToString(); - // } - - /// - /// Convert a Color to a HTML color identifier string. - /// - /// Color to convert. - /// If this is true, an empty string - /// is returned if the color is transparent. - /// HTML color identifier string. - public static string ColorToUnnamedHtml(Color color, bool bEmptyIfTransparent) - { - if(bEmptyIfTransparent && (color.A != 255)) - return string.Empty; - - StringBuilder sb = new StringBuilder(); - byte bt; - - sb.Append('#'); - - bt = (byte)(color.R >> 4); - if(bt < 10) sb.Append((char)('0' + bt)); else sb.Append((char)('A' - 10 + bt)); - bt = (byte)(color.R & 0x0F); - if(bt < 10) sb.Append((char)('0' + bt)); else sb.Append((char)('A' - 10 + bt)); - - bt = (byte)(color.G >> 4); - if(bt < 10) sb.Append((char)('0' + bt)); else sb.Append((char)('A' - 10 + bt)); - bt = (byte)(color.G & 0x0F); - if(bt < 10) sb.Append((char)('0' + bt)); else sb.Append((char)('A' - 10 + bt)); - - bt = (byte)(color.B >> 4); - if(bt < 10) sb.Append((char)('0' + bt)); else sb.Append((char)('A' - 10 + bt)); - bt = (byte)(color.B & 0x0F); - if(bt < 10) sb.Append((char)('0' + bt)); else sb.Append((char)('A' - 10 + bt)); - - return sb.ToString(); - } - - /// - /// Format an exception and convert it to a string. - /// - /// Exception to convert/format. - /// String representing the exception. - public static string FormatException(Exception excp) - { - string strText = string.Empty; - - if(!string.IsNullOrEmpty(excp.Message)) - strText += excp.Message + MessageService.NewLine; -#if !KeePassLibSD - if(!string.IsNullOrEmpty(excp.Source)) - strText += excp.Source + MessageService.NewLine; -#endif - if(!string.IsNullOrEmpty(excp.StackTrace)) - strText += excp.StackTrace + MessageService.NewLine; -#if !KeePassLibSD -#if !ModernKeePassLib && !KeePassRT - if(excp.TargetSite != null) - strText += excp.TargetSite.ToString() + MessageService.NewLine; -#endif - - if(excp.Data != null) - { - strText += MessageService.NewLine; - foreach(DictionaryEntry de in excp.Data) - strText += @"'" + de.Key + @"' -> '" + de.Value + @"'" + - MessageService.NewLine; - } -#endif - - if(excp.InnerException != null) - { - strText += MessageService.NewLine + "Inner:" + MessageService.NewLine; - if(!string.IsNullOrEmpty(excp.InnerException.Message)) - strText += excp.InnerException.Message + MessageService.NewLine; -#if !KeePassLibSD - if(!string.IsNullOrEmpty(excp.InnerException.Source)) - strText += excp.InnerException.Source + MessageService.NewLine; -#endif - if(!string.IsNullOrEmpty(excp.InnerException.StackTrace)) - strText += excp.InnerException.StackTrace + MessageService.NewLine; -#if !KeePassLibSD -#if !ModernKeePassLib && !KeePassRT - if(excp.InnerException.TargetSite != null) - strText += excp.InnerException.TargetSite.ToString(); -#endif - - if(excp.InnerException.Data != null) - { - strText += MessageService.NewLine; - foreach(DictionaryEntry de in excp.InnerException.Data) - strText += @"'" + de.Key + @"' -> '" + de.Value + @"'" + - MessageService.NewLine; - } -#endif - } - - return strText; - } - - public static bool TryParseUShort(string str, out ushort u) - { -#if !KeePassLibSD - return ushort.TryParse(str, out u); -#else - try { u = ushort.Parse(str); return true; } - catch(Exception) { u = 0; return false; } -#endif - } - - public static bool TryParseInt(string str, out int n) - { -#if !KeePassLibSD - return int.TryParse(str, out n); -#else - try { n = int.Parse(str); return true; } - catch(Exception) { n = 0; } - return false; -#endif - } - - public static bool TryParseIntInvariant(string str, out int n) - { -#if !KeePassLibSD - return int.TryParse(str, NumberStyles.Integer, - NumberFormatInfo.InvariantInfo, out n); -#else - try - { - n = int.Parse(str, NumberStyles.Integer, - NumberFormatInfo.InvariantInfo); - return true; - } - catch(Exception) { n = 0; } - return false; -#endif - } - - public static bool TryParseUInt(string str, out uint u) - { -#if !KeePassLibSD - return uint.TryParse(str, out u); -#else - try { u = uint.Parse(str); return true; } - catch(Exception) { u = 0; } - return false; -#endif - } - - public static bool TryParseUIntInvariant(string str, out uint u) - { -#if !KeePassLibSD - return uint.TryParse(str, NumberStyles.Integer, - NumberFormatInfo.InvariantInfo, out u); -#else - try - { - u = uint.Parse(str, NumberStyles.Integer, - NumberFormatInfo.InvariantInfo); - return true; - } - catch(Exception) { u = 0; } - return false; -#endif - } - - public static bool TryParseLong(string str, out long n) - { -#if !KeePassLibSD - return long.TryParse(str, out n); -#else - try { n = long.Parse(str); return true; } - catch(Exception) { n = 0; } - return false; -#endif - } - - public static bool TryParseLongInvariant(string str, out long n) - { -#if !KeePassLibSD - return long.TryParse(str, NumberStyles.Integer, - NumberFormatInfo.InvariantInfo, out n); -#else - try - { - n = long.Parse(str, NumberStyles.Integer, - NumberFormatInfo.InvariantInfo); - return true; - } - catch(Exception) { n = 0; } - return false; -#endif - } - - public static bool TryParseULong(string str, out ulong u) - { -#if !KeePassLibSD - return ulong.TryParse(str, out u); -#else - try { u = ulong.Parse(str); return true; } - catch(Exception) { u = 0; } - return false; -#endif - } - - public static bool TryParseULongInvariant(string str, out ulong u) - { -#if !KeePassLibSD - return ulong.TryParse(str, NumberStyles.Integer, - NumberFormatInfo.InvariantInfo, out u); -#else - try - { - u = ulong.Parse(str, NumberStyles.Integer, - NumberFormatInfo.InvariantInfo); - return true; - } - catch(Exception) { u = 0; } - return false; -#endif - } - - public static bool TryParseDateTime(string str, out DateTime dt) - { -#if !KeePassLibSD - return DateTime.TryParse(str, out dt); -#else - try { dt = DateTime.Parse(str); return true; } - catch(Exception) { dt = DateTime.UtcNow; } - return false; -#endif - } - - public static string CompactString3Dots(string strText, int nMaxChars) - { - Debug.Assert(strText != null); - if(strText == null) throw new ArgumentNullException("strText"); - Debug.Assert(nMaxChars >= 0); - if(nMaxChars < 0) throw new ArgumentOutOfRangeException("nMaxChars"); - - if(nMaxChars == 0) return string.Empty; - if(strText.Length <= nMaxChars) return strText; - - if(nMaxChars <= 3) return strText.Substring(0, nMaxChars); - - return strText.Substring(0, nMaxChars - 3) + "..."; - } - - public static string GetStringBetween(string strText, int nStartIndex, - string strStart, string strEnd) - { - int nTemp; - return GetStringBetween(strText, nStartIndex, strStart, strEnd, out nTemp); - } - - public static string GetStringBetween(string strText, int nStartIndex, - string strStart, string strEnd, out int nInnerStartIndex) - { - if(strText == null) throw new ArgumentNullException("strText"); - if(strStart == null) throw new ArgumentNullException("strStart"); - if(strEnd == null) throw new ArgumentNullException("strEnd"); - - nInnerStartIndex = -1; - - int nIndex = strText.IndexOf(strStart, nStartIndex); - if(nIndex < 0) return string.Empty; - - nIndex += strStart.Length; - - int nEndIndex = strText.IndexOf(strEnd, nIndex); - if(nEndIndex < 0) return string.Empty; - - nInnerStartIndex = nIndex; - return strText.Substring(nIndex, nEndIndex - nIndex); - } - - /// - /// Removes all characters that are not valid XML characters, - /// according to https://www.w3.org/TR/xml/#charsets . - /// - /// Source text. - /// Text containing only valid XML characters. - public static string SafeXmlString(string strText) - { - Debug.Assert(strText != null); // No throw - if(string.IsNullOrEmpty(strText)) return strText; - - int nLength = strText.Length; - StringBuilder sb = new StringBuilder(nLength); - - for(int i = 0; i < nLength; ++i) - { - char ch = strText[i]; - - if(((ch >= '\u0020') && (ch <= '\uD7FF')) || - (ch == '\u0009') || (ch == '\u000A') || (ch == '\u000D') || - ((ch >= '\uE000') && (ch <= '\uFFFD'))) - sb.Append(ch); - else if((ch >= '\uD800') && (ch <= '\uDBFF')) // High surrogate - { - if((i + 1) < nLength) - { - char chLow = strText[i + 1]; - if((chLow >= '\uDC00') && (chLow <= '\uDFFF')) // Low sur. - { - sb.Append(ch); - sb.Append(chLow); - ++i; - } - else { Debug.Assert(false); } // Low sur. invalid - } - else { Debug.Assert(false); } // Low sur. missing - } - - Debug.Assert((ch < '\uDC00') || (ch > '\uDFFF')); // Lonely low sur. - } - - return sb.ToString(); - } - - /* private static Regex g_rxNaturalSplit = null; - public static int CompareNaturally(string strX, string strY) - { - Debug.Assert(strX != null); - if(strX == null) throw new ArgumentNullException("strX"); - Debug.Assert(strY != null); - if(strY == null) throw new ArgumentNullException("strY"); - - if(NativeMethods.SupportsStrCmpNaturally) - return NativeMethods.StrCmpNaturally(strX, strY); - - if(g_rxNaturalSplit == null) - g_rxNaturalSplit = new Regex(@"([0-9]+)", RegexOptions.Compiled); - - string[] vPartsX = g_rxNaturalSplit.Split(strX); - string[] vPartsY = g_rxNaturalSplit.Split(strY); - - int n = Math.Min(vPartsX.Length, vPartsY.Length); - for(int i = 0; i < n; ++i) - { - string strPartX = vPartsX[i], strPartY = vPartsY[i]; - int iPartCompare; - -#if KeePassLibSD - try - { - ulong uX = ulong.Parse(strPartX); - ulong uY = ulong.Parse(strPartY); - iPartCompare = uX.CompareTo(uY); - } - catch(Exception) { iPartCompare = string.Compare(strPartX, strPartY, true); } -#else - ulong uX, uY; - if(ulong.TryParse(strPartX, out uX) && ulong.TryParse(strPartY, out uY)) - iPartCompare = uX.CompareTo(uY); - else iPartCompare = string.Compare(strPartX, strPartY, true); -#endif - - if(iPartCompare != 0) return iPartCompare; - } - - if(vPartsX.Length == vPartsY.Length) return 0; - if(vPartsX.Length < vPartsY.Length) return -1; - return 1; - } */ - - public static int CompareNaturally(string strX, string strY) - { - Debug.Assert(strX != null); - if(strX == null) throw new ArgumentNullException("strX"); - Debug.Assert(strY != null); - if(strY == null) throw new ArgumentNullException("strY"); - - if(NativeMethods.SupportsStrCmpNaturally) - return NativeMethods.StrCmpNaturally(strX, strY); - - int cX = strX.Length; - int cY = strY.Length; - if(cX == 0) return ((cY == 0) ? 0 : -1); - if(cY == 0) return 1; - - char chFirstX = strX[0]; - char chFirstY = strY[0]; - bool bExpNum = ((chFirstX >= '0') && (chFirstX <= '9')); - bool bExpNumY = ((chFirstY >= '0') && (chFirstY <= '9')); -#if ModernKeePassLib - if (bExpNum != bExpNumY) return StringComparer.OrdinalIgnoreCase.Compare(strX, strY); -#else - if(bExpNum != bExpNumY) return string.Compare(strX, strY, true); -#endif - - int pX = 0; - int pY = 0; - while((pX < cX) && (pY < cY)) - { - Debug.Assert(((strX[pX] >= '0') && (strX[pX] <= '9')) == bExpNum); - Debug.Assert(((strY[pY] >= '0') && (strY[pY] <= '9')) == bExpNum); - - int pExclX = pX + 1; - while(pExclX < cX) - { - char ch = strX[pExclX]; - bool bChNum = ((ch >= '0') && (ch <= '9')); - if(bChNum != bExpNum) break; - ++pExclX; - } - - int pExclY = pY + 1; - while(pExclY < cY) - { - char ch = strY[pExclY]; - bool bChNum = ((ch >= '0') && (ch <= '9')); - if(bChNum != bExpNum) break; - ++pExclY; - } - - string strPartX = strX.Substring(pX, pExclX - pX); - string strPartY = strY.Substring(pY, pExclY - pY); - - bool bStrCmp = true; - if(bExpNum) - { - // 2^64 - 1 = 18446744073709551615 has length 20 - if((strPartX.Length <= 19) && (strPartY.Length <= 19)) - { - ulong uX, uY; - if(ulong.TryParse(strPartX, out uX) && ulong.TryParse(strPartY, out uY)) - { - if(uX < uY) return -1; - if(uX > uY) return 1; - - bStrCmp = false; - } - else { Debug.Assert(false); } - } - else - { - double dX, dY; - if(double.TryParse(strPartX, out dX) && double.TryParse(strPartY, out dY)) - { - if(dX < dY) return -1; - if(dX > dY) return 1; - - bStrCmp = false; - } - else { Debug.Assert(false); } - } - } - if(bStrCmp) - { -#if ModernKeePassLib - int c = StringComparer.OrdinalIgnoreCase.Compare(strPartX, strPartY); -#else - int c = string.Compare(strPartX, strPartY, true); -#endif - if(c != 0) return c; - } - - bExpNum = !bExpNum; - pX = pExclX; - pY = pExclY; - } - - if(pX >= cX) - { - Debug.Assert(pX == cX); - if(pY >= cY) { Debug.Assert(pY == cY); return 0; } - return -1; - } - - Debug.Assert(pY == cY); - return 1; - } - - public static string RemoveAccelerator(string strMenuText) - { - if(strMenuText == null) throw new ArgumentNullException("strMenuText"); - - string str = strMenuText; - - for(char ch = 'A'; ch <= 'Z'; ++ch) - { - string strEnhAcc = @"(&" + ch.ToString() + @")"; - if(str.IndexOf(strEnhAcc) >= 0) - { - str = str.Replace(@" " + strEnhAcc, string.Empty); - str = str.Replace(strEnhAcc, string.Empty); - } - } - - str = str.Replace(@"&", string.Empty); - - return str; - } - - public static string AddAccelerator(string strMenuText, - List lAvailKeys) - { - if(strMenuText == null) { Debug.Assert(false); return null; } - if(lAvailKeys == null) { Debug.Assert(false); return strMenuText; } - - int xa = -1, xs = 0; - for(int i = 0; i < strMenuText.Length; ++i) - { - char ch = strMenuText[i]; - -#if KeePassLibSD - char chUpper = char.ToUpper(ch); -#else - char chUpper = char.ToUpperInvariant(ch); -#endif - xa = lAvailKeys.IndexOf(chUpper); - if(xa >= 0) { xs = i; break; } - -#if KeePassLibSD - char chLower = char.ToLower(ch); -#else - char chLower = char.ToLowerInvariant(ch); -#endif - xa = lAvailKeys.IndexOf(chLower); - if(xa >= 0) { xs = i; break; } - } - - if(xa < 0) return strMenuText; - - lAvailKeys.RemoveAt(xa); - return strMenuText.Insert(xs, @"&"); - } - - public static string EncodeMenuText(string strText) - { - if(strText == null) throw new ArgumentNullException("strText"); - - return strText.Replace(@"&", @"&&"); - } - - public static string EncodeToolTipText(string strText) - { - if(strText == null) throw new ArgumentNullException("strText"); - - return strText.Replace(@"&", @"&&&"); - } - - public static bool IsHexString(string str, bool bStrict) - { - if(str == null) throw new ArgumentNullException("str"); - - foreach(char ch in str) - { - if((ch >= '0') && (ch <= '9')) continue; - if((ch >= 'a') && (ch <= 'f')) continue; - if((ch >= 'A') && (ch <= 'F')) continue; - - if(bStrict) return false; - - if((ch == ' ') || (ch == '\t') || (ch == '\r') || (ch == '\n')) - continue; - - return false; - } - - return true; - } - - public static bool IsHexString(byte[] pbUtf8, bool bStrict) - { - if(pbUtf8 == null) throw new ArgumentNullException("pbUtf8"); - - for(int i = 0; i < pbUtf8.Length; ++i) - { - byte bt = pbUtf8[i]; - if((bt >= (byte)'0') && (bt <= (byte)'9')) continue; - if((bt >= (byte)'a') && (bt <= (byte)'f')) continue; - if((bt >= (byte)'A') && (bt <= (byte)'F')) continue; - - if(bStrict) return false; - - if((bt == (byte)' ') || (bt == (byte)'\t') || - (bt == (byte)'\r') || (bt == (byte)'\n')) - continue; - - return false; - } - - return true; - } - -#if !KeePassLibSD - private static readonly char[] m_vPatternPartsSep = new char[] { '*' }; - public static bool SimplePatternMatch(string strPattern, string strText, - StringComparison sc) - { - if(strPattern == null) throw new ArgumentNullException("strPattern"); - if(strText == null) throw new ArgumentNullException("strText"); - - if(strPattern.IndexOf('*') < 0) return strText.Equals(strPattern, sc); - - string[] vPatternParts = strPattern.Split(m_vPatternPartsSep, - StringSplitOptions.RemoveEmptyEntries); - if(vPatternParts == null) { Debug.Assert(false); return true; } - if(vPatternParts.Length == 0) return true; - - if(strText.Length == 0) return false; - - if(!strPattern.StartsWith(@"*") && !strText.StartsWith(vPatternParts[0], sc)) - { - return false; - } - - if(!strPattern.EndsWith(@"*") && !strText.EndsWith(vPatternParts[ - vPatternParts.Length - 1], sc)) - { - return false; - } - - int iOffset = 0; - for(int i = 0; i < vPatternParts.Length; ++i) - { - string strPart = vPatternParts[i]; - - int iFound = strText.IndexOf(strPart, iOffset, sc); - if(iFound < iOffset) return false; - - iOffset = iFound + strPart.Length; - if(iOffset == strText.Length) return (i == (vPatternParts.Length - 1)); - } - - return true; - } -#endif // !KeePassLibSD - - public static bool StringToBool(string str) - { - if(string.IsNullOrEmpty(str)) return false; // No assert - - string s = str.Trim().ToLower(); - if(s == "true") return true; - if(s == "yes") return true; - if(s == "1") return true; - if(s == "enabled") return true; - if(s == "checked") return true; - - return false; - } - - public static bool? StringToBoolEx(string str) - { - if(string.IsNullOrEmpty(str)) return null; - - string s = str.Trim().ToLower(); - if(s == "true") return true; - if(s == "false") return false; - - return null; - } - - public static string BoolToString(bool bValue) - { - return (bValue ? "true" : "false"); - } - - public static string BoolToStringEx(bool? bValue) - { - if(bValue.HasValue) return BoolToString(bValue.Value); - return "null"; - } - - /// - /// Normalize new line characters in a string. Input strings may - /// contain mixed new line character sequences from all commonly - /// used operating systems (i.e. \r\n from Windows, \n from Unix - /// and \r from Mac OS. - /// - /// String with mixed new line characters. - /// If true, new line characters - /// are normalized for Windows (\r\n); if false, new line - /// characters are normalized for Unix (\n). - /// String with normalized new line characters. - public static string NormalizeNewLines(string str, bool bWindows) - { - if(string.IsNullOrEmpty(str)) return str; - - str = str.Replace("\r\n", "\n"); - str = str.Replace("\r", "\n"); - - if(bWindows) str = str.Replace("\n", "\r\n"); - - return str; - } - - public static void NormalizeNewLines(ProtectedStringDictionary dict, - bool bWindows) - { - if(dict == null) { Debug.Assert(false); return; } - - List lKeys = dict.GetKeys(); - foreach(string strKey in lKeys) - { - ProtectedString ps = dict.Get(strKey); - if(ps == null) { Debug.Assert(false); continue; } - - char[] v = ps.ReadChars(); - if(!IsNewLineNormalized(v, bWindows)) - dict.Set(strKey, new ProtectedString(ps.IsProtected, - NormalizeNewLines(ps.ReadString(), bWindows))); - MemUtil.ZeroArray(v); - } - } - - internal static bool IsNewLineNormalized(char[] v, bool bWindows) - { - if(v == null) { Debug.Assert(false); return true; } - - if(bWindows) - { - int iFreeCr = -2; // Must be < -1 (for test "!= (i - 1)") - - for(int i = 0; i < v.Length; ++i) - { - char ch = v[i]; - - if(ch == '\r') - { - if(iFreeCr >= 0) return false; - iFreeCr = i; - } - else if(ch == '\n') - { - if(iFreeCr != (i - 1)) return false; - iFreeCr = -2; // Consume \r - } - } - - return (iFreeCr < 0); // Ensure no \r at end - } - - return (Array.IndexOf(v, '\r') < 0); - } - - public static string GetNewLineSeq(string str) - { - if(str == null) { Debug.Assert(false); return MessageService.NewLine; } - - int n = str.Length, nLf = 0, nCr = 0, nCrLf = 0; - char chLast = char.MinValue; - for(int i = 0; i < n; ++i) - { - char ch = str[i]; - - if(ch == '\r') ++nCr; - else if(ch == '\n') - { - ++nLf; - if(chLast == '\r') ++nCrLf; - } - - chLast = ch; - } - - nCr -= nCrLf; - nLf -= nCrLf; - - int nMax = Math.Max(nCrLf, Math.Max(nCr, nLf)); - if(nMax == 0) return MessageService.NewLine; - - if(nCrLf == nMax) return "\r\n"; - return ((nLf == nMax) ? "\n" : "\r"); - } - - public static string AlphaNumericOnly(string str) - { - if(string.IsNullOrEmpty(str)) return str; - - StringBuilder sb = new StringBuilder(); - for(int i = 0; i < str.Length; ++i) - { - char ch = str[i]; - if(((ch >= 'a') && (ch <= 'z')) || ((ch >= 'A') && (ch <= 'Z')) || - ((ch >= '0') && (ch <= '9'))) - sb.Append(ch); - } - - return sb.ToString(); - } - - public static string FormatDataSize(ulong uBytes) - { - const ulong uKB = 1024; - const ulong uMB = uKB * uKB; - const ulong uGB = uMB * uKB; - const ulong uTB = uGB * uKB; - - if(uBytes == 0) return "0 KB"; - if(uBytes <= uKB) return "1 KB"; - if(uBytes <= uMB) return (((uBytes - 1UL) / uKB) + 1UL).ToString() + " KB"; - if(uBytes <= uGB) return (((uBytes - 1UL) / uMB) + 1UL).ToString() + " MB"; - if(uBytes <= uTB) return (((uBytes - 1UL) / uGB) + 1UL).ToString() + " GB"; - - return (((uBytes - 1UL)/ uTB) + 1UL).ToString() + " TB"; - } - - public static string FormatDataSizeKB(ulong uBytes) - { - const ulong uKB = 1024; - - if(uBytes == 0) return "0 KB"; - if(uBytes <= uKB) return "1 KB"; - - return (((uBytes - 1UL) / uKB) + 1UL).ToString() + " KB"; - } - - private static readonly char[] m_vVersionSep = new char[]{ '.', ',' }; - public static ulong ParseVersion(string strVersion) - { - if(strVersion == null) { Debug.Assert(false); return 0; } - - string[] vVer = strVersion.Split(m_vVersionSep); - if((vVer == null) || (vVer.Length == 0)) { Debug.Assert(false); return 0; } - - ushort uPart; - StrUtil.TryParseUShort(vVer[0].Trim(), out uPart); - ulong uVer = ((ulong)uPart << 48); - - if(vVer.Length >= 2) - { - StrUtil.TryParseUShort(vVer[1].Trim(), out uPart); - uVer |= ((ulong)uPart << 32); - } - - if(vVer.Length >= 3) - { - StrUtil.TryParseUShort(vVer[2].Trim(), out uPart); - uVer |= ((ulong)uPart << 16); - } - - if(vVer.Length >= 4) - { - StrUtil.TryParseUShort(vVer[3].Trim(), out uPart); - uVer |= (ulong)uPart; - } - - return uVer; - } - - public static string VersionToString(ulong uVersion) - { - return VersionToString(uVersion, 1U); - } - - [Obsolete] - public static string VersionToString(ulong uVersion, - bool bEnsureAtLeastTwoComp) - { - return VersionToString(uVersion, (bEnsureAtLeastTwoComp ? 2U : 1U)); - } - - public static string VersionToString(ulong uVersion, uint uMinComp) - { - StringBuilder sb = new StringBuilder(); - uint uComp = 0; - - for(int i = 0; i < 4; ++i) - { - if(uVersion == 0UL) break; - - ushort us = (ushort)(uVersion >> 48); - - if(sb.Length > 0) sb.Append('.'); - - sb.Append(us.ToString(NumberFormatInfo.InvariantInfo)); - ++uComp; - - uVersion <<= 16; - } - - while(uComp < uMinComp) - { - if(sb.Length > 0) sb.Append('.'); - - sb.Append('0'); - ++uComp; - } - - return sb.ToString(); - } - - private static readonly byte[] m_pbOptEnt = { 0xA5, 0x74, 0x2E, 0xEC }; - - public static string EncryptString(string strPlainText) - { - if(string.IsNullOrEmpty(strPlainText)) return string.Empty; - - try - { - byte[] pbPlain = StrUtil.Utf8.GetBytes(strPlainText); - byte[] pbEnc = CryptoUtil.ProtectData(pbPlain, m_pbOptEnt, - DataProtectionScope.CurrentUser); - -#if (!ModernKeePassLib && !KeePassLibSD && !KeePassRT) - return Convert.ToBase64String(pbEnc, Base64FormattingOptions.None); -#else - return Convert.ToBase64String(pbEnc); -#endif - } - catch(Exception) { Debug.Assert(false); } - - return strPlainText; - } - - public static string DecryptString(string strCipherText) - { - if(string.IsNullOrEmpty(strCipherText)) return string.Empty; - - try - { - byte[] pbEnc = Convert.FromBase64String(strCipherText); - byte[] pbPlain = CryptoUtil.UnprotectData(pbEnc, m_pbOptEnt, - DataProtectionScope.CurrentUser); - - return StrUtil.Utf8.GetString(pbPlain, 0, pbPlain.Length); - } - catch(Exception) { Debug.Assert(false); } - - return strCipherText; - } - - public static string SerializeIntArray(int[] vNumbers) - { - if(vNumbers == null) throw new ArgumentNullException("vNumbers"); - - StringBuilder sb = new StringBuilder(); - for(int i = 0; i < vNumbers.Length; ++i) - { - if(i > 0) sb.Append(' '); - sb.Append(vNumbers[i].ToString(NumberFormatInfo.InvariantInfo)); - } - - return sb.ToString(); - } - - public static int[] DeserializeIntArray(string strSerialized) - { - if(strSerialized == null) throw new ArgumentNullException("strSerialized"); - if(strSerialized.Length == 0) return new int[0]; - - string[] vParts = strSerialized.Split(' '); - int[] v = new int[vParts.Length]; - - for(int i = 0; i < vParts.Length; ++i) - { - int n; - if(!TryParseIntInvariant(vParts[i], out n)) { Debug.Assert(false); } - v[i] = n; - } - - return v; - } - - private static readonly char[] m_vTagSep = new char[] { ',', ';', ':' }; - public static string TagsToString(List vTags, bool bForDisplay) - { - if(vTags == null) throw new ArgumentNullException("vTags"); - - StringBuilder sb = new StringBuilder(); - bool bFirst = true; - - foreach(string strTag in vTags) - { - if(string.IsNullOrEmpty(strTag)) { Debug.Assert(false); continue; } - Debug.Assert(strTag.IndexOfAny(m_vTagSep) < 0); - - if(!bFirst) - { - if(bForDisplay) sb.Append(", "); - else sb.Append(';'); - } - sb.Append(strTag); - - bFirst = false; - } - - return sb.ToString(); - } - - public static List StringToTags(string strTags) - { - if(strTags == null) throw new ArgumentNullException("strTags"); - - List lTags = new List(); - if(strTags.Length == 0) return lTags; - - string[] vTags = strTags.Split(m_vTagSep); - foreach(string strTag in vTags) - { - string strFlt = strTag.Trim(); - if(strFlt.Length > 0) lTags.Add(strFlt); - } - - return lTags; - } - - public static string Obfuscate(string strPlain) - { - if(strPlain == null) { Debug.Assert(false); return string.Empty; } - if(strPlain.Length == 0) return string.Empty; - - byte[] pb = StrUtil.Utf8.GetBytes(strPlain); - - Array.Reverse(pb); - for(int i = 0; i < pb.Length; ++i) pb[i] = (byte)(pb[i] ^ 0x65); - -#if (!ModernKeePassLib && !KeePassLibSD && !KeePassRT) - return Convert.ToBase64String(pb, Base64FormattingOptions.None); -#else - return Convert.ToBase64String(pb); -#endif - } - - public static string Deobfuscate(string strObf) - { - if(strObf == null) { Debug.Assert(false); return string.Empty; } - if(strObf.Length == 0) return string.Empty; - - try - { - byte[] pb = Convert.FromBase64String(strObf); - - for(int i = 0; i < pb.Length; ++i) pb[i] = (byte)(pb[i] ^ 0x65); - Array.Reverse(pb); - - return StrUtil.Utf8.GetString(pb, 0, pb.Length); - } - catch(Exception) { Debug.Assert(false); } - - return string.Empty; - } - - /// - /// Split a string and include the separators in the splitted array. - /// - /// String to split. - /// Separators. - /// Specifies whether separators are - /// matched case-sensitively or not. - /// Splitted string including separators. - public static List SplitWithSep(string str, string[] vSeps, - bool bCaseSensitive) - { - if(str == null) throw new ArgumentNullException("str"); - if(vSeps == null) throw new ArgumentNullException("vSeps"); - - List v = new List(); - while(true) - { - int minIndex = int.MaxValue, minSep = -1; - for(int i = 0; i < vSeps.Length; ++i) - { - string strSep = vSeps[i]; - if(string.IsNullOrEmpty(strSep)) { Debug.Assert(false); continue; } - - int iIndex = (bCaseSensitive ? str.IndexOf(strSep) : - str.IndexOf(strSep, StrUtil.CaseIgnoreCmp)); - if((iIndex >= 0) && (iIndex < minIndex)) - { - minIndex = iIndex; - minSep = i; - } - } - - if(minIndex == int.MaxValue) break; - - v.Add(str.Substring(0, minIndex)); - v.Add(vSeps[minSep]); - - str = str.Substring(minIndex + vSeps[minSep].Length); - } - - v.Add(str); - return v; - } - - public static string MultiToSingleLine(string strMulti) - { - if(strMulti == null) { Debug.Assert(false); return string.Empty; } - if(strMulti.Length == 0) return string.Empty; - - string str = strMulti; - str = str.Replace("\r\n", " "); - str = str.Replace("\r", " "); - str = str.Replace("\n", " "); - - return str; - } - - public static List SplitSearchTerms(string strSearch) - { - List l = new List(); - if(strSearch == null) { Debug.Assert(false); return l; } - - StringBuilder sbTerm = new StringBuilder(); - bool bQuoted = false; - - for(int i = 0; i < strSearch.Length; ++i) - { - char ch = strSearch[i]; - - if(((ch == ' ') || (ch == '\t') || (ch == '\r') || - (ch == '\n')) && !bQuoted) - { - if(sbTerm.Length > 0) l.Add(sbTerm.ToString()); - - sbTerm.Remove(0, sbTerm.Length); - } - else if(ch == '\"') bQuoted = !bQuoted; - else sbTerm.Append(ch); - } - if(sbTerm.Length > 0) l.Add(sbTerm.ToString()); - - return l; - } - - public static int CompareLengthGt(string x, string y) - { - if(x.Length == y.Length) return 0; - return ((x.Length > y.Length) ? -1 : 1); - } - - public static bool IsDataUri(string strUri) - { - return IsDataUri(strUri, null); - } - - public static bool IsDataUri(string strUri, string strReqMimeType) - { - if(strUri == null) { Debug.Assert(false); return false; } - // strReqMimeType may be null - - const string strPrefix = "data:"; - if(!strUri.StartsWith(strPrefix, StrUtil.CaseIgnoreCmp)) - return false; - - int iC = strUri.IndexOf(','); - if(iC < 0) return false; - - if(!string.IsNullOrEmpty(strReqMimeType)) - { - int iS = strUri.IndexOf(';', 0, iC); - int iTerm = ((iS >= 0) ? iS : iC); - - string strMime = strUri.Substring(strPrefix.Length, - iTerm - strPrefix.Length); - if(!strMime.Equals(strReqMimeType, StrUtil.CaseIgnoreCmp)) - return false; - } - - return true; - } - - /// - /// Create a data URI (according to RFC 2397). - /// - /// Data to encode. - /// Optional MIME type. If null, - /// an appropriate type is used. - /// Data URI. - public static string DataToDataUri(byte[] pbData, string strMimeType) - { - if(pbData == null) throw new ArgumentNullException("pbData"); - - if(strMimeType == null) strMimeType = "application/octet-stream"; - -#if (!ModernKeePassLib && !KeePassLibSD && !KeePassRT) - return ("data:" + strMimeType + ";base64," + Convert.ToBase64String( - pbData, Base64FormattingOptions.None)); -#else - return ("data:" + strMimeType + ";base64," + Convert.ToBase64String( - pbData)); -#endif - } - - /// - /// Convert a data URI (according to RFC 2397) to binary data. - /// - /// Data URI to decode. - /// Decoded binary data. - public static byte[] DataUriToData(string strDataUri) - { - if(strDataUri == null) throw new ArgumentNullException("strDataUri"); - if(!strDataUri.StartsWith("data:", StrUtil.CaseIgnoreCmp)) return null; - - int iSep = strDataUri.IndexOf(','); - if(iSep < 0) return null; - - string strDesc = strDataUri.Substring(5, iSep - 5); - bool bBase64 = strDesc.EndsWith(";base64", StrUtil.CaseIgnoreCmp); - - string strData = strDataUri.Substring(iSep + 1); - - if(bBase64) return Convert.FromBase64String(strData); - - MemoryStream ms = new MemoryStream(); - -#if ModernKeePassLib || KeePassRT - Encoding enc = StrUtil.Utf8; -#else - Encoding enc = Encoding.ASCII; -#endif - - string[] v = strData.Split('%'); - byte[] pb = enc.GetBytes(v[0]); - ms.Write(pb, 0, pb.Length); - for(int i = 1; i < v.Length; ++i) - { - ms.WriteByte(Convert.ToByte(v[i].Substring(0, 2), 16)); - pb = enc.GetBytes(v[i].Substring(2)); - ms.Write(pb, 0, pb.Length); - } - - pb = ms.ToArray(); - ms.Dispose(); - return pb; - } - - /// - /// Remove placeholders from a string (wrapped in '{' and '}'). - /// This doesn't remove environment variables (wrapped in '%'). - /// - public static string RemovePlaceholders(string str) - { - if(str == null) { Debug.Assert(false); return string.Empty; } - - while(true) - { - int iPlhStart = str.IndexOf('{'); - if(iPlhStart < 0) break; - - int iPlhEnd = str.IndexOf('}', iPlhStart); // '{' might be at end - if(iPlhEnd < 0) break; - - str = (str.Substring(0, iPlhStart) + str.Substring(iPlhEnd + 1)); - } - - return str; - } - - public static StrEncodingInfo GetEncoding(StrEncodingType t) - { - foreach(StrEncodingInfo sei in StrUtil.Encodings) - { - if(sei.Type == t) return sei; - } - - return null; - } - - public static StrEncodingInfo GetEncoding(string strName) - { - foreach(StrEncodingInfo sei in StrUtil.Encodings) - { - if(sei.Name == strName) return sei; - } - - return null; - } - - private static string[] m_vPrefSepChars = null; - /// - /// Find a character that does not occur within a given text. - /// - public static char GetUnusedChar(string strText) - { - if(strText == null) { Debug.Assert(false); return '@'; } - - if(m_vPrefSepChars == null) - m_vPrefSepChars = new string[5] { - "@!$%#/\\:;,.*-_?", - PwCharSet.UpperCase, PwCharSet.LowerCase, - PwCharSet.Digits, PwCharSet.PrintableAsciiSpecial - }; - - for(int i = 0; i < m_vPrefSepChars.Length; ++i) - { - foreach(char ch in m_vPrefSepChars[i]) - { - if(strText.IndexOf(ch) < 0) return ch; - } - } - - for(char ch = '\u00C0'; ch < char.MaxValue; ++ch) - { - if(strText.IndexOf(ch) < 0) return ch; - } - - return char.MinValue; - } - - public static char ByteToSafeChar(byte bt) - { - const char chDefault = '.'; - - // 00-1F are C0 control chars - if(bt < 0x20) return chDefault; - - // 20-7F are basic Latin; 7F is DEL - if(bt < 0x7F) return (char)bt; - - // 80-9F are C1 control chars - if(bt < 0xA0) return chDefault; - - // A0-FF are Latin-1 supplement; AD is soft hyphen - if(bt == 0xAD) return '-'; - return (char)bt; - } - - public static int Count(string str, string strNeedle) - { - if(str == null) { Debug.Assert(false); return 0; } - if(string.IsNullOrEmpty(strNeedle)) { Debug.Assert(false); return 0; } - - int iOffset = 0, iCount = 0; - while(iOffset < str.Length) - { - int p = str.IndexOf(strNeedle, iOffset); - if(p < 0) break; - - ++iCount; - iOffset = p + 1; - } - - return iCount; - } - - internal static string ReplaceNulls(string str) - { - if(str == null) { Debug.Assert(false); return null; } - - if(str.IndexOf('\0') < 0) return str; - - // Replacing null characters by spaces is the - // behavior of Notepad (on Windows 10) - return str.Replace('\0', ' '); - } - } -} diff --git a/ModernKeePassLib/Utility/StreamExtensions.cs b/ModernKeePassLib/Utility/StreamExtensions.cs deleted file mode 100644 index 499a4c2..0000000 --- a/ModernKeePassLib/Utility/StreamExtensions.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.IO; -using Windows.Storage.Streams; - -namespace ModernKeePassLibPCL.Utility -{ - public static class StreamExtensions - { - public static Stream AsStream(this IRandomAccessStream inputStream) - { - var reader = new DataReader(inputStream.GetInputStreamAt(0)); - var bytes = new byte[inputStream.Size]; - reader.LoadAsync((uint)inputStream.Size).GetResults(); - reader.ReadBytes(bytes); - return new MemoryStream(bytes); - } - } -} diff --git a/ModernKeePassLib/Utility/TimeUtil.cs b/ModernKeePassLib/Utility/TimeUtil.cs deleted file mode 100644 index 54f0e77..0000000 --- a/ModernKeePassLib/Utility/TimeUtil.cs +++ /dev/null @@ -1,482 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Globalization; -using System.Text; - -using ModernKeePassLib.Interfaces; - -namespace ModernKeePassLib.Utility -{ - /// - /// Contains various static time structure manipulation and conversion - /// routines. - /// - public static class TimeUtil - { - /// - /// Length of a compressed PW_TIME structure in bytes. - /// - public const int PwTimeLength = 7; - - public static readonly DateTime SafeMinValueUtc = new DateTime( - DateTime.MinValue.Ticks + TimeSpan.TicksPerDay, DateTimeKind.Utc); - public static readonly DateTime SafeMaxValueUtc = new DateTime( - DateTime.MaxValue.Ticks - TimeSpan.TicksPerDay, DateTimeKind.Utc); - -#if !KeePassLibSD - private static string m_strDtfStd = null; - private static string m_strDtfDate = null; -#endif - - // private static long m_lTicks2PowLess1s = 0; - - private static DateTime? m_odtUnixRoot = null; - public static DateTime UnixRoot - { - get - { - if(m_odtUnixRoot.HasValue) return m_odtUnixRoot.Value; - - DateTime dtRoot = new DateTime(1970, 1, 1, 0, 0, 0, 0, - DateTimeKind.Utc); - - m_odtUnixRoot = dtRoot; - return dtRoot; - } - } - - /// - /// Pack a DateTime object into 5 bytes. Layout: 2 zero bits, - /// year 12 bits, month 4 bits, day 5 bits, hour 5 bits, minute 6 - /// bits, second 6 bits. - /// - [Obsolete] - public static byte[] PackTime(DateTime dt) - { - dt = ToLocal(dt, true); - - byte[] pb = new byte[5]; - - // Pack time to 5 byte structure: - // Byte bits: 11111111 22222222 33333333 44444444 55555555 - // Contents : 00YYYYYY YYYYYYMM MMDDDDDH HHHHMMMM MMSSSSSS - pb[0] = (byte)((dt.Year >> 6) & 0x3F); - pb[1] = (byte)(((dt.Year & 0x3F) << 2) | ((dt.Month >> 2) & 0x03)); - pb[2] = (byte)(((dt.Month & 0x03) << 6) | ((dt.Day & 0x1F) << 1) | - ((dt.Hour >> 4) & 0x01)); - pb[3] = (byte)(((dt.Hour & 0x0F) << 4) | ((dt.Minute >> 2) & 0x0F)); - pb[4] = (byte)(((dt.Minute & 0x03) << 6) | (dt.Second & 0x3F)); - - return pb; - } - - /// - /// Unpack a packed time (5 bytes, packed by the PackTime - /// member function) to a DateTime object. - /// - /// Packed time, 5 bytes. - /// Unpacked DateTime object. - [Obsolete] - public static DateTime UnpackTime(byte[] pb) - { - Debug.Assert((pb != null) && (pb.Length == 5)); - if(pb == null) throw new ArgumentNullException("pb"); - if(pb.Length != 5) throw new ArgumentException(); - - int n1 = pb[0], n2 = pb[1], n3 = pb[2], n4 = pb[3], n5 = pb[4]; - - // Unpack 5 byte structure to date and time - int nYear = (n1 << 6) | (n2 >> 2); - int nMonth = ((n2 & 0x00000003) << 2) | (n3 >> 6); - int nDay = (n3 >> 1) & 0x0000001F; - int nHour = ((n3 & 0x00000001) << 4) | (n4 >> 4); - int nMinute = ((n4 & 0x0000000F) << 2) | (n5 >> 6); - int nSecond = n5 & 0x0000003F; - - return (new DateTime(nYear, nMonth, nDay, nHour, nMinute, - nSecond, DateTimeKind.Local)).ToUniversalTime(); - } - - /// - /// Pack a DateTime object into 7 bytes (PW_TIME). - /// - /// Object to be encoded. - /// Packed time, 7 bytes (PW_TIME). - [Obsolete] - public static byte[] PackPwTime(DateTime dt) - { - Debug.Assert(PwTimeLength == 7); - - dt = ToLocal(dt, true); - - byte[] pb = new byte[7]; - pb[0] = (byte)(dt.Year & 0xFF); - pb[1] = (byte)(dt.Year >> 8); - pb[2] = (byte)dt.Month; - pb[3] = (byte)dt.Day; - pb[4] = (byte)dt.Hour; - pb[5] = (byte)dt.Minute; - pb[6] = (byte)dt.Second; - - return pb; - } - - /// - /// Unpack a packed time (7 bytes, PW_TIME) to a DateTime object. - /// - /// Packed time, 7 bytes. - /// Unpacked DateTime object. - [Obsolete] - public static DateTime UnpackPwTime(byte[] pb) - { - Debug.Assert(PwTimeLength == 7); - - Debug.Assert(pb != null); if(pb == null) throw new ArgumentNullException("pb"); - Debug.Assert(pb.Length == 7); if(pb.Length != 7) throw new ArgumentException(); - - return (new DateTime(((int)pb[1] << 8) | (int)pb[0], (int)pb[2], (int)pb[3], - (int)pb[4], (int)pb[5], (int)pb[6], DateTimeKind.Local)).ToUniversalTime(); - } - - /// - /// Convert a DateTime object to a displayable string. - /// - /// DateTime object to convert to a string. - /// String representing the specified DateTime object. - public static string ToDisplayString(DateTime dt) - { - return ToLocal(dt, true).ToString(); - } - - public static string ToDisplayStringDateOnly(DateTime dt) - { - return ToLocal(dt, true).ToString("d"); - } - - public static DateTime FromDisplayString(string strDisplay) - { - DateTime dt; - if(FromDisplayStringEx(strDisplay, out dt)) return dt; - return DateTime.Now; - } - - public static bool FromDisplayStringEx(string strDisplay, out DateTime dt) - { -#if KeePassLibSD - try { dt = ToLocal(DateTime.Parse(strDisplay), true); return true; } - catch(Exception) { } -#else - if(DateTime.TryParse(strDisplay, out dt)) - { - dt = ToLocal(dt, true); - return true; - } - - // For some custom formats specified using the Control Panel, - // DateTime.ToString returns the correct string, but - // DateTime.TryParse fails (e.g. for "//dd/MMM/yyyy"); - // https://sourceforge.net/p/keepass/discussion/329221/thread/3a225b29/?limit=25&page=1#c6ae - if((m_strDtfStd == null) || (m_strDtfDate == null)) - { - DateTime dtUni = new DateTime(2111, 3, 4, 5, 6, 7, DateTimeKind.Local); - m_strDtfStd = DeriveCustomFormat(ToDisplayString(dtUni), dtUni); - m_strDtfDate = DeriveCustomFormat(ToDisplayStringDateOnly(dtUni), dtUni); - } - const DateTimeStyles dts = DateTimeStyles.AllowWhiteSpaces; - if(DateTime.TryParseExact(strDisplay, m_strDtfStd, null, dts, out dt)) - { - dt = ToLocal(dt, true); - return true; - } - if(DateTime.TryParseExact(strDisplay, m_strDtfDate, null, dts, out dt)) - { - dt = ToLocal(dt, true); - return true; - } -#endif - - Debug.Assert(false); - return false; - } - -#if !KeePassLibSD - private static string DeriveCustomFormat(string strDT, DateTime dt) - { - string[] vPlh = new string[] { - // Names, sorted by length - "MMMM", "dddd", - "MMM", "ddd", - "gg", "g", - - // Numbers, the ones with prefix '0' first - "yyyy", "yyy", "yy", "y", - "MM", "M", - "dd", "d", - "HH", "hh", "H", "h", - "mm", "m", - "ss", "s", - - "tt", "t" - }; - - List lValues = new List(); - foreach(string strPlh in vPlh) - { - string strEval = strPlh; - if(strEval.Length == 1) strEval = @"%" + strPlh; // Make custom - - lValues.Add(dt.ToString(strEval)); - } - - StringBuilder sbAll = new StringBuilder(); - sbAll.Append("dfFghHKmMstyz:/\"\'\\%"); - sbAll.Append(strDT); - foreach(string strVEnum in lValues) { sbAll.Append(strVEnum); } - - List lCodes = new List(); - for(int i = 0; i < vPlh.Length; ++i) - { - char ch = StrUtil.GetUnusedChar(sbAll.ToString()); - lCodes.Add(ch); - sbAll.Append(ch); - } - - string str = strDT; - for(int i = 0; i < vPlh.Length; ++i) - { - string strValue = lValues[i]; - if(string.IsNullOrEmpty(strValue)) continue; - - str = str.Replace(strValue, new string(lCodes[i], 1)); - } - - StringBuilder sbFmt = new StringBuilder(); - bool bInLiteral = false; - foreach(char ch in str) - { - int iCode = lCodes.IndexOf(ch); - - // The escape character doesn't work correctly (e.g. - // "dd\\.MM\\.yyyy\\ HH\\:mm\\:ss" doesn't work, but - // "dd'.'MM'.'yyyy' 'HH':'mm':'ss" does); use '' instead - - // if(iCode >= 0) sbFmt.Append(vPlh[iCode]); - // else // Literal - // { - // sbFmt.Append('\\'); - // sbFmt.Append(ch); - // } - - if(iCode >= 0) - { - if(bInLiteral) { sbFmt.Append('\''); bInLiteral = false; } - sbFmt.Append(vPlh[iCode]); - } - else // Literal - { - if(!bInLiteral) { sbFmt.Append('\''); bInLiteral = true; } - sbFmt.Append(ch); - } - } - if(bInLiteral) sbFmt.Append('\''); - - return sbFmt.ToString(); - } -#endif - - public static string SerializeUtc(DateTime dt) - { - Debug.Assert(dt.Kind != DateTimeKind.Unspecified); - - string str = ToUtc(dt, false).ToString("s"); - if(!str.EndsWith("Z")) str += "Z"; - return str; - } - - public static bool TryDeserializeUtc(string str, out DateTime dt) - { - if(str == null) throw new ArgumentNullException("str"); - - if(str.EndsWith("Z")) str = str.Substring(0, str.Length - 1); - - bool bResult = StrUtil.TryParseDateTime(str, out dt); - if(bResult) dt = ToUtc(dt, true); - return bResult; - } - - public static double SerializeUnix(DateTime dt) - { - return (ToUtc(dt, false) - TimeUtil.UnixRoot).TotalSeconds; - } - - public static DateTime ConvertUnixTime(double dtUnix) - { - try { return TimeUtil.UnixRoot.AddSeconds(dtUnix); } - catch(Exception) { Debug.Assert(false); } - - return DateTime.UtcNow; - } - -#if !KeePassLibSD - [Obsolete] - public static DateTime? ParseUSTextDate(string strDate) - { - return ParseUSTextDate(strDate, DateTimeKind.Unspecified); - } - - private static string[] m_vUSMonths = null; - /// - /// Parse a US textual date string, like e.g. "January 02, 2012". - /// - public static DateTime? ParseUSTextDate(string strDate, DateTimeKind k) - { - if(strDate == null) { Debug.Assert(false); return null; } - - if(m_vUSMonths == null) - m_vUSMonths = new string[] { "January", "February", "March", - "April", "May", "June", "July", "August", "September", - "October", "November", "December" }; - - string str = strDate.Trim(); - for(int i = 0; i < m_vUSMonths.Length; ++i) - { - if(str.StartsWith(m_vUSMonths[i], StrUtil.CaseIgnoreCmp)) - { - str = str.Substring(m_vUSMonths[i].Length); - string[] v = str.Split(new char[] { ',', ';' }); - if((v == null) || (v.Length != 2)) return null; - - string strDay = v[0].Trim().TrimStart('0'); - int iDay, iYear; - if(int.TryParse(strDay, out iDay) && - int.TryParse(v[1].Trim(), out iYear)) - return new DateTime(iYear, i + 1, iDay, 0, 0, 0, k); - else { Debug.Assert(false); return null; } - } - } - - return null; - } -#endif - - private static readonly DateTime m_dtInvMin = - new DateTime(2999, 12, 27, 23, 59, 59, DateTimeKind.Utc); - private static readonly DateTime m_dtInvMax = - new DateTime(2999, 12, 29, 23, 59, 59, DateTimeKind.Utc); - public static int Compare(DateTime dtA, DateTime dtB, bool bUnkIsPast) - { - Debug.Assert(dtA.Kind == dtB.Kind); - - if(bUnkIsPast) - { - // 2999-12-28 23:59:59 in KeePass 1.x means 'unknown'; - // expect time zone corruption (twice) - // bool bInvA = ((dtA.Year == 2999) && (dtA.Month == 12) && - // (dtA.Day >= 27) && (dtA.Day <= 29) && (dtA.Minute == 59) && - // (dtA.Second == 59)); - // bool bInvB = ((dtB.Year == 2999) && (dtB.Month == 12) && - // (dtB.Day >= 27) && (dtB.Day <= 29) && (dtB.Minute == 59) && - // (dtB.Second == 59)); - // Faster due to internal implementation of DateTime: - bool bInvA = ((dtA >= m_dtInvMin) && (dtA <= m_dtInvMax) && - (dtA.Minute == 59) && (dtA.Second == 59)); - bool bInvB = ((dtB >= m_dtInvMin) && (dtB <= m_dtInvMax) && - (dtB.Minute == 59) && (dtB.Second == 59)); - - if(bInvA) return (bInvB ? 0 : -1); - if(bInvB) return 1; - } - - return dtA.CompareTo(dtB); - } - - internal static int CompareLastMod(ITimeLogger tlA, ITimeLogger tlB, - bool bUnkIsPast) - { - if(tlA == null) { Debug.Assert(false); return ((tlB == null) ? 0 : -1); } - if(tlB == null) { Debug.Assert(false); return 1; } - - return Compare(tlA.LastModificationTime, tlB.LastModificationTime, - bUnkIsPast); - } - - public static DateTime ToUtc(DateTime dt, bool bUnspecifiedIsUtc) - { - DateTimeKind k = dt.Kind; - if(k == DateTimeKind.Utc) return dt; - if(k == DateTimeKind.Local) return dt.ToUniversalTime(); - - Debug.Assert(k == DateTimeKind.Unspecified); - if(bUnspecifiedIsUtc) - return new DateTime(dt.Ticks, DateTimeKind.Utc); - return dt.ToUniversalTime(); // Unspecified = local - } - - public static DateTime ToLocal(DateTime dt, bool bUnspecifiedIsLocal) - { - DateTimeKind k = dt.Kind; - if(k == DateTimeKind.Local) return dt; - if(k == DateTimeKind.Utc) return dt.ToLocalTime(); - - Debug.Assert(k == DateTimeKind.Unspecified); - if(bUnspecifiedIsLocal) - return new DateTime(dt.Ticks, DateTimeKind.Local); - return dt.ToLocalTime(); // Unspecified = UTC - } - - /* internal static DateTime RoundToMultOf2PowLess1s(DateTime dt) - { - long l2Pow = m_lTicks2PowLess1s; - if(l2Pow == 0) - { - l2Pow = 1; - while(true) - { - l2Pow <<= 1; - if(l2Pow >= TimeSpan.TicksPerSecond) break; - } - l2Pow >>= 1; - m_lTicks2PowLess1s = l2Pow; - - Debug.Assert(TimeSpan.TicksPerSecond == 10000000L); // .NET - Debug.Assert(l2Pow == (1L << 23)); // .NET - } - - long l = dt.Ticks; - if((l % l2Pow) == 0L) return dt; - - // Round down to full second - l /= TimeSpan.TicksPerSecond; - l *= TimeSpan.TicksPerSecond; - - // Round up to multiple of l2Pow - long l2PowM1 = l2Pow - 1L; - l = (l + l2PowM1) & ~l2PowM1; - DateTime dtRnd = new DateTime(l, dt.Kind); - - Debug.Assert((dtRnd.Ticks % l2Pow) == 0L); - Debug.Assert(dtRnd.ToString("u") == dt.ToString("u")); - return dtRnd; - } */ - } -} diff --git a/ModernKeePassLib/Utility/TypeOverridePool.cs b/ModernKeePassLib/Utility/TypeOverridePool.cs deleted file mode 100644 index 454d5cb..0000000 --- a/ModernKeePassLib/Utility/TypeOverridePool.cs +++ /dev/null @@ -1,65 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; -using System.Text; - -using ModernKeePassLib.Delegates; - -namespace KeePassLib.Utility -{ - public static class TypeOverridePool - { - private static Dictionary> g_d = - new Dictionary>(); - - public static void Register(Type t, GFunc f) - { - if(t == null) throw new ArgumentNullException("t"); - if(f == null) throw new ArgumentNullException("f"); - - g_d[t] = f; - } - - public static void Unregister(Type t) - { - if(t == null) throw new ArgumentNullException("t"); - - g_d.Remove(t); - } - - public static bool IsRegistered(Type t) - { - if(t == null) throw new ArgumentNullException("t"); - - return g_d.ContainsKey(t); - } - - public static T CreateInstance() - where T : new() - { - GFunc f; - if(g_d.TryGetValue(typeof(T), out f)) - return (T)(f()); - - return new T(); - } - } -} diff --git a/ModernKeePassLib/Utility/UrlUtil.cs b/ModernKeePassLib/Utility/UrlUtil.cs deleted file mode 100644 index adeef2e..0000000 --- a/ModernKeePassLib/Utility/UrlUtil.cs +++ /dev/null @@ -1,787 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Globalization; -using System.IO; -using System.Text; - -#if ModernKeePassLib -using Windows.Storage; -#endif - -using ModernKeePassLib.Native; - -namespace ModernKeePassLib.Utility -{ - /// - /// A class containing various static path utility helper methods (like - /// stripping extension from a file, etc.). - /// - public static class UrlUtil - { - private static readonly char[] m_vDirSeps = new char[] { - '\\', '/', UrlUtil.LocalDirSepChar }; -#if !ModernKeePassLib - private static readonly char[] m_vPathTrimCharsWs = new char[] { - '\"', ' ', '\t', '\r', '\n' }; -#endif - - public static char LocalDirSepChar - { -#if KeePassRT - get { return '\\'; } -#elif ModernKeePassLib - //get { return PortablePath.DirectorySeparatorChar; } - get { return '\\'; } -#else - get { return Path.DirectorySeparatorChar; } -#endif - } - - /// - /// Get the directory (path) of a file name. The returned string may be - /// terminated by a directory separator character. Example: - /// passing C:\\My Documents\\My File.kdb in - /// and true to - /// would produce this string: C:\\My Documents\\. - /// - /// Full path of a file. - /// Append a terminating directory separator - /// character to the returned path. - /// If true, the returned path - /// is guaranteed to be a valid directory path (for example X:\\ instead - /// of X:, overriding ). - /// This should only be set to true, if the returned path is directly - /// passed to some directory API. - /// Directory of the file. - public static string GetFileDirectory(string strFile, bool bAppendTerminatingChar, - bool bEnsureValidDirSpec) - { - Debug.Assert(strFile != null); - if(strFile == null) throw new ArgumentNullException("strFile"); - - int nLastSep = strFile.LastIndexOfAny(m_vDirSeps); - if(nLastSep < 0) return string.Empty; // No directory - - if(bEnsureValidDirSpec && (nLastSep == 2) && (strFile[1] == ':') && - (strFile[2] == '\\')) // Length >= 3 and Windows root directory - bAppendTerminatingChar = true; - - if(!bAppendTerminatingChar) return strFile.Substring(0, nLastSep); - return EnsureTerminatingSeparator(strFile.Substring(0, nLastSep), - (strFile[nLastSep] == '/')); - } - - /// - /// Gets the file name of the specified file (full path). Example: - /// if is C:\\My Documents\\My File.kdb - /// the returned string is My File.kdb. - /// - /// Full path of a file. - /// File name of the specified file. The return value is - /// an empty string ("") if the input parameter is null. - public static string GetFileName(string strPath) - { - Debug.Assert(strPath != null); if(strPath == null) throw new ArgumentNullException("strPath"); - - int nLastSep = strPath.LastIndexOfAny(m_vDirSeps); - - if(nLastSep < 0) return strPath; - if(nLastSep >= (strPath.Length - 1)) return string.Empty; - - return strPath.Substring(nLastSep + 1); - } - - /// - /// Strip the extension of a file. - /// - /// Full path of a file with extension. - /// File name without extension. - public static string StripExtension(string strPath) - { - Debug.Assert(strPath != null); if(strPath == null) throw new ArgumentNullException("strPath"); - - int nLastDirSep = strPath.LastIndexOfAny(m_vDirSeps); - int nLastExtDot = strPath.LastIndexOf('.'); - - if(nLastExtDot <= nLastDirSep) return strPath; - - return strPath.Substring(0, nLastExtDot); - } - - /// - /// Get the extension of a file. - /// - /// Full path of a file with extension. - /// Extension without prepending dot. - public static string GetExtension(string strPath) - { - Debug.Assert(strPath != null); if(strPath == null) throw new ArgumentNullException("strPath"); - - int nLastDirSep = strPath.LastIndexOfAny(m_vDirSeps); - int nLastExtDot = strPath.LastIndexOf('.'); - - if(nLastExtDot <= nLastDirSep) return string.Empty; - if(nLastExtDot == (strPath.Length - 1)) return string.Empty; - - return strPath.Substring(nLastExtDot + 1); - } - - /// - /// Ensure that a path is terminated with a directory separator character. - /// - /// Input path. - /// If true, a slash (/) is appended to - /// the string if it's not terminated already. If false, the - /// default system directory separator character is used. - /// Path having a directory separator as last character. - public static string EnsureTerminatingSeparator(string strPath, bool bUrl) - { - Debug.Assert(strPath != null); if(strPath == null) throw new ArgumentNullException("strPath"); - - int nLength = strPath.Length; - if(nLength <= 0) return string.Empty; - - char chLast = strPath[nLength - 1]; - - for(int i = 0; i < m_vDirSeps.Length; ++i) - { - if(chLast == m_vDirSeps[i]) return strPath; - } - - if(bUrl) return (strPath + '/'); - return (strPath + UrlUtil.LocalDirSepChar); - } - - /* /// - /// File access mode enumeration. Used by the FileAccessible - /// method. - /// - public enum FileAccessMode - { - /// - /// Opening a file in read mode. The specified file must exist. - /// - Read = 0, - - /// - /// Opening a file in create mode. If the file exists already, it - /// will be overwritten. If it doesn't exist, it will be created. - /// The return value is true, if data can be written to the - /// file. - /// - Create - } */ - - /* /// - /// Test if a specified path is accessible, either in read or write mode. - /// - /// Path to test. - /// Requested file access mode. - /// Returns true if the specified path is accessible in - /// the requested mode, otherwise the return value is false. - public static bool FileAccessible(string strFilePath, FileAccessMode fMode) - { - Debug.Assert(strFilePath != null); - if(strFilePath == null) throw new ArgumentNullException("strFilePath"); - - if(fMode == FileAccessMode.Read) - { - FileStream fs; - - try { fs = File.OpenRead(strFilePath); } - catch(Exception) { return false; } - if(fs == null) return false; - - fs.Close(); - return true; - } - else if(fMode == FileAccessMode.Create) - { - FileStream fs; - - try { fs = File.Create(strFilePath); } - catch(Exception) { return false; } - if(fs == null) return false; - - fs.Close(); - return true; - } - - return false; - } */ - - public static string GetQuotedAppPath(string strPath) - { - if(strPath == null) { Debug.Assert(false); return string.Empty; } - - // int nFirst = strPath.IndexOf('\"'); - // int nSecond = strPath.IndexOf('\"', nFirst + 1); - // if((nFirst >= 0) && (nSecond >= 0)) - // return strPath.Substring(nFirst + 1, nSecond - nFirst - 1); - // return strPath; - - string str = strPath.Trim(); - if(str.Length <= 1) return str; - if(str[0] != '\"') return str; - - int iSecond = str.IndexOf('\"', 1); - if(iSecond <= 0) return str; - - return str.Substring(1, iSecond - 1); - } - - public static string FileUrlToPath(string strUrl) - { - Debug.Assert(strUrl != null); - if(strUrl == null) throw new ArgumentNullException("strUrl"); - - string str = strUrl; - if(str.StartsWith(@"file:///", StrUtil.CaseIgnoreCmp)) - str = str.Substring(8, str.Length - 8); - - str = str.Replace('/', UrlUtil.LocalDirSepChar); - - return str; - } - - public static bool UnhideFile(string strFile) - { -#if (ModernKeePassLib || KeePassLibSD || KeePassRT) - return false; -#else - if(strFile == null) throw new ArgumentNullException("strFile"); - - try - { - FileAttributes fa = File.GetAttributes(strFile); - if((long)(fa & FileAttributes.Hidden) == 0) return false; - - return HideFile(strFile, false); - } - catch(Exception) { } - - return false; -#endif - } - - public static bool HideFile(string strFile, bool bHide) - { -#if (ModernKeePassLib || KeePassLibSD || KeePassRT) - return false; -#else - if(strFile == null) throw new ArgumentNullException("strFile"); - - try - { - FileAttributes fa = File.GetAttributes(strFile); - - if(bHide) fa = ((fa & ~FileAttributes.Normal) | FileAttributes.Hidden); - else // Unhide - { - fa &= ~FileAttributes.Hidden; - if((long)fa == 0) fa = FileAttributes.Normal; - } - - File.SetAttributes(strFile, fa); - return true; - } - catch(Exception) { } - - return false; -#endif - } - - public static string MakeRelativePath(string strBaseFile, string strTargetFile) - { - if(strBaseFile == null) throw new ArgumentNullException("strBasePath"); - if(strTargetFile == null) throw new ArgumentNullException("strTargetPath"); - if(strBaseFile.Length == 0) return strTargetFile; - if(strTargetFile.Length == 0) return string.Empty; - - // Test whether on different Windows drives - if((strBaseFile.Length >= 3) && (strTargetFile.Length >= 3)) - { - if((strBaseFile[1] == ':') && (strTargetFile[1] == ':') && - (strBaseFile[2] == '\\') && (strTargetFile[2] == '\\') && - (strBaseFile[0] != strTargetFile[0])) - return strTargetFile; - } - -#if (!KeePassLibSD && !KeePassUAP && !ModernKeePassLib) - if(NativeLib.IsUnix()) - { -#endif - bool bBaseUnc = IsUncPath(strBaseFile); - bool bTargetUnc = IsUncPath(strTargetFile); - if((!bBaseUnc && bTargetUnc) || (bBaseUnc && !bTargetUnc)) - return strTargetFile; - - string strBase = GetShortestAbsolutePath(strBaseFile); - string strTarget = GetShortestAbsolutePath(strTargetFile); - string[] vBase = strBase.Split(m_vDirSeps); - string[] vTarget = strTarget.Split(m_vDirSeps); - - int i = 0; - while((i < (vBase.Length - 1)) && (i < (vTarget.Length - 1)) && - (vBase[i] == vTarget[i])) { ++i; } - - StringBuilder sbRel = new StringBuilder(); - for(int j = i; j < (vBase.Length - 1); ++j) - { - if(sbRel.Length > 0) sbRel.Append(UrlUtil.LocalDirSepChar); - sbRel.Append(".."); - } - for(int k = i; k < vTarget.Length; ++k) - { - if(sbRel.Length > 0) sbRel.Append(UrlUtil.LocalDirSepChar); - sbRel.Append(vTarget[k]); - } - - return sbRel.ToString(); -#if (!KeePassLibSD && !KeePassUAP && !ModernKeePassLib) - } - - try // Windows - { - const int nMaxPath = NativeMethods.MAX_PATH * 2; - StringBuilder sb = new StringBuilder(nMaxPath + 2); - if(NativeMethods.PathRelativePathTo(sb, strBaseFile, 0, - strTargetFile, 0) == false) - return strTargetFile; - - string str = sb.ToString(); - while(str.StartsWith(".\\")) str = str.Substring(2, str.Length - 2); - - return str; - } - catch(Exception) { Debug.Assert(false); } - return strTargetFile; -#endif - } - - public static string MakeAbsolutePath(string strBaseFile, string strTargetFile) - { - if(strBaseFile == null) throw new ArgumentNullException("strBasePath"); - if(strTargetFile == null) throw new ArgumentNullException("strTargetPath"); - if(strBaseFile.Length == 0) return strTargetFile; - if(strTargetFile.Length == 0) return string.Empty; - - if(IsAbsolutePath(strTargetFile)) return strTargetFile; - - string strBaseDir = GetFileDirectory(strBaseFile, true, false); - return GetShortestAbsolutePath(strBaseDir + strTargetFile); - } - - public static bool IsAbsolutePath(string strPath) - { - if(strPath == null) throw new ArgumentNullException("strPath"); - if(strPath.Length == 0) return false; - - if(IsUncPath(strPath)) return true; - - try { return Path.IsPathRooted(strPath); } - catch(Exception) { Debug.Assert(false); } - - return true; - } - - public static string GetShortestAbsolutePath(string strPath) - { - if(strPath == null) throw new ArgumentNullException("strPath"); - if(strPath.Length == 0) return string.Empty; - - // Path.GetFullPath is incompatible with UNC paths traversing over - // different server shares (which are created by PathRelativePathTo); - // we need to build the absolute path on our own... - if(IsUncPath(strPath)) - { - char chSep = strPath[0]; - Debug.Assert(Array.IndexOf(m_vDirSeps, chSep) >= 0); - - List l = new List(); -#if !KeePassLibSD - string[] v = strPath.Split(m_vDirSeps, StringSplitOptions.None); -#else - string[] v = strPath.Split(m_vDirSeps); -#endif - Debug.Assert((v.Length >= 3) && (v[0].Length == 0) && - (v[1].Length == 0)); - - foreach(string strPart in v) - { - if(strPart.Equals(".")) continue; - else if(strPart.Equals("..")) - { - if(l.Count > 0) l.RemoveAt(l.Count - 1); - else { Debug.Assert(false); } - } - else l.Add(strPart); // Do not ignore zero length parts - } - - StringBuilder sb = new StringBuilder(); - for(int i = 0; i < l.Count; ++i) - { - // Don't test length of sb, might be 0 due to initial UNC seps - if(i > 0) sb.Append(chSep); - - sb.Append(l[i]); - } - - return sb.ToString(); - } - - string str; - try - { -#if ModernKeePassLib - var dirT = StorageFolder.GetFolderFromPathAsync( - strPath).GetResults(); - str = dirT.Path; -#else - str = Path.GetFullPath(strPath); -#endif - } - catch(Exception) { Debug.Assert(false); return strPath; } - - Debug.Assert(str.IndexOf("\\..\\") < 0); - foreach(char ch in m_vDirSeps) - { - string strSep = new string(ch, 1); - str = str.Replace(strSep + "." + strSep, strSep); - } - - return str; - } - - public static int GetUrlLength(string strText, int nOffset) - { - if(strText == null) throw new ArgumentNullException("strText"); - if(nOffset > strText.Length) throw new ArgumentException(); // Not >= (0 len) - - int iPosition = nOffset, nLength = 0, nStrLen = strText.Length; - - while(iPosition < nStrLen) - { - char ch = strText[iPosition]; - ++iPosition; - - if((ch == ' ') || (ch == '\t') || (ch == '\r') || (ch == '\n')) - break; - - ++nLength; - } - - return nLength; - } - - public static string RemoveScheme(string strUrl) - { - if(string.IsNullOrEmpty(strUrl)) return string.Empty; - - int nNetScheme = strUrl.IndexOf(@"://", StrUtil.CaseIgnoreCmp); - int nShScheme = strUrl.IndexOf(@":/", StrUtil.CaseIgnoreCmp); - int nSmpScheme = strUrl.IndexOf(@":", StrUtil.CaseIgnoreCmp); - - if((nNetScheme < 0) && (nShScheme < 0) && (nSmpScheme < 0)) - return strUrl; // No scheme - - int nMin = Math.Min(Math.Min((nNetScheme >= 0) ? nNetScheme : int.MaxValue, - (nShScheme >= 0) ? nShScheme : int.MaxValue), - (nSmpScheme >= 0) ? nSmpScheme : int.MaxValue); - - if(nMin == nNetScheme) return strUrl.Substring(nMin + 3); - if(nMin == nShScheme) return strUrl.Substring(nMin + 2); - return strUrl.Substring(nMin + 1); - } - - public static string ConvertSeparators(string strPath) - { - return ConvertSeparators(strPath, UrlUtil.LocalDirSepChar); - } - - public static string ConvertSeparators(string strPath, char chSeparator) - { - if(string.IsNullOrEmpty(strPath)) return string.Empty; - - strPath = strPath.Replace('/', chSeparator); - strPath = strPath.Replace('\\', chSeparator); - - return strPath; - } - - public static bool IsUncPath(string strPath) - { - if(strPath == null) throw new ArgumentNullException("strPath"); - - return (strPath.StartsWith("\\\\") || strPath.StartsWith("//")); - } - - public static string FilterFileName(string strName) - { - if(strName == null) { Debug.Assert(false); return string.Empty; } - - string str = strName; - - str = str.Replace('/', '-'); - str = str.Replace('\\', '-'); - str = str.Replace(":", string.Empty); - str = str.Replace("*", string.Empty); - str = str.Replace("?", string.Empty); - str = str.Replace("\"", string.Empty); - str = str.Replace(@"'", string.Empty); - str = str.Replace('<', '('); - str = str.Replace('>', ')'); - str = str.Replace('|', '-'); - - return str; - } - - /// - /// Get the host component of an URL. - /// This method is faster and more fault-tolerant than creating - /// an Uri object and querying its Host - /// property. - /// - /// - /// For the input s://u:p@d.tld:p/p?q#f the return - /// value is d.tld. - /// - public static string GetHost(string strUrl) - { - if(strUrl == null) { Debug.Assert(false); return string.Empty; } - - StringBuilder sb = new StringBuilder(); - bool bInExtHost = false; - for(int i = 0; i < strUrl.Length; ++i) - { - char ch = strUrl[i]; - if(bInExtHost) - { - if(ch == '/') - { - if(sb.Length == 0) { } // Ignore leading '/'s - else break; - } - else sb.Append(ch); - } - else // !bInExtHost - { - if(ch == ':') bInExtHost = true; - } - } - - string str = sb.ToString(); - if(str.Length == 0) str = strUrl; - - // Remove the login part - int nLoginLen = str.IndexOf('@'); - if(nLoginLen >= 0) str = str.Substring(nLoginLen + 1); - - // Remove the port - int iPort = str.LastIndexOf(':'); - if(iPort >= 0) str = str.Substring(0, iPort); - - return str; - } - - public static bool AssemblyEquals(string strExt, string strShort) - { - if((strExt == null) || (strShort == null)) { Debug.Assert(false); return false; } - - if(strExt.Equals(strShort, StrUtil.CaseIgnoreCmp) || - strExt.StartsWith(strShort + ",", StrUtil.CaseIgnoreCmp)) - return true; - - if(!strShort.EndsWith(".dll", StrUtil.CaseIgnoreCmp)) - { - if(strExt.Equals(strShort + ".dll", StrUtil.CaseIgnoreCmp) || - strExt.StartsWith(strShort + ".dll,", StrUtil.CaseIgnoreCmp)) - return true; - } - - if(!strShort.EndsWith(".exe", StrUtil.CaseIgnoreCmp)) - { - if(strExt.Equals(strShort + ".exe", StrUtil.CaseIgnoreCmp) || - strExt.StartsWith(strShort + ".exe,", StrUtil.CaseIgnoreCmp)) - return true; - } - - return false; - } - - public static string GetTempPath() - { - string strDir; - if(NativeLib.IsUnix()) - strDir = NativeMethods.GetUserRuntimeDir(); -#if KeePassUAP || ModernKeePassLib - else strDir = Windows.Storage.ApplicationData.Current.TemporaryFolder.Path; -#else - else strDir = Path.GetTempPath(); - - try - { - if(!Directory.Exists(strDir)) Directory.CreateDirectory(strDir); - } - catch(Exception) { Debug.Assert(false); } - -#endif - return strDir; - } - -#if !ModernKeePassLib && !KeePassLibSD - // Structurally mostly equivalent to UrlUtil.GetFileInfos - public static List GetFilePaths(string strDir, string strPattern, - SearchOption opt) - { - List l = new List(); - if(strDir == null) { Debug.Assert(false); return l; } - if(strPattern == null) { Debug.Assert(false); return l; } - - string[] v = Directory.GetFiles(strDir, strPattern, opt); - if(v == null) { Debug.Assert(false); return l; } - - // Only accept files with the correct extension; GetFiles may - // return additional files, see GetFiles documentation - string strExt = GetExtension(strPattern); - if(!string.IsNullOrEmpty(strExt) && (strExt.IndexOf('*') < 0) && - (strExt.IndexOf('?') < 0)) - { - strExt = "." + strExt; - - foreach(string strPathRaw in v) - { - if(strPathRaw == null) { Debug.Assert(false); continue; } - string strPath = strPathRaw.Trim(m_vPathTrimCharsWs); - if(strPath.Length == 0) { Debug.Assert(false); continue; } - Debug.Assert(strPath == strPathRaw); - - if(strPath.EndsWith(strExt, StrUtil.CaseIgnoreCmp)) - l.Add(strPathRaw); - } - } - else l.AddRange(v); - - return l; - } - - // Structurally mostly equivalent to UrlUtil.GetFilePaths - public static List GetFileInfos(DirectoryInfo di, string strPattern, - SearchOption opt) - { - List l = new List(); - if(di == null) { Debug.Assert(false); return l; } - if(strPattern == null) { Debug.Assert(false); return l; } - - FileInfo[] v = di.GetFiles(strPattern, opt); - if(v == null) { Debug.Assert(false); return l; } - - // Only accept files with the correct extension; GetFiles may - // return additional files, see GetFiles documentation - string strExt = GetExtension(strPattern); - if(!string.IsNullOrEmpty(strExt) && (strExt.IndexOf('*') < 0) && - (strExt.IndexOf('?') < 0)) - { - strExt = "." + strExt; - - foreach(FileInfo fi in v) - { - if(fi == null) { Debug.Assert(false); continue; } - string strPathRaw = fi.FullName; - if(strPathRaw == null) { Debug.Assert(false); continue; } - string strPath = strPathRaw.Trim(m_vPathTrimCharsWs); - if(strPath.Length == 0) { Debug.Assert(false); continue; } - Debug.Assert(strPath == strPathRaw); - - if(strPath.EndsWith(strExt, StrUtil.CaseIgnoreCmp)) - l.Add(fi); - } - } - else l.AddRange(v); - - return l; - } -#endif - - /// - /// Expand shell variables in a string. - /// [0] is the value of %1, etc. - /// - public static string ExpandShellVariables(string strText, string[] vParams) - { - if(strText == null) { Debug.Assert(false); return string.Empty; } - if(vParams == null) { Debug.Assert(false); vParams = new string[0]; } - - string str = strText; - NumberFormatInfo nfi = NumberFormatInfo.InvariantInfo; - - for(int i = 0; i <= 9; ++i) - { - string strPlh = "%" + i.ToString(nfi); - - string strValue = string.Empty; - if((i > 0) && ((i - 1) < vParams.Length)) - strValue = (vParams[i - 1] ?? string.Empty); - - str = str.Replace(strPlh, strValue); - - if(i == 1) - { - // %L is replaced by the long version of %1; e.g. - // HKEY_CLASSES_ROOT\\IE.AssocFile.URL\\Shell\\Open\\Command - str = str.Replace("%L", strValue); - str = str.Replace("%l", strValue); - } - } - - if(str.IndexOf("%*") >= 0) - { - StringBuilder sb = new StringBuilder(); - foreach(string strValue in vParams) - { - if(!string.IsNullOrEmpty(strValue)) - { - if(sb.Length > 0) sb.Append(' '); - sb.Append(strValue); - } - } - - str = str.Replace("%*", sb.ToString()); - } - - return str; - } - - public static char GetDriveLetter(string strPath) - { - if(strPath == null) throw new ArgumentNullException("strPath"); - - Debug.Assert(default(char) == '\0'); - if(strPath.Length < 3) return '\0'; - if((strPath[1] != ':') || (strPath[2] != '\\')) return '\0'; - - char ch = char.ToUpperInvariant(strPath[0]); - return (((ch >= 'A') && (ch <= 'Z')) ? ch : '\0'); - } - } -} diff --git a/ModernKeePassLib/Utility/XmlUtilEx.cs b/ModernKeePassLib/Utility/XmlUtilEx.cs deleted file mode 100644 index cafb410..0000000 --- a/ModernKeePassLib/Utility/XmlUtilEx.cs +++ /dev/null @@ -1,127 +0,0 @@ -/* - KeePass Password Safe - The Open-Source Password Manager - Copyright (C) 2003-2018 Dominik Reichl - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Text; -using System.Xml; -using System.Xml.Serialization; -using Windows.Data.Xml.Dom; - -namespace ModernKeePassLib.Utility -{ - public static class XmlUtilEx - { - public static XmlDocument CreateXmlDocument() - { - XmlDocument d = new XmlDocument(); - - // .NET 4.5.2 and newer do not resolve external XML resources - // by default; for older .NET versions, we explicitly - // prevent resolving -#if !ModernKeePassLib - d.XmlResolver = null; // Default in old .NET: XmlUrlResolver object -#endif - - return d; - } - - public static XmlReaderSettings CreateXmlReaderSettings() - { - XmlReaderSettings xrs = new XmlReaderSettings(); - - xrs.CloseInput = false; - xrs.IgnoreComments = true; - xrs.IgnoreProcessingInstructions = true; - xrs.IgnoreWhitespace = true; - -#if KeePassUAP || ModernKeePassLib - xrs.DtdProcessing = DtdProcessing.Prohibit; -#else - // Also see PrepMonoDev.sh script - xrs.ProhibitDtd = true; // Obsolete in .NET 4, but still there - // xrs.DtdProcessing = DtdProcessing.Prohibit; // .NET 4 only - - xrs.ValidationType = ValidationType.None; - xrs.XmlResolver = null; -#endif - - return xrs; - } - - public static XmlReader CreateXmlReader(Stream s) - { - if(s == null) { Debug.Assert(false); throw new ArgumentNullException("s"); } - - return XmlReader.Create(s, CreateXmlReaderSettings()); - } - - public static XmlWriterSettings CreateXmlWriterSettings(bool isVersionGreaterThan4 = false) - { - XmlWriterSettings xws = new XmlWriterSettings(); - - xws.CloseOutput = isVersionGreaterThan4; - xws.Encoding = StrUtil.Utf8; - xws.Indent = true; - xws.IndentChars = "\t"; - xws.NewLineOnAttributes = false; -#if ModernKeePassLib - // This is needed for Argon2Kdf write - xws.Async = true; -#endif - - return xws; - } - - public static XmlWriter CreateXmlWriter(Stream s, bool isVersionGreaterThan4 = false) - { - if(s == null) { Debug.Assert(false); throw new ArgumentNullException("s"); } - - return XmlWriter.Create(s, CreateXmlWriterSettings(isVersionGreaterThan4)); - } - - public static void Serialize(Stream s, T t) - { - if(s == null) { Debug.Assert(false); throw new ArgumentNullException("s"); } - - XmlSerializer xs = new XmlSerializer(typeof(T)); - using(XmlWriter xw = CreateXmlWriter(s)) - { - xs.Serialize(xw, t); - } - } - - public static T Deserialize(Stream s) - { - if(s == null) { Debug.Assert(false); throw new ArgumentNullException("s"); } - - XmlSerializer xs = new XmlSerializer(typeof(T)); - - T t = default(T); - using(XmlReader xr = CreateXmlReader(s)) - { - t = (T)xs.Deserialize(xr); - } - - return t; - } - } -} diff --git a/ModernKeePassLib/app.config b/ModernKeePassLib/app.config deleted file mode 100644 index 9cf3b82..0000000 --- a/ModernKeePassLib/app.config +++ /dev/null @@ -1,139 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/ModernKeePassLib/project.json b/ModernKeePassLib/project.json deleted file mode 100644 index 5f63232..0000000 --- a/ModernKeePassLib/project.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "supports": {}, - "dependencies": { - "Microsoft.NETCore.Portable.Compatibility": "1.0.2", - "NETStandard.Library": "2.0.3", - "Portable.BouncyCastle": "1.8.2", - "Splat": "3.0.0", - "System.Runtime.WindowsRuntime": "4.3.0", - "System.Xml.ReaderWriter": "4.3.1", - "System.Xml.XmlSerializer": "4.3.0", - "Validation": "2.4.18" - }, - "frameworks": { - "netstandard1.2": {} - } -} \ No newline at end of file

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