/// <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; } } }