/// <summary> /// Processes PingRequest message from client. /// <para>Simply copies the payload to a new ping response message.</para> /// </summary> /// <param name="Client">Client that sent the request.</param> /// <param name="RequestMessage">Full request message.</param> /// <returns>Response message to be sent to the client.</returns> public ProxProtocolMessage ProcessMessagePingRequest(IncomingClient Client, ProxProtocolMessage RequestMessage) { log.Trace("()"); ProxMessageBuilder messageBuilder = Client.MessageBuilder; PingRequest pingRequest = RequestMessage.Request.SingleRequest.Ping; ProxProtocolMessage res = messageBuilder.CreatePingResponse(RequestMessage, pingRequest.Payload.ToByteArray(), ProtocolHelper.GetUnixTimestampMs()); log.Trace("(-):*.Response.Status={0}", res.Response.Status); return(res); }
/// <summary> /// Processes VerifyIdentityRequest message from client. /// <para>It verifies the identity's public key against the signature of the challenge provided during the start of the conversation. /// If everything is OK, the status of the conversation is upgraded to Verified.</para> /// </summary> /// <param name="Client">Client that sent the request.</param> /// <param name="RequestMessage">Full request message.</param> /// <returns>Response message to be sent to the client.</returns> public ProxProtocolMessage ProcessMessageVerifyIdentityRequest(IncomingClient Client, ProxProtocolMessage RequestMessage) { log.Trace("()"); ProxProtocolMessage res = null; if (!CheckSessionConditions(Client, RequestMessage, ServerRole.Client | ServerRole.Neighbor, ClientConversationStatus.ConversationStarted, out res)) { log.Trace("(-):*.Response.Status={0}", res.Response.Status); return(res); } ProxMessageBuilder messageBuilder = Client.MessageBuilder; VerifyIdentityRequest verifyIdentityRequest = RequestMessage.Request.ConversationRequest.VerifyIdentity; byte[] challenge = verifyIdentityRequest.Challenge.ToByteArray(); if (StructuralEqualityComparer <byte[]> .Default.Equals(challenge, Client.AuthenticationChallenge)) { if (messageBuilder.VerifySignedConversationRequestBody(RequestMessage, verifyIdentityRequest, Client.PublicKey)) { log.Debug("Identity '{0}' successfully verified its public key.", Client.IdentityId.ToHex()); Client.ConversationStatus = ClientConversationStatus.Verified; res = messageBuilder.CreateVerifyIdentityResponse(RequestMessage); } else { log.Warn("Client's challenge signature is invalid."); res = messageBuilder.CreateErrorInvalidSignatureResponse(RequestMessage); } } else { log.Warn("Challenge provided in the request does not match the challenge created by the proximity server."); res = messageBuilder.CreateErrorInvalidValueResponse(RequestMessage, "challenge"); } log.Trace("(-):*.Response.Status={0}", res.Response.Status); return(res); }
/// <summary> /// Processes ListRolesRequest message from client. /// <para>Obtains a list of role servers and returns it in the response.</para> /// </summary> /// <param name="Client">Client that sent the request.</param> /// <param name="RequestMessage">Full request message.</param> /// <returns>Response message to be sent to the client.</returns> public ProxProtocolMessage ProcessMessageListRolesRequest(IncomingClient Client, ProxProtocolMessage RequestMessage) { log.Trace("()"); ProxProtocolMessage res = null; if (!CheckSessionConditions(Client, RequestMessage, ServerRole.Primary, null, out res)) { log.Trace("(-):*.Response.Status={0}", res.Response.Status); return(res); } ProxMessageBuilder messageBuilder = Client.MessageBuilder; ListRolesRequest listRolesRequest = RequestMessage.Request.SingleRequest.ListRoles; List <Iop.Proximityserver.ServerRole> roles = GetRolesFromServerComponent(); res = messageBuilder.CreateListRolesResponse(RequestMessage, roles); log.Trace("(-):*.Response.Status={0}", res.Response.Status); return(res); }
/// <summary> /// Processing of a message received from a client. /// </summary> /// <param name="Client">TCP client who send the message.</param> /// <param name="IncomingMessage">Full ProtoBuf message to be processed.</param> /// <returns>true if the conversation with the client should continue, false if a protocol violation error occurred and the client should be disconnected.</returns> public async Task <bool> ProcessMessageAsync(ClientBase Client, IProtocolMessage IncomingMessage) { IncomingClient client = (IncomingClient)Client; ProxProtocolMessage incomingMessage = (ProxProtocolMessage)IncomingMessage; ProxMessageBuilder messageBuilder = client.MessageBuilder; bool res = false; log.Debug("()"); try { // Update time until this client's connection is considered inactive. client.NextKeepAliveTime = DateTime.UtcNow.AddMilliseconds(client.KeepAliveIntervalMs); log.Trace("Client ID {0} NextKeepAliveTime updated to {1}.", client.Id.ToHex(), client.NextKeepAliveTime.ToString("yyyy-MM-dd HH:mm:ss")); log.Trace("Received message type is {0}, message ID is {1}.", incomingMessage.MessageTypeCase, incomingMessage.Id); switch (incomingMessage.MessageTypeCase) { case Message.MessageTypeOneofCase.Request: { ProxProtocolMessage responseMessage = messageBuilder.CreateErrorProtocolViolationResponse(incomingMessage); Request request = incomingMessage.Request; log.Trace("Request conversation type is {0}.", request.ConversationTypeCase); switch (request.ConversationTypeCase) { case Request.ConversationTypeOneofCase.SingleRequest: { SingleRequest singleRequest = request.SingleRequest; SemVer version = new SemVer(singleRequest.Version); log.Trace("Single request type is {0}, version is {1}.", singleRequest.RequestTypeCase, version); if (!version.IsValid()) { responseMessage.Response.Details = "version"; break; } switch (singleRequest.RequestTypeCase) { case SingleRequest.RequestTypeOneofCase.Ping: responseMessage = ProcessMessagePingRequest(client, incomingMessage); break; case SingleRequest.RequestTypeOneofCase.ListRoles: responseMessage = ProcessMessageListRolesRequest(client, incomingMessage); break; default: log.Warn("Invalid request type '{0}'.", singleRequest.RequestTypeCase); break; } break; } case Request.ConversationTypeOneofCase.ConversationRequest: { ConversationRequest conversationRequest = request.ConversationRequest; log.Trace("Conversation request type is {0}.", conversationRequest.RequestTypeCase); if (conversationRequest.Signature.Length > 0) { log.Trace("Conversation signature is '{0}'.", conversationRequest.Signature.ToByteArray().ToHex()); } else { log.Trace("No signature provided."); } switch (conversationRequest.RequestTypeCase) { case ConversationRequest.RequestTypeOneofCase.Start: responseMessage = ProcessMessageStartConversationRequest(client, incomingMessage); break; case ConversationRequest.RequestTypeOneofCase.VerifyIdentity: responseMessage = ProcessMessageVerifyIdentityRequest(client, incomingMessage); break; default: log.Warn("Invalid request type '{0}'.", conversationRequest.RequestTypeCase); // Connection will be closed in ReceiveMessageLoop. break; } break; } default: log.Error("Unknown conversation type '{0}'.", request.ConversationTypeCase); // Connection will be closed in ReceiveMessageLoop. break; } if (responseMessage != null) { // Send response to client. res = await client.SendMessageAsync(responseMessage); if (res) { // If the message was sent successfully to the target, we close the connection in case it was a protocol violation error response. if (responseMessage.MessageTypeCase == Message.MessageTypeOneofCase.Response) { res = responseMessage.Response.Status != Status.ErrorProtocolViolation; } } } else { // If there is no response to send immediately to the client, // we want to keep the connection open. res = true; } break; } case Message.MessageTypeOneofCase.Response: { Response response = incomingMessage.Response; log.Trace("Response status is {0}, details are '{1}', conversation type is {2}.", response.Status, response.Details, response.ConversationTypeCase); // Find associated request. If it does not exist, disconnect the client as it // send a response without receiving a request. This is protocol violation, // but as this is a reponse, we have no how to inform the client about it, // so we just disconnect it. UnfinishedRequest unfinishedRequest = client.GetAndRemoveUnfinishedRequest(incomingMessage.Id); if ((unfinishedRequest != null) && (unfinishedRequest.RequestMessage != null)) { ProxProtocolMessage requestMessage = (ProxProtocolMessage)unfinishedRequest.RequestMessage; Request request = requestMessage.Request; // We now check whether the response message type corresponds with the request type. // This is only valid if the status is Ok. If the message types do not match, we disconnect // for the protocol violation again. bool typeMatch = false; bool isErrorResponse = response.Status != Status.Ok; if (!isErrorResponse) { if (response.ConversationTypeCase == Response.ConversationTypeOneofCase.SingleResponse) { typeMatch = (request.ConversationTypeCase == Request.ConversationTypeOneofCase.SingleRequest) && ((int)response.SingleResponse.ResponseTypeCase == (int)request.SingleRequest.RequestTypeCase); } else { typeMatch = (request.ConversationTypeCase == Request.ConversationTypeOneofCase.ConversationRequest) && ((int)response.ConversationResponse.ResponseTypeCase == (int)request.ConversationRequest.RequestTypeCase); } } else { typeMatch = true; } if (typeMatch) { // Now we know the types match, so we can rely on request type even if response is just an error. switch (request.ConversationTypeCase) { case Request.ConversationTypeOneofCase.SingleRequest: { SingleRequest singleRequest = request.SingleRequest; switch (singleRequest.RequestTypeCase) { default: log.Warn("Invalid conversation type '{0}' of the corresponding request.", request.ConversationTypeCase); // Connection will be closed in ReceiveMessageLoop. break; } break; } case Request.ConversationTypeOneofCase.ConversationRequest: { ConversationRequest conversationRequest = request.ConversationRequest; switch (conversationRequest.RequestTypeCase) { default: log.Warn("Invalid type '{0}' of the corresponding request.", conversationRequest.RequestTypeCase); // Connection will be closed in ReceiveMessageLoop. break; } break; } default: log.Error("Unknown conversation type '{0}' of the corresponding request.", request.ConversationTypeCase); // Connection will be closed in ReceiveMessageLoop. break; } } else { log.Warn("Message type of the response ID {0} does not match the message type of the request ID {1}, the connection will be closed.", incomingMessage.Id, unfinishedRequest.RequestMessage.Id); // Connection will be closed in ReceiveMessageLoop. } } else { log.Warn("No unfinished request found for incoming response ID {0}, the connection will be closed.", incomingMessage.Id); // Connection will be closed in ReceiveMessageLoop. } break; } default: log.Error("Unknown message type '{0}', connection to the client will be closed.", incomingMessage.MessageTypeCase); await SendProtocolViolation(client); // Connection will be closed in ReceiveMessageLoop. break; } } catch (Exception e) { log.Error("Exception occurred, connection to the client will be closed: {0}", e.ToString()); await SendProtocolViolation(client); // Connection will be closed in ReceiveMessageLoop. } if (res && client.ForceDisconnect) { log.Debug("Connection to the client will be forcefully closed."); res = false; } log.Debug("(-):{0}", res); return(res); }
/// <summary> /// Processes StartConversationRequest message from client. /// <para>Initiates a conversation with the client provided that there is a common version of the protocol supported by both sides.</para> /// </summary> /// <param name="Client">Client that sent the request.</param> /// <param name="RequestMessage">Full request message.</param> /// <returns>Response message to be sent to the client.</returns> public ProxProtocolMessage ProcessMessageStartConversationRequest(IncomingClient Client, ProxProtocolMessage RequestMessage) { log.Trace("()"); ProxProtocolMessage res = null; if (!CheckSessionConditions(Client, RequestMessage, null, ClientConversationStatus.NoConversation, out res)) { log.Trace("(-):*.Response.Status={0}", res.Response.Status); return(res); } ProxMessageBuilder messageBuilder = Client.MessageBuilder; StartConversationRequest startConversationRequest = RequestMessage.Request.ConversationRequest.Start; byte[] clientChallenge = startConversationRequest.ClientChallenge.ToByteArray(); byte[] pubKey = startConversationRequest.PublicKey.ToByteArray(); if (clientChallenge.Length == ProxMessageBuilder.ChallengeDataSize) { if ((0 < pubKey.Length) && (pubKey.Length <= ProtocolHelper.MaxPublicKeyLengthBytes)) { SemVer version; if (GetCommonSupportedVersion(startConversationRequest.SupportedVersions, out version)) { Client.PublicKey = pubKey; Client.IdentityId = Crypto.Sha256(Client.PublicKey); if (clientList.AddNetworkPeerWithIdentity(Client)) { Client.MessageBuilder.SetProtocolVersion(version); byte[] challenge = new byte[ProxMessageBuilder.ChallengeDataSize]; Crypto.Rng.GetBytes(challenge); Client.AuthenticationChallenge = challenge; Client.ConversationStatus = ClientConversationStatus.ConversationStarted; log.Debug("Client {0} conversation status updated to {1}, selected version is '{2}', client public key set to '{3}', client identity ID set to '{4}', challenge set to '{5}'.", Client.RemoteEndPoint, Client.ConversationStatus, version, Client.PublicKey.ToHex(), Client.IdentityId.ToHex(), Client.AuthenticationChallenge.ToHex()); res = messageBuilder.CreateStartConversationResponse(RequestMessage, version, Config.Configuration.Keys.PublicKey, Client.AuthenticationChallenge, clientChallenge); } else { res = messageBuilder.CreateErrorInternalResponse(RequestMessage); } } else { log.Warn("Client and server are incompatible in protocol versions."); res = messageBuilder.CreateErrorUnsupportedResponse(RequestMessage); } } else { log.Warn("Client send public key of invalid length of {0} bytes.", pubKey.Length); res = messageBuilder.CreateErrorInvalidValueResponse(RequestMessage, "publicKey"); } } else { log.Warn("Client send clientChallenge, which is {0} bytes long, but it should be {1} bytes long.", clientChallenge.Length, ProxMessageBuilder.ChallengeDataSize); res = messageBuilder.CreateErrorInvalidValueResponse(RequestMessage, "clientChallenge"); } log.Trace("(-):*.Response.Status={0}", res.Response.Status); return(res); }
/// <summary> /// Verifies that client's request was not sent against the protocol rules - i.e. that the role server /// that received the message is serving the role the message was designed for and that the conversation /// status with the clients matches the required status for the particular message. /// </summary> /// <param name="Client">Client that sent the request.</param> /// <param name="RequestMessage">Full request message.</param> /// <param name="RequiredRole">Server role required for the message, or null if all roles servers can handle this message.</param> /// <param name="RequiredConversationStatus">Required conversation status for the message, or null for single messages.</param> /// <param name="ResponseMessage">If the verification fails, this is filled with error response to be sent to the client.</param> /// <returns>true if the function succeeds (i.e. required conditions are met and the message can be processed), false otherwise.</returns> public bool CheckSessionConditions(IncomingClient Client, ProxProtocolMessage RequestMessage, ServerRole?RequiredRole, ClientConversationStatus?RequiredConversationStatus, out ProxProtocolMessage ResponseMessage) { log.Trace("(RequiredRole:{0},RequiredConversationStatus:{1})", RequiredRole != null ? RequiredRole.ToString() : "null", RequiredConversationStatus != null ? RequiredConversationStatus.Value.ToString() : "null"); bool res = false; ResponseMessage = null; string requestName = RequestMessage.Request.ConversationTypeCase == Request.ConversationTypeOneofCase.SingleRequest ? "single request " + RequestMessage.Request.SingleRequest.RequestTypeCase.ToString() : "conversation request " + RequestMessage.Request.ConversationRequest.RequestTypeCase.ToString(); // RequiredRole contains one or more roles and the current server has to have at least one of them. if ((RequiredRole == null) || ((roleServer.Roles & (uint)RequiredRole.Value) != 0)) { if (RequiredConversationStatus == null) { res = true; } else { switch (RequiredConversationStatus.Value) { case ClientConversationStatus.NoConversation: case ClientConversationStatus.ConversationStarted: res = Client.ConversationStatus == RequiredConversationStatus.Value; if (!res) { log.Warn("Client sent {0} but the conversation status is {1}.", requestName, Client.ConversationStatus); ResponseMessage = Client.MessageBuilder.CreateErrorBadConversationStatusResponse(RequestMessage); } break; case ClientConversationStatus.Verified: res = Client.ConversationStatus == RequiredConversationStatus.Value; if (!res) { log.Warn("Client sent {0} but the conversation status is {1}.", requestName, Client.ConversationStatus); ResponseMessage = Client.MessageBuilder.CreateErrorUnauthorizedResponse(RequestMessage); } break; case ClientConversationStatus.ConversationAny: res = (Client.ConversationStatus == ClientConversationStatus.ConversationStarted) || (Client.ConversationStatus == ClientConversationStatus.Verified); if (!res) { log.Warn("Client sent {0} but the conversation status is {1}.", requestName, Client.ConversationStatus); ResponseMessage = Client.MessageBuilder.CreateErrorBadConversationStatusResponse(RequestMessage); } break; default: log.Error("Unknown conversation status '{0}'.", Client.ConversationStatus); ResponseMessage = Client.MessageBuilder.CreateErrorInternalResponse(RequestMessage); break; } } } else { log.Warn("Received {0} on server without {1} role(s) (server roles are {2}).", requestName, RequiredRole.Value, roleServer.Roles); ResponseMessage = Client.MessageBuilder.CreateErrorBadRoleResponse(RequestMessage); } log.Trace("(-):{0}", res); return(res); }
/// <summary> /// Checks whether the update activity request is valid. /// <para>This function does not verify whether the activity exists. /// It also does not verify whether a closer proximity server exists to which the activity should be migrated. /// It also does not verify whether the changed activity's start time will be before expiration time.</para> /// </summary> /// <param name="UpdateActivityRequest">Update activity request part of the client's request message.</param> /// <param name="Client">Client that sent the request.</param> /// <param name="RequestMessage">Full request message from client.</param> /// <param name="ErrorResponse">If the function fails, this is filled with error response message that is ready to be sent to the client.</param> /// <returns>true if the update activity request can be applied, false otherwise.</returns> public static bool ValidateUpdateActivityRequest(UpdateActivityRequest UpdateActivityRequest, IncomingClient Client, ProxProtocolMessage RequestMessage, out ProxProtocolMessage ErrorResponse) { log.Trace("()"); bool res = false; ErrorResponse = null; string details = null; if (UpdateActivityRequest == null) { UpdateActivityRequest = new UpdateActivityRequest(); } if (UpdateActivityRequest.Activity == null) { UpdateActivityRequest.Activity = new ActivityInformation(); } SignedActivityInformation signedActivity = new SignedActivityInformation() { Activity = UpdateActivityRequest.Activity, Signature = RequestMessage.Request.ConversationRequest.Signature }; ProxMessageBuilder messageBuilder = Client.MessageBuilder; if (ValidateSignedActivityInformation(signedActivity, Client.PublicKey, messageBuilder, RequestMessage, "", false, out ErrorResponse)) { if (details == null) { int index = 0; foreach (ByteString serverId in UpdateActivityRequest.IgnoreServerIds) { if (serverId.Length != ProtocolHelper.NetworkIdentifierLength) { log.Debug("Ignored server ID #{0} is not a valid network ID as its length is {1} bytes.", index, serverId.Length); details = "ignoreServerIds"; break; } index++; } } if (details == null) { res = true; } else { ErrorResponse = messageBuilder.CreateErrorInvalidValueResponse(RequestMessage, details); } } log.Trace("(-):{0}", res); return(res); }
/// <summary> /// Checks whether the create activity request is valid. /// <para>This function does not verify the uniqueness of the activity identifier. /// It also does not verify whether a closer proximity server exists.</para> /// </summary> /// <param name="CreateActivityRequest">Create activity request part of the client's request message.</param> /// <param name="Client">Client that sent the request.</param> /// <param name="RequestMessage">Full request message from client.</param> /// <param name="ErrorResponse">If the function fails, this is filled with error response message that is ready to be sent to the client.</param> /// <returns>true if the create activity request can be applied, false otherwise.</returns> public static bool ValidateCreateActivityRequest(CreateActivityRequest CreateActivityRequest, IncomingClient Client, ProxProtocolMessage RequestMessage, out ProxProtocolMessage ErrorResponse) { log.Trace("()"); bool res = false; ErrorResponse = null; string details = null; if (CreateActivityRequest == null) { CreateActivityRequest = new CreateActivityRequest(); } if (CreateActivityRequest.Activity == null) { CreateActivityRequest.Activity = new ActivityInformation(); } if (CreateActivityRequest.Activity.ProfileServerContact == null) { CreateActivityRequest.Activity.ProfileServerContact = new ServerContactInfo(); } SignedActivityInformation signedActivity = new SignedActivityInformation() { Activity = CreateActivityRequest.Activity, Signature = RequestMessage.Request.ConversationRequest.Signature }; if (ValidateSignedActivityInformation(signedActivity, Client.PublicKey, Client.MessageBuilder, RequestMessage, "", false, out ErrorResponse)) { byte[] ownerPubKey = CreateActivityRequest.Activity.OwnerPublicKey.ToByteArray(); if (!ByteArrayComparer.Equals(Client.PublicKey, ownerPubKey)) { log.Debug("Client's public key '{0}' does not match activity owner's public key '{1}'.", Client.PublicKey.ToHex(), ownerPubKey.ToHex()); details = "activity.ownerPublicKey"; } if (details == null) { int index = 0; foreach (ByteString serverId in CreateActivityRequest.IgnoreServerIds) { if (serverId.Length != ProtocolHelper.NetworkIdentifierLength) { log.Debug("Ignored server ID #{0} is not a valid network ID as its length is {1} bytes.", index, serverId.Length); details = "ignoreServerIds"; break; } index++; } } if (details == null) { res = true; } else { ErrorResponse = Client.MessageBuilder.CreateErrorInvalidValueResponse(RequestMessage, details); } } log.Trace("(-):{0}", res); return(res); }