├── .editorconfig ├── .gitattributes ├── .github └── workflows │ ├── release.yml │ └── workflow.yml ├── .gitignore ├── KeePassNatMsg.sln ├── KeePassNatMsg ├── AccessControlForm.Designer.cs ├── AccessControlForm.cs ├── AccessControlForm.resx ├── ConfigOpt.cs ├── ConfirmAssociationForm.Designer.cs ├── ConfirmAssociationForm.cs ├── ConfirmAssociationForm.resx ├── Entry │ ├── EntryConfig.cs │ ├── EntrySearch.cs │ └── EntryUpdate.cs ├── KeePassNatMsg.csproj ├── KeePassNatMsgExt.cs ├── NativeMessaging │ ├── BrowserSelectForm.Designer.cs │ ├── BrowserSelectForm.cs │ ├── BrowserSelectForm.resx │ ├── LinuxHost.cs │ ├── MacOsxHost.cs │ ├── NativeMessagingHost.cs │ ├── PosixHost.cs │ └── WindowsHost.cs ├── Options │ ├── DatabaseItem.cs │ ├── DatabaseKeyItem.cs │ ├── OptionsForm.Designer.cs │ ├── OptionsForm.cs │ └── OptionsForm.resx ├── Properties │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ └── Resources.resx ├── Protocol │ ├── Action │ │ ├── ErrorResponse.cs │ │ ├── JsonBase.cs │ │ ├── Request.cs │ │ └── Response.cs │ ├── Actions.cs │ ├── Crypto │ │ ├── Helper.cs │ │ ├── KeyPair.cs │ │ └── Nacl.cs │ ├── Errors.cs │ ├── Handlers.cs │ └── Listener │ │ ├── IListener.cs │ │ ├── IMessageWriter.cs │ │ ├── NamedPipeListener.cs │ │ ├── PipeThreadState.cs │ │ ├── PipeWriter.cs │ │ ├── SocketReadState.cs │ │ ├── SocketWriter.cs │ │ ├── UdpListener.cs │ │ └── UnixSocketListener.cs ├── PwEntryDatabase.cs ├── Resources │ └── earth_lock.png ├── Utils │ └── EnumExtension.cs └── lib │ ├── Mono.Posix.dll │ └── Newtonsoft.Json.dll ├── LICENSE ├── README.md ├── documentation └── images │ ├── advanced-string-fields.png │ ├── http-listener-error.png │ ├── keepass-context-menu.png │ ├── keepass-duplicate-entry-references.png │ ├── menu.png │ ├── options-advanced.png │ ├── options-general.png │ ├── options-keys.png │ └── psd │ ├── http-listener-error.psd │ ├── keepass-context-menu.psd │ ├── keepass-duplicate-entries-references.psd │ └── options.psd └── logo.png /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | end_of_line = lf 3 | trim_trailing_whitespace = true 4 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.plgx -text 2 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*.*.*" 7 | 8 | jobs: 9 | build: 10 | runs-on: 'windows-2019' 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v3 14 | - name: Get tag 15 | id: tag 16 | uses: devops-actions/action-get-tag@v1.0.1 17 | - id: dlkp 18 | name: Download Keepass 19 | uses: smorks/keepass-download-action@v0.0.5 20 | - name: Add msbuild to PATH 21 | uses: microsoft/setup-msbuild@v1 22 | - name: Extract KeePass and Build 23 | run: | 24 | mkdir build 25 | 7z x -o"build" ${{ steps.dlkp.outputs.filename }} KeePass.exe 26 | $ainfo = "KeePassNatMsg\Properties\AssemblyInfo.cs" 27 | $content = (Get-Content -Path $ainfo) -replace '\[assembly: AssemblyVersion\("([\d.]+?)"\)\]', ('[assembly: AssemblyVersion("' + "${{ steps.tag.outputs.tag }}".substring(1) + '")]') 28 | $content | Set-Content -Path $ainfo 29 | msbuild /p:Configuration=Release 30 | - name: Prepare Release 31 | run: | 32 | cp -Recurse KeePassNatMsg\bin\Release\ build\ 33 | mv build\Release build\KeePassNatMsg 34 | $zip = 'KeePassNatMsg-${{ steps.tag.outputs.tag }}-binaries.zip' 35 | 7z a $zip .\build\KeePassNatMsg\ "-xr!*.pdb" 36 | $hash = (Get-FileHash "$zip" sha256).Hash.ToLower() 37 | Set-Content -Path 'release.txt' -Value ('### SHA256 Hash',('* '+$zip),(' * '+$hash)) 38 | git clean -fx KeePassNatMsg 39 | $p = Start-Process -FilePath ".\build\KeePass.exe" -ArgumentList "--plgx-create","$pwd\KeePassNatMsg" -PassThru 40 | $p.WaitForExit() 41 | $plgx = 'KeePassNatMsg.plgx' 42 | $hash = (Get-FileHash "$plgx" sha256).Hash.ToLower() 43 | Add-Content -Path 'release.txt' -Value (('* '+$plgx),(' * '+$hash)) 44 | - name: Publish Release 45 | uses: softprops/action-gh-release@v0.1.15 46 | with: 47 | body_path: release.txt 48 | files: | 49 | KeePassNatMsg-${{ steps.tag.outputs.tag }}-binaries.zip 50 | KeePassNatMsg.plgx 51 | -------------------------------------------------------------------------------- /.github/workflows/workflow.yml: -------------------------------------------------------------------------------- 1 | name: Main workflow 2 | 3 | on: 4 | push: 5 | paths-ignore: 6 | - '**.md' 7 | pull_request: 8 | types: [opened, reopened, review_requested] 9 | 10 | jobs: 11 | build: 12 | runs-on: 'windows-2019' 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v3 16 | - id: dlkp 17 | name: Download Keepass 18 | uses: smorks/keepass-download-action@v0.0.5 19 | - name: Add msbuild to PATH 20 | uses: microsoft/setup-msbuild@v1 21 | - name: Extract KeePass and Build 22 | run: | 23 | mkdir build 24 | 7z x -o"build" ${{ steps.dlkp.outputs.filename }} KeePass.exe 25 | msbuild -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.suo 3 | obj 4 | bin 5 | create-plgx.bat 6 | /#portable/ 7 | *.cer 8 | *.pvk 9 | *.csproj.user 10 | /KeePassHttp/KeePassHttp.dll 11 | .vs/ 12 | /packages/ 13 | /KeePassNatMsg/lib/KeePass.exe 14 | build/ 15 | .idea/ 16 | *.DotSettings.user -------------------------------------------------------------------------------- /KeePassNatMsg.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27130.2010 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KeePassNatMsg", "KeePassNatMsg\KeePassNatMsg.csproj", "{9DFAB9D7-6BB5-4C6C-86EC-C67B494008D2}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {9DFAB9D7-6BB5-4C6C-86EC-C67B494008D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {9DFAB9D7-6BB5-4C6C-86EC-C67B494008D2}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {9DFAB9D7-6BB5-4C6C-86EC-C67B494008D2}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {9DFAB9D7-6BB5-4C6C-86EC-C67B494008D2}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {44A27E43-C7F1-4DE4-9A1A-F87B5D2782B3} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /KeePassNatMsg/AccessControlForm.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace KeePassNatMsg 2 | { 3 | partial class AccessControlForm 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | System.Windows.Forms.Button AllowButton; 32 | System.Windows.Forms.Button DenyButton; 33 | this.EntriesBox = new System.Windows.Forms.ListBox(); 34 | this.RememberCheck = new System.Windows.Forms.CheckBox(); 35 | this.ConfirmTextLabel = new System.Windows.Forms.Label(); 36 | AllowButton = new System.Windows.Forms.Button(); 37 | DenyButton = new System.Windows.Forms.Button(); 38 | this.SuspendLayout(); 39 | // 40 | // AllowButton 41 | // 42 | AllowButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); 43 | AllowButton.Location = new System.Drawing.Point(176, 207); 44 | AllowButton.Name = "AllowButton"; 45 | AllowButton.Size = new System.Drawing.Size(75, 23); 46 | AllowButton.TabIndex = 1; 47 | AllowButton.Text = "&Allow"; 48 | AllowButton.UseVisualStyleBackColor = true; 49 | AllowButton.Click += new System.EventHandler(this.AllowButton_Click); 50 | // 51 | // DenyButton 52 | // 53 | DenyButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); 54 | DenyButton.Location = new System.Drawing.Point(257, 207); 55 | DenyButton.Name = "DenyButton"; 56 | DenyButton.Size = new System.Drawing.Size(75, 23); 57 | DenyButton.TabIndex = 2; 58 | DenyButton.Text = "&Deny"; 59 | DenyButton.UseVisualStyleBackColor = true; 60 | DenyButton.Click += new System.EventHandler(this.DenyButton_Click); 61 | // 62 | // EntriesBox 63 | // 64 | this.EntriesBox.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 65 | | System.Windows.Forms.AnchorStyles.Left) 66 | | System.Windows.Forms.AnchorStyles.Right))); 67 | this.EntriesBox.FormattingEnabled = true; 68 | this.EntriesBox.Location = new System.Drawing.Point(12, 12); 69 | this.EntriesBox.Name = "EntriesBox"; 70 | this.EntriesBox.Size = new System.Drawing.Size(320, 108); 71 | this.EntriesBox.Sorted = true; 72 | this.EntriesBox.TabIndex = 0; 73 | // 74 | // RememberCheck 75 | // 76 | this.RememberCheck.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); 77 | this.RememberCheck.AutoSize = true; 78 | this.RememberCheck.Location = new System.Drawing.Point(12, 211); 79 | this.RememberCheck.Name = "RememberCheck"; 80 | this.RememberCheck.Size = new System.Drawing.Size(138, 17); 81 | this.RememberCheck.TabIndex = 3; 82 | this.RememberCheck.Text = "Remember this decision"; 83 | this.RememberCheck.UseVisualStyleBackColor = true; 84 | // 85 | // ConfirmTextLabel 86 | // 87 | this.ConfirmTextLabel.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) 88 | | System.Windows.Forms.AnchorStyles.Right))); 89 | this.ConfirmTextLabel.Location = new System.Drawing.Point(9, 136); 90 | this.ConfirmTextLabel.Name = "ConfirmTextLabel"; 91 | this.ConfirmTextLabel.Size = new System.Drawing.Size(323, 65); 92 | this.ConfirmTextLabel.TabIndex = 4; 93 | this.ConfirmTextLabel.Text = "www.somewhere.com has requested access to passwords for the above item(s). Please" + 94 | " select whether you want to allow access."; 95 | // 96 | // AccessControlForm 97 | // 98 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 99 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 100 | this.ClientSize = new System.Drawing.Size(344, 242); 101 | this.Controls.Add(this.ConfirmTextLabel); 102 | this.Controls.Add(this.RememberCheck); 103 | this.Controls.Add(DenyButton); 104 | this.Controls.Add(AllowButton); 105 | this.Controls.Add(this.EntriesBox); 106 | this.MaximizeBox = false; 107 | this.MinimizeBox = false; 108 | this.Name = "AccessControlForm"; 109 | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; 110 | this.Text = "KeePassNatMsg: Confirm Access"; 111 | this.TopMost = true; 112 | this.ResumeLayout(false); 113 | this.PerformLayout(); 114 | 115 | } 116 | 117 | #endregion 118 | 119 | private System.Windows.Forms.ListBox EntriesBox; 120 | private System.Windows.Forms.CheckBox RememberCheck; 121 | private System.Windows.Forms.Label ConfirmTextLabel; 122 | } 123 | } -------------------------------------------------------------------------------- /KeePassNatMsg/AccessControlForm.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Data; 5 | using System.Drawing; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Windows.Forms; 9 | 10 | using KeePassLib; 11 | 12 | namespace KeePassNatMsg 13 | { 14 | public partial class AccessControlForm : Form 15 | { 16 | public AccessControlForm() 17 | { 18 | InitializeComponent(); 19 | } 20 | 21 | private void AllowButton_Click(object sender, EventArgs e) 22 | { 23 | Allowed = true; 24 | Close(); 25 | } 26 | 27 | private void DenyButton_Click(object sender, EventArgs e) 28 | { 29 | Denied = true; 30 | Close(); 31 | } 32 | 33 | public bool Allowed = false; 34 | public bool Denied = false; 35 | 36 | public bool Remember 37 | { 38 | get { return RememberCheck.Checked; } 39 | } 40 | 41 | public List Entries 42 | { 43 | set 44 | { 45 | EntriesBox.SelectionMode = SelectionMode.None; 46 | Count = value.Count; 47 | SetLabel(); 48 | foreach (var e in value) 49 | { 50 | if (e == null || e.Strings == null || 51 | e.Strings.Get(PwDefs.TitleField) == null) continue; 52 | var title = e.Strings.Get(PwDefs.TitleField).ReadString(); 53 | if (Plugin == null || Plugin.GetUserPass(e) == null) 54 | continue; 55 | var username = Plugin.GetUserPass(e)[0]; 56 | 57 | EntriesBox.Items.Add(title + " - " + username); 58 | } 59 | } 60 | } 61 | 62 | public string Host 63 | { 64 | set 65 | { 66 | _Host = value; 67 | SetLabel(); 68 | } 69 | } 70 | 71 | private void SetLabel() 72 | { 73 | if (_Host == null) 74 | return; 75 | ConfirmTextLabel.Text = String.Format( 76 | Message, 77 | _Host, 78 | Count == 1 ? "item" : "items", 79 | Count == 1 ? "" : "\nYou can only grant access to all items.", 80 | Count == 1 ? "" : " to all of them" 81 | ); 82 | } 83 | 84 | private int Count = 0; 85 | private const string Message = "{0} has requested access to passwords for the above {1}.{2} " + 86 | "Please select whether you want to allow access{3}."; 87 | private string _Host = null; 88 | internal KeePassNatMsgExt Plugin = null; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /KeePassNatMsg/AccessControlForm.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | False 122 | 123 | 124 | False 125 | 126 | -------------------------------------------------------------------------------- /KeePassNatMsg/ConfigOpt.cs: -------------------------------------------------------------------------------- 1 | using KeePass.App.Configuration; 2 | using System.ComponentModel; 3 | 4 | namespace KeePassNatMsg 5 | { 6 | public enum AllowSearchDatabase 7 | { 8 | [Description("Target database for search")] 9 | SearchInOnlySelectedDatabase, 10 | SearchInAllOpenedDatabases, 11 | RestrictSearchInSpecificDatabase 12 | } 13 | 14 | public class ConfigOpt 15 | { 16 | readonly AceCustomConfig _config; 17 | const string ReceiveCredentialNotificationKey = "KeePassHttp_ReceiveCredentialNotification"; 18 | const string SpecificMatchingOnlyKey = "KeePassHttp_SpecificMatchingOnly"; 19 | const string UnlockDatabaseRequestKey = "KeePassHttp_UnlockDatabaseRequest"; 20 | const string AlwaysAllowAccessKey = "KeePassHttp_AlwaysAllowAccess"; 21 | const string AlwaysAllowUpdatesKey = "KeePassHttp_AlwaysAllowUpdates"; 22 | const string SearchInAllOpenedDatabasesKey = "KeePassHttp_SearchInAllOpenedDatabases"; // Only for backward compatibility 23 | const string AllowSearchDatabaseKey = "KeePassHttp_AllowSearchDatabase"; 24 | const string SearchDatabaseHashKey = "KeePassHttp_SearchDatabaseHash"; 25 | const string HideExpiredKey = "KeePassHttp_HideExpired"; 26 | const string MatchSchemesKey = "KeePassHttp_MatchSchemes"; 27 | const string ReturnStringFieldsKey = "KeePassHttp_ReturnStringFields"; 28 | const string ReturnStringFieldsWithKphOnlyKey = "KeePassHttp_ReturnStringFieldsWithKphOnly"; 29 | const string SortResultByUsernameKey = "KeePassHttp_SortResultByUsername"; 30 | const string OverrideKeePassXcVersionKey = "KeePassNatMsg_OverrideKeePassXcVersion"; 31 | const string ConnectionDatabaseHashKey = "KeePassHttp_ConnectionDatabaseHash"; 32 | const string SearchUrlsKey = "KeePassHttp_SearchUrls"; 33 | const string UseKeePassXcSettingsKey = "KeePassNatMsg_UseKpxcSettings"; 34 | private const string UseLegacyHostMatchingKey = "KeePassNatMsg_UseLegacyHostMatching"; 35 | 36 | public ConfigOpt(AceCustomConfig config) 37 | { 38 | _config = config; 39 | } 40 | 41 | public bool ReceiveCredentialNotification 42 | { 43 | get { return _config.GetBool(ReceiveCredentialNotificationKey, true); } 44 | set { _config.SetBool(ReceiveCredentialNotificationKey, value); } 45 | } 46 | 47 | public bool UnlockDatabaseRequest 48 | { 49 | get { return _config.GetBool(UnlockDatabaseRequestKey, false); } 50 | set { _config.SetBool(UnlockDatabaseRequestKey, value); } 51 | } 52 | 53 | public bool SpecificMatchingOnly 54 | { 55 | get { return _config.GetBool(SpecificMatchingOnlyKey, false); } 56 | set { _config.SetBool(SpecificMatchingOnlyKey, value); } 57 | } 58 | 59 | public bool AlwaysAllowAccess 60 | { 61 | get { return _config.GetBool(AlwaysAllowAccessKey, false); } 62 | set { _config.SetBool(AlwaysAllowAccessKey, value); } 63 | } 64 | 65 | public bool AlwaysAllowUpdates 66 | { 67 | get { return _config.GetBool(AlwaysAllowUpdatesKey, false); } 68 | set { _config.SetBool(AlwaysAllowUpdatesKey, value); } 69 | } 70 | 71 | public bool SearchInAllOpenedDatabases // Only for backward compatibility 72 | { 73 | get { return _config.GetBool(SearchInAllOpenedDatabasesKey, false); } 74 | set { _config.SetBool(SearchInAllOpenedDatabasesKey, value); } 75 | } 76 | 77 | public ulong AllowSearchDatabase 78 | { 79 | get { 80 | return _config.GetULong(AllowSearchDatabaseKey, 0); } 81 | set { _config.SetULong(AllowSearchDatabaseKey, value); } 82 | } 83 | 84 | public string SearchDatabaseHash 85 | { 86 | get { return _config.GetString(SearchDatabaseHashKey, string.Empty); } 87 | set { _config.SetString(SearchDatabaseHashKey, value); } 88 | } 89 | 90 | public bool HideExpired 91 | { 92 | get { return _config.GetBool(HideExpiredKey, false); } 93 | set { _config.SetBool(HideExpiredKey, value); } 94 | } 95 | public bool MatchSchemes 96 | { 97 | get { return _config.GetBool(MatchSchemesKey, false); } 98 | set { _config.SetBool(MatchSchemesKey, value); } 99 | } 100 | 101 | public bool ReturnStringFields 102 | { 103 | get { return _config.GetBool(ReturnStringFieldsKey, false); } 104 | set { _config.SetBool(ReturnStringFieldsKey, value); } 105 | } 106 | 107 | public bool ReturnStringFieldsWithKphOnly 108 | { 109 | get { return _config.GetBool(ReturnStringFieldsWithKphOnlyKey, true); } 110 | set { _config.SetBool(ReturnStringFieldsWithKphOnlyKey, value); } 111 | } 112 | 113 | public bool SortResultByUsername 114 | { 115 | get { return _config.GetBool(SortResultByUsernameKey, true); } 116 | set { _config.SetBool(SortResultByUsernameKey, value); } 117 | } 118 | 119 | public string OverrideKeePassXcVersion 120 | { 121 | get 122 | { 123 | return _config.GetString(OverrideKeePassXcVersionKey); 124 | } 125 | set 126 | { 127 | _config.SetString(OverrideKeePassXcVersionKey, value); 128 | } 129 | } 130 | 131 | public string ConnectionDatabaseHash 132 | { 133 | get { return _config.GetString(ConnectionDatabaseHashKey, string.Empty); } 134 | set { _config.SetString(ConnectionDatabaseHashKey, value); } 135 | } 136 | 137 | public bool SearchUrls 138 | { 139 | get 140 | { 141 | return _config.GetBool(SearchUrlsKey, false); 142 | } 143 | set 144 | { 145 | _config.SetBool(SearchUrlsKey, value); 146 | } 147 | } 148 | 149 | public bool UseKeePassXcSettings 150 | { 151 | get 152 | { 153 | return _config.GetBool(UseKeePassXcSettingsKey, false); 154 | } 155 | set 156 | { 157 | _config.SetBool(UseKeePassXcSettingsKey, value); 158 | } 159 | } 160 | 161 | public bool UseLegacyHostMatching 162 | { 163 | get { return _config.GetBool(UseLegacyHostMatchingKey, false); } 164 | set { _config.SetBool(UseLegacyHostMatchingKey, value); } 165 | } 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /KeePassNatMsg/ConfirmAssociationForm.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace KeePassNatMsg 2 | { 3 | partial class ConfirmAssociationForm 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | System.Windows.Forms.Label label1; 32 | System.Windows.Forms.Label label2; 33 | System.Windows.Forms.Label label3; 34 | this.KeyLabel = new System.Windows.Forms.Label(); 35 | this.KeyName = new System.Windows.Forms.TextBox(); 36 | this.Save = new System.Windows.Forms.Button(); 37 | this.Cancel = new System.Windows.Forms.Button(); 38 | label1 = new System.Windows.Forms.Label(); 39 | label2 = new System.Windows.Forms.Label(); 40 | label3 = new System.Windows.Forms.Label(); 41 | this.SuspendLayout(); 42 | // 43 | // label1 44 | // 45 | label1.AutoSize = true; 46 | label1.Location = new System.Drawing.Point(12, 9); 47 | label1.Name = "label1"; 48 | label1.Size = new System.Drawing.Size(80, 13); 49 | label1.TabIndex = 0; 50 | label1.Text = "Encryption key:"; 51 | // 52 | // label2 53 | // 54 | label2.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 55 | | System.Windows.Forms.AnchorStyles.Right))); 56 | label2.Location = new System.Drawing.Point(12, 78); 57 | label2.Name = "label2"; 58 | label2.Size = new System.Drawing.Size(440, 48); 59 | label2.TabIndex = 1; 60 | label2.Text = "You have received an association request for the above key. If you would like to " + 61 | "allow it access to your KeePass database give it a unique name to identify and a" + 62 | "ccept it."; 63 | // 64 | // label3 65 | // 66 | label3.AutoSize = true; 67 | label3.Location = new System.Drawing.Point(12, 37); 68 | label3.Name = "label3"; 69 | label3.Size = new System.Drawing.Size(57, 13); 70 | label3.TabIndex = 3; 71 | label3.Text = "Key name:"; 72 | // 73 | // KeyLabel 74 | // 75 | this.KeyLabel.AutoSize = true; 76 | this.KeyLabel.Location = new System.Drawing.Point(99, 9); 77 | this.KeyLabel.Name = "KeyLabel"; 78 | this.KeyLabel.Size = new System.Drawing.Size(87, 13); 79 | this.KeyLabel.TabIndex = 2; 80 | this.KeyLabel.Text = "Placeholder Text"; 81 | // 82 | // KeyName 83 | // 84 | this.KeyName.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 85 | | System.Windows.Forms.AnchorStyles.Right))); 86 | this.KeyName.Location = new System.Drawing.Point(102, 34); 87 | this.KeyName.Name = "KeyName"; 88 | this.KeyName.Size = new System.Drawing.Size(350, 20); 89 | this.KeyName.TabIndex = 4; 90 | // 91 | // Save 92 | // 93 | this.Save.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); 94 | this.Save.Location = new System.Drawing.Point(296, 129); 95 | this.Save.Name = "Save"; 96 | this.Save.Size = new System.Drawing.Size(75, 23); 97 | this.Save.TabIndex = 5; 98 | this.Save.Text = "&Save"; 99 | this.Save.UseVisualStyleBackColor = true; 100 | this.Save.Click += new System.EventHandler(this.Save_Click); 101 | // 102 | // Cancel 103 | // 104 | this.Cancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); 105 | this.Cancel.Location = new System.Drawing.Point(377, 129); 106 | this.Cancel.Name = "Cancel"; 107 | this.Cancel.Size = new System.Drawing.Size(75, 23); 108 | this.Cancel.TabIndex = 6; 109 | this.Cancel.Text = "&Cancel"; 110 | this.Cancel.UseVisualStyleBackColor = true; 111 | this.Cancel.Click += new System.EventHandler(this.Cancel_Click); 112 | // 113 | // ConfirmAssociationForm 114 | // 115 | this.AcceptButton = this.Save; 116 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 117 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 118 | this.ClientSize = new System.Drawing.Size(464, 164); 119 | this.Controls.Add(this.Cancel); 120 | this.Controls.Add(this.Save); 121 | this.Controls.Add(this.KeyName); 122 | this.Controls.Add(label3); 123 | this.Controls.Add(this.KeyLabel); 124 | this.Controls.Add(label2); 125 | this.Controls.Add(label1); 126 | this.MaximizeBox = false; 127 | this.MinimizeBox = false; 128 | this.Name = "ConfirmAssociationForm"; 129 | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; 130 | this.Text = "KeePassNatMsg: Confirm New Key Association"; 131 | this.TopMost = true; 132 | this.ResumeLayout(false); 133 | this.PerformLayout(); 134 | 135 | } 136 | 137 | #endregion 138 | 139 | private System.Windows.Forms.Label KeyLabel; 140 | private System.Windows.Forms.TextBox KeyName; 141 | private System.Windows.Forms.Button Save; 142 | private System.Windows.Forms.Button Cancel; 143 | 144 | } 145 | } -------------------------------------------------------------------------------- /KeePassNatMsg/ConfirmAssociationForm.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Data; 5 | using System.Drawing; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Windows.Forms; 9 | 10 | namespace KeePassNatMsg 11 | { 12 | public partial class ConfirmAssociationForm : Form 13 | { 14 | public ConfirmAssociationForm() 15 | { 16 | InitializeComponent(); 17 | Saved = false; 18 | } 19 | 20 | private void Save_Click(object sender, EventArgs e) 21 | { 22 | var value = KeyName.Text; 23 | if (value != null && value.Trim() != "") 24 | { 25 | Saved = true; 26 | Close(); 27 | } 28 | } 29 | 30 | private void Cancel_Click(object sender, EventArgs e) 31 | { 32 | Close(); 33 | } 34 | 35 | public string KeyId 36 | { 37 | get 38 | { 39 | return Saved ? KeyName.Text : null; 40 | } 41 | } 42 | 43 | public bool Saved { get; private set; } 44 | public string Key 45 | { 46 | get 47 | { 48 | return KeyLabel.Text; 49 | } 50 | set 51 | { 52 | KeyLabel.Text = value; 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /KeePassNatMsg/ConfirmAssociationForm.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | False 122 | 123 | 124 | False 125 | 126 | 127 | False 128 | 129 | -------------------------------------------------------------------------------- /KeePassNatMsg/Entry/EntryConfig.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace KeePassNatMsg.Entry 4 | { 5 | public class EntryConfig 6 | { 7 | public HashSet Allow = new HashSet(); 8 | public HashSet Deny = new HashSet(); 9 | public string Realm = null; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /KeePassNatMsg/Entry/EntrySearch.cs: -------------------------------------------------------------------------------- 1 | using KeePass.Plugins; 2 | using KeePass.UI; 3 | using KeePass.Util.Spr; 4 | using KeePassNatMsg.Protocol; 5 | using KeePassNatMsg.Protocol.Action; 6 | using KeePassLib; 7 | using KeePassLib.Collections; 8 | using Newtonsoft.Json.Linq; 9 | using System; 10 | using System.Collections.Generic; 11 | using System.Linq; 12 | using System.Windows.Forms; 13 | using KeePassLib.Utility; 14 | using KeePassLib.Delegates; 15 | 16 | namespace KeePassNatMsg.Entry 17 | { 18 | public sealed class EntrySearch 19 | { 20 | private const string TotpKey = "TimeOtp-Secret"; 21 | private const string TotpPlaceholder = "{TIMEOTP}"; 22 | private const string TotpLegacyPlaceholder = "{TOTP}"; 23 | 24 | private readonly IPluginHost _host; 25 | private readonly KeePassNatMsgExt _ext; 26 | private readonly List _allowedSchemes = new List(new[] { "http", "https", "ftp", "sftp" }); 27 | 28 | public EntrySearch() 29 | { 30 | _host = KeePassNatMsgExt.HostInstance; 31 | _ext = KeePassNatMsgExt.ExtInstance; 32 | } 33 | 34 | internal Response GetLoginsHandler(Request req) 35 | { 36 | if (!req.TryDecrypt()) return new ErrorResponse(req, ErrorType.CannotDecryptMessage); 37 | 38 | var msg = req.Message; 39 | var id = msg.GetString("id"); 40 | var url = msg.GetString("url"); 41 | var submitUrl = msg.GetString("submitUrl"); 42 | 43 | Uri hostUri; 44 | Uri submitUri = null; 45 | var uris = new List(); 46 | 47 | if (!string.IsNullOrEmpty(url)) 48 | { 49 | hostUri = new Uri(url); 50 | uris.Add(hostUri); 51 | } 52 | else 53 | { 54 | return new ErrorResponse(req, ErrorType.NoUrlProvided); 55 | } 56 | 57 | if (!string.IsNullOrEmpty(submitUrl)) 58 | { 59 | submitUri = new Uri(submitUrl); 60 | // Exclude if "submitUrl" is Javascript (or anything else) 61 | if (_allowedSchemes.Contains(submitUri.Scheme) && submitUri.Authority != null) 62 | { 63 | uris.Add(submitUri); 64 | } 65 | else 66 | { 67 | submitUrl = null; 68 | submitUri = null; 69 | } 70 | } 71 | 72 | var resp = req.GetResponse(); 73 | resp.Message.Add("id", id); 74 | 75 | var items = FindMatchingEntries(url, null); 76 | if (items.ToList().Count > 0) 77 | { 78 | var configOpt = new ConfigOpt(_host.CustomConfig); 79 | 80 | var filter = new GFunc((PwEntry e) => 81 | { 82 | var c = _ext.GetEntryConfig(e); 83 | 84 | if (configOpt.UseLegacyHostMatching) 85 | { 86 | var fields = new[] {PwDefs.TitleField, PwDefs.UrlField}; 87 | var entryUrls = e.Strings 88 | .Where(s => fields.Contains(s.Key) && s.Value != null && !s.Value.IsEmpty) 89 | .Select(s => s.Value.ReadString()) 90 | .ToList(); 91 | 92 | var isAllowed = c != null && uris.Select(u => u.Host).Any(u => c.Allow.Contains(u)); 93 | var hostMatch = uris.Select(u => u.Host).Any(u => entryUrls.Contains(u)); 94 | 95 | return (c == null && !hostMatch) || (c != null && !hostMatch && !isAllowed); 96 | } 97 | 98 | return c == null || (!c.Allow.Contains(hostUri.Authority)) || (submitUri != null && !c.Allow.Contains(submitUri.Authority)); 99 | }); 100 | 101 | var needPrompting = items.Where(e => filter(e.entry)).ToList(); 102 | 103 | if (needPrompting.Count > 0 && !configOpt.AlwaysAllowAccess) 104 | { 105 | var win = _host.MainWindow; 106 | 107 | using (var f = new AccessControlForm()) 108 | { 109 | win.Invoke((MethodInvoker)delegate 110 | { 111 | f.Icon = win.Icon; 112 | f.Plugin = _ext; 113 | f.StartPosition = win.Visible ? FormStartPosition.CenterParent : FormStartPosition.CenterScreen; 114 | f.Entries = needPrompting.Select(e => e.entry).ToList(); 115 | f.Host = submitUri != null ? submitUri.Authority : hostUri.Authority; 116 | f.Load += delegate { f.Activate(); }; 117 | f.ShowDialog(win); 118 | if (f.Remember && (f.Allowed || f.Denied)) 119 | { 120 | foreach (var e in needPrompting) 121 | { 122 | var c = _ext.GetEntryConfig(e.entry) ?? new EntryConfig(); 123 | var set = f.Allowed ? c.Allow : c.Deny; 124 | set.Add(hostUri.Authority); 125 | if (submitUri != null && submitUri.Authority != hostUri.Authority) 126 | set.Add(submitUri.Authority); 127 | _ext.SetEntryConfig(e.entry, c); 128 | } 129 | } 130 | if (!f.Allowed) 131 | { 132 | items = items.Except(needPrompting); 133 | } 134 | }); 135 | } 136 | } 137 | 138 | var uri = submitUri != null ? submitUri : hostUri; 139 | 140 | foreach (var entryDatabase in items) 141 | { 142 | string entryUrl = string.Copy(entryDatabase.entry.Strings.ReadSafe(PwDefs.UrlField)); 143 | if (string.IsNullOrEmpty(entryUrl)) 144 | entryUrl = entryDatabase.entry.Strings.ReadSafe(PwDefs.TitleField); 145 | 146 | entryUrl = entryUrl.ToLower(); 147 | 148 | entryDatabase.entry.UsageCount = (ulong)LevenshteinDistance(uri.ToString().ToLower(), entryUrl); 149 | } 150 | 151 | var itemsList = items.ToList(); 152 | 153 | if (configOpt.SpecificMatchingOnly) 154 | { 155 | itemsList = (from e in itemsList 156 | orderby e.entry.UsageCount ascending 157 | select e).ToList(); 158 | 159 | ulong lowestDistance = itemsList.Count > 0 ? 160 | itemsList[0].entry.UsageCount : 161 | 0; 162 | 163 | itemsList = (from e in itemsList 164 | where e.entry.UsageCount == lowestDistance 165 | orderby e.entry.UsageCount 166 | select e).ToList(); 167 | } 168 | 169 | if (configOpt.SortResultByUsername) 170 | { 171 | var items2 = from e in itemsList orderby e.entry.UsageCount ascending, _ext.GetUserPass(e)[0] ascending select e; 172 | itemsList = items2.ToList(); 173 | } 174 | else 175 | { 176 | var items2 = from e in itemsList orderby e.entry.UsageCount ascending, e.entry.Strings.ReadSafe(PwDefs.TitleField) ascending select e; 177 | itemsList = items2.ToList(); 178 | } 179 | 180 | var entries = new JArray(itemsList.Select(item => 181 | { 182 | var up = _ext.GetUserPass(item); 183 | JArray fldArr = null; 184 | var fields = GetFields(configOpt, item); 185 | if (fields != null) 186 | { 187 | fldArr = new JArray(fields.Select(f => new JObject { { f.Key, f.Value } })); 188 | } 189 | var jobj = new JObject { 190 | { "name", item.entry.Strings.ReadSafe(PwDefs.TitleField) }, 191 | { "login", up[0] }, 192 | { "password", up[1] }, 193 | { "uuid", item.entry.Uuid.ToHexString() }, 194 | { "stringFields", fldArr }, 195 | }; 196 | 197 | CheckTotp(item, jobj); 198 | 199 | return jobj; 200 | })); 201 | 202 | resp.Message.Add("count", itemsList.Count); 203 | resp.Message.Add("entries", entries); 204 | 205 | if (itemsList.Count > 0) 206 | { 207 | var names = (from e in itemsList select e.entry.Strings.ReadSafe(PwDefs.TitleField)).Distinct(); 208 | var n = String.Join("\n ", names); 209 | 210 | if (configOpt.ReceiveCredentialNotification) 211 | _ext.ShowNotification(String.Format("{0}: {1} is receiving credentials for:\n {2}", req.GetString("id"), hostUri.Host, n)); 212 | } 213 | 214 | return resp; 215 | } 216 | 217 | resp.Message.Add("count", 0); 218 | resp.Message.Add("entries", new JArray()); 219 | 220 | return resp; 221 | } 222 | 223 | private void CheckTotp(PwEntryDatabase item, JObject obj) 224 | { 225 | var totp = GetTotpFromEntry(item); 226 | if (!string.IsNullOrEmpty(totp)) 227 | { 228 | obj.Add("totp", totp); 229 | } 230 | } 231 | 232 | internal string GetTotp(string uuid) 233 | { 234 | var dbEntry = FindEntry(uuid); 235 | 236 | if (dbEntry == null) 237 | return null; 238 | 239 | return GetTotpFromEntry(dbEntry); 240 | } 241 | 242 | private string GetTotpFromEntry(PwEntryDatabase item) 243 | { 244 | string totp = null; 245 | 246 | if (HasLegacyTotp(item.entry)) 247 | { 248 | totp = GenerateTotp(item, TotpLegacyPlaceholder); 249 | } 250 | 251 | if (string.IsNullOrEmpty(totp) && HasTotp(item.entry)) 252 | { 253 | // add support for keepass totp 254 | // https://keepass.info/help/base/placeholders.html#otp 255 | totp = GenerateTotp(item, TotpPlaceholder); 256 | } 257 | return totp; 258 | } 259 | 260 | private string GenerateTotp(PwEntryDatabase item, string placeholder) 261 | { 262 | var ctx = new SprContext(item.entry, item.database, SprCompileFlags.All, false, false); 263 | return SprEngine.Compile(placeholder, ctx); 264 | } 265 | 266 | private static bool HasTotp(PwEntry entry) 267 | { 268 | return entry.Strings.Any(x => x.Key.StartsWith(TotpKey)); 269 | } 270 | 271 | // KeeOtp support through keepassxc-browser 272 | // KeeOtp stores the TOTP config in a string field "otp" and provides a placeholder "{TOTP}" 273 | // KeeTrayTOTP uses by default a "TOTP Seed" string field, and the {TOTP} placeholder. 274 | // keepassxc-browser needs the value in a string field named "KPH: {TOTP}" 275 | private static bool HasLegacyTotp(PwEntry entry) 276 | { 277 | return entry.Strings.Any(x => 278 | x.Key.Equals("otp", StringComparison.InvariantCultureIgnoreCase) || 279 | x.Key.Equals("TOTP Seed", StringComparison.InvariantCultureIgnoreCase)); 280 | } 281 | 282 | private PwEntryDatabase FindEntry(string uuid) 283 | { 284 | PwUuid id = new PwUuid(MemUtil.HexStringToByteArray(uuid)); 285 | 286 | var configOpt = new ConfigOpt(_host.CustomConfig); 287 | 288 | if (configOpt.AllowSearchDatabase == (ulong)AllowSearchDatabase.SearchInAllOpenedDatabases) 289 | { 290 | foreach (var doc in _host.MainWindow.DocumentManager.Documents) 291 | { 292 | if (doc.Database.IsOpen) 293 | { 294 | var entry = doc.Database.RootGroup.FindEntry(id, true); 295 | if (entry != null) 296 | return new PwEntryDatabase(entry, doc.Database); 297 | } 298 | } 299 | } 300 | else if (configOpt.AllowSearchDatabase == (ulong)AllowSearchDatabase.RestrictSearchInSpecificDatabase) 301 | { 302 | var entry = _ext.GetSearchDatabase().RootGroup.FindEntry(id, true); 303 | if (entry != null) 304 | return new PwEntryDatabase(entry, _ext.GetSearchDatabase()); 305 | } 306 | else 307 | { 308 | var entry = _host.Database.RootGroup.FindEntry(id, true); 309 | if (entry != null) 310 | return new PwEntryDatabase(entry, _host.Database); 311 | } 312 | 313 | return null; 314 | } 315 | 316 | //http://en.wikibooks.org/wiki/Algorithm_Implementation/Strings/Levenshtein_distance#C.23 317 | private static int LevenshteinDistance(string source, string target) 318 | { 319 | if (String.IsNullOrEmpty(source)) 320 | { 321 | if (String.IsNullOrEmpty(target)) return 0; 322 | return target.Length; 323 | } 324 | if (String.IsNullOrEmpty(target)) return source.Length; 325 | 326 | if (source.Length > target.Length) 327 | { 328 | var temp = target; 329 | target = source; 330 | source = temp; 331 | } 332 | 333 | var m = target.Length; 334 | var n = source.Length; 335 | var distance = new int[2, m + 1]; 336 | // Initialize the distance 'matrix' 337 | for (var j = 1; j <= m; j++) distance[0, j] = j; 338 | 339 | var currentRow = 0; 340 | for (var i = 1; i <= n; ++i) 341 | { 342 | currentRow = i & 1; 343 | distance[currentRow, 0] = i; 344 | var previousRow = currentRow ^ 1; 345 | for (var j = 1; j <= m; j++) 346 | { 347 | var cost = (target[j - 1] == source[i - 1] ? 0 : 1); 348 | distance[currentRow, j] = Math.Min(Math.Min( 349 | distance[previousRow, j] + 1, 350 | distance[currentRow, j - 1] + 1), 351 | distance[previousRow, j - 1] + cost); 352 | } 353 | } 354 | return distance[currentRow, m]; 355 | } 356 | 357 | private static IEnumerable> GetFields(ConfigOpt configOpt, PwEntryDatabase entryDatabase) 358 | { 359 | SprContext ctx = new SprContext(entryDatabase.entry, entryDatabase.database, SprCompileFlags.All, false, false); 360 | 361 | List> fields = null; 362 | if (configOpt.ReturnStringFields) 363 | { 364 | fields = new List>(); 365 | 366 | foreach (var sf in entryDatabase.entry.Strings) 367 | { 368 | var sfValue = entryDatabase.entry.Strings.ReadSafe(sf.Key); 369 | 370 | // follow references 371 | sfValue = SprEngine.Compile(sfValue, ctx); 372 | 373 | if (configOpt.ReturnStringFieldsWithKphOnly && sf.Key.StartsWith("KPH: ")) 374 | { 375 | fields.Add(new KeyValuePair(sf.Key.Substring(5), sfValue)); 376 | } 377 | else 378 | { 379 | fields.Add(new KeyValuePair(sf.Key, sfValue)); 380 | } 381 | } 382 | 383 | if (fields.Count > 0) 384 | { 385 | var sorted = from e2 in fields orderby e2.Key ascending select e2; 386 | fields = sorted.ToList(); 387 | } 388 | else 389 | { 390 | fields = null; 391 | } 392 | } 393 | 394 | return fields; 395 | } 396 | 397 | private IEnumerable FindMatchingEntries(string url, string realm) 398 | { 399 | var listResult = new List(); 400 | var hostUri = new Uri(url); 401 | 402 | var formHost = hostUri.Host; 403 | var searchHost = hostUri.Host; 404 | var origSearchHost = hostUri.Host; 405 | var searchScheme = hostUri.Scheme; 406 | 407 | List listDatabases = new List(); 408 | 409 | var configOpt = new ConfigOpt(_host.CustomConfig); 410 | if (configOpt.AllowSearchDatabase == (ulong)AllowSearchDatabase.SearchInAllOpenedDatabases) 411 | { 412 | foreach (PwDocument doc in _host.MainWindow.DocumentManager.Documents) 413 | { 414 | if (doc.Database.IsOpen) 415 | { 416 | listDatabases.Add(doc.Database); 417 | } 418 | } 419 | } 420 | else if (configOpt.AllowSearchDatabase == (ulong)AllowSearchDatabase.RestrictSearchInSpecificDatabase) 421 | { 422 | listDatabases.Add(_ext.GetSearchDatabase()); 423 | } 424 | else 425 | { 426 | listDatabases.Add(_host.Database); 427 | } 428 | 429 | var parms = MakeSearchParameters(configOpt.HideExpired); 430 | var searchUrls = configOpt.SearchUrls; 431 | int listCount = 0; 432 | 433 | foreach (PwDatabase db in listDatabases) 434 | { 435 | searchHost = origSearchHost; 436 | //get all possible entries for given host-name 437 | while (listResult.Count == listCount && (origSearchHost == searchHost || searchHost.IndexOf(".") != -1)) 438 | { 439 | if (configOpt.MatchSchemes) 440 | { 441 | parms.SearchString = string.Format("^{0}$|{1}://{0}/?", searchHost, searchScheme); 442 | } 443 | else 444 | { 445 | parms.SearchString = string.Format("^{0}$|/{0}/?", searchHost); 446 | } 447 | var listEntries = new PwObjectList(); 448 | db.RootGroup.SearchEntries(parms, listEntries); 449 | listResult.AddRange(listEntries.Select(x => new PwEntryDatabase(x, db))); 450 | if (searchUrls) AddURLCandidates(db, listResult, parms.RespectEntrySearchingDisabled); 451 | searchHost = searchHost.Substring(searchHost.IndexOf(".") + 1); 452 | 453 | //searchHost contains no dot --> prevent possible infinite loop 454 | if (searchHost == origSearchHost) 455 | break; 456 | } 457 | listCount = listResult.Count; 458 | } 459 | 460 | var filter = new GFunc((PwEntry e) => 461 | { 462 | var title = e.Strings.ReadSafe(PwDefs.TitleField); 463 | var entryUrl = e.Strings.ReadSafe(PwDefs.UrlField); 464 | var c = _ext.GetEntryConfig(e); 465 | if (c != null) 466 | { 467 | if (c.Allow.Contains(formHost)) 468 | return true; 469 | if (c.Deny.Contains(formHost)) 470 | return false; 471 | if (!string.IsNullOrEmpty(realm) && c.Realm != realm) 472 | return false; 473 | } 474 | 475 | if (IsValidUrl(entryUrl, formHost)) 476 | return true; 477 | 478 | if (IsValidUrl(title, formHost)) 479 | return true; 480 | 481 | if (searchUrls) 482 | { 483 | foreach (var sf in e.Strings.Where(s => s.Key.StartsWith("URL", StringComparison.InvariantCultureIgnoreCase) || s.Key.StartsWith("KP2A_URL_", StringComparison.InvariantCultureIgnoreCase))) 484 | { 485 | var sfv = e.Strings.ReadSafe(sf.Key); 486 | 487 | if (sf.Key.IndexOf("regex", StringComparison.OrdinalIgnoreCase) >= 0 488 | && System.Text.RegularExpressions.Regex.IsMatch(formHost, sfv)) 489 | { 490 | return true; 491 | } 492 | 493 | if (IsValidUrl(sfv, formHost)) 494 | return true; 495 | } 496 | } 497 | 498 | return formHost.Contains(title) || (!string.IsNullOrEmpty(entryUrl) && formHost.Contains(entryUrl)); 499 | }); 500 | 501 | var result = listResult.Where(e => filter(e.entry)); 502 | 503 | if (configOpt.HideExpired) 504 | { 505 | result = result.Where(x => !(x.entry.Expires && x.entry.ExpiryTime <= DateTime.UtcNow)); 506 | } 507 | 508 | return result; 509 | } 510 | 511 | private void AddURLCandidates(PwDatabase db, List listResult, bool bRespectEntrySearchingDisabled) 512 | { 513 | var alreadyFound = listResult.Select(x => x.entry); 514 | var listEntries = db.RootGroup.GetEntries(true) 515 | .AsEnumerable() 516 | .Where(x => !alreadyFound.Contains(x)); 517 | 518 | if (bRespectEntrySearchingDisabled) listEntries = listEntries.Where(x => x.GetSearchingEnabled()); 519 | foreach (var entry in listEntries) 520 | { 521 | if (!entry.Strings.Any(x => 522 | x.Key.StartsWith("URL", StringComparison.InvariantCultureIgnoreCase) 523 | && x.Key.ToLowerInvariant().Contains("regex"))) continue; 524 | listResult.Add(new PwEntryDatabase(entry, db)); 525 | } 526 | } 527 | 528 | private bool IsValidUrl(string url, string host) 529 | { 530 | Uri uri; 531 | return Uri.TryCreate(url, UriKind.Absolute, out uri) && _allowedSchemes.Contains(uri.Scheme) && host.EndsWith(uri.Host); 532 | } 533 | 534 | private static SearchParameters MakeSearchParameters(bool excludeExpired) 535 | { 536 | return new SearchParameters 537 | { 538 | SearchInTitles = true, 539 | SearchInGroupNames = false, 540 | SearchInNotes = false, 541 | SearchInOther = true, 542 | SearchInPasswords = false, 543 | SearchInTags = false, 544 | SearchInUrls = true, 545 | SearchInUserNames = false, 546 | SearchInUuids = false, 547 | ExcludeExpired = excludeExpired, 548 | SearchMode = PwSearchMode.Regular, 549 | ComparisonMode = StringComparison.InvariantCultureIgnoreCase, 550 | }; 551 | } 552 | } 553 | } 554 | -------------------------------------------------------------------------------- /KeePassNatMsg/Entry/EntryUpdate.cs: -------------------------------------------------------------------------------- 1 | using KeePass.Plugins; 2 | using KeePass.UI; 3 | using KeePassLib; 4 | using KeePassLib.Collections; 5 | using KeePassLib.Security; 6 | using KeePassLib.Utility; 7 | using System; 8 | using System.IO; 9 | using System.Windows.Forms; 10 | 11 | namespace KeePassNatMsg.Entry 12 | { 13 | public sealed class EntryUpdate 14 | { 15 | private IPluginHost _host; 16 | private KeePassNatMsgExt _ext; 17 | 18 | public EntryUpdate() 19 | { 20 | _host = KeePassNatMsgExt.HostInstance; 21 | _ext = KeePassNatMsgExt.ExtInstance; 22 | } 23 | 24 | public bool UpdateEntry(string uuid, string username, string password, string formHost) 25 | { 26 | PwEntry entry = null; 27 | PwUuid id = new PwUuid(MemUtil.HexStringToByteArray(uuid)); 28 | PwDatabase db = null; 29 | var configOpt = new ConfigOpt(_host.CustomConfig); 30 | if (configOpt.AllowSearchDatabase == (ulong)AllowSearchDatabase.SearchInAllOpenedDatabases) 31 | { 32 | foreach (PwDocument doc in _host.MainWindow.DocumentManager.Documents) 33 | { 34 | if (doc.Database.IsOpen) 35 | { 36 | entry = doc.Database.RootGroup.FindEntry(id, true); 37 | if (entry != null) 38 | { 39 | db = doc.Database; 40 | break; 41 | } 42 | } 43 | } 44 | } 45 | else if (configOpt.AllowSearchDatabase == (ulong)AllowSearchDatabase.RestrictSearchInSpecificDatabase) 46 | { 47 | entry = _ext.GetSearchDatabase().RootGroup.FindEntry(id, true); 48 | db = _ext.GetSearchDatabase(); 49 | } 50 | else 51 | { 52 | entry = _host.Database.RootGroup.FindEntry(id, true); 53 | db = _host.Database; 54 | } 55 | 56 | if (entry == null) 57 | { 58 | return false; 59 | } 60 | 61 | string[] up = _ext.GetUserPass(entry); 62 | var u = up[0]; 63 | var p = up[1]; 64 | 65 | if (u != username || p != password) 66 | { 67 | bool allowUpdate = configOpt.AlwaysAllowUpdates; 68 | 69 | if (!allowUpdate) 70 | { 71 | _host.MainWindow.Activate(); 72 | 73 | DialogResult result; 74 | if (_host.MainWindow.IsTrayed()) 75 | { 76 | result = MessageBox.Show( 77 | String.Format("Do you want to update the information in {0} - {1}?", formHost, u), 78 | "Update Entry", MessageBoxButtons.YesNo, 79 | MessageBoxIcon.None, MessageBoxDefaultButton.Button1, MessageBoxOptions.DefaultDesktopOnly); 80 | } 81 | else 82 | { 83 | result = MessageBox.Show( 84 | _host.MainWindow, 85 | String.Format("Do you want to update the information in {0} - {1}?", formHost, u), 86 | "Update Entry", MessageBoxButtons.YesNo, 87 | MessageBoxIcon.Information, MessageBoxDefaultButton.Button1); 88 | } 89 | 90 | 91 | if (result == DialogResult.Yes) 92 | { 93 | allowUpdate = true; 94 | } 95 | } 96 | 97 | if (allowUpdate) 98 | { 99 | PwObjectList m_vHistory = entry.History.CloneDeep(); 100 | entry.History = m_vHistory; 101 | entry.CreateBackup(null); 102 | 103 | entry.Strings.Set(PwDefs.UserNameField, new ProtectedString(false, username)); 104 | entry.Strings.Set(PwDefs.PasswordField, new ProtectedString(true, password)); 105 | entry.Touch(true, false); 106 | _ext.UpdateUI(entry.ParentGroup); 107 | 108 | AutoSaveIfRequired(db); 109 | 110 | return true; 111 | } 112 | } 113 | 114 | return false; 115 | } 116 | 117 | private void AutoSaveIfRequired(PwDatabase db) 118 | { 119 | if (!KeePass.Program.Config.Application.AutoSaveAfterEntryEdit) return; 120 | _host.MainWindow.Invoke(new MethodInvoker(() => 121 | { //different thread access UI elements 122 | KeePassNatMsgExt.HostInstance.MainWindow.SaveDatabase(db, null); 123 | })); 124 | } 125 | 126 | public bool CreateEntry(string username, string password, string url, string submithost, string realm, string groupUuid) 127 | { 128 | string baseUrl = url; 129 | // index bigger than https:// <-- this slash 130 | if (baseUrl.LastIndexOf("/") > 9) 131 | { 132 | baseUrl = baseUrl.Substring(0, baseUrl.LastIndexOf("/") + 1); 133 | } 134 | 135 | var uri = new Uri(url); 136 | 137 | PwEntry entry = new PwEntry(true, true); 138 | entry.Strings.Set(PwDefs.TitleField, new ProtectedString(false, uri.Host)); 139 | entry.Strings.Set(PwDefs.UserNameField, new ProtectedString(false, username)); 140 | entry.Strings.Set(PwDefs.PasswordField, new ProtectedString(true, password)); 141 | entry.Strings.Set(PwDefs.UrlField, new ProtectedString(true, baseUrl)); 142 | 143 | if ((submithost != null && uri.Host != submithost) || realm != null) 144 | { 145 | var config = new EntryConfig(); 146 | if (submithost != null) 147 | config.Allow.Add(submithost); 148 | if (realm != null) 149 | config.Realm = realm; 150 | 151 | _ext.SetEntryConfig(entry, config); 152 | } 153 | 154 | PwGroup group = null; 155 | 156 | if (!string.IsNullOrEmpty(groupUuid)) 157 | { 158 | var db = _ext.GetConnectionDatabase(); 159 | if (db.RootGroup != null) 160 | { 161 | var uuid = new PwUuid(MemUtil.HexStringToByteArray(groupUuid)); 162 | group = db.RootGroup.FindGroup(uuid, true); 163 | } 164 | } 165 | 166 | if (group == null) 167 | group = _ext.GetPasswordsGroup(); 168 | 169 | group.AddEntry(entry, true); 170 | _ext.UpdateUI(group); 171 | 172 | AutoSaveIfRequired(_ext.GetConnectionDatabase()); 173 | 174 | return true; 175 | } 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /KeePassNatMsg/KeePassNatMsg.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | 8.0.30703 7 | 2.0 8 | {9DFAB9D7-6BB5-4C6C-86EC-C67B494008D2} 9 | Library 10 | Properties 11 | KeePassNatMsg 12 | KeePassNatMsg 13 | v4.0 14 | 512 15 | 16 | 17 | 18 | 5 19 | 20 | 21 | true 22 | full 23 | false 24 | bin\Debug\ 25 | DEBUG;TRACE 26 | prompt 27 | 4 28 | false 29 | 30 | 31 | pdbonly 32 | true 33 | bin\Release\ 34 | TRACE 35 | prompt 36 | 4 37 | false 38 | 39 | 40 | 41 | ..\build\KeePass.exe 42 | False 43 | 44 | 45 | 46 | lib\Mono.Posix.dll 47 | 48 | 49 | lib\Newtonsoft.Json.dll 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | Form 63 | 64 | 65 | AccessControlForm.cs 66 | 67 | 68 | 69 | 70 | 71 | 72 | Form 73 | 74 | 75 | OptionsForm.cs 76 | 77 | 78 | 79 | 80 | Form 81 | 82 | 83 | BrowserSelectForm.cs 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | True 92 | True 93 | Resources.resx 94 | 95 | 96 | Form 97 | 98 | 99 | ConfirmAssociationForm.cs 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | AccessControlForm.cs 128 | 129 | 130 | ConfirmAssociationForm.cs 131 | 132 | 133 | BrowserSelectForm.cs 134 | 135 | 136 | OptionsForm.cs 137 | 138 | 139 | ResXFileCodeGenerator 140 | Resources.Designer.cs 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 155 | -------------------------------------------------------------------------------- /KeePassNatMsg/NativeMessaging/BrowserSelectForm.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace KeePassNatMsg.NativeMessaging 2 | { 3 | partial class BrowserSelectForm 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | this.btnOk = new System.Windows.Forms.Button(); 32 | this.btnCancel = new System.Windows.Forms.Button(); 33 | this.chkChrome = new System.Windows.Forms.CheckBox(); 34 | this.chkChromium = new System.Windows.Forms.CheckBox(); 35 | this.chkFirefox = new System.Windows.Forms.CheckBox(); 36 | this.chkVivaldi = new System.Windows.Forms.CheckBox(); 37 | this.chkMsEdge = new System.Windows.Forms.CheckBox(); 38 | this.chkThunderbird = new System.Windows.Forms.CheckBox(); 39 | this.SuspendLayout(); 40 | // 41 | // btnOk 42 | // 43 | this.btnOk.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); 44 | this.btnOk.Location = new System.Drawing.Point(93, 160); 45 | this.btnOk.Name = "btnOk"; 46 | this.btnOk.Size = new System.Drawing.Size(75, 23); 47 | this.btnOk.TabIndex = 1; 48 | this.btnOk.Text = "&Ok"; 49 | this.btnOk.UseVisualStyleBackColor = true; 50 | this.btnOk.Click += new System.EventHandler(this.btnOk_Click); 51 | // 52 | // btnCancel 53 | // 54 | this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); 55 | this.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; 56 | this.btnCancel.Location = new System.Drawing.Point(174, 160); 57 | this.btnCancel.Name = "btnCancel"; 58 | this.btnCancel.Size = new System.Drawing.Size(75, 23); 59 | this.btnCancel.TabIndex = 2; 60 | this.btnCancel.Text = "Cancel"; 61 | this.btnCancel.UseVisualStyleBackColor = true; 62 | // 63 | // chkChrome 64 | // 65 | this.chkChrome.AutoSize = true; 66 | this.chkChrome.Location = new System.Drawing.Point(12, 12); 67 | this.chkChrome.Name = "chkChrome"; 68 | this.chkChrome.Size = new System.Drawing.Size(99, 17); 69 | this.chkChrome.TabIndex = 3; 70 | this.chkChrome.Text = "Google Chrome"; 71 | this.chkChrome.UseVisualStyleBackColor = true; 72 | // 73 | // chkChromium 74 | // 75 | this.chkChromium.AutoSize = true; 76 | this.chkChromium.Location = new System.Drawing.Point(12, 35); 77 | this.chkChromium.Name = "chkChromium"; 78 | this.chkChromium.Size = new System.Drawing.Size(72, 17); 79 | this.chkChromium.TabIndex = 4; 80 | this.chkChromium.Text = "Chromium"; 81 | this.chkChromium.UseVisualStyleBackColor = true; 82 | // 83 | // chkFirefox 84 | // 85 | this.chkFirefox.AutoSize = true; 86 | this.chkFirefox.Location = new System.Drawing.Point(12, 58); 87 | this.chkFirefox.Name = "chkFirefox"; 88 | this.chkFirefox.Size = new System.Drawing.Size(92, 17); 89 | this.chkFirefox.TabIndex = 5; 90 | this.chkFirefox.Text = "Mozilla Firefox"; 91 | this.chkFirefox.UseVisualStyleBackColor = true; 92 | // 93 | // chkVivaldi 94 | // 95 | this.chkVivaldi.AutoSize = true; 96 | this.chkVivaldi.Location = new System.Drawing.Point(12, 81); 97 | this.chkVivaldi.Name = "chkVivaldi"; 98 | this.chkVivaldi.Size = new System.Drawing.Size(57, 17); 99 | this.chkVivaldi.TabIndex = 6; 100 | this.chkVivaldi.Text = "Vivaldi"; 101 | this.chkVivaldi.UseVisualStyleBackColor = true; 102 | // 103 | // chkMsEdge 104 | // 105 | this.chkMsEdge.AutoSize = true; 106 | this.chkMsEdge.Location = new System.Drawing.Point(12, 105); 107 | this.chkMsEdge.Name = "chkMsEdge"; 108 | this.chkMsEdge.Size = new System.Drawing.Size(97, 17); 109 | this.chkMsEdge.TabIndex = 7; 110 | this.chkMsEdge.Text = "Microsoft Edge"; 111 | this.chkMsEdge.UseVisualStyleBackColor = true; 112 | // 113 | // chkThunderbird 114 | // 115 | this.chkThunderbird.AutoSize = true; 116 | this.chkThunderbird.Location = new System.Drawing.Point(12, 128); 117 | this.chkThunderbird.Name = "chkThunderbird"; 118 | this.chkThunderbird.Size = new System.Drawing.Size(83, 17); 119 | this.chkThunderbird.TabIndex = 8; 120 | this.chkThunderbird.Text = "Thunderbird"; 121 | this.chkThunderbird.UseVisualStyleBackColor = true; 122 | // 123 | // BrowserSelectForm 124 | // 125 | this.AcceptButton = this.btnOk; 126 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 127 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 128 | this.CancelButton = this.btnCancel; 129 | this.ClientSize = new System.Drawing.Size(261, 195); 130 | this.Controls.Add(this.chkThunderbird); 131 | this.Controls.Add(this.chkMsEdge); 132 | this.Controls.Add(this.chkVivaldi); 133 | this.Controls.Add(this.chkFirefox); 134 | this.Controls.Add(this.chkChromium); 135 | this.Controls.Add(this.chkChrome); 136 | this.Controls.Add(this.btnCancel); 137 | this.Controls.Add(this.btnOk); 138 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; 139 | this.Name = "BrowserSelectForm"; 140 | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; 141 | this.Text = "Select Browsers"; 142 | this.Load += new System.EventHandler(this.BrowserSelectForm_Load); 143 | this.ResumeLayout(false); 144 | this.PerformLayout(); 145 | 146 | } 147 | 148 | #endregion 149 | private System.Windows.Forms.Button btnOk; 150 | private System.Windows.Forms.Button btnCancel; 151 | private System.Windows.Forms.CheckBox chkChrome; 152 | private System.Windows.Forms.CheckBox chkChromium; 153 | private System.Windows.Forms.CheckBox chkFirefox; 154 | private System.Windows.Forms.CheckBox chkVivaldi; 155 | private System.Windows.Forms.CheckBox chkMsEdge; 156 | private System.Windows.Forms.CheckBox chkThunderbird; 157 | } 158 | } -------------------------------------------------------------------------------- /KeePassNatMsg/NativeMessaging/BrowserSelectForm.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Forms; 2 | using KeePassNatMsg.Utils; 3 | 4 | namespace KeePassNatMsg.NativeMessaging 5 | { 6 | public partial class BrowserSelectForm : Form 7 | { 8 | private readonly NativeMessagingHost _host; 9 | 10 | public BrowserSelectForm(NativeMessagingHost host) 11 | { 12 | InitializeComponent(); 13 | _host = host; 14 | } 15 | 16 | public Browsers SelectedBrowsers 17 | { 18 | get 19 | { 20 | Browsers b = Browsers.None; 21 | if (chkChrome.Checked) b |= Browsers.Chrome; 22 | if (chkChromium.Checked) b |= Browsers.Chromium; 23 | if (chkFirefox.Checked) b |= Browsers.Firefox; 24 | if (chkVivaldi.Checked) b |= Browsers.Vivaldi; 25 | if (chkMsEdge.Checked) b |= Browsers.Edge; 26 | if (chkThunderbird.Checked) b |= Browsers.Thunderbird; 27 | return b; 28 | } 29 | } 30 | 31 | private void btnOk_Click(object sender, System.EventArgs e) 32 | { 33 | if (chkChrome.Checked || chkChromium.Checked || chkFirefox.Checked || chkVivaldi.Checked || chkMsEdge.Checked || chkThunderbird.Checked) 34 | { 35 | DialogResult = DialogResult.OK; 36 | Close(); 37 | } 38 | else 39 | { 40 | MessageBox.Show("You must select at least one browser to install the Native Messaging Host for.", "Invalid Selection", MessageBoxButtons.OK, MessageBoxIcon.Information); 41 | } 42 | } 43 | 44 | private void BrowserSelectForm_Load(object sender, System.EventArgs e) 45 | { 46 | var statuses = _host.GetBrowserStatuses(); 47 | 48 | foreach(var b in statuses.Keys) 49 | { 50 | CheckBox cb = null; 51 | 52 | switch (b) 53 | { 54 | case Browsers.Chrome: 55 | cb = chkChrome; 56 | break; 57 | case Browsers.Chromium: 58 | cb = chkChromium; 59 | break; 60 | case Browsers.Firefox: 61 | cb = chkFirefox; 62 | break; 63 | case Browsers.Vivaldi: 64 | cb = chkVivaldi; 65 | break; 66 | case Browsers.Edge: 67 | cb = chkMsEdge; 68 | break; 69 | case Browsers.Thunderbird: 70 | cb = chkThunderbird; 71 | break; 72 | } 73 | 74 | if (cb != null) 75 | { 76 | var status = statuses[b]; 77 | cb.Text = string.Format("{0}: {1}", b.GetDescription(), status.GetDescription()); 78 | cb.Checked = (status == BrowserStatus.Detected); 79 | } 80 | } 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /KeePassNatMsg/NativeMessaging/BrowserSelectForm.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | -------------------------------------------------------------------------------- /KeePassNatMsg/NativeMessaging/LinuxHost.cs: -------------------------------------------------------------------------------- 1 | namespace KeePassNatMsg.NativeMessaging 2 | { 3 | public class LinuxHost : PosixHost 4 | { 5 | private const string MozillaNmh = ".mozilla/native-messaging-hosts"; 6 | 7 | protected override string[] BrowserPaths 8 | { 9 | get 10 | { 11 | return new[] 12 | { 13 | string.Empty, 14 | ".config/google-chrome/NativeMessagingHosts", 15 | ".config/chromium/NativeMessagingHosts", 16 | MozillaNmh, 17 | ".config/vivaldi/NativeMessagingHosts", 18 | ".config/microsoft edge/NativeMessagingHosts", 19 | MozillaNmh, 20 | }; 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /KeePassNatMsg/NativeMessaging/MacOsxHost.cs: -------------------------------------------------------------------------------- 1 | namespace KeePassNatMsg.NativeMessaging 2 | { 3 | public class MacOsxHost : PosixHost 4 | { 5 | private const string MozillaNmh = "Library/Application Support/Mozilla/NativeMessagingHosts"; 6 | 7 | protected override string[] BrowserPaths 8 | { 9 | get 10 | { 11 | return new[] 12 | { 13 | string.Empty, 14 | "Library/Application Support/Google/Chrome/NativeMessagingHosts", 15 | "Library/Application Support/Chromium/NativeMessagingHosts", 16 | MozillaNmh, 17 | "Library/Application Support/Vivaldi/NativeMessagingHosts", 18 | "Library/Application Support/Microsoft Edge/NativeMessagingHosts", 19 | MozillaNmh, 20 | }; 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /KeePassNatMsg/NativeMessaging/NativeMessagingHost.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.IO; 5 | using System.Text; 6 | using System.Windows.Forms; 7 | 8 | namespace KeePassNatMsg.NativeMessaging 9 | { 10 | [Flags] 11 | public enum Browsers 12 | { 13 | None, 14 | 15 | [Description("Google Chrome")] 16 | Chrome, 17 | 18 | Chromium, 19 | Firefox = 4, 20 | Vivaldi = 8, 21 | 22 | [Description("Microsoft Edge")] 23 | Edge = 16, 24 | Thunderbird = 32, 25 | } 26 | 27 | public enum BrowserStatus 28 | { 29 | [Description("Not Installed")] 30 | NotInstalled, 31 | Detected, 32 | Installed 33 | } 34 | 35 | public abstract class NativeMessagingHost 36 | { 37 | protected const string NmhKey = "NativeMessagingHosts"; 38 | protected const string ProxyExecutable = "keepassnatmsg-proxy.exe"; 39 | private const string GithubRepo = "https://github.com/smorks/keepassnatmsg-proxy"; 40 | private const string ExtKeyBrowser = "org.keepassxc.keepassxc_browser"; 41 | private const string ExtKeyThunderbird = "de.kkapsner.keepassxc_mail"; 42 | 43 | protected Encoding _utf8 = new UTF8Encoding(false); 44 | 45 | public Form ParentForm { get; set; } 46 | public string ProxyExePath 47 | { 48 | get 49 | { 50 | return Path.Combine(ProxyPath, ProxyExecutable); 51 | } 52 | } 53 | 54 | protected NativeMessagingHost() 55 | { 56 | // enable TLS 1.2 57 | System.Net.ServicePointManager.SecurityProtocol = (System.Net.SecurityProtocolType)3072; 58 | } 59 | 60 | public static NativeMessagingHost GetHost() 61 | { 62 | switch (Environment.OSVersion.Platform) 63 | { 64 | case PlatformID.Win32NT: 65 | return new WindowsHost(); 66 | case PlatformID.Unix: 67 | return new LinuxHost(); 68 | case PlatformID.MacOSX: 69 | return new MacOsxHost(); 70 | default: 71 | throw new PlatformNotSupportedException(string.Format("{0} is not a supported platform.", Environment.OSVersion.Platform)); 72 | } 73 | } 74 | 75 | protected string GetJsonData(Browsers b) 76 | { 77 | switch (b) 78 | { 79 | case Browsers.Chrome: 80 | case Browsers.Chromium: 81 | case Browsers.Vivaldi: 82 | return Properties.Resources.chrome_json; 83 | case Browsers.Firefox: 84 | return Properties.Resources.firefox_json; 85 | case Browsers.Edge: 86 | return Properties.Resources.edge_json; 87 | case Browsers.Thunderbird: 88 | return Properties.Resources.thunderbird_json; 89 | } 90 | return null; 91 | } 92 | 93 | public Version GetProxyVersion() 94 | { 95 | if (File.Exists(ProxyExePath)) 96 | { 97 | var fvi = System.Diagnostics.FileVersionInfo.GetVersionInfo(ProxyExePath); 98 | Version exeVer; 99 | if (Version.TryParse(fvi.FileVersion, out exeVer)) 100 | { 101 | return exeVer; 102 | } 103 | } 104 | return null; 105 | } 106 | 107 | public Version GetLatestProxyVersion() 108 | { 109 | var web = new System.Net.WebClient(); 110 | var latestVersion = web.DownloadString(string.Format("{0}/raw/master/version.txt", GithubRepo)); 111 | Version lv; 112 | if (Version.TryParse(latestVersion, out lv)) 113 | { 114 | return lv; 115 | } 116 | return null; 117 | } 118 | 119 | public bool UpdateProxy() 120 | { 121 | try 122 | { 123 | var latestVersion = GetLatestProxyVersion(); 124 | var exeVer = GetProxyVersion(); 125 | var newVersion = exeVer == null ? true : latestVersion > exeVer; 126 | 127 | if (newVersion) 128 | { 129 | var web = new System.Net.WebClient(); 130 | web.DownloadFile(string.Format("{0}/releases/download/v{1}/{2}", GithubRepo, latestVersion, ProxyExecutable), ProxyExePath); 131 | } 132 | 133 | return true; 134 | } 135 | catch (Exception ex) 136 | { 137 | MessageBox.Show(ParentForm, "An error occurred attempting to download the proxy application: " + ex.ToString(), "Proxy Download Error", MessageBoxButtons.OK, MessageBoxIcon.Error); 138 | } 139 | return false; 140 | } 141 | 142 | protected string GetExtKey(Browsers b) 143 | { 144 | switch(b) 145 | { 146 | case Browsers.Thunderbird: 147 | return ExtKeyThunderbird; 148 | default: 149 | return ExtKeyBrowser; 150 | } 151 | } 152 | 153 | public abstract string ProxyPath { get; } 154 | public abstract void Install(Browsers browsers); 155 | public abstract Dictionary GetBrowserStatuses(); 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /KeePassNatMsg/NativeMessaging/PosixHost.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | 5 | namespace KeePassNatMsg.NativeMessaging 6 | { 7 | public abstract class PosixHost : NativeMessagingHost 8 | { 9 | private const string PosixScript = "#!/bin/bash\nmono {0}\n"; 10 | private const string PosixProxyPath = ".keepassnatmsg"; 11 | 12 | private string _home = Environment.GetEnvironmentVariable("HOME"); 13 | 14 | public override string ProxyPath 15 | { 16 | get 17 | { 18 | return Path.Combine(_home, PosixProxyPath); 19 | } 20 | } 21 | 22 | protected abstract string[] BrowserPaths { get; } 23 | 24 | public override void Install(Browsers browsers) 25 | { 26 | InstallPosix(browsers); 27 | } 28 | 29 | public override Dictionary GetBrowserStatuses() 30 | { 31 | var statuses = new Dictionary(); 32 | var i = 0; 33 | 34 | foreach (Browsers b in Enum.GetValues(typeof(Browsers))) 35 | { 36 | if (b != Browsers.None) 37 | { 38 | var status = BrowserStatus.NotInstalled; 39 | var jsonFile = Path.Combine(_home, BrowserPaths[i], string.Format("{0}.json", GetExtKey(b))); 40 | var jsonDir = Path.GetDirectoryName(jsonFile); 41 | var jsonDirInfo = new DirectoryInfo(jsonDir); 42 | var jsonParent = jsonDirInfo.Parent.FullName; 43 | 44 | if (Directory.Exists(jsonParent)) 45 | { 46 | status = BrowserStatus.Detected; 47 | } 48 | 49 | if (File.Exists(jsonFile)) 50 | { 51 | status = BrowserStatus.Installed; 52 | } 53 | statuses.Add(b, status); 54 | } 55 | i++; 56 | } 57 | 58 | return statuses; 59 | } 60 | 61 | protected void InstallPosix(Browsers browsers) 62 | { 63 | if (!Directory.Exists(ProxyPath)) 64 | { 65 | Directory.CreateDirectory(ProxyPath); 66 | } 67 | var monoScript = Path.Combine(ProxyPath, "run-proxy.sh"); 68 | File.WriteAllText(monoScript, string.Format(PosixScript, ProxyExecutable), _utf8); 69 | 70 | Mono.Unix.Native.Stat st; 71 | Mono.Unix.Native.Syscall.stat(monoScript, out st); 72 | if (!st.st_mode.HasFlag(Mono.Unix.Native.FilePermissions.S_IXUSR)) 73 | { 74 | Mono.Unix.Native.Syscall.chmod(monoScript, Mono.Unix.Native.FilePermissions.S_IXUSR | st.st_mode); 75 | } 76 | 77 | var i = 0; 78 | 79 | foreach (Browsers b in Enum.GetValues(typeof(Browsers))) 80 | { 81 | if (b != Browsers.None && browsers.HasFlag(b)) 82 | { 83 | var jsonFile = Path.Combine(_home, BrowserPaths[i], string.Format("{0}.json", GetExtKey(b))); 84 | var jsonDir = Path.GetDirectoryName(jsonFile); 85 | 86 | var jsonDirInfo = new DirectoryInfo(jsonDir); 87 | var jsonParent = jsonDirInfo.Parent.FullName; 88 | 89 | if (Directory.Exists(jsonParent)) 90 | { 91 | if (!Directory.Exists(jsonDir)) 92 | { 93 | Directory.CreateDirectory(jsonDir); 94 | } 95 | File.WriteAllText(jsonFile, string.Format(GetJsonData(b), monoScript), _utf8); 96 | } 97 | } 98 | i++; 99 | } 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /KeePassNatMsg/NativeMessaging/WindowsHost.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Windows.Forms; 5 | 6 | namespace KeePassNatMsg.NativeMessaging 7 | { 8 | public class WindowsHost : NativeMessagingHost 9 | { 10 | private const string MozillaNmhKey= "Software\\Mozilla"; 11 | 12 | private readonly string[] RegKeys = new[] { 13 | string.Empty, 14 | "Software\\Google\\Chrome", 15 | "Software\\Chromium", 16 | MozillaNmhKey, 17 | "Software\\Vivaldi", 18 | "Software\\Microsoft\\Edge", 19 | MozillaNmhKey, 20 | }; 21 | 22 | public override string ProxyPath 23 | { 24 | get 25 | { 26 | return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "KeePassNatMsg"); 27 | } 28 | } 29 | 30 | public override void Install(Browsers browsers) 31 | { 32 | var i = 0; 33 | foreach(Browsers b in Enum.GetValues(typeof(Browsers))) 34 | { 35 | if (b != Browsers.None && browsers.HasFlag(b)) 36 | { 37 | var key = Microsoft.Win32.Registry.CurrentUser.CreateSubKey(RegKeys[i]); 38 | if (key != null) 39 | { 40 | var nmhKey = key.CreateSubKey(string.Format("{0}\\{1}", NmhKey, GetExtKey(b))); 41 | if (nmhKey != null) 42 | { 43 | CreateRegKeyAndFile(b, nmhKey); 44 | nmhKey.Close(); 45 | } 46 | key.Close(); 47 | } 48 | } 49 | i++; 50 | } 51 | } 52 | 53 | public override Dictionary GetBrowserStatuses() 54 | { 55 | var statuses = new Dictionary(); 56 | var i = 0; 57 | foreach (Browsers b in Enum.GetValues(typeof(Browsers))) 58 | { 59 | if (b != Browsers.None) 60 | { 61 | var status = BrowserStatus.NotInstalled; 62 | var key = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(RegKeys[i], false); 63 | if (key != null) 64 | { 65 | status = BrowserStatus.Detected; 66 | var nmhKey = key.OpenSubKey(string.Format("{0}\\{1}", NmhKey, GetExtKey(b)), false); 67 | if (nmhKey != null) 68 | { 69 | var jsonFile = (string)nmhKey.GetValue(string.Empty, string.Empty); 70 | if (!string.IsNullOrEmpty(jsonFile) && File.Exists(jsonFile)) 71 | { 72 | status = BrowserStatus.Installed; 73 | } 74 | nmhKey.Close(); 75 | } 76 | key.Close(); 77 | } 78 | statuses.Add(b, status); 79 | } 80 | i++; 81 | } 82 | return statuses; 83 | } 84 | 85 | private void CreateRegKeyAndFile(Browsers b, Microsoft.Win32.RegistryKey key) 86 | { 87 | try 88 | { 89 | var jsonFile = Path.Combine(ProxyPath, string.Format("kpnm_{0}.json", b.ToString().ToLower())); 90 | key.SetValue(string.Empty, jsonFile, Microsoft.Win32.RegistryValueKind.String); 91 | if (!Directory.Exists(ProxyPath)) 92 | { 93 | Directory.CreateDirectory(ProxyPath); 94 | } 95 | File.WriteAllText(jsonFile, string.Format(GetJsonData(b), ProxyExecutable), _utf8); 96 | } 97 | catch (Exception ex) 98 | { 99 | MessageBox.Show(ParentForm, "An error occurred attempting to install the native messaging host for KeePassNatMsg: " + ex.ToString(), "Install Error", MessageBoxButtons.OK, MessageBoxIcon.Error); 100 | } 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /KeePassNatMsg/Options/DatabaseItem.cs: -------------------------------------------------------------------------------- 1 | namespace KeePassNatMsg.Options 2 | { 3 | class DatabaseItem 4 | { 5 | public string Id { get; set; } 6 | public string DbHash { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /KeePassNatMsg/Options/DatabaseKeyItem.cs: -------------------------------------------------------------------------------- 1 | namespace KeePassNatMsg.Options 2 | { 3 | class DatabaseKeyItem 4 | { 5 | public string Name { get; set; } 6 | public string Key { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /KeePassNatMsg/Options/OptionsForm.cs: -------------------------------------------------------------------------------- 1 | using KeePassLib; 2 | using KeePassNatMsg.NativeMessaging; 3 | using KeePassNatMsg.Utils; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Threading.Tasks; 8 | using System.Windows.Forms; 9 | 10 | namespace KeePassNatMsg.Options 11 | { 12 | public partial class OptionsForm : Form 13 | { 14 | readonly ConfigOpt _config; 15 | private bool _restartRequired = false; 16 | private readonly NativeMessagingHost _host; 17 | 18 | private string AssemblyVersion 19 | { 20 | get 21 | { 22 | try 23 | { 24 | return System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString(); 25 | } 26 | catch { } 27 | 28 | return "unknown"; 29 | } 30 | } 31 | 32 | public OptionsForm(ConfigOpt config) 33 | { 34 | _host = NativeMessagingHost.GetHost(); 35 | _config = config; 36 | InitializeComponent(); 37 | lblVersion.Text = string.Format("KeePassNatMsg v{0}", AssemblyVersion); 38 | } 39 | 40 | private void OptionsForm_Load(object sender, EventArgs e) 41 | { 42 | credNotifyCheckbox.Checked = _config.ReceiveCredentialNotification; 43 | credMatchingCheckbox.Checked = _config.SpecificMatchingOnly; 44 | unlockDatabaseCheckbox.Checked = _config.UnlockDatabaseRequest; 45 | credAllowAccessCheckbox.Checked = _config.AlwaysAllowAccess; 46 | credAllowUpdatesCheckbox.Checked = _config.AlwaysAllowUpdates; 47 | if (_config.SearchInAllOpenedDatabases) 48 | { 49 | // Only for backward compatibility 50 | credSearchInAllOpenedDatabasesRadioButton.Checked = true; 51 | _config.SearchInAllOpenedDatabases = false; 52 | } 53 | else 54 | { 55 | credOnlySearchInSelectedDatabaseRadioButton.Checked = (_config.AllowSearchDatabase == (ulong)AllowSearchDatabase.SearchInOnlySelectedDatabase); 56 | credSearchInAllOpenedDatabasesRadioButton.Checked = (_config.AllowSearchDatabase == (ulong)AllowSearchDatabase.SearchInAllOpenedDatabases); 57 | credRestrictSearchInSpecificDatabaseRadioButton.Checked = (_config.AllowSearchDatabase == (ulong)AllowSearchDatabase.RestrictSearchInSpecificDatabase); 58 | } 59 | comboBoxSearchDatabases.Enabled = credRestrictSearchInSpecificDatabaseRadioButton.Checked; 60 | hideExpiredCheckbox.Checked = _config.HideExpired; 61 | matchSchemesCheckbox.Checked = _config.MatchSchemes; 62 | returnStringFieldsCheckbox.Checked = _config.ReturnStringFields; 63 | returnStringFieldsWithKphOnlyCheckBox.Checked = _config.ReturnStringFieldsWithKphOnly; 64 | SortByUsernameRadioButton.Checked = _config.SortResultByUsername; 65 | SortByTitleRadioButton.Checked = !_config.SortResultByUsername; 66 | txtKPXCVerOverride.Text = _config.OverrideKeePassXcVersion; 67 | chkSearchUrls.Checked = _config.SearchUrls; 68 | chkUseKpxcSettingsKey.Checked = _config.UseKeePassXcSettings; 69 | chkUseLegacyHostMatching.Checked = _config.UseLegacyHostMatching; 70 | 71 | this.returnStringFieldsCheckbox_CheckedChanged(null, EventArgs.Empty); 72 | 73 | InitDatabasesDropdown(); 74 | foreach (DatabaseItem item in comboBoxSearchDatabases.Items) 75 | { 76 | if (item.DbHash == _config.SearchDatabaseHash) 77 | { 78 | comboBoxSearchDatabases.SelectedItem = item; 79 | } 80 | } 81 | foreach (DatabaseItem item in comboBoxDatabases.Items) 82 | { 83 | if (item.DbHash == _config.ConnectionDatabaseHash) 84 | { 85 | comboBoxDatabases.SelectedItem = item; 86 | } 87 | } 88 | } 89 | 90 | private void okButton_Click(object sender, EventArgs e) 91 | { 92 | _config.ReceiveCredentialNotification = credNotifyCheckbox.Checked; 93 | _config.SpecificMatchingOnly = credMatchingCheckbox.Checked; 94 | _config.UnlockDatabaseRequest = unlockDatabaseCheckbox.Checked; 95 | _config.AlwaysAllowAccess = credAllowAccessCheckbox.Checked; 96 | _config.AlwaysAllowUpdates = credAllowUpdatesCheckbox.Checked; 97 | _config.SearchDatabaseHash = (comboBoxSearchDatabases.SelectedItem as DatabaseItem) == null ? null : (comboBoxSearchDatabases.SelectedItem as DatabaseItem).DbHash; 98 | _config.HideExpired = hideExpiredCheckbox.Checked; 99 | _config.MatchSchemes = matchSchemesCheckbox.Checked; 100 | _config.ReturnStringFields = returnStringFieldsCheckbox.Checked; 101 | _config.ReturnStringFieldsWithKphOnly = returnStringFieldsWithKphOnlyCheckBox.Checked; 102 | _config.SortResultByUsername = SortByUsernameRadioButton.Checked; 103 | _config.OverrideKeePassXcVersion = txtKPXCVerOverride.Text; 104 | _config.ConnectionDatabaseHash = (comboBoxDatabases.SelectedItem as DatabaseItem) == null ? null : (comboBoxDatabases.SelectedItem as DatabaseItem).DbHash; 105 | _config.SearchUrls = chkSearchUrls.Checked; 106 | _config.UseLegacyHostMatching = chkUseLegacyHostMatching.Checked; 107 | 108 | if (_config.UseKeePassXcSettings != chkUseKpxcSettingsKey.Checked) 109 | { 110 | MigrateSettings(true); 111 | } 112 | 113 | _config.UseKeePassXcSettings = chkUseKpxcSettingsKey.Checked; 114 | 115 | if (_restartRequired) 116 | { 117 | MessageBox.Show( 118 | "You have successfully changed the port number and/or the host name.\nA restart of KeePass is required!\n\nPlease restart KeePass now.", 119 | "Restart required!", 120 | MessageBoxButtons.OK, 121 | MessageBoxIcon.Information 122 | ); 123 | } 124 | 125 | DialogResult = DialogResult.OK; 126 | Close(); 127 | } 128 | 129 | private void cancelButton_Click(object sender, EventArgs e) 130 | { 131 | DialogResult = DialogResult.Cancel; 132 | Close(); 133 | } 134 | 135 | private void removePermissionsButton_Click(object sender, EventArgs e) 136 | { 137 | if (KeePass.Program.MainForm.DocumentManager.ActiveDatabase.IsOpen) 138 | { 139 | PwDatabase db = KeePass.Program.MainForm.DocumentManager.ActiveDatabase; 140 | 141 | uint counter = 0; 142 | var entries = db.RootGroup.GetEntries(true); 143 | 144 | if (entries.Count() > 999) 145 | { 146 | MessageBox.Show( 147 | String.Format("{0} entries detected!\nSearching and removing permissions could take some while.\n\nWe will inform you when the process has been finished.", entries.Count().ToString()), 148 | String.Format("{0} entries detected", entries.Count().ToString()), 149 | MessageBoxButtons.OK, 150 | MessageBoxIcon.Information 151 | ); 152 | } 153 | 154 | foreach (var entry in entries) 155 | { 156 | foreach (var str in entry.CustomData) 157 | { 158 | if (str.Key.Equals(KeePassNatMsgExt.SettingKey)) 159 | { 160 | entry.History = entry.History.CloneDeep(); 161 | entry.CreateBackup(null); 162 | entry.CustomData.Remove(str.Key); 163 | entry.Touch(true); 164 | 165 | counter++; 166 | 167 | break; 168 | } 169 | } 170 | } 171 | 172 | if (counter > 0) 173 | { 174 | KeePass.Program.MainForm.UpdateUI(false, null, true, db.RootGroup, true, null, true); 175 | MessageBox.Show( 176 | String.Format("Successfully removed permissions from {0} entr{1}.", counter.ToString(), counter == 1 ? "y" : "ies"), 177 | String.Format("Removed permissions from {0} entr{1}", counter.ToString(), counter == 1 ? "y" : "ies"), 178 | MessageBoxButtons.OK, 179 | MessageBoxIcon.Information 180 | ); 181 | } 182 | else 183 | { 184 | MessageBox.Show( 185 | "The active database does not contain an entry with permissions.", 186 | "No entry with permissions found!", 187 | MessageBoxButtons.OK, 188 | MessageBoxIcon.Information 189 | ); 190 | } 191 | } 192 | else 193 | { 194 | MessageBox.Show("The active database is locked!\nPlease unlock the selected database or choose another one which is unlocked.", "Database locked!", MessageBoxButtons.OK, MessageBoxIcon.Error); 195 | } 196 | } 197 | 198 | private void returnStringFieldsCheckbox_CheckedChanged(object sender, EventArgs e) 199 | { 200 | this.returnStringFieldsWithKphOnlyCheckBox.Enabled = this.returnStringFieldsCheckbox.Checked; 201 | } 202 | 203 | private void btnInstallNativeMessaging_Click(object sender, EventArgs e) 204 | { 205 | var bsf = new BrowserSelectForm(_host); 206 | 207 | if (bsf.ShowDialog(this) == DialogResult.OK) 208 | { 209 | var t = new Task(() => 210 | { 211 | _host.Install(bsf.SelectedBrowsers); 212 | _host.UpdateProxy(); 213 | GetNativeMessagingStatus(); 214 | Invoke(new Action(() => MessageBox.Show(this, "The native messaging host installed completed successfully.", "Install Complete", MessageBoxButtons.OK, MessageBoxIcon.Information))); 215 | }); 216 | t.Start(); 217 | } 218 | } 219 | 220 | private void CheckNativeMessagingHost() 221 | { 222 | var t = new Task(() => _host.GetBrowserStatuses().Any(bs => bs.Value == BrowserStatus.Installed)); 223 | 224 | var t2 = t.ContinueWith((ti) => 225 | { 226 | if (ti.IsCompleted && !ti.Result) 227 | { 228 | Invoke(new Action(() => PromptInstall())); 229 | } 230 | GetNativeMessagingStatus(); 231 | }); 232 | 233 | SetProxyVersionText("Loading Native Messaging Status..."); 234 | 235 | t.Start(); 236 | } 237 | 238 | private void PromptInstall() 239 | { 240 | var nmiInstall = MessageBox.Show(this, "The native messaging host was not detected. It must be installed for KeePassNatMsg to work. Do you want to install it now?", "Native Messaging Host Not Detected", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2); 241 | if (nmiInstall == DialogResult.Yes) 242 | { 243 | var bsf = new BrowserSelectForm(_host); 244 | if (bsf.ShowDialog(this) == DialogResult.OK) 245 | { 246 | _host.Install(bsf.SelectedBrowsers); 247 | _host.UpdateProxy(); 248 | MessageBox.Show(this, "The native messaging host installed completed successfully.", "Install Complete", MessageBoxButtons.OK, MessageBoxIcon.Information); 249 | } 250 | } 251 | } 252 | 253 | private void OptionsForm_Shown(object sender, EventArgs e) 254 | { 255 | CheckNativeMessagingHost(); 256 | } 257 | 258 | private void GetNativeMessagingStatus() 259 | { 260 | var statuses = _host.GetBrowserStatuses(); 261 | var lst = new List(); 262 | 263 | foreach (var b in statuses.Keys) 264 | { 265 | lst.Add(string.Format("{0}: {1}", b.GetDescription(), statuses[b].GetDescription())); 266 | } 267 | 268 | var latestVersion = _host.GetLatestProxyVersion(); 269 | var proxyVersion = _host.GetProxyVersion(); 270 | var proxyDisplay = proxyVersion == null ? "Not Installed" : proxyVersion.ToString(); 271 | var latestVersionDisplay = string.Empty; 272 | 273 | if (proxyVersion != null && latestVersion != null) 274 | { 275 | if (latestVersion > proxyVersion) 276 | { 277 | latestVersionDisplay = " New Version Available: " + latestVersion; 278 | } 279 | else 280 | { 281 | latestVersionDisplay = " (Up To Date)"; 282 | } 283 | } 284 | 285 | lst.Add(string.Format("Proxy: {0}{1}", proxyDisplay, latestVersionDisplay)); 286 | 287 | SetProxyVersionText(string.Join(Environment.NewLine, lst)); 288 | } 289 | 290 | private void SetProxyVersionText(string text) 291 | { 292 | if (lblProxyVersion.InvokeRequired) 293 | { 294 | lblProxyVersion.Invoke(new Action((x) => SetProxyVersionText(x)), text); 295 | } 296 | else 297 | { 298 | lblProxyVersion.Text = text; 299 | } 300 | } 301 | 302 | private void InitDatabasesDropdown() 303 | { 304 | foreach (var item in KeePass.Program.MainForm.DocumentManager.Documents) 305 | { 306 | if (!item.Database.IsOpen) 307 | continue; 308 | 309 | var dbIdentifier = item.Database.Name; 310 | if (string.IsNullOrEmpty(dbIdentifier)) 311 | { 312 | dbIdentifier = item.Database.IOConnectionInfo.Path; 313 | } 314 | 315 | comboBoxSearchDatabases.Items.Add(new DatabaseItem { Id = dbIdentifier, DbHash = KeePassNatMsgExt.ExtInstance.GetDbHash(item.Database) }); 316 | comboBoxDatabases.Items.Add(new DatabaseItem { Id = dbIdentifier, DbHash = KeePassNatMsgExt.ExtInstance.GetDbHash(item.Database) }); 317 | } 318 | } 319 | 320 | private void LoadDatabaseKeys() 321 | { 322 | LoadDatabaseKeys(KeePass.Program.MainForm.DocumentManager.ActiveDatabase); 323 | } 324 | 325 | private void LoadDatabaseKeys(PwDatabase db) 326 | { 327 | if (db.IsOpen) 328 | { 329 | var keys = new List(); 330 | var dbKey = KeePassNatMsgExt.GetDbKey(_config.UseKeePassXcSettings); 331 | 332 | foreach (var cd in db.CustomData) 333 | { 334 | if (cd.Key.StartsWith(dbKey)) 335 | { 336 | var keyName = cd.Key.Substring(dbKey.Length); 337 | keys.Add(new DatabaseKeyItem { Name = keyName, Key = cd.Value }); 338 | } 339 | } 340 | 341 | dgvKeys.DataSource = keys; 342 | } 343 | } 344 | 345 | private void tabControl1_Selected(object sender, TabControlEventArgs e) 346 | { 347 | if (e.TabPage == tabPage3) 348 | { 349 | LoadDatabaseKeys(); 350 | } 351 | } 352 | 353 | private void btnRemoveSelectedKeys_Click(object sender, EventArgs e) 354 | { 355 | var db = KeePass.Program.MainForm.DocumentManager.ActiveDatabase; 356 | 357 | if (db.IsOpen) 358 | { 359 | var dbKey = KeePassNatMsgExt.GetDbKey(_config.UseKeePassXcSettings); 360 | 361 | var items = dgvKeys.SelectedRows 362 | .OfType() 363 | .Select(x => dbKey + ((x.DataBoundItem as DatabaseKeyItem) == null ? string.Empty : (x.DataBoundItem as DatabaseKeyItem).Name)); 364 | 365 | var deleteKeys = db.CustomData 366 | .Where(x => items.Contains(x.Key)) 367 | .Select(x => x.Key).ToList(); 368 | 369 | RemoveKeys(deleteKeys, db); 370 | } 371 | } 372 | 373 | private void btnRemoveAllKeys_Click(object sender, EventArgs e) 374 | { 375 | var db = KeePass.Program.MainForm.DocumentManager.ActiveDatabase; 376 | 377 | if (db.IsOpen) 378 | { 379 | var dbKey = KeePassNatMsgExt.GetDbKey(_config.UseKeePassXcSettings); 380 | 381 | var deleteKeys = db.CustomData 382 | .Where(x => x.Key.StartsWith(dbKey)) 383 | .Select(x => x.Key).ToList(); 384 | 385 | RemoveKeys(deleteKeys, db); 386 | } 387 | else 388 | { 389 | MessageBox.Show("The active database is locked!\nPlease unlock the selected database or choose another one which is unlocked.", "Database locked!", MessageBoxButtons.OK, MessageBoxIcon.Error); 390 | } 391 | } 392 | 393 | private void RemoveKeys(List keys, PwDatabase db) 394 | { 395 | if (keys.Count > 0) 396 | { 397 | foreach (var key in keys) 398 | { 399 | db.CustomData.Remove(key); 400 | } 401 | 402 | LoadDatabaseKeys(db); 403 | 404 | KeePass.Program.MainForm.UpdateUI(false, null, true, db.RootGroup, true, null, true); 405 | MessageBox.Show( 406 | string.Format("Successfully removed {0} encryption-key{1} from KeePassNatMsg Settings.", keys.Count, keys.Count == 1 ? "" : "s"), 407 | string.Format("Removed {0} key{1} from database", keys.Count, keys.Count == 1 ? "" : "s"), 408 | MessageBoxButtons.OK, 409 | MessageBoxIcon.Information 410 | ); 411 | } 412 | else 413 | { 414 | MessageBox.Show( 415 | "No shared encryption-keys found in KeePassNatMsg Settings.", "No keys found", 416 | MessageBoxButtons.OK, 417 | MessageBoxIcon.Information 418 | ); 419 | } 420 | } 421 | 422 | private void btnCheckForLegacyConfig_Click(object sender, EventArgs e) 423 | { 424 | var ext = KeePassNatMsgExt.ExtInstance; 425 | var db = KeePass.Program.MainForm.DocumentManager.ActiveDatabase; 426 | 427 | if (!db.IsOpen) 428 | { 429 | MessageBox.Show(this, "The active database is not open, config cannot be migrated.", "Active Database Not Open"); 430 | return; 431 | } 432 | 433 | if (ext.HasLegacyConfig(db)) 434 | { 435 | ext.PromptToMigrate(db); 436 | } 437 | else 438 | { 439 | MessageBox.Show(this, "Legacy Configuration was not found, or the config has already been migrated for the active database.", "Legacy Config Not Found"); 440 | } 441 | } 442 | 443 | private void btnMigrateSettings_Click(object sender, EventArgs e) 444 | { 445 | MigrateSettings(false); 446 | } 447 | 448 | private bool MigrateSettings(bool quiet) 449 | { 450 | var ext = KeePassNatMsgExt.ExtInstance; 451 | var db = KeePass.Program.MainForm.DocumentManager.ActiveDatabase; 452 | 453 | if (!db.IsOpen) 454 | { 455 | if (!quiet) 456 | MessageBox.Show(this, "The active database is not open, config cannot be migrated.", "Active Database Not Open"); 457 | return false; 458 | } 459 | 460 | var fromKpnm = chkUseKpxcSettingsKey.Checked; 461 | var from = fromKpnm ? "KeePassNatMsg" : "KeePassXC"; 462 | var to = fromKpnm ? "KeePassXC" : "KeePassNatMsg"; 463 | 464 | if (ext.HasConfig(db, fromKpnm)) 465 | { 466 | var result = DialogResult.Yes; 467 | 468 | if (!quiet) 469 | { 470 | result = MessageBox.Show( 471 | this, 472 | string.Format("CAUTION: This will move all {0} Settings to {1}. Any existing {1} settings will be overwritten. You should create a backup of the database before proceeding. Are you sure you want to migrate settings from {0} to {1}?", from, to), 473 | "Confirm Migrate Settings", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2); 474 | } 475 | 476 | if (result == DialogResult.Yes) 477 | { 478 | UseWaitCursor = true; 479 | ext.MoveConfig(db, fromKpnm); 480 | UseWaitCursor = false; 481 | } 482 | } 483 | else 484 | { 485 | if (!quiet) 486 | MessageBox.Show(this, string.Format("No {0} Settings found.", from), "No Settings to be Migrated"); 487 | 488 | return false; 489 | } 490 | 491 | return true; 492 | } 493 | 494 | private void rbSearchDatabase_CheckedChanged(object sender, EventArgs e) 495 | { 496 | if (credOnlySearchInSelectedDatabaseRadioButton.Checked) 497 | _config.AllowSearchDatabase = (ulong)AllowSearchDatabase.SearchInOnlySelectedDatabase; 498 | else if (credSearchInAllOpenedDatabasesRadioButton.Checked) 499 | _config.AllowSearchDatabase = (ulong)AllowSearchDatabase.SearchInAllOpenedDatabases; 500 | else 501 | _config.AllowSearchDatabase = (ulong)AllowSearchDatabase.RestrictSearchInSpecificDatabase; 502 | 503 | this.comboBoxSearchDatabases.Enabled = this.credRestrictSearchInSpecificDatabaseRadioButton.Checked; 504 | } 505 | } 506 | } 507 | -------------------------------------------------------------------------------- /KeePassNatMsg/Options/OptionsForm.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | True 122 | 123 | 124 | True 125 | 126 | -------------------------------------------------------------------------------- /KeePassNatMsg/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("KeePassNatMsg")] 9 | [assembly: AssemblyDescription("A plugin to expose a secure interface to your KeePass database")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Andy Brandt")] 12 | [assembly: AssemblyProduct("KeePass Plugin")] 13 | [assembly: AssemblyCopyright("Copyright © Andy Brandt 2021")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("5A642578-DFA7-4F75-8483-729B7C32F8E6")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | [assembly: AssemblyVersion("2.0.13")] -------------------------------------------------------------------------------- /KeePassNatMsg/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace KeePassNatMsg.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("KeePassNatMsg.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | 63 | /// 64 | /// Looks up a localized string similar to {{ 65 | /// "name": "org.keepassxc.keepassxc_browser", 66 | /// "description": "KeepassXC integration with Native Messaging support", 67 | /// "path" : "{0}", 68 | /// "type": "stdio", 69 | /// "allowed_origins": [ 70 | /// "chrome-extension://iopaggbpplllidnfmcghoonnokmjoicf/", 71 | /// "chrome-extension://oboonakemofpalcgghocfoadofidjkkk/", 72 | /// "chrome-extension://pdffhmdngciaglkoonimfcmckehcpafo/" 73 | /// ] 74 | ///}}. 75 | /// 76 | internal static string chrome_json { 77 | get { 78 | return ResourceManager.GetString("chrome_json", resourceCulture); 79 | } 80 | } 81 | 82 | /// 83 | /// Looks up a localized resource of type System.Drawing.Bitmap. 84 | /// 85 | internal static System.Drawing.Bitmap earth_lock { 86 | get { 87 | object obj = ResourceManager.GetObject("earth_lock", resourceCulture); 88 | return ((System.Drawing.Bitmap)(obj)); 89 | } 90 | } 91 | 92 | /// 93 | /// Looks up a localized string similar to {{ 94 | /// "name": "org.keepassxc.keepassxc_browser", 95 | /// "description": "KeepassXC integration with Native Messaging support", 96 | /// "path" : "{0}", 97 | /// "type": "stdio", 98 | /// "allowed_origins": [ 99 | /// "chrome-extension://iopaggbpplllidnfmcghoonnokmjoicf/", 100 | /// "chrome-extension://oboonakemofpalcgghocfoadofidjkkk/", 101 | /// "chrome-extension://pdffhmdngciaglkoonimfcmckehcpafo/" 102 | /// ] 103 | ///}}. 104 | /// 105 | internal static string edge_json { 106 | get { 107 | return ResourceManager.GetString("edge_json", resourceCulture); 108 | } 109 | } 110 | 111 | /// 112 | /// Looks up a localized string similar to {{ 113 | /// "name": "org.keepassxc.keepassxc_browser", 114 | /// "description": "KeepassXC integration with Firefox with Native Messaging support", 115 | /// "path" : "{0}", 116 | /// "type": "stdio", 117 | /// "allowed_extensions": [ 118 | /// "keepassxc-browser@keepassxc.org" 119 | /// ] 120 | ///}}. 121 | /// 122 | internal static string firefox_json { 123 | get { 124 | return ResourceManager.GetString("firefox_json", resourceCulture); 125 | } 126 | } 127 | 128 | /// 129 | /// Looks up a localized string similar to {{ 130 | /// "name": "de.kkapsner.keepassxc_mail", 131 | /// "description": "KeepassXC integration with Thunderbird with Native Messaging support", 132 | /// "path" : "{0}", 133 | /// "type": "stdio", 134 | /// "allowed_extensions": [ 135 | /// "keepassxc-mail@kkapsner.de" 136 | /// ] 137 | ///}}. 138 | /// 139 | internal static string thunderbird_json { 140 | get { 141 | return ResourceManager.GetString("thunderbird_json", resourceCulture); 142 | } 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /KeePassNatMsg/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | {{ 122 | "name": "org.keepassxc.keepassxc_browser", 123 | "description": "KeepassXC integration with Native Messaging support", 124 | "path" : "{0}", 125 | "type": "stdio", 126 | "allowed_origins": [ 127 | "chrome-extension://dphoaaiomekdhacmfoblfblmncpnbahm/", 128 | "chrome-extension://iopaggbpplllidnfmcghoonnokmjoicf/", 129 | "chrome-extension://oboonakemofpalcgghocfoadofidjkkk/", 130 | "chrome-extension://pdffhmdngciaglkoonimfcmckehcpafo/" 131 | ] 132 | }} 133 | 134 | 135 | 136 | ..\Resources\earth_lock.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 137 | 138 | 139 | {{ 140 | "name": "org.keepassxc.keepassxc_browser", 141 | "description": "KeepassXC integration with Native Messaging support", 142 | "path" : "{0}", 143 | "type": "stdio", 144 | "allowed_origins": [ 145 | "chrome-extension://dphoaaiomekdhacmfoblfblmncpnbahm/", 146 | "chrome-extension://iopaggbpplllidnfmcghoonnokmjoicf/", 147 | "chrome-extension://oboonakemofpalcgghocfoadofidjkkk/", 148 | "chrome-extension://pdffhmdngciaglkoonimfcmckehcpafo/" 149 | ] 150 | }} 151 | 152 | 153 | {{ 154 | "name": "org.keepassxc.keepassxc_browser", 155 | "description": "KeepassXC integration with Firefox with Native Messaging support", 156 | "path" : "{0}", 157 | "type": "stdio", 158 | "allowed_extensions": [ 159 | "keepassxc-browser@keepassxc.org" 160 | ] 161 | }} 162 | 163 | 164 | {{ 165 | "name": "de.kkapsner.keepassxc_mail", 166 | "description": "KeepassXC integration with Thunderbird with Native Messaging support", 167 | "path" : "{0}", 168 | "type": "stdio", 169 | "allowed_extensions": [ 170 | "keepassxc-mail@kkapsner.de" 171 | ] 172 | }} 173 | 174 | 175 | -------------------------------------------------------------------------------- /KeePassNatMsg/Protocol/Action/ErrorResponse.cs: -------------------------------------------------------------------------------- 1 | namespace KeePassNatMsg.Protocol.Action 2 | { 3 | public class ErrorResponse : Response 4 | { 5 | public ErrorResponse(Request req, ErrorType error) : base(req, false) 6 | { 7 | Remove("nonce"); 8 | Add("errorCode", (int)error); 9 | Add("error", Errors.GetErrorMessage(error)); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /KeePassNatMsg/Protocol/Action/JsonBase.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json.Linq; 2 | 3 | namespace KeePassNatMsg.Protocol.Action 4 | { 5 | public class JsonBase : JObject 6 | { 7 | public JsonBase() : base() 8 | { 9 | } 10 | 11 | public JsonBase(JObject obj) : base(obj) 12 | { 13 | } 14 | 15 | public void AddBytes(string key, byte[] data) 16 | { 17 | Add(key, System.Convert.ToBase64String(data)); 18 | } 19 | 20 | public string GetString(string key) 21 | { 22 | var value = this[key] as JValue; 23 | return value == null ? null : value.Value as string; 24 | } 25 | 26 | public byte[] GetBytes(string key) 27 | { 28 | return System.Convert.FromBase64String(GetString(key)); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /KeePassNatMsg/Protocol/Action/Request.cs: -------------------------------------------------------------------------------- 1 |  2 | using Newtonsoft.Json; 3 | using Newtonsoft.Json.Linq; 4 | using System; 5 | 6 | namespace KeePassNatMsg.Protocol.Action 7 | { 8 | public class Request : JsonBase 9 | { 10 | private JsonBase _msg; 11 | 12 | public Request(JObject obj) : base(obj) 13 | { 14 | } 15 | 16 | public static Request ReadFromStream(System.IO.Stream s) 17 | { 18 | var reader = new JsonTextReader(new System.IO.StreamReader(s)); 19 | return new Request((JObject)ReadFrom(reader)); 20 | } 21 | 22 | public static Request FromString(string s) 23 | { 24 | var rdr = new JsonTextReader(new System.IO.StringReader(s)); 25 | return new Request((JObject)ReadFrom(rdr)); 26 | } 27 | 28 | public string ClientId 29 | { 30 | get 31 | { 32 | return GetString("clientID"); 33 | } 34 | } 35 | 36 | public string Action 37 | { 38 | get 39 | { 40 | return GetString("action"); 41 | } 42 | } 43 | 44 | public string Nonce 45 | { 46 | get 47 | { 48 | return GetString("nonce"); 49 | } 50 | } 51 | 52 | public byte[] NonceBytes 53 | { 54 | get 55 | { 56 | return GetBytes("nonce"); 57 | } 58 | } 59 | 60 | public bool TriggerUnlock 61 | { 62 | get 63 | { 64 | bool x; 65 | return bool.TryParse(GetString("triggerUnlock"), out x) && x; 66 | } 67 | } 68 | 69 | public JsonBase Message 70 | { 71 | get 72 | { 73 | return _msg; 74 | } 75 | } 76 | 77 | public Response GetResponse() 78 | { 79 | return new Response(this); 80 | } 81 | 82 | public Response GetResponse(bool createMessage) 83 | { 84 | return new Response(this, createMessage); 85 | } 86 | 87 | public bool TryDecrypt() 88 | { 89 | try 90 | { 91 | _msg = KeePassNatMsgExt.CryptoHelper.DecryptMessage(ClientId, GetBytes("message"), GetBytes("nonce")); 92 | return true; 93 | } 94 | catch (Exception) 95 | { 96 | _msg = null; 97 | } 98 | return false; 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /KeePassNatMsg/Protocol/Action/Response.cs: -------------------------------------------------------------------------------- 1 | using KeePassNatMsg.Protocol.Crypto; 2 | using Newtonsoft.Json.Linq; 3 | 4 | namespace KeePassNatMsg.Protocol.Action 5 | { 6 | public class Response : JsonBase 7 | { 8 | private JsonBase _msg; 9 | private string _clientId; 10 | 11 | public Response(string action) 12 | { 13 | Add("action", action); 14 | } 15 | 16 | public Response(Request req, bool createMessage) 17 | { 18 | Init(req, createMessage); 19 | } 20 | 21 | public Response(Request req) 22 | { 23 | Init(req, true); 24 | } 25 | 26 | public byte[] Nonce 27 | { 28 | get 29 | { 30 | return GetBytes("nonce"); 31 | } 32 | } 33 | 34 | public JsonBase Message 35 | { 36 | get 37 | { 38 | return _msg; 39 | } 40 | } 41 | 42 | public string GetEncryptedResponse() 43 | { 44 | if (_msg != null) 45 | { 46 | AddBytes("message", KeePassNatMsgExt.CryptoHelper.EncryptMessage(_clientId, _msg.ToString(), Nonce)); 47 | } 48 | return ToString(); 49 | } 50 | 51 | private void Init(Request req, bool createMessage) 52 | { 53 | Add("action", new JValue(req.Action)); 54 | AddBytes("nonce", Helper.GenerateNonce(req.NonceBytes)); 55 | if (createMessage) CreateMessage(); 56 | _clientId = req.ClientId; 57 | } 58 | 59 | private void CreateMessage() 60 | { 61 | _msg = new JsonBase 62 | { 63 | {"hash", KeePassNatMsgExt.ExtInstance.GetDbHashForMessage()}, 64 | {"version", KeePassNatMsgExt.GetVersion()}, 65 | {"success", "true"}, 66 | {"nonce", Nonce} 67 | }; 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /KeePassNatMsg/Protocol/Actions.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace KeePassNatMsg.Protocol 3 | { 4 | public sealed class Actions 5 | { 6 | public const string GET_DATABASE_HASH = "get-databasehash"; 7 | public const string ASSOCIATE = "associate"; 8 | public const string TEST_ASSOCIATE = "test-associate"; 9 | public const string GET_LOGINS = "get-logins"; 10 | public const string SET_LOGIN = "set-login"; 11 | public const string GENERATE_PASSWORD = "generate-password"; 12 | public const string CHANGE_PUBLIC_KEYS = "change-public-keys"; 13 | public const string LOCK_DATABASE = "lock-database"; 14 | public const string DATABASE_LOCKED = "database-locked"; 15 | public const string DATABASE_UNLOCKED = "database-unlocked"; 16 | public const string GET_DATABASE_GROUPS = "get-database-groups"; 17 | public const string CREATE_NEW_GROUP = "create-new-group"; 18 | public const string GET_TOTP = "get-totp"; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /KeePassNatMsg/Protocol/Crypto/Helper.cs: -------------------------------------------------------------------------------- 1 |  2 | using KeePassNatMsg.Protocol.Action; 3 | using Newtonsoft.Json.Linq; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | 7 | namespace KeePassNatMsg.Protocol.Crypto 8 | { 9 | public sealed class Helper 10 | { 11 | private UTF8Encoding _utf8 = new UTF8Encoding(false); 12 | private Dictionary _clientKeys; 13 | 14 | public Helper() 15 | { 16 | _clientKeys = new Dictionary(); 17 | } 18 | 19 | public JsonBase DecryptMessage(string clientId, byte[] message, byte[] nonce) 20 | { 21 | if (_clientKeys.ContainsKey(clientId)) 22 | { 23 | var pair = _clientKeys[clientId]; 24 | var data = TweetNaCl.CryptoBoxOpen(message, nonce, pair.PublicKey, pair.PrivateKey); 25 | return new JsonBase(JObject.Parse(_utf8.GetString(data))); 26 | } 27 | return null; 28 | } 29 | 30 | public byte[] EncryptMessage(string clientId, string msg, byte[] nonce) 31 | { 32 | if (_clientKeys.ContainsKey(clientId)) 33 | { 34 | var pair = _clientKeys[clientId]; 35 | return TweetNaCl.CryptoBox(_utf8.GetBytes(msg), nonce, pair.PublicKey, pair.PrivateKey); 36 | } 37 | return null; 38 | } 39 | 40 | public byte[] GenerateKeyPair(string clientId, byte[] clientPublicKey) 41 | { 42 | var pair = new KeyPair(); 43 | 44 | if (_clientKeys.ContainsKey(clientId)) 45 | { 46 | _clientKeys.Remove(clientId); 47 | } 48 | 49 | _clientKeys.Add(clientId, new KeyPair(pair.PrivateKey, clientPublicKey)); 50 | 51 | return pair.PublicKey; 52 | } 53 | 54 | public byte[] ClientPublicKey(string clientId) 55 | { 56 | if (_clientKeys.ContainsKey(clientId)) 57 | { 58 | return _clientKeys[clientId].PublicKey; 59 | } 60 | return null; 61 | } 62 | 63 | public static byte[] GenerateNonce(byte[] nonce) 64 | { 65 | return TweetNaCl.Increment(nonce); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /KeePassNatMsg/Protocol/Crypto/KeyPair.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace KeePassNatMsg.Protocol.Crypto 4 | { 5 | public sealed class KeyPair 6 | { 7 | public byte[] PrivateKey { get; private set; } 8 | public byte[] PublicKey { get; private set; } 9 | 10 | public KeyPair() 11 | { 12 | PrivateKey = new byte[TweetNaCl.BoxSecretKeyBytes]; 13 | PublicKey = TweetNaCl.CryptoBoxKeypair(PrivateKey); 14 | } 15 | 16 | public KeyPair(byte[] privateKey, byte[] publicKey) 17 | { 18 | PrivateKey = privateKey; 19 | PublicKey = publicKey; 20 | } 21 | 22 | public string ToBase64() 23 | { 24 | var ms = new System.IO.MemoryStream(); 25 | ms.Write(PrivateKey, 0, PrivateKey.Length); 26 | ms.Write(PublicKey, 0, PublicKey.Length); 27 | return Convert.ToBase64String(ms.ToArray()); 28 | } 29 | 30 | public static KeyPair FromBase64(string s) 31 | { 32 | var data = Convert.FromBase64String(s); 33 | var sk = new byte[TweetNaCl.BoxSecretKeyBytes]; 34 | var pk = new byte[TweetNaCl.BoxPublicKeyBytes]; 35 | Array.Copy(data, sk, sk.Length); 36 | Array.Copy(data, sk.Length, pk, 0, pk.Length); 37 | return new KeyPair(sk, pk); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /KeePassNatMsg/Protocol/Errors.cs: -------------------------------------------------------------------------------- 1 | namespace KeePassNatMsg.Protocol 2 | { 3 | public enum ErrorType 4 | { 5 | DatabaseNotOpened = 1, 6 | DatabaseHashNotReceived, 7 | ClientPublicKeyNotReceived, 8 | CannotDecryptMessage, 9 | TimeoutOrNotConnected, 10 | ActionCancelledOrDenied, 11 | CannotEncryptMessage, 12 | AssociationFailed, 13 | KeyChangeFailed, 14 | EncryptionKeyUnrecognized, 15 | NoSavedDatabasesFound, 16 | IncorrectAction, 17 | EmptyMessageReceived, 18 | NoUrlProvided, 19 | NoLoginsFound, 20 | NoGroupsFound, 21 | CannotCreateNewGroup 22 | } 23 | 24 | public static class Errors 25 | { 26 | public static string GetErrorMessage(ErrorType error) 27 | { 28 | switch (error) 29 | { 30 | case ErrorType.DatabaseNotOpened: 31 | return "Database not opened"; 32 | case ErrorType.DatabaseHashNotReceived: 33 | return "Database hash not available"; 34 | case ErrorType.ClientPublicKeyNotReceived: 35 | return "Client public key not received"; 36 | case ErrorType.CannotDecryptMessage: 37 | return "Cannot decrypt message"; 38 | case ErrorType.TimeoutOrNotConnected: 39 | return "Timeout or cannot connect to KeePass"; 40 | case ErrorType.ActionCancelledOrDenied: 41 | return "Action cancelled or denied"; 42 | case ErrorType.CannotEncryptMessage: 43 | return "Cannot encrypt message or public key not found"; 44 | case ErrorType.AssociationFailed: 45 | return "Association failed"; 46 | case ErrorType.KeyChangeFailed: 47 | return "Key change was not successful"; 48 | case ErrorType.EncryptionKeyUnrecognized: 49 | return "Encryption key is not recognized"; 50 | case ErrorType.NoSavedDatabasesFound: 51 | return "No saved databases found"; 52 | case ErrorType.IncorrectAction: 53 | return "Incorrect action"; 54 | case ErrorType.EmptyMessageReceived: 55 | return "Empty message received"; 56 | case ErrorType.NoUrlProvided: 57 | return "No URL provided"; 58 | case ErrorType.NoLoginsFound: 59 | return "No Logins Found"; 60 | case ErrorType.NoGroupsFound: 61 | return "No Groups Found"; 62 | case ErrorType.CannotCreateNewGroup: 63 | return "Cannot Create New Group"; 64 | default: 65 | return error.ToString(); 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /KeePassNatMsg/Protocol/Handlers.cs: -------------------------------------------------------------------------------- 1 | using KeePass.Plugins; 2 | using KeePassLib; 3 | using KeePassNatMsg.Entry; 4 | using KeePassNatMsg.Protocol.Action; 5 | using Newtonsoft.Json.Linq; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | 10 | namespace KeePassNatMsg.Protocol 11 | { 12 | public sealed class Handlers 13 | { 14 | private KeePassNatMsgExt _ext; 15 | private Dictionary _handlers; 16 | private IPluginHost _host; 17 | private object _unlockLock; 18 | 19 | public delegate Response RequestHandler(Request req); 20 | 21 | public Handlers() 22 | { 23 | _ext = KeePassNatMsgExt.ExtInstance; 24 | _host = KeePassNatMsgExt.HostInstance; 25 | _unlockLock = new object(); 26 | } 27 | 28 | public void Initialize() 29 | { 30 | _handlers = new Dictionary 31 | { 32 | {Actions.GET_DATABASE_HASH, GetDatabaseHash}, 33 | {Actions.TEST_ASSOCIATE, TestAssociate}, 34 | {Actions.ASSOCIATE, Associate}, 35 | {Actions.CHANGE_PUBLIC_KEYS, ChangePublicKeys}, 36 | {Actions.GET_LOGINS, GetLogins}, 37 | {Actions.SET_LOGIN, SetLogin}, 38 | {Actions.GENERATE_PASSWORD, GeneratePassword}, 39 | {Actions.LOCK_DATABASE, LockDatabase}, 40 | {Actions.GET_DATABASE_GROUPS, GetDatabaseGroups}, 41 | {Actions.CREATE_NEW_GROUP, CreateNewGroup}, 42 | {Actions.GET_TOTP, GetTotp}, 43 | }; 44 | } 45 | 46 | public Response ProcessRequest(Request req) 47 | { 48 | var handler = GetHandler(req.Action); 49 | if (handler != null) 50 | { 51 | if (handler != ChangePublicKeys && !UnlockDatabase(req.TriggerUnlock)) 52 | { 53 | return new ErrorResponse(req, ErrorType.DatabaseNotOpened); 54 | } 55 | 56 | return handler.Invoke(req); 57 | } 58 | return new ErrorResponse(req, ErrorType.IncorrectAction); 59 | } 60 | 61 | private bool UnlockDatabase(bool triggerUnlock) 62 | { 63 | lock (_unlockLock) 64 | { 65 | var config = new ConfigOpt(_host.CustomConfig); 66 | if (!_host.Database.IsOpen && config.UnlockDatabaseRequest && KeePass.UI.GlobalWindowManager.WindowCount == 0 && triggerUnlock) 67 | { 68 | _host.MainWindow.Invoke(new System.Action(() => _host.MainWindow.OpenDatabase(_host.MainWindow.DocumentManager.ActiveDocument.LockedIoc, null, false))); 69 | } 70 | 71 | return _host.Database.IsOpen; 72 | } 73 | } 74 | 75 | private RequestHandler GetHandler(string action) 76 | { 77 | return _handlers.ContainsKey(action) ? _handlers[action] : null; 78 | } 79 | 80 | private Response GetDatabaseHash(Request req) 81 | { 82 | if (req.TryDecrypt()) 83 | { 84 | return req.GetResponse(); 85 | } 86 | return new ErrorResponse(req, ErrorType.CannotDecryptMessage); 87 | } 88 | 89 | private Response TestAssociate(Request req) 90 | { 91 | if (req.TryDecrypt()) 92 | { 93 | var db = _ext.GetConnectionDatabase(); 94 | var msg = req.Message; 95 | var customKey = KeePassNatMsgExt.DbKey + msg.GetString("id"); 96 | if (!db.CustomData.Exists(customKey)) 97 | return new ErrorResponse(req, ErrorType.AssociationFailed); 98 | var key = db.CustomData.Get(customKey); 99 | var reqKey = msg.GetBytes("key"); 100 | var id = msg.GetString("id"); 101 | var dbKey = Convert.FromBase64String(key); 102 | if (dbKey.SequenceEqual(reqKey) && !string.IsNullOrWhiteSpace(id)) 103 | { 104 | var resp = req.GetResponse(); 105 | resp.Message.Add("id", id); 106 | return resp; 107 | } 108 | return new ErrorResponse(req, ErrorType.AssociationFailed); 109 | } 110 | return new ErrorResponse(req, ErrorType.CannotDecryptMessage); 111 | } 112 | 113 | private Response Associate(Request req) 114 | { 115 | if (req.TryDecrypt()) 116 | { 117 | var msg = req.Message; 118 | var keyBytes = msg.GetBytes("key"); 119 | if (keyBytes.SequenceEqual(KeePassNatMsgExt.CryptoHelper.ClientPublicKey(req.ClientId))) 120 | { 121 | var id = _ext.ShowConfirmAssociationDialog(msg.GetString("idKey")); 122 | if (string.IsNullOrEmpty(id)) 123 | { 124 | return new ErrorResponse(req, ErrorType.AssociationFailed); 125 | } 126 | var resp = req.GetResponse(); 127 | resp.Message.Add("id", id); 128 | return resp; 129 | } 130 | else 131 | { 132 | _ext.ShowNotification("Association Failed. Public Keys don't match."); 133 | return new ErrorResponse(req, ErrorType.AssociationFailed); 134 | } 135 | } 136 | return new ErrorResponse(req, ErrorType.CannotDecryptMessage); 137 | } 138 | 139 | private Response ChangePublicKeys(Request req) 140 | { 141 | var crypto = KeePassNatMsgExt.CryptoHelper; 142 | var publicKey = req.GetString("publicKey"); 143 | 144 | if (string.IsNullOrEmpty(publicKey)) 145 | return new ErrorResponse(req, ErrorType.ClientPublicKeyNotReceived); 146 | 147 | var serverPublicKey = crypto.GenerateKeyPair(req.ClientId, Convert.FromBase64String(publicKey)); 148 | var resp = req.GetResponse(false); 149 | resp.AddBytes("publicKey", serverPublicKey); 150 | resp.Add("version", KeePassNatMsgExt.GetVersion()); 151 | resp.Add("success", "true"); 152 | return resp; 153 | } 154 | 155 | private Response GetLogins(Request req) 156 | { 157 | var es = new EntrySearch(); 158 | return es.GetLoginsHandler(req); 159 | } 160 | 161 | private Response SetLogin(Request req) 162 | { 163 | if (req.TryDecrypt()) 164 | { 165 | var eu = new EntryUpdate(); 166 | var reqMsg = req.Message; 167 | var url = reqMsg.GetString("url"); 168 | var uuid = reqMsg.GetString("uuid"); 169 | var login = reqMsg.GetString("login"); 170 | var pw = reqMsg.GetString("password"); 171 | var submitUrl = reqMsg.GetString("submitUrl"); 172 | var groupUuid = reqMsg.GetString("groupUuid"); 173 | 174 | bool result; 175 | 176 | if (string.IsNullOrEmpty(uuid)) 177 | { 178 | result = eu.CreateEntry(login, pw, url, submitUrl, null, groupUuid); 179 | } 180 | else 181 | { 182 | result = eu.UpdateEntry(uuid, login, pw, url); 183 | } 184 | 185 | var resp = req.GetResponse(); 186 | 187 | resp.Message.Add("count", JValue.CreateNull()); 188 | resp.Message.Add("entries", JValue.CreateNull()); 189 | resp.Message.Add("error", result ? "success" : "error"); 190 | 191 | return resp; 192 | } 193 | return new ErrorResponse(req, ErrorType.CannotDecryptMessage); 194 | } 195 | 196 | private Response GeneratePassword(Request req) 197 | { 198 | var resp = req.GetResponse(); 199 | var msg = resp.Message; 200 | msg.Add("entries", new JArray(_ext.GeneratePassword())); 201 | return resp; 202 | } 203 | 204 | private Response LockDatabase(Request req) 205 | { 206 | 207 | _host.MainWindow.Invoke(new System.Action(() => _host.MainWindow.LockAllDocuments())); 208 | return req.GetResponse(); 209 | } 210 | 211 | private Response GetDatabaseGroups(Request req) 212 | { 213 | var db = _ext.GetConnectionDatabase(); 214 | 215 | if (db.RootGroup == null) 216 | { 217 | return new ErrorResponse(req, ErrorType.NoGroupsFound); 218 | } 219 | 220 | var root = new JObject 221 | { 222 | { "name", db.RootGroup.Name }, 223 | { "uuid", db.RootGroup.Uuid.ToHexString() }, 224 | { "children", GetGroupChildren(db.RootGroup) } 225 | }; 226 | 227 | var resp = req.GetResponse(); 228 | 229 | resp.Message.Add("groups", new JObject 230 | { 231 | { "groups", new JArray { root } } 232 | }); 233 | 234 | return resp; 235 | } 236 | 237 | private JArray GetGroupChildren(PwGroup group) 238 | { 239 | var groups = new JArray(); 240 | 241 | foreach(var grp in group.GetGroups(false)) 242 | { 243 | groups.Add(new JObject 244 | { 245 | { "name", grp.Name }, 246 | { "uuid", grp.Uuid.ToHexString() }, 247 | { "children", GetGroupChildren(grp) } 248 | }); 249 | } 250 | 251 | return groups; 252 | } 253 | 254 | private Response CreateNewGroup(Request req) 255 | { 256 | if (!req.TryDecrypt()) 257 | return new ErrorResponse(req, ErrorType.CannotDecryptMessage); 258 | 259 | var groupName = req.Message.GetString("groupName"); 260 | 261 | var db = _ext.GetConnectionDatabase(); 262 | 263 | var group = db.RootGroup.FindCreateSubTree(groupName, new[] { '/' }, true); 264 | 265 | if (group == null) 266 | return new ErrorResponse(req, ErrorType.CannotCreateNewGroup); 267 | 268 | var resp = req.GetResponse(); 269 | 270 | resp.Message.Add("name", group.Name); 271 | resp.Message.Add("uuid", group.Uuid.ToHexString()); 272 | 273 | return resp; 274 | } 275 | 276 | private Response GetTotp(Request req) 277 | { 278 | if (!req.TryDecrypt()) 279 | return new ErrorResponse(req, ErrorType.CannotDecryptMessage); 280 | 281 | var uuid = req.Message.GetString("uuid"); 282 | 283 | var es = new EntrySearch(); 284 | var totp = es.GetTotp(uuid); 285 | 286 | if (string.IsNullOrEmpty(totp)) 287 | return new ErrorResponse(req, ErrorType.NoLoginsFound); 288 | 289 | var resp = req.GetResponse(); 290 | 291 | resp.Message.Add("totp", totp); 292 | 293 | return resp; 294 | } 295 | } 296 | } 297 | -------------------------------------------------------------------------------- /KeePassNatMsg/Protocol/Listener/IListener.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace KeePassNatMsg.Protocol.Listener 4 | { 5 | public interface IListener 6 | { 7 | void Start(); 8 | void Stop(); 9 | void Write(string msg); 10 | event EventHandler MessageReceived; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /KeePassNatMsg/Protocol/Listener/IMessageWriter.cs: -------------------------------------------------------------------------------- 1 | namespace KeePassNatMsg.Protocol.Listener 2 | { 3 | public interface IMessageWriter 4 | { 5 | void Send(string msg); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /KeePassNatMsg/Protocol/Listener/NamedPipeListener.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.IO.Pipes; 5 | using System.Threading; 6 | 7 | namespace KeePassNatMsg.Protocol.Listener 8 | { 9 | public sealed class NamedPipeListener : IListener 10 | { 11 | private const int BufferSize = 1024*1024; 12 | private const int Threads = 5; 13 | private readonly string _name; 14 | private volatile bool _active; 15 | private readonly List _threads; 16 | 17 | public event EventHandler MessageReceived; 18 | 19 | public NamedPipeListener(string name) 20 | { 21 | _name = name; 22 | _threads = new List(); 23 | } 24 | 25 | public void Start() 26 | { 27 | _active = true; 28 | for (var i = 0; i < Threads; i++) 29 | { 30 | CreateAndRunThread(); 31 | } 32 | } 33 | 34 | public void Stop() 35 | { 36 | _active = false; 37 | foreach (var pts in _threads) 38 | { 39 | pts.Close(); 40 | } 41 | } 42 | 43 | public void Write(string msg) 44 | { 45 | var pts = _threads.Find(x => x.Server.IsConnected); 46 | if (pts != null) 47 | { 48 | var pw = new PipeWriter(pts.Server); 49 | pw.Send(msg); 50 | } 51 | } 52 | 53 | private void CreateAndRunThread() 54 | { 55 | var pts = CreateThreadState(new Thread(Run)); 56 | pts.Thread.Start(pts); 57 | } 58 | 59 | private PipeThreadState CreateThreadState(Thread t) 60 | { 61 | var pts = new PipeThreadState(t); 62 | _threads.Add(pts); 63 | return pts; 64 | } 65 | 66 | private void RunThreadClosed(object args) 67 | { 68 | var pts = (PipeThreadState)args; 69 | _threads.Remove(pts); 70 | pts.Close(); 71 | pts = CreateThreadState(Thread.CurrentThread); 72 | Run(pts); 73 | } 74 | 75 | private void ThreadClosed(PipeThreadState pts) 76 | { 77 | if (_active) 78 | { 79 | var t = new Thread(RunThreadClosed); 80 | t.Start(pts); 81 | } 82 | } 83 | 84 | private void Run(object args) 85 | { 86 | var read = true; 87 | var pts = (PipeThreadState)args; 88 | NamedPipeServerStream server; 89 | 90 | server = new NamedPipeServerStream(_name, PipeDirection.InOut, Threads, PipeTransmissionMode.Byte, PipeOptions.Asynchronous); 91 | 92 | pts.Server = server; 93 | 94 | try 95 | { 96 | server.WaitForConnection(); 97 | 98 | while (_active && server.IsConnected && read) 99 | { 100 | var buffer = new byte[BufferSize]; 101 | var bytes = server.Read(buffer, 0, buffer.Length); 102 | 103 | if (bytes > 0) 104 | { 105 | var data = new byte[bytes]; 106 | Array.Copy(buffer, data, bytes); 107 | if (MessageReceived != null) 108 | MessageReceived.BeginInvoke(this, new PipeMessageReceivedEventArgs(new PipeWriter(server), data), null, null); 109 | } 110 | else if (bytes == 0) 111 | { 112 | read = false; 113 | } 114 | } 115 | } 116 | catch (IOException) 117 | { 118 | } 119 | 120 | ThreadClosed(pts); 121 | } 122 | } 123 | 124 | public class PipeMessageReceivedEventArgs : EventArgs 125 | { 126 | public string Message { get; set; } 127 | public IMessageWriter Writer { get; set; } 128 | 129 | public PipeMessageReceivedEventArgs(IMessageWriter writer, byte[] data) 130 | { 131 | Writer = writer; 132 | Message = System.Text.Encoding.UTF8.GetString(data); 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /KeePassNatMsg/Protocol/Listener/PipeThreadState.cs: -------------------------------------------------------------------------------- 1 | using System.IO.Pipes; 2 | using System.Threading; 3 | 4 | namespace KeePassNatMsg.Protocol.Listener 5 | { 6 | public class PipeThreadState 7 | { 8 | public Thread Thread { get; private set; } 9 | public NamedPipeServerStream Server { get; set; } 10 | 11 | public PipeThreadState(Thread t) 12 | { 13 | Thread = t; 14 | } 15 | 16 | public void Close() 17 | { 18 | Server.Close(); 19 | Thread.Join(); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /KeePassNatMsg/Protocol/Listener/PipeWriter.cs: -------------------------------------------------------------------------------- 1 | using System.IO.Pipes; 2 | 3 | namespace KeePassNatMsg.Protocol.Listener 4 | { 5 | public class PipeWriter : IMessageWriter 6 | { 7 | private readonly NamedPipeServerStream _server; 8 | private readonly System.Text.UTF8Encoding _utf8; 9 | 10 | public PipeWriter(NamedPipeServerStream server) 11 | { 12 | _server = server; 13 | _utf8 = new System.Text.UTF8Encoding(false); 14 | } 15 | 16 | public void Send(string msg) 17 | { 18 | var data = _utf8.GetBytes(msg); 19 | _server.Write(data, 0, data.Length); 20 | _server.Flush(); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /KeePassNatMsg/Protocol/Listener/SocketReadState.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.Sockets; 3 | using System.Threading; 4 | 5 | namespace KeePassNatMsg.Protocol.Listener 6 | { 7 | internal sealed class SocketReadState : IDisposable 8 | { 9 | public Socket Socket; 10 | public bool Active; 11 | public byte[] Data; 12 | public ManualResetEventSlim WaitHandle; 13 | 14 | public SocketReadState() 15 | { 16 | WaitHandle = new ManualResetEventSlim(false); 17 | Active = true; 18 | } 19 | 20 | public void Dispose() 21 | { 22 | Dispose(true); 23 | GC.SuppressFinalize(this); 24 | } 25 | 26 | private void Dispose(bool disposing) 27 | { 28 | if (disposing) 29 | { 30 | // dispose managed resources 31 | WaitHandle.Dispose(); 32 | } 33 | // free native resources 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /KeePassNatMsg/Protocol/Listener/SocketWriter.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Sockets; 2 | 3 | namespace KeePassNatMsg.Protocol.Listener 4 | { 5 | public class SocketWriter : IMessageWriter 6 | { 7 | private readonly Socket _socket; 8 | private readonly System.Text.UTF8Encoding _utf8; 9 | 10 | public SocketWriter(Socket socket) 11 | { 12 | _socket = socket; 13 | _utf8 = new System.Text.UTF8Encoding(false); 14 | } 15 | 16 | public void Send(string msg) 17 | { 18 | var data = _utf8.GetBytes(msg); 19 | _socket.Send(data); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /KeePassNatMsg/Protocol/Listener/UdpListener.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Net.Sockets; 4 | using System.Threading; 5 | 6 | namespace KeePassNatMsg.Protocol.Listener 7 | { 8 | public sealed class UdpListener : IDisposable 9 | { 10 | private readonly Thread _thread; 11 | private UdpClient _client; 12 | private IPEndPoint _ep; 13 | private volatile bool _active; 14 | private readonly System.Text.UTF8Encoding _utf8 = new System.Text.UTF8Encoding(false); 15 | private readonly int _port; 16 | 17 | public event EventHandler MessageReceived; 18 | 19 | public UdpListener(int port) 20 | { 21 | _port = port; 22 | _thread = new Thread(new ThreadStart(Run)) 23 | { 24 | Name = string.Format("{0}_Thread", GetType().Name), 25 | }; 26 | } 27 | 28 | public void Start() 29 | { 30 | if (_thread.ThreadState == ThreadState.Unstarted) 31 | { 32 | _active = true; 33 | _thread.Start(); 34 | } 35 | } 36 | 37 | public void Stop() 38 | { 39 | _active = false; 40 | _client.Close(); 41 | _thread.Join(); 42 | } 43 | 44 | public void Send(string msg, IPEndPoint ep) 45 | { 46 | var data = _utf8.GetBytes(msg); 47 | _client.Send(data, data.Length, ep); 48 | } 49 | 50 | public void Dispose() 51 | { 52 | Dispose(true); 53 | GC.SuppressFinalize(this); 54 | } 55 | 56 | private void Dispose(bool disposing) 57 | { 58 | if (disposing) 59 | { 60 | // dispose managed resources 61 | _client.Close(); 62 | } 63 | // free native resources 64 | } 65 | 66 | private void Run() 67 | { 68 | _ep = new IPEndPoint(IPAddress.Any, _port); 69 | _client = new UdpClient(_ep); 70 | 71 | while (_active) 72 | { 73 | var ar = _client.BeginReceive(ReceiveData, null); 74 | ar.AsyncWaitHandle.WaitOne(); 75 | } 76 | } 77 | 78 | private void ReceiveData(IAsyncResult ar) 79 | { 80 | if (_active) 81 | { 82 | var ep = new IPEndPoint(IPAddress.Any, 0); 83 | var data = _client.EndReceive(ar, ref ep); 84 | var str = System.Text.Encoding.UTF8.GetString(data); 85 | if (MessageReceived != null) 86 | MessageReceived.Invoke(this, new UdpMessageReceivedEventArgs(ep, str)); 87 | } 88 | } 89 | } 90 | 91 | public sealed class UdpMessageReceivedEventArgs: EventArgs 92 | { 93 | public IPEndPoint From { get; private set; } 94 | public string Message { get; private set; } 95 | 96 | public UdpMessageReceivedEventArgs(IPEndPoint from, string msg) 97 | { 98 | From = from; 99 | Message = msg; 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /KeePassNatMsg/Protocol/Listener/UnixSocketListener.cs: -------------------------------------------------------------------------------- 1 | using Mono.Unix; 2 | using System; 3 | using System.Diagnostics; 4 | using System.Net.Sockets; 5 | using System.Threading; 6 | 7 | namespace KeePassNatMsg.Protocol.Listener 8 | { 9 | public class UnixSocketListener : IListener, IDisposable 10 | { 11 | private const string SocketName = "kpxc_server"; 12 | 13 | private UnixEndPoint _uep; 14 | private Socket _socket; 15 | private bool _active; 16 | private Thread _t; 17 | private CancellationTokenSource _cts; 18 | 19 | public event EventHandler MessageReceived; 20 | 21 | public UnixSocketListener() 22 | { 23 | var path = "/tmp/" + SocketName; 24 | var xdg = Environment.GetEnvironmentVariable("XDG_RUNTIME_DIR"); 25 | if (!string.IsNullOrEmpty(xdg)) 26 | { 27 | path = System.IO.Path.Combine(xdg, SocketName); 28 | } 29 | _uep = new UnixEndPoint(path); 30 | _cts = new CancellationTokenSource(); 31 | } 32 | 33 | public void Start() 34 | { 35 | _active = true; 36 | _t = new Thread(RunThread); 37 | _t.Start(); 38 | } 39 | 40 | public void Stop() 41 | { 42 | _active = false; 43 | _cts.Cancel(); 44 | _socket.Close(); 45 | _t.Join(); 46 | DeleteSocketFile(); 47 | } 48 | 49 | public void Write(string msg) 50 | { 51 | if (_socket.Connected) 52 | { 53 | var sw = new SocketWriter(_socket); 54 | sw.Send(msg); 55 | } 56 | } 57 | 58 | public void Dispose() 59 | { 60 | Dispose(true); 61 | GC.SuppressFinalize(this); 62 | } 63 | 64 | private void Dispose(bool disposing) 65 | { 66 | if (disposing) 67 | { 68 | // dispose managed resources 69 | _socket.Close(); 70 | _cts.Dispose(); 71 | } 72 | // free native resources 73 | } 74 | 75 | private void RunThread() 76 | { 77 | DeleteSocketFile(); 78 | 79 | _socket = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.IP); 80 | _socket.Bind(_uep); 81 | _socket.Listen(5); 82 | 83 | while (_active) 84 | { 85 | try 86 | { 87 | var ar = _socket.BeginAccept(SocketAccept, null); 88 | ar.AsyncWaitHandle.WaitOne(); 89 | } 90 | catch (Exception ex) 91 | { 92 | Debug.WriteLine("Socket Error: " + ex.ToString()); 93 | } 94 | } 95 | } 96 | 97 | private void SocketAccept(IAsyncResult r) 98 | { 99 | if (r.IsCompleted && _active) 100 | { 101 | var s = _socket.EndAccept(r); 102 | var buffer = new byte[1024 * 16]; 103 | var read = true; 104 | 105 | while (_active && s.Connected && read) 106 | { 107 | var srs = new SocketReadState 108 | { 109 | Socket = s, 110 | Data = buffer 111 | }; 112 | var ar = s.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, SocketRead, srs); 113 | try 114 | { 115 | srs.WaitHandle.Wait(_cts.Token); 116 | read = srs.Active; 117 | } 118 | catch (OperationCanceledException) 119 | { 120 | read = false; 121 | } 122 | } 123 | } 124 | } 125 | 126 | private void SocketRead(IAsyncResult r) 127 | { 128 | var srs = r.AsyncState as SocketReadState; 129 | if (r.IsCompleted) 130 | { 131 | var bytes = srs.Socket.EndReceive(r); 132 | if (bytes > 0) 133 | { 134 | var data = new byte[bytes]; 135 | Array.Copy(srs.Data, data, bytes); 136 | if (MessageReceived != null) 137 | MessageReceived.BeginInvoke(this, new PipeMessageReceivedEventArgs(new SocketWriter(srs.Socket), data), null, null); 138 | } 139 | else if (bytes == 0) 140 | { 141 | srs.Active = false; 142 | } 143 | } 144 | srs.WaitHandle.Set(); 145 | } 146 | 147 | private void DeleteSocketFile() 148 | { 149 | if (System.IO.File.Exists(_uep.Filename)) 150 | { 151 | try 152 | { 153 | System.IO.File.Delete(_uep.Filename); 154 | } 155 | catch (Exception ex) 156 | { 157 | Debug.WriteLine(string.Format("Error Deleting Socket File ({0}): {1}", _uep.Filename, ex)); 158 | } 159 | } 160 | } 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /KeePassNatMsg/PwEntryDatabase.cs: -------------------------------------------------------------------------------- 1 | using KeePassLib; 2 | 3 | namespace KeePassNatMsg 4 | { 5 | class PwEntryDatabase 6 | { 7 | private PwEntry _entry; 8 | public PwEntry entry 9 | { 10 | get { return _entry; } 11 | } 12 | private PwDatabase _database; 13 | public PwDatabase database 14 | { 15 | get { return _database; } 16 | } 17 | 18 | //public PwEntryDatabase(ref PwEntry e, ref PwDatabase db) 19 | public PwEntryDatabase(PwEntry e, PwDatabase db) 20 | { 21 | _entry = e; 22 | _database = db; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /KeePassNatMsg/Resources/earth_lock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smorks/keepassnatmsg/ad2a7625986b0104106c89f6cdd490597aba3599/KeePassNatMsg/Resources/earth_lock.png -------------------------------------------------------------------------------- /KeePassNatMsg/Utils/EnumExtension.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | 4 | namespace KeePassNatMsg.Utils 5 | { 6 | public static class EnumExtension 7 | { 8 | public static string GetDescription(this T e) where T : IConvertible 9 | { 10 | string description = e.ToString(); 11 | 12 | if (e is Enum) 13 | { 14 | Type type = e.GetType(); 15 | Array values = Enum.GetValues(type); 16 | 17 | foreach (int val in values) 18 | { 19 | if (val == e.ToInt32(System.Globalization.CultureInfo.InvariantCulture)) 20 | { 21 | var memInfo = type.GetMember(type.GetEnumName(val)); 22 | var descriptionAttributes = memInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false); 23 | if (descriptionAttributes.Length > 0) 24 | { 25 | // we're only getting the first description we find 26 | // others will be ignored 27 | description = ((DescriptionAttribute)descriptionAttributes[0]).Description; 28 | } 29 | break; 30 | } 31 | } 32 | } 33 | 34 | return description; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /KeePassNatMsg/lib/Mono.Posix.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smorks/keepassnatmsg/ad2a7625986b0104106c89f6cdd490597aba3599/KeePassNatMsg/lib/Mono.Posix.dll -------------------------------------------------------------------------------- /KeePassNatMsg/lib/Newtonsoft.Json.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smorks/keepassnatmsg/ad2a7625986b0104106c89f6cdd490597aba3599/KeePassNatMsg/lib/Newtonsoft.Json.dll -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Main workflow](https://github.com/smorks/keepassnatmsg/actions/workflows/workflow.yml/badge.svg)](https://github.com/smorks/keepassnatmsg/actions/workflows/workflow.yml) 2 | 3 | # *** KeePassNatMsg is in maintenance only mode *** 4 | 5 | I don't personally use this plugin anymore, therefore it is currently in maintenance mode, which means I will fix critical bugs, and will still accept PR's. I'm also open to passing this project on to a new maintainer, see the [relevant discussion topic](https://github.com/smorks/keepassnatmsg/discussions/104). 6 | 7 | # KeePassNatMsg 8 | 9 | is a plugin for KeePass 2.x and provides a secure means of exposing KeePass credentials to a browser via [Native Messaging](https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Native_messaging). 10 | 11 | It is based on [KeePassHttp](https://github.com/pfn/keepasshttp). 12 | 13 | This plugin is primarily intended for use with the [keepassxc-browser](https://github.com/keepassxreboot/keepassxc-browser) browser extension. 14 | 15 | ## Features 16 | * returns all matching entries for a given URL 17 | * updates entries 18 | * secure exchange of entries 19 | * notifies user if entries are delivered 20 | * user can allow or deny access to single entries 21 | * works only if the database is unlocked 22 | * request for unlocking the database if it is locked while connecting 23 | * searches in all opened databases (if user activates this feature) 24 | * Whenever events occur, the user is prompted either by tray notification or requesting interaction (allow/deny/remember). 25 | 26 | ## System requirements 27 | * KeePass 2.17 or higher 28 | * For Windows: .NET Framework 4.0 or higher 29 | * For Linux: Mono 4.0 or higher 30 | * For Mac: Mono 4.0 or higher (untested) 31 | 32 | ## Installation 33 | 34 | 1. Download the latest [KeePassNatMsg](https://github.com/smorks/keepassnatmsg/releases) release 35 | * Arch Linux (AUR): https://aur.archlinux.org/packages/keepass-natmsg/ 36 | 2. Unzip it into the KeePass\Plugins directory 37 | * default directory in Ubuntu14.04: /usr/lib/keepass2/ 38 | * default directory in Arch: /usr/share/keepass 39 | 3. On linux systems you maybe need to install mono-complete: `$ apt-get install mono-complete` (in Debian it should be enough to install the packages libmono-system-runtime-serialization4.0-cil and libmono-posix2.0-cil) 40 | * Tips to run KeePassNatMsg on lastest KeePass 2.31: install packages 41 | `sudo apt-get install libmono-system-xml-linq4.0-cil libmono-system-data-datasetextensions4.0-cil libmono-system-runtime-serialization4.0-cil mono-mcs` 42 | 4. Restart KeePass 43 | 5. Go to Tools -> KeePassNatMsg Options 44 | 6. Click on "Install/Update Native Messaging Host", wait for message telling you it was installed. 45 | 7. Install the [KeePassXC-Browser](https://github.com/keepassxreboot/keepassxc-browser) extension for your browser, and Connect to the database from within the extension. 46 | 47 | #### Chocolatey 📦 48 | Or you can [use Chocolatey to install](https://community.chocolatey.org/packages/keepass-plugin-keepassnatmsg#install) it in a more automated manner: 49 | 50 | ``` 51 | choco install keepass-plugin-keepassnatmsg 52 | ``` 53 | 54 | To [upgrade KeePass Plugin KeePassNatMsg](https://community.chocolatey.org/packages/keepass-plugin-keepassnatmsg#upgrade) to the [latest release version](https://community.chocolatey.org/packages/keepass-plugin-keepassnatmsg#versionhistory) for enjoying the newest features, run the following command from the command line or from PowerShell: 55 | 56 | ``` 57 | choco upgrade keepass-plugin-keepassnatmsg 58 | ``` 59 | 60 | ### KeePassNatMsg on Linux and Mac 61 | 62 | KeePass needs Mono. You can find detailed [installation instructions on the official page of KeePass](http://keepass.info/help/v2/setup.html#mono). 63 | 64 | ## Configuration and Options 65 | 66 | KeePassNatMsg works out-of-the-box. You don't have to explicitly configure it. 67 | 68 | * KeePassNatMsg stores shared public keys in "KeePassNatMsg Settings" in the root group of a password database. 69 | * Password entries saved by KeePassNatMsg are stored in a new group named "KeePassNatMsg Passwords" within the password database. 70 | * Remembered Allow/Deny settings are stored as JSON in custom string fields within the individual password entry in the database. 71 | 72 | ### Settings in KeePassNatMsg options. 73 | 74 | You can open the options dialog with menu: Tools > KeePassNatMsg Options 75 | 76 | ![KeePassNatMsg Options Menu](documentation/images/menu.png) 77 | 78 | The options dialog will appear: 79 | 80 | ![KeePassNatMsg Options Dialog](documentation/images/options-general.png) 81 | 82 | #### General tab 83 | 84 | 1. show a notification balloon whenever entries are delivered to the inquirer. 85 | 2. returns only the best matching entries for the given url, otherwise all entries for a domain are send. 86 | - e.g. of two entries with the URLs http://example.org and http://example.org/, only the second one will returned if the requested URL is http://example.org/index.html 87 | 3. if the active database in KeePass is locked, KeePassNatMsg sends a request to unlock the database. Now KeePass opens and the user has to enter the master password to unlock the database. Otherwise KeePassNatMsg tells the inquirer that the database is closed. 88 | 4. expired entries are ignored if enabled. 89 | 5. KeePassNatMsg returns only these entries which match the scheme of the given URL. 90 | - given URL: https://example.org --> scheme: https:// --> only entries whose URL starts with https:// 91 | 6. sort found entries by username or title. 92 | 7. removes all stored permissions in the entries of the currently selected database. 93 | 8. Shows the status of the Native Messaging Host installations for the supported browsers, and the current Proxy version. 94 | 9. Installs or Updates the Native Messaging Host, and updates the Proxy if an update is available. 95 | 96 | ![KeePassNatMsg Options Advanced](documentation/images/options-advanced.png) 97 | 98 | #### Advanced tab 99 | 100 | 10. KeePassNatMsg no longer asks for permissions to retrieve entries, it always allows access. 101 | 11. KeePassNatMsg no longer asks for permission to update an entry, it always allows updating them. 102 | 12. Choice of databases used for searches: 103 | - Use only the active database (default). 104 | - Use all open databases. 105 | - Always use a specific database. 106 | 13. When activated, it will search all string fields beginning with "URL". 107 | 14. if activated KeePassNatMsg also search for string fields which are defined in the found entries and start with "KPH: " (note the space after colon). __The string fields will be transferred to the client in alphabetical order__. You can set string fields in the tab _Advanced_ of an entry. 108 | [advanced tab of an entry](https://raw.github.com/smorks/KeePassNatMsg/master/documentation/images/advanced-string-fields.png) 109 | 15. Override the version returned to KeePassXC-Browser 110 | 16. When a database is selected, KeePassNatMsg will always use the selected database to search for entries. 111 | 17. Use the same settings as KeePassXC. If checked, it will share all Allow/Deny lists and keys with KeePassXC. 112 | - ***It is strongly recommended that you make a backup of your database before using the Migrate Settings and Check for Legacy Config buttons.*** 113 | - Migrate Settings: will migrate settings between KeePassNatMsg and KeePassXC. 114 | - Check for Legacy Config: will check to see if any legacy config exists in the current database, and migrate it to the new CustomData format. 115 | 116 | ![KeePassNatMsg Options Keys](documentation/images/options-keys.png) 117 | 118 | #### Keys Tab 119 | 120 | Will display all configured browser keys, and you can remove them as needed. 121 | 122 | ## Tips and Tricks 123 | 124 | ### Support multiple URLs for one username + password 125 | This is already implemented directly in KeePass. 126 | 127 | 1. Open the context menu of an entry by clicking right on it and select _Duplicate entry_: 128 | [context-menu-entry](https://raw.github.com/smorks/KeePassNatMsg/master/documentation/images/keepass-context-menu.png) 129 | 130 | 2. Check the option to use references for username and password: 131 | [mark checkbox references](https://raw.github.com/smorks/KeePassNatMsg/master/documentation/images/keepass-duplicate-entry-references.png) 132 | 133 | 3. You can change the title, URL and everything of the copied entry, but not the username and password. These fields contain a _Reference Key_ which refers to the _master entry_ you copied from. 134 | 135 | ### TOTP Field Support 136 | 137 | KeePassNatMsg can use the built-in TOTP support in KeePass (since KeePass v2.47, [official docs](https://keepass.info/help/base/placeholders.html#otp)). 138 | 139 | KeePassNatMsg can also use the existence of either KeeOtp (`otp`) or KeeTrayTOTP (`TOTP Seed`) string fields to detect when TOTP entries should be returned in credential requests. 140 | 141 | ## Troubleshooting 142 | 143 | __First:__ If an error occurs it will be shown as notification in system tray or as message box in KeePass. 144 | 145 | Otherwise please check if it could be an error of the client you are using. For keepassxc-browser issues you can [report an error here](https://github.com/varjolintu/keepassxc-browser/issues/). 146 | 147 | 148 | If you are having problems with KeePassNatMsg, please tell us at least the following information: 149 | * operating system & version 150 | * version of KeePass 151 | * version of KeePassNatMsg 152 | * error message (if available) 153 | * used clients and their versions 154 | * URLs on which the problem occur (if available) 155 | 156 | ## URL matching: How does it work? 157 | 158 | KeePassNatMsg can receive 2 different URLs, called URL and SubmitURL. 159 | 160 | CompareToUrl = SubmitURL if set, URL otherwise 161 | 162 | For every entry, the [Levenshtein Distance](http://en.wikipedia.org/wiki/Levenshtein_distance) of his Entry-URL (or Title, if Entry-URL is not set) to the CompareToURL is calculated. 163 | 164 | Only the Entries with the minimal distance are returned. 165 | 166 | ###Example: 167 | Submit-Url: http://www.host.com/subdomain1/login 168 | 169 | Entry-URL|Distance 170 | ---|--- 171 | http://www.host.com/|16 172 | http://www.host.com/subdomain1|6 173 | http://www.host.com/subdomain2|7 174 | 175 | __Result:__ second entry is returned 176 | 177 | ## Protocol 178 | 179 | - View [detailed protocol information](https://github.com/keepassxreboot/keepassxc-browser/blob/develop/keepassxc-protocol.md). 180 | -------------------------------------------------------------------------------- /documentation/images/advanced-string-fields.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smorks/keepassnatmsg/ad2a7625986b0104106c89f6cdd490597aba3599/documentation/images/advanced-string-fields.png -------------------------------------------------------------------------------- /documentation/images/http-listener-error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smorks/keepassnatmsg/ad2a7625986b0104106c89f6cdd490597aba3599/documentation/images/http-listener-error.png -------------------------------------------------------------------------------- /documentation/images/keepass-context-menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smorks/keepassnatmsg/ad2a7625986b0104106c89f6cdd490597aba3599/documentation/images/keepass-context-menu.png -------------------------------------------------------------------------------- /documentation/images/keepass-duplicate-entry-references.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smorks/keepassnatmsg/ad2a7625986b0104106c89f6cdd490597aba3599/documentation/images/keepass-duplicate-entry-references.png -------------------------------------------------------------------------------- /documentation/images/menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smorks/keepassnatmsg/ad2a7625986b0104106c89f6cdd490597aba3599/documentation/images/menu.png -------------------------------------------------------------------------------- /documentation/images/options-advanced.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smorks/keepassnatmsg/ad2a7625986b0104106c89f6cdd490597aba3599/documentation/images/options-advanced.png -------------------------------------------------------------------------------- /documentation/images/options-general.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smorks/keepassnatmsg/ad2a7625986b0104106c89f6cdd490597aba3599/documentation/images/options-general.png -------------------------------------------------------------------------------- /documentation/images/options-keys.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smorks/keepassnatmsg/ad2a7625986b0104106c89f6cdd490597aba3599/documentation/images/options-keys.png -------------------------------------------------------------------------------- /documentation/images/psd/http-listener-error.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smorks/keepassnatmsg/ad2a7625986b0104106c89f6cdd490597aba3599/documentation/images/psd/http-listener-error.psd -------------------------------------------------------------------------------- /documentation/images/psd/keepass-context-menu.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smorks/keepassnatmsg/ad2a7625986b0104106c89f6cdd490597aba3599/documentation/images/psd/keepass-context-menu.psd -------------------------------------------------------------------------------- /documentation/images/psd/keepass-duplicate-entries-references.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smorks/keepassnatmsg/ad2a7625986b0104106c89f6cdd490597aba3599/documentation/images/psd/keepass-duplicate-entries-references.psd -------------------------------------------------------------------------------- /documentation/images/psd/options.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smorks/keepassnatmsg/ad2a7625986b0104106c89f6cdd490597aba3599/documentation/images/psd/options.psd -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smorks/keepassnatmsg/ad2a7625986b0104106c89f6cdd490597aba3599/logo.png --------------------------------------------------------------------------------