Example #1
0
        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));
                }
            }
        }
Example #2
0
        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)));
                }
            }
        }
Example #3
0
        /// <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);
            }
        }
Example #4
0
        /// <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();
            }
        }