Esempio n. 1
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);
        }
Esempio n. 2
0
        /// <summary>
        /// Handle an incoming datagram
        /// </summary>
        /// <param name="span">Bytes of the datagram</param>
        private void HandleReceive(ByteSpan span)
        {
            // Each incoming packet may contain multiple DTLS
            // records
            while (span.Length > 0)
            {
                Record record;
                if (!Record.Parse(out record, span))
                {
                    this.logger.WriteError("Dropping malformed record");
                    return;
                }
                span = span.Slice(Record.Size);

                if (span.Length < record.Length)
                {
                    this.logger.WriteError($"Dropping malformed record. Length({record.Length}) Available Bytes({span.Length})");
                    return;
                }

                ByteSpan recordPayload = span.Slice(0, record.Length);
                span = span.Slice(record.Length);

                // Early out and drop ApplicationData records
                if (record.ContentType == ContentType.ApplicationData && this.nextEpoch.State != HandshakeState.Established)
                {
                    this.logger.WriteError("Dropping ApplicationData record. Cannot process yet");
                    continue;
                }

                // Drop records from a different epoch
                if (record.Epoch != this.epoch)
                {
                    this.logger.WriteError($"Dropping bad-epoch record. RecordEpoch({record.Epoch}) Epoch({this.epoch})");
                    continue;
                }

                // Prevent replay attacks by dropping records
                // we've already processed
                int   windowIndex = (int)(this.currentEpoch.NextExpectedSequence - record.SequenceNumber - 1);
                ulong windowMask  = 1ul << windowIndex;
                if (record.SequenceNumber < this.currentEpoch.NextExpectedSequence)
                {
                    if (windowIndex >= 64)
                    {
                        this.logger.WriteError($"Dropping too-old record: Sequnce({record.SequenceNumber}) Expected({this.currentEpoch.NextExpectedSequence})");
                        continue;
                    }

                    if ((this.currentEpoch.PreviousSequenceWindowBitmask & windowMask) != 0)
                    {
                        this.logger.WriteError("Dropping duplicate record");
                        continue;
                    }
                }

                // Verify record authenticity
                int      decryptedSize    = this.currentEpoch.RecordProtection.GetDecryptedSize(recordPayload.Length);
                ByteSpan decryptedPayload = recordPayload.ReuseSpanIfPossible(decryptedSize);

                if (!this.currentEpoch.RecordProtection.DecryptCiphertextFromServer(decryptedPayload, recordPayload, ref record))
                {
                    this.logger.WriteError("Dropping non-authentic record");
                    return;
                }

                recordPayload = decryptedPayload;

                // Update out sequence number bookkeeping
                if (record.SequenceNumber >= this.currentEpoch.NextExpectedSequence)
                {
                    int windowShift = (int)(record.SequenceNumber + 1 - this.currentEpoch.NextExpectedSequence);
                    this.currentEpoch.PreviousSequenceWindowBitmask <<= windowShift;
                    this.currentEpoch.NextExpectedSequence            = record.SequenceNumber + 1;
                }
                else
                {
                    this.currentEpoch.PreviousSequenceWindowBitmask |= windowMask;
                }

                switch (record.ContentType)
                {
                case ContentType.ChangeCipherSpec:
                    if (this.nextEpoch.State != HandshakeState.ExpectingChangeCipherSpec)
                    {
                        this.logger.WriteError($"Dropping unexpected ChangeCipherSpec State({this.nextEpoch.State})");
                        break;
                    }
                    else if (this.nextEpoch.RecordProtection == null)
                    {
                        ///NOTE(mendsley): This _should_ not
                        /// happen on a well-formed client.
                        Debug.Assert(false, "How did we receive a ChangeCipherSpec message without a pending record protection instance?");
                        break;
                    }

                    if (!ChangeCipherSpec.Parse(recordPayload))
                    {
                        this.logger.WriteError("Dropping malformed ChangeCipherSpec message");
                        break;
                    }

                    // Migrate to the next epoch
                    this.epoch = this.nextEpoch.Epoch;
                    this.currentEpoch.RecordProtection              = this.nextEpoch.RecordProtection;
                    this.currentEpoch.NextOutgoingSequence          = this.nextEpoch.NextOutgoingSequence;
                    this.currentEpoch.NextExpectedSequence          = 1;
                    this.currentEpoch.PreviousSequenceWindowBitmask = 0;

                    this.nextEpoch.State = HandshakeState.ExpectingFinished;
                    this.nextEpoch.SelectedCipherSuite = CipherSuite.TLS_NULL_WITH_NULL_NULL;
                    this.nextEpoch.RecordProtection    = null;
                    this.nextEpoch.Handshake?.Dispose();
                    this.nextEpoch.Cookie = ByteSpan.Empty;
                    this.nextEpoch.VerificationStream.SetLength(0);
                    this.nextEpoch.ServerPublicKey = null;
                    this.nextEpoch.ServerRandom.SecureClear();
                    this.nextEpoch.ClientRandom.SecureClear();
                    this.nextEpoch.MasterSecret.SecureClear();
                    break;

                case ContentType.Alert:
                    this.logger.WriteError("Dropping unsupported alert record");
                    continue;

                case ContentType.Handshake:
                    if (!ProcessHandshake(ref record, recordPayload))
                    {
                        return;
                    }
                    break;

                case ContentType.ApplicationData:
                    // Forward data to the application
                    MessageReader reader = MessageReader.GetSized(recordPayload.Length);
                    reader.Length = recordPayload.Length;
                    recordPayload.CopyTo(reader.Buffer);

                    base.HandleReceive(reader, recordPayload.Length);
                    break;
                }
            }
        }