📄 sess_rw.c
字号:
/****************************************************************************
* *
* cryptlib Session Read/Write Support Routines *
* Copyright Peter Gutmann 1998-2004 *
* *
****************************************************************************/
#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#if defined( INC_ALL )
#include "crypt.h"
#include "asn1.h"
#include "session.h"
#elif defined( INC_CHILD )
#include "../crypt.h"
#include "../misc/asn1.h"
#include "session.h"
#else
#include "crypt.h"
#include "misc/asn1.h"
#include "session/session.h"
#endif /* Compiler-specific includes */
#ifdef USE_SESSIONS
/* Common code to read and write data over the secure connection. This
is called by the protocol-specific handlers, which supply three functions:
readHeaderFunction() - Reads the header for a packet and sets up
length information.
processBodyFunction() - Processes the body of a packet.
preparePacketFunction() - Wraps a packet in preparation for sending it.
The behaviour of the network-level stream handlers is as follows:
Timeout byteCount Result
------- --------- ------
- error - error
0 0 0
0 > 0 byteCount
> 0 0 CRYPT_ERROR_TIMEOUT
> 0 > 0 byteCount
Errors in the processBodyFunction() and preparePacketFunction() are
always fatal. In theory we could try to recover, however the functions
update assorted crypto state such as packet sequence numbers and IVs that
would be tricky to roll back, and in practice recoverable errors are
likely to be extremely rare (at best perhaps a CRYPT_ERROR_TIMEOUT for a
context tied to a device, however even this won't occur since the
conventional encryption and MAC contexts are all internal native
contexts), so there's little point in trying to make the functions
recoverable */
/****************************************************************************
* *
* Secure Session Data Read Functions *
* *
****************************************************************************/
/* The read data code uses a helper function tryRead() that either reads
everything which is available or to the end of the current packet. In
other words it's an atomic, all-or-nothing function that can be used by
higher-level code to handle network-level packetisation. Buffer
management is handled as follows: The bPos index always points to the end
of the decoded data (i.e. data that can be used by the user), if there's
no partial packet present this index is the same as bEnd:
----+------------------------
////|
----+------------------------
^
|
bEnd/bPos
If there's a partial packet present, pendingPacketRemaining contains the
number of bytes required to complete the packet and bEnd points to the
end of the received data, and is advanced as more data is read:
<----> pPR
----+-------------------+----+----
////|///////////////////|....|
----+-------------------+----+----
^ ^
| |
bPos bEnd
Once the complete packet is read (pPR reaches 0), it's decrypted, and
bPos and bEnd are adjusted to point to the end of the new data:
----+------------------------+----
////|////////////////////////|
----+------------------------+----
^
|
bEnd/bPos
The handling of any header data present at the start of the packet
depends on the packet format, if the header is independent of the
encrypted data it's handled entirely by the readHeaderFunction() and
there's no need to provide special-case handling. If the header is part
of the encrypted data, decryption is a two-stage operation in which
readHeaderFunction() decrypts just enough of the packet to extract and
process the header (depositing any leftover non-header data at the start
of the buffer) and processBodyFunction() processes the rest of the data.
Errors in the readHeaderFunction() are fatal if they come from the session
protocol level (e.g. a MAC failure or bad packet) and nonfatal if they
come from the network layer below the session (the stream-level code has
its own handling of fatal vs. nonfatal errors, so we don't try and get
down to that level) */
static int tryRead( SESSION_INFO *sessionInfoPtr, READSTATE_INFO *readInfo )
{
int bytesLeft, status;
/* Clear return value */
*readInfo = READINFO_NONE;
/* If there's no pending packet information present, try and read it.
This can return one of four classes of values:
1. An error code.
2. Zero, to indicate that nothing was read.
3. OK_SPECIAL and read info READINFO_NOOP to indicate that header
data but no payload data was read.
4. A byte count and read info READINFO_HEADERPAYLOAD to indicate
that some payload data was read as part of the header */
if( sessionInfoPtr->pendingPacketLength <= 0 )
{
status = sessionInfoPtr->readHeaderFunction( sessionInfoPtr, readInfo );
if( status <= 0 && status != OK_SPECIAL )
return( status );
assert( ( status == OK_SPECIAL && *readInfo == READINFO_NOOP ) || \
( status > 0 && *readInfo == READINFO_HEADERPAYLOAD ) );
if( *readInfo == READINFO_HEADERPAYLOAD )
{
/* Some protocols treat the header information for a secured
data packet as part of the data, so when we read the header we
can get part of the payload included in the read. When the
protocol-specific header read code obtained some payload data
alongside the header, it returns READINFO_HEADERPAYLOAD to
indicate that the packet info needs to be adjusted for the
packet header data that was just read */
sessionInfoPtr->receiveBufEnd += status;
sessionInfoPtr->pendingPacketPartialLength = status;
sessionInfoPtr->pendingPacketRemaining -= status;
}
}
bytesLeft = sessionInfoPtr->receiveBufSize - sessionInfoPtr->receiveBufEnd;
assert( sessionInfoPtr->partialHeaderLength == 0 );
/* Sanity-check the read state */
if( sessionInfoPtr->receiveBufEnd < 0 || \
sessionInfoPtr->receiveBufEnd > sessionInfoPtr->receiveBufSize || \
sessionInfoPtr->receiveBufPos < 0 || \
sessionInfoPtr->receiveBufPos > sessionInfoPtr->receiveBufEnd || \
sessionInfoPtr->pendingPacketLength < 0 || \
sessionInfoPtr->pendingPacketRemaining <= 0 || \
sessionInfoPtr->pendingPacketPartialLength < 0 )
{
assert( NOTREACHED );
retExt( sessionInfoPtr, CRYPT_ERROR_BADDATA,
"Internal error: Inconsistent state detected in session "
"read stream" );
}
/* If there's not enough room in the receive buffer to read at least 1K
of packet data, don't try anything until the user has emptied more
data from the buffer */
if( bytesLeft < min( sessionInfoPtr->pendingPacketRemaining, 1024 ) )
return( 0 );
/* Try and read more of the packet */
status = sread( &sessionInfoPtr->stream,
sessionInfoPtr->receiveBuffer + sessionInfoPtr->receiveBufEnd,
min( sessionInfoPtr->pendingPacketRemaining, bytesLeft ) );
if( cryptStatusError( status ) )
{
sNetGetErrorInfo( &sessionInfoPtr->stream,
sessionInfoPtr->errorMessage,
&sessionInfoPtr->errorCode );
return( status );
}
if( status <= 0 )
/* Nothing read, try again later. This happens only if we're using
non-blocking reads (i.e. polled I/O), if any kind of timeout is
specified we'll get a timeout error if no data is read */
return( 0 );
sessionInfoPtr->receiveBufEnd += status;
sessionInfoPtr->pendingPacketRemaining -= status;
if( sessionInfoPtr->pendingPacketRemaining > 0 )
{
/* We got some but not all of the data, try again later */
*readInfo = READINFO_PARTIAL;
return( OK_SPECIAL );
}
assert( sessionInfoPtr->pendingPacketRemaining == 0 );
/* We've got a complete packet in the buffer, process it */
return( sessionInfoPtr->processBodyFunction( sessionInfoPtr, readInfo ) );
}
/* Get data from the remote system */
static int getData( SESSION_INFO *sessionInfoPtr,
BYTE *buffer, const int length, int *bytesCopied )
{
const int bytesToCopy = min( length, sessionInfoPtr->receiveBufPos );
READSTATE_INFO readInfo;
int remainder, status;
assert( bytesToCopy >= 0 );
/* Clear return value */
*bytesCopied = 0;
/* Sanity-check the read state */
if( sessionInfoPtr->receiveBufPos < 0 || \
sessionInfoPtr->receiveBufPos > sessionInfoPtr->receiveBufEnd || \
sessionInfoPtr->receiveBufEnd < 0 || \
sessionInfoPtr->receiveBufEnd > sessionInfoPtr->receiveBufSize )
{
assert( NOTREACHED );
retExt( sessionInfoPtr, CRYPT_ERROR_BADDATA,
"Internal error: Inconsistent state detected in session "
"read stream" );
}
/* Copy as much data as we can across and move any remaining data down
to the start of the receive buffer. We copy out up to receiveBufPos,
the end of the decoded data, but move up to receiveBufEnd, the
combined decoded data and any as-yet-undecoded partial data that
follows the decoded data */
if( bytesToCopy > 0 )
{
memcpy( buffer, sessionInfoPtr->receiveBuffer, bytesToCopy );
remainder = sessionInfoPtr->receiveBufEnd - bytesToCopy;
assert( remainder >= 0 );
if( remainder > 0 )
{
/* There's decoded and/or non-decoded data left, move it down to
the start of the buffer */
memmove( sessionInfoPtr->receiveBuffer,
sessionInfoPtr->receiveBuffer + bytesToCopy, remainder );
sessionInfoPtr->receiveBufPos -= bytesToCopy;
sessionInfoPtr->receiveBufEnd = remainder;
}
else
/* We've consumed all of the data in the buffer, reset the buffer
info */
sessionInfoPtr->receiveBufPos = sessionInfoPtr->receiveBufEnd = 0;
assert( sessionInfoPtr->receiveBufPos >= 0 );
/* Remember how much we've copied and, if we've satisfied the
request, exit */
*bytesCopied = bytesToCopy;
if( bytesToCopy >= length )
return( CRYPT_OK );
}
assert( sessionInfoPtr->receiveBufPos == 0 );
/* Try and read a complete packet. This can return one of four classes
of values:
1. An error code.
2. Zero to indicate that nothing was read (only happens on non-
blocking reads (polled I/O), a blocking read will return a
timeout error) or that there isn't enough room left in the read
buffer to read any more.
3a.OK_SPECIAL and read info READINFO_PARTIAL to indicate that a
partial packet (not enough to process) was read.
3b.OK_SPECIAL and read info READINFO_NOOP to indicate that a no-op
packet was read and the caller should try again without changing
the read timeout value.
4. A byte count if a complete packet was read and processed */
status = tryRead( sessionInfoPtr, &readInfo );
if( cryptStatusError( status ) && status != OK_SPECIAL )
{
/* If there's an error reading data, only return an error status if
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -