private async void HandleHandshake(NetPeerData peer, NetConnection connection) { try { var incPacket = await AwaitData(connection); var msgLogin = new MsgLoginStart(); msgLogin.ReadFromBuffer(incPacket); var ip = connection.RemoteEndPoint.Address; var isLocal = IPAddress.IsLoopback(ip) && _config.GetCVar(CVars.AuthAllowLocal); var canAuth = msgLogin.CanAuth; var needPk = msgLogin.NeedPubKey; var authServer = _config.GetSecureCVar <string>("auth.server"); if (Auth == AuthMode.Required && !isLocal) { if (!canAuth) { connection.Disconnect("Connecting to this server requires authentication"); return; } } NetEncryption?encryption = null; NetUserId userId; string userName; LoginType type; var padSuccessMessage = true; if (canAuth && Auth != AuthMode.Disabled) { var verifyToken = new byte[4]; RandomNumberGenerator.Fill(verifyToken); var msgEncReq = new MsgEncryptionRequest { PublicKey = needPk ? RsaPublicKey : Array.Empty <byte>(), VerifyToken = verifyToken }; var outMsgEncReq = peer.Peer.CreateMessage(); outMsgEncReq.Write(false); outMsgEncReq.WritePadBits(); msgEncReq.WriteToBuffer(outMsgEncReq); peer.Peer.SendMessage(outMsgEncReq, connection, NetDeliveryMethod.ReliableOrdered); incPacket = await AwaitData(connection); var msgEncResponse = new MsgEncryptionResponse(); msgEncResponse.ReadFromBuffer(incPacket); byte[] verifyTokenCheck; byte[] sharedSecret; try { verifyTokenCheck = _authRsaPrivateKey !.Decrypt( msgEncResponse.VerifyToken, RSAEncryptionPadding.OaepSHA256); sharedSecret = _authRsaPrivateKey !.Decrypt( msgEncResponse.SharedSecret, RSAEncryptionPadding.OaepSHA256); } catch (CryptographicException) { // Launcher gives the client the public RSA key of the server BUT // that doesn't persist if the server restarts. // In that case, the decrypt can fail here. connection.Disconnect("Token decryption failed./nPlease reconnect to this server from the launcher."); return; } if (!verifyToken.SequenceEqual(verifyTokenCheck)) { connection.Disconnect("Verify token is invalid"); return; } encryption = new NetAESEncryption(peer.Peer, sharedSecret, 0, sharedSecret.Length); var authHashBytes = MakeAuthHash(sharedSecret, RsaPublicKey !); var authHash = Base64Helpers.ConvertToBase64Url(authHashBytes); var client = new HttpClient(); var url = $"{authServer}api/session/hasJoined?hash={authHash}&userId={msgEncResponse.UserId}"; var joinedResp = await client.GetAsync(url); joinedResp.EnsureSuccessStatusCode(); var joinedRespJson = JsonConvert.DeserializeObject <HasJoinedResponse>( await joinedResp.Content.ReadAsStringAsync()); if (!joinedRespJson.IsValid) { connection.Disconnect("Failed to validate login"); return; } userId = new NetUserId(joinedRespJson.UserData !.UserId); userName = joinedRespJson.UserData.UserName; padSuccessMessage = false; type = LoginType.LoggedIn; } else { var reqUserName = msgLogin.UserName; if (!UsernameHelpers.IsNameValid(reqUserName, out var reason)) { connection.Disconnect($"Username is invalid ({reason.ToText()})."); return; } // If auth is set to "optional" we need to avoid conflicts between real accounts and guests, // so we explicitly prefix guests. var origName = Auth == AuthMode.Disabled ? reqUserName : (isLocal ? $"localhost@{reqUserName}" : $"guest@{reqUserName}"); var name = origName; var iterations = 1; while (_assignedUsernames.ContainsKey(name)) { // This is shit but I don't care. name = $"{origName}_{++iterations}"; } userName = name; (userId, type) = await AssignUserIdAsync(name); } var endPoint = connection.RemoteEndPoint; var connect = await OnConnecting(endPoint, userId, userName, type); if (connect.IsDenied) { connection.Disconnect($"Connection denied: {connect.DenyReason}"); return; } // Well they're in. Kick a connected client with the same GUID if we have to. if (_assignedUserIds.TryGetValue(userId, out var existing)) { existing.Disconnect("Another connection has been made with your account."); // Have to wait until they're properly off the server to avoid any collisions. await AwaitDisconnectAsync(existing); } var msg = peer.Peer.CreateMessage(); var msgResp = new MsgLoginSuccess { UserId = userId.UserId, UserName = userName, Type = type }; if (padSuccessMessage) { msg.Write(true); msg.WritePadBits(); } msgResp.WriteToBuffer(msg); encryption?.Encrypt(msg); peer.Peer.SendMessage(msg, connection, NetDeliveryMethod.ReliableOrdered); Logger.InfoS("net", "Approved {ConnectionEndpoint} with username {Username} user ID {userId} into the server", connection.RemoteEndPoint, userName, userId); // Handshake complete! HandleInitialHandshakeComplete(peer, connection, userId, userName, encryption, type); } catch (ClientDisconnectedException) { Logger.InfoS("net", $"Peer {NetUtility.ToHexString(connection.RemoteUniqueIdentifier)} disconnected while handshake was in-progress."); } catch (Exception e) { connection.Disconnect("Unknown server error occured during handshake."); Logger.ErrorS("net", "Exception during handshake with peer {0}:\n{1}", NetUtility.ToHexString(connection.RemoteUniqueIdentifier), e); } }
private async void HandleHandshake(NetPeerData peer, NetConnection connection) { try { var incPacket = await AwaitData(connection); var msgLogin = new MsgLoginStart(); msgLogin.ReadFromBuffer(incPacket); var ip = connection.RemoteEndPoint.Address; var isLocal = IPAddress.IsLoopback(ip) && _config.GetCVar(CVars.AuthAllowLocal); var canAuth = msgLogin.CanAuth; var needPk = msgLogin.NeedPubKey; var authServer = _config.GetCVar(CVars.AuthServer); if (Auth == AuthMode.Required && !isLocal) { if (!canAuth) { connection.Disconnect("Connecting to this server requires authentication"); return; } } NetEncryption?encryption = null; NetUserData userData; LoginType type; var padSuccessMessage = true; if (canAuth && Auth != AuthMode.Disabled) { var verifyToken = new byte[4]; RandomNumberGenerator.Fill(verifyToken); var msgEncReq = new MsgEncryptionRequest { PublicKey = needPk ? RsaPublicKey : Array.Empty <byte>(), VerifyToken = verifyToken }; var outMsgEncReq = peer.Peer.CreateMessage(); outMsgEncReq.Write(false); outMsgEncReq.WritePadBits(); msgEncReq.WriteToBuffer(outMsgEncReq); peer.Peer.SendMessage(outMsgEncReq, connection, NetDeliveryMethod.ReliableOrdered); incPacket = await AwaitData(connection); var msgEncResponse = new MsgEncryptionResponse(); msgEncResponse.ReadFromBuffer(incPacket); byte[] verifyTokenCheck; byte[] sharedSecret; try { verifyTokenCheck = _authRsaPrivateKey !.Decrypt( msgEncResponse.VerifyToken, RSAEncryptionPadding.OaepSHA256); sharedSecret = _authRsaPrivateKey !.Decrypt( msgEncResponse.SharedSecret, RSAEncryptionPadding.OaepSHA256); } catch (CryptographicException) { // Launcher gives the client the public RSA key of the server BUT // that doesn't persist if the server restarts. // In that case, the decrypt can fail here. connection.Disconnect( "Token decryption failed.\nPlease reconnect to this server from the launcher."); return; } if (!verifyToken.SequenceEqual(verifyTokenCheck)) { connection.Disconnect("Verify token is invalid"); return; } if (msgLogin.Encrypt) { encryption = new NetAESEncryption(peer.Peer, sharedSecret, 0, sharedSecret.Length); } var authHashBytes = MakeAuthHash(sharedSecret, RsaPublicKey !); var authHash = Base64Helpers.ConvertToBase64Url(authHashBytes); var client = new HttpClient(); var url = $"{authServer}api/session/hasJoined?hash={authHash}&userId={msgEncResponse.UserId}"; var joinedRespJson = await client.GetFromJsonAsync <HasJoinedResponse>(url); if (joinedRespJson is not { IsValid : true }) { connection.Disconnect("Failed to validate login"); return; } var userId = new NetUserId(joinedRespJson.UserData !.UserId); userData = new NetUserData(userId, joinedRespJson.UserData.UserName) { PatronTier = joinedRespJson.UserData.PatronTier, HWId = msgLogin.HWId }; padSuccessMessage = false; type = LoginType.LoggedIn; }