Example #1
0
        /// <summary>
        /// Flush any queued application data packets
        /// </summary>
        private void FlushQueuedApplicationData()
        {
            foreach (ByteSpan queuedSpan in this.queuedApplicationData)
            {
                Record outgoingRecord = new Record();
                outgoingRecord.ContentType    = ContentType.ApplicationData;
                outgoingRecord.Epoch          = this.epoch;
                outgoingRecord.SequenceNumber = this.currentEpoch.NextOutgoingSequence;
                outgoingRecord.Length         = (ushort)this.currentEpoch.RecordProtection.GetEncryptedSize(queuedSpan.Length);
                ++this.currentEpoch.NextOutgoingSequence;

                // Encode the record to wire format
                ByteSpan packet = new byte[Record.Size + outgoingRecord.Length];
                ByteSpan writer = packet;
                outgoingRecord.Encode(writer);
                writer = writer.Slice(Record.Size);
                queuedSpan.CopyTo(writer);

                // Protect the record
                this.currentEpoch.RecordProtection.EncryptClientPlaintext(
                    packet.Slice(Record.Size, outgoingRecord.Length)
                    , packet.Slice(Record.Size, queuedSpan.Length)
                    , ref outgoingRecord
                    );

                base.WriteBytesToConnection(packet.GetUnderlyingArray(), packet.Length);
            }
            this.queuedApplicationData.Clear();
        }
Example #2
0
        /// <summary>
        /// Request from the application to write data to the DTLS
        /// stream. If appropriate, returns a byte span to send to
        /// the wire.
        /// </summary>
        /// <param name="bytes">Plaintext bytes to write</param>
        /// <param name="length">Length of the bytes to write</param>
        /// <returns>
        /// Encrypted data to put on the wire if appropriate,
        /// otherwise an empty span
        /// </returns>
        private ByteSpan WriteBytesToConnectionInternal(byte[] bytes, int length)
        {
            lock (this.syncRoot)
            {
                // If we're negotiating a new epoch, queue data
                if (this.nextEpoch.State != HandshakeState.Established)
                {
                    ByteSpan copyOfSpan = new byte[length];
                    new ByteSpan(bytes, 0, length).CopyTo(copyOfSpan);

                    this.queuedApplicationData.Add(copyOfSpan);
                    return(ByteSpan.Empty);
                }

                // Send any queued application data now
                this.FlushQueuedApplicationData();

                Record outgoinRecord = new Record();
                outgoinRecord.ContentType    = ContentType.ApplicationData;
                outgoinRecord.Epoch          = this.epoch;
                outgoinRecord.SequenceNumber = this.currentEpoch.NextOutgoingSequence;
                outgoinRecord.Length         = (ushort)this.currentEpoch.RecordProtection.GetEncryptedSize(length);
                ++this.currentEpoch.NextOutgoingSequence;

                // Encode the record to wire format
                ByteSpan packet = new byte[Record.Size + outgoinRecord.Length];
                ByteSpan writer = packet;
                outgoinRecord.Encode(writer);
                writer = writer.Slice(Record.Size);
                new ByteSpan(bytes, 0, length).CopyTo(writer);

                // Protect the record
                this.currentEpoch.RecordProtection.EncryptClientPlaintext(
                    packet.Slice(Record.Size, outgoinRecord.Length)
                    , packet.Slice(Record.Size, length)
                    , ref outgoinRecord
                    );

                return(packet);
            }
        }
        private static void EncryptPlaintext(ByteSpan output, ByteSpan input, ref Record record, Aes128Gcm cipher, ByteSpan writeIV)
        {
            Debug.Assert(output.Length >= GetEncryptedSizeImpl(input.Length));

            // Build GCM nonce (authenticated data)
            ByteSpan nonce = new byte[ImplicitNonceSize + ExplicitNonceSize];

            writeIV.CopyTo(nonce);
            nonce.WriteBigEndian16(record.Epoch, ImplicitNonceSize);
            nonce.WriteBigEndian48(record.SequenceNumber, ImplicitNonceSize + 2);

            // Serialize record as additional data
            Record plaintextRecord = record;

            plaintextRecord.Length = (ushort)input.Length;
            ByteSpan associatedData = new byte[Record.Size];

            plaintextRecord.Encode(associatedData);

            cipher.Seal(output, nonce, input, associatedData);
        }
Example #4
0
        /// <summary>
        /// Send (resend) the ClientKeyExchange flight
        /// </summary>
        /// <param name="isRetransmit">
        /// True if this is a retransmit of the flight. Otherwise,
        /// false
        /// </param>
        private void SendClientKeyExchangeFlight(bool isRetransmit)
        {
            // Describe our flight
            Handshake keyExchangeHandshake = new Handshake();

            keyExchangeHandshake.MessageType     = HandshakeType.ClientKeyExchange;
            keyExchangeHandshake.Length          = (ushort)this.nextEpoch.Handshake.CalculateClientMessageSize();
            keyExchangeHandshake.MessageSequence = 5;
            keyExchangeHandshake.FragmentOffset  = 0;
            keyExchangeHandshake.FragmentLength  = keyExchangeHandshake.Length;

            Record keyExchangeRecord = new Record();

            keyExchangeRecord.ContentType    = ContentType.Handshake;
            keyExchangeRecord.Epoch          = this.epoch;
            keyExchangeRecord.SequenceNumber = this.currentEpoch.NextOutgoingSequence;
            keyExchangeRecord.Length         = (ushort)this.currentEpoch.RecordProtection.GetEncryptedSize(Handshake.Size + (int)keyExchangeHandshake.Length);
            ++this.currentEpoch.NextOutgoingSequence;

            Record changeCipherSpecRecord = new Record();

            changeCipherSpecRecord.ContentType    = ContentType.ChangeCipherSpec;
            changeCipherSpecRecord.Epoch          = this.epoch;
            changeCipherSpecRecord.SequenceNumber = this.currentEpoch.NextOutgoingSequence;
            changeCipherSpecRecord.Length         = (ushort)this.currentEpoch.RecordProtection.GetEncryptedSize(ChangeCipherSpec.Size);
            ++this.currentEpoch.NextOutgoingSequence;

            Handshake finishedHandshake = new Handshake();

            finishedHandshake.MessageType     = HandshakeType.Finished;
            finishedHandshake.Length          = Finished.Size;
            finishedHandshake.MessageSequence = 6;
            finishedHandshake.FragmentOffset  = 0;
            finishedHandshake.FragmentLength  = finishedHandshake.Length;

            Record finishedRecord = new Record();

            finishedRecord.ContentType    = ContentType.Handshake;
            finishedRecord.Epoch          = this.nextEpoch.Epoch;
            finishedRecord.SequenceNumber = this.nextEpoch.NextOutgoingSequence;
            finishedRecord.Length         = (ushort)this.nextEpoch.RecordProtection.GetEncryptedSize(Handshake.Size + (int)finishedHandshake.Length);
            ++this.nextEpoch.NextOutgoingSequence;

            // Encode flight to wire format
            int packetLength = 0
                               + Record.Size + keyExchangeRecord.Length
                               + Record.Size + changeCipherSpecRecord.Length
                               + Record.Size + finishedRecord.Length;

            ;
            ByteSpan packet = new byte[packetLength];
            ByteSpan writer = packet;

            keyExchangeRecord.Encode(writer);
            writer = writer.Slice(Record.Size);
            keyExchangeHandshake.Encode(writer);
            writer = writer.Slice(Handshake.Size);
            this.nextEpoch.Handshake.EncodeClientKeyExchangeMessage(writer);

            ByteSpan startOfChangeCipherSpecRecord = packet.Slice(Record.Size + keyExchangeRecord.Length);

            writer = startOfChangeCipherSpecRecord;
            changeCipherSpecRecord.Encode(writer);
            writer = writer.Slice(Record.Size);
            ChangeCipherSpec.Encode(writer);
            writer = writer.Slice(ChangeCipherSpec.Size);

            ByteSpan startOfFinishedRecord = startOfChangeCipherSpecRecord.Slice(Record.Size + changeCipherSpecRecord.Length);

            writer = startOfFinishedRecord;
            finishedRecord.Encode(writer);
            writer = writer.Slice(Record.Size);
            finishedHandshake.Encode(writer);
            writer = writer.Slice(Handshake.Size);

            // Interject here to writer our client key exchange
            // message into the verification stream
            if (!isRetransmit)
            {
                this.nextEpoch.VerificationStream.Write(
                    packet.GetUnderlyingArray()
                    , Record.Size
                    , Handshake.Size + (int)keyExchangeHandshake.Length
                    );
            }

            // Calculate the hash of the verification stream
            ByteSpan handshakeHash;

            using (SHA256 sha256 = SHA256.Create())
            {
                this.nextEpoch.VerificationStream.Position = 0;
                handshakeHash = sha256.ComputeHash(this.nextEpoch.VerificationStream);
            }

            // Expand our master secret into Finished digests for the client and server
            PrfSha256.ExpandSecret(
                this.nextEpoch.ServerVerification
                , this.nextEpoch.MasterSecret
                , PrfLabel.SERVER_FINISHED
                , handshakeHash
                );

            PrfSha256.ExpandSecret(
                writer.Slice(0, Finished.Size)
                , this.nextEpoch.MasterSecret
                , PrfLabel.CLIENT_FINISHED
                , handshakeHash
                );
            writer = writer.Slice(Finished.Size);

            // Protect the ClientKeyExchange record
            this.currentEpoch.RecordProtection.EncryptClientPlaintext(
                packet.Slice(Record.Size, keyExchangeRecord.Length)
                , packet.Slice(Record.Size, Handshake.Size + (int)keyExchangeHandshake.Length)
                , ref keyExchangeRecord
                );

            // Protect the ChangeCipherSpec record
            this.currentEpoch.RecordProtection.EncryptClientPlaintext(
                startOfChangeCipherSpecRecord.Slice(Record.Size, changeCipherSpecRecord.Length)
                , startOfChangeCipherSpecRecord.Slice(Record.Size, ChangeCipherSpec.Size)
                , ref changeCipherSpecRecord
                );

            // Protect the Finished record
            this.nextEpoch.RecordProtection.EncryptClientPlaintext(
                startOfFinishedRecord.Slice(Record.Size, finishedRecord.Length)
                , startOfFinishedRecord.Slice(Record.Size, Handshake.Size + (int)finishedHandshake.Length)
                , ref finishedRecord
                );

            this.nextEpoch.State = HandshakeState.ExpectingChangeCipherSpec;
            this.nextEpoch.NextPacketResendTime = DateTime.UtcNow + this.handshakeResendTimeout;
            base.WriteBytesToConnection(packet.GetUnderlyingArray(), packet.Length);
        }
Example #5
0
        /// <summary>
        /// Send (resend) a ClientHello message to the server
        /// </summary>
        private void SendClientHello()
        {
            // Reset our verification stream
            this.nextEpoch.VerificationStream.SetLength(0);
            this.nextEpoch.ClientRandom.FillWithRandom(this.random);

            // Describe our ClientHello flight
            ClientHello clientHello = new ClientHello();

            clientHello.Random       = this.nextEpoch.ClientRandom;
            clientHello.Cookie       = this.nextEpoch.Cookie;
            clientHello.CipherSuites = new byte[2];
            clientHello.CipherSuites.WriteBigEndian16((ushort)CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256);
            clientHello.SupportedCurves = new byte[2];
            clientHello.SupportedCurves.WriteBigEndian16((ushort)NamedCurve.x25519);

            Handshake handshake = new Handshake();

            handshake.MessageType     = HandshakeType.ClientHello;
            handshake.Length          = (uint)clientHello.CalculateSize();
            handshake.MessageSequence = 0;
            handshake.FragmentOffset  = 0;
            handshake.FragmentLength  = handshake.Length;

            // Describe the record
            int    plaintextLength = (int)(Handshake.Size + handshake.Length);
            Record outgoingRecord  = new Record();

            outgoingRecord.ContentType    = ContentType.Handshake;
            outgoingRecord.Epoch          = this.epoch;
            outgoingRecord.SequenceNumber = this.currentEpoch.NextOutgoingSequence;
            outgoingRecord.Length         = (ushort)this.currentEpoch.RecordProtection.GetEncryptedSize(plaintextLength);
            ++this.currentEpoch.NextOutgoingSequence;

            // Convert the record to wire format
            ByteSpan packet = new byte[Record.Size + outgoingRecord.Length];
            ByteSpan writer = packet;

            outgoingRecord.Encode(packet);
            writer = writer.Slice(Record.Size);
            handshake.Encode(writer);
            writer = writer.Slice(Handshake.Size);
            clientHello.Encode(writer);

            // Write ClientHello to the verification stream
            this.nextEpoch.VerificationStream.Write(
                packet.GetUnderlyingArray()
                , Record.Size
                , Handshake.Size + (int)handshake.Length
                );

            // Protect the record
            this.currentEpoch.RecordProtection.EncryptClientPlaintext(
                packet.Slice(Record.Size, outgoingRecord.Length)
                , packet.Slice(Record.Size, plaintextLength)
                , ref outgoingRecord
                );

            this.nextEpoch.State = HandshakeState.ExpectingServerHello;
            this.nextEpoch.NextPacketResendTime = DateTime.UtcNow + this.handshakeResendTimeout;
            base.WriteBytesToConnection(packet.GetUnderlyingArray(), packet.Length);
        }