Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
ScramAuthenticationMechanism.cs
1using System;
2using System.Collections.Generic;
3using System.Net.Security;
4using System.Security.Cryptography;
5using System.Text;
6using System.Threading.Tasks;
9
11{
18 {
19 private static readonly byte[] clientKey = Encoding.UTF8.GetBytes("Client Key");
20 private static readonly byte[] serverKey = Encoding.UTF8.GetBytes("Server Key");
21
22 private static byte[] salt = null;
23 private static string saltBase64 = null;
24
31 {
32 }
33
39 public override bool Allowed(SslStream SslStream)
40 {
41 return true;
42 }
43
51 public override Task<bool?> AuthenticationRequest(string Data, ISaslServerSide Connection, ISaslPersistenceLayer PersistenceLayer)
52 {
53 byte[] Bin = Convert.FromBase64String(Data);
54 string Request = Encoding.UTF8.GetString(Bin);
55 string UserName = null;
56 string Nonce = null;
57 int NrIterations = 4096;
58
59 foreach (KeyValuePair<string, string> Pair in this.ParseCommaSeparatedParameterList(Request))
60 {
61 switch (Pair.Key.ToLower())
62 {
63 case "n":
64 UserName = Pair.Value;
65 break;
66
67 case "r":
68 Nonce = Pair.Value;
69 break;
70 }
71 }
72
73 if (UserName is null || Nonce is null)
74 {
75 Connection.SaslErrorMalformedRequest();
76 LoginAuditor.Fail("Login attempt using malformed request.", UserName, Connection.RemoteEndpoint, Connection.Protocol);
77 return Task.FromResult<bool?>(null);
78 }
79
80 Connection.SetUserIdentity(UserName);
81
82 string ServerNonce = Nonce + Convert.ToBase64String(PersistenceLayer.GetRandomNumbers(32));
83
84 string Challenge = "r=" + ServerNonce + ",s=" + saltBase64 + ",i=" + NrIterations.ToString();
85 Bin = Encoding.UTF8.GetBytes(Challenge);
86 string ChallengeBase64 = Convert.ToBase64String(Bin);
87
88 Connection.Tag = new object[] { UserName, Nonce, NrIterations, ServerNonce, Request, Challenge };
89 Connection.SaslChallenge(ChallengeBase64);
90
91 return Task.FromResult<bool?>(null);
92 }
93
101 public override async Task<bool?> ResponseRequest(string Data, ISaslServerSide Connection, ISaslPersistenceLayer PersistenceLayer)
102 {
103 byte[] Bin = Convert.FromBase64String(Data);
104 string Request = Encoding.UTF8.GetString(Bin);
105 object[] P = (object[])Connection.Tag;
106 string UserName = (string)P[0];
107 //string Nonce = (string)P[1];
108 int NrIterations = (int)P[2];
109 string ServerNonce = (string)P[3];
110 string ClientRequest = (string)P[4];
111 string ServerChallenge = (string)P[5];
112 string c;
113 string Proof = null;
114 bool ServerNonceChecked = false;
115
116 foreach (KeyValuePair<string, string> Pair in this.ParseCommaSeparatedParameterList(Request))
117 {
118 switch (Pair.Key.ToLower())
119 {
120 case "c":
121 c = Pair.Value;
122 break;
123
124 case "r":
125 ServerNonceChecked = true;
126 if (ServerNonce != Pair.Value)
127 {
128 await Connection.SaslErrorMalformedRequest();
129 LoginAuditor.Fail("Login attempt using malformed request.", UserName, Connection.RemoteEndpoint, Connection.Protocol);
130 return null;
131 }
132 break;
133
134 case "p":
135 Proof = Pair.Value;
136 break;
137 }
138 }
139
140 if (!ServerNonceChecked)
141 {
142 await Connection.SaslErrorMalformedRequest();
143 LoginAuditor.Fail("Login attempt using malformed request.", UserName, Connection.RemoteEndpoint, Connection.Protocol);
144 return null;
145 }
146
147 IAccount Account = await PersistenceLayer.GetAccount(UserName);
148 if (Account is null)
149 {
150 LoginAuditor.Fail("Login attempt using invalid user name.", UserName, Connection.RemoteEndpoint, Connection.Protocol,
151 new KeyValuePair<string, object>("UserName", UserName));
152 await Connection.SaslErrorNotAuthorized();
153 return null;
154 }
155
156 if (!Account.Enabled)
157 {
158 LoginAuditor.Fail("Login attempt using disabled account.", UserName, Connection.RemoteEndpoint, Connection.Protocol);
159 await Connection.SaslErrorAccountDisabled();
160 return null;
161 }
162
163 byte[] SaltedPassword = this.Hi(Encoding.UTF8.GetBytes(Account.Password.Normalize()), salt, NrIterations);
164 byte[] ClientKey = this.HMAC(SaltedPassword, clientKey);
165 byte[] StoredKey = this.H(ClientKey);
166 StringBuilder sb;
167
168 sb = new StringBuilder();
169
170 int i;
171
172 i = ClientRequest.IndexOf("n=");
173 if (i < 0)
174 sb.Append(ClientRequest);
175 else
176 sb.Append(ClientRequest.Substring(i));
177
178 sb.Append(',');
179 sb.Append(ServerChallenge);
180 sb.Append(',');
181
182 i = Request.IndexOf(",p=");
183 if (i < 0)
184 sb.Append(Request);
185 else
186 sb.Append(Request.Substring(0, i));
187
188 byte[] AuthenticationMessage = Encoding.UTF8.GetBytes(sb.ToString());
189 byte[] ClientSignature = this.HMAC(StoredKey, AuthenticationMessage);
190 byte[] ClientProof = XOR(ClientKey, ClientSignature);
191
192 byte[] ServerKey = this.HMAC(SaltedPassword, serverKey);
193 byte[] ServerSignature = this.HMAC(ServerKey, AuthenticationMessage);
194
195 string ClientProofStr = Convert.ToBase64String(ClientProof);
196 if (Proof == ClientProofStr)
197 {
198 await Connection.SetAccount(Account);
199
200 string Response = "v=" + Convert.ToBase64String(ServerSignature);
201 Response = Convert.ToBase64String(Encoding.UTF8.GetBytes(Response));
202
203 Connection.ResetState(true);
204 await Connection.SaslSuccess(Response);
205 LoginAuditor.Success("Login successful.", UserName, Connection.RemoteEndpoint, Connection.Protocol);
206 }
207 else
208 {
209 await Connection.SaslErrorNotAuthorized();
210 LoginAuditor.Fail("Login attempt failed.", UserName, Connection.RemoteEndpoint, Connection.Protocol);
211 }
212
213 return null;
214 }
215
216 private byte[] Hi(byte[] String, byte[] Salt, int NrIterations)
217 {
218 byte[] U1 = this.HMAC(String, CONCAT(Salt, One));
219 byte[] U2 = this.HMAC(String, U1);
220 byte[] Response = XOR(U1, U2);
221
222 while (NrIterations > 2)
223 {
224 U1 = U2;
225 U2 = this.HMAC(String, U1);
226 Response = XOR(Response, U2);
227 NrIterations--;
228 }
229
230 return Response;
231 }
232
233 private static readonly byte[] One = new byte[] { 0, 0, 0, 1 };
234
240 public override async Task Initialize()
241 {
242 saltBase64 = await GetSaltBase64("XMPP.SCRAM.Server.Salt");
243 salt = Convert.FromBase64String(saltBase64);
244 }
245
251 public static async Task<string> GetSaltBase64(string Key)
252 {
253 string Result = await RuntimeSettings.GetAsync(Key, string.Empty);
254 if (string.IsNullOrEmpty(Result))
255 {
256 byte[] Bin = new byte[32];
257
258 using (RandomNumberGenerator Rnd = RandomNumberGenerator.Create())
259 {
260 Rnd.GetBytes(Bin);
261 }
262
263 Result = Convert.ToBase64String(Bin);
264 await RuntimeSettings.SetAsync(Key, Result);
265 }
266
267 return Result;
268 }
269
277 public override async Task<bool?> Authenticate(string UserName, string Password, ISaslClientSide Connection)
278 {
279 string Nonce = Convert.ToBase64String(GetRandomBytes(16));
280 string s = "n,,n=" + UserName + ",r=" + Nonce;
281 byte[] Data = Encoding.UTF8.GetBytes(s);
282 string Challenge = await Connection.Initiate(this, Convert.ToBase64String(Data));
283 byte[] ChallengeBinary = Convert.FromBase64String(Challenge);
284 string ChallengeString = Encoding.UTF8.GetString(ChallengeBinary);
285 string ServerNonce = null;
286 string SaltString;
287 int NrIterations = 0;
288 byte[] Salt = null;
289
290 foreach (KeyValuePair<string, string> Pair in this.ParseCommaSeparatedParameterList(ChallengeString))
291 {
292 switch (Pair.Key.ToLower())
293 {
294 case "r":
295 ServerNonce = Pair.Value;
296 break;
297
298 case "s":
299 SaltString = Pair.Value;
300 Salt = Convert.FromBase64String(SaltString);
301 break;
302
303 case "i":
304 NrIterations = int.Parse(Pair.Value);
305 break;
306 }
307 }
308
309 if (string.IsNullOrEmpty(ServerNonce) || !ServerNonce.StartsWith(Nonce) || Salt is null || NrIterations <= 0)
310 return null;
311
312 byte[] SaltedPassword = this.Hi(Encoding.UTF8.GetBytes(Password.Normalize()), Salt, NrIterations);
313
314 byte[] ClientKey = this.HMAC(SaltedPassword, Encoding.UTF8.GetBytes("Client Key"));
315 byte[] StoredKey = this.H(ClientKey);
316
317 StringBuilder sb;
318
319 sb = new StringBuilder();
320 sb.Append("n=");
321 sb.Append(UserName);
322 sb.Append(",r=");
323 sb.Append(Nonce);
324 sb.Append(',');
325 sb.Append(ChallengeString);
326 sb.Append(",c=biws,r=");
327 sb.Append(ServerNonce);
328
329 byte[] AuthenticationMessage = Encoding.UTF8.GetBytes(sb.ToString());
330 byte[] ClientSignature = this.HMAC(StoredKey, AuthenticationMessage);
331 byte[] ClientProof = XOR(ClientKey, ClientSignature);
332
333 byte[] ServerKey = this.HMAC(SaltedPassword, Encoding.UTF8.GetBytes("Server Key"));
334 byte[] ServerSignatureBinary = this.HMAC(ServerKey, AuthenticationMessage);
335
336 string ServerSignature = Convert.ToBase64String(ServerSignatureBinary);
337
338 sb = new StringBuilder();
339 sb.Append("c=biws,r="); // biws="n,,"
340 sb.Append(ServerNonce);
341 sb.Append(",p=");
342 sb.Append(Convert.ToBase64String(ClientProof));
343
344 string Success = await Connection.FinalResponse(this, Convert.ToBase64String(Encoding.UTF8.GetBytes(sb.ToString())));
345 if (string.IsNullOrEmpty(Success))
346 return true;
347
348 byte[] ResponseBinary = Convert.FromBase64String(Success);
349 string ResponseString = Encoding.UTF8.GetString(ResponseBinary);
350
351 foreach (KeyValuePair<string, string> Pair in this.ParseCommaSeparatedParameterList(ResponseString))
352 {
353 if (string.Compare(Pair.Key, "v", true) == 0)
354 return (Pair.Value == ServerSignature);
355 }
356
357 return false;
358 }
359
360 }
361}
static byte[] CONCAT(params byte[][] Data)
Concatenates a sequence of byte arrays.
static byte[] XOR(byte[] U1, byte[] U2)
XORs two byte arrays.
KeyValuePair< string, string >[] ParseCommaSeparatedParameterList(string s)
Parses a parameter list in a challenge string.
static byte[] GetRandomBytes(int Count)
Gets an array of random bytes.
Base class for all hashed authentication mechanisms.
abstract byte[] H(byte[] Data)
Hash function
byte[] HMAC(byte[] Key, byte[] Text)
See RFC 2104 for a description of the HMAC algorithm: http://tools.ietf.org/html/rfc2104
Authentication done by SCRAM-* defined in RFC 5802 & 7677: https://tools.ietf.org/html/rfc5802 https:...
override async Task< bool?> ResponseRequest(string Data, ISaslServerSide Connection, ISaslPersistenceLayer PersistenceLayer)
Response request has been made.
static async Task< string > GetSaltBase64(string Key)
Gets a salt value, given a key.
ScramAuthenticationMechanism()
Authentication done by SCRAM-* defined in RFC 5802 & 7677: https://tools.ietf.org/html/rfc5802 https:...
override Task< bool?> AuthenticationRequest(string Data, ISaslServerSide Connection, ISaslPersistenceLayer PersistenceLayer)
Authentication request has been made.
override async Task Initialize()
Performs intitialization of the mechanism. Can be used to set static properties that will be used thr...
override async Task< bool?> Authenticate(string UserName, string Password, ISaslClientSide Connection)
Authenticates the user using the provided credentials.
override bool Allowed(SslStream SslStream)
Checks if a mechanism is allowed during the current conditions.
Static class managing persistent settings.
static async Task< string > GetAsync(string Key, string DefaultValue)
Gets a string-valued setting.
static async Task< bool > SetAsync(string Key, string Value)
Sets a string-valued setting.
Class that monitors login events, and help applications determine malicious intent....
Definition: LoginAuditor.cs:26
static async void Success(string Message, string UserName, string RemoteEndpoint, string Protocol, params KeyValuePair< string, object >[] Tags)
Handles a successful login attempt.
static async void Fail(string Message, string UserName, string RemoteEndpoint, string Protocol, params KeyValuePair< string, object >[] Tags)
Handles a failed login attempt.
Interface for SMTP user accounts.
Definition: IAccount.cs:11
bool Enabled
If the account is enabled.
Definition: IAccount.cs:40
Interface for client-side client connections.
Task< string > Initiate(IAuthenticationMechanism Mechanism, string Parameters)
Initiates authentication
Interface for XMPP Server persistence layers. The persistence layer should implement caching.
Interface for server-side client connections.
object Tag
Object tagged to the connection.