2using System.Collections.Generic;
3using System.Net.Security;
4using System.Security.Cryptography;
6using System.Threading.Tasks;
19 private static readonly
byte[] clientKey = Encoding.UTF8.GetBytes(
"Client Key");
20 private static readonly
byte[] serverKey = Encoding.UTF8.GetBytes(
"Server Key");
22 private static byte[] salt =
null;
23 private static string saltBase64 =
null;
39 public override bool Allowed(SslStream SslStream)
53 byte[] Bin = Convert.FromBase64String(Data);
54 string Request = Encoding.UTF8.GetString(Bin);
55 string UserName =
null;
57 int NrIterations = 4096;
61 switch (Pair.Key.ToLower())
64 UserName = Pair.Value;
73 if (UserName is
null || Nonce is
null)
75 Connection.SaslErrorMalformedRequest();
76 LoginAuditor.
Fail(
"Login attempt using malformed request.", UserName, Connection.RemoteEndpoint, Connection.Protocol);
77 return Task.FromResult<
bool?>(
null);
80 Connection.SetUserIdentity(UserName);
82 string ServerNonce = Nonce + Convert.ToBase64String(PersistenceLayer.GetRandomNumbers(32));
84 string Challenge =
"r=" + ServerNonce +
",s=" + saltBase64 +
",i=" + NrIterations.ToString();
85 Bin = Encoding.UTF8.GetBytes(Challenge);
86 string ChallengeBase64 = Convert.ToBase64String(Bin);
88 Connection.Tag =
new object[] { UserName, Nonce, NrIterations, ServerNonce, Request, Challenge };
89 Connection.SaslChallenge(ChallengeBase64);
91 return Task.FromResult<
bool?>(
null);
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];
108 int NrIterations = (int)P[2];
109 string ServerNonce = (string)P[3];
110 string ClientRequest = (string)P[4];
111 string ServerChallenge = (string)P[5];
114 bool ServerNonceChecked =
false;
118 switch (Pair.Key.ToLower())
125 ServerNonceChecked =
true;
126 if (ServerNonce != Pair.Value)
128 await Connection.SaslErrorMalformedRequest();
129 LoginAuditor.
Fail(
"Login attempt using malformed request.", UserName, Connection.RemoteEndpoint, Connection.Protocol);
140 if (!ServerNonceChecked)
142 await Connection.SaslErrorMalformedRequest();
143 LoginAuditor.
Fail(
"Login attempt using malformed request.", UserName, Connection.RemoteEndpoint, Connection.Protocol);
147 IAccount Account = await PersistenceLayer.GetAccount(UserName);
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();
158 LoginAuditor.
Fail(
"Login attempt using disabled account.", UserName, Connection.RemoteEndpoint, Connection.Protocol);
159 await Connection.SaslErrorAccountDisabled();
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);
168 sb =
new StringBuilder();
172 i = ClientRequest.IndexOf(
"n=");
174 sb.Append(ClientRequest);
176 sb.Append(ClientRequest.Substring(i));
179 sb.Append(ServerChallenge);
182 i = Request.IndexOf(
",p=");
186 sb.Append(Request.Substring(0, i));
188 byte[] AuthenticationMessage = Encoding.UTF8.GetBytes(sb.ToString());
189 byte[] ClientSignature = this.
HMAC(StoredKey, AuthenticationMessage);
190 byte[] ClientProof =
XOR(ClientKey, ClientSignature);
192 byte[] ServerKey = this.
HMAC(SaltedPassword, serverKey);
193 byte[] ServerSignature = this.
HMAC(ServerKey, AuthenticationMessage);
195 string ClientProofStr = Convert.ToBase64String(ClientProof);
196 if (Proof == ClientProofStr)
198 await Connection.SetAccount(Account);
200 string Response =
"v=" + Convert.ToBase64String(ServerSignature);
201 Response = Convert.ToBase64String(Encoding.UTF8.GetBytes(Response));
203 Connection.ResetState(
true);
204 await Connection.SaslSuccess(Response);
205 LoginAuditor.
Success(
"Login successful.", UserName, Connection.RemoteEndpoint, Connection.Protocol);
209 await Connection.SaslErrorNotAuthorized();
210 LoginAuditor.
Fail(
"Login attempt failed.", UserName, Connection.RemoteEndpoint, Connection.Protocol);
216 private byte[] Hi(
byte[] String,
byte[] Salt,
int NrIterations)
218 byte[] U1 = this.
HMAC(String,
CONCAT(Salt, One));
219 byte[] U2 = this.
HMAC(String, U1);
220 byte[] Response =
XOR(U1, U2);
222 while (NrIterations > 2)
225 U2 = this.
HMAC(String, U1);
226 Response =
XOR(Response, U2);
233 private static readonly
byte[] One =
new byte[] { 0, 0, 0, 1 };
243 salt = Convert.FromBase64String(saltBase64);
254 if (
string.IsNullOrEmpty(Result))
256 byte[] Bin =
new byte[32];
258 using (RandomNumberGenerator Rnd = RandomNumberGenerator.Create())
263 Result = Convert.ToBase64String(Bin);
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;
287 int NrIterations = 0;
292 switch (Pair.Key.ToLower())
295 ServerNonce = Pair.Value;
299 SaltString = Pair.Value;
300 Salt = Convert.FromBase64String(SaltString);
304 NrIterations =
int.Parse(Pair.Value);
309 if (
string.IsNullOrEmpty(ServerNonce) || !ServerNonce.StartsWith(Nonce) || Salt is
null || NrIterations <= 0)
312 byte[] SaltedPassword = this.Hi(Encoding.UTF8.GetBytes(Password.Normalize()), Salt, NrIterations);
314 byte[] ClientKey = this.
HMAC(SaltedPassword, Encoding.UTF8.GetBytes(
"Client Key"));
315 byte[] StoredKey = this.
H(ClientKey);
319 sb =
new StringBuilder();
325 sb.Append(ChallengeString);
326 sb.Append(
",c=biws,r=");
327 sb.Append(ServerNonce);
329 byte[] AuthenticationMessage = Encoding.UTF8.GetBytes(sb.ToString());
330 byte[] ClientSignature = this.
HMAC(StoredKey, AuthenticationMessage);
331 byte[] ClientProof =
XOR(ClientKey, ClientSignature);
333 byte[] ServerKey = this.
HMAC(SaltedPassword, Encoding.UTF8.GetBytes(
"Server Key"));
334 byte[] ServerSignatureBinary = this.
HMAC(ServerKey, AuthenticationMessage);
336 string ServerSignature = Convert.ToBase64String(ServerSignatureBinary);
338 sb =
new StringBuilder();
339 sb.Append(
"c=biws,r=");
340 sb.Append(ServerNonce);
342 sb.Append(Convert.ToBase64String(ClientProof));
344 string Success = await Connection.FinalResponse(
this, Convert.ToBase64String(Encoding.UTF8.GetBytes(sb.ToString())));
345 if (
string.IsNullOrEmpty(Success))
348 byte[] ResponseBinary = Convert.FromBase64String(Success);
349 string ResponseString = Encoding.UTF8.GetString(ResponseBinary);
353 if (
string.Compare(Pair.Key,
"v",
true) == 0)
354 return (Pair.Value == ServerSignature);
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....
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.
bool Enabled
If the account is enabled.
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.