@@ -247,12 +247,15 @@ private async Task<ReadOnlyMemory<byte>> ReceiveResponse(
247247
248248 // Get initialization packet, handling keep-alive
249249 var initPacket = await _connection . ReceiveAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
250- while ( GetPacketCommand ( initPacket . Span ) == CtapConstants . CtapHidKeepAlive )
250+ while ( IsKeepAlivePacket ( initPacket . Span ) )
251251 {
252+ ValidateInitPacket ( initPacket . Span , channelId ) ;
252253 _logger . LogTrace ( "Received keep-alive, waiting for response" ) ;
253254 initPacket = await _connection . ReceiveAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
254255 }
255256
257+ ValidateInitPacket ( initPacket . Span , channelId ) ;
258+
256259 var responseLength = GetPacketLength ( initPacket . Span ) ;
257260 if ( responseLength > CtapConstants . MaxPayloadSize )
258261 throw new InvalidOperationException ( $ "Response length { responseLength } exceeds max payload size") ;
@@ -261,32 +264,25 @@ private async Task<ReadOnlyMemory<byte>> ReceiveResponse(
261264 var responseData = new byte [ responseLength ] ;
262265 var initDataLength = Math . Min ( responseLength , CtapConstants . InitDataSize ) ;
263266
264- // Ensure we don't try to read more data than the packet contains
265- var availableDataInPacket = Math . Min ( initDataLength , initPacket . Length - CtapConstants . InitHeaderSize ) ;
266- if ( availableDataInPacket < 0 )
267- availableDataInPacket = 0 ;
268-
269- initPacket . Span . Slice ( CtapConstants . InitHeaderSize , availableDataInPacket )
267+ initPacket . Span . Slice ( CtapConstants . InitHeaderSize , initDataLength )
270268 . CopyTo ( responseData ) ;
271269
272270 // Receive continuation packets if needed
273- var bytesReceived = availableDataInPacket ;
271+ var bytesReceived = initDataLength ;
272+ byte expectedSequence = 0 ;
274273 while ( bytesReceived < responseLength )
275274 {
276275 var contPacket = await _connection . ReceiveAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
276+ ValidateContinuationPacket ( contPacket . Span , channelId , expectedSequence ) ;
277277 var contDataLength = Math . Min (
278278 responseLength - bytesReceived ,
279279 CtapConstants . ContinuationDataSize ) ;
280280
281- // Ensure we don't try to read more data than the packet contains
282- var availableContData = Math . Min ( contDataLength , contPacket . Length - CtapConstants . ContinuationHeaderSize ) ;
283- if ( availableContData < 0 )
284- availableContData = 0 ;
285-
286- contPacket . Span . Slice ( CtapConstants . ContinuationHeaderSize , availableContData )
281+ contPacket . Span . Slice ( CtapConstants . ContinuationHeaderSize , contDataLength )
287282 . CopyTo ( responseData . AsSpan ( bytesReceived ) ) ;
288283
289- bytesReceived += availableContData ;
284+ bytesReceived += contDataLength ;
285+ expectedSequence ++ ;
290286 }
291287
292288 _logger . LogTrace ( "Received {Length} bytes in response" , responseLength ) ;
@@ -343,6 +339,44 @@ private static byte[] ConstructContinuationPacket(uint channelId, byte sequence,
343339 private static byte GetPacketCommand ( ReadOnlySpan < byte > packet ) =>
344340 ( byte ) ( packet [ 4 ] & ~ CtapConstants . InitPacketMask ) ;
345341
342+ private static bool IsKeepAlivePacket ( ReadOnlySpan < byte > packet ) =>
343+ packet . Length >= CtapConstants . InitHeaderSize &&
344+ ( packet [ 4 ] & CtapConstants . InitPacketMask ) != 0 &&
345+ GetPacketCommand ( packet ) == CtapConstants . CtapHidKeepAlive ;
346+
347+ private static void ValidateInitPacket ( ReadOnlySpan < byte > packet , uint channelId )
348+ {
349+ if ( packet . Length != CtapConstants . PacketSize )
350+ throw new InvalidOperationException ( "CTAP HID init packet must be exactly 64 bytes" ) ;
351+
352+ var packetChannelId = BinaryPrimitives . ReadUInt32BigEndian ( packet ) ;
353+ if ( packetChannelId != channelId )
354+ throw new InvalidOperationException ( "CTAP HID init packet channel mismatch" ) ;
355+
356+ if ( ( packet [ 4 ] & CtapConstants . InitPacketMask ) == 0 )
357+ throw new InvalidOperationException ( "CTAP HID response packet is not an init packet" ) ;
358+ }
359+
360+ private static void ValidateContinuationPacket ( ReadOnlySpan < byte > packet , uint channelId , byte expectedSequence )
361+ {
362+ if ( packet . Length != CtapConstants . PacketSize )
363+ throw new InvalidOperationException ( "CTAP HID continuation packet must be exactly 64 bytes" ) ;
364+
365+ var packetChannelId = BinaryPrimitives . ReadUInt32BigEndian ( packet ) ;
366+ if ( packetChannelId != channelId )
367+ throw new InvalidOperationException ( "CTAP HID continuation packet channel mismatch" ) ;
368+
369+ if ( ( packet [ 4 ] & CtapConstants . InitPacketMask ) != 0 )
370+ throw new InvalidOperationException ( "CTAP HID continuation packet has init bit set" ) ;
371+
372+ var sequence = packet [ 4 ] ;
373+ if ( ! IsExpectedContinuationSequence ( sequence , expectedSequence ) )
374+ throw new InvalidOperationException ( "CTAP HID continuation packet sequence mismatch" ) ;
375+ }
376+
377+ internal static bool IsExpectedContinuationSequence ( byte sequence , byte expectedSequence ) =>
378+ ( sequence & ~ CtapConstants . InitPacketMask ) == ( expectedSequence & ~ CtapConstants . InitPacketMask ) ;
379+
346380 /// <summary>
347381 /// Extracts the payload length from an init packet.
348382 /// </summary>
0 commit comments