├── .gitattributes ├── latest-version.txt ├── update-version.txt ├── logo.png ├── test ├── lib │ ├── js.jar │ ├── test-harness.js │ └── sjcl.js ├── test.kdbx ├── test.js └── tests │ └── test-kph.js ├── KeePassHttp.plgx ├── mono ├── KeePassHttp.dll └── Newtonsoft.Json.dll ├── documentation └── images │ ├── menu.jpg │ ├── psd │ ├── options.psd │ ├── http-listener-error.psd │ ├── keepass-context-menu.psd │ └── keepass-duplicate-entries-references.psd │ ├── options-general.png │ ├── options-advanced.png │ ├── http-listener-error.png │ ├── keepass-context-menu.png │ ├── advanced-string-fields.png │ └── keepass-duplicate-entry-references.png ├── KeePassHttp ├── Newtonsoft.Json.dll ├── Resources │ └── earth_lock.png ├── PwEntryDatabase.cs ├── ConfirmAssociationForm.cs ├── Properties │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ └── Resources.resx ├── AccessControlForm.cs ├── ConfigOpt.cs ├── KeePassHttp.csproj ├── OptionsForm.resx ├── AccessControlForm.Designer.cs ├── AccessControlForm.resx ├── ConfirmAssociationForm.Designer.cs ├── ConfirmAssociationForm.resx ├── Protocol.cs ├── OptionsForm.cs ├── KeePassHttp.cs ├── OptionsForm.Designer.cs └── Handlers.cs ├── .gitignore ├── KeePassHttp.sln └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | *.plgx -text 2 | -------------------------------------------------------------------------------- /latest-version.txt: -------------------------------------------------------------------------------- 1 | : 2 | KeePassHttp:1.8.4.2 3 | : 4 | -------------------------------------------------------------------------------- /update-version.txt: -------------------------------------------------------------------------------- 1 | : 2 | KeePassHttp:1.8.4.2 3 | : 4 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freynder/keepasshttp/master/logo.png -------------------------------------------------------------------------------- /test/lib/js.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freynder/keepasshttp/master/test/lib/js.jar -------------------------------------------------------------------------------- /test/test.kdbx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freynder/keepasshttp/master/test/test.kdbx -------------------------------------------------------------------------------- /KeePassHttp.plgx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freynder/keepasshttp/master/KeePassHttp.plgx -------------------------------------------------------------------------------- /mono/KeePassHttp.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freynder/keepasshttp/master/mono/KeePassHttp.dll -------------------------------------------------------------------------------- /mono/Newtonsoft.Json.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freynder/keepasshttp/master/mono/Newtonsoft.Json.dll -------------------------------------------------------------------------------- /documentation/images/menu.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freynder/keepasshttp/master/documentation/images/menu.jpg -------------------------------------------------------------------------------- /KeePassHttp/Newtonsoft.Json.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freynder/keepasshttp/master/KeePassHttp/Newtonsoft.Json.dll -------------------------------------------------------------------------------- /KeePassHttp/Resources/earth_lock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freynder/keepasshttp/master/KeePassHttp/Resources/earth_lock.png -------------------------------------------------------------------------------- /documentation/images/psd/options.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freynder/keepasshttp/master/documentation/images/psd/options.psd -------------------------------------------------------------------------------- /documentation/images/options-general.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freynder/keepasshttp/master/documentation/images/options-general.png -------------------------------------------------------------------------------- /documentation/images/options-advanced.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freynder/keepasshttp/master/documentation/images/options-advanced.png -------------------------------------------------------------------------------- /documentation/images/http-listener-error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freynder/keepasshttp/master/documentation/images/http-listener-error.png -------------------------------------------------------------------------------- /documentation/images/keepass-context-menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freynder/keepasshttp/master/documentation/images/keepass-context-menu.png -------------------------------------------------------------------------------- /documentation/images/advanced-string-fields.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freynder/keepasshttp/master/documentation/images/advanced-string-fields.png -------------------------------------------------------------------------------- /documentation/images/psd/http-listener-error.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freynder/keepasshttp/master/documentation/images/psd/http-listener-error.psd -------------------------------------------------------------------------------- /documentation/images/psd/keepass-context-menu.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freynder/keepasshttp/master/documentation/images/psd/keepass-context-menu.psd -------------------------------------------------------------------------------- /.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 -------------------------------------------------------------------------------- /documentation/images/keepass-duplicate-entry-references.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freynder/keepasshttp/master/documentation/images/keepass-duplicate-entry-references.png -------------------------------------------------------------------------------- /documentation/images/psd/keepass-duplicate-entries-references.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freynder/keepasshttp/master/documentation/images/psd/keepass-duplicate-entries-references.psd -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | // To test, load up test.kdbx into KeePass, 2 | // the password is 'test' without quotes 3 | 4 | // be sure to have KeePassHttp installed and minimally working first 5 | 6 | // disable optimizations because envjs/rhino overruns them... 7 | Packages.org.mozilla.javascript.Context.getCurrentContext() 8 | .setOptimizationLevel(-1); 9 | 10 | load('lib/env.rhino.1.2.js'); 11 | load('lib/test-harness.js'); 12 | load('tests/test-kph.js'); 13 | 14 | test(); 15 | -------------------------------------------------------------------------------- /KeePassHttp/PwEntryDatabase.cs: -------------------------------------------------------------------------------- 1 | using KeePassLib; 2 | 3 | namespace KeePassHttp 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 | -------------------------------------------------------------------------------- /KeePassHttp.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 11.00 3 | # Visual C# Express 2010 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KeePassHttp", "KeePassHttp\KeePassHttp.csproj", "{9DFAB9D7-6BB5-4C6C-86EC-C67B494008D2}" 5 | EndProject 6 | Global 7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 8 | Debug|Any CPU = Debug|Any CPU 9 | Release|Any CPU = Release|Any CPU 10 | EndGlobalSection 11 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 12 | {9DFAB9D7-6BB5-4C6C-86EC-C67B494008D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 13 | {9DFAB9D7-6BB5-4C6C-86EC-C67B494008D2}.Debug|Any CPU.Build.0 = Debug|Any CPU 14 | {9DFAB9D7-6BB5-4C6C-86EC-C67B494008D2}.Release|Any CPU.ActiveCfg = Release|Any CPU 15 | {9DFAB9D7-6BB5-4C6C-86EC-C67B494008D2}.Release|Any CPU.Build.0 = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(SolutionProperties) = preSolution 18 | HideSolutionNode = FALSE 19 | EndGlobalSection 20 | EndGlobal 21 | -------------------------------------------------------------------------------- /KeePassHttp/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 KeePassHttp 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 | -------------------------------------------------------------------------------- /KeePassHttp/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("KeePassHttp")] 9 | [assembly: AssemblyDescription("A plugin to expose a secure HTTP interface to your KeePass database")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Perry Nguyen, Lukas Schulze")] 12 | [assembly: AssemblyProduct("KeePass Plugin")] 13 | [assembly: AssemblyCopyright("Copyright © Perry Nguyen, Lukas Schulze 2013")] 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("1.0.*")] 35 | [assembly: AssemblyVersion("2.34.0.0")] 36 | [assembly: AssemblyFileVersion("1.8.4.2")] 37 | -------------------------------------------------------------------------------- /KeePassHttp/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 KeePassHttp 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 KeePassHttpExt Plugin = null; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /KeePassHttp/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.18034 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 KeePassHttp.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", "4.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("KeePassHttp.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 resource of type System.Drawing.Bitmap. 65 | /// 66 | internal static System.Drawing.Bitmap earth_lock { 67 | get { 68 | object obj = ResourceManager.GetObject("earth_lock", resourceCulture); 69 | return ((System.Drawing.Bitmap)(obj)); 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /KeePassHttp/ConfigOpt.cs: -------------------------------------------------------------------------------- 1 | using KeePass.App.Configuration; 2 | 3 | namespace KeePassHttp 4 | { 5 | public class ConfigOpt 6 | { 7 | readonly AceCustomConfig _config; 8 | const string ReceiveCredentialNotificationKey = "KeePassHttp_ReceiveCredentialNotification"; 9 | const string SpecificMatchingOnlyKey = "KeePassHttp_SpecificMatchingOnly"; 10 | const string UnlockDatabaseRequestKey = "KeePassHttp_UnlockDatabaseRequest"; 11 | const string AlwaysAllowAccessKey = "KeePassHttp_AlwaysAllowAccess"; 12 | const string AlwaysAllowUpdatesKey = "KeePassHttp_AlwaysAllowUpdates"; 13 | const string SearchInAllOpenedDatabasesKey = "KeePassHttp_SearchInAllOpenedDatabases"; 14 | const string HideExpiredKey = "KeePassHttp_HideExpired"; 15 | const string MatchSchemesKey = "KeePassHttp_MatchSchemes"; 16 | const string ReturnStringFieldsKey = "KeePassHttp_ReturnStringFields"; 17 | const string ReturnStringFieldsWithKphOnlyKey = "KeePassHttp_ReturnStringFieldsWithKphOnly"; 18 | const string SortResultByUsernameKey = "KeePassHttp_SortResultByUsername"; 19 | const string ListenerPortKey = "KeePassHttp_ListenerPort"; 20 | const string ListenerHostKey = "KeePassHttp_ListenerHost"; 21 | 22 | public ConfigOpt(AceCustomConfig config) 23 | { 24 | _config = config; 25 | } 26 | 27 | public bool ReceiveCredentialNotification 28 | { 29 | get { return _config.GetBool(ReceiveCredentialNotificationKey, true); } 30 | set { _config.SetBool(ReceiveCredentialNotificationKey, value); } 31 | } 32 | 33 | public bool UnlockDatabaseRequest 34 | { 35 | get { return _config.GetBool(UnlockDatabaseRequestKey, false); } 36 | set { _config.SetBool(UnlockDatabaseRequestKey, value); } 37 | } 38 | 39 | public bool SpecificMatchingOnly 40 | { 41 | get { return _config.GetBool(SpecificMatchingOnlyKey, false); } 42 | set { _config.SetBool(SpecificMatchingOnlyKey, value); } 43 | } 44 | 45 | public bool AlwaysAllowAccess 46 | { 47 | get { return _config.GetBool(AlwaysAllowAccessKey, false); } 48 | set { _config.SetBool(AlwaysAllowAccessKey, value); } 49 | } 50 | 51 | public bool AlwaysAllowUpdates 52 | { 53 | get { return _config.GetBool(AlwaysAllowUpdatesKey, false); } 54 | set { _config.SetBool(AlwaysAllowUpdatesKey, value); } 55 | } 56 | 57 | public bool SearchInAllOpenedDatabases 58 | { 59 | get { return _config.GetBool(SearchInAllOpenedDatabasesKey, false); } 60 | set { _config.SetBool(SearchInAllOpenedDatabasesKey, value); } 61 | } 62 | 63 | public bool HideExpired 64 | { 65 | get { return _config.GetBool(HideExpiredKey, false); } 66 | set { _config.SetBool(HideExpiredKey, value); } 67 | } 68 | public bool MatchSchemes 69 | { 70 | get { return _config.GetBool(MatchSchemesKey, false); } 71 | set { _config.SetBool(MatchSchemesKey, value); } 72 | } 73 | 74 | public bool ReturnStringFields 75 | { 76 | get { return _config.GetBool(ReturnStringFieldsKey, false); } 77 | set { _config.SetBool(ReturnStringFieldsKey, value); } 78 | } 79 | 80 | public bool ReturnStringFieldsWithKphOnly 81 | { 82 | get { return _config.GetBool(ReturnStringFieldsWithKphOnlyKey, true); } 83 | set { _config.SetBool(ReturnStringFieldsWithKphOnlyKey, value); } 84 | } 85 | 86 | public bool SortResultByUsername 87 | { 88 | get { return _config.GetBool(SortResultByUsernameKey, true); } 89 | set { _config.SetBool(SortResultByUsernameKey, value); } 90 | } 91 | 92 | public long ListenerPort 93 | { 94 | get { return _config.GetLong(ListenerPortKey, KeePassHttpExt.DEFAULT_PORT); } 95 | set { _config.SetLong(ListenerPortKey, value); } 96 | } 97 | 98 | public string ListenerHost 99 | { 100 | get { return _config.GetString(ListenerHostKey, KeePassHttpExt.DEFAULT_HOST); } 101 | set { _config.SetString(ListenerHostKey, value); } 102 | } 103 | } 104 | } -------------------------------------------------------------------------------- /test/lib/test-harness.js: -------------------------------------------------------------------------------- 1 | load('lib/sjcl.js'); 2 | 3 | function print(s) { java.lang.System.out.print(s) } 4 | function println(s) { java.lang.System.out.println(s) } 5 | 6 | function Lock() { 7 | this.waitMethod = this.class.getMethod("wait", null); 8 | this.notifyMethod = this.class.getMethod("notify", null); 9 | } 10 | Lock.prototype = { 11 | get class() { 12 | return java.lang.Class.forName("java.lang.Object"); 13 | }, 14 | done: false, 15 | 16 | wait: sync(function() { 17 | if (this.done) { 18 | this.done = false; 19 | return; 20 | } 21 | this.waitMethod.invoke(this, null); 22 | }), 23 | notify: sync(function() { 24 | this.done = true; 25 | this.notifyMethod.invoke(this, null); 26 | }), 27 | }; 28 | 29 | var xhr = { 30 | request: function(method, request, callback) { 31 | var xhr = new XMLHttpRequest(); 32 | var onchange = function() { 33 | if (xhr.readyState == 4) { 34 | lock.notify(); 35 | callback(xhr.responseText); 36 | } 37 | }; 38 | //print("Request: " + request); 39 | xhr.onreadystatechange = onchange; 40 | xhr.open(method, "http://localhost:19455", true); 41 | xhr.send(request); 42 | lock.wait(); 43 | assert_status(xhr.status); 44 | }, 45 | post: function(request, callback) { 46 | xhr.request("POST", request, callback); 47 | }, 48 | success: function(s) { 49 | return s >= 200 && s <= 299; 50 | } 51 | }; 52 | 53 | function generate_iv() { 54 | var iv = []; 55 | for (var i = 0; i < 16; i++) { 56 | iv.push(String.fromCharCode(Math.floor(Math.random() * 256))); 57 | } 58 | iv = iv.join(''); 59 | return btoa(iv); 60 | } 61 | 62 | function set_verifier(request) { 63 | var iv = generate_iv(); 64 | request.Id = name; 65 | request.Nonce = iv; 66 | request.Verifier = encrypt(iv, iv); 67 | } 68 | 69 | function assert_status(status) { 70 | if (!xhr.success(status)) { 71 | throw new Error("Bad status: " + status); 72 | } 73 | } 74 | 75 | function assert_success(response) { 76 | var r = JSON.parse(response); 77 | if (!r.Success) 78 | throw new Error("Non-successful response"); 79 | } 80 | 81 | function assert_equals(a, b) { 82 | if (a !== b) 83 | throw new Error("Expected: [" + a + "] got [" + b + "]"); 84 | } 85 | 86 | 87 | function skiptest(tfn) { println("Skipping test: " + tfn.name) } // noop 88 | var test = (function() { 89 | var _tests = []; 90 | return function(tfn) { 91 | var failures = 0; 92 | if (tfn) 93 | _tests.push(tfn); 94 | else { 95 | for (var i in _tests) { 96 | print("Test: " + _tests[i].name); 97 | try { 98 | _tests[i](); 99 | println(" -- OK"); 100 | } catch (e) { 101 | println(" -- failed: " + e); 102 | failures++; 103 | } 104 | } 105 | if (failures) { 106 | console.log("\nFailed %d/%d tests", failures, _tests.length); 107 | } 108 | else { 109 | println("\nAll tests passed"); 110 | } 111 | return failures == 0; 112 | } 113 | } 114 | })(); 115 | 116 | function get_logins(url, submiturl, realm, cb) { 117 | var request = { 118 | RequestType: "get-logins", 119 | }; 120 | set_verifier(request); 121 | var iv = request.Nonce; 122 | request.Url = encrypt(url, iv); 123 | if (submiturl) 124 | request.SubmitUrl = encrypt(submiturl, iv); 125 | if (realm) 126 | request.Realm = encrypt(realm, iv); 127 | xhr.post(JSON.stringify(request), function(r) { 128 | cb(r); 129 | }); 130 | } 131 | 132 | function encrypt(data, iv) { 133 | var enc = sjcl.mode.cbc.encrypt( 134 | new sjcl.cipher.aes(sjcl.codec.base64.toBits(key)), 135 | sjcl.codec.utf8String.toBits(data), 136 | sjcl.codec.base64.toBits(iv)); 137 | return sjcl.codec.base64.fromBits(enc); 138 | } 139 | 140 | function decrypt(data, iv) { 141 | var dec = sjcl.mode.cbc.decrypt( 142 | new sjcl.cipher.aes(sjcl.codec.base64.toBits(key)), 143 | sjcl.codec.base64.toBits(data), 144 | sjcl.codec.base64.toBits(iv)); 145 | return sjcl.codec.utf8String.fromBits(dec); 146 | } 147 | -------------------------------------------------------------------------------- /KeePassHttp/KeePassHttp.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 | KeePassHttp 12 | KeePassHttp 13 | v4.0 14 | 512 15 | 16 | 17 | 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | 34 | 35 | 36 | C:\Program Files (x86)\KeePass Password Safe 2\KeePass.exe 37 | False 38 | 39 | 40 | .\Newtonsoft.Json.dll 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | Form 54 | 55 | 56 | AccessControlForm.cs 57 | 58 | 59 | 60 | Form 61 | 62 | 63 | OptionsForm.cs 64 | 65 | 66 | True 67 | True 68 | Resources.resx 69 | 70 | 71 | 72 | 73 | Form 74 | 75 | 76 | ConfirmAssociationForm.cs 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | AccessControlForm.cs 88 | 89 | 90 | ConfirmAssociationForm.cs 91 | 92 | 93 | OptionsForm.cs 94 | 95 | 96 | ResXFileCodeGenerator 97 | Resources.Designer.cs 98 | 99 | 100 | 101 | 102 | 103 | 104 | 111 | 112 | -------------------------------------------------------------------------------- /test/tests/test-kph.js: -------------------------------------------------------------------------------- 1 | var name = "Test Key"; 2 | var key = "Lgh8xMEkV2j10bG7O42GjCibsUEpM80T7Db+skKGiNc="; 3 | 4 | var lock = new Lock(); 5 | 6 | test(function test_associate_ok() { 7 | var request = { 8 | RequestType: "test-associate", 9 | }; 10 | set_verifier(request); 11 | var resp; 12 | xhr.post(JSON.stringify(request), function(r) { 13 | resp = r; 14 | lock.notify(); 15 | }); 16 | lock.wait(); 17 | assert_success(resp); 18 | }); 19 | 20 | test(function get_logins_empty() { 21 | var resp; 22 | get_logins("http://www.doesnotexist.com/", null, null, function(r) { 23 | resp = r; 24 | lock.notify(); 25 | }); 26 | lock.wait(); 27 | var response = JSON.parse(resp); 28 | assert_equals(0, response.Count); 29 | }); 30 | 31 | test(function get_logins_match_partial_title() { 32 | var resp; 33 | get_logins("http://www.google.com/", null, null, function(r) { 34 | resp = r; 35 | lock.notify(); 36 | }); 37 | lock.wait(); 38 | var response = JSON.parse(resp); 39 | assert_equals(1, response.Entries.length); 40 | assert_equals("google-user", decrypt(response.Entries[0].Login, response.Nonce)); 41 | }); 42 | 43 | test(function get_logins_match_exact_title() { 44 | var resp; 45 | get_logins("http://www.yahoo.com/", null, null, function(r) { 46 | resp = r; 47 | lock.notify(); 48 | }); 49 | lock.wait(); 50 | var response = JSON.parse(resp); 51 | assert_equals(1, response.Entries.length); 52 | assert_equals("www.yahoo-user", decrypt(response.Entries[0].Login, response.Nonce)); 53 | }); 54 | 55 | test(function get_logins_match_partial_title_yahoo() { 56 | var resp; 57 | get_logins("http://yahoo.com/", null, null, function(r) { 58 | resp = r; 59 | lock.notify(); 60 | }); 61 | lock.wait(); 62 | var response = JSON.parse(resp); 63 | assert_equals(1, response.Entries.length); 64 | assert_equals("yahoo-user", decrypt(response.Entries[0].Login, response.Nonce)); 65 | }); 66 | 67 | test(function get_logins_match_host_urlfield() { 68 | var resp; 69 | get_logins("http://citi.com/", null, null, function(r) { 70 | resp = r; 71 | lock.notify(); 72 | }); 73 | lock.wait(); 74 | var response = JSON.parse(resp); 75 | assert_equals(1, response.Entries.length); 76 | assert_equals("citi-user", decrypt(response.Entries[0].Login, response.Nonce)); 77 | }); 78 | 79 | test(function get_logins_match_real_urlfield() { 80 | var resp; 81 | get_logins("http://citi1.com/", null, null, function(r) { 82 | resp = r; 83 | lock.notify(); 84 | }); 85 | lock.wait(); 86 | var response = JSON.parse(resp); 87 | assert_equals(1, response.Entries.length); 88 | assert_equals("citi1-user", decrypt(response.Entries[0].Login, response.Nonce)); 89 | }); 90 | 91 | test(function get_logins_match_title_and_urlfield() { 92 | var resp; 93 | get_logins("https://cititest.com/", null, null, function(r) { 94 | resp = r; 95 | lock.notify(); 96 | }); 97 | lock.wait(); 98 | var response = JSON.parse(resp); 99 | assert_equals(1, response.Entries.length); 100 | assert_equals("cititest-user", decrypt(response.Entries[0].Login, response.Nonce)); 101 | }); 102 | 103 | test(function get_logins_match_title_urltitle_mismatch() { 104 | var resp; 105 | get_logins("https://bogustest.com/", null, null, function(r) { 106 | resp = r; 107 | lock.notify(); 108 | }); 109 | lock.wait(); 110 | var response = JSON.parse(resp); 111 | assert_equals(1, response.Entries.length); 112 | assert_equals("bogustest-user", decrypt(response.Entries[0].Login, response.Nonce)); 113 | }); 114 | 115 | test(function get_logins_match_url_urltitle_mismatch() { 116 | var resp; 117 | get_logins("https://www.bogustest.com/", null, null, function(r) { 118 | resp = r; 119 | lock.notify(); 120 | }); 121 | lock.wait(); 122 | var response = JSON.parse(resp); 123 | assert_equals(1, response.Entries.length); 124 | assert_equals("bogustest-user", decrypt(response.Entries[0].Login, response.Nonce)); 125 | }); 126 | 127 | test(function get_logins_match_title_urltitle_mismatch2() { 128 | var resp; 129 | get_logins("https://bogustest1.com/", null, null, function(r) { 130 | resp = r; 131 | lock.notify(); 132 | }); 133 | lock.wait(); 134 | var response = JSON.parse(resp); 135 | assert_equals(1, response.Entries.length); 136 | assert_equals("bogustest1-user", decrypt(response.Entries[0].Login, response.Nonce)); 137 | }); 138 | 139 | test(function get_logins_match_url_urltitle_mismatch2() { 140 | var resp; 141 | get_logins("https://www.bogustest1.com/", null, null, function(r) { 142 | resp = r; 143 | lock.notify(); 144 | }); 145 | lock.wait(); 146 | var response = JSON.parse(resp); 147 | assert_equals(1, response.Entries.length); 148 | assert_equals("bogustest1-user", decrypt(response.Entries[0].Login, response.Nonce)); 149 | }); 150 | 151 | test(function get_logins_subpath() { 152 | var resp; 153 | get_logins("http://www.host.com", "http://www.host.com/path1", null, function(r) { 154 | resp = r; 155 | lock.notify(); 156 | }); 157 | lock.wait(); 158 | var response = JSON.parse(resp); 159 | assert_equals(1, response.Entries.length); 160 | assert_equals("user1", decrypt(response.Entries[0].Login, response.Nonce)); 161 | }); 162 | test(function get_logins_subpath_2() { 163 | var resp; 164 | get_logins("http://www.host.com", "http://www.host.com/path2?param=value", null, function(r) { 165 | resp = r; 166 | lock.notify(); 167 | }); 168 | lock.wait(); 169 | var response = JSON.parse(resp); 170 | assert_equals(1, response.Entries.length); 171 | assert_equals("user2", decrypt(response.Entries[0].Login, response.Nonce)); 172 | }); 173 | -------------------------------------------------------------------------------- /KeePassHttp/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=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | -------------------------------------------------------------------------------- /KeePassHttp/AccessControlForm.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace KeePassHttp 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.TabIndex = 0; 72 | // 73 | // RememberCheck 74 | // 75 | this.RememberCheck.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); 76 | this.RememberCheck.AutoSize = true; 77 | this.RememberCheck.Location = new System.Drawing.Point(12, 211); 78 | this.RememberCheck.Name = "RememberCheck"; 79 | this.RememberCheck.Size = new System.Drawing.Size(138, 17); 80 | this.RememberCheck.TabIndex = 3; 81 | this.RememberCheck.Text = "Remember this decision"; 82 | this.RememberCheck.UseVisualStyleBackColor = true; 83 | // 84 | // ConfirmTextLabel 85 | // 86 | this.ConfirmTextLabel.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) 87 | | System.Windows.Forms.AnchorStyles.Right))); 88 | this.ConfirmTextLabel.Location = new System.Drawing.Point(9, 136); 89 | this.ConfirmTextLabel.Name = "ConfirmTextLabel"; 90 | this.ConfirmTextLabel.Size = new System.Drawing.Size(323, 65); 91 | this.ConfirmTextLabel.TabIndex = 4; 92 | this.ConfirmTextLabel.Text = "www.somewhere.com has requested access to passwords for the above item(s). Please" + 93 | " select whether you want to allow access."; 94 | // 95 | // AccessControlForm 96 | // 97 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 98 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 99 | this.ClientSize = new System.Drawing.Size(344, 242); 100 | this.Controls.Add(this.ConfirmTextLabel); 101 | this.Controls.Add(this.RememberCheck); 102 | this.Controls.Add(DenyButton); 103 | this.Controls.Add(AllowButton); 104 | this.Controls.Add(this.EntriesBox); 105 | this.MaximizeBox = false; 106 | this.MinimizeBox = false; 107 | this.Name = "AccessControlForm"; 108 | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; 109 | this.Text = "KeePassHttp: Confirm Access"; 110 | this.ResumeLayout(false); 111 | this.PerformLayout(); 112 | 113 | } 114 | 115 | #endregion 116 | 117 | private System.Windows.Forms.ListBox EntriesBox; 118 | private System.Windows.Forms.CheckBox RememberCheck; 119 | private System.Windows.Forms.Label ConfirmTextLabel; 120 | } 121 | } -------------------------------------------------------------------------------- /KeePassHttp/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=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | 122 | ..\Resources\earth_lock.png;System.Drawing.Bitmap, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 123 | 124 | -------------------------------------------------------------------------------- /KeePassHttp/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=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | False 122 | 123 | 124 | False 125 | 126 | -------------------------------------------------------------------------------- /KeePassHttp/ConfirmAssociationForm.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace KeePassHttp 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 = "KeePassHttp: Confirm New Key Association"; 131 | this.ResumeLayout(false); 132 | this.PerformLayout(); 133 | 134 | } 135 | 136 | #endregion 137 | 138 | private System.Windows.Forms.Label KeyLabel; 139 | private System.Windows.Forms.TextBox KeyName; 140 | private System.Windows.Forms.Button Save; 141 | private System.Windows.Forms.Button Cancel; 142 | 143 | } 144 | } -------------------------------------------------------------------------------- /KeePassHttp/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=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | False 122 | 123 | 124 | False 125 | 126 | 127 | False 128 | 129 | -------------------------------------------------------------------------------- /KeePassHttp/Protocol.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Security.Cryptography; 3 | using System.Text; 4 | 5 | using KeePass.Plugins; 6 | using System.Reflection; 7 | using System.Diagnostics; 8 | 9 | namespace KeePassHttp 10 | { 11 | public sealed partial class KeePassHttpExt : Plugin 12 | { 13 | private static string encode64(byte[] b) 14 | { 15 | return System.Convert.ToBase64String(b); 16 | } 17 | 18 | private static byte[] decode64(string s) 19 | { 20 | return System.Convert.FromBase64String(s); 21 | } 22 | private bool VerifyRequest(Request r, Aes aes) 23 | { 24 | var entry = GetConfigEntry(false); 25 | if (entry == null) 26 | return false; 27 | var s = entry.Strings.Get(ASSOCIATE_KEY_PREFIX + r.Id); 28 | if (s == null) 29 | return false; 30 | 31 | return TestRequestVerifier(r, aes, s.ReadString()); 32 | } 33 | 34 | private bool TestRequestVerifier(Request r, Aes aes, string key) 35 | { 36 | var success = false; 37 | var crypted = decode64(r.Verifier); 38 | 39 | aes.Key = decode64(key); 40 | aes.IV = decode64(r.Nonce); 41 | 42 | using (var dec = aes.CreateDecryptor()) 43 | { 44 | try { 45 | var buf = dec.TransformFinalBlock(crypted, 0, crypted.Length); 46 | var value = Encoding.UTF8.GetString(buf); 47 | success = value == r.Nonce; 48 | } catch (CryptographicException) { } // implicit failure 49 | } 50 | return success; 51 | } 52 | 53 | private void SetResponseVerifier(Response r, Aes aes) 54 | { 55 | aes.GenerateIV(); 56 | r.Nonce = encode64(aes.IV); 57 | r.Verifier = CryptoTransform(r.Nonce, false, true, aes, CMode.ENCRYPT); 58 | } 59 | } 60 | public class Request 61 | { 62 | public const string GET_LOGINS = "get-logins"; 63 | public const string GET_LOGINS_COUNT = "get-logins-count"; 64 | public const string GET_ALL_LOGINS = "get-all-logins"; 65 | public const string SET_LOGIN = "set-login"; 66 | public const string ASSOCIATE = "associate"; 67 | public const string TEST_ASSOCIATE = "test-associate"; 68 | public const string GENERATE_PASSWORD = "generate-password"; 69 | 70 | public string RequestType; 71 | 72 | /// 73 | /// Sort selection by best URL matching for given hosts 74 | /// 75 | public string SortSelection; 76 | 77 | /// 78 | /// Trigger unlock of database even if feature is disabled in KPH (because of user interaction to fill-in) 79 | /// 80 | public string TriggerUnlock; 81 | 82 | /// 83 | /// Always encrypted, used with set-login, uuid is set 84 | /// if modifying an existing login 85 | /// 86 | public string Login; 87 | public string Password; 88 | public string Uuid; 89 | 90 | /// 91 | /// Always encrypted, used with get and set-login 92 | /// 93 | public string Url; 94 | 95 | /// 96 | /// Always encrypted, used with get-login 97 | /// 98 | public string SubmitUrl; 99 | 100 | /// 101 | /// Send the AES key ID with the 'associate' request 102 | /// 103 | public string Key; 104 | 105 | /// 106 | /// Always required, an identifier given by the KeePass user 107 | /// 108 | public string Id; 109 | /// 110 | /// A value used to ensure that the correct key has been chosen, 111 | /// it is always the value of Nonce encrypted with Key 112 | /// 113 | public string Verifier; 114 | /// 115 | /// Nonce value used in conjunction with all encrypted fields, 116 | /// randomly generated for each request 117 | /// 118 | public string Nonce; 119 | 120 | /// 121 | /// Realm value used for filtering results. Always encrypted. 122 | /// 123 | public string Realm; 124 | } 125 | 126 | public class Response 127 | { 128 | public Response(string request, string hash) 129 | { 130 | RequestType = request; 131 | 132 | if (request == Request.GET_LOGINS || request == Request.GET_ALL_LOGINS || request == Request.GENERATE_PASSWORD) 133 | Entries = new List(); 134 | else 135 | Entries = null; 136 | 137 | Assembly assembly = Assembly.GetExecutingAssembly(); 138 | FileVersionInfo fvi = FileVersionInfo.GetVersionInfo(assembly.Location); 139 | this.Version = fvi.ProductVersion; 140 | 141 | this.Hash = hash; 142 | } 143 | 144 | /// 145 | /// Mirrors the request type of KeePassRequest 146 | /// 147 | public string RequestType; 148 | 149 | public string Error = null; 150 | 151 | public bool Success = false; 152 | 153 | /// 154 | /// The user selected string as a result of 'associate', 155 | /// always returned on every request 156 | /// 157 | public string Id; 158 | 159 | /// 160 | /// response to get-logins-count, number of entries for requested Url 161 | /// 162 | public int Count = 0; 163 | 164 | /// 165 | /// response the current version of KeePassHttp 166 | /// 167 | public string Version = ""; 168 | 169 | /// 170 | /// response an unique hash of the database composed of RootGroup UUid and RecycleBin UUid 171 | /// 172 | public string Hash = ""; 173 | 174 | /// 175 | /// The resulting entries for a get-login request 176 | /// 177 | public List Entries { get; private set; } 178 | 179 | /// 180 | /// Nonce value used in conjunction with all encrypted fields, 181 | /// randomly generated for each request 182 | /// 183 | public string Nonce; 184 | 185 | /// 186 | /// Same purpose as Request.Verifier, but a new value 187 | /// 188 | public string Verifier; 189 | } 190 | public class ResponseEntry 191 | { 192 | public ResponseEntry() { } 193 | public ResponseEntry(string name, string login, string password, string uuid, List stringFields) 194 | { 195 | Login = login; 196 | Password = password; 197 | Uuid = uuid; 198 | Name = name; 199 | StringFields = stringFields; 200 | } 201 | public string Login; 202 | public string Password; 203 | public string Uuid; 204 | public string Name; 205 | public List StringFields = null; 206 | } 207 | public class ResponseStringField 208 | { 209 | public ResponseStringField() {} 210 | public ResponseStringField(string key, string value) 211 | { 212 | Key = key; 213 | Value = value; 214 | } 215 | public string Key; 216 | public string Value; 217 | } 218 | public class KeePassHttpEntryConfig 219 | { 220 | public HashSet Allow = new HashSet(); 221 | public HashSet Deny = new HashSet(); 222 | public string Realm = null; 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /KeePassHttp/OptionsForm.cs: -------------------------------------------------------------------------------- 1 | using KeePassLib; 2 | using KeePassHttp; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.ComponentModel; 6 | using System.Data; 7 | using System.Drawing; 8 | using System.Linq; 9 | using System.Text; 10 | using System.Windows.Forms; 11 | using KeePassLib.Collections; 12 | 13 | namespace KeePassHttp 14 | { 15 | public partial class OptionsForm : Form 16 | { 17 | readonly ConfigOpt _config; 18 | private bool _restartRequired = false; 19 | 20 | public OptionsForm(ConfigOpt config) 21 | { 22 | _config = config; 23 | InitializeComponent(); 24 | } 25 | 26 | 27 | private PwEntry GetConfigEntry(PwDatabase db) 28 | { 29 | var kphe = new KeePassHttpExt(); 30 | var root = db.RootGroup; 31 | var uuid = new PwUuid(kphe.KEEPASSHTTP_UUID); 32 | var entry = root.FindEntry(uuid, false); 33 | return entry; 34 | } 35 | 36 | private void OptionsForm_Load(object sender, EventArgs e) 37 | { 38 | credNotifyCheckbox.Checked = _config.ReceiveCredentialNotification; 39 | credMatchingCheckbox.Checked = _config.SpecificMatchingOnly; 40 | unlockDatabaseCheckbox.Checked = _config.UnlockDatabaseRequest; 41 | credAllowAccessCheckbox.Checked = _config.AlwaysAllowAccess; 42 | credAllowUpdatesCheckbox.Checked = _config.AlwaysAllowUpdates; 43 | credSearchInAllOpenedDatabases.Checked = _config.SearchInAllOpenedDatabases; 44 | hideExpiredCheckbox.Checked = _config.HideExpired; 45 | matchSchemesCheckbox.Checked = _config.MatchSchemes; 46 | returnStringFieldsCheckbox.Checked = _config.ReturnStringFields; 47 | returnStringFieldsWithKphOnlyCheckBox.Checked = _config.ReturnStringFieldsWithKphOnly; 48 | SortByUsernameRadioButton.Checked = _config.SortResultByUsername; 49 | SortByTitleRadioButton.Checked = !_config.SortResultByUsername; 50 | portNumber.Value = _config.ListenerPort; 51 | hostName.Text = _config.ListenerHost; 52 | 53 | this.returnStringFieldsCheckbox_CheckedChanged(null, EventArgs.Empty); 54 | } 55 | 56 | private void okButton_Click(object sender, EventArgs e) 57 | { 58 | _config.ReceiveCredentialNotification = credNotifyCheckbox.Checked; 59 | _config.SpecificMatchingOnly = credMatchingCheckbox.Checked; 60 | _config.UnlockDatabaseRequest = unlockDatabaseCheckbox.Checked; 61 | _config.AlwaysAllowAccess = credAllowAccessCheckbox.Checked; 62 | _config.AlwaysAllowUpdates = credAllowUpdatesCheckbox.Checked; 63 | _config.SearchInAllOpenedDatabases = credSearchInAllOpenedDatabases.Checked; 64 | _config.HideExpired = hideExpiredCheckbox.Checked; 65 | _config.MatchSchemes = matchSchemesCheckbox.Checked; 66 | _config.ReturnStringFields = returnStringFieldsCheckbox.Checked; 67 | _config.ReturnStringFieldsWithKphOnly = returnStringFieldsWithKphOnlyCheckBox.Checked; 68 | _config.SortResultByUsername = SortByUsernameRadioButton.Checked; 69 | _config.ListenerPort = (int)portNumber.Value; 70 | _config.ListenerHost = hostName.Text; 71 | if (_restartRequired) 72 | { 73 | MessageBox.Show( 74 | "You have successfully changed the port number and/or the host name.\nA restart of KeePass is required!\n\nPlease restart KeePass now.", 75 | "Restart required!", 76 | MessageBoxButtons.OK, 77 | MessageBoxIcon.Information 78 | ); 79 | } 80 | } 81 | 82 | private void removeButton_Click(object sender, EventArgs e) 83 | { 84 | if (KeePass.Program.MainForm.DocumentManager.ActiveDatabase.IsOpen) 85 | { 86 | PwDatabase db = KeePass.Program.MainForm.DocumentManager.ActiveDatabase; 87 | var entry = GetConfigEntry(db); 88 | if (entry != null) 89 | { 90 | List deleteKeys = new List(); 91 | 92 | foreach (var s in entry.Strings) 93 | { 94 | if (s.Key.IndexOf(KeePassHttpExt.ASSOCIATE_KEY_PREFIX) == 0) 95 | { 96 | deleteKeys.Add(s.Key); 97 | } 98 | } 99 | 100 | 101 | if (deleteKeys.Count > 0) 102 | { 103 | PwObjectList m_vHistory = entry.History.CloneDeep(); 104 | entry.History = m_vHistory; 105 | entry.CreateBackup(null); 106 | 107 | foreach (var key in deleteKeys) 108 | { 109 | entry.Strings.Remove(key); 110 | } 111 | 112 | entry.Touch(true); 113 | KeePass.Program.MainForm.UpdateUI(false, null, true, db.RootGroup, true, null, true); 114 | MessageBox.Show( 115 | String.Format("Successfully removed {0} encryption-key{1} from KeePassHttp Settings.", deleteKeys.Count.ToString(), deleteKeys.Count == 1 ? "" : "s"), 116 | String.Format("Removed {0} key{1} from database", deleteKeys.Count.ToString(), deleteKeys.Count == 1 ? "" : "s"), 117 | MessageBoxButtons.OK, 118 | MessageBoxIcon.Information 119 | ); 120 | } 121 | else 122 | { 123 | MessageBox.Show( 124 | "No shared encryption-keys found in KeePassHttp Settings.", "No keys found", 125 | MessageBoxButtons.OK, 126 | MessageBoxIcon.Information 127 | ); 128 | } 129 | } 130 | else 131 | { 132 | MessageBox.Show("The active database does not contain an entry of KeePassHttp Settings.", "KeePassHttp Settings not available!", MessageBoxButtons.OK, MessageBoxIcon.Information); 133 | } 134 | } 135 | else 136 | { 137 | 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); 138 | } 139 | } 140 | 141 | private void removePermissionsButton_Click(object sender, EventArgs e) 142 | { 143 | if (KeePass.Program.MainForm.DocumentManager.ActiveDatabase.IsOpen) 144 | { 145 | PwDatabase db = KeePass.Program.MainForm.DocumentManager.ActiveDatabase; 146 | 147 | uint counter = 0; 148 | var entries = db.RootGroup.GetEntries(true); 149 | 150 | if (entries.Count() > 999) 151 | { 152 | MessageBox.Show( 153 | 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()), 154 | String.Format("{0} entries detected", entries.Count().ToString()), 155 | MessageBoxButtons.OK, 156 | MessageBoxIcon.Information 157 | ); 158 | } 159 | 160 | foreach (var entry in entries) 161 | { 162 | foreach (var str in entry.Strings) 163 | { 164 | if (str.Key == KeePassHttpExt.KEEPASSHTTP_NAME) 165 | { 166 | PwObjectList m_vHistory = entry.History.CloneDeep(); 167 | entry.History = m_vHistory; 168 | entry.CreateBackup(null); 169 | 170 | entry.Strings.Remove(str.Key); 171 | 172 | entry.Touch(true); 173 | 174 | counter += 1; 175 | 176 | break; 177 | } 178 | } 179 | } 180 | 181 | if (counter > 0) 182 | { 183 | KeePass.Program.MainForm.UpdateUI(false, null, true, db.RootGroup, true, null, true); 184 | MessageBox.Show( 185 | String.Format("Successfully removed permissions from {0} entr{1}.", counter.ToString(), counter == 1 ? "y" : "ies"), 186 | String.Format("Removed permissions from {0} entr{1}", counter.ToString(), counter == 1 ? "y" : "ies"), 187 | MessageBoxButtons.OK, 188 | MessageBoxIcon.Information 189 | ); 190 | } 191 | else 192 | { 193 | MessageBox.Show( 194 | "The active database does not contain an entry with permissions.", 195 | "No entry with permissions found!", 196 | MessageBoxButtons.OK, 197 | MessageBoxIcon.Information 198 | ); 199 | } 200 | } 201 | else 202 | { 203 | 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); 204 | } 205 | } 206 | 207 | private void portNumber_ValueChanged(object sender, EventArgs e) 208 | { 209 | SetRestartRequired(); 210 | } 211 | 212 | private void hostName_TextChanged(object sender, EventArgs e) 213 | { 214 | SetRestartRequired(); 215 | } 216 | 217 | private void SetRestartRequired() 218 | { 219 | _restartRequired = (_config.ListenerPort != portNumber.Value) || (_config.ListenerHost != hostName.Text); 220 | } 221 | 222 | private void returnStringFieldsCheckbox_CheckedChanged(object sender, EventArgs e) 223 | { 224 | this.returnStringFieldsWithKphOnlyCheckBox.Enabled = this.returnStringFieldsCheckbox.Checked; 225 | } 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /KeePassHttp/KeePassHttp.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading; 7 | using System.Net; 8 | using System.Windows.Forms; 9 | using System.Runtime.Remoting.Metadata.W3cXsd2001; 10 | using System.Security.Cryptography; 11 | 12 | using KeePass.Plugins; 13 | using KeePass.UI; 14 | using KeePassLib; 15 | using KeePassLib.Collections; 16 | using KeePassLib.Security; 17 | 18 | using Newtonsoft.Json; 19 | using KeePass.Util.Spr; 20 | using KeePassLib.Serialization; 21 | using System.Resources; 22 | 23 | namespace KeePassHttp 24 | { 25 | internal delegate void RequestHandler(Request request, Response response, Aes aes); 26 | 27 | public enum CMode { ENCRYPT, DECRYPT } 28 | public sealed partial class KeePassHttpExt : Plugin 29 | { 30 | 31 | /// 32 | /// an arbitrarily generated uuid for the keepasshttp root entry 33 | /// 34 | public readonly byte[] KEEPASSHTTP_UUID = { 35 | 0x34, 0x69, 0x7a, 0x40, 0x8a, 0x5b, 0x41, 0xc0, 36 | 0x9f, 0x36, 0x89, 0x7d, 0x62, 0x3e, 0xcb, 0x31 37 | }; 38 | 39 | private const int DEFAULT_NOTIFICATION_TIME = 5000; 40 | public const string KEEPASSHTTP_NAME = "KeePassHttp Settings"; 41 | private const string KEEPASSHTTP_GROUP_NAME = "KeePassHttp Passwords"; 42 | public const string ASSOCIATE_KEY_PREFIX = "AES Key: "; 43 | private IPluginHost host; 44 | private HttpListener listener; 45 | public const int DEFAULT_PORT = 19455; 46 | public const string DEFAULT_HOST = "localhost"; 47 | /// 48 | /// TODO make configurable 49 | /// 50 | private const string HTTP_SCHEME = "http://"; 51 | //private const string HTTPS_PREFIX = "https://localhost:"; 52 | //private int HTTPS_PORT = DEFAULT_PORT + 1; 53 | private Thread httpThread; 54 | private volatile bool stopped = false; 55 | Dictionary handlers = new Dictionary(); 56 | 57 | //public string UpdateUrl = ""; 58 | public override string UpdateUrl { get { return "https://passifox.appspot.com/kph/latest-version.txt"; } } 59 | 60 | private SearchParameters MakeSearchParameters() 61 | { 62 | var p = new SearchParameters(); 63 | p.SearchInTitles = true; 64 | p.RegularExpression = true; 65 | p.SearchInGroupNames = false; 66 | p.SearchInNotes = false; 67 | p.SearchInOther = false; 68 | p.SearchInPasswords = false; 69 | p.SearchInTags = false; 70 | p.SearchInUrls = true; 71 | p.SearchInUserNames = false; 72 | p.SearchInUuids = false; 73 | return p; 74 | } 75 | 76 | private string CryptoTransform(string input, bool base64in, bool base64out, Aes cipher, CMode mode) 77 | { 78 | byte[] bytes; 79 | if (base64in) 80 | bytes = decode64(input); 81 | else 82 | bytes = Encoding.UTF8.GetBytes(input); 83 | 84 | 85 | using (var c = mode == CMode.ENCRYPT ? cipher.CreateEncryptor() : cipher.CreateDecryptor()) { 86 | var buf = c.TransformFinalBlock(bytes, 0, bytes.Length); 87 | return base64out ? encode64(buf) : Encoding.UTF8.GetString(buf); 88 | } 89 | } 90 | 91 | private PwEntry GetConfigEntry(bool create) 92 | { 93 | var root = host.Database.RootGroup; 94 | var uuid = new PwUuid(KEEPASSHTTP_UUID); 95 | var entry = root.FindEntry(uuid, false); 96 | if (entry == null && create) 97 | { 98 | entry = new PwEntry(false, true); 99 | entry.Uuid = uuid; 100 | entry.Strings.Set(PwDefs.TitleField, new ProtectedString(false, KEEPASSHTTP_NAME)); 101 | root.AddEntry(entry, true); 102 | UpdateUI(null); 103 | } 104 | return entry; 105 | } 106 | 107 | private int GetNotificationTime() 108 | { 109 | var time = DEFAULT_NOTIFICATION_TIME; 110 | var entry = GetConfigEntry(false); 111 | if (entry != null) 112 | { 113 | var s = entry.Strings.ReadSafe("Prompt Timeout"); 114 | if (s != null && s.Trim() != "") 115 | { 116 | try 117 | { 118 | time = Int32.Parse(s) * 1000; 119 | } 120 | catch { } 121 | } 122 | } 123 | 124 | return time; 125 | } 126 | 127 | private void ShowNotification(string text) 128 | { 129 | ShowNotification(text, null, null); 130 | } 131 | 132 | private void ShowNotification(string text, EventHandler onclick) 133 | { 134 | ShowNotification(text, onclick, null); 135 | } 136 | 137 | private void ShowNotification(string text, EventHandler onclick, EventHandler onclose) 138 | { 139 | MethodInvoker m = delegate 140 | { 141 | var notify = host.MainWindow.MainNotifyIcon; 142 | if (notify == null) 143 | return; 144 | 145 | EventHandler clicked = null; 146 | EventHandler closed = null; 147 | 148 | clicked = delegate 149 | { 150 | notify.BalloonTipClicked -= clicked; 151 | notify.BalloonTipClosed -= closed; 152 | if (onclick != null) 153 | onclick(notify, null); 154 | }; 155 | closed = delegate 156 | { 157 | notify.BalloonTipClicked -= clicked; 158 | notify.BalloonTipClosed -= closed; 159 | if (onclose != null) 160 | onclose(notify, null); 161 | }; 162 | 163 | //notify.BalloonTipIcon = ToolTipIcon.Info; 164 | notify.BalloonTipTitle = "KeePassHttp"; 165 | notify.BalloonTipText = text; 166 | notify.ShowBalloonTip(GetNotificationTime()); 167 | // need to add listeners after showing, or closed is sent right away 168 | notify.BalloonTipClosed += closed; 169 | notify.BalloonTipClicked += clicked; 170 | }; 171 | if (host.MainWindow.InvokeRequired) 172 | host.MainWindow.Invoke(m); 173 | else 174 | m.Invoke(); 175 | } 176 | 177 | public override bool Initialize(IPluginHost host) 178 | { 179 | var httpSupported = HttpListener.IsSupported; 180 | this.host = host; 181 | 182 | var optionsMenu = new ToolStripMenuItem("KeePassHttp Options..."); 183 | optionsMenu.Click += OnOptions_Click; 184 | optionsMenu.Image = KeePassHttp.Properties.Resources.earth_lock; 185 | //optionsMenu.Image = global::KeePass.Properties.Resources.B16x16_File_Close; 186 | this.host.MainWindow.ToolsMenu.DropDownItems.Add(optionsMenu); 187 | 188 | if (httpSupported) 189 | { 190 | try 191 | { 192 | handlers.Add(Request.TEST_ASSOCIATE, TestAssociateHandler); 193 | handlers.Add(Request.ASSOCIATE, AssociateHandler); 194 | handlers.Add(Request.GET_LOGINS, GetLoginsHandler); 195 | handlers.Add(Request.GET_LOGINS_COUNT, GetLoginsCountHandler); 196 | handlers.Add(Request.GET_ALL_LOGINS, GetAllLoginsHandler); 197 | handlers.Add(Request.SET_LOGIN, SetLoginHandler); 198 | handlers.Add(Request.GENERATE_PASSWORD, GeneratePassword); 199 | 200 | listener = new HttpListener(); 201 | 202 | var configOpt = new ConfigOpt(this.host.CustomConfig); 203 | 204 | listener.Prefixes.Add(HTTP_SCHEME + configOpt.ListenerHost + ":" + configOpt.ListenerPort.ToString() + "/"); 205 | //listener.Prefixes.Add(HTTPS_PREFIX + HTTPS_PORT + "/"); 206 | listener.Start(); 207 | 208 | httpThread = new Thread(new ThreadStart(Run)); 209 | httpThread.Start(); 210 | } catch (HttpListenerException e) { 211 | MessageBox.Show(host.MainWindow, 212 | "Unable to start HttpListener!\nDo you really have only one installation of KeePassHttp in your KeePass-directory?\n\n" + e, 213 | "Unable to start HttpListener", 214 | MessageBoxButtons.OK, 215 | MessageBoxIcon.Error 216 | ); 217 | } 218 | } 219 | else 220 | { 221 | MessageBox.Show(host.MainWindow, "The .NET HttpListener is not supported on your OS", 222 | ".NET HttpListener not supported", 223 | MessageBoxButtons.OK, 224 | MessageBoxIcon.Error 225 | ); 226 | } 227 | return httpSupported; 228 | } 229 | 230 | void OnOptions_Click(object sender, EventArgs e) 231 | { 232 | var form = new OptionsForm(new ConfigOpt(host.CustomConfig)); 233 | UIUtil.ShowDialogAndDestroy(form); 234 | } 235 | 236 | private void Run() 237 | { 238 | while (!stopped) 239 | { 240 | try 241 | { 242 | var r = listener.BeginGetContext(new AsyncCallback(RequestHandler), listener); 243 | r.AsyncWaitHandle.WaitOne(); 244 | r.AsyncWaitHandle.Close(); 245 | } 246 | catch (ThreadInterruptedException) { } 247 | catch (HttpListenerException e) { 248 | MessageBox.Show(host.MainWindow, "Unable to process request!\n\n" + e, 249 | "Unable to process request", 250 | MessageBoxButtons.OK, 251 | MessageBoxIcon.Error 252 | ); 253 | } 254 | } 255 | } 256 | 257 | private JsonSerializer NewJsonSerializer() 258 | { 259 | var settings = new JsonSerializerSettings(); 260 | settings.DefaultValueHandling = DefaultValueHandling.Ignore; 261 | settings.NullValueHandling = NullValueHandling.Ignore; 262 | 263 | return JsonSerializer.Create(settings); 264 | } 265 | private Response ProcessRequest(Request r, HttpListenerResponse resp) 266 | { 267 | string hash = host.Database.RootGroup.Uuid.ToHexString() + host.Database.RecycleBinUuid.ToHexString(); 268 | hash = getSHA1(hash); 269 | 270 | var response = new Response(r.RequestType, hash); 271 | 272 | using (var aes = new AesManaged()) 273 | { 274 | aes.Mode = CipherMode.CBC; 275 | aes.Padding = PaddingMode.PKCS7; 276 | var handler = handlers[r.RequestType]; 277 | if (handler != null) 278 | { 279 | try 280 | { 281 | handler(r, response, aes); 282 | } 283 | catch (Exception e) 284 | { 285 | ShowNotification("***BUG*** " + e, (s,evt) => MessageBox.Show(host.MainWindow, e + "")); 286 | response.Error = e + ""; 287 | resp.StatusCode = (int)HttpStatusCode.BadRequest; 288 | } 289 | } 290 | else 291 | { 292 | response.Error = "Unknown command: " + r.RequestType; 293 | resp.StatusCode = (int)HttpStatusCode.BadRequest; 294 | } 295 | } 296 | 297 | return response; 298 | } 299 | private void RequestHandler(IAsyncResult r) 300 | { 301 | try { 302 | _RequestHandler(r); 303 | } catch (Exception e) { 304 | MessageBox.Show(host.MainWindow, "RequestHandler failed: " + e); 305 | } 306 | } 307 | private void _RequestHandler(IAsyncResult r) 308 | { 309 | if (stopped) return; 310 | var l = (HttpListener)r.AsyncState; 311 | var ctx = l.EndGetContext(r); 312 | var req = ctx.Request; 313 | var resp = ctx.Response; 314 | 315 | var serializer = NewJsonSerializer(); 316 | Request request = null; 317 | 318 | resp.StatusCode = (int)HttpStatusCode.OK; 319 | using (var ins = new JsonTextReader(new StreamReader(req.InputStream))) 320 | { 321 | try 322 | { 323 | request = serializer.Deserialize(ins); 324 | } 325 | catch (JsonSerializationException e) 326 | { 327 | var buffer = Encoding.UTF8.GetBytes(e + ""); 328 | resp.StatusCode = (int)HttpStatusCode.BadRequest; 329 | resp.ContentLength64 = buffer.Length; 330 | resp.OutputStream.Write(buffer, 0, buffer.Length); 331 | } // ignore, bad request 332 | } 333 | 334 | var db = host.Database; 335 | 336 | var configOpt = new ConfigOpt(this.host.CustomConfig); 337 | 338 | if (request != null && (configOpt.UnlockDatabaseRequest || request.TriggerUnlock == "true") && !db.IsOpen) 339 | { 340 | host.MainWindow.Invoke((MethodInvoker)delegate 341 | { 342 | host.MainWindow.EnsureVisibleForegroundWindow(true, true); 343 | }); 344 | 345 | // UnlockDialog not already opened 346 | bool bNoDialogOpened = (KeePass.UI.GlobalWindowManager.WindowCount == 0); 347 | if (!db.IsOpen && bNoDialogOpened) 348 | { 349 | host.MainWindow.Invoke((MethodInvoker)delegate 350 | { 351 | host.MainWindow.OpenDatabase(host.MainWindow.DocumentManager.ActiveDocument.LockedIoc, null, false); 352 | }); 353 | } 354 | } 355 | 356 | if (request != null && db.IsOpen) 357 | { 358 | Response response = null; 359 | if (request != null) 360 | response = ProcessRequest(request, resp); 361 | 362 | resp.ContentType = "application/json"; 363 | var writer = new StringWriter(); 364 | if (response != null) 365 | { 366 | serializer.Serialize(writer, response); 367 | var buffer = Encoding.UTF8.GetBytes(writer.ToString()); 368 | resp.ContentLength64 = buffer.Length; 369 | resp.OutputStream.Write(buffer, 0, buffer.Length); 370 | } 371 | } 372 | else 373 | { 374 | resp.StatusCode = (int)HttpStatusCode.ServiceUnavailable; 375 | } 376 | 377 | var outs = resp.OutputStream; 378 | outs.Close(); 379 | resp.Close(); 380 | } 381 | 382 | public override void Terminate() 383 | { 384 | stopped = true; 385 | listener.Stop(); 386 | listener.Close(); 387 | httpThread.Interrupt(); 388 | } 389 | 390 | private void UpdateUI(PwGroup group) 391 | { 392 | var win = host.MainWindow; 393 | if (group == null) group = host.Database.RootGroup; 394 | var f = (MethodInvoker) delegate { 395 | win.UpdateUI(false, null, true, group, true, null, true); 396 | }; 397 | if (win.InvokeRequired) 398 | win.Invoke(f); 399 | else 400 | f.Invoke(); 401 | } 402 | 403 | internal string[] GetUserPass(PwEntry entry) 404 | { 405 | return GetUserPass(new PwEntryDatabase(entry, host.Database)); 406 | } 407 | 408 | internal string[] GetUserPass(PwEntryDatabase entryDatabase) 409 | { 410 | // follow references 411 | SprContext ctx = new SprContext(entryDatabase.entry, entryDatabase.database, 412 | SprCompileFlags.All, false, false); 413 | 414 | return GetUserPass(entryDatabase, ctx); 415 | } 416 | 417 | internal string[] GetUserPass(PwEntryDatabase entryDatabase, SprContext ctx) 418 | { 419 | // follow references 420 | string user = SprEngine.Compile( 421 | entryDatabase.entry.Strings.ReadSafe(PwDefs.UserNameField), ctx); 422 | string pass = SprEngine.Compile( 423 | entryDatabase.entry.Strings.ReadSafe(PwDefs.PasswordField), ctx); 424 | var f = (MethodInvoker)delegate 425 | { 426 | // apparently, SprEngine.Compile might modify the database 427 | host.MainWindow.UpdateUI(false, null, false, null, false, null, false); 428 | }; 429 | if (host.MainWindow.InvokeRequired) 430 | host.MainWindow.Invoke(f); 431 | else 432 | f.Invoke(); 433 | 434 | return new string[] { user, pass }; 435 | } 436 | 437 | /// 438 | /// Liefert den SHA1 Hash 439 | /// 440 | /// Eingabestring 441 | /// SHA1 Hash der Eingabestrings 442 | private string getSHA1(string input) 443 | { 444 | //Umwandlung des Eingastring in den SHA1 Hash 445 | System.Security.Cryptography.SHA1 sha1 = new System.Security.Cryptography.SHA1CryptoServiceProvider(); 446 | byte[] textToHash = Encoding.Default.GetBytes(input); 447 | byte[] result = sha1.ComputeHash(textToHash); 448 | 449 | //SHA1 Hash in String konvertieren 450 | System.Text.StringBuilder s = new System.Text.StringBuilder(); 451 | foreach (byte b in result) 452 | { 453 | s.Append(b.ToString("x2").ToLower()); 454 | } 455 | 456 | return s.ToString(); 457 | } 458 | } 459 | } 460 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # KeePassHttp 2 | 3 | is a plugin for KeePass 2.x and provides a secure means of exposing KeePass entries via HTTP for clients to 4 | consume. 5 | 6 | This plugin is primarily intended for use with [PassIFox for Mozilla Firefox](https://github.com/pfn/passifox/) and [chromeIPass for Google Chrome](https://chrome.google.com/webstore/detail/chromeipass/ompiailgknfdndiefoaoiligalphfdae?hl=en). 7 | 8 | ## Features 9 | * returns all matching entries for a given URL 10 | * updates entries 11 | * secure exchange of entries 12 | * notifies user if entries are delivered 13 | * user can allow or deny access to single entries 14 | * works only if the database is unlocked 15 | * request for unlocking the database if it is locked while connecting 16 | * searches in all opened databases (if user activates this feature) 17 | * Whenever events occur, the user is prompted either by tray notification or requesting interaction (allow/deny/remember). 18 | 19 | ## System requirements 20 | * KeePass 2.17 or higher 21 | * For Windows: Windows XP SP3 or higher 22 | * For Linux: installed mono 23 | * For Mac: installed mono | it seems to fully support KeePassHttp, but we cannot test it 24 | 25 | ## Windows installation using Chocolatey 26 | 27 | 1. Install using [Chocolatey](https://chocolatey.org/) with `choco install keepass-keepasshttp` 28 | 2. Restart KeePass if it is currently running to load the plugin 29 | 30 | ## Non-Windows / Manual Windows installation 31 | 32 | 1. Download [KeePassHttp](https://raw.github.com/pfn/keepasshttp/master/KeePassHttp.plgx) 33 | 2. Copy it into the KeePass directory 34 | * default directory in Ubuntu14.04: /usr/lib/keepass2/ 35 | * default directory in Arch: /usr/share/keepass 36 | 3. Set chmod 644 on file `KeePassHttp.plgx` 37 | 4. 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) 38 | * Tips to run KeePassHttp on lastest KeePass 2.31: install packages 39 | `sudo apt-get install libmono-system-xml-linq4.0-cil libmono-system-data-datasetextensions4.0-cil libmono-system-runtime-serialization4.0-cil mono-mcs` 40 | 5. Restart KeePass 41 | 42 | ### KeePassHttp on Linux and Mac 43 | 44 | KeePass needs Mono. You can find detailed [installation instructions on the official page of KeePass](http://keepass.info/help/v2/setup.html#mono). 45 | 46 | Perry has tested KeePassHttp with Mono 2.6.7 and it appears to work well. 47 | With Mono 2.6.7 and a version of KeePass lower than 2.20 he could not get the plgx file to work on linux. 48 | If the plgx file does also not work for you, you can try the two DLL files KeePassHttp.dll and Newtonsoft.Json.dll from directory [mono](https://github.com/pfn/keepasshttp/tree/master/mono) which should work for you. 49 | 50 | With newer versions of Mono and KeePass it seems that the plgx file works pretty fine. 51 | More information about it are contained in the following experience report. 52 | 53 | #### Experience report by dunkelfuerst 54 | Just wanted to let you know, I'm running Fedora 18, which currently uses 55 | mono v2.10.8: 56 | ~~~ 57 | > mono-core.x86_64 2.10.8-3.fc18 @fedora 58 | > mono-data.x86_64 2.10.8-3.fc18 @fedora 59 | > mono-data-sqlite.x86_64 2.10.8-3.fc18 @fedora 60 | > mono-extras.x86_64 2.10.8-3.fc18 @fedora 61 | > mono-mvc.x86_64 2.10.8-3.fc18 @fedora 62 | > mono-wcf.x86_64 2.10.8-3.fc18 @fedora 63 | > mono-web.x86_64 2.10.8-3.fc18 @fedora 64 | > mono-winforms.x86_64 2.10.8-3.fc18 @fedora 65 | > mono-winfx.x86_64 2.10.8-3.fc18 @fedora 66 | ~~~ 67 | 68 | I have no problems using "KeePassHttp.plgx". I simply dropped the .plgx-file in my KeePass folder, and it works. 69 | 70 | I'm currently using KeePass v2.22. 71 | Nevertheless, until KeePass v2.21 I used the 2 suggested .dll's and it worked fine too. 72 | 73 | Usually I only use chromeIPass, but I did a short test 74 | with PassIFox and seems to be working just fine. 75 | 76 | ## Configuration and Options 77 | 78 | KeePassHttp works out-of-the-box. You don't have to explicitely configure it. 79 | 80 | * KeePassHttp stores shared AES encryption keys in "KeePassHttp Settings" in the root group of a password database. 81 | * Password entries saved by KeePassHttp are stored in a new group named "KeePassHttp Passwords" within the password database. 82 | * Remembered Allow/Deny settings are stored as JSON in custom string fields within the individual password entry in the database. 83 | 84 | ### Settings in KeePassHttp options. 85 | 86 | You can open the options dialog with menu: Tools > KeePassHttp Options 87 | 88 | [menu](https://raw.github.com/pfn/keepasshttp/master/documentation/images/menu.jpg) 89 | 90 | The options dialog will appear: 91 | 92 | [options-general](https://raw.github.com/pfn/keepasshttp/master/documentation/images/options-general.png) 93 | 94 | General tab 95 | 96 | 1. show a notification balloon whenever entries are delivered to the inquirer. 97 | 2. returns only the best matching entries for the given url, otherwise all entries for a domain are send. 98 | - 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 99 | 3. if the active database in KeePass is locked, KeePassHttp sends a request to unlock the database. Now KeePass opens and the user has to enter the master password to unlock the database. Otherwise KeePassHttp tells the inquirer that the database is closed. 100 | 4. KeePassHttp returns only these entries which match the scheme of the given URL. 101 | - given URL: https://example.org --> scheme: https:// --> only entries whose URL starts with https:// 102 | 5. sort found entries by username or title. 103 | 6. removes all shared encryption-keys which are stored in the currently selected database. Every inquirer has to reauthenticate. 104 | 7. removes all stored permissions in the entries of the currently selected database. 105 | 106 | [options-advanced](https://raw.github.com/pfn/keepasshttp/master/documentation/images/options-advanced.png) 107 | 108 | Advanced tab 109 | 110 | 8. KeePassHttp no longer asks for permissions to retrieve entries, it always allows access. 111 | 9. KeePassHttp no longer asks for permission to update an entry, it always allows updating them. 112 | 10. Searching for entries is no longer restricted to the current active database in KeePass but is extended to all opened databases! 113 | - __Important:__ Even if another database is not connected with the inquirer, KeePassHttp will search and retrieve entries of all opened databases if the active one is connected to KeePassHttp! 114 | 11. if activated KeePassHttp 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 transfered to the client in alphabetical order__. You can set string fields in the tab _Advanced_ of an entry. 115 | [advanced tab of an entry](https://raw.github.com/pfn/keepasshttp/master/documentation/images/advanced-string-fields.png) 116 | 117 | ## Tips and Tricks 118 | 119 | ### Support multiple URLs for one username + password 120 | This is already implemented directly in KeePass. 121 | 122 | 1. Open the context menu of an entry by clicking right on it and select _Duplicate entry_: 123 | [context-menu-entry](https://raw.github.com/pfn/keepasshttp/master/documentation/images/keepass-context-menu.png) 124 | 125 | 2. Check the option to use references for username and password: 126 | [mark checkbox references](https://raw.github.com/pfn/keepasshttp/master/documentation/images/keepass-duplicate-entry-references.png) 127 | 128 | 3. You can change the title, URL and evertything 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. 129 | 130 | ## Troubleshooting 131 | 132 | __First:__ If an error occures it will be shown as notification in system tray or as message box in KeePass. 133 | 134 | Otherwise please check if it could be an error of the client you are using. For passIFox and chromeIPass you can [report an error here](https://github.com/pfn/passifox/issues/). 135 | 136 | 137 | If you are having problems with KeePassHttp, please tell us at least the following information: 138 | * version of KeePass 139 | * version of KeePassHttp 140 | * error message (if available) 141 | * used clients and their versions 142 | * URLs on which the problem occur (if available) 143 | 144 | ### HTTP Listener error message 145 | 146 | Maybe you get the following error message: 147 | [http listener error](https://raw.github.com/pfn/keepasshttp/master/documentation/images/http-listener-error.png) 148 | 149 | In old versions the explaining first part of the message does not exist! 150 | 151 | This error occurs because you have multiple copies of KeePassHttp in your KeePass directory! Please check __all__ PLGX- and DLL-files in your _KeePass directory and all sub-directories_ whether they are a copy of KeePassHttp. 152 | __Note:__ KeePass does _not_ detect plugins by filename but by extension! If you rename _KeePassHttp.plgx_ to _HelloWorld.plgx_ it is still a valid copy of KeePassHttp. 153 | 154 | If you _really_ have only one copy of KeePassHttp in your KeePass directory another application seems to use port 19455 to wait for signals. In this case try to stop all applications and restart everyone again while checking if the error still occurs. 155 | 156 | ## URL matching: How does it work? 157 | 158 | KeePassHttp 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 | 178 | ## Security 179 | 180 | For security reasons KeePassHttp communicates only with the symmetric-key algorithm AES. 181 | The entries are crypted with a 256bit AES key. 182 | 183 | There is one single point where someone else will be able to steal the encryption keys. 184 | If a new client has to connect to KeePassHttp, the encryption key is generated and send to KeyPassHttp via an unencrypted connection. 185 | 186 | 187 | ## Compile at your own 188 | 189 | If you want to develop new features or improve existing ones here is a way to build it at your own: 190 | 191 | 1. copy the file [Newtonsoft.Json.dll](http://json.codeplex.com/releases/) into the sourcecode folder 192 | 2. delete the directory "bin" from sourcecode 193 | 3. delete the directory "obj" from sourcecode 194 | 4. delete the file "KeePassHttp.dll" 195 | 196 | I use the following batch code to automatically do steps 2 - 4: 197 | 198 | RD /S /Q C:\full-path-to-keepasshttp-source\bin 199 | RD /S /Q C:\full-path-to-keepasshttp-source\obj 200 | DEL C:\full-path-to-keepasshttp-source\KeePassHttp.dll 201 | "C:\Program Files (x86)\KeePass Password Safe 2\keepass.exe" --plgx-create C:\full-path-to-keepasshttp-source 202 | 203 | 204 | ## Protocol 205 | 206 | ### A. New client or stale client (key not in database). 207 | 208 | This is the only point at which an administrator snooping traffic will be able to steal encryption keys: 209 | 210 | 1. client sends "test-associate" with payload to server 211 | 2. server sends fail response to client (cannot decrypt) 212 | 3. client sends "associate" with 256bit AES key and payload to server 213 | 4. server decrypts payload with provided key and prompts user to save 214 | 5. server saves key into "KeePassHttpSettings":"AES key: label" 215 | 6. client saves label/key into local password storage 216 | 217 | (1) can be skipped if client does not have a key configured 218 | 219 | ### B. Client with key stored in server 220 | 1. client sends "test-associate" with label+encrypted payload to server 221 | 2. server verifies payload and responds with success to client 222 | 3. client sends any of "get-logins-count", "get-logins", "set-login" using the previously negotiated key in (A) 223 | 4. if any subsequent request fails, it is necessary to "test-associate" again 224 | 225 | ## A little deeper into protocol 226 | 227 | ### Generic HTTP request 228 | (based on packet sniffing and code analyssis) 229 | Generic HTTP request is json sent in POST message. Cipher, by means of OpenSSL library is `AES-256-CBC`, so key is 32 byte long. 230 | 231 | ``` 232 | Host: localhost:19455 233 | Connection: keep-alive 234 | Content-Length: 54 235 | Content-Type: application/json 236 | Accept: */* 237 | Accept-Encoding: gzip, deflate, br 238 | 239 | {"RequestType":"test-associate","TriggerUnlock":false} 240 | 241 | ``` 242 | 243 | Also, minimal JSON request (except that one without key set up) consists of four main parameters: 244 | - RequestType - `test-associate`, `associate`, `get-logins`, `get-logins-count`, `set-login`, ... 245 | - TriggerUnlock - TODO: what is this good for? seems always false 246 | - Nonce - 128 bit (16 bytes) long random vector, base64 encoded, used as IV for aes encryption 247 | - Verifier - verifier, base64 encoded AES encrypted data: `encrypt(base64_encode($nonce), $key, $nonce);` 248 | - Id - Key id entered into KeePass GUI while `associate`, not used during `associate` 249 | 250 | ### test-associate 251 | Request, without key, seems like initialization of every key assignation session: 252 | ```javascript 253 | { 254 | "RequestType":"test-associate", 255 | "TriggerUnlock":false 256 | } 257 | ``` 258 | 259 | Response: (without success) 260 | ```javascript 261 | { 262 | "Count":null, 263 | "Entries":null, 264 | "Error":"", 265 | "Hash":"d8312a59523d3c37d6a5401d3cfddd077e194680", 266 | "Id":"", 267 | "Nonce":"", 268 | "RequestType":"test-associate", 269 | "Success":false, 270 | "Verifier":"", 271 | "Version":"1.8.4.1", 272 | "objectName":"" 273 | } 274 | ``` 275 | 276 | If you have key, you can test with request like this: 277 | ```javascript 278 | { 279 | "Nonce":"+bG+EpbCR4jSnjROKAAw4A==", // random 128bit vector, base64 encoded 280 | "Verifier":"2nVUxyddGpe62WGx5cm3hcb604Xn8AXrYxUK2WP9dU0=", // Nonce in base64 form, encoded with aes 281 | "RequestType":"test-associate", 282 | "TriggerUnlock":false, 283 | "Id":"PHP" 284 | } 285 | ``` 286 | 287 | ### associate 288 | Request: 289 | ```javascript 290 | { 291 | "RequestType":"associate", 292 | "Key":"CRyXRbH9vBkdPrkdm52S3bTG2rGtnYuyJttk/mlJ15g=", // Base64 encoded 256 bit key 293 | "Nonce":"epIt2nuAZbHt5JgEsxolWg==", 294 | "Verifier":"Lj+3N58jkjoxS2zNRmTpeQ4g065OlFfJsHNQWYaOJto=" 295 | } 296 | ``` 297 | 298 | Response: 299 | ```javascript 300 | { 301 | "Count":null, 302 | "Entries":null, 303 | "Error":"", 304 | "Hash":"d8312a59523d3c37d6a5401d3cfddd077e194680", 305 | "Id":"PHP", // You need to save this - to use in future 306 | "Nonce":"cJUFe18NSThQ/0yAqZMaDA==", 307 | "RequestType":"associate", 308 | "Success":true, 309 | "Verifier":"ChH0PtuQWP4UKTPhdP3XSgwFyVdekHmHT7YdL1EKA+A=", 310 | "Version":"1.8.4.1", 311 | "objectName":"" 312 | } 313 | ``` 314 | 315 | ### get-logins 316 | 317 | Request: 318 | ```javascript 319 | { 320 | "RequestType":"get-logins", 321 | "SortSelection":"true", 322 | "TriggerUnlock":"false", 323 | "Id":"PHP", 324 | "Nonce":"vCysO8UwsWyE2b+nMzE3/Q==", 325 | "Verifier":"5Nyi5973GawqdP3qF9QlAF/KlZAyvb6c5Smhun8n9wA=", 326 | "Url":"Gz+ZCSjHAGmeYdrtS78hSxH3yD5LiYidSq9n+8TdQXc=", // Encrypted URL 327 | "SubmitUrl":"" // Encrypted submit URL 328 | } 329 | ``` 330 | 331 | Response: 332 | ```javascript 333 | { 334 | "Count":3, 335 | "Entries":[ 336 | { 337 | "Login":"{encrypted login base64}", 338 | "Name":"{encrypted item name}", 339 | "Password":"{encrypted Password}", 340 | "StringFields":null, 341 | "Uuid":"{encrypted UUID}" 342 | }, 343 | { 344 | 345 | }, 346 | { 347 | 348 | } 349 | ], 350 | "Error":"", 351 | "Hash":"d8312a59523d3c37d6a5401d3cfddd077e194680", 352 | "Id":"PHP", 353 | "Nonce":"Aeh9maerCjE5v5V8Tz2YxA==", 354 | "RequestType":"get-logins", 355 | "Success":true, 356 | "Verifier":"F87c4ggkMTSEptJT8/FypBH491kRexTAiEZxovLMvD8=", 357 | "Version":"1.8.4.1", 358 | "objectName":"" 359 | } 360 | 361 | ``` 362 | 363 | ### get-logins-count 364 | 365 | Request: 366 | ```javascript 367 | { 368 | "RequestType":"get-logins-count", 369 | "TriggerUnlock":"false", 370 | "Id":"PHP", 371 | "Nonce":"vCysO8UwsWyE2b+nMzE3/Q==", 372 | "Verifier":"5Nyi5973GawqdP3qF9QlAF/KlZAyvb6c5Smhun8n9wA=", 373 | "Url":"Gz+ZCSjHAGmeYdrtS78hSxH3yD5LiYidSq9n+8TdQXc=", // Encrypted URL 374 | "SubmitUrl":"" // Encrypted submit URL 375 | } 376 | ``` 377 | 378 | Response: 379 | ```javascript 380 | { 381 | "Count":3, 382 | "Entries":null, 383 | "Error":"", 384 | "Hash":"d8312a59523d3c37d6a5401d3cfddd077e194680", 385 | "Id":"PHP", 386 | "Nonce":"Aeh9maerCjE5v5V8Tz2YxA==", 387 | "RequestType":"get-logins", 388 | "Success":true, 389 | "Verifier":"F87c4ggkMTSEptJT8/FypBH491kRexTAiEZxovLMvD8=", 390 | "Version":"1.8.4.1", 391 | "objectName":"" 392 | } 393 | ``` 394 | 395 | ### set-login 396 | 397 | Request: 398 | ```javascript 399 | { 400 | "RequestType":"set-login", 401 | "Id":"PHP", 402 | "Nonce":"VBrPACEOQGxIBkq58/5Xig==", 403 | "Verifier":"1dT0gnw6I1emxDzhtYn1Ecn1sobLG98GfTf7Z/Ma0R0=", 404 | "Login":"lm9qo5HcAYEIaHsCdSsYHQ==", // encrypted username 405 | "Password":"EZLtRxFgZVqIwv5xI9tfvA==", // encrypted password 406 | "Url":"", 407 | "SubmitUrl":"" 408 | } 409 | ``` 410 | 411 | Response: 412 | ```javascript 413 | { 414 | "Count":null, 415 | "Entries":null, 416 | "Error":"", 417 | "Hash":"d8312a59523d3c37d6a5401d3cfddd077e194680", 418 | "Id":"PHP", 419 | "Nonce":"uofAcMtnPQo5TOdI21VjBw==", 420 | "RequestType":"set-login", 421 | "Success":true, 422 | "Verifier":"4u8OINVGBtlCCPY7OnW5T616iPlzvf56LzPtPAwZIs0=", 423 | "Version":"1.8.4.1", 424 | "objectName":"" 425 | } 426 | ``` 427 | -------------------------------------------------------------------------------- /test/lib/sjcl.js: -------------------------------------------------------------------------------- 1 | /** @fileOverview Javascript cryptography implementation. 2 | * 3 | * Crush to remove comments, shorten variable names and 4 | * generally reduce transmission size. 5 | * 6 | * @author Emily Stark 7 | * @author Mike Hamburg 8 | * @author Dan Boneh 9 | */ 10 | 11 | "use strict"; 12 | /*jslint indent: 2, bitwise: false, nomen: false, plusplus: false, white: false, regexp: false */ 13 | /*global document, window, escape, unescape */ 14 | 15 | /** @namespace The Stanford Javascript Crypto Library, top-level namespace. */ 16 | var sjcl = { 17 | /** @namespace Symmetric ciphers. */ 18 | cipher: {}, 19 | 20 | /** @namespace Hash functions. Right now only SHA256 is implemented. */ 21 | hash: {}, 22 | 23 | /** @namespace Key exchange functions. Right now only SRP is implemented. */ 24 | keyexchange: {}, 25 | 26 | /** @namespace Block cipher modes of operation. */ 27 | mode: {}, 28 | 29 | /** @namespace Miscellaneous. HMAC and PBKDF2. */ 30 | misc: {}, 31 | 32 | /** 33 | * @namespace Bit array encoders and decoders. 34 | * 35 | * @description 36 | * The members of this namespace are functions which translate between 37 | * SJCL's bitArrays and other objects (usually strings). Because it 38 | * isn't always clear which direction is encoding and which is decoding, 39 | * the method names are "fromBits" and "toBits". 40 | */ 41 | codec: {}, 42 | 43 | /** @namespace Exceptions. */ 44 | exception: { 45 | /** @class Ciphertext is corrupt. */ 46 | corrupt: function(message) { 47 | this.toString = function() { return "CORRUPT: "+this.message; }; 48 | this.message = message; 49 | }, 50 | 51 | /** @class Invalid parameter. */ 52 | invalid: function(message) { 53 | this.toString = function() { return "INVALID: "+this.message; }; 54 | this.message = message; 55 | }, 56 | 57 | /** @class Bug or missing feature in SJCL. */ 58 | bug: function(message) { 59 | this.toString = function() { return "BUG: "+this.message; }; 60 | this.message = message; 61 | }, 62 | 63 | /** @class Something isn't ready. */ 64 | notReady: function(message) { 65 | this.toString = function() { return "NOT READY: "+this.message; }; 66 | this.message = message; 67 | } 68 | } 69 | }; 70 | /** @fileOverview Low-level AES implementation. 71 | * 72 | * This file contains a low-level implementation of AES, optimized for 73 | * size and for efficiency on several browsers. It is based on 74 | * OpenSSL's aes_core.c, a public-domain implementation by Vincent 75 | * Rijmen, Antoon Bosselaers and Paulo Barreto. 76 | * 77 | * An older version of this implementation is available in the public 78 | * domain, but this one is (c) Emily Stark, Mike Hamburg, Dan Boneh, 79 | * Stanford University 2008-2010 and BSD-licensed for liability 80 | * reasons. 81 | * 82 | * @author Emily Stark 83 | * @author Mike Hamburg 84 | * @author Dan Boneh 85 | */ 86 | 87 | /** 88 | * Schedule out an AES key for both encryption and decryption. This 89 | * is a low-level class. Use a cipher mode to do bulk encryption. 90 | * 91 | * @constructor 92 | * @param {Array} key The key as an array of 4, 6 or 8 words. 93 | * 94 | * @class Advanced Encryption Standard (low-level interface) 95 | */ 96 | sjcl.cipher.aes = function (key) { 97 | if (!this._tables[0][0][0]) { 98 | this._precompute(); 99 | } 100 | 101 | var i, j, tmp, 102 | encKey, decKey, 103 | sbox = this._tables[0][4], decTable = this._tables[1], 104 | keyLen = key.length, rcon = 1; 105 | 106 | if (keyLen !== 4 && keyLen !== 6 && keyLen !== 8) { 107 | throw new sjcl.exception.invalid("invalid aes key size"); 108 | } 109 | 110 | this._key = [encKey = key.slice(0), decKey = []]; 111 | 112 | // schedule encryption keys 113 | for (i = keyLen; i < 4 * keyLen + 28; i++) { 114 | tmp = encKey[i-1]; 115 | 116 | // apply sbox 117 | if (i%keyLen === 0 || (keyLen === 8 && i%keyLen === 4)) { 118 | tmp = sbox[tmp>>>24]<<24 ^ sbox[tmp>>16&255]<<16 ^ sbox[tmp>>8&255]<<8 ^ sbox[tmp&255]; 119 | 120 | // shift rows and add rcon 121 | if (i%keyLen === 0) { 122 | tmp = tmp<<8 ^ tmp>>>24 ^ rcon<<24; 123 | rcon = rcon<<1 ^ (rcon>>7)*283; 124 | } 125 | } 126 | 127 | encKey[i] = encKey[i-keyLen] ^ tmp; 128 | } 129 | 130 | // schedule decryption keys 131 | for (j = 0; i; j++, i--) { 132 | tmp = encKey[j&3 ? i : i - 4]; 133 | if (i<=4 || j<4) { 134 | decKey[j] = tmp; 135 | } else { 136 | decKey[j] = decTable[0][sbox[tmp>>>24 ]] ^ 137 | decTable[1][sbox[tmp>>16 & 255]] ^ 138 | decTable[2][sbox[tmp>>8 & 255]] ^ 139 | decTable[3][sbox[tmp & 255]]; 140 | } 141 | } 142 | }; 143 | 144 | sjcl.cipher.aes.prototype = { 145 | // public 146 | /* Something like this might appear here eventually 147 | name: "AES", 148 | blockSize: 4, 149 | keySizes: [4,6,8], 150 | */ 151 | 152 | /** 153 | * Encrypt an array of 4 big-endian words. 154 | * @param {Array} data The plaintext. 155 | * @return {Array} The ciphertext. 156 | */ 157 | encrypt:function (data) { return this._crypt(data,0); }, 158 | 159 | /** 160 | * Decrypt an array of 4 big-endian words. 161 | * @param {Array} data The ciphertext. 162 | * @return {Array} The plaintext. 163 | */ 164 | decrypt:function (data) { return this._crypt(data,1); }, 165 | 166 | /** 167 | * The expanded S-box and inverse S-box tables. These will be computed 168 | * on the client so that we don't have to send them down the wire. 169 | * 170 | * There are two tables, _tables[0] is for encryption and 171 | * _tables[1] is for decryption. 172 | * 173 | * The first 4 sub-tables are the expanded S-box with MixColumns. The 174 | * last (_tables[01][4]) is the S-box itself. 175 | * 176 | * @private 177 | */ 178 | _tables: [[[],[],[],[],[]],[[],[],[],[],[]]], 179 | 180 | /** 181 | * Expand the S-box tables. 182 | * 183 | * @private 184 | */ 185 | _precompute: function () { 186 | var encTable = this._tables[0], decTable = this._tables[1], 187 | sbox = encTable[4], sboxInv = decTable[4], 188 | i, x, xInv, d=[], th=[], x2, x4, x8, s, tEnc, tDec; 189 | 190 | // Compute double and third tables 191 | for (i = 0; i < 256; i++) { 192 | th[( d[i] = i<<1 ^ (i>>7)*283 )^i]=i; 193 | } 194 | 195 | for (x = xInv = 0; !sbox[x]; x ^= x2 || 1, xInv = th[xInv] || 1) { 196 | // Compute sbox 197 | s = xInv ^ xInv<<1 ^ xInv<<2 ^ xInv<<3 ^ xInv<<4; 198 | s = s>>8 ^ s&255 ^ 99; 199 | sbox[x] = s; 200 | sboxInv[s] = x; 201 | 202 | // Compute MixColumns 203 | x8 = d[x4 = d[x2 = d[x]]]; 204 | tDec = x8*0x1010101 ^ x4*0x10001 ^ x2*0x101 ^ x*0x1010100; 205 | tEnc = d[s]*0x101 ^ s*0x1010100; 206 | 207 | for (i = 0; i < 4; i++) { 208 | encTable[i][x] = tEnc = tEnc<<24 ^ tEnc>>>8; 209 | decTable[i][s] = tDec = tDec<<24 ^ tDec>>>8; 210 | } 211 | } 212 | 213 | // Compactify. Considerable speedup on Firefox. 214 | for (i = 0; i < 5; i++) { 215 | encTable[i] = encTable[i].slice(0); 216 | decTable[i] = decTable[i].slice(0); 217 | } 218 | }, 219 | 220 | /** 221 | * Encryption and decryption core. 222 | * @param {Array} input Four words to be encrypted or decrypted. 223 | * @param dir The direction, 0 for encrypt and 1 for decrypt. 224 | * @return {Array} The four encrypted or decrypted words. 225 | * @private 226 | */ 227 | _crypt:function (input, dir) { 228 | if (input.length !== 4) { 229 | throw new sjcl.exception.invalid("invalid aes block size"); 230 | } 231 | 232 | var key = this._key[dir], 233 | // state variables a,b,c,d are loaded with pre-whitened data 234 | a = input[0] ^ key[0], 235 | b = input[dir ? 3 : 1] ^ key[1], 236 | c = input[2] ^ key[2], 237 | d = input[dir ? 1 : 3] ^ key[3], 238 | a2, b2, c2, 239 | 240 | nInnerRounds = key.length/4 - 2, 241 | i, 242 | kIndex = 4, 243 | out = [0,0,0,0], 244 | table = this._tables[dir], 245 | 246 | // load up the tables 247 | t0 = table[0], 248 | t1 = table[1], 249 | t2 = table[2], 250 | t3 = table[3], 251 | sbox = table[4]; 252 | 253 | // Inner rounds. Cribbed from OpenSSL. 254 | for (i = 0; i < nInnerRounds; i++) { 255 | a2 = t0[a>>>24] ^ t1[b>>16 & 255] ^ t2[c>>8 & 255] ^ t3[d & 255] ^ key[kIndex]; 256 | b2 = t0[b>>>24] ^ t1[c>>16 & 255] ^ t2[d>>8 & 255] ^ t3[a & 255] ^ key[kIndex + 1]; 257 | c2 = t0[c>>>24] ^ t1[d>>16 & 255] ^ t2[a>>8 & 255] ^ t3[b & 255] ^ key[kIndex + 2]; 258 | d = t0[d>>>24] ^ t1[a>>16 & 255] ^ t2[b>>8 & 255] ^ t3[c & 255] ^ key[kIndex + 3]; 259 | kIndex += 4; 260 | a=a2; b=b2; c=c2; 261 | } 262 | 263 | // Last round. 264 | for (i = 0; i < 4; i++) { 265 | out[dir ? 3&-i : i] = 266 | sbox[a>>>24 ]<<24 ^ 267 | sbox[b>>16 & 255]<<16 ^ 268 | sbox[c>>8 & 255]<<8 ^ 269 | sbox[d & 255] ^ 270 | key[kIndex++]; 271 | a2=a; a=b; b=c; c=d; d=a2; 272 | } 273 | 274 | return out; 275 | } 276 | }; 277 | 278 | /** @fileOverview Arrays of bits, encoded as arrays of Numbers. 279 | * 280 | * @author Emily Stark 281 | * @author Mike Hamburg 282 | * @author Dan Boneh 283 | */ 284 | 285 | /** @namespace Arrays of bits, encoded as arrays of Numbers. 286 | * 287 | * @description 288 | *

289 | * These objects are the currency accepted by SJCL's crypto functions. 290 | *

291 | * 292 | *

293 | * Most of our crypto primitives operate on arrays of 4-byte words internally, 294 | * but many of them can take arguments that are not a multiple of 4 bytes. 295 | * This library encodes arrays of bits (whose size need not be a multiple of 8 296 | * bits) as arrays of 32-bit words. The bits are packed, big-endian, into an 297 | * array of words, 32 bits at a time. Since the words are double-precision 298 | * floating point numbers, they fit some extra data. We use this (in a private, 299 | * possibly-changing manner) to encode the number of bits actually present 300 | * in the last word of the array. 301 | *

302 | * 303 | *

304 | * Because bitwise ops clear this out-of-band data, these arrays can be passed 305 | * to ciphers like AES which want arrays of words. 306 | *

307 | */ 308 | sjcl.bitArray = { 309 | /** 310 | * Array slices in units of bits. 311 | * @param {bitArray a} The array to slice. 312 | * @param {Number} bstart The offset to the start of the slice, in bits. 313 | * @param {Number} bend The offset to the end of the slice, in bits. If this is undefined, 314 | * slice until the end of the array. 315 | * @return {bitArray} The requested slice. 316 | */ 317 | bitSlice: function (a, bstart, bend) { 318 | a = sjcl.bitArray._shiftRight(a.slice(bstart/32), 32 - (bstart & 31)).slice(1); 319 | return (bend === undefined) ? a : sjcl.bitArray.clamp(a, bend-bstart); 320 | }, 321 | 322 | /** 323 | * Extract a number packed into a bit array. 324 | * @param {bitArray} a The array to slice. 325 | * @param {Number} bstart The offset to the start of the slice, in bits. 326 | * @param {Number} length The length of the number to extract. 327 | * @return {Number} The requested slice. 328 | */ 329 | extract: function(a, bstart, blength) { 330 | // FIXME: this Math.floor is not necessary at all, but for some reason 331 | // seems to suppress a bug in the Chromium JIT. 332 | var x, sh = Math.floor((-bstart-blength) & 31); 333 | if ((bstart + blength - 1 ^ bstart) & -32) { 334 | // it crosses a boundary 335 | x = (a[bstart/32|0] << (32 - sh)) ^ (a[bstart/32+1|0] >>> sh); 336 | } else { 337 | // within a single word 338 | x = a[bstart/32|0] >>> sh; 339 | } 340 | return x & ((1< 0 && len) { 386 | a[l-1] = sjcl.bitArray.partial(len, a[l-1] & 0x80000000 >> (len-1), 1); 387 | } 388 | return a; 389 | }, 390 | 391 | /** 392 | * Make a partial word for a bit array. 393 | * @param {Number} len The number of bits in the word. 394 | * @param {Number} x The bits. 395 | * @param {Number} [0] _end Pass 1 if x has already been shifted to the high side. 396 | * @return {Number} The partial word. 397 | */ 398 | partial: function (len, x, _end) { 399 | if (len === 32) { return x; } 400 | return (_end ? x|0 : x << (32-len)) + len * 0x10000000000; 401 | }, 402 | 403 | /** 404 | * Get the number of bits used by a partial word. 405 | * @param {Number} x The partial word. 406 | * @return {Number} The number of bits used by the partial word. 407 | */ 408 | getPartial: function (x) { 409 | return Math.round(x/0x10000000000) || 32; 410 | }, 411 | 412 | /** 413 | * Compare two arrays for equality in a predictable amount of time. 414 | * @param {bitArray} a The first array. 415 | * @param {bitArray} b The second array. 416 | * @return {boolean} true if a == b; false otherwise. 417 | */ 418 | equal: function (a, b) { 419 | if (sjcl.bitArray.bitLength(a) !== sjcl.bitArray.bitLength(b)) { 420 | return false; 421 | } 422 | var x = 0, i; 423 | for (i=0; i= 32; shift -= 32) { 441 | out.push(carry); 442 | carry = 0; 443 | } 444 | if (shift === 0) { 445 | return out.concat(a); 446 | } 447 | 448 | for (i=0; i>>shift); 450 | carry = a[i] << (32-shift); 451 | } 452 | last2 = a.length ? a[a.length-1] : 0; 453 | shift2 = sjcl.bitArray.getPartial(last2); 454 | out.push(sjcl.bitArray.partial(shift+shift2 & 31, (shift + shift2 > 32) ? carry : out.pop(),1)); 455 | return out; 456 | }, 457 | 458 | /** xor a block of 4 words together. 459 | * @private 460 | */ 461 | _xor4: function(x,y) { 462 | return [x[0]^y[0],x[1]^y[1],x[2]^y[2],x[3]^y[3]]; 463 | } 464 | }; 465 | /** @fileOverview CBC mode implementation 466 | * 467 | * @author Emily Stark 468 | * @author Mike Hamburg 469 | * @author Dan Boneh 470 | */ 471 | 472 | /** @namespace 473 | * Dangerous: CBC mode with PKCS#5 padding. 474 | * 475 | * @author Emily Stark 476 | * @author Mike Hamburg 477 | * @author Dan Boneh 478 | */ 479 | sjcl.mode.cbc = { 480 | /** The name of the mode. 481 | * @constant 482 | */ 483 | name: "cbc", 484 | 485 | /** Encrypt in CBC mode with PKCS#5 padding. 486 | * @param {Object} prp The block cipher. It must have a block size of 16 bytes. 487 | * @param {bitArray} plaintext The plaintext data. 488 | * @param {bitArray} iv The initialization value. 489 | * @param {bitArray} [adata=[]] The authenticated data. Must be empty. 490 | * @return The encrypted data, an array of bytes. 491 | * @throws {sjcl.exception.invalid} if the IV isn't exactly 128 bits, or if any adata is specified. 492 | */ 493 | encrypt: function(prp, plaintext, iv, adata) { 494 | if (adata && adata.length) { 495 | throw new sjcl.exception.invalid("cbc can't authenticate data"); 496 | } 497 | if (sjcl.bitArray.bitLength(iv) !== 128) { 498 | throw new sjcl.exception.invalid("cbc iv must be 128 bits"); 499 | } 500 | var i, 501 | w = sjcl.bitArray, 502 | xor = w._xor4, 503 | bl = w.bitLength(plaintext), 504 | bp = 0, 505 | output = []; 506 | 507 | if (bl&7) { 508 | throw new sjcl.exception.invalid("pkcs#5 padding only works for multiples of a byte"); 509 | } 510 | 511 | for (i=0; bp+128 <= bl; i+=4, bp+=128) { 512 | /* Encrypt a non-final block */ 513 | iv = prp.encrypt(xor(iv, plaintext.slice(i,i+4))); 514 | output.splice(i,0,iv[0],iv[1],iv[2],iv[3]); 515 | } 516 | 517 | /* Construct the pad. */ 518 | bl = (16 - ((bl >> 3) & 15)) * 0x1010101; 519 | 520 | /* Pad and encrypt. */ 521 | iv = prp.encrypt(xor(iv,w.concat(plaintext,[bl,bl,bl,bl]).slice(i,i+4))); 522 | output.splice(i,0,iv[0],iv[1],iv[2],iv[3]); 523 | return output; 524 | }, 525 | 526 | /** Decrypt in CBC mode. 527 | * @param {Object} prp The block cipher. It must have a block size of 16 bytes. 528 | * @param {bitArray} ciphertext The ciphertext data. 529 | * @param {bitArray} iv The initialization value. 530 | * @param {bitArray} [adata=[]] The authenticated data. It must be empty. 531 | * @return The decrypted data, an array of bytes. 532 | * @throws {sjcl.exception.invalid} if the IV isn't exactly 128 bits, or if any adata is specified. 533 | * @throws {sjcl.exception.corrupt} if if the message is corrupt. 534 | */ 535 | decrypt: function(prp, ciphertext, iv, adata) { 536 | if (adata && adata.length) { 537 | throw new sjcl.exception.invalid("cbc can't authenticate data"); 538 | } 539 | if (sjcl.bitArray.bitLength(iv) !== 128) { 540 | throw new sjcl.exception.invalid("cbc iv must be 128 bits"); 541 | } 542 | if ((sjcl.bitArray.bitLength(ciphertext) & 127) || !ciphertext.length) { 543 | throw new sjcl.exception.corrupt("cbc ciphertext must be a positive multiple of the block size"); 544 | } 545 | var i, 546 | w = sjcl.bitArray, 547 | xor = w._xor4, 548 | bi, bo, 549 | output = []; 550 | 551 | adata = adata || []; 552 | 553 | for (i=0; i 16) { 563 | throw new sjcl.exception.corrupt("pkcs#5 padding corrupt"); 564 | } 565 | bo = bi * 0x1010101; 566 | if (!w.equal(w.bitSlice([bo,bo,bo,bo], 0, bi*8), 567 | w.bitSlice(output, output.length*32 - bi*8, output.length*32))) { 568 | throw new sjcl.exception.corrupt("pkcs#5 padding corrupt"); 569 | } 570 | 571 | return w.bitSlice(output, 0, output.length*32 - bi*8); 572 | } 573 | }; 574 | /** @fileOverview Bit array codec implementations. 575 | * 576 | * @author Emily Stark 577 | * @author Mike Hamburg 578 | * @author Dan Boneh 579 | */ 580 | 581 | /** @namespace Hexadecimal */ 582 | sjcl.codec.hex = { 583 | /** Convert from a bitArray to a hex string. */ 584 | fromBits: function (arr) { 585 | var out = "", i, x; 586 | for (i=0; i>> 24); 621 | tmp <<= 8; 622 | } 623 | return decodeURIComponent(escape(out)); 624 | }, 625 | 626 | /** Convert from a UTF-8 string to a bitArray. */ 627 | toBits: function (str) { 628 | str = unescape(encodeURIComponent(str)); 629 | var out = [], i, tmp=0; 630 | for (i=0; i>>bits) >>> 26); 663 | if (bits < 6) { 664 | ta = arr[i] << (6-bits); 665 | bits += 26; 666 | i++; 667 | } else { 668 | ta <<= 6; 669 | bits -= 6; 670 | } 671 | } 672 | while ((out.length & 3) && !_noEquals) { out += "="; } 673 | return out; 674 | }, 675 | 676 | /** Convert from a base64 string to a bitArray */ 677 | toBits: function(str, _url) { 678 | str = str.replace(/\s|=/g,''); 679 | var out = [], i, bits=0, c = sjcl.codec.base64._chars, ta=0, x; 680 | if (_url) c = c.substr(0,62) + '-_'; 681 | for (i=0; i 26) { 687 | bits -= 26; 688 | out.push(ta ^ x>>>bits); 689 | ta = x << (32-bits); 690 | } else { 691 | bits += 6; 692 | ta ^= x << (32-bits); 693 | } 694 | } 695 | if (bits&56) { 696 | out.push(sjcl.bitArray.partial(bits&56, ta, 1)); 697 | } 698 | return out; 699 | } 700 | }; 701 | 702 | sjcl.codec.base64url = { 703 | fromBits: function (arr) { return sjcl.codec.base64.fromBits(arr,1,1); }, 704 | toBits: function (str) { return sjcl.codec.base64.toBits(str,1); } 705 | }; 706 | -------------------------------------------------------------------------------- /KeePassHttp/OptionsForm.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace KeePassHttp 2 | { 3 | partial class OptionsForm 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.cancelButton = new System.Windows.Forms.Button(); 32 | this.okButton = new System.Windows.Forms.Button(); 33 | this.tabControl1 = new System.Windows.Forms.TabControl(); 34 | this.tabPage1 = new System.Windows.Forms.TabPage(); 35 | this.SortByUsernameRadioButton = new System.Windows.Forms.RadioButton(); 36 | this.SortByTitleRadioButton = new System.Windows.Forms.RadioButton(); 37 | this.hideExpiredCheckbox = new System.Windows.Forms.CheckBox(); 38 | this.matchSchemesCheckbox = new System.Windows.Forms.CheckBox(); 39 | this.removePermissionsButton = new System.Windows.Forms.Button(); 40 | this.unlockDatabaseCheckbox = new System.Windows.Forms.CheckBox(); 41 | this.removeButton = new System.Windows.Forms.Button(); 42 | this.credMatchingCheckbox = new System.Windows.Forms.CheckBox(); 43 | this.credNotifyCheckbox = new System.Windows.Forms.CheckBox(); 44 | this.tabPage2 = new System.Windows.Forms.TabPage(); 45 | this.returnStringFieldsWithKphOnlyCheckBox = new System.Windows.Forms.CheckBox(); 46 | this.hostName = new System.Windows.Forms.TextBox(); 47 | this.label10 = new System.Windows.Forms.Label(); 48 | this.label8 = new System.Windows.Forms.Label(); 49 | this.label9 = new System.Windows.Forms.Label(); 50 | this.label7 = new System.Windows.Forms.Label(); 51 | this.portNumber = new System.Windows.Forms.NumericUpDown(); 52 | this.label5 = new System.Windows.Forms.Label(); 53 | this.label6 = new System.Windows.Forms.Label(); 54 | this.label4 = new System.Windows.Forms.Label(); 55 | this.label3 = new System.Windows.Forms.Label(); 56 | this.returnStringFieldsCheckbox = new System.Windows.Forms.CheckBox(); 57 | this.label2 = new System.Windows.Forms.Label(); 58 | this.credSearchInAllOpenedDatabases = new System.Windows.Forms.CheckBox(); 59 | this.label1 = new System.Windows.Forms.Label(); 60 | this.credAllowUpdatesCheckbox = new System.Windows.Forms.CheckBox(); 61 | this.credAllowAccessCheckbox = new System.Windows.Forms.CheckBox(); 62 | this.tabControl1.SuspendLayout(); 63 | this.tabPage1.SuspendLayout(); 64 | this.tabPage2.SuspendLayout(); 65 | ((System.ComponentModel.ISupportInitialize)(this.portNumber)).BeginInit(); 66 | this.SuspendLayout(); 67 | // 68 | // cancelButton 69 | // 70 | this.cancelButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); 71 | this.cancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; 72 | this.cancelButton.Location = new System.Drawing.Point(313, 508); 73 | this.cancelButton.Name = "cancelButton"; 74 | this.cancelButton.Size = new System.Drawing.Size(88, 28); 75 | this.cancelButton.TabIndex = 2; 76 | this.cancelButton.Text = "&Cancel"; 77 | this.cancelButton.UseVisualStyleBackColor = true; 78 | // 79 | // okButton 80 | // 81 | this.okButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); 82 | this.okButton.DialogResult = System.Windows.Forms.DialogResult.OK; 83 | this.okButton.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 84 | this.okButton.Location = new System.Drawing.Point(219, 508); 85 | this.okButton.Name = "okButton"; 86 | this.okButton.Size = new System.Drawing.Size(88, 28); 87 | this.okButton.TabIndex = 1; 88 | this.okButton.Text = "&Save"; 89 | this.okButton.UseVisualStyleBackColor = true; 90 | this.okButton.Click += new System.EventHandler(this.okButton_Click); 91 | // 92 | // tabControl1 93 | // 94 | this.tabControl1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 95 | | System.Windows.Forms.AnchorStyles.Left) 96 | | System.Windows.Forms.AnchorStyles.Right))); 97 | this.tabControl1.Controls.Add(this.tabPage1); 98 | this.tabControl1.Controls.Add(this.tabPage2); 99 | this.tabControl1.Location = new System.Drawing.Point(1, 3); 100 | this.tabControl1.Name = "tabControl1"; 101 | this.tabControl1.SelectedIndex = 0; 102 | this.tabControl1.Size = new System.Drawing.Size(410, 498); 103 | this.tabControl1.TabIndex = 3; 104 | // 105 | // tabPage1 106 | // 107 | this.tabPage1.Controls.Add(this.SortByUsernameRadioButton); 108 | this.tabPage1.Controls.Add(this.SortByTitleRadioButton); 109 | this.tabPage1.Controls.Add(this.hideExpiredCheckbox); 110 | this.tabPage1.Controls.Add(this.matchSchemesCheckbox); 111 | this.tabPage1.Controls.Add(this.removePermissionsButton); 112 | this.tabPage1.Controls.Add(this.unlockDatabaseCheckbox); 113 | this.tabPage1.Controls.Add(this.removeButton); 114 | this.tabPage1.Controls.Add(this.credMatchingCheckbox); 115 | this.tabPage1.Controls.Add(this.credNotifyCheckbox); 116 | this.tabPage1.Location = new System.Drawing.Point(4, 22); 117 | this.tabPage1.Name = "tabPage1"; 118 | this.tabPage1.Padding = new System.Windows.Forms.Padding(3); 119 | this.tabPage1.Size = new System.Drawing.Size(402, 497); 120 | this.tabPage1.TabIndex = 0; 121 | this.tabPage1.Text = "General"; 122 | this.tabPage1.UseVisualStyleBackColor = true; 123 | // 124 | // SortByUsernameRadioButton 125 | // 126 | this.SortByUsernameRadioButton.AutoSize = true; 127 | this.SortByUsernameRadioButton.Location = new System.Drawing.Point(7, 147); 128 | this.SortByUsernameRadioButton.Name = "SortByUsernameRadioButton"; 129 | this.SortByUsernameRadioButton.Size = new System.Drawing.Size(171, 17); 130 | this.SortByUsernameRadioButton.TabIndex = 19; 131 | this.SortByUsernameRadioButton.TabStop = true; 132 | this.SortByUsernameRadioButton.Text = "Sort found entries by &username"; 133 | this.SortByUsernameRadioButton.UseVisualStyleBackColor = true; 134 | // 135 | // SortByTitleRadioButton 136 | // 137 | this.SortByTitleRadioButton.AutoSize = true; 138 | this.SortByTitleRadioButton.Location = new System.Drawing.Point(7, 170); 139 | this.SortByTitleRadioButton.Name = "SortByTitleRadioButton"; 140 | this.SortByTitleRadioButton.Size = new System.Drawing.Size(141, 17); 141 | this.SortByTitleRadioButton.TabIndex = 18; 142 | this.SortByTitleRadioButton.TabStop = true; 143 | this.SortByTitleRadioButton.Text = "Sort found entries by &title"; 144 | this.SortByTitleRadioButton.UseVisualStyleBackColor = true; 145 | // 146 | // hideExpiredCheckbox 147 | // 148 | this.hideExpiredCheckbox.AutoSize = true; 149 | this.hideExpiredCheckbox.Location = new System.Drawing.Point(7, 88); 150 | this.hideExpiredCheckbox.Name = "hideExpiredCheckbox"; 151 | this.hideExpiredCheckbox.Size = new System.Drawing.Size(256, 17); 152 | this.hideExpiredCheckbox.TabIndex = 17; 153 | this.hideExpiredCheckbox.Text = "Don't return e&xpired entries"; 154 | this.hideExpiredCheckbox.UseVisualStyleBackColor = true; 155 | // 156 | // matchSchemesCheckbox 157 | // 158 | this.matchSchemesCheckbox.AutoSize = true; 159 | this.matchSchemesCheckbox.Location = new System.Drawing.Point(7, 111); 160 | this.matchSchemesCheckbox.Name = "matchSchemesCheckbox"; 161 | this.matchSchemesCheckbox.Size = new System.Drawing.Size(375, 30); 162 | this.matchSchemesCheckbox.TabIndex = 17; 163 | this.matchSchemesCheckbox.Text = "&Match URL schemes\r\nonly entries with the same scheme (http://, https://, ftp://," + 164 | " ...) are returned"; 165 | this.matchSchemesCheckbox.UseVisualStyleBackColor = true; 166 | // 167 | // removePermissionsButton 168 | // 169 | this.removePermissionsButton.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 170 | | System.Windows.Forms.AnchorStyles.Right))); 171 | this.removePermissionsButton.ImageAlign = System.Drawing.ContentAlignment.TopLeft; 172 | this.removePermissionsButton.Location = new System.Drawing.Point(14, 239); 173 | this.removePermissionsButton.Name = "removePermissionsButton"; 174 | this.removePermissionsButton.Size = new System.Drawing.Size(372, 28); 175 | this.removePermissionsButton.TabIndex = 16; 176 | this.removePermissionsButton.Text = "Remo&ve all stored permissions from entries in active database"; 177 | this.removePermissionsButton.UseVisualStyleBackColor = true; 178 | this.removePermissionsButton.Click += new System.EventHandler(this.removePermissionsButton_Click); 179 | // 180 | // unlockDatabaseCheckbox 181 | // 182 | this.unlockDatabaseCheckbox.AutoSize = true; 183 | this.unlockDatabaseCheckbox.Location = new System.Drawing.Point(7, 65); 184 | this.unlockDatabaseCheckbox.Name = "unlockDatabaseCheckbox"; 185 | this.unlockDatabaseCheckbox.Size = new System.Drawing.Size(256, 17); 186 | this.unlockDatabaseCheckbox.TabIndex = 15; 187 | this.unlockDatabaseCheckbox.Text = "Re&quest for unlocking the database if it is locked"; 188 | this.unlockDatabaseCheckbox.UseVisualStyleBackColor = true; 189 | // 190 | // removeButton 191 | // 192 | this.removeButton.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 193 | | System.Windows.Forms.AnchorStyles.Right))); 194 | this.removeButton.Location = new System.Drawing.Point(14, 205); 195 | this.removeButton.Name = "removeButton"; 196 | this.removeButton.Size = new System.Drawing.Size(372, 28); 197 | this.removeButton.TabIndex = 11; 198 | this.removeButton.Text = "R&emove all shared encryption-keys from active database"; 199 | this.removeButton.UseVisualStyleBackColor = true; 200 | this.removeButton.Click += new System.EventHandler(this.removeButton_Click); 201 | // 202 | // credMatchingCheckbox 203 | // 204 | this.credMatchingCheckbox.AutoSize = true; 205 | this.credMatchingCheckbox.Location = new System.Drawing.Point(7, 29); 206 | this.credMatchingCheckbox.Name = "credMatchingCheckbox"; 207 | this.credMatchingCheckbox.Size = new System.Drawing.Size(238, 30); 208 | this.credMatchingCheckbox.TabIndex = 9; 209 | this.credMatchingCheckbox.Text = "&Return only best matching entries for an URL\r\ninstead of all entries for the who" + 210 | "le domain"; 211 | this.credMatchingCheckbox.UseVisualStyleBackColor = true; 212 | // 213 | // credNotifyCheckbox 214 | // 215 | this.credNotifyCheckbox.AutoSize = true; 216 | this.credNotifyCheckbox.Location = new System.Drawing.Point(7, 6); 217 | this.credNotifyCheckbox.Name = "credNotifyCheckbox"; 218 | this.credNotifyCheckbox.Size = new System.Drawing.Size(267, 17); 219 | this.credNotifyCheckbox.TabIndex = 8; 220 | this.credNotifyCheckbox.Text = "Sh&ow a notification when credentials are requested"; 221 | this.credNotifyCheckbox.UseVisualStyleBackColor = true; 222 | // 223 | // tabPage2 224 | // 225 | this.tabPage2.Controls.Add(this.returnStringFieldsWithKphOnlyCheckBox); 226 | this.tabPage2.Controls.Add(this.hostName); 227 | this.tabPage2.Controls.Add(this.label10); 228 | this.tabPage2.Controls.Add(this.label8); 229 | this.tabPage2.Controls.Add(this.label9); 230 | this.tabPage2.Controls.Add(this.label7); 231 | this.tabPage2.Controls.Add(this.portNumber); 232 | this.tabPage2.Controls.Add(this.label5); 233 | this.tabPage2.Controls.Add(this.label6); 234 | this.tabPage2.Controls.Add(this.label4); 235 | this.tabPage2.Controls.Add(this.label3); 236 | this.tabPage2.Controls.Add(this.returnStringFieldsCheckbox); 237 | this.tabPage2.Controls.Add(this.label2); 238 | this.tabPage2.Controls.Add(this.credSearchInAllOpenedDatabases); 239 | this.tabPage2.Controls.Add(this.label1); 240 | this.tabPage2.Controls.Add(this.credAllowUpdatesCheckbox); 241 | this.tabPage2.Controls.Add(this.credAllowAccessCheckbox); 242 | this.tabPage2.Location = new System.Drawing.Point(4, 22); 243 | this.tabPage2.Name = "tabPage2"; 244 | this.tabPage2.Padding = new System.Windows.Forms.Padding(3); 245 | this.tabPage2.Size = new System.Drawing.Size(402, 472); 246 | this.tabPage2.TabIndex = 1; 247 | this.tabPage2.Text = "Advanced"; 248 | this.tabPage2.UseVisualStyleBackColor = true; 249 | // 250 | // returnStringFieldsWithKphOnlyCheckBox 251 | // 252 | this.returnStringFieldsWithKphOnlyCheckBox.AutoSize = true; 253 | this.returnStringFieldsWithKphOnlyCheckBox.Location = new System.Drawing.Point(55, 215); 254 | this.returnStringFieldsWithKphOnlyCheckBox.Name = "returnStringFieldsWithKphOnlyCheckBox"; 255 | this.returnStringFieldsWithKphOnlyCheckBox.Size = new System.Drawing.Size(300, 30); 256 | this.returnStringFieldsWithKphOnlyCheckBox.TabIndex = 31; 257 | this.returnStringFieldsWithKphOnlyCheckBox.Text = "Only return advanced string fields which start with \"KPH: \"\r\n(Mind the space afte" + 258 | "r KPH:)"; 259 | this.returnStringFieldsWithKphOnlyCheckBox.UseVisualStyleBackColor = true; 260 | // 261 | // hostName 262 | // 263 | this.hostName.Location = new System.Drawing.Point(48, 318); 264 | this.hostName.Name = "hostName"; 265 | this.hostName.Size = new System.Drawing.Size(103, 20); 266 | this.hostName.TabIndex = 25; 267 | this.hostName.Text = "localhost"; 268 | this.hostName.TextChanged += new System.EventHandler(this.hostName_TextChanged); 269 | // 270 | // label10 271 | // 272 | this.label10.AutoSize = true; 273 | this.label10.Location = new System.Drawing.Point(7, 283); 274 | this.label10.Name = "label10"; 275 | this.label10.Size = new System.Drawing.Size(375, 26); 276 | this.label10.TabIndex = 23; 277 | this.label10.Text = "Only change the host to bind to if you want to give access to other computers.\r\nU" + 278 | "se \'*\' to bind it to all your IP addresses (potentially dangerous!)\r\n"; 279 | // 280 | // label8 281 | // 282 | this.label8.AutoSize = true; 283 | this.label8.Location = new System.Drawing.Point(157, 318); 284 | this.label8.Name = "label8"; 285 | this.label8.Size = new System.Drawing.Size(244, 65); 286 | this.label8.TabIndex = 26; 287 | this.label8.Text = "Default: localhost\r\nYou might need to run KeePass as administrator \r\nwhen you cha" + 288 | "nge this.\r\nAlso don\'t forget to open the firewall if you want to \r\nbe able to us" + 289 | "e it from a different computer."; 290 | // 291 | // label9 292 | // 293 | this.label9.AutoSize = true; 294 | this.label9.Location = new System.Drawing.Point(10, 321); 295 | this.label9.Name = "label9"; 296 | this.label9.Size = new System.Drawing.Size(32, 13); 297 | this.label9.TabIndex = 24; 298 | this.label9.Text = "Host:"; 299 | // 300 | // label7 301 | // 302 | this.label7.AutoSize = true; 303 | this.label7.Location = new System.Drawing.Point(157, 415); 304 | this.label7.Name = "label7"; 305 | this.label7.Size = new System.Drawing.Size(241, 39); 306 | this.label7.TabIndex = 30; 307 | this.label7.Text = "Default: 19455\r\nDon\'t forget to change the port number also in\r\nthe plugins like " + 308 | "chromeIPass, PassIFox, kypass,..."; 309 | // 310 | // portNumber 311 | // 312 | this.portNumber.Location = new System.Drawing.Point(48, 415); 313 | this.portNumber.Maximum = new decimal(new int[] { 314 | 99999, 315 | 0, 316 | 0, 317 | 0}); 318 | this.portNumber.Minimum = new decimal(new int[] { 319 | 1025, 320 | 0, 321 | 0, 322 | 0}); 323 | this.portNumber.Name = "portNumber"; 324 | this.portNumber.Size = new System.Drawing.Size(60, 20); 325 | this.portNumber.TabIndex = 29; 326 | this.portNumber.Value = new decimal(new int[] { 327 | 19455, 328 | 0, 329 | 0, 330 | 0}); 331 | this.portNumber.ValueChanged += new System.EventHandler(this.portNumber_ValueChanged); 332 | // 333 | // label5 334 | // 335 | this.label5.AutoSize = true; 336 | this.label5.Location = new System.Drawing.Point(8, 393); 337 | this.label5.Name = "label5"; 338 | this.label5.Size = new System.Drawing.Size(312, 13); 339 | this.label5.TabIndex = 27; 340 | this.label5.Text = "Change the default port number if you have connection problems"; 341 | // 342 | // label6 343 | // 344 | this.label6.AutoSize = true; 345 | this.label6.Location = new System.Drawing.Point(13, 417); 346 | this.label6.Name = "label6"; 347 | this.label6.Size = new System.Drawing.Size(29, 13); 348 | this.label6.TabIndex = 28; 349 | this.label6.Text = "Port:"; 350 | // 351 | // label4 352 | // 353 | this.label4.AutoSize = true; 354 | this.label4.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 355 | this.label4.Location = new System.Drawing.Point(52, 248); 356 | this.label4.Name = "label4"; 357 | this.label4.Size = new System.Drawing.Size(277, 26); 358 | this.label4.TabIndex = 22; 359 | this.label4.Text = "Automatic creates or updates are not supported\r\nfor string fields!"; 360 | // 361 | // label3 362 | // 363 | this.label3.AutoSize = true; 364 | this.label3.Location = new System.Drawing.Point(52, 156); 365 | this.label3.Name = "label3"; 366 | this.label3.Size = new System.Drawing.Size(289, 52); 367 | this.label3.TabIndex = 21; 368 | this.label3.Text = "If there are more fields needed than username + password,\r\nnormal \"String Fields\"" + 369 | " are used, which can be defined in the\r\n\"Advanced\" tab of an entry.\r\nString fiel" + 370 | "ds are returned in alphabetical order."; 371 | // 372 | // returnStringFieldsCheckbox 373 | // 374 | this.returnStringFieldsCheckbox.AutoSize = true; 375 | this.returnStringFieldsCheckbox.Location = new System.Drawing.Point(7, 136); 376 | this.returnStringFieldsCheckbox.Name = "returnStringFieldsCheckbox"; 377 | this.returnStringFieldsCheckbox.Size = new System.Drawing.Size(186, 17); 378 | this.returnStringFieldsCheckbox.TabIndex = 20; 379 | this.returnStringFieldsCheckbox.Text = "&Return also advanced string fields"; 380 | this.returnStringFieldsCheckbox.UseVisualStyleBackColor = true; 381 | this.returnStringFieldsCheckbox.CheckedChanged += new System.EventHandler(this.returnStringFieldsCheckbox_CheckedChanged); 382 | // 383 | // label2 384 | // 385 | this.label2.AutoSize = true; 386 | this.label2.Location = new System.Drawing.Point(52, 108); 387 | this.label2.Name = "label2"; 388 | this.label2.Size = new System.Drawing.Size(299, 13); 389 | this.label2.TabIndex = 19; 390 | this.label2.Text = "Only the selected database has to be connected with a client!"; 391 | // 392 | // credSearchInAllOpenedDatabases 393 | // 394 | this.credSearchInAllOpenedDatabases.AutoSize = true; 395 | this.credSearchInAllOpenedDatabases.Location = new System.Drawing.Point(7, 88); 396 | this.credSearchInAllOpenedDatabases.Name = "credSearchInAllOpenedDatabases"; 397 | this.credSearchInAllOpenedDatabases.Size = new System.Drawing.Size(270, 17); 398 | this.credSearchInAllOpenedDatabases.TabIndex = 18; 399 | this.credSearchInAllOpenedDatabases.Text = "Searc&h in all opened databases for matching entries"; 400 | this.credSearchInAllOpenedDatabases.UseVisualStyleBackColor = true; 401 | // 402 | // label1 403 | // 404 | this.label1.AutoSize = true; 405 | this.label1.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 406 | this.label1.ForeColor = System.Drawing.Color.Red; 407 | this.label1.Location = new System.Drawing.Point(4, 7); 408 | this.label1.Name = "label1"; 409 | this.label1.Size = new System.Drawing.Size(391, 13); 410 | this.label1.TabIndex = 17; 411 | this.label1.Text = "Activate the following options only, if you know what you are doing!"; 412 | // 413 | // credAllowUpdatesCheckbox 414 | // 415 | this.credAllowUpdatesCheckbox.AutoSize = true; 416 | this.credAllowUpdatesCheckbox.Location = new System.Drawing.Point(6, 56); 417 | this.credAllowUpdatesCheckbox.Name = "credAllowUpdatesCheckbox"; 418 | this.credAllowUpdatesCheckbox.Size = new System.Drawing.Size(164, 17); 419 | this.credAllowUpdatesCheckbox.TabIndex = 16; 420 | this.credAllowUpdatesCheckbox.Text = "Always allow &updating entries"; 421 | this.credAllowUpdatesCheckbox.UseVisualStyleBackColor = true; 422 | // 423 | // credAllowAccessCheckbox 424 | // 425 | this.credAllowAccessCheckbox.AutoSize = true; 426 | this.credAllowAccessCheckbox.Location = new System.Drawing.Point(6, 33); 427 | this.credAllowAccessCheckbox.Name = "credAllowAccessCheckbox"; 428 | this.credAllowAccessCheckbox.Size = new System.Drawing.Size(169, 17); 429 | this.credAllowAccessCheckbox.TabIndex = 15; 430 | this.credAllowAccessCheckbox.Text = "Always allow &access to entries"; 431 | this.credAllowAccessCheckbox.UseVisualStyleBackColor = true; 432 | // 433 | // OptionsForm 434 | // 435 | this.AcceptButton = this.okButton; 436 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 437 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 438 | this.CancelButton = this.cancelButton; 439 | this.ClientSize = new System.Drawing.Size(411, 545); 440 | this.Controls.Add(this.tabControl1); 441 | this.Controls.Add(this.okButton); 442 | this.Controls.Add(this.cancelButton); 443 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; 444 | this.MaximizeBox = false; 445 | this.MinimizeBox = false; 446 | this.Name = "OptionsForm"; 447 | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; 448 | this.Text = "KeePassHttp Options"; 449 | this.Load += new System.EventHandler(this.OptionsForm_Load); 450 | this.tabControl1.ResumeLayout(false); 451 | this.tabPage1.ResumeLayout(false); 452 | this.tabPage1.PerformLayout(); 453 | this.tabPage2.ResumeLayout(false); 454 | this.tabPage2.PerformLayout(); 455 | ((System.ComponentModel.ISupportInitialize)(this.portNumber)).EndInit(); 456 | this.ResumeLayout(false); 457 | 458 | } 459 | 460 | #endregion 461 | 462 | private System.Windows.Forms.Button cancelButton; 463 | private System.Windows.Forms.Button okButton; 464 | private System.Windows.Forms.TabControl tabControl1; 465 | private System.Windows.Forms.TabPage tabPage1; 466 | private System.Windows.Forms.CheckBox hideExpiredCheckbox; 467 | private System.Windows.Forms.CheckBox matchSchemesCheckbox; 468 | private System.Windows.Forms.Button removePermissionsButton; 469 | private System.Windows.Forms.CheckBox unlockDatabaseCheckbox; 470 | private System.Windows.Forms.Button removeButton; 471 | private System.Windows.Forms.CheckBox credMatchingCheckbox; 472 | private System.Windows.Forms.CheckBox credNotifyCheckbox; 473 | private System.Windows.Forms.TabPage tabPage2; 474 | private System.Windows.Forms.CheckBox credSearchInAllOpenedDatabases; 475 | private System.Windows.Forms.Label label1; 476 | private System.Windows.Forms.CheckBox credAllowUpdatesCheckbox; 477 | private System.Windows.Forms.CheckBox credAllowAccessCheckbox; 478 | private System.Windows.Forms.Label label2; 479 | private System.Windows.Forms.CheckBox returnStringFieldsCheckbox; 480 | private System.Windows.Forms.Label label3; 481 | private System.Windows.Forms.Label label4; 482 | private System.Windows.Forms.RadioButton SortByUsernameRadioButton; 483 | private System.Windows.Forms.RadioButton SortByTitleRadioButton; 484 | private System.Windows.Forms.Label label7; 485 | private System.Windows.Forms.NumericUpDown portNumber; 486 | private System.Windows.Forms.Label label5; 487 | private System.Windows.Forms.Label label6; 488 | private System.Windows.Forms.TextBox hostName; 489 | private System.Windows.Forms.Label label10; 490 | private System.Windows.Forms.Label label8; 491 | private System.Windows.Forms.Label label9; 492 | private System.Windows.Forms.CheckBox returnStringFieldsWithKphOnlyCheckBox; 493 | } 494 | } -------------------------------------------------------------------------------- /KeePassHttp/Handlers.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Cryptography; 2 | using System.Windows.Forms; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.IO; 6 | using System; 7 | using System.Threading; 8 | 9 | using KeePass.Plugins; 10 | using KeePassLib.Collections; 11 | using KeePassLib.Security; 12 | using KeePassLib.Utility; 13 | using KeePassLib; 14 | 15 | using Newtonsoft.Json; 16 | using Microsoft.Win32; 17 | using KeePass.UI; 18 | using KeePass; 19 | using KeePassLib.Cryptography.PasswordGenerator; 20 | using KeePassLib.Cryptography; 21 | using KeePass.Util.Spr; 22 | 23 | namespace KeePassHttp { 24 | public sealed partial class KeePassHttpExt : Plugin 25 | { 26 | private string GetHost(string uri) 27 | { 28 | var host = uri; 29 | try 30 | { 31 | var url = new Uri(uri); 32 | host = url.Host; 33 | 34 | if (!url.IsDefaultPort) 35 | { 36 | host += ":" + url.Port.ToString(); 37 | } 38 | } 39 | catch 40 | { 41 | // ignore exception, not a URI, assume input is host 42 | } 43 | return host; 44 | } 45 | 46 | private string GetScheme(string uri) 47 | { 48 | var scheme = ""; 49 | try 50 | { 51 | var url = new Uri(uri); 52 | scheme = url.Scheme; 53 | } 54 | catch 55 | { 56 | // ignore exception, not a URI, assume input is host 57 | } 58 | return scheme; 59 | } 60 | 61 | private bool canShowBalloonTips() 62 | { 63 | // tray icon is not visible --> no balloon tips for it 64 | if (Program.Config.UI.TrayIcon.ShowOnlyIfTrayed && !host.MainWindow.IsTrayed()) 65 | { 66 | return false; 67 | } 68 | 69 | // only use balloon tips on windows machines 70 | if (Environment.OSVersion.Platform == PlatformID.Win32NT || Environment.OSVersion.Platform == System.PlatformID.Win32S || Environment.OSVersion.Platform == System.PlatformID.Win32Windows) 71 | { 72 | int enabledBalloonTipsMachine = (int)Registry.GetValue("HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced", 73 | "EnableBalloonTips", 74 | 1); 75 | int enabledBalloonTipsUser = (int)Registry.GetValue("HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced", 76 | "EnableBalloonTips", 77 | 1); 78 | return (enabledBalloonTipsMachine == 1 && enabledBalloonTipsUser == 1); 79 | } 80 | 81 | return false; 82 | } 83 | 84 | private void GetAllLoginsHandler(Request r, Response resp, Aes aes) 85 | { 86 | if (!VerifyRequest(r, aes)) 87 | return; 88 | 89 | var list = new PwObjectList(); 90 | 91 | var root = host.Database.RootGroup; 92 | 93 | var parms = MakeSearchParameters(); 94 | 95 | parms.SearchString = @"^[A-Za-z0-9:/-]+\.[A-Za-z0-9:/-]+$"; // match anything looking like a domain or url 96 | 97 | root.SearchEntries(parms, list); 98 | foreach (var entry in list) 99 | { 100 | var name = entry.Strings.ReadSafe(PwDefs.TitleField); 101 | var login = GetUserPass(entry)[0]; 102 | var uuid = entry.Uuid.ToHexString(); 103 | var e = new ResponseEntry(name, login, null, uuid, null); 104 | resp.Entries.Add(e); 105 | } 106 | resp.Success = true; 107 | resp.Id = r.Id; 108 | SetResponseVerifier(resp, aes); 109 | foreach (var entry in resp.Entries) 110 | { 111 | entry.Name = CryptoTransform(entry.Name, false, true, aes, CMode.ENCRYPT); 112 | entry.Login = CryptoTransform(entry.Login, false, true, aes, CMode.ENCRYPT); 113 | entry.Uuid = CryptoTransform(entry.Uuid, false, true, aes, CMode.ENCRYPT); 114 | } 115 | } 116 | 117 | private IEnumerable FindMatchingEntries(Request r, Aes aes) 118 | { 119 | string submitHost = null; 120 | string realm = null; 121 | var listResult = new List(); 122 | var url = CryptoTransform(r.Url, true, false, aes, CMode.DECRYPT); 123 | string formHost, searchHost; 124 | formHost = searchHost = GetHost(url); 125 | string hostScheme = GetScheme(url); 126 | if (r.SubmitUrl != null) { 127 | submitHost = GetHost(CryptoTransform(r.SubmitUrl, true, false, aes, CMode.DECRYPT)); 128 | } 129 | if (r.Realm != null) 130 | realm = CryptoTransform(r.Realm, true, false, aes, CMode.DECRYPT); 131 | 132 | var origSearchHost = searchHost; 133 | var parms = MakeSearchParameters(); 134 | 135 | List listDatabases = new List(); 136 | 137 | var configOpt = new ConfigOpt(this.host.CustomConfig); 138 | if (configOpt.SearchInAllOpenedDatabases) 139 | { 140 | foreach (PwDocument doc in host.MainWindow.DocumentManager.Documents) 141 | { 142 | if (doc.Database.IsOpen) 143 | { 144 | listDatabases.Add(doc.Database); 145 | } 146 | } 147 | } 148 | else 149 | { 150 | listDatabases.Add(host.Database); 151 | } 152 | 153 | int listCount = 0; 154 | foreach (PwDatabase db in listDatabases) 155 | { 156 | searchHost = origSearchHost; 157 | //get all possible entries for given host-name 158 | while (listResult.Count == listCount && (origSearchHost == searchHost || searchHost.IndexOf(".") != -1)) 159 | { 160 | parms.SearchString = String.Format("^{0}$|/{0}/?", searchHost); 161 | var listEntries = new PwObjectList(); 162 | db.RootGroup.SearchEntries(parms, listEntries); 163 | foreach (var le in listEntries) 164 | { 165 | listResult.Add(new PwEntryDatabase(le, db)); 166 | } 167 | searchHost = searchHost.Substring(searchHost.IndexOf(".") + 1); 168 | 169 | //searchHost contains no dot --> prevent possible infinite loop 170 | if (searchHost == origSearchHost) 171 | break; 172 | } 173 | listCount = listResult.Count; 174 | } 175 | 176 | 177 | Func filter = delegate(PwEntry e) 178 | { 179 | var title = e.Strings.ReadSafe(PwDefs.TitleField); 180 | var entryUrl = e.Strings.ReadSafe(PwDefs.UrlField); 181 | var c = GetEntryConfig(e); 182 | if (c != null) 183 | { 184 | if (c.Allow.Contains(formHost) && (submitHost == null || c.Allow.Contains(submitHost))) 185 | return true; 186 | if (c.Deny.Contains(formHost) || (submitHost != null && c.Deny.Contains(submitHost))) 187 | return false; 188 | if (realm != null && c.Realm != realm) 189 | return false; 190 | } 191 | 192 | if (entryUrl != null && (entryUrl.StartsWith("http://") || entryUrl.StartsWith("https://") || title.StartsWith("ftp://") || title.StartsWith("sftp://"))) 193 | { 194 | var uHost = GetHost(entryUrl); 195 | if (formHost.EndsWith(uHost)) 196 | return true; 197 | } 198 | 199 | if (title.StartsWith("http://") || title.StartsWith("https://") || title.StartsWith("ftp://") || title.StartsWith("sftp://")) 200 | { 201 | var uHost = GetHost(title); 202 | if (formHost.EndsWith(uHost)) 203 | return true; 204 | } 205 | return formHost.Contains(title) || (entryUrl != null && formHost.Contains(entryUrl)); 206 | }; 207 | 208 | Func filterSchemes = delegate(PwEntry e) 209 | { 210 | var title = e.Strings.ReadSafe(PwDefs.TitleField); 211 | var entryUrl = e.Strings.ReadSafe(PwDefs.UrlField); 212 | 213 | if (entryUrl != null) 214 | { 215 | var entryScheme = GetScheme(entryUrl); 216 | if (entryScheme == hostScheme) 217 | { 218 | return true; 219 | } 220 | } 221 | 222 | var titleScheme = GetScheme(title); 223 | if (titleScheme == hostScheme) 224 | { 225 | return true; 226 | } 227 | 228 | return false; 229 | }; 230 | 231 | var result = from e in listResult where filter(e.entry) select e; 232 | 233 | if (configOpt.MatchSchemes) 234 | { 235 | result = from e in result where filterSchemes(e.entry) select e; 236 | } 237 | 238 | Func hideExpired = delegate(PwEntry e) 239 | { 240 | DateTime dtNow = DateTime.UtcNow; 241 | 242 | if(e.Expires && (e.ExpiryTime <= dtNow)) 243 | { 244 | return false; 245 | } 246 | 247 | return true; 248 | }; 249 | 250 | if (configOpt.HideExpired) 251 | { 252 | result = from e in result where hideExpired(e.entry) select e; 253 | } 254 | 255 | return result; 256 | } 257 | 258 | private void GetLoginsCountHandler(Request r, Response resp, Aes aes) 259 | { 260 | if (!VerifyRequest(r, aes)) 261 | return; 262 | 263 | resp.Success = true; 264 | resp.Id = r.Id; 265 | var items = FindMatchingEntries(r, aes); 266 | SetResponseVerifier(resp, aes); 267 | resp.Count = items.ToList().Count; 268 | } 269 | 270 | private void GetLoginsHandler(Request r, Response resp, Aes aes) 271 | { 272 | if (!VerifyRequest(r, aes)) 273 | return; 274 | 275 | string submithost = null; 276 | var host = GetHost(CryptoTransform(r.Url, true, false, aes, CMode.DECRYPT)); 277 | if (r.SubmitUrl != null) 278 | submithost = GetHost(CryptoTransform(r.SubmitUrl, true, false, aes, CMode.DECRYPT)); 279 | 280 | var items = FindMatchingEntries(r, aes); 281 | if (items.ToList().Count > 0) 282 | { 283 | Func filter = delegate(PwEntry e) 284 | { 285 | var c = GetEntryConfig(e); 286 | 287 | var title = e.Strings.ReadSafe(PwDefs.TitleField); 288 | var entryUrl = e.Strings.ReadSafe(PwDefs.UrlField); 289 | if (c != null) 290 | { 291 | return title != host && entryUrl != host && !c.Allow.Contains(host) || (submithost != null && !c.Allow.Contains(submithost) && submithost != title && submithost != entryUrl); 292 | } 293 | return title != host && entryUrl != host || (submithost != null && title != submithost && entryUrl != submithost); 294 | }; 295 | 296 | var configOpt = new ConfigOpt(this.host.CustomConfig); 297 | var config = GetConfigEntry(true); 298 | var autoAllowS = config.Strings.ReadSafe("Auto Allow"); 299 | var autoAllow = autoAllowS != null && autoAllowS.Trim() != ""; 300 | autoAllow = autoAllow || configOpt.AlwaysAllowAccess; 301 | var needPrompting = from e in items where filter(e.entry) select e; 302 | 303 | if (needPrompting.ToList().Count > 0 && !autoAllow) 304 | { 305 | var win = this.host.MainWindow; 306 | 307 | using (var f = new AccessControlForm()) 308 | { 309 | win.Invoke((MethodInvoker)delegate 310 | { 311 | f.Icon = win.Icon; 312 | f.Plugin = this; 313 | f.Entries = (from e in items where filter(e.entry) select e.entry).ToList(); 314 | //f.Entries = needPrompting.ToList(); 315 | f.Host = submithost != null ? submithost : host; 316 | f.Load += delegate { f.Activate(); }; 317 | f.ShowDialog(win); 318 | if (f.Remember && (f.Allowed || f.Denied)) 319 | { 320 | foreach (var e in needPrompting) 321 | { 322 | var c = GetEntryConfig(e.entry); 323 | if (c == null) 324 | c = new KeePassHttpEntryConfig(); 325 | var set = f.Allowed ? c.Allow : c.Deny; 326 | set.Add(host); 327 | if (submithost != null && submithost != host) 328 | set.Add(submithost); 329 | SetEntryConfig(e.entry, c); 330 | 331 | } 332 | } 333 | if (!f.Allowed) 334 | { 335 | items = items.Except(needPrompting); 336 | } 337 | }); 338 | } 339 | } 340 | 341 | string compareToUrl = null; 342 | if (r.SubmitUrl != null) 343 | { 344 | compareToUrl = CryptoTransform(r.SubmitUrl, true, false, aes, CMode.DECRYPT); 345 | } 346 | if(String.IsNullOrEmpty(compareToUrl)) 347 | compareToUrl = CryptoTransform(r.Url, true, false, aes, CMode.DECRYPT); 348 | 349 | compareToUrl = compareToUrl.ToLower(); 350 | 351 | foreach (var entryDatabase in items) 352 | { 353 | string entryUrl = String.Copy(entryDatabase.entry.Strings.ReadSafe(PwDefs.UrlField)); 354 | if (String.IsNullOrEmpty(entryUrl)) 355 | entryUrl = entryDatabase.entry.Strings.ReadSafe(PwDefs.TitleField); 356 | 357 | entryUrl = entryUrl.ToLower(); 358 | 359 | entryDatabase.entry.UsageCount = (ulong)LevenshteinDistance(compareToUrl, entryUrl); 360 | 361 | } 362 | 363 | var itemsList = items.ToList(); 364 | 365 | if (configOpt.SpecificMatchingOnly) 366 | { 367 | itemsList = (from e in itemsList 368 | orderby e.entry.UsageCount ascending 369 | select e).ToList(); 370 | 371 | ulong lowestDistance = itemsList.Count > 0 ? 372 | itemsList[0].entry.UsageCount : 373 | 0; 374 | 375 | itemsList = (from e in itemsList 376 | where e.entry.UsageCount == lowestDistance 377 | orderby e.entry.UsageCount 378 | select e).ToList(); 379 | 380 | } 381 | 382 | if (configOpt.SortResultByUsername) 383 | { 384 | var items2 = from e in itemsList orderby e.entry.UsageCount ascending, GetUserPass(e)[0] ascending select e; 385 | itemsList = items2.ToList(); 386 | } 387 | else 388 | { 389 | var items2 = from e in itemsList orderby e.entry.UsageCount ascending, e.entry.Strings.ReadSafe(PwDefs.TitleField) ascending select e; 390 | itemsList = items2.ToList(); 391 | } 392 | 393 | foreach (var entryDatabase in itemsList) 394 | { 395 | var e = PrepareElementForResponseEntries(configOpt, entryDatabase); 396 | resp.Entries.Add(e); 397 | } 398 | 399 | if (itemsList.Count > 0) 400 | { 401 | var names = (from e in resp.Entries select e.Name).Distinct(); 402 | var n = String.Join("\n ", names.ToArray()); 403 | 404 | if (configOpt.ReceiveCredentialNotification) 405 | ShowNotification(String.Format("{0}: {1} is receiving credentials for:\n {2}", r.Id, host, n)); 406 | } 407 | 408 | resp.Success = true; 409 | resp.Id = r.Id; 410 | SetResponseVerifier(resp, aes); 411 | 412 | foreach (var entry in resp.Entries) 413 | { 414 | entry.Name = CryptoTransform(entry.Name, false, true, aes, CMode.ENCRYPT); 415 | entry.Login = CryptoTransform(entry.Login, false, true, aes, CMode.ENCRYPT); 416 | entry.Uuid = CryptoTransform(entry.Uuid, false, true, aes, CMode.ENCRYPT); 417 | entry.Password = CryptoTransform(entry.Password, false, true, aes, CMode.ENCRYPT); 418 | 419 | if (entry.StringFields != null) 420 | { 421 | foreach (var sf in entry.StringFields) 422 | { 423 | sf.Key = CryptoTransform(sf.Key, false, true, aes, CMode.ENCRYPT); 424 | sf.Value = CryptoTransform(sf.Value, false, true, aes, CMode.ENCRYPT); 425 | } 426 | } 427 | } 428 | 429 | resp.Count = resp.Entries.Count; 430 | } 431 | else 432 | { 433 | resp.Success = true; 434 | resp.Id = r.Id; 435 | SetResponseVerifier(resp, aes); 436 | } 437 | } 438 | //http://en.wikibooks.org/wiki/Algorithm_Implementation/Strings/Levenshtein_distance#C.23 439 | private int LevenshteinDistance(string source, string target) 440 | { 441 | if (String.IsNullOrEmpty(source)) 442 | { 443 | if (String.IsNullOrEmpty(target)) return 0; 444 | return target.Length; 445 | } 446 | if (String.IsNullOrEmpty(target)) return source.Length; 447 | 448 | if (source.Length > target.Length) 449 | { 450 | var temp = target; 451 | target = source; 452 | source = temp; 453 | } 454 | 455 | var m = target.Length; 456 | var n = source.Length; 457 | var distance = new int[2, m + 1]; 458 | // Initialize the distance 'matrix' 459 | for (var j = 1; j <= m; j++) distance[0, j] = j; 460 | 461 | var currentRow = 0; 462 | for (var i = 1; i <= n; ++i) 463 | { 464 | currentRow = i & 1; 465 | distance[currentRow, 0] = i; 466 | var previousRow = currentRow ^ 1; 467 | for (var j = 1; j <= m; j++) 468 | { 469 | var cost = (target[j - 1] == source[i - 1] ? 0 : 1); 470 | distance[currentRow, j] = Math.Min(Math.Min( 471 | distance[previousRow, j] + 1, 472 | distance[currentRow, j - 1] + 1), 473 | distance[previousRow, j - 1] + cost); 474 | } 475 | } 476 | return distance[currentRow, m]; 477 | } 478 | 479 | private ResponseEntry PrepareElementForResponseEntries(ConfigOpt configOpt, PwEntryDatabase entryDatabase) 480 | { 481 | SprContext ctx = new SprContext(entryDatabase.entry, entryDatabase.database, SprCompileFlags.All, false, false); 482 | 483 | var name = entryDatabase.entry.Strings.ReadSafe(PwDefs.TitleField); 484 | var loginpass = GetUserPass(entryDatabase, ctx); 485 | var login = loginpass[0]; 486 | var passwd = loginpass[1]; 487 | var uuid = entryDatabase.entry.Uuid.ToHexString(); 488 | 489 | List fields = null; 490 | if (configOpt.ReturnStringFields) 491 | { 492 | fields = new List(); 493 | foreach (var sf in entryDatabase.entry.Strings) 494 | { 495 | var sfValue = entryDatabase.entry.Strings.ReadSafe(sf.Key); 496 | 497 | // follow references 498 | sfValue = SprEngine.Compile(sfValue, ctx); 499 | 500 | if (configOpt.ReturnStringFieldsWithKphOnly) 501 | { 502 | if (sf.Key.StartsWith("KPH: ")) 503 | { 504 | fields.Add(new ResponseStringField(sf.Key.Substring(5), sfValue)); 505 | } 506 | } 507 | else 508 | { 509 | fields.Add(new ResponseStringField(sf.Key, sfValue)); 510 | } 511 | } 512 | 513 | if (fields.Count > 0) 514 | { 515 | var fields2 = from e2 in fields orderby e2.Key ascending select e2; 516 | fields = fields2.ToList(); 517 | } 518 | else 519 | { 520 | fields = null; 521 | } 522 | } 523 | 524 | return new ResponseEntry(name, login, passwd, uuid, fields); 525 | } 526 | 527 | private void SetLoginHandler(Request r, Response resp, Aes aes) 528 | { 529 | if (!VerifyRequest(r, aes)) 530 | return; 531 | 532 | string url = CryptoTransform(r.Url, true, false, aes, CMode.DECRYPT); 533 | var urlHost = GetHost(url); 534 | 535 | PwUuid uuid = null; 536 | string username, password; 537 | 538 | username = CryptoTransform(r.Login, true, false, aes, CMode.DECRYPT); 539 | password = CryptoTransform(r.Password, true, false, aes, CMode.DECRYPT); 540 | 541 | if (r.Uuid != null) 542 | { 543 | uuid = new PwUuid(MemUtil.HexStringToByteArray( 544 | CryptoTransform(r.Uuid, true, false, aes, CMode.DECRYPT))); 545 | } 546 | 547 | if (uuid != null) 548 | { 549 | // modify existing entry 550 | UpdateEntry(uuid, username, password, urlHost, r.Id); 551 | } 552 | else 553 | { 554 | // create new entry 555 | CreateEntry(username, password, urlHost, url, r, aes); 556 | } 557 | 558 | resp.Success = true; 559 | resp.Id = r.Id; 560 | SetResponseVerifier(resp, aes); 561 | } 562 | 563 | private void AssociateHandler(Request r, Response resp, Aes aes) 564 | { 565 | if (!TestRequestVerifier(r, aes, r.Key)) 566 | return; 567 | 568 | // key is good, prompt user to save 569 | using (var f = new ConfirmAssociationForm()) 570 | { 571 | var win = host.MainWindow; 572 | win.Invoke((MethodInvoker)delegate 573 | { 574 | f.Activate(); 575 | f.Icon = win.Icon; 576 | f.Key = r.Key; 577 | f.Load += delegate { f.Activate(); }; 578 | f.ShowDialog(win); 579 | 580 | if (f.KeyId != null) 581 | { 582 | var entry = GetConfigEntry(true); 583 | 584 | bool keyNameExists = true; 585 | while (keyNameExists) 586 | { 587 | DialogResult keyExistsResult = DialogResult.Yes; 588 | foreach (var s in entry.Strings) 589 | { 590 | if (s.Key == ASSOCIATE_KEY_PREFIX + f.KeyId) 591 | { 592 | keyExistsResult = MessageBox.Show( 593 | win, 594 | "A shared encryption-key with the name \"" + f.KeyId + "\" already exists.\nDo you want to overwrite it?", 595 | "Overwrite existing key?", 596 | MessageBoxButtons.YesNo, 597 | MessageBoxIcon.Warning, 598 | MessageBoxDefaultButton.Button1 599 | ); 600 | break; 601 | } 602 | } 603 | 604 | if (keyExistsResult == DialogResult.No) 605 | { 606 | f.ShowDialog(win); 607 | } 608 | else 609 | { 610 | keyNameExists = false; 611 | } 612 | } 613 | 614 | if (f.KeyId != null) 615 | { 616 | entry.Strings.Set(ASSOCIATE_KEY_PREFIX + f.KeyId, new ProtectedString(true, r.Key)); 617 | entry.Touch(true); 618 | resp.Id = f.KeyId; 619 | resp.Success = true; 620 | SetResponseVerifier(resp, aes); 621 | UpdateUI(null); 622 | } 623 | } 624 | }); 625 | } 626 | } 627 | 628 | private void TestAssociateHandler(Request r, Response resp, Aes aes) 629 | { 630 | if (!VerifyRequest(r, aes)) 631 | return; 632 | 633 | resp.Success = true; 634 | resp.Id = r.Id; 635 | SetResponseVerifier(resp, aes); 636 | } 637 | 638 | private void GeneratePassword(Request r, Response resp, Aes aes) 639 | { 640 | if (!VerifyRequest(r, aes)) 641 | return; 642 | 643 | byte[] pbEntropy = null; 644 | ProtectedString psNew; 645 | PwProfile autoProfile = Program.Config.PasswordGenerator.AutoGeneratedPasswordsProfile; 646 | PwGenerator.Generate(out psNew, autoProfile, pbEntropy, Program.PwGeneratorPool); 647 | 648 | byte[] pbNew = psNew.ReadUtf8(); 649 | if (pbNew != null) 650 | { 651 | uint uBits = QualityEstimation.EstimatePasswordBits(pbNew); 652 | ResponseEntry item = new ResponseEntry(Request.GENERATE_PASSWORD, uBits.ToString(), StrUtil.Utf8.GetString(pbNew), Request.GENERATE_PASSWORD, null); 653 | resp.Entries.Add(item); 654 | resp.Success = true; 655 | resp.Count = 1; 656 | MemUtil.ZeroByteArray(pbNew); 657 | } 658 | 659 | resp.Id = r.Id; 660 | SetResponseVerifier(resp, aes); 661 | 662 | foreach (var entry in resp.Entries) 663 | { 664 | entry.Name = CryptoTransform(entry.Name, false, true, aes, CMode.ENCRYPT); 665 | entry.Login = CryptoTransform(entry.Login, false, true, aes, CMode.ENCRYPT); 666 | entry.Uuid = CryptoTransform(entry.Uuid, false, true, aes, CMode.ENCRYPT); 667 | entry.Password = CryptoTransform(entry.Password, false, true, aes, CMode.ENCRYPT); 668 | } 669 | } 670 | 671 | private KeePassHttpEntryConfig GetEntryConfig(PwEntry e) 672 | { 673 | var serializer = NewJsonSerializer(); 674 | if (e.Strings.Exists(KEEPASSHTTP_NAME)) 675 | { 676 | var json = e.Strings.ReadSafe(KEEPASSHTTP_NAME); 677 | using (var ins = new JsonTextReader(new StringReader(json))) 678 | { 679 | return serializer.Deserialize(ins); 680 | } 681 | } 682 | return null; 683 | } 684 | 685 | private void SetEntryConfig(PwEntry e, KeePassHttpEntryConfig c) 686 | { 687 | var serializer = NewJsonSerializer(); 688 | var writer = new StringWriter(); 689 | serializer.Serialize(writer, c); 690 | e.Strings.Set(KEEPASSHTTP_NAME, new ProtectedString(false, writer.ToString())); 691 | e.Touch(true); 692 | UpdateUI(e.ParentGroup); 693 | } 694 | 695 | private bool UpdateEntry(PwUuid uuid, string username, string password, string formHost, string requestId) 696 | { 697 | PwEntry entry = null; 698 | 699 | var configOpt = new ConfigOpt(this.host.CustomConfig); 700 | if (configOpt.SearchInAllOpenedDatabases) 701 | { 702 | foreach (PwDocument doc in host.MainWindow.DocumentManager.Documents) 703 | { 704 | if (doc.Database.IsOpen) 705 | { 706 | entry = doc.Database.RootGroup.FindEntry(uuid, true); 707 | if (entry != null) 708 | { 709 | break; 710 | } 711 | } 712 | } 713 | } 714 | else 715 | { 716 | entry = host.Database.RootGroup.FindEntry(uuid, true); 717 | } 718 | 719 | if (entry == null) 720 | { 721 | return false; 722 | } 723 | 724 | string[] up = GetUserPass(entry); 725 | var u = up[0]; 726 | var p = up[1]; 727 | 728 | if (u != username || p != password) 729 | { 730 | bool allowUpdate = configOpt.AlwaysAllowUpdates; 731 | 732 | if (!allowUpdate) 733 | { 734 | host.MainWindow.Activate(); 735 | 736 | DialogResult result; 737 | if (host.MainWindow.IsTrayed()) 738 | { 739 | result = MessageBox.Show( 740 | String.Format("Do you want to update the information in {0} - {1}?", formHost, u), 741 | "Update Entry", MessageBoxButtons.YesNo, 742 | MessageBoxIcon.None, MessageBoxDefaultButton.Button1, MessageBoxOptions.DefaultDesktopOnly); 743 | } 744 | else 745 | { 746 | result = MessageBox.Show( 747 | host.MainWindow, 748 | String.Format("Do you want to update the information in {0} - {1}?", formHost, u), 749 | "Update Entry", MessageBoxButtons.YesNo, 750 | MessageBoxIcon.Information, MessageBoxDefaultButton.Button1); 751 | } 752 | 753 | 754 | if (result == DialogResult.Yes) 755 | { 756 | allowUpdate = true; 757 | } 758 | } 759 | 760 | if (allowUpdate) 761 | { 762 | PwObjectList m_vHistory = entry.History.CloneDeep(); 763 | entry.History = m_vHistory; 764 | entry.CreateBackup(null); 765 | 766 | entry.Strings.Set(PwDefs.UserNameField, new ProtectedString(false, username)); 767 | entry.Strings.Set(PwDefs.PasswordField, new ProtectedString(true, password)); 768 | entry.Touch(true, false); 769 | UpdateUI(entry.ParentGroup); 770 | 771 | return true; 772 | } 773 | } 774 | 775 | return false; 776 | } 777 | 778 | private bool CreateEntry(string username, string password, string urlHost, string url, Request r, Aes aes) 779 | { 780 | string realm = null; 781 | if (r.Realm != null) 782 | realm = CryptoTransform(r.Realm, true, false, aes, CMode.DECRYPT); 783 | 784 | var root = host.Database.RootGroup; 785 | var group = root.FindCreateGroup(KEEPASSHTTP_GROUP_NAME, false); 786 | if (group == null) 787 | { 788 | group = new PwGroup(true, true, KEEPASSHTTP_GROUP_NAME, PwIcon.WorldComputer); 789 | root.AddGroup(group, true); 790 | UpdateUI(null); 791 | } 792 | 793 | string submithost = null; 794 | if (r.SubmitUrl != null) 795 | submithost = GetHost(CryptoTransform(r.SubmitUrl, true, false, aes, CMode.DECRYPT)); 796 | 797 | string baseUrl = url; 798 | // index bigger than https:// <-- this slash 799 | if (baseUrl.LastIndexOf("/") > 9) 800 | { 801 | baseUrl = baseUrl.Substring(0, baseUrl.LastIndexOf("/") + 1); 802 | } 803 | 804 | PwEntry entry = new PwEntry(true, true); 805 | entry.Strings.Set(PwDefs.TitleField, new ProtectedString(false, urlHost)); 806 | entry.Strings.Set(PwDefs.UserNameField, new ProtectedString(false, username)); 807 | entry.Strings.Set(PwDefs.PasswordField, new ProtectedString(true, password)); 808 | entry.Strings.Set(PwDefs.UrlField, new ProtectedString(true, baseUrl)); 809 | 810 | if ((submithost != null && urlHost != submithost) || realm != null) 811 | { 812 | var config = new KeePassHttpEntryConfig(); 813 | if (submithost != null) 814 | config.Allow.Add(submithost); 815 | if (realm != null) 816 | config.Realm = realm; 817 | 818 | var serializer = NewJsonSerializer(); 819 | var writer = new StringWriter(); 820 | serializer.Serialize(writer, config); 821 | entry.Strings.Set(KEEPASSHTTP_NAME, new ProtectedString(false, writer.ToString())); 822 | } 823 | 824 | group.AddEntry(entry, true); 825 | UpdateUI(group); 826 | 827 | return true; 828 | } 829 | } 830 | } 831 | --------------------------------------------------------------------------------