1 using System; 2 using System.Collections.Generic; 3 using System.ComponentModel; 4 using System.Data; 5 using System.Drawing; 6 using System.IO; 7 using System.Text; 8 using System.Windows.Forms; 9 10 using KeePass; 11 using KeePass.UI; 12 using KeePass.Plugins; 13 using KeePass.Resources; 14 using KeePassRPC; 15 using System.Reflection; 16 17 namespace KeePassRPC.Forms 18 { 19 public partial class OptionsForm : Form 20 { 21 private IPluginHost _host; 22 private KeePassRPCExt _plugin; 23 OptionsForm(IPluginHost host, KeePassRPCExt plugin)24 public OptionsForm(IPluginHost host, KeePassRPCExt plugin) 25 { 26 _host = host; 27 _plugin = plugin; 28 29 InitializeComponent(); 30 Icon = global::KeePassRPC.Properties.Resources.kee; 31 checkBox1.Text = "Automatically save KeePass database when Kee makes changes"; 32 if (host.CustomConfig.GetBool("KeePassRPC.KeeFox.autoCommit", true)) 33 checkBox1.Checked = true; 34 else 35 checkBox1.Checked = false; 36 37 checkBox2.Text = "Immediately edit entries created by Kee"; 38 if (host.CustomConfig.GetBool("KeePassRPC.KeeFox.editNewEntries", false)) 39 checkBox2.Checked = true; 40 else 41 checkBox2.Checked = false; 42 43 label13.Text = "You can generate new random passwords from Kee. These are stored in your system clipboard ready for you to paste into \"new user\" or \"change password\" fields. To protect against accidents these new passwords can be automatically stored in your current KeePass database under a special \"Kee Generated Password Backups\" group. You can generate new passwords when not logged in to a KeePass database but they will not receive this extra protection. The KeePass database can NOT be automatically saved after creating these backups so some problems can still result in a lost generated password."; 44 45 checkBox3.Text = "Store a backup of each password generated by Kee"; 46 if (host.CustomConfig.GetBool("KeePassRPC.KeeFox.backupNewPasswords", true)) 47 checkBox3.Checked = true; 48 else 49 checkBox3.Checked = false; 50 51 textBoxAuthExpiry.Text = (_host.CustomConfig.GetLong("KeePassRPC.AuthorisationExpiryTime", 8760 * 3600) / 3600).ToString(); 52 53 long secLevel = _host.CustomConfig.GetLong("KeePassRPC.SecurityLevel", 2); 54 long secLevelClientMin = _host.CustomConfig.GetLong("KeePassRPC.SecurityLevelClientMinimum", 2); 55 switch (secLevel) 56 { 57 case 1: comboBoxSecLevelKeePass.SelectedItem = "Low"; break; 58 case 2: comboBoxSecLevelKeePass.SelectedItem = "Medium"; break; 59 default: comboBoxSecLevelKeePass.SelectedItem = "High"; break; 60 } 61 switch (secLevelClientMin) 62 { 63 case 1: comboBoxSecLevelMinClient.SelectedItem = "Low"; break; 64 case 2: comboBoxSecLevelMinClient.SelectedItem = "Medium"; break; 65 default: comboBoxSecLevelMinClient.SelectedItem = "High"; break; 66 } 67 68 label6.Text = "Listen for connections on this TCP/IP port."; 69 textBoxPort.Text = _host.CustomConfig.GetLong("KeePassRPC.webSocket.port", 12546).ToString(); 70 71 UpdateAuthorisedConnections(); 72 } 73 UpdateAuthorisedConnections()74 private void UpdateAuthorisedConnections() 75 { 76 KeyContainerClass[] kcs = FindAuthorisedConnections(); 77 78 if (kcs == null) 79 { 80 // Tell the user it's not worked. 81 dataGridView1.Visible = false; 82 labelAuthorisedClientsFail.Visible = true; 83 return; 84 } 85 86 List<string> connectedClientUsernames = new List<string>(); 87 88 foreach (KeePassRPCClientConnection client in _plugin.GetConnectedRPCClients()) 89 if (!string.IsNullOrEmpty(client.UserName)) 90 connectedClientUsernames.Add(client.UserName); 91 92 // Update the screen 93 foreach (KeyContainerClass kc in kcs) 94 { 95 bool connected = false; 96 if (connectedClientUsernames.Contains(kc.Username)) 97 connected = true; 98 99 string[] row = new string[] { kc.ClientName, kc.Username, kc.AuthExpires.ToString() }; 100 int rowid = dataGridView1.Rows.Add(row); 101 dataGridView1.Rows[rowid].Cells[3].Value = connected; 102 } 103 return; 104 105 } 106 FindAuthorisedConnections()107 private KeyContainerClass[] FindAuthorisedConnections() 108 { 109 //This might not work, especially in .NET 2.0 RTM, a shame but more 110 //up to date users might as well use the feature if possible. 111 Dictionary<string, string> configValues; 112 try 113 { 114 FieldInfo fi = typeof(KeePass.App.Configuration.AceCustomConfig) 115 .GetField("m_vItems", BindingFlags.NonPublic | BindingFlags.Instance); 116 configValues = (Dictionary<string, string>)fi.GetValue(_host.CustomConfig); 117 118 119 } 120 catch 121 { 122 return null; 123 } 124 125 List<KeyContainerClass> keyContainers = new List<KeyContainerClass>(); 126 127 foreach (KeyValuePair<string, string> kvp in configValues) 128 { 129 if (kvp.Key.StartsWith("KeePassRPC.Key.")) 130 { 131 string username = kvp.Key.Substring(15); 132 byte[] serialisedKeyContainer = null; 133 134 // Assume config entry is encrypted but fall back to attempting direct deserialisation if something goes wrong 135 136 if (string.IsNullOrEmpty(kvp.Value)) 137 return null; 138 try 139 { 140 byte[] keyBytes = System.Security.Cryptography.ProtectedData.Unprotect( 141 Convert.FromBase64String(kvp.Value), 142 new byte[] { 172, 218, 37, 36, 15 }, 143 System.Security.Cryptography.DataProtectionScope.CurrentUser); 144 serialisedKeyContainer = keyBytes; 145 System.Xml.Serialization.XmlSerializer mySerializer = new System.Xml.Serialization.XmlSerializer(typeof(KeyContainerClass)); 146 using (MemoryStream ms = new System.IO.MemoryStream(serialisedKeyContainer)) 147 { 148 keyContainers.Add((KeyContainerClass) mySerializer.Deserialize(ms)); 149 } 150 } 151 catch (Exception) 152 { 153 try 154 { 155 serialisedKeyContainer = Convert.FromBase64String(kvp.Value); 156 System.Xml.Serialization.XmlSerializer mySerializer = new System.Xml.Serialization.XmlSerializer(typeof(KeyContainerClass)); 157 using (MemoryStream ms = new MemoryStream(serialisedKeyContainer)) 158 { 159 keyContainers.Add((KeyContainerClass) mySerializer.Deserialize(ms)); 160 } 161 } 162 catch (Exception) 163 { 164 // It's not a valid entry so ignore it and move on 165 continue; 166 } 167 } 168 169 } 170 } 171 return keyContainers.ToArray(); 172 } 173 m_btnOK_Click(object sender, EventArgs e)174 private void m_btnOK_Click(object sender, EventArgs e) 175 { 176 ulong port = 0; 177 try 178 { 179 if (this.textBoxPort.Text.Length > 0) 180 { 181 port = ulong.Parse(this.textBoxPort.Text); 182 if (port <= 0 || port > 65535) 183 throw new ArgumentOutOfRangeException(); 184 if (port == _host.CustomConfig.GetULong("KeePassRPC.connection.port", 12536)) 185 throw new ArgumentException("The legacy KeePassRPC connection system is configured to use the port you have selected so please select a different port."); 186 if (port == 19455) 187 throw new ArgumentException("Port 19455 is commonly used by the unrelated KeePassHTTP plugin so please select a different port."); 188 } 189 } 190 catch (ArgumentOutOfRangeException) 191 { 192 MessageBox.Show("Invalid listening port. Type a number between 1 and 65535 or leave empty to use the default port."); 193 DialogResult = DialogResult.None; 194 return; 195 } 196 catch (ArgumentException ex) 197 { 198 MessageBox.Show(ex.Message); 199 DialogResult = DialogResult.None; 200 return; 201 } 202 203 long expTime = 8760; 204 try 205 { 206 expTime = long.Parse(this.textBoxAuthExpiry.Text); 207 } 208 catch (Exception) 209 { 210 MessageBox.Show("Invalid expiry time."); 211 DialogResult = DialogResult.None; 212 return; 213 } 214 215 if (expTime < 1) 216 { 217 expTime = 1; 218 MessageBox.Show("Expiry time set to 1 hour. This is the minimum allowed."); 219 } 220 221 if (expTime > 876000) 222 { 223 expTime = 876000; 224 MessageBox.Show("Expiry time set to 100 years. This is the maximum allowed."); 225 } 226 227 long secLevel = 2; 228 long secLevelClientMin = 2; 229 switch ((string)comboBoxSecLevelKeePass.SelectedItem) 230 { 231 case "Low": secLevel = 1; break; 232 case "Medium": secLevel = 2; break; 233 default: secLevel = 3; break; 234 } 235 switch ((string)comboBoxSecLevelMinClient.SelectedItem) 236 { 237 case "Low": secLevelClientMin = 1; break; 238 case "Medium": secLevelClientMin = 2; break; 239 default: secLevelClientMin = 3; break; 240 } 241 242 _host.CustomConfig.SetBool("KeePassRPC.KeeFox.autoCommit", checkBox1.Checked); 243 _host.CustomConfig.SetBool("KeePassRPC.KeeFox.editNewEntries", checkBox2.Checked); 244 _host.CustomConfig.SetBool("KeePassRPC.KeeFox.backupNewPasswords", checkBox3.Checked); 245 _host.CustomConfig.SetLong("KeePassRPC.AuthorisationExpiryTime", expTime * 3600); 246 _host.CustomConfig.SetLong("KeePassRPC.SecurityLevel", secLevel); 247 _host.CustomConfig.SetLong("KeePassRPC.SecurityLevelClientMinimum", secLevelClientMin); 248 249 if (port > 0) 250 _host.CustomConfig.SetULong("KeePassRPC.webSocket.port", port); 251 252 _host.MainWindow.Invoke((MethodInvoker)delegate { _host.MainWindow.SaveConfig(); }); 253 } 254 OnFormLoad(object sender, EventArgs e)255 private void OnFormLoad(object sender, EventArgs e) 256 { 257 GlobalWindowManager.AddWindow(this); 258 259 // Prevent one cell being blue by default to reduce distraction 260 dataGridView1.ClearSelection(); 261 } 262 btnCancel_Click(object sender, EventArgs e)263 private void btnCancel_Click(object sender, EventArgs e) 264 { 265 this.Close(); 266 } 267 OnFormClosed(object sender, FormClosedEventArgs e)268 private void OnFormClosed(object sender, FormClosedEventArgs e) 269 { 270 GlobalWindowManager.RemoveWindow(this); 271 } 272 comboBoxSecLevelKeePass_SelectedIndexChanged(object sender, EventArgs e)273 private void comboBoxSecLevelKeePass_SelectedIndexChanged(object sender, EventArgs e) 274 { 275 if ((string)comboBoxSecLevelKeePass.SelectedItem == "Low") 276 labelSecLevelWarning.Text = "A low security setting could increase the chance of your passwords being stolen. Please make sure you read the information in the manual (see link above)."; 277 else if ((string)comboBoxSecLevelKeePass.SelectedItem == "High") 278 labelSecLevelWarning.Text = "A high security setting will require you to enter a randomly generated password every time you start KeePass or its client. A medium setting should suffice in most situations, especially if you set a low authorisation timeout below."; 279 else 280 labelSecLevelWarning.Text = ""; 281 } 282 dataGridView1_CellContentClick(object sender, DataGridViewCellEventArgs e)283 private void dataGridView1_CellContentClick(object sender, DataGridViewCellEventArgs e) 284 { 285 if (e.ColumnIndex == 4) 286 { 287 string username = (string)dataGridView1.Rows[e.RowIndex].Cells[1].Value; 288 289 // Revoke authorisation by deleting stored key data 290 _host.CustomConfig.SetString("KeePassRPC.Key." + username, null); 291 292 // If this connection is active, destroy it now 293 foreach (KeePassRPCClientConnection client in _plugin.GetConnectedRPCClients()) 294 if (!string.IsNullOrEmpty(client.UserName) && client.UserName == username) 295 { 296 client.WebSocketConnection.Close(); 297 break; 298 } 299 300 // Refresh the view 301 dataGridView1.Rows.RemoveAt(e.RowIndex); 302 dataGridView1.Refresh(); 303 //UpdateAuthorisedConnections(); 304 } 305 } 306 307 } 308 } 309