📄 cmp_wr.c
字号:
/****************************************************************************
* *
* Write CMP Messages *
* Copyright Peter Gutmann 1999-2003 *
* *
****************************************************************************/
#include <stdlib.h>
#include <string.h>
#if defined( INC_ALL )
#include "crypt.h"
#include "asn1.h"
#include "asn1_ext.h"
#include "session.h"
#include "cmp.h"
#elif defined( INC_CHILD )
#include "../crypt.h"
#include "../misc/asn1.h"
#include "../misc/asn1_ext.h"
#include "session.h"
#include "cmp.h"
#else
#include "crypt.h"
#include "misc/asn1.h"
#include "misc/asn1_ext.h"
#include "session/session.h"
#include "session/cmp.h"
#endif /* Compiler-specific includes */
/* The CMP message header includes a large amount of ambiguous, confusing,
and redundant information, we remove all the unnecessary junk required by
CMP by only sending the fields that are actually useful. Fields that are
completely pointless or can't be provided (sender and recipient DN,
nonces) are omitted entirely, fields that remain static throughout an
exchange (user ID info) are only sent in the first message and are
assumed to be the same as for the previous message if absent. The
general schema for message fields during various sample exchanges is:
ir: transID userID-user mac-param clibID
ip: transID mac clibID
cr: transID sig clibID certID-user
cp: transID sig clibID certID-CA
ir: transID userID-user mac-param clibID
ip: transID mac clibID
ir: transID mac
ip: transID mac
ir: transID userID-user mac-param clibID
ip: transID mac clibID
cr: transID sig certID-user
cp: transID sig certID-CA
genm: transID userID-user mac-param clibID
genp: transID mac clibID certID-CA
ir: transID mac
ip: transID mac
cr: transID sig certID-user
cp: transID sig
The transID (= nonce) is sent in all messages. The user ID, cert ID,
and MAC parameters are sent once, if absent they're assumed to be "same
as previous" (in the case of the MAC parameters we simply send the MAC
OID with NULL parameters to indicate no change). The cryptlib ID is sent
in the first message only.
The sending of the CA cert ID in the PKIBoot response even though the
response is MAC'd is necessary because we need this value to identify
which of the certs in the CTL is the CA/RA cert to be used for further
exchanges. There are actually several ways in which we can identify
the cert:
1. PKIBoot response is a CTL, CA cert is implicitly trusted (via the CTL).
Issues: Mostly an implementation issue, we need to provide a CA cert
when we activate the session, not having this requires special-case
handling in the CMP startup code to check for an implicitly-trusted
cert if a CA cert isn't explicitly provided. In addition there
currently isn't a means of fetching a trusted cert based on its cert
ID, only of querying whether a cert is trusted or fetching a trusted
issuer cert for an existing cert.
2. PKIBoot response is a CTL, userID identifies the CA cert.
Issues: The userID is probably only meant to identify the
authenticator of the message (the spec is, as usual, unclear on
this), not a random cert located elsewhere.
3. PKIBoot response is a CTL, certID identifies the CA cert.
Issues: A less serious version of (2) above, we're overloading the
certID to some extent but since it doesn't affect CMP messages as a
whole (as overloading the userID would) this is probably OK.
4. PKIBoot response is SignedData, signer is CA cert.
Issues: Mostly nitpicks, we should probably only be sending a pure
CTL rather than signed data, and the means of identifying the CA
cert seems a bit clunky. On one hand it provides POP of the CA key
at the PKIBoot stage, but on the other it requires a signing
operation for every PKIBoot exchange, which can get rather
heavyweight if clients use it in a DHCP-like manner every time they
start up. In addition it requires a general-purpose signature-
capable CA key, which often isn't the case if it's reserved
specifically for cert and CRL signing.
Enabling the following define forces the use of full headers at all times.
cryptlib always sends minimal headers once it detects that the other side
is using cryptlib, ommitting as much of the unnecessary junk as possible,
which significantly reduces the overall message size */
/* #define USE_FULL_HEADERS */
/* Prototypes for functions in lib_sign.c */
int createRawSignature( void *signature, int *signatureLength,
const int sigMaxLength,
const CRYPT_CONTEXT iSignContext,
const CRYPT_CONTEXT iHashContext );
#ifdef USE_CMP
/****************************************************************************
* *
* Utility Routines *
* *
****************************************************************************/
/* Write full cert ID info. This is written as an attribute in the
generalInfo field of the message header to allow unambiguous
identification of the signing cert, which the standard CMP format can't
do. Although CMP uses a gratuitously incompatible definition of the
standard attribute type (calling it InfoTypeAndValue), it's possible to
shoehorn a standard attribute type in by taking the "ANY" in "ANY DEFINED
BY x" to mean "SET OF AttributeValue" (for once the use of obsolete ASN.1
is a help, since it's so imprecise that we can shovel in anything and it's
still valid):
SigningCertificate ::= SEQUENCE {
certs SEQUENCE OF ESSCertID -- Size (1)
}
ESSCertID ::= SEQUENCE {
certID OCTET STRING
}
All we really need is the cert ID, so instead of writing a full ESSCertID
(which also contains an optional incompatible reinvention of the CMS
IssuerAndSerialNumber) we write the sole mandatory field, the cert hash,
which also keeps the overall size down */
static int writeCertID( STREAM *stream, const CRYPT_CONTEXT iCryptCert )
{
RESOURCE_DATA msgData;
BYTE certHash[ CRYPT_MAX_HASHSIZE ];
int essCertIDSize, payloadSize, status;
/* Find out how big the payload will be */
setMessageData( &msgData, certHash, CRYPT_MAX_HASHSIZE );
status = krnlSendMessage( iCryptCert, IMESSAGE_GETATTRIBUTE_S,
&msgData, CRYPT_CERTINFO_FINGERPRINT_SHA );
if( cryptStatusError( status ) )
return( status );
essCertIDSize = ( int ) sizeofObject( msgData.length );
payloadSize = objSize( objSize( objSize( essCertIDSize ) ) );
/* If we've been passed a null stream, it's a size request only */
if( stream == NULL )
return( objSize( sizeofOID( OID_ESS_CERTID ) + \
sizeofObject( payloadSize ) ) );
/* Write the signing cert ID info */
writeSequence( stream, sizeofOID( OID_ESS_CERTID ) + \
( int ) sizeofObject( payloadSize ) );
writeOID( stream, OID_ESS_CERTID );
writeSet( stream, payloadSize );
writeSequence( stream, objSize( objSize( essCertIDSize ) ) );
writeSequence( stream, objSize( essCertIDSize ) );
writeSequence( stream, essCertIDSize );
return( writeOctetString( stream, certHash, msgData.length,
DEFAULT_TAG ) );
}
/* Write PKIStatus information:
PKIStatusInfo ::= SEQUENCE {
status INTEGER,
statusString SEQUENCE OF UTF8String OPTIONAL,
failInfo BIT STRING OPTIONAL
} */
static int writePkiStatusInfo( STREAM *stream, const int pkiStatus,
const long pkiFailureInfo )
{
const long localPKIFailureInfo = \
( pkiFailureInfo != CMPFAILINFO_OK ) ? pkiFailureInfo : \
( pkiStatus == CRYPT_ERROR_NOTAVAIL ) ? CMPFAILINFO_BADALG : \
( pkiStatus == CRYPT_ERROR_SIGNATURE ) ? CMPFAILINFO_BADMESSAGECHECK : \
( pkiStatus == CRYPT_ERROR_PERMISSION ) ? CMPFAILINFO_BADREQUEST : \
( pkiStatus == CRYPT_ERROR_BADDATA ) ? CMPFAILINFO_BADDATAFORMAT : \
( pkiStatus == CRYPT_ERROR_INVALID ) ? CMPFAILINFO_BADCERTTEMPLATE : \
( pkiStatus == CRYPT_ERROR_DUPLICATE ) ? CMPFAILINFO_DUPLICATECERTREQ : \
( pkiStatus == CRYPT_ERROR_WRONGKEY ) ? CMPFAILINFO_SIGNERNOTTRUSTED : \
0;
const int length = \
sizeofShortInteger( PKISTATUS_REJECTED ) + \
( localPKIFailureInfo ? sizeofBitString( localPKIFailureInfo ) : 0 );
int status;
/* If we've been passed a null stream, it's a size request only */
if( stream == NULL )
return( objSize( length ) );
/* Write the error status info. If there's a specific failure info code
set by the caller we use that, otherwise we try and convert the
cryptlib status into an appropriate failure info value */
writeSequence( stream, length );
status = writeShortInteger( stream, PKISTATUS_REJECTED, DEFAULT_TAG );
if( localPKIFailureInfo )
status = writeBitString( stream, localPKIFailureInfo, DEFAULT_TAG );
return( status );
}
/* Write the CMP/Entrust MAC information:
macInfo ::= SEQUENCE {
algoID OBJECT IDENTIFIER (entrustMAC),
algoParams SEQUENCE {
salt OCTET STRING,
pwHashAlgo AlgorithmIdentifier (SHA-1)
iterations INTEGER,
macAlgo AlgorithmIdentifier (HMAC-SHA1)
} OPTIONAL
} */
static int writeMacInfo( STREAM *stream,
const CMP_PROTOCOL_INFO *protocolInfo,
const BOOLEAN parametersSent )
{
int paramSize;
/* If we've already sent the MAC parameters in an earlier transaction,
just send an indication that we're using MAC protection */
if( parametersSent )
{
writeSequence( stream, sizeofOID( OID_ENTRUST_MAC ) + sizeofNull() );
writeOID( stream, OID_ENTRUST_MAC );
return( writeNull( stream, DEFAULT_TAG ) );
}
/* Determine how big the payload will be */
paramSize = ( int ) sizeofObject( protocolInfo->saltSize ) + \
sizeofAlgoID( CRYPT_ALGO_SHA ) + \
sizeofShortInteger( CMP_PASSWORD_ITERATIONS ) + \
sizeofAlgoID( CRYPT_ALGO_HMAC_SHA );
/* Write the wrapper */
writeSequence( stream, sizeofOID( OID_ENTRUST_MAC ) + \
( int ) sizeofObject( paramSize ) );
writeOID( stream, OID_ENTRUST_MAC );
/* Write the payload */
writeSequence( stream, paramSize );
writeOctetString( stream, protocolInfo->salt, protocolInfo->saltSize,
DEFAULT_TAG );
writeAlgoID( stream, CRYPT_ALGO_SHA );
writeShortInteger( stream, CMP_PASSWORD_ITERATIONS, DEFAULT_TAG );
return( writeAlgoID( stream, CRYPT_ALGO_HMAC_SHA ) );
}
/****************************************************************************
* *
* PKI Body Functions *
* *
****************************************************************************/
/* Write request body */
static int writeRequestBody( STREAM *stream,
const SESSION_INFO *sessionInfoPtr,
const CMP_PROTOCOL_INFO *protocolInfo )
{
const CRYPT_CERTFORMAT_TYPE certType = \
( protocolInfo->operation == CTAG_PB_RR ) ? \
CRYPT_ICERTFORMAT_DATA : CRYPT_CERTFORMAT_CERTIFICATE;
RESOURCE_DATA msgData;
int status;
UNUSED( protocolInfo );
/* Find out how big the payload will be. Since revocation requests are
unsigned entities we have to vary the attribute type that we're
reading based on whether we're submitting a signed or unsigned object
in the request */
setMessageData( &msgData, NULL, 0 );
status = krnlSendMessage( sessionInfoPtr->iCertRequest,
IMESSAGE_CRT_EXPORT, &msgData, certType );
if( cryptStatusError( status ) )
return( status );
/* Write the request body */
writeConstructed( stream, objSize( msgData.length ),
protocolInfo->operation );
writeSequence( stream, msgData.length );
return( exportCertToStream( stream, sessionInfoPtr->iCertRequest,
certType ) );
}
/* Write response body. If we're returning an encryption-only cert, we send
it as standard CMS data under a new tag to avoid having to hand-assemble
the garbled mess that CMP uses for this */
static int writeResponseBody( STREAM *stream,
const SESSION_INFO *sessionInfoPtr,
const CMP_PROTOCOL_INFO *protocolInfo )
{
RESOURCE_DATA msgData;
const int startPos = stell( stream );
int payloadSize = sizeofShortInteger( 0 ), dataLength, status;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -