├── .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 | [](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 | 
77 |
78 | The options dialog will appear:
79 |
80 | 
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 | 
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 | [
](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 | 
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 | [
](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 | [
](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
--------------------------------------------------------------------------------