private static async Task Server(HandshakeState handshakeState) { var buffer = new byte[Protocol.MaxMessageLength]; // Receive the first handshake message from the client. var received = await clientToServer.Receive(); handshakeState.ReadMessage(received, buffer); // Send the second handshake message to the client. var(bytesWritten, _, transport) = handshakeState.WriteMessage(null, buffer); await serverToClient.Send(Slice(buffer, bytesWritten)); // Handshake complete, switch to transport mode. using (transport) { for (;;) { // Receive the message from the client. var request = await clientToServer.Receive(); var bytesRead = transport.ReadMessage(request, buffer); // Echo the message back to the client. bytesWritten = transport.WriteMessage(Slice(buffer, bytesRead), buffer); await serverToClient.Send(Slice(buffer, bytesWritten)); } } }
private static async Task Client(HandshakeState state) { var buffer = new byte[Protocol.MaxMessageLength]; // Send the first handshake message to the server. var(bytesWritten, _, _) = state.WriteMessage(null, buffer); await clientToServer.Send(Slice(buffer, bytesWritten)); // Receive the second handshake message from the server. var received = await serverToClient.Receive(); var(_, _, transport) = state.ReadMessage(received, buffer); // Handshake complete, switch to transport mode. using (transport) { foreach (var message in messages) { Memory <byte> request = Encoding.UTF8.GetBytes(message); // Send the message to the server. bytesWritten = transport.WriteMessage(request.Span, buffer); await clientToServer.Send(Slice(buffer, bytesWritten)); // Receive the response and print it to the standard output. var response = await serverToClient.Receive(); var bytesRead = transport.ReadMessage(response, buffer); Console.WriteLine(Encoding.UTF8.GetString(Slice(buffer, bytesRead))); } } }
/// <summary> /// Asynchronously reads the handshake message from the input stream. /// </summary> /// <param name="cancellationToken">The token to monitor for cancellation requests.</param> /// <returns> /// A task that represents the asynchronous read operation. /// The result of the task contains the decrypted message body. /// </returns> /// <exception cref="ObjectDisposedException"> /// Thrown if either the current instance, or the input stream has already been disposed. /// </exception> /// <exception cref="InvalidOperationException"> /// Thrown if the call to this method was unexpected in the current state of this object. /// </exception> /// <exception cref="ArgumentException"> /// Thrown if the decrypted message body length was invalid. /// </exception> /// <exception cref="System.Security.Cryptography.CryptographicException"> /// Thrown if the decryption of the message has failed. /// </exception> /// <exception cref="IOException">Thrown if an I/O error occurs.</exception> /// <exception cref="NotSupportedException">Thrown if the stream does not support reading.</exception> /// <remarks> /// This method can also throw all exceptions that <see cref="Protocol.Create(ProtocolConfig)"/> /// and <see cref="HandshakeState.Fallback(Protocol, ProtocolConfig)"/> methods can throw. /// See <see cref="Protocol"/> and <see cref="HandshakeState"/> documentation for more details. /// </remarks> public async Task <byte[]> ReadHandshakeMessageAsync(CancellationToken cancellationToken = default) { ThrowIfDisposed(); if (this.transport != null) { throw new InvalidOperationException($"Cannot call {nameof(ReadHandshakeMessageAsync)} after the handshake has been completed."); } InitializeHandshakeState(); var noiseMessage = await ReadPacketAsync(stream, cancellationToken).ConfigureAwait(false); ProcessMessage(HandshakeOperation.ReadHandshakeMessage, noiseMessage, false); var plaintext = new byte[noiseMessage.Length]; var(bytesRead, handshakeHash, transport) = handshakeState.ReadMessage(noiseMessage, plaintext); if (transport != null) { handshakeState.Dispose(); handshakeState = null; this.handshakeHash = handshakeHash; this.transport = transport; } if (isNextMessageEncrypted) { return(ReadPacket(plaintext.AsSpan(0, bytesRead))); } isNextMessageEncrypted = true; return(plaintext.AsSpan(0, bytesRead).ToArray()); }
/// <summary> /// Perform handshake protocol if it has not been already run. /// </summary> /// <remarks> /// Most uses of this package need not call Handshake explicitly: /// the first Read or Write will call it automatically. /// </remarks> internal void HandShake() { // Locking the handshakeMutex this.handshakeMutex.WaitOne(); HandshakeState handshakeState = null; try { Strobe c1; Strobe c2; byte[] receivedPayload = null; // did we already go through the handshake? if (this.handshakeComplite) { return; } KeyPair remoteKeyPair = null; if (this.config.RemoteKey != null) { if (this.config.RemoteKey.Length != Asymmetric.DhLen) { throw new Exception($"disco: the provided remote key is not {Asymmetric.DhLen}-byte"); } remoteKeyPair = new KeyPair { PublicKey = new byte[this.config.RemoteKey.Length] }; Array.Copy(this.config.RemoteKey, remoteKeyPair.PublicKey, this.config.RemoteKey.Length); } handshakeState = DiscoHelper.InitializeDisco( this.config.HandshakePattern, this.isClient, this.config.Prologue, this.config.KeyPair, null, remoteKeyPair, null); // pre-shared key handshakeState.Psk = this.config.PreSharedKey; do { // start handshake if (handshakeState.ShouldWrite) { // we're writing the next message pattern // if it's the message pattern and we're sending a static key, we also send a proof // TODO: is this the best way of sending a proof :/ ? byte[] bufToWrite; if (handshakeState.MessagePatterns.Length <= 2 && this.config.StaticPublicKeyProof != null) { (c1, c2) = handshakeState.WriteMessage(this.config.StaticPublicKeyProof, out bufToWrite); } else { (c1, c2) = handshakeState.WriteMessage(new byte[] { }, out bufToWrite); } // header (length) var length = new[] { (byte)(bufToWrite.Length >> 8), (byte)(bufToWrite.Length % 256) }; // write var dataToWrite = length.Concat(bufToWrite).ToArray(); this.connectionStream.Write(dataToWrite, 0, dataToWrite.Length); } else { var bufHeader = this.ReadFromUntil(this.connectionStream, 2); var length = (bufHeader[0] << 8) | bufHeader[1]; if (length > Config.NoiseMessageLength) { throw new Exception("disco: Disco message received exceeds DiscoMessageLength"); } var noiseMessage = this.ReadFromUntil(this.connectionStream, length); (c1, c2) = handshakeState.ReadMessage(noiseMessage, out receivedPayload); } }while (c1 == null); // Has the other peer been authenticated so far? if (!this.isRemoteAuthenticated && this.config.PublicKeyVerifier != null) { byte isRemoteStaticKeySet = 0; // test if remote static key is empty foreach (var val in handshakeState.Rs.PublicKey) { isRemoteStaticKeySet |= val; } if (isRemoteStaticKeySet != 0) { // a remote static key has been received. Verify it if (!this.config.PublicKeyVerifier(handshakeState.Rs.PublicKey, receivedPayload)) { throw new Exception("disco: the received public key could not be authenticated"); } this.isRemoteAuthenticated = true; this.RemotePublicKey = handshakeState.Rs.PublicKey; } } // Processing the final handshake message returns two CipherState objects // the first for encrypting transport messages from initiator to responder // and the second for messages in the other direction. if (c2 != null) { if (this.isClient) { (this.strobeOut, this.strobeIn) = (c1, c2); } else { (this.strobeOut, this.strobeIn) = (c2, c1); } } else { this.IsHalfDuplex = true; this.strobeIn = c1; this.strobeOut = c1; } // TODO: preserve c.hs.symmetricState.h // At that point the HandshakeState should be deleted except for the hash value h, which may be used for post-handshake channel binding (see Section 11.2). handshakeState.Dispose(); // no errors :) this.handshakeComplite = true; } finally { handshakeState?.Dispose(); this.handshakeMutex.ReleaseMutex(); } }