1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 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 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224
|
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;
using KeePass.Plugins;
using System.Reflection;
using System.Diagnostics;
namespace KeePassHttp
{
public sealed partial class KeePassHttpExt : Plugin
{
private static string encode64(byte[] b)
{
return System.Convert.ToBase64String(b);
}
private static byte[] decode64(string s)
{
return System.Convert.FromBase64String(s);
}
private bool VerifyRequest(Request r, Aes aes)
{
var entry = GetConfigEntry(false);
if (entry == null)
return false;
var s = entry.Strings.Get(ASSOCIATE_KEY_PREFIX + r.Id);
if (s == null)
return false;
return TestRequestVerifier(r, aes, s.ReadString());
}
private bool TestRequestVerifier(Request r, Aes aes, string key)
{
var success = false;
var crypted = decode64(r.Verifier);
aes.Key = decode64(key);
aes.IV = decode64(r.Nonce);
using (var dec = aes.CreateDecryptor())
{
try {
var buf = dec.TransformFinalBlock(crypted, 0, crypted.Length);
var value = Encoding.UTF8.GetString(buf);
success = value == r.Nonce;
} catch (CryptographicException) { } // implicit failure
}
return success;
}
private void SetResponseVerifier(Response r, Aes aes)
{
aes.GenerateIV();
r.Nonce = encode64(aes.IV);
r.Verifier = CryptoTransform(r.Nonce, false, true, aes, CMode.ENCRYPT);
}
}
public class Request
{
public const string GET_LOGINS = "get-logins";
public const string GET_LOGINS_COUNT = "get-logins-count";
public const string GET_ALL_LOGINS = "get-all-logins";
public const string SET_LOGIN = "set-login";
public const string ASSOCIATE = "associate";
public const string TEST_ASSOCIATE = "test-associate";
public const string GENERATE_PASSWORD = "generate-password";
public string RequestType;
/// <summary>
/// Sort selection by best URL matching for given hosts
/// </summary>
public string SortSelection;
/// <summary>
/// Trigger unlock of database even if feature is disabled in KPH (because of user interaction to fill-in)
/// </summary>
public string TriggerUnlock;
/// <summary>
/// Always encrypted, used with set-login, uuid is set
/// if modifying an existing login
/// </summary>
public string Login;
public string Password;
public string Uuid;
/// <summary>
/// Always encrypted, used with get and set-login
/// </summary>
public string Url;
/// <summary>
/// Always encrypted, used with get-login
/// </summary>
public string SubmitUrl;
/// <summary>
/// Send the AES key ID with the 'associate' request
/// </summary>
public string Key;
/// <summary>
/// Always required, an identifier given by the KeePass user
/// </summary>
public string Id;
/// <summary>
/// A value used to ensure that the correct key has been chosen,
/// it is always the value of Nonce encrypted with Key
/// </summary>
public string Verifier;
/// <summary>
/// Nonce value used in conjunction with all encrypted fields,
/// randomly generated for each request
/// </summary>
public string Nonce;
/// <summary>
/// Realm value used for filtering results. Always encrypted.
/// </summary>
public string Realm;
}
public class Response
{
public Response(string request, string hash)
{
RequestType = request;
if (request == Request.GET_LOGINS || request == Request.GET_ALL_LOGINS || request == Request.GENERATE_PASSWORD)
Entries = new List<ResponseEntry>();
else
Entries = null;
Assembly assembly = Assembly.GetExecutingAssembly();
FileVersionInfo fvi = FileVersionInfo.GetVersionInfo(assembly.Location);
this.Version = fvi.ProductVersion;
this.Hash = hash;
}
/// <summary>
/// Mirrors the request type of KeePassRequest
/// </summary>
public string RequestType;
public string Error = null;
public bool Success = false;
/// <summary>
/// The user selected string as a result of 'associate',
/// always returned on every request
/// </summary>
public string Id;
/// <summary>
/// response to get-logins-count, number of entries for requested Url
/// </summary>
public int Count = 0;
/// <summary>
/// response the current version of KeePassHttp
/// </summary>
public string Version = "";
/// <summary>
/// response an unique hash of the database composed of RootGroup UUid and RecycleBin UUid
/// </summary>
public string Hash = "";
/// <summary>
/// The resulting entries for a get-login request
/// </summary>
public List<ResponseEntry> Entries { get; private set; }
/// <summary>
/// Nonce value used in conjunction with all encrypted fields,
/// randomly generated for each request
/// </summary>
public string Nonce;
/// <summary>
/// Same purpose as Request.Verifier, but a new value
/// </summary>
public string Verifier;
}
public class ResponseEntry
{
public ResponseEntry() { }
public ResponseEntry(string name, string login, string password, string uuid, List<ResponseStringField> stringFields)
{
Login = login;
Password = password;
Uuid = uuid;
Name = name;
StringFields = stringFields;
}
public string Login;
public string Password;
public string Uuid;
public string Name;
public List<ResponseStringField> StringFields = null;
}
public class ResponseStringField
{
public ResponseStringField() {}
public ResponseStringField(string key, string value)
{
Key = key;
Value = value;
}
public string Key;
public string Value;
}
public class KeePassHttpEntryConfig
{
public HashSet<string> Allow = new HashSet<string>();
public HashSet<string> Deny = new HashSet<string>();
public string Realm = null;
}
}
|