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 writes the negotiation data and the handshake message to the input stream. /// </summary> /// <param name="negotiationData">The negotiation data.</param> /// <param name="messageBody">The message body to encrypt.</param> /// <param name="paddedLength"> /// If this message has an encrypted payload and the length of the /// <paramref name="messageBody"/> is less than <paramref name="paddedLength"/>, /// <paramref name="messageBody"/> is padded to make its /// length equal to <paramref name="paddedLength"/>. /// </param> /// <param name="cancellationToken">The token to monitor for cancellation requests.</param> /// <returns>A task that represents the asynchronous write operation.</returns> /// <exception cref="ObjectDisposedException"> /// Thrown if either the current instance, or the output 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 either the negotiation data, or the Noise message was greater /// than <see cref="Protocol.MaxMessageLength"/> bytes in length. /// </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 WriteHandshakeMessageAsync( Memory <byte> negotiationData = default, Memory <byte> messageBody = default, ushort paddedLength = default, CancellationToken cancellationToken = default) { ThrowIfDisposed(); if (this.transport != null) { throw new InvalidOperationException($"Cannot call {nameof(WriteHandshakeMessageAsync)} after the handshake has been completed."); } if (negotiationData.Length > Protocol.MaxMessageLength) { throw new ArgumentException($"Negotiation data must be less than or equal to {Protocol.MaxMessageLength} bytes in length."); } if (messageBody.Length > Protocol.MaxMessageLength) { throw new ArgumentException($"Handshake message must be less than or equal to {Protocol.MaxMessageLength} bytes in length."); } ProcessMessage(HandshakeOperation.WriteNegotiationData, negotiationData); InitializeHandshakeState(); // negotiation_data_len (2 bytes) // negotiation_data // noise_message_len (2 bytes) // noise_message int negotiationLength = LenFieldSize + negotiationData.Length; int maxNoiseLength = LenFieldSize + Protocol.MaxMessageLength; // Prevent the buffer from going to the LOH (it may be greater than 85000 bytes). var pool = ArrayPool <byte> .Shared; var buffer = pool.Rent(negotiationLength + maxNoiseLength); try { Memory <byte> plaintext = messageBody; if (isNextMessageEncrypted) { plaintext = new byte[LenFieldSize + Math.Max(messageBody.Length, paddedLength)]; WritePacket(messageBody.Span, plaintext.Span); } var ciphertext = buffer.AsMemory(negotiationLength + LenFieldSize); var(bytesWritten, handshakeHash, transport) = handshakeState.WriteMessage(plaintext.Span, ciphertext.Span); isNextMessageEncrypted = true; if (transport != null) { handshakeState.Dispose(); handshakeState = null; this.handshakeHash = handshakeHash; this.transport = transport; } WritePacket(negotiationData.Span, buffer); BinaryPrimitives.WriteUInt16BigEndian(buffer.AsSpan(negotiationLength), (ushort)bytesWritten); ProcessMessage(HandshakeOperation.WriteHandshakeMessage, ciphertext.Slice(0, bytesWritten)); int noiseLength = LenFieldSize + bytesWritten; int handshakeLength = negotiationLength + noiseLength; await stream.WriteAsync(buffer, 0, handshakeLength, cancellationToken).ConfigureAwait(false); } finally { pool.Return(buffer); } }
/// <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(); } }