-
Notifications
You must be signed in to change notification settings - Fork 76
/
Copy pathAuthenticationMechanism.cs
268 lines (233 loc) · 8.95 KB
/
AuthenticationMechanism.cs
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
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
using System;
using System.Collections.Generic;
using System.Net.Security;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using Waher.Security;
namespace Waher.Networking.SASL
{
/// <summary>
/// Base class for all authentication mechanisms.
/// </summary>
public abstract class AuthenticationMechanism : IAuthenticationMechanism
{
private static readonly RandomNumberGenerator rnd = RandomNumberGenerator.Create();
/// <summary>
/// Base class for all authentication mechanisms.
/// </summary>
public AuthenticationMechanism()
{
}
/// <summary>
/// Name of the mechanism.
/// </summary>
public abstract string Name
{
get;
}
/// <summary>
/// Weight of mechanisms. The higher the value, the more preferred.
/// </summary>
public abstract int Weight
{
get;
}
/// <summary>
/// Checks if a mechanism is allowed during the current conditions.
/// </summary>
/// <param name="SslStream">SSL stream, if available.</param>
/// <returns>If mechanism is allowed.</returns>
public abstract bool Allowed(SslStream SslStream);
/// <summary>
/// Parses a parameter list in a challenge string.
/// </summary>
/// <param name="s">Encoded parameter list.</param>
/// <returns>Parsed parameters.</returns>
protected KeyValuePair<string, string>[] ParseCommaSeparatedParameterList(string s)
{
List<KeyValuePair<string, string>> Result = new List<KeyValuePair<string, string>>();
StringBuilder sb = new StringBuilder();
string Key = string.Empty;
int State = 0;
foreach (char ch in s)
{
switch (State)
{
case 0: // ID
if (ch == '=')
{
Key = sb.ToString();
sb.Clear();
State++;
}
else if (ch == ',')
{
Result.Add(new KeyValuePair<string, string>(sb.ToString(), string.Empty));
sb.Clear();
}
else
sb.Append(ch);
break;
case 1: // Value, first character
if (ch == '"')
State += 2;
else if (ch == ',')
{
Result.Add(new KeyValuePair<string, string>(Key, string.Empty));
sb.Clear();
State = 0;
Key = string.Empty;
}
else
{
sb.Append(ch);
State++;
}
break;
case 2: // Value, following characters
if (ch == ',')
{
Result.Add(new KeyValuePair<string, string>(Key, sb.ToString()));
sb.Clear();
State = 0;
Key = string.Empty;
}
else
sb.Append(ch);
break;
case 3: // Value, between quotes
if (ch == '"')
State--;
else if (ch == '\\')
State++;
else
sb.Append(ch);
break;
case 4: // Escaped character
sb.Append(ch);
State--;
break;
}
}
if (State == 2 && !string.IsNullOrEmpty(Key))
Result.Add(new KeyValuePair<string, string>(Key, sb.ToString()));
return Result.ToArray();
}
/// <summary>
/// Concatenates a sequence of byte arrays.
/// </summary>
/// <param name="Data">Byte arrays to concatenate.</param>
/// <returns>Concatenated byte array.</returns>
protected static byte[] CONCAT(params byte[][] Data)
{
int c = 0;
foreach (byte[] Part in Data)
c += Part.Length;
int i = 0;
int j;
byte[] Result = new byte[c];
foreach (byte[] Part in Data)
{
j = Part.Length;
Array.Copy(Part, 0, Result, i, j);
i += j;
}
return Result;
}
/// <summary>
/// Concatenates a sequence of strings.
/// </summary>
/// <param name="Parameters">Strings.</param>
/// <returns>Concatenation of strings.</returns>
protected static string CONCAT(params string[] Parameters)
{
StringBuilder sb = new StringBuilder();
foreach (string s in Parameters)
sb.Append(s);
return sb.ToString();
}
/// <summary>
/// Concatenates a byte array with a sequence of strings.
/// </summary>
/// <param name="Data">Byte array</param>
/// <param name="Parameters">Strings</param>
/// <returns>Byte array</returns>
protected static byte[] CONCAT(byte[] Data, params string[] Parameters)
{
return CONCAT(Data, Encoding.UTF8.GetBytes(CONCAT(Parameters)));
}
/// <summary>
/// Converts a byte array to a hexadecimal string.
/// </summary>
/// <param name="Data"></param>
/// <returns>Hexadecimal string representation of binary data.</returns>
protected static string HEX(byte[] Data)
{
return Hashes.BinaryToString(Data);
}
/// <summary>
/// XORs two byte arrays.
/// </summary>
/// <param name="U1">Array 1</param>
/// <param name="U2">Array 2</param>
/// <returns>Byte array with individual elements XORed.</returns>
/// <exception cref="Exception">If arrays of different sizes.</exception>
protected static byte[] XOR(byte[] U1, byte[] U2)
{
int i, c = U1.Length;
if (U2.Length != c)
throw new Exception("Arrays must be of the same size.");
byte[] Response = new byte[c];
for (i = 0; i < c; i++)
Response[i] = (byte)(U1[i] ^ U2[i]);
return Response;
}
/// <summary>
/// Authentication request has been made.
/// </summary>
/// <param name="Data">Data in authentication request.</param>
/// <param name="Connection">Connection performing the authentication.</param>
/// <param name="PersistenceLayer">Persistence layer.</param>
/// <returns>If authentication was successful (true). If false, mechanism must send the corresponding challenge.</returns>
public abstract Task<bool?> AuthenticationRequest(string Data, ISaslServerSide Connection, ISaslPersistenceLayer PersistenceLayer);
/// <summary>
/// Response request has been made.
/// </summary>
/// <param name="Data">Data in response request.</param>
/// <param name="Connection">Connection performing the authentication.</param>
/// <param name="PersistenceLayer">Persistence layer.</param>
/// <returns>If authentication was successful (true).</returns>
public abstract Task<bool?> ResponseRequest(string Data, ISaslServerSide Connection, ISaslPersistenceLayer PersistenceLayer);
/// <summary>
/// Performs intitialization of the mechanism. Can be used to set
/// static properties that will be used through-out the runtime of the
/// server.
/// </summary>
public abstract Task Initialize();
/// <summary>
/// Authenticates the user using the provided credentials.
/// </summary>
/// <param name="UserName">User Name</param>
/// <param name="Password">Password</param>
/// <param name="Connection">Connection</param>
/// <returns>If authentication was successful or not. If null is returned, the mechanism did not perform authentication.</returns>
public abstract Task<bool?> Authenticate(string UserName, string Password, ISaslClientSide Connection);
/// <summary>
/// Gets an array of random bytes.
/// </summary>
/// <param name="Count">Number of random bytes to generate.</param>
/// <returns>Array of random bytes.</returns>
protected static byte[] GetRandomBytes(int Count)
{
if (Count < 0)
throw new ArgumentException("Count must be positive.", nameof(Count));
byte[] Result = new byte[Count];
lock(rnd)
{
rnd.GetBytes(Result);
}
return Result;
}
}
}