/****************************************************************************
*																			*
*							PGP Read/Write Routines							*
*						Copyright Peter Gutmann 1992-2025					*
*																			*
****************************************************************************/

#if defined( INC_ALL )
  #include "crypt.h"
  #include "pgp_rw.h"
#else
  #include "crypt.h"
  #include "enc_dec/pgp_rw.h"
#endif /* Compiler-specific includes */

#if defined( USE_PGP ) || defined( USE_PGPKEYS )

/****************************************************************************
*																			*
*							PGP Read/Write Routines							*
*																			*
****************************************************************************/

/* Read a length in OpenPGP or PGP2 format */

CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2, 3 ) ) \
static int readOpenPGPLength( INOUT_PTR STREAM *stream, 
							  OUT_LENGTH_Z int *length,
							  OUT_BOOL BOOLEAN *indefiniteLength,
							  IN_BOOL const BOOLEAN indefOK )
	{
	int localLength, status;

	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	assert( isWritePtr( length, sizeof( int ) ) );
	assert( isWritePtr( indefiniteLength, sizeof( BOOLEAN ) ) );

	REQUIRES_S( isBooleanValue( indefOK ) );

	/* Clear return values */
	*length = 0;
	*indefiniteLength = FALSE;

	/* Get the initial length byte to allow us to decode what else is 
	   there */
	status = localLength = sgetc( stream );
	if( cryptStatusError( status ) )
		return( status );

	/* 0...191 is a literal value */
	if( localLength <= 191 )
		{
		*length = localLength;
		return( CRYPT_OK );
		}

	/* 192...223 is a 13-bit value with offset 192, giving a length in the 
	   range 192...8383 */
	if( localLength <= 223 )
		{
		int value;
		
		status = value = sgetc( stream );
		if( cryptStatusError( status ) )
			return( status );
		localLength = ( ( localLength - 192 ) << 8 ) + value + 192;
		if( localLength < 192 || localLength > 8383 )
			return( sSetError( stream, CRYPT_ERROR_BADDATA ) );
		*length = localLength;

		return( CRYPT_OK );
		}

	/* 224...254 is PGP's annoying interpretation of indefinite-length 
	   encoding.  This is an incredible pain to handle but fortunately 
	   except for McAfee's PGP implementation and GPG under some 
	   circumstances when it's used in a pipeline it doesn't seem to be used 
	   by anything.  The only data type that would normally need indefinite 
	   lengths, compressed data, uses the 2.x CTB 0xA3 instead */
	if( localLength < 255 )
		{
		const int shiftAmount = localLength & 0x1F;
		
		if( !indefOK )
			return( sSetError( stream, CRYPT_ERROR_BADDATA ) );

		/* Unlike ASN.1, which encodes an outer indefinite-length marker and 
		   then encodes each sub-segment as a data unit within it, PGP 
		   encodes a partial length as a sequence of power-of-two exponent 
		   values with a standard length encoding for the last sub-segment.
		   So once we're in indefinite-length mode we have to record the 
		   current *type* of the length as well as its value to determine 
		   whether more length packets follow.
		   
		   The spec says (section 4.2.2.4) that the "length is a power of 2, 
		   from 1 to 1,073,741,824 (2 to the 30th power)" but it's not clear
		   that 2^0, or even slightly larger values, should be allowed since
		   any odd bits at the end of the data would be included in the
		   definite-length final sub-segment, so we require a shift amount 
		   of at least 2, for four bytes of data.
		   
		   Fortunately the standard also specifies an upper bound, 2^30, 
		   which keeps us away from problems with 32-bit integers */
		if( shiftAmount < 2 || shiftAmount > 30 )
			return( sSetError( stream, CRYPT_ERROR_BADDATA ) );
		localLength = 1 << shiftAmount;
		if( !isIntegerRangeNZ( localLength ) )
			return( sSetError( stream, CRYPT_ERROR_BADDATA ) );
		*indefiniteLength = TRUE;
		*length = localLength;

		return( CRYPT_OK );
		}
	ENSURES_S( localLength == 255 );

	/* 255 is a marker that a standard 32-bit length follows */
	status = localLength = readUint32( stream );
	if( cryptStatusError( status ) )
		return( status );
	ENSURES_S( isIntegerRange( localLength ) );
			   /* Guaranteed by readUint32() */
	*length = localLength;

	return( CRYPT_OK );
	}

CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2 ) ) \
static int readPGP2Length( INOUT_PTR STREAM *stream, 
						   OUT_LENGTH_Z int *length, 
						   IN_BYTE const int ctb )
	{
	int localLength = 0, status;

	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	assert( isWritePtr( length, sizeof( int ) ) );

	REQUIRES_S( ctb >= 0 && ctb <= 0xFF );

	/* Clear return value */
	*length = 0;

	/* It's a PGP 2.x CTB, decode the length as a byte, word, or long */
	switch( ctb & 3 )
		{
		case 0:
			status = localLength = sgetc( stream );
			break;

		case 1:
			status = localLength = readUint16( stream );
			break;

		case 2:
			status = localLength = readUint32( stream );
			break;

		default:
			/* A length value of 3 indicates that the data length is 
			   determined externally, this is a deprecated PGP 2.x value 
			   that we don't handle */
			status = CRYPT_ERROR_BADDATA;
		}
	if( cryptStatusError( status ) )
		return( sSetError( stream, status ) );
	if( !isIntegerRange( localLength ) )
		return( sSetError( stream, CRYPT_ERROR_BADDATA ) );
	*length = localLength;

	return( CRYPT_OK );
	}

/* Read PGP variable-length length values and packet headers (CTB + length).  
   We also have a short-length version which is used to read small packets 
   such as keyrings and sigs and which ensures that the length is in the 
   range 1...16K */

CHECK_RETVAL_SPECIAL STDC_NONNULL_ARG( ( 1, 2 ) ) \
static int pgpReadLength( INOUT_PTR STREAM *stream, 
						  OUT_LENGTH_BOUNDED_Z( maxLength ) int *length, 
						  IN_BYTE const int ctb, 
						  IN_LENGTH_SHORT_Z const int minLength, 
						  IN_LENGTH const int maxLength, 
						  IN_BOOL const BOOLEAN indefOK )
	{
	BOOLEAN indefiniteLength = FALSE;
	int localLength, status;

	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	assert( isWritePtr( length, sizeof( int ) ) );

	REQUIRES_S( isShortIntegerRange( minLength ) && \
				minLength < maxLength && maxLength < MAX_INTLENGTH );
	REQUIRES_S( isBooleanValue( indefOK ) );

	/* Clear return value */
	*length = 0;

	/* If it doesn't look like PGP data, don't go any further */
	if( !pgpIsCTB( ctb ) )
		return( sSetError( stream, CRYPT_ERROR_BADDATA ) );

	/* If it's an OpenPGP CTB, undo the hand-Huffman-coding */
	if( pgpGetPacketVersion( ctb ) == PGP_VERSION_OPENPGP )
		{
		status = readOpenPGPLength( stream, &localLength, 
									&indefiniteLength, indefOK );
		}
	else
		status = readPGP2Length( stream, &localLength, ctb );
	if( cryptStatusError( status ) )
		return( status );
	if( localLength < minLength || localLength > maxLength )
		return( sSetError( stream, CRYPT_ERROR_BADDATA ) );
	*length = localLength;
	return( indefiniteLength ? OK_SPECIAL : CRYPT_OK );
	}

CHECK_RETVAL_SPECIAL STDC_NONNULL_ARG( ( 1 ) ) \
static int readPacketHeader( INOUT_PTR STREAM *stream, 
							 OUT_OPT_BYTE int *ctb, 
							 OUT_OPT_LENGTH_Z int *length, 
							 IN_LENGTH_SHORT_Z const int minLength, 
							 IN_LENGTH const int maxLength, 
							 IN_BOOL const BOOLEAN indefOK )
	{
	int localCTB, localLength, status;

	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	assert( ctb == NULL || isWritePtr( ctb, sizeof( int ) ) );
	assert( length == NULL || isWritePtr( length, sizeof( int ) ) );
	
	REQUIRES_S( isShortIntegerRange( minLength ) && \
				minLength < maxLength && maxLength < MAX_INTLENGTH );
	REQUIRES_S( isBooleanValue( indefOK ) );

	/* Clear return values */
	if( ctb != NULL )
		*ctb = 0;
	if( length != NULL )
		*length = 0;

	/* Examine the CTB and figure out whether we need to perform any 
	   special-case handling */
	status = localCTB = sgetc( stream );
	if( cryptStatusError( status ) )
		return( status );
	if( !pgpIsCTB( localCTB ) )
		{
		/* If it doesn't look like PGP data, don't go any further */
		return( sSetError( stream, CRYPT_ERROR_BADDATA ) );
		}
	if( localCTB == PGP_CTB_COMPRESSED )
		{
		/* If it's a compressed data packet, there's no length present.
		   Normally we reject any indefinite-length packets since these
		   can't be processed sensibly (PGP 2.x, which used intermediate
		   files for everything, just read to EOF, OpenPGP deprecates them
		   because this doesn't exactly lead to portable implementations).
		   However, compressed-data packets can only be stored in this
		   manner but can still be processed because the user has to
		   explicitly flush the data at some point and we assume that this
		   is EOF.
		   
		   For this reason we don't return OK_SPECIAL to indicate an 
		   indefinite-length encoding because this isn't a standard 
		   segmented encoding but a virtual definite-length that ends when
		   the user says it ends.   This is far uglier than the PKCS #7/CMS/
		   SMIME equivalent where we've got an explicit end-of-data
		   indication, but it's the best that we can do.
		   
		   In addition it's not clear what we should return as the "length"
		   value for this non-length, the contract with the caller says that
		   we'll only permit a returned value within the range 
		   { minLength ... maxLength } but there isn't any length present.  
		   To deal with this we return a fake length equal to minLength, 
		   which means that we stick to the contract, and which will be 
		   ignored by any caller that can process compressed data since, by 
		   definition, the length value is meaningless */
		if( ctb != NULL )
			*ctb = localCTB;
		if( length != NULL )
			*length = minLength;	/* See comment above */
		return( CRYPT_OK );	/* Not-really-indef. return status */
		}

	/* Now that we know the format, get the length information */
	status = pgpReadLength( stream, &localLength, localCTB,
							minLength, maxLength, indefOK );
	if( cryptStatusError( status ) )
		{
		int type;

		if( status != OK_SPECIAL )
			return( status );

		/* It's an indefinite-length encoding, this is only valid for 
		   payload data packets (RFC 4880 section 4.2.2.4) so we make sure 
		   that we've got one of these packet types present */
		ENSURES_S( indefOK );
		type = pgpGetPacketType( localCTB );
		if( type != PGP_PACKET_DATA && type != PGP_PACKET_COPR && \
			type != PGP_PACKET_ENCR && type != PGP_PACKET_ENCR_MDC )
			return( sSetError( stream, CRYPT_ERROR_BADDATA ) );
		
		/* localLength at this point is first partial length, not the 
		   overall data length, which we indicate to the caller through the 
		   OK_SPECIAL return status which passes through to the following
		   code */
		}
	if( ctb != NULL )
		*ctb = localCTB;
	if( length != NULL )
		*length = localLength;

	return( status );
	}

CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2 ) ) \
int pgpReadShortLength( INOUT_PTR STREAM *stream, 
						OUT_LENGTH_SHORT_Z int *length, 
						IN_BYTE const int ctb )
	{
	int localLength, status;

	assert( isWritePtr( stream, sizeof( STREAM ) ) );

	/* Clear return value */
	*length = 0;

	status = pgpReadLength( stream, &localLength, ctb, 0, 
							MAX_INTLENGTH_SHORT, FALSE );
	if( cryptStatusError( status ) )
		return( status );
	if( !isShortIntegerRange( localLength ) )
		return( sSetError( stream, CRYPT_ERROR_BADDATA ) );
	*length = ( int ) localLength;

	return( CRYPT_OK );
	}

CHECK_RETVAL STDC_NONNULL_ARG( ( 1 ) ) \
int pgpReadPacketHeader( INOUT_PTR STREAM *stream, OUT_OPT_BYTE int *ctb, 
						 OUT_OPT_LENGTH_Z int *length, 
						 IN_LENGTH_SHORT const int minLength,
						 IN_LENGTH const int maxLength )
	{
	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	assert( ctb == NULL || isWritePtr( ctb, sizeof( int ) ) );
	assert( length == NULL || isWritePtr( length, sizeof( int ) ) );
	
	REQUIRES_S( isShortIntegerRange( minLength ) && \
				maxLength > minLength && maxLength < MAX_INTLENGTH );

	return( readPacketHeader( stream, ctb, length, minLength, maxLength, 
							  FALSE ) );
	}

CHECK_RETVAL_SPECIAL STDC_NONNULL_ARG( ( 1 ) ) \
int pgpReadPacketHeaderI( INOUT_PTR STREAM *stream, OUT_OPT_BYTE int *ctb, 
						  OUT_OPT_LENGTH_Z int *length, 
						  IN_LENGTH_SHORT const int minLength )
	{
	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	assert( ctb == NULL || isWritePtr( ctb, sizeof( int ) ) );
	assert( length == NULL || isWritePtr( length, sizeof( int ) ) );
	
	REQUIRES_S( isShortIntegerRange( minLength ) );

	return( readPacketHeader( stream, ctb, length, minLength, 
							  MAX_INTLENGTH - 1, TRUE ) );
	}

CHECK_RETVAL_SPECIAL STDC_NONNULL_ARG( ( 1, 2 ) ) \
int pgpReadPartialLength( INOUT_PTR STREAM *stream, 
						  OUT_LENGTH_Z int *length )
	{
	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	assert( isWritePtr( length, sizeof( int ) ) );
	
	/* This is a raw length value so we have to feed in a pseudo-CTB */
	return( pgpReadLength( stream, length, PGP_CTB_OPENPGP,
						   0, MAX_INTLENGTH - 1, TRUE ) );
	}

/* Write PGP variable-length length values and packet headers (CTB + 
   length) */

RETVAL STDC_NONNULL_ARG( ( 1 ) ) \
int pgpWriteLength( INOUT_PTR STREAM *stream, 
					IN_LENGTH const int length )
	{
	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	
	REQUIRES_S( isIntegerRangeNZ( length ) );

	/* A second check for systems with 64-bit longs, for which values over 
	   4GB would get truncated, as well as 32-bit longs, for which shifting
	   sign bits causes gcc to throw a wobbly.  Since we should never get
	   lengths of this size we make it a REQUIRES() rather than a parameter 
	   check */
	REQUIRES_S( length < 0x7FFFFFFFL );

	if( length <= 191 )
		return( sputc( stream, length ) );
	if( length <= 8383 )
		{
		const int adjustedLength = length - 192;

		sputc( stream, ( ( adjustedLength >> 8 ) & 0xFF ) + 192 );
		return( sputc( stream, ( adjustedLength & 0xFF ) ) );
		}
	sputc( stream, 0xFF );
	sputc( stream, intToByte( length >> 24 ) );
	sputc( stream, intToByte( length >> 16 ) );
	sputc( stream, intToByte( length >> 8 ) );
	return( sputc( stream, intToByte( length ) ) );
	}

CHECK_RETVAL STDC_NONNULL_ARG( ( 1 ) ) \
int pgpWritePacketHeader( INOUT_PTR STREAM *stream, 
						  IN_ENUM( PGP_PACKET ) const PGP_PACKET_TYPE packetType,
						  IN_LENGTH const int length )
	{
	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	
	REQUIRES_S( isEnumRange( packetType, PGP_PACKET ) );
	REQUIRES_S( isIntegerRangeNZ( length ) );

	sputc( stream, PGP_CTB_OPENPGP | packetType );
	return( pgpWriteLength( stream, length ) );
	}
#endif /* USE_PGP || USE_PGPKEYS */
