public async Task <AccountEntity> Create(string password, AccountSecurityLevel securityLevel) { password = password?.ToUpperInvariant() ?? throw new ArgumentNullException(nameof(password)); if (IsExists) { throw new AccountExistsException($"account {State.Name} already exists; cannot create account"); } var random = GrainFactory.GetGrain <IRandomService>(0); using (var sha1 = new Digester(SHA1.Create())) { var salt = await random.GetRandomBigInteger(32); var identityHash = sha1.CalculateDigest($"{this.GetPrimaryKeyString()}:{password}"); var secret = BigIntegers.FromUnsignedByteArray(sha1.CalculateDigest(new byte[][] { salt.ToByteArray(32), identityHash })); var verifier = BigInteger.ModPow(G, secret, N); State.Salt = salt; State.Verifier = verifier; State.Name = this.GetPrimaryKeyString(); State.Enabled = true; State.SecurityLevel = securityLevel; } await WriteStateAsync(); // orleans will automatically make a safe copy of this return(State); }
public override void ExecuteCommand(AuthServer server, string[] arguments) { if (arguments.Length < 3) { log.ErrorFormat("{0} requires 2 arguments", Name); } else { string identity = arguments[1].ToUpperInvariant(); string password = arguments[2].ToUpperInvariant(); log.InfoFormat("creating new account '{0}'", identity); SecureRemotePasswordArgs srp = new SecureRemotePasswordArgs(); using (Digester sha1 = new Digester(new SHA1CryptoServiceProvider())) { BigInteger s = server.SecureRandom.GetRandomBigInteger(32); BigInteger x = BigIntegers.FromUnsignedByteArray(sha1.CalculateDigest(new byte[][] { Arrays.Left(s.ToByteArray(), 32), sha1.CalculateDigest(identity + ':' + password) })); BigInteger v = BigInteger.ModPow(srp.g, x, srp.N); if (server.AuthDB.ExecuteQuery("select 1 from account where name = ?", identity, reader => { if (reader.Read()) { log.ErrorFormat("cannot create account '{0}': an account with this name already exists", identity); return(false); } return(true); })) { int result = server.AuthDB.ExecuteNonQuery("insert into account (name, salt, verifier) values (?, ?, ?)", new object[] { identity, string.Format("{0:X}", s), string.Format("{0:X}", v) }); if (result == 1) { log.InfoFormat("successfully created account '{0}'", identity); } else { log.ErrorFormat("failed to create account '{0}'. expected 1 update, got {1}", identity, result); } } } } }
public async Task <BigInteger> Authenticate(AuthSessionRequest authRequest) { if (String.IsNullOrEmpty(authRequest.Identity)) { throw new ArgumentNullException(nameof(authRequest.Identity)); } if (seed == 0) { await Send(new AuthSessionResponse() { Response = AuthResponseCode.Failed }); throw new AuthenticationFailedException("cannot authenticate with a server seed of 0"); } var account = GrainFactory.GetGrain <IAccount>(authRequest.Identity); if (await account.Exists()) { try { var sessionKey = await account.GetSessionKey(); using (var sha1 = new Digester(SHA1.Create())) { var serverDigest = BigIntegers.FromUnsignedByteArray( sha1.CalculateDigest(new byte[][] { Encoding.UTF8.GetBytes(authRequest.Identity), new byte[4], BitConverter.GetBytes(authRequest.ClientSeed), BitConverter.GetBytes(seed), sessionKey.ToByteArray(40), }) ); if (serverDigest == authRequest.ClientDigest) { GetLogger().Info($"{authRequest.Identity} successfully authenticated to {ShardName} {nameof(ShardSession)} {this.GetPrimaryKey()}"); // we can't just Send the Success response here, since the client expects the packet cipher to be initialized at this point AuthenticatedIdentity = authRequest.Identity; return(sessionKey); } else { await Send(new AuthSessionResponse() { Response = AuthResponseCode.Failed }); throw new AuthenticationFailedException($"account {authRequest.Identity} failed authentication proof"); } } } catch (AccountDoesNotExistException) { await Send(new AuthSessionResponse() { Response = AuthResponseCode.UnknownAccount }); throw new AuthenticationFailedException($"account {authRequest.Identity} does not exist"); } catch (AccountStateException) { GetLogger().Warn($"received {nameof(AuthSessionRequest)} with unauthenticated identity {authRequest.Identity}"); await Send(new AuthSessionResponse() { Response = AuthResponseCode.Failed }); throw new AuthenticationFailedException($"account {authRequest.Identity} is not authenticated"); } } else { await Send(new AuthSessionResponse() { Response = AuthResponseCode.UnknownAccount }); throw new AuthenticationFailedException($"account {authRequest.Identity} does not exist"); } }
public Task <SrpResult> SrpHandshake(BigInteger a, BigInteger m1) { if (!IsExists) { throw new AccountDoesNotExistException($"account {this.GetPrimaryKeyString()} does not exist"); } if (AuthState != AccountAuthState.ParametersGenerated) { throw new AccountStateException("cannot handshake without first generating SRP initial parameters"); } if (a.IsZero) { throw new SrpException("A cannot be zero"); } if (BPublic.IsZero) { throw new SrpException("B cannot be zero"); } using (var sha1 = new Digester(SHA1.Create())) { var u = BigIntegers.FromUnsignedByteArray(sha1.CalculateDigest(a, BPublic)); var s = BigInteger.ModPow(a * BigInteger.ModPow(State.Verifier, u, N), BPrivate, N); var t = s.ToByteArray(32); var t1 = new byte[16]; var vK = new byte[40]; // modified srp - uses a session key (vK, K) created by interleaving two hashes (tK) created from half (t1) of the shared secret (s) for (int i = 0; i < 16; ++i) { t1[i] = t[i * 2]; } var tK = sha1.CalculateDigest(t1); for (int i = 0; i < 20; ++i) { vK[i * 2] = tK[i]; } for (int i = 0; i < 16; ++i) { t1[i] = t[i * 2 + 1]; } tK = sha1.CalculateDigest(t1); for (int i = 0; i < 20; ++i) { vK[i * 2 + 1] = tK[i]; } SessionKey = BigIntegers.FromUnsignedByteArray(vK); var nghash = sha1.CalculateDigest(N.ToByteArray(32)); var ghash = sha1.CalculateDigest(new[] { G }); for (int i = 0; i < sha1.DigestSize; ++i) { nghash[i] ^= ghash[i]; } var serverM1Bytes = sha1.CalculateDigest(new byte[][] { nghash, sha1.CalculateDigest(State.Name), State.Salt.ToByteArray(32), a.ToByteArray(32), BPublic.ToByteArray(32), vK, }); // if client M1 matches server M1, then client and server have agreed on shared SessionKey, but only server knows that right now var serverM1 = BigIntegers.FromUnsignedByteArray(serverM1Bytes); if (serverM1 == m1) { // need to send M2 so client knows session key is shared also var m2Bytes = sha1.CalculateDigest(new byte[][] { a.ToByteArray(32), serverM1Bytes, SessionKey.ToByteArray(40), }); AuthState = AccountAuthState.Authenticated; var m2 = BigIntegers.FromUnsignedByteArray(m2Bytes); return(Task.FromResult(new SrpResult(true, m2))); } else { Deauthenticate().Wait(); return(Task.FromResult(new SrpResult(false, BigInteger.Zero))); } } }
public unsafe override void ExecuteCommand(AuthSession session, AuthRequest request, LogonProof header) { log.DebugFormat("handling {0} opcode. securityflags = {1}, keycount = {2}", header.Opcode, header.SecurityFlags, header.KeyCount); using (Digester sha1 = new Digester(new SHA1CryptoServiceProvider())) { // client sends public ephemeral value A session.SRP.A = BigIntegers.FromUnsignedByteArray(FixedBuffers.ToArray(header.A, 32)); log.DebugFormat("server N = {0:X}", session.SRP.N); log.DebugFormat("client A = {0:X}", session.SRP.A); log.DebugFormat("server B = {0:X}", session.SRP.B); log.DebugFormat("server s = {0:X}", session.SRP.s); log.DebugFormat("server v = {0:X}", session.SRP.v); // srp is required to abort if A or B is 0 if (session.SRP.A.IsZero) { throw new ArgumentException("security abort: SRP6.A = 0"); } session.SRP.u = BigIntegers.FromUnsignedByteArray(sha1.CalculateDigest(session.SRP.A, session.SRP.B)); session.SRP.S = BigInteger.ModPow(session.SRP.A * BigInteger.ModPow(session.SRP.v, session.SRP.u, session.SRP.N), session.SRP.b, session.SRP.N); log.DebugFormat("server u = {0:X}", session.SRP.u); log.DebugFormat("server S = {0:X}", session.SRP.S); // BigIntever.ToByteArrays are chopped/padded on the right (taking the 'Left' of the array) because of little endian. we're either killing the sign byte or padding with leading 0's in case of small number byte[] t = Arrays.Left(session.SRP.S.ToByteArray(), 32); byte[] t1 = new byte[16]; byte[] vK = new byte[40]; // modified srp - uses a session key (vK, K) created by interleaving two hashes (tK) created from half (t1) of the shared secret (S) for (int i = 0; i < 16; ++i) { t1[i] = t[i * 2]; } byte[] tK = sha1.CalculateDigest(t1); for (int i = 0; i < 20; ++i) { vK[i * 2] = tK[i]; } for (int i = 0; i < 16; ++i) { t1[i] = t[i * 2 + 1]; } tK = sha1.CalculateDigest(t1); for (int i = 0; i < 20; ++i) { vK[i * 2 + 1] = tK[i]; } session.SRP.K = BigIntegers.FromUnsignedByteArray(vK); log.DebugFormat("server K = {0:X}", session.SRP.K); byte[] Nghash = sha1.CalculateDigest(Arrays.Left(session.SRP.N.ToByteArray(), 32)); byte[] ghash = sha1.CalculateDigest(Arrays.Left(session.SRP.g.ToByteArray(), 1)); for (int i = 0; i < sha1.DigestSize; ++i) { Nghash[i] ^= ghash[i]; } // calculating M1 byte[] M1S = sha1.CalculateDigest(new byte[][] { Nghash, sha1.CalculateDigest(session.SRP.I), Arrays.Left(session.SRP.s.ToByteArray(), 32), Arrays.Left(session.SRP.A.ToByteArray(), 32), Arrays.Left(session.SRP.B.ToByteArray(), 32), vK, }); byte[] M1C = FixedBuffers.ToArray(header.M1, 20); BigInteger M1s = BigIntegers.FromUnsignedByteArray(M1S); BigInteger M1c = BigIntegers.FromUnsignedByteArray(M1C); log.DebugFormat("client M1 = {0:X}", M1c); log.DebugFormat("server M1 = {0:X}", M1s); // if client M1 matches server M1, then client and server have agreed on shared session key (vK, K), but only server knows that right now if (M1s == M1c) { session.Status = AuthStatus.Authenticated; // need to send M2 so client knows session key is shared also byte[] M2S = sha1.CalculateDigest(new byte[][] { Arrays.Left(session.SRP.A.ToByteArray(), 32), M1C, Arrays.Left(session.SRP.K.ToByteArray(), 40), }); BigInteger M2s = BigIntegers.FromUnsignedByteArray(M2S); log.InfoFormat("authentication successful for user {0}", session.SRP.I); log.DebugFormat("server M2 = {0:X}", M2s); int result = session.Server.AuthDB.ExecuteNonQuery("update account set last_login = now(), last_ip = ?, session_key = ? where name = ?", session.RemoteEndPoint.Address.ToString(), string.Format("{0:X}", session.SRP.K), session.SRP.I); if (result != 1) { log.ErrorFormat("expected 1 result when updating account for login, but got {0}", result); } using (ByteBuffer response = new ByteBuffer()) { response.Append((byte)AuthRequestOpcode.LogonProof); response.Append((byte)AuthResponseOpcode.Success); response.Append(Arrays.Left(M2S, 20)); response.Append(0); session.Send(response.GetArraySegment()); } } else { log.InfoFormat("authentication failed for user {0}", session.SRP.I); using (ByteBuffer response = new ByteBuffer()) { response.Append((byte)AuthRequestOpcode.LogonProof); response.Append((byte)AuthResponseOpcode.FailBadCredentials); session.Send(response.GetArraySegment()); } } } }
public override void ExecuteCommand(ShardSession session, ShardRequest request, ClientPacketHeader header) { int clientBuild = BitConverter.ToInt32(request.Packet, 6); int identityLength; string identity = Strings.FromNullTerminated(request.Packet, 14, Encoding.UTF8, out identityLength); int clientSeed = BitConverter.ToInt32(request.Packet, 14 + identityLength + 1); // + 1 for null terminator byte[] clientDigest = new byte[20]; Array.Copy(request.Packet, 14 + identityLength + 1 + 4, clientDigest, 0, 20); log.DebugFormat("read {0} packet. client build = {1}, identity = {2}, client seed = {3:X}, packet size = {4}", header.Opcode, clientBuild, identity, clientSeed, request.Size); int accountId = -1; string sessionKey = null; if (!session.Server.AuthDB.ExecuteQuery("select name, session_key, id from account where name = ? and enabled = true", identity, result => { if (result.Read()) { identity = result.GetString(0); sessionKey = result.GetString(1); accountId = result.GetInt32(2); return(true); } return(false); })) { // no identity match log.DebugFormat("account {0} not found. sending {1} response", identity, AuthResponse.UnknownAccount); session.Send(ShardServerOpcode.AuthResponse, (byte)AuthResponse.UnknownAccount); return; } BigInteger sessionKeyInt = BigInteger.Parse(sessionKey, NumberStyles.AllowHexSpecifier); // verify client/server identity and session key match using (Digester sha1 = new Digester(new SHA1CryptoServiceProvider())) { byte[] serverDigest = sha1.CalculateDigest(new byte[][] { Encoding.UTF8.GetBytes(identity), new byte[4], BitConverter.GetBytes(clientSeed), BitConverter.GetBytes(session.Seed), Arrays.Left(sessionKeyInt.ToByteArray(), 40) }); log.DebugFormat("client digest = {0}", Strings.HexOf(clientDigest)); log.DebugFormat("server digest = {0}", Strings.HexOf(serverDigest)); if (Arrays.AreEqual(clientDigest, serverDigest)) { log.InfoFormat("client {0} successfully authenticated from {1}", identity, session.RemoteEndPoint.Address.ToString()); session.AccountID = accountId; session.Status = SessionStatus.Authenticated; session.InitializeCipher(sessionKeyInt); using (ByteBuffer authPacket = new ByteBuffer()) { authPacket.Append((byte)AuthResponse.Success); authPacket.Append(0); authPacket.Append((byte)0); authPacket.Append(0); session.Send(ShardServerOpcode.AuthResponse, authPacket); } using (ByteBuffer addonPacket = BuildAddonPacket(new ArraySegment <byte>(request.Packet, 14 + identityLength + 1 + 4 + 20, request.Size - (14 + identityLength + 1 + 4 + 20)))) session.Send(ShardServerOpcode.AddonInfo, addonPacket); } else { // digest mismatch log.DebugFormat("client digest did not match server digest for account {0}. sending {1} response", identity, AuthResponse.Failed); session.Send(ShardServerOpcode.AuthResponse, (byte)AuthResponse.Failed); return; } } }