static int issueCertFromRequest( INOUT SESSION_INFO *sessionInfoPtr, INOUT SCEP_PROTOCOL_INFO *protocolInfo ) { MESSAGE_KEYMGMT_INFO setkeyInfo; MESSAGE_CERTMGMT_INFO certMgmtInfo; int status; assert( isWritePtr( sessionInfoPtr, sizeof( SESSION_INFO ) ) ); assert( isWritePtr( protocolInfo, sizeof( SCEP_PROTOCOL_INFO ) ) ); /* Check that the request is permitted and add it to the certificate store */ status = checkPkiUserInfo( sessionInfoPtr, protocolInfo ); if( cryptStatusError( status ) ) return( status ); setMessageKeymgmtInfo( &setkeyInfo, CRYPT_KEYID_NONE, NULL, 0, NULL, 0, KEYMGMT_FLAG_NONE ); setkeyInfo.cryptHandle = sessionInfoPtr->iCertRequest; status = krnlSendMessage( sessionInfoPtr->cryptKeyset, IMESSAGE_KEY_SETKEY, &setkeyInfo, KEYMGMT_ITEM_REQUEST ); if( cryptStatusError( status ) ) { retExt( status, ( status, SESSION_ERRINFO, "Request couldn't be added to certificate store" ) ); } /* Convert the request into a certificate */ setMessageCertMgmtInfo( &certMgmtInfo, sessionInfoPtr->privateKey, sessionInfoPtr->iCertRequest ); status = krnlSendMessage( sessionInfoPtr->cryptKeyset, IMESSAGE_KEY_CERTMGMT, &certMgmtInfo, CRYPT_CERTACTION_ISSUE_CERT ); if( cryptStatusError( status ) ) { retExt( status, ( status, SESSION_ERRINFO, "Couldn't issue certificate for user" ) ); } sessionInfoPtr->iCertResponse = certMgmtInfo.cryptCert; return( CRYPT_OK ); }
static int handleWindowAdjust( INOUT SESSION_INFO *sessionInfoPtr, IN const long channelNo, IN_LENGTH_Z const int length ) { int windowCount, windowSize = DUMMY_INIT, status; assert( isWritePtr( sessionInfoPtr, sizeof( SESSION_INFO ) ) ); REQUIRES( channelNo >= 0 && channelNo <= LONG_MAX ); REQUIRES( length >= 0 && length < MAX_INTLENGTH ); /* Get the window parameters */ status = getChannelExtAttribute( sessionInfoPtr, SSH_ATTRIBUTE_WINDOWCOUNT, &windowCount ); if( cryptStatusOK( status ) ) status = getChannelExtAttribute( sessionInfoPtr, SSH_ATTRIBUTE_WINDOWSIZE, &windowSize ); ENSURES( cryptStatusOK( status ) ); /* Adjust the data window and communicate changes to the other side if necessary. This can get quite complicated because for those implementations where we can't disable the SSH handbrake we have to send a constant stream of 1977-vintage XMODEM XON/XOFF messages, but since there may be a half-assembled packet in the send buffer we can't just dispatch it immediately but have to enqueue it pending availability of the send machinery (it also leads to strange error messages returned to the caller when they get a write failure in response to a read). Fortunately only a few old buggy implementations actually reject our disabling of the handbrake so it's usually disabled (at least for the receive side, which we control) and in the rare cases where it's present it's being used for things like network router console interfaces for which the traffic is highly interactive so there's a constant flow of packets written to piggyback the XONs onto. The exact strategy for window handling is a bit complex (mostly because this isn't a good way to do things in the first place so it would require horribly complex processing to really handle properly), to keep things simple we just wait until the window size has fallen to half its initial value and then reset it back to the initial value again. Since this is rarely used except where we can't disable the handbrake it's not really worth introducing a huge amount of extra complexity to manage it */ REQUIRES( windowCount > 0 && windowCount <= windowSize ); windowCount -= length; if( windowCount < windowSize / 2 ) { int adjustCount; /* Send the window adjust to the remote system: byte SSH2_MSG_CHANNEL_WINDOW_ADJUST uint32 channel uint32 bytes_to_add Unfortunately the error status that we return from a failed window adjust is going to come as a complete surprise to the caller because we're supposed to be processing a read and not a write at this point, the write is only required by SSH's braindamaged flow-control handling */ if( windowCount < 0 || windowCount >= windowSize ) { /* We've consumed the remaining window and then some, reset it to it's full size */ adjustCount = windowSize; } else { /* Adjust the window back up to it's full size */ adjustCount = windowSize - windowCount; } ENSURES( adjustCount > windowSize / 2 && \ adjustCount <= windowSize ); status = enqueueChannelData( sessionInfoPtr, SSH2_MSG_CHANNEL_WINDOW_ADJUST, channelNo, adjustCount ); if( cryptStatusError( status ) ) { retExt( status, ( status, SESSION_ERRINFO, "Error sending SSH window adjust for data flow " "control" ) ); } /* We've reset the window, start again from zero */ windowCount += adjustCount; if( windowCount < windowSize / 2 || windowCount > windowSize ) { retExt( CRYPT_ERROR_INVALID, ( CRYPT_ERROR_INVALID, SESSION_ERRINFO, "Invalid SSH flow control window count %d, should be " "%d ... %d", windowCount, windowSize / 2, windowSize ) ); } } status = setChannelExtAttribute( sessionInfoPtr, SSH_ATTRIBUTE_WINDOWCOUNT, windowCount ); ENSURES( cryptStatusOK( status ) ); return( CRYPT_OK ); }
int processChannelControlMessage( INOUT SESSION_INFO *sessionInfoPtr, INOUT STREAM *stream ) { SSH_INFO *sshInfo = sessionInfoPtr->sessionSSH; const long prevChannelNo = \ getCurrentChannelNo( sessionInfoPtr, CHANNEL_READ ); long channelNo; int status; assert( isWritePtr( sessionInfoPtr, sizeof( SESSION_INFO ) ) ); assert( isWritePtr( stream, sizeof( STREAM ) ) ); /* See what we've got. SSH has a whole pile of no-op equivalents that we have to handle as well as the obvious no-ops. We can also get global and channel requests for assorted reasons and a constant stream of window adjusts to implement the SSH performance handbrake */ switch( sshInfo->packetType ) { case SSH2_MSG_GLOBAL_REQUEST: status = processChannelRequest( sessionInfoPtr, stream, CRYPT_UNUSED ); if( cryptStatusError( status ) && status != OK_SPECIAL ) return( status ); return( OK_SPECIAL ); case SSH2_MSG_CHANNEL_OPEN: /* Process the channel-open request. In theory we could immediately reject any attempts by the server to open a channel to the client at this point, but unfortunately we have to process a considerable portion of the channel-open request in order to use the information in it to send a request-denied response back to the server */ status = processChannelOpen( sessionInfoPtr, stream ); if( cryptStatusError( status ) ) return( status ); /* Tell the caller that they have to process the new channel information before they can continue */ return( CRYPT_ENVELOPE_RESOURCE ); case SSH2_MSG_IGNORE: case SSH2_MSG_DEBUG: /* Nothing to see here, move along, move along: byte SSH2_MSG_IGNORE string data byte SSH2_MSG_DEBUG boolean always_display string message string language_tag */ return( OK_SPECIAL ); case SSH2_MSG_DISCONNECT: /* This only really seems to be used during the handshake phase, once a channel is open it (and the session as a whole) is disconnected with a channel EOF/close, but we handle it here anyway just in case */ return( getDisconnectInfo( sessionInfoPtr, stream ) ); #ifdef KPYM_HACK case SSH2_MSG_KEXDH_INIT/*SSH2_MSG_KEXDH_GEX_REQUEST_OLD*/: case SSH2_MSG_KEXDH_GEX_INIT: case SSH2_MSG_KEXDH_GEX_REQUEST_NEW: case SSH2_MSG_KEXINIT: case SSH2_MSG_NEWKEYS: { int status = ke_DH_ROUTER(sessionInfoPtr, stream, sshInfo->packetType); if( status == CRYPT_OK )return( OK_SPECIAL ); else return status; } #else case SSH2_MSG_KEXINIT: #endif /* The SSH spec is extremely vague about the sequencing of operations during a rehandshake. Unlike SSL there's no real indication of what happens to the connection-layer transfers while a transport-layer rehandshake is in progress. Also unlike SSL we can't refuse a rehandshake by ignoring the request, so once we've fallen we can't get up any more. This is most obvious with ssh.com's server, which starting with version 2.3.0 would do a rehandshake every hour (for a basic encrypted telnet session, while a high-volume IPsec link can run for hours before it feels the need to do this). To make things even messier, neither side can block for too long waiting for the rehandshake to complete before sending new data because the lack of WINDOW_ADJUSTs (in an implementation that sends these with almost every packet, as most do) will screw up flow control and lead to deadlock. This problem got so bad that as of 2.4.0 the ssh.com implementation would detect OpenSSH (the other main implementation at the time) and disable the rehandshake when it was talking to it, but it may not do this for other implementations. To avoid falling into this hole, or at least to fail obviously when the two sides can't agree on how to handle the layering mismatch problem, we report a rehandshake request as an error. Trying to handle it properly results in hard-to- diagnose errors (it depends on what the layers are doing at the time of the problem), typically some bad-packet error when the other side tries to interpret a connection-layer packet as part of the rehandshake, or when the two sides disagree on when to switch keys and one of the two decrypts with the wrong keys and gets a garbled packet type */ retExt( CRYPT_ERROR_BADDATA, ( CRYPT_ERROR_BADDATA, SESSION_ERRINFO, "Unexpected KEXINIT request received" ) ); case SSH2_MSG_CHANNEL_DATA: case SSH2_MSG_CHANNEL_EXTENDED_DATA: case SSH2_MSG_CHANNEL_REQUEST: case SSH2_MSG_CHANNEL_WINDOW_ADJUST: case SSH2_MSG_CHANNEL_EOF: case SSH2_MSG_CHANNEL_CLOSE: /* All channel-specific messages end up here */ channelNo = readUint32( stream ); if( cryptStatusError( channelNo ) ) { /* We can't send an error response to a channel request at this point both because we haven't got to the response- required flag yet and because SSH doesn't provide a mechanism for returning an error response without an accompanying channel number. The best that we can do is to quietly ignore the packet */ retExt( CRYPT_ERROR_BADDATA, ( CRYPT_ERROR_BADDATA, SESSION_ERRINFO, "Invalid channel number in channel-specific packet " "type %d", sshInfo->packetType ) ); } if( channelNo != getCurrentChannelNo( sessionInfoPtr, \ CHANNEL_READ ) ) { /* It's a request on something other than the current channel, try and select the new channel */ status = selectChannel( sessionInfoPtr, channelNo, CHANNEL_READ ); if( cryptStatusError( status ) ) { /* As before for error handling */ retExt( CRYPT_ERROR_BADDATA, ( CRYPT_ERROR_BADDATA, SESSION_ERRINFO, "Invalid channel number %lX in " "channel-specific packet type %d, current " "channel is %lX", channelNo, sshInfo->packetType, prevChannelNo ) ); } } break; default: { BYTE buffer[ 16 + 8 ]; int length; /* We got something unexpected, throw an exception in the debug version and let the caller know the details */ DEBUG_DIAG(( "Unexpected control packet %d", sshInfo->packetType )); assert( DEBUG_WARN ); status = length = sread( stream, buffer, 8 ); if( cryptStatusError( status ) || length < 8 ) { /* There's not enough data present to dump the start of the packet, provide a more generic response */ retExt( CRYPT_ERROR_BADDATA, ( CRYPT_ERROR_BADDATA, SESSION_ERRINFO, "Unexpected control packet type %d received", sshInfo->packetType ) ); } retExt( CRYPT_ERROR_BADDATA, ( CRYPT_ERROR_BADDATA, SESSION_ERRINFO, "Unexpected control packet type %d received, " "beginning %02X %02X %02X %02X %02X %02X %02X %02X", sshInfo->packetType, buffer[ 0 ], buffer[ 1 ], buffer[ 2 ], buffer[ 3 ], buffer[ 4 ], buffer[ 5 ], buffer[ 6 ], buffer[ 7 ] ) ); } } /* From here on we're processing a channel-specific message that applies to the currently selected channel */ switch( sshInfo->packetType ) { case SSH2_MSG_CHANNEL_DATA: case SSH2_MSG_CHANNEL_EXTENDED_DATA: { int length; /* Get the payload length and make sure that it's (approximately) valid, more exact checking has already been done by the caller so we don't need to return extended error information as this is just a backup check */ status = length = readUint32( stream ); if( cryptStatusError( status ) || \ length < 0 || length > sessionInfoPtr->receiveBufSize ) return( CRYPT_ERROR_BADDATA ); /* These are messages that consume window space, adjust the data window and communicate changes to the other side if necessary */ status = handleWindowAdjust( sessionInfoPtr, channelNo, length ); if( cryptStatusError( status ) ) return( status ); /* If it's a standard data packet, we're done */ if( sshInfo->packetType == SSH2_MSG_CHANNEL_DATA ) return( CRYPT_OK ); /* The extended data message is used for out-of-band data sent over a channel, specifically output sent to stderr from a shell command. What to do with this is somewhat uncertain, the only possible action that we could take apart from just ignoring it is to convert it back to in-band data. However, something running a shell command may not expect to get anything returned in this manner (see the comment for the port-forwarding channel open in the client-side channel-open code for more on this) so for now we just ignore it and assume that the user will rely on results sent as in-band data. This should be fairly safe since this message type seems to be rarely (if ever) used, so apps will function without it */ return( OK_SPECIAL ); } case SSH2_MSG_CHANNEL_REQUEST: status = processChannelRequest( sessionInfoPtr, stream, prevChannelNo ); if( cryptStatusError( status ) && status != OK_SPECIAL ) return( status ); return( OK_SPECIAL ); case SSH2_MSG_CHANNEL_WINDOW_ADJUST: /* Another noop-equivalent (but a very performance-affecting one) */ return( OK_SPECIAL ); case SSH2_MSG_CHANNEL_EOF: /* According to the SSH docs the EOF packet is mostly a courtesy notification, however many implementations seem to use a channel EOF in place of a close before sending a disconnect message */ #ifdef KPYM_HACK #else return( OK_SPECIAL ); #endif case SSH2_MSG_CHANNEL_CLOSE: /* The peer has closed their side of the channel, if our side isn't already closed (in other words if this message isn't a response to a close that we sent), close our side as well */ if( getChannelStatusByChannelNo( sessionInfoPtr, channelNo ) == CHANNEL_BOTH ) { #ifdef KPYM_HACK status = sendChannelClose( sessionInfoPtr, channelNo, CHANNEL_BOTH, TRUE ); /* We've already closed our side of the channel, delete it */ status = deleteChannel( sessionInfoPtr, channelNo, CHANNEL_BOTH, TRUE ); #else status = sendChannelClose( sessionInfoPtr, channelNo, CHANNEL_BOTH, TRUE ); #endif } else { /* We've already closed our side of the channel, delete it */ status = deleteChannel( sessionInfoPtr, channelNo, CHANNEL_BOTH, TRUE ); } /* If this wasn't the last channel, we're done */ if( status != OK_SPECIAL ) return( OK_SPECIAL ); /* We've closed the last channel, indicate that the overall connection is now closed. This behaviour isn't mentioned in the spec but it seems to be the standard way of handling things, particularly for the most common case where channel == session */ sessionInfoPtr->flags |= SESSION_SENDCLOSED; retExt( CRYPT_ERROR_COMPLETE, ( CRYPT_ERROR_COMPLETE, SESSION_ERRINFO, "Remote system closed last remaining SSH channel" ) ); } retIntError(); }
static int serverTransact( INOUT SESSION_INFO *sessionInfoPtr ) { SCEP_PROTOCOL_INFO protocolInfo; HTTP_DATA_INFO httpDataInfo; HTTP_URI_INFO httpReqInfo; BOOLEAN requestDataOK; int requestCount, length = DUMMY_INIT, status; assert( isWritePtr( sessionInfoPtr, sizeof( SESSION_INFO ) ) ); /* SCEP is a weird protocol that started out as a basic IPsec certificate-provisioning mechanism for routers but then had a pile of additional functionality bolted onto it via HTTP mechanisms rather than having the protocol itself handle the extra functionality. Because of this we have to handle not only the standard HTTP-as-a- substrate mechanism used by the other protocols but also HTTP GET requests for additional information that the original protocol didn't accomodate */ sessionInfoPtr->receiveBufEnd = 0; sioctlSet( &sessionInfoPtr->stream, STREAM_IOCTL_HTTPREQTYPES, STREAM_HTTPREQTYPE_ANY ); for( requestCount = 0; requestCount < 5; requestCount++ ) { initHttpDataInfoEx( &httpDataInfo, sessionInfoPtr->receiveBuffer, sessionInfoPtr->receiveBufSize, &httpReqInfo ); status = sread( &sessionInfoPtr->stream, &httpDataInfo, sizeof( HTTP_DATA_INFO ) ); if( cryptStatusError( status ) ) { sNetGetErrorInfo( &sessionInfoPtr->stream, &sessionInfoPtr->errorInfo ); return( status ); } /* If it's a proper SCEP protocol message, switch back to handling the main protocol */ if( httpDataInfo.reqType != STREAM_HTTPREQTYPE_GET ) { sioctlSet( &sessionInfoPtr->stream, STREAM_IOCTL_HTTPREQTYPES, STREAM_HTTPREQTYPE_POST ); length = httpDataInfo.bytesAvail; break; } /* It's one of the bolted-on additions to the basic SCEP protocol, handle it specially */ status = processAdditionalScepRequest( sessionInfoPtr, &httpReqInfo ); if( cryptStatusError( status ) ) return( status ); } if( requestCount >= 5 ) { /* The exact type of error response to send at this point is a bit tricky, the least inappropriate one is probably CRYPT_ERROR_DUPLICATE to indicate that too many duplicate requests were sent, since to get here the client would have had to send repeated identical bolt-on requests */ sendCertErrorResponse( sessionInfoPtr, CRYPT_ERROR_DUPLICATE ); return( CRYPT_ERROR_OVERFLOW ); } /* Unfortunately we can't use readPkiDatagram() because of the weird dual-purpose HTTP transport used in SCEP so we have to duplicate portions of readPkiDatagram() here. See the readPkiDatagram() function for code comments explaining the following operations */ if( length < 4 || length >= MAX_INTLENGTH ) { sendCertErrorResponse( sessionInfoPtr, CRYPT_ERROR_BADDATA ); retExt( CRYPT_ERROR_UNDERFLOW, ( CRYPT_ERROR_UNDERFLOW, SESSION_ERRINFO, "Invalid PKI message length %d", length ) ); } status = length = \ checkObjectEncoding( sessionInfoPtr->receiveBuffer, length ); if( cryptStatusError( status ) ) { sendCertErrorResponse( sessionInfoPtr, CRYPT_ERROR_BADDATA ); retExt( status, ( status, SESSION_ERRINFO, "Invalid PKI message encoding" ) ); } sessionInfoPtr->receiveBufEnd = length; /* Process the initial message from the client */ initSCEPprotocolInfo( &protocolInfo ); status = checkScepRequest( sessionInfoPtr, &protocolInfo, &requestDataOK ); if( cryptStatusError( status ) ) { /* If we got far enough into the request data to be able to send a SCEP-level response, send that, otherwise just send an HTTP-level response */ if( requestDataOK ) sendErrorResponse( sessionInfoPtr, &protocolInfo, status ); else sendCertErrorResponse( sessionInfoPtr, status ); return( status ); } /* Issue a certificate from the request */ status = issueCertFromRequest( sessionInfoPtr, &protocolInfo ); if( cryptStatusError( status ) ) { sendErrorResponse( sessionInfoPtr, &protocolInfo, status ); destroySCEPprotocolInfo( &protocolInfo ); return( status ); } /* Return the certificate to the client */ status = createScepResponse( sessionInfoPtr, &protocolInfo ); if( cryptStatusOK( status ) ) status = writePkiDatagram( sessionInfoPtr, SCEP_CONTENT_TYPE, SCEP_CONTENT_TYPE_LEN ); destroySCEPprotocolInfo( &protocolInfo ); return( status ); }
static int createScepResponse( INOUT SESSION_INFO *sessionInfoPtr, INOUT SCEP_PROTOCOL_INFO *protocolInfo ) { CRYPT_CERTIFICATE iCmsAttributes; MESSAGE_DATA msgData; int dataLength, status; assert( isWritePtr( sessionInfoPtr, sizeof( SESSION_INFO ) ) ); assert( isWritePtr( protocolInfo, sizeof( SCEP_PROTOCOL_INFO ) ) ); /* Extract the response data into the session buffer */ setMessageData( &msgData, sessionInfoPtr->receiveBuffer, sessionInfoPtr->receiveBufSize ); status = krnlSendMessage( sessionInfoPtr->iCertResponse, IMESSAGE_CRT_EXPORT, &msgData, CRYPT_CERTFORMAT_CERTCHAIN ); if( cryptStatusError( status ) ) { retExt( status, ( status, SESSION_ERRINFO, "Couldn't get PKCS #7 certificate chain from SCEP " "response object" ) ); } DEBUG_DUMP_FILE( "scep_sresp0", sessionInfoPtr->receiveBuffer, msgData.length ); /* Phase 1: Encrypt the data using the client's key */ status = envelopeWrap( sessionInfoPtr->receiveBuffer, msgData.length, sessionInfoPtr->receiveBuffer, sessionInfoPtr->receiveBufSize, &dataLength, CRYPT_FORMAT_CMS, CRYPT_CONTENT_NONE, protocolInfo->iScepCert ); if( cryptStatusError( status ) ) { retExt( status, ( status, SESSION_ERRINFO, "Couldn't encrypt response data with client key" ) ); } DEBUG_DUMP_FILE( "scep_sresp1", sessionInfoPtr->receiveBuffer, dataLength ); /* Create the SCEP signing attributes */ status = createScepAttributes( sessionInfoPtr, protocolInfo, &iCmsAttributes, FALSE, CRYPT_OK ); if( cryptStatusError( status ) ) { retExt( status, ( status, SESSION_ERRINFO, "Couldn't create SCEP response signing attributes" ) ); } /* Phase 2: Sign the data using the CA key and SCEP attributes */ status = envelopeSign( sessionInfoPtr->receiveBuffer, dataLength, sessionInfoPtr->receiveBuffer, sessionInfoPtr->receiveBufSize, &sessionInfoPtr->receiveBufEnd, CRYPT_CONTENT_NONE, sessionInfoPtr->privateKey, iCmsAttributes ); krnlSendNotifier( iCmsAttributes, IMESSAGE_DECREFCOUNT ); if( cryptStatusError( status ) ) { retExt( status, ( status, SESSION_ERRINFO, "Couldn't sign response data with CA key" ) ); } DEBUG_DUMP_FILE( "scep_sresp2", sessionInfoPtr->receiveBuffer, sessionInfoPtr->receiveBufEnd ); return( CRYPT_OK ); }
static int checkScepRequest( INOUT SESSION_INFO *sessionInfoPtr, INOUT SCEP_PROTOCOL_INFO *protocolInfo, OUT BOOLEAN *requestDataAvailable ) { CRYPT_CERTIFICATE iCmsAttributes; MESSAGE_CREATEOBJECT_INFO createInfo; MESSAGE_DATA msgData; int dataLength, sigResult, value, status; assert( isWritePtr( sessionInfoPtr, sizeof( SESSION_INFO ) ) ); assert( isWritePtr( protocolInfo, sizeof( SCEP_PROTOCOL_INFO ) ) ); assert( isWritePtr( requestDataAvailable, sizeof( BOOLEAN ) ) ); /* Clear return value */ *requestDataAvailable = FALSE; /* Phase 1: Sig-check the self-signed data */ DEBUG_DUMP_FILE( "scep_sreq2", sessionInfoPtr->receiveBuffer, sessionInfoPtr->receiveBufEnd ); status = envelopeSigCheck( sessionInfoPtr->receiveBuffer, sessionInfoPtr->receiveBufEnd, sessionInfoPtr->receiveBuffer, sessionInfoPtr->receiveBufSize, &dataLength, CRYPT_UNUSED, &sigResult, &protocolInfo->iScepCert, &iCmsAttributes ); if( cryptStatusError( status ) ) { retExt( status, ( status, SESSION_ERRINFO, "Invalid CMS signed data in client request" ) ); } DEBUG_DUMP_FILE( "scep_sreq1", sessionInfoPtr->receiveBuffer, dataLength ); if( cryptStatusError( sigResult ) ) { /* The signed data was valid but the signature on it wasn't, this is a different style of error than the previous one */ krnlSendNotifier( iCmsAttributes, IMESSAGE_DECREFCOUNT ); retExt( sigResult, ( sigResult, SESSION_ERRINFO, "Bad signature on client request data" ) ); } /* Make sure that the client certificate is valid for signing and decryption. In effect the signing capability has already been checked by the fact that the certificate signed the request but we do an explicit check here just to be thorough */ status = krnlSendMessage( protocolInfo->iScepCert, IMESSAGE_CHECK, NULL, MESSAGE_CHECK_PKC_SIGCHECK ); if( cryptStatusOK( status ) ) status = krnlSendMessage( protocolInfo->iScepCert, IMESSAGE_CHECK, NULL, MESSAGE_CHECK_PKC_ENCRYPT ); if( cryptStatusError( status ) ) { krnlSendNotifier( iCmsAttributes, IMESSAGE_DECREFCOUNT ); retExt( CRYPT_ERROR_INVALID, ( CRYPT_ERROR_INVALID, SESSION_ERRINFO, "Ephemeral SCEP client certificate isn't valid for " "signing/encryption" ) ); } /* Get the nonce and transaction ID and save it for the reply */ setMessageData( &msgData, protocolInfo->nonce, CRYPT_MAX_HASHSIZE ); status = krnlSendMessage( iCmsAttributes, IMESSAGE_GETATTRIBUTE_S, &msgData, CRYPT_CERTINFO_SCEP_SENDERNONCE ); if( cryptStatusOK( status ) ) { protocolInfo->nonceSize = msgData.length; setMessageData( &msgData, protocolInfo->transID, CRYPT_MAX_HASHSIZE ); status = krnlSendMessage( iCmsAttributes, IMESSAGE_GETATTRIBUTE_S, &msgData, CRYPT_CERTINFO_SCEP_TRANSACTIONID ); } if( cryptStatusError( status ) ) { krnlSendNotifier( iCmsAttributes, IMESSAGE_DECREFCOUNT ); retExt( CRYPT_ERROR_BADDATA, ( CRYPT_ERROR_BADDATA, SESSION_ERRINFO, "Request is missing a nonce/transaction ID" ) ); } protocolInfo->transIDsize = msgData.length; /* We've now got enough request data available to construct a SCEP-level response to an error instead of an HTTP-level one, let the caller know. Note that this lets an attacker know that they've made it this far in the checking, but it's not obvious that this is a security problem, especially since they can get a good idea of how far they got from the different error conditions that will be returned */ *requestDataAvailable = TRUE; /* Since the request is for a cryptlib server it'll have a transaction ID that's a cryptlib-encoded PKI user ID. If we don't get this then we reject the request */ if( protocolInfo->transIDsize != 17 || \ !isPKIUserValue( protocolInfo->transID, protocolInfo->transIDsize ) ) { krnlSendNotifier( iCmsAttributes, IMESSAGE_DECREFCOUNT ); retExt( CRYPT_ERROR_BADDATA, ( CRYPT_ERROR_BADDATA, SESSION_ERRINFO, "Request has an invalid transaction ID '%s'", sanitiseString( protocolInfo->transID, protocolInfo->transIDsize, protocolInfo->transIDsize ) ) ); } status = updateSessionInfo( &sessionInfoPtr->attributeList, CRYPT_SESSINFO_USERNAME, protocolInfo->transID, protocolInfo->transIDsize, CRYPT_MAX_TEXTSIZE, ATTR_FLAG_ENCODEDVALUE ); if( cryptStatusError( status ) ) { krnlSendNotifier( iCmsAttributes, IMESSAGE_DECREFCOUNT ); return( status ); } /* Check that we've been sent the correct type of message */ status = getScepStatusValue( iCmsAttributes, CRYPT_CERTINFO_SCEP_MESSAGETYPE, &value ); if( cryptStatusOK( status ) && value != MESSAGETYPE_PKCSREQ_VALUE ) status = CRYPT_ERROR_BADDATA; krnlSendNotifier( iCmsAttributes, IMESSAGE_DECREFCOUNT ); if( cryptStatusError( status ) ) { retExt( status, ( status, SESSION_ERRINFO, "Incorrect SCEP message type %d", value ) ); } /* Phase 2: Decrypt the data using our CA key */ status = envelopeUnwrap( sessionInfoPtr->receiveBuffer, dataLength, sessionInfoPtr->receiveBuffer, sessionInfoPtr->receiveBufSize, &dataLength, sessionInfoPtr->privateKey ); if( cryptStatusError( status ) ) { retExt( status, ( status, SESSION_ERRINFO, "Couldn't decrypt CMS enveloped data in client request" ) ); } /* Finally, import the request as a PKCS #10 request */ setMessageCreateObjectIndirectInfo( &createInfo, sessionInfoPtr->receiveBuffer, dataLength, CRYPT_CERTTYPE_CERTREQUEST ); status = krnlSendMessage( SYSTEM_OBJECT_HANDLE, IMESSAGE_DEV_CREATEOBJECT_INDIRECT, &createInfo, OBJECT_TYPE_CERTIFICATE ); if( cryptStatusError( status ) ) { retExt( status, ( status, SESSION_ERRINFO, "Invalid PKCS #10 request in client request" ) ); } sessionInfoPtr->iCertRequest = createInfo.cryptHandle; return( CRYPT_OK ); }
static int checkPkiUserInfo( INOUT SESSION_INFO *sessionInfoPtr, INOUT SCEP_PROTOCOL_INFO *protocolInfo ) { const ATTRIBUTE_LIST *userNamePtr = \ findSessionInfo( sessionInfoPtr->attributeList, CRYPT_SESSINFO_USERNAME ); MESSAGE_KEYMGMT_INFO getkeyInfo; MESSAGE_DATA msgData; BYTE keyID[ 64 + 8 ]; BYTE requestPassword[ CRYPT_MAX_TEXTSIZE + 8 ]; BYTE userPassword[ CRYPT_MAX_TEXTSIZE + 8 ]; int requestPasswordSize, userPasswordSize = DUMMY_INIT; int keyIDsize, status; assert( isWritePtr( sessionInfoPtr, sizeof( SESSION_INFO ) ) ); assert( isWritePtr( protocolInfo, sizeof( SCEP_PROTOCOL_INFO ) ) ); REQUIRES( userNamePtr != NULL ); /* Get the password from the PKCS #10 request */ setMessageData( &msgData, requestPassword, CRYPT_MAX_TEXTSIZE ); status = krnlSendMessage( sessionInfoPtr->iCertRequest, IMESSAGE_GETATTRIBUTE_S, &msgData, CRYPT_CERTINFO_CHALLENGEPASSWORD ); if( cryptStatusError( status ) ) { retExt( status, ( status, SESSION_ERRINFO, "Couldn't get challenge password from PKCS #10 request" ) ); } requestPasswordSize = msgData.length; /* Since it's a cryptlib encoded user ID we need to decode it before we can look up a PKI user with it */ REQUIRES( userNamePtr->flags & ATTR_FLAG_ENCODEDVALUE ); status = decodePKIUserValue( keyID, 64, &keyIDsize, userNamePtr->value, userNamePtr->valueLength ); ENSURES( cryptStatusOK( status ) ); /* Get the user information for the request from the certificate store */ setMessageKeymgmtInfo( &getkeyInfo, CRYPT_IKEYID_KEYID, keyID, keyIDsize, NULL, 0, KEYMGMT_FLAG_NONE ); status = krnlSendMessage( sessionInfoPtr->cryptKeyset, IMESSAGE_KEY_GETKEY, &getkeyInfo, KEYMGMT_ITEM_PKIUSER ); if( cryptStatusError( status ) ) { char userID[ CRYPT_MAX_TEXTSIZE + 8 ]; int userIDlen; zeroise( requestPassword, CRYPT_MAX_TEXTSIZE ); userIDlen = min( userNamePtr->valueLength, CRYPT_MAX_TEXTSIZE ); memcpy( userID, userNamePtr->value, userIDlen ); retExtObj( status, ( status, SESSION_ERRINFO, sessionInfoPtr->cryptKeyset, "Couldn't find PKI user information for %s", sanitiseString( userID, CRYPT_MAX_TEXTSIZE, userIDlen ) ) ); } /* Get the password from the PKI user object */ setMessageData( &msgData, userPassword, CRYPT_MAX_TEXTSIZE ); status = krnlSendMessage( getkeyInfo.cryptHandle, IMESSAGE_GETATTRIBUTE_S, &msgData, CRYPT_CERTINFO_PKIUSER_ISSUEPASSWORD ); if( cryptStatusOK( status ) ) { userPasswordSize = msgData.length; status = updateSessionInfo( &sessionInfoPtr->attributeList, CRYPT_SESSINFO_PASSWORD, userPassword, userPasswordSize, CRYPT_MAX_TEXTSIZE, ATTR_FLAG_ENCODEDVALUE ); } if( cryptStatusError( status ) ) { zeroise( requestPassword, CRYPT_MAX_TEXTSIZE ); zeroise( userPassword, CRYPT_MAX_TEXTSIZE ); krnlSendNotifier( getkeyInfo.cryptHandle, IMESSAGE_DECREFCOUNT ); retExt( status, ( status, SESSION_ERRINFO, "Couldn't copy read PKI user data from PKI user object " "into session object" ) ); } /* Make sure that the password matches the one in the request */ if( userPasswordSize != requestPasswordSize || \ !compareDataConstTime( userPassword, requestPassword, userPasswordSize ) ) { zeroise( requestPassword, CRYPT_MAX_TEXTSIZE ); zeroise( userPassword, CRYPT_MAX_TEXTSIZE ); krnlSendNotifier( getkeyInfo.cryptHandle, IMESSAGE_DECREFCOUNT ); retExt( status, ( status, SESSION_ERRINFO, "Password in PKCS #10 request doesn't match PKI user " "password" ) ); } zeroise( userPassword, CRYPT_MAX_TEXTSIZE ); /* If the subject only knows their CN, they may send a CN-only subject DN in the hope that we can fill it in for them. In addition there may be other constraints that the CA wants to apply, these are handled by applying the PKI user information to the request */ status = krnlSendMessage( sessionInfoPtr->iCertRequest, IMESSAGE_SETATTRIBUTE, &getkeyInfo.cryptHandle, CRYPT_IATTRIBUTE_PKIUSERINFO ); krnlSendNotifier( getkeyInfo.cryptHandle, IMESSAGE_DECREFCOUNT ); if( cryptStatusError( status ) ) { retExt( CRYPT_ERROR_INVALID, ( CRYPT_ERROR_INVALID, SESSION_ERRINFO, "User information in PKCS #10 request can't be " "reconciled with stored information for the user" ) ); } return( CRYPT_OK ); }
static int processAdditionalScepRequest( INOUT SESSION_INFO *sessionInfoPtr, const HTTP_URI_INFO *httpReqInfo ) { HTTP_URI_INFO rewrittenHttpReqInfo; MESSAGE_DATA msgData; int operationType, status; assert( isWritePtr( sessionInfoPtr, sizeof( SESSION_INFO ) ) ); assert( isReadPtr( httpReqInfo, sizeof( HTTP_URI_INFO ) ) ); /* If the client has fed us an HTTP GET request, find out what they want. SCEP's handling of HTTP requests is a bit different from the "attribute '=' value" lookup that's normally used for HTTP data retrieval. Instead, it uses the format "'operation =' value '&' extraData", with the search key buried in the 'extraData' value. In addition the content of the 'extraData' value isn't defined outside of "any string which is understood by the CA". However since 'value' defines what we want, we can determine what to return based on this and ignore the 'extraData' portion. In order to fix up the query information into a format that works with standard HTTP queries, we rewrite the query data from the "'operation =' value '&' extraData" form into "attribute '=' value" before we process the query */ memset( &rewrittenHttpReqInfo, 0, sizeof( HTTP_URI_INFO ) ); memcpy( rewrittenHttpReqInfo.attribute, httpReqInfo->value, httpReqInfo->valueLen ); rewrittenHttpReqInfo.attributeLen = httpReqInfo->valueLen; if( httpReqInfo->extraDataLen > 0 ) { memcpy( rewrittenHttpReqInfo.value, httpReqInfo->extraData, httpReqInfo->extraDataLen ); rewrittenHttpReqInfo.valueLen = httpReqInfo->extraDataLen; } status = processCertQuery( sessionInfoPtr, &rewrittenHttpReqInfo, certstoreReadInfo, FAILSAFE_ARRAYSIZE( certstoreReadInfo, \ CERTSTORE_READ_INFO ), &operationType, NULL, 0, NULL ); if( cryptStatusError( status ) ) { sendCertErrorResponse( sessionInfoPtr, status ); return( status ); } ENSURES( operationType == SCEP_OPERATION_GETCACAPS || \ operationType == SCEP_OPERATION_GETCACERT || \ operationType == SCEP_OPERATION_GETCACERTCHAIN ); /* If it's a CA capabilities query, return the information as raw text over HTTP */ if( operationType == SCEP_OPERATION_GETCACAPS ) { STREAM stream; sMemOpen( &stream, sessionInfoPtr->receiveBuffer, 1024 ); status = swrite( &stream, "POSTPKIOperation\n", 17 ); if( algoAvailable( CRYPT_ALGO_SHA1 ) ) status = swrite( &stream, "SHA-1\n", 6 ); if( algoAvailable( CRYPT_ALGO_SHA2 ) ) status = swrite( &stream, "SHA-256\n", 8 ); if( algoAvailable( CRYPT_ALGO_SHAng ) ) status = swrite( &stream, "SHAng\n", 6 ); if( algoAvailable( CRYPT_ALGO_3DES ) ) status = swrite( &stream, "DES3\n", 5 ); if( algoAvailable( CRYPT_ALGO_AES ) ) status = swrite( &stream, "AES\n", 4 ); if( cryptStatusOK( status ) ) sessionInfoPtr->receiveBufEnd = stell( &stream ); sMemDisconnect( &stream ); ENSURES( cryptStatusOK( status ) ); return( writePkiDatagram( sessionInfoPtr, SCEP_CONTENT_TYPE, SCEP_CONTENT_TYPE_LEN ) ); } /* Export the CA certificate and send it to the client */ setMessageData( &msgData, sessionInfoPtr->receiveBuffer, sessionInfoPtr->receiveBufSize ); status = krnlSendMessage( sessionInfoPtr->privateKey, IMESSAGE_CRT_EXPORT, &msgData, ( operationType == SCEP_OPERATION_GETCACERT ) ? \ CRYPT_CERTFORMAT_CERTIFICATE : \ CRYPT_CERTFORMAT_CERTCHAIN ); if( cryptStatusError( status ) ) { retExt( status, ( status, SESSION_ERRINFO, "Couldn't export CA certificate%s for '%s' request", ( operationType == SCEP_OPERATION_GETCACERT ) ? \ "" : " chain", ( operationType == SCEP_OPERATION_GETCACERT ) ? \ "GetCACert" : "GetCACertChain" ) ); } sessionInfoPtr->receiveBufEnd = msgData.length; if( operationType == SCEP_OPERATION_GETCACERT ) { return( writePkiDatagram( sessionInfoPtr, SCEP_CONTENT_TYPE_GETCACERT, SCEP_CONTENT_TYPE_GETCACERT_LEN ) ); } return( writePkiDatagram( sessionInfoPtr, SCEP_CONTENT_TYPE_GETCACERTCHAIN, SCEP_CONTENT_TYPE_GETCACERTCHAIN_LEN ) ); }
int checkServerCertValid( const CRYPT_CERTIFICATE iServerKey, INOUT ERROR_INFO *errorInfo ) { CRYPT_CERTIFICATE iServerCert; CRYPT_ERRTYPE_TYPE errorType DUMMY_INIT; CRYPT_ATTRIBUTE_TYPE errorLocus DUMMY_INIT; static const int complianceLevelStandard = CRYPT_COMPLIANCELEVEL_STANDARD; int complianceLevel, status; assert( isWritePtr( errorInfo, sizeof( ERROR_INFO ) ) ); REQUIRES( isHandleRangeValid( iServerKey ) ); status = krnlSendMessage( iServerKey, IMESSAGE_GETATTRIBUTE, &complianceLevel, CRYPT_OPTION_CERT_COMPLIANCELEVEL ); if( cryptStatusError( status ) ) { /* We can't do much more if we can't even get the initial compliance level */ return( CRYPT_ERROR_INVALID ); } /* Check whether the certificate is valid at a standard level of compliance, which catches expired certificates and other obvious problems */ krnlSendMessage( iServerKey, IMESSAGE_SETATTRIBUTE, ( MESSAGE_CAST ) &complianceLevelStandard, CRYPT_OPTION_CERT_COMPLIANCELEVEL ); status = krnlSendMessage( iServerKey, IMESSAGE_CHECK, NULL, MESSAGE_CHECK_CERT ); krnlSendMessage( iServerKey, IMESSAGE_SETATTRIBUTE, ( MESSAGE_CAST ) &complianceLevel, CRYPT_OPTION_CERT_COMPLIANCELEVEL ); if( cryptStatusOK( status ) ) return( CRYPT_OK ); /* The certificate associated with the key isn't valid, get the certificate (since otherwise we'd be querying the key rather than the certificate) and fetch the extended error information */ status = krnlSendMessage( iServerKey, IMESSAGE_GETDEPENDENT, &iServerCert, OBJECT_TYPE_CERTIFICATE ); if( cryptStatusOK( status ) ) { int value; status = krnlSendMessage( iServerCert, IMESSAGE_GETATTRIBUTE, &value, CRYPT_ATTRIBUTE_ERRORLOCUS ); if( cryptStatusOK( status ) ) { errorLocus = value; /* int to enum */ status = krnlSendMessage( iServerCert, IMESSAGE_GETATTRIBUTE, &value, CRYPT_ATTRIBUTE_ERRORTYPE ); } if( cryptStatusOK( status ) ) errorType = value; /* int to enum */ } if( cryptStatusError( status ) ) { /* If we can't get extended error information then there's not much more that we can do */ retExt( CRYPT_ERROR_INVALID, ( CRYPT_ERROR_INVALID, errorInfo, "Server certificate is invalid" ) ); } /* Try and get more information on common errors and report them to the caller */ if( errorType == CRYPT_ERRTYPE_CONSTRAINT ) { switch( errorLocus ) { case CRYPT_CERTINFO_VALIDFROM: retExt( CRYPT_ERROR_INVALID, ( CRYPT_ERROR_INVALID, errorInfo, "Server certificate is not valid yet" ) ); case CRYPT_CERTINFO_VALIDTO: retExt( CRYPT_ERROR_INVALID, ( CRYPT_ERROR_INVALID, errorInfo, "Server certificate has expired" ) ); case CRYPT_CERTINFO_KEYUSAGE: retExt( CRYPT_ERROR_INVALID, ( CRYPT_ERROR_INVALID, errorInfo, "Server certificate's keyUsage doesn't allow it " "to be used" ) ); } } retExt( CRYPT_ERROR_INVALID, ( CRYPT_ERROR_INVALID, errorInfo, "Server certificate is invalid, error type %d, error " "locus %d", errorType, errorLocus ) ); }
static int checkAddInfo( const PKCS15_INFO *pkcs15infoPtr, IN_HANDLE const CRYPT_HANDLE iCryptHandle, const BOOLEAN isCertChain, const BOOLEAN privkeyPresent, const BOOLEAN certPresent, const BOOLEAN pkcs15keyPresent, const BOOLEAN pkcs15certPresent, OUT BOOLEAN *isCertUpdate, INOUT ERROR_INFO *errorInfo ) { MESSAGE_DATA msgData; BOOLEAN unneededCert, unneededKey; int status; assert( isReadPtr( pkcs15infoPtr, sizeof( PKCS15_INFO ) ) ); assert( isWritePtr( isCertUpdate, sizeof( BOOLEAN ) ) ); REQUIRES( isHandleRangeValid( iCryptHandle ) ); REQUIRES( errorInfo != NULL ); /* Clear return value */ *isCertUpdate = FALSE; /* Check what we can update (if anything) */ unneededKey = privkeyPresent & pkcs15keyPresent; unneededCert = certPresent & pkcs15certPresent; if( ( ( unneededCert && !privkeyPresent ) || \ ( unneededKey && unneededCert ) ) && \ pkcs15infoPtr->validTo > MIN_TIME_VALUE ) { time_t validTo; /* The certificate would be a duplicate, see if it's more recent than the existing one. We only perform this check if there's a validTo time stored for the certificate since without this restriction any certificate without a stored time could be overwritten */ setMessageData( &msgData, &validTo, sizeof( time_t ) ); status = krnlSendMessage( iCryptHandle, IMESSAGE_GETATTRIBUTE_S, &msgData, CRYPT_CERTINFO_VALIDTO ); if( cryptStatusOK( status ) && validTo > pkcs15infoPtr->validTo ) { time_t validFrom; /* It's a newer certificate, don't treat it as a duplicate. This check is effectively impossible to perform automatically since there are an infinite number of variations that have to be taken into account, for example a certificate for the same key issued by a different CA, same CA but it's changed the bits it sets in the keyUsage (digitalSignature vs. nonRepudiation), slightly different issuer DN (Thawte certificates with a date encoded in the DN), and so on and so on. Because this really requires manual processing by a human we don't even try and sort it all out but just allow a certificate for a given key (checked by the ID match) to be replaced by a newer certificate for the same key. This is restrictive enough to prevent most obviously-wrong replacements while being permissive enough to allow most probably-OK replacements */ unneededCert = FALSE; *isCertUpdate = TRUE; /* There's one special-case situation in which odd things can happen when updating certificates and that's when adding a future-dated certificate, which would result in the certificate being replaced with one that can't be used yet. There's no clean way to handle this because in order to know what to do we'd have to be able to guess the intent of the user, however for anything but signature certificates it's likely that the hit-and-miss certificate checking performed by most software won't even notice a future-dated certificate, and for signature certificates the semantics of signing data now using a certificate that isn't valid yet are somewhat uncertain. Since in most cases no-one will even notice the problem, we throw an exception in the debug build but don't do anything in release builds. This is probably less annoying to users than having the code reject an otherwise-valid future-dated certificate. If anyone ever complains about this then we can ask the users at that time what sort of behaviour they're prefer */ setMessageData( &msgData, &validFrom, sizeof( time_t ) ); status = krnlSendMessage( iCryptHandle, IMESSAGE_GETATTRIBUTE_S, &msgData, CRYPT_CERTINFO_VALIDFROM ); if( cryptStatusOK( status ) && \ validFrom > getApproxTime() + 86400L ) { assert( !"Attempt to replace certificate with future-dated certificate" ); } } } /* Make sure that we can update at least one of the objects in the PKCS #15 personality */ if( ( unneededKey && !certPresent ) || /* Key only, duplicate */ ( unneededCert && !privkeyPresent ) || /* Certificate only, duplicate */ ( unneededKey && unneededCert ) ) /* Key+certificate, duplicate */ { /* If it's anything other than a certificate chain, we can't add anything */ if( !isCertChain ) { retExt( CRYPT_ERROR_DUPLICATE, ( CRYPT_ERROR_DUPLICATE, errorInfo, "No new data to add" ) ); } /* Tell the caller that it's an opportunistic certificate-chain update */ return( OK_SPECIAL ); } return( CRYPT_OK ); }
static int readKey( INOUT STREAM *stream, INOUT PGP_INFO *pgpInfo, IN_INT_SHORT_Z const int keyGroupNo, INOUT ERROR_INFO *errorInfo ) { PGP_KEYINFO *keyInfo = &pgpInfo->key; HASHFUNCTION hashFunction; HASHINFO hashInfo; BYTE hash[ CRYPT_MAX_HASHSIZE + 8 ], packetHeader[ 64 + 8 ]; BOOLEAN isPublicKey = TRUE, isPrimaryKey = FALSE; void *pubKeyPayload; long packetLength; int pubKeyPos, pubKeyPayloadPos, endPos, pubKeyPayloadLen; int ctb, length, value, hashSize, iterationCount, status; assert( isWritePtr( stream, sizeof( STREAM ) ) ); assert( isWritePtr( pgpInfo, sizeof( PGP_INFO ) ) ); REQUIRES( keyGroupNo >= 0 && keyGroupNo < MAX_INTLENGTH_SHORT ); REQUIRES( errorInfo != NULL ); /* Process the CTB and packet length */ ctb = sPeek( stream ); if( cryptStatusError( ctb ) ) { /* If there was an error reading the CTB, which is the first byte of the packet group, it means that we've run out of data so we return the status as a not-found error rather than the actual stream status */ return( CRYPT_ERROR_NOTFOUND ); } switch( pgpGetPacketType( ctb ) ) { case PGP_PACKET_SECKEY_SUB: keyInfo = &pgpInfo->subKey; isPublicKey = FALSE; break; case PGP_PACKET_SECKEY: isPublicKey = FALSE; break; case PGP_PACKET_PUBKEY_SUB: keyInfo = &pgpInfo->subKey; break; case PGP_PACKET_PUBKEY: isPrimaryKey = TRUE; break; default: retExt( CRYPT_ERROR_BADDATA, ( CRYPT_ERROR_BADDATA, errorInfo, "Invalid PGP CTB %02X for key packet group %d", ctb, keyGroupNo ) ); } status = pgpReadPacketHeader( stream, NULL, &packetLength, 64 ); if( cryptStatusError( status ) ) { retExt( status, ( status, errorInfo, "Invalid PGP key packet header for key packet group %d", keyGroupNo ) ); } if( packetLength < 64 || packetLength > sMemDataLeft( stream ) ) { retExt( CRYPT_ERROR_BADDATA, ( CRYPT_ERROR_BADDATA, errorInfo, "Invalid PGP key packet length %ld for key packet group %d", packetLength, keyGroupNo ) ); } /* Since there can (in theory) be arbitrary numbers of subkeys and other odds and ends attached to a key and the details of what to do with these things gets a bit vague, we just skip any further subkeys that may be present */ if( keyInfo->pkcAlgo != CRYPT_ALGO_NONE ) { status = sSkip( stream, packetLength ); for( iterationCount = 0; cryptStatusOK( status ) && \ iterationCount < FAILSAFE_ITERATIONS_MED; iterationCount++ ) { status = readUserID( stream, pgpInfo, isPrimaryKey ? &hashInfo : NULL ); } ENSURES( iterationCount < FAILSAFE_ITERATIONS_MED ); if( cryptStatusError( status ) && status != OK_SPECIAL ) { retExt( status, ( status, errorInfo, "Invalid additional PGP subkey information for key " "packet group %d", keyGroupNo ) ); } /* We've skipped the current subkey, we're done */ return( CRYPT_OK ); } /* Determine which bits make up the public and the private key data. The public-key data starts at the version number and includes the date, validity, and public-key components. Since there's no length information included for this data block we have to record bookmarks and then later retroactively calculate the length based on how much data we've read in the meantime: pubKey pubKeyPayload privKey endPos | | | | v v v v +---+---------------------------+-------------------+ |hdr| | Public key | Private key | +---+---------------------------+-------------------+ | |<pubKeyPayloadLen->| | |<----- pubKeyDataLen ----->|<-- privKeyDLen -->| |<--------------- packetLength ---------------->| */ pubKeyPos = stell( stream ); endPos = pubKeyPos + packetLength; ENSURES( endPos > pubKeyPos && endPos < MAX_BUFFER_SIZE ); status = value = sgetc( stream ); if( cryptStatusError( status ) ) return( status ); if( value != PGP_VERSION_2 && value != PGP_VERSION_3 && \ value != PGP_VERSION_OPENPGP ) { /* Unknown version number, skip this packet */ return( OK_SPECIAL ); } pgpInfo->isOpenPGP = ( value == PGP_VERSION_OPENPGP ) ? TRUE : FALSE; /* Build the packet header, which is hashed along with the key components to get the OpenPGP keyID. This is generated anyway when the context is created but we need to generate it here as well in order to locate the key in the first place: byte ctb = 0x99 byte[2] length byte version = 4 byte[4] key generation time [ byte[2] validity time - PGP 2.x only ] byte[] key data We can't add the length or key data yet since we have to parse the key data to know how long it is, so we can only build the static part of the header at this point */ packetHeader[ 0 ] = 0x99; packetHeader[ 3 ] = PGP_VERSION_OPENPGP; /* Read the timestamp and validity period (for PGP 2.x keys) */ status = sread( stream, packetHeader + 4, 4 ); if( !cryptStatusError( status ) && !pgpInfo->isOpenPGP ) status = sSkip( stream, 2 ); if( cryptStatusError( status ) ) return( status ); /* Read the public key components */ pubKeyPayloadPos = stell( stream ); status = readPublicKeyComponents( stream, keyInfo, &length ); if( cryptStatusError( status ) ) { /* If the error status is OK_SPECIAL then the problem was an unrecognised algorithm or something similar so we just skip the packet */ if( status == OK_SPECIAL ) { DEBUG_DIAG(( "Encountered unrecognised algorithm while " "reading key" )); assert( DEBUG_WARN ); return( OK_SPECIAL ); } retExt( status, ( status, errorInfo, "Invalid PGP public-key components for key packet group %d", keyGroupNo ) ); } /* Now that we know where the public key data starts and finishes, we can set up references to it */ keyInfo->pubKeyDataLen = stell( stream ) - pubKeyPos; status = sMemGetDataBlockAbs( stream, pubKeyPos, &keyInfo->pubKeyData, keyInfo->pubKeyDataLen ); if( cryptStatusError( status ) ) { DEBUG_DIAG(( "Couldn't set up reference to key data" )); assert( DEBUG_WARN ); return( status ); } pubKeyPayloadLen = stell( stream ) - pubKeyPayloadPos; status = sMemGetDataBlockAbs( stream, pubKeyPayloadPos, &pubKeyPayload, pubKeyPayloadLen ); if( cryptStatusError( status ) ) { DEBUG_DIAG(( "Couldn't set up reference to key data" )); assert( DEBUG_WARN ); return( status ); } /* Complete the packet header that we read earlier on by adding the length information */ packetHeader[ 1 ] = intToByte( ( ( 1 + 4 + length ) >> 8 ) & 0xFF ); packetHeader[ 2 ] = intToByte( ( 1 + 4 + length ) & 0xFF ); /* Hash the data needed to generate the OpenPGP keyID */ getHashParameters( CRYPT_ALGO_SHA1, 0, &hashFunction, &hashSize ); hashFunction( hashInfo, NULL, 0, packetHeader, 1 + 2 + 1 + 4, HASH_STATE_START ); hashFunction( hashInfo, hash, CRYPT_MAX_HASHSIZE, pubKeyPayload, pubKeyPayloadLen, HASH_STATE_END ); memcpy( keyInfo->openPGPkeyID, hash + hashSize - PGP_KEYID_SIZE, PGP_KEYID_SIZE ); /* If it's a private keyring, process the private key components */ if( !isPublicKey ) { /* Handle decryption information for private-key components if necessary */ status = readPrivateKeyDecryptionInfo( stream, keyInfo ); if( cryptStatusError( status ) ) { /* If the error status is OK_SPECIAL then the problem was an unrecognised algorithm or something similar so we just skip the packet */ if( status == OK_SPECIAL ) { DEBUG_DIAG(( "Encountered unrecognised algorithm while " "reading key" )); assert( DEBUG_WARN ); return( OK_SPECIAL ); } retExt( status, ( status, errorInfo, "Invalid PGP private-key decryption information for " "key packet group %d", keyGroupNo ) ); } /* What's left is the private-key data */ keyInfo->privKeyDataLen = endPos - stell( stream ); status = sMemGetDataBlock( stream, &keyInfo->privKeyData, keyInfo->privKeyDataLen ); if( cryptStatusOK( status ) ) status = sSkip( stream, keyInfo->privKeyDataLen ); if( cryptStatusError( status ) ) return( status ); } /* If it's the primary key, start hashing it in preparation for performing signature checks on subpackets */ if( isPrimaryKey ) { packetHeader[ 0 ] = 0x99; packetHeader[ 1 ] = intToByte( ( keyInfo->pubKeyDataLen >> 8 ) & 0xFF ); packetHeader[ 2 ] = intToByte( keyInfo->pubKeyDataLen & 0xFF ); hashFunction( hashInfo, NULL, 0, packetHeader, 1 + 2, HASH_STATE_START ); hashFunction( hashInfo, NULL, 0, keyInfo->pubKeyData, keyInfo->pubKeyDataLen, HASH_STATE_CONTINUE ); } /* Read any associated subpacket(s), of which the only ones of real interest are the userID packet(s) */ for( iterationCount = 0; cryptStatusOK( status ) && \ iterationCount < FAILSAFE_ITERATIONS_MED; iterationCount++ ) { status = readUserID( stream, pgpInfo, isPrimaryKey ? &hashInfo : NULL ); } ENSURES( iterationCount < FAILSAFE_ITERATIONS_MED ); if( cryptStatusError( status ) && status != OK_SPECIAL ) { retExt( status, ( status, errorInfo, "Invalid PGP userID information for key packet group %d", keyGroupNo ) ); } /* If there's no user ID present, set a generic label */ if( pgpInfo->lastUserID <= 0 ) { pgpInfo->userID[ 0 ] = "PGP key (no user ID found)"; pgpInfo->userIDlen[ 0 ] = 26; pgpInfo->lastUserID = 1; } return( CRYPT_OK ); }
static int readPkiHeader( INOUT STREAM *stream, INOUT CMP_PROTOCOL_INFO *protocolInfo, INOUT ERROR_INFO *errorInfo, const BOOLEAN isServerInitialMessage ) { int length, endPos, status; assert( isWritePtr( stream, sizeof( STREAM ) ) ); assert( isWritePtr( protocolInfo, sizeof( CMP_PROTOCOL_INFO ) ) ); assert( isWritePtr( errorInfo, sizeof( ERROR_INFO ) ) ); /* Clear per-message state information */ protocolInfo->userIDchanged = protocolInfo->certIDchanged = \ protocolInfo->useMACreceive = FALSE; protocolInfo->macInfoPos = CRYPT_ERROR; protocolInfo->senderDNPtr = NULL; protocolInfo->senderDNlength = 0; protocolInfo->headerRead = FALSE; /* Read the wrapper and skip the static information, which matches what we sent and is protected by the MAC so there's little point in looking at it */ status = readSequence( stream, &length ); if( cryptStatusError( status ) ) return( status ); endPos = stell( stream ) + length; readShortInteger( stream, NULL ); /* Version */ if( !protocolInfo->isCryptlib ) { /* The ID of the key used for integrity protection (or in general the identity of the sender) can be specified either as the sender DN or the senderKID or both, or in some cases even indirectly via the transaction ID. With no real guidance as to which one to use, implementors are using any of these options to identify the key. Since we need to check that the integrity-protection key that we're using is correct so that we can report a more appropriate error than bad signature or bad data, we need to remember the sender DN for later in case this is the only form of key identification provided. Unfortunately since the sender DN can't uniquely identify a certificate, if this is the only identifier that we're given then the caller can still get a bad signature error, yet another one of CMPs many wonderful features */ status = readConstructed( stream, &protocolInfo->senderDNlength, 4 ); if( cryptStatusOK( status ) && protocolInfo->senderDNlength > 0 ) { status = sMemGetDataBlock( stream, &protocolInfo->senderDNPtr, protocolInfo->senderDNlength ); if( cryptStatusOK( status ) ) status = readUniversal( stream ); } } else { /* cryptlib includes a proper certID so the whole signer identification mess is avoided and we can ignore the sender DN */ status = readUniversal( stream ); /* Sender DN */ } if( cryptStatusOK( status ) ) status = readUniversal( stream ); /* Recipient DN */ if( peekTag( stream ) == MAKE_CTAG( CTAG_PH_MESSAGETIME ) ) status = readUniversal( stream ); /* Message time */ if( cryptStatusError( status ) ) { retExt( CRYPT_ERROR_BADDATA, ( CRYPT_ERROR_BADDATA, errorInfo, "Invalid DN information in PKI header" ) ); } if( peekTag( stream ) != MAKE_CTAG( CTAG_PH_PROTECTIONALGO ) ) { /* The message was sent without integrity protection, report it as a signature error rather than the generic bad data error that we'd get from the following read */ retExt( CRYPT_ERROR_SIGNATURE, ( CRYPT_ERROR_SIGNATURE, errorInfo, "Message was sent without integrity protection" ) ); } status = readProtectionAlgo( stream, protocolInfo ); if( cryptStatusError( status ) ) { retExt( status, ( status, errorInfo, "Invalid integrity protection information in PKI " "header" ) ); } if( peekTag( stream ) == MAKE_CTAG( CTAG_PH_SENDERKID ) ) { /* Sender protection keyID */ status = readUserID( stream, protocolInfo ); if( cryptStatusError( status ) ) { retExt( status, ( status, errorInfo, "Invalid PKI user ID in PKI header" ) ); } } else { /* If we're the server, the client must provide a PKI user ID in the first message unless we got one in an earlier transaction */ if( isServerInitialMessage && protocolInfo->userIDsize <= 0 ) { retExt( CRYPT_ERROR_BADDATA, ( CRYPT_ERROR_BADDATA, errorInfo, "Missing PKI user ID in PKI header" ) ); } } if( peekTag( stream ) == MAKE_CTAG( CTAG_PH_RECIPKID ) ) readUniversal( stream ); /* Recipient protection keyID */ /* Record the transaction ID (which is effectively the nonce) or make sure that it matches the one that we sent. There's no real need to do an explicit duplicate check since a replay attempt will be rejected as a duplicate by the certificate store and the locking performed at that level makes it a much better place to catch duplicates, but we do it anyway because it doesn't cost anything and we can catch at least some problems a bit earlier */ status = readConstructed( stream, NULL, CTAG_PH_TRANSACTIONID ); if( cryptStatusError( status ) ) { retExt( status, ( status, errorInfo, "Missing transaction ID in PKI header" ) ); } status = readTransactionID( stream, protocolInfo, isServerInitialMessage ); if( cryptStatusError( status ) ) { protocolInfo->pkiFailInfo = CMPFAILINFO_BADRECIPIENTNONCE; retExt( status, ( status, errorInfo, ( status == CRYPT_ERROR_SIGNATURE ) ? \ "Returned message transaction ID doesn't match our " "transaction ID" : \ "Invalid transaction ID in PKI header" ) ); } /* Read the sender nonce, which becomes the new recipient nonce, and skip the recipient nonce if there's one present. These values may be absent, either because the other side doesn't implement them or because they're not available, for example because it's sending a response to an error that occurred before it could read the nonce from a request. In any case we don't bother checking the nonce values since the transaction ID serves the same purpose */ if( stell( stream ) < endPos && \ peekTag( stream ) == MAKE_CTAG( CTAG_PH_SENDERNONCE ) ) { readConstructed( stream, NULL, CTAG_PH_SENDERNONCE ); status = readOctetString( stream, protocolInfo->recipNonce, &protocolInfo->recipNonceSize, 4, CRYPT_MAX_HASHSIZE ); if( cryptStatusError( status ) ) { protocolInfo->pkiFailInfo = CMPFAILINFO_BADSENDERNONCE; retExt( status, ( status, errorInfo, "Invalid sender nonce in PKI header" ) ); } } if( stell( stream ) < endPos && \ peekTag( stream ) == MAKE_CTAG( CTAG_PH_RECIPNONCE ) ) { readConstructed( stream, NULL, CTAG_PH_RECIPNONCE ); status = readUniversal( stream ); if( cryptStatusError( status ) ) { protocolInfo->pkiFailInfo = CMPFAILINFO_BADRECIPIENTNONCE; retExt( status, ( status, errorInfo, "Invalid recipient nonce in PKI header" ) ); } } /* Remember that we've successfully read enough of the header information to generate a response */ protocolInfo->headerRead = TRUE; /* Skip any further junk and process the general information if there is any */ if( stell( stream ) < endPos && \ peekTag( stream ) == MAKE_CTAG( CTAG_PH_FREETEXT ) ) { status = readUniversal( stream ); /* Junk */ if( cryptStatusError( status ) ) return( status ); } if( stell( stream ) < endPos && \ peekTag( stream ) == MAKE_CTAG( CTAG_PH_GENERALINFO ) ) { status = readGeneralInfo( stream, protocolInfo ); if( cryptStatusError( status ) ) { retExt( status, ( status, errorInfo, "Invalid generalInfo information in PKI header" ) ); } } return( CRYPT_OK ); }
int updateUserID( INOUT SESSION_INFO *sessionInfoPtr, INOUT CMP_PROTOCOL_INFO *protocolInfo, const BOOLEAN isServerInitialMessage, const BOOLEAN useMAC ) { int status; assert( isWritePtr( sessionInfoPtr, sizeof( SESSION_INFO ) ) ); assert( isWritePtr( protocolInfo, sizeof( CMP_PROTOCOL_INFO ) ) ); /* We've got a new PKI user ID, if it looks like a cryptlib encoded ID save it in encoded form, otherwise save it as is. Again, CMP's totally ambiguous protocol fields complicate things for us because although in theory we could reject any message containing a non-cryptlib user ID on the basis that it couldn't have been assigned to the user by a cryptlib server, the fact that an arbitrary client could be sending us who knows what sort of data in the user ID field, expecting the key to be identified through other means, means that we can't perform this simple check. We can at least reject a non-cryptlib ID for the ir, which must be MAC'd */ if( isServer( sessionInfoPtr ) && protocolInfo->userIDsize == 9 ) { char encodedUserID[ CRYPT_MAX_TEXTSIZE + 8 ]; int encodedUserIDLength; status = encodePKIUserValue( encodedUserID, CRYPT_MAX_TEXTSIZE, &encodedUserIDLength, protocolInfo->userID, protocolInfo->userIDsize, 3 ); if( cryptStatusError( status ) ) return( status ); status = updateSessionInfo( &sessionInfoPtr->attributeList, CRYPT_SESSINFO_USERNAME, encodedUserID, encodedUserIDLength, CRYPT_MAX_TEXTSIZE, ATTR_FLAG_ENCODEDVALUE ); } else { /* If we're processing an ir then that at least must have a valid cryptlib user ID */ if( isServerInitialMessage && useMAC ) { retExt( CRYPT_ERROR_WRONGKEY, ( CRYPT_ERROR_WRONGKEY, SESSION_ERRINFO, "User ID provided by client isn't a cryptlib user " "ID" ) ); } /* It's not a valid cryptlib PKI user ID, save it anyway since it'll be used for diagnostic/error-reporting purposes */ status = updateSessionInfo( &sessionInfoPtr->attributeList, CRYPT_SESSINFO_USERNAME, protocolInfo->userID, protocolInfo->userIDsize, CRYPT_MAX_TEXTSIZE, ATTR_FLAG_NONE ); } if( cryptStatusError( status ) ) return( status ); /* If this is the first message to the server and we're using MAC-based authentication, set up the server's MAC context based on the information supplied by the client */ if( isServerInitialMessage && useMAC ) return( initServerAuthentMAC( sessionInfoPtr, protocolInfo ) ); return( CRYPT_OK ); }
int exchangeServerKeys( INOUT SESSION_INFO *sessionInfoPtr, INOUT SSL_HANDSHAKE_INFO *handshakeInfo ) { STREAM *stream = &handshakeInfo->stream; int length, status; assert( isWritePtr( sessionInfoPtr, sizeof( SESSION_INFO ) ) ); assert( isWritePtr( handshakeInfo, sizeof( SSL_HANDSHAKE_INFO ) ) ); /* Read the response from the client and, if we're expecting a client certificate, make sure that it's present */ status = readHSPacketSSL( sessionInfoPtr, handshakeInfo, &length, SSL_MSG_HANDSHAKE ); if( cryptStatusError( status ) ) return( status ); sMemConnect( stream, sessionInfoPtr->receiveBuffer, length ); if( clientCertAuthRequired( sessionInfoPtr ) ) { /* Read the client certificate chain and make sure that the certificate being presented is valid for access */ status = readCheckClientCerts( sessionInfoPtr, handshakeInfo, stream ); if( cryptStatusError( status ) ) { sMemDisconnect( stream ); return( status ); } /* Read the next packet(s) if necessary */ status = refreshHSStream( sessionInfoPtr, handshakeInfo ); if( cryptStatusError( status ) ) return( status ); } /* Process the client key exchange packet: byte ID = SSL_HAND_CLIENT_KEYEXCHANGE uint24 len DH: uint16 yLen byte[] y ECDH: uint16 ecPointLen byte[] ecPoint PSK: uint16 userIDLen byte[] userID RSA: [ uint16 encKeyLen -- TLS 1.x ] byte[] rsaPKCS1( byte[2] { 0x03, 0x0n } || byte[46] random ) */ status = checkHSPacketHeader( sessionInfoPtr, stream, &length, SSL_HAND_CLIENT_KEYEXCHANGE, UINT16_SIZE + 1 ); if( cryptStatusError( status ) ) { sMemDisconnect( stream ); return( status ); } if( isKeyxAlgo( handshakeInfo->keyexAlgo ) ) { KEYAGREE_PARAMS keyAgreeParams; const BOOLEAN isECC = isEccAlgo( handshakeInfo->keyexAlgo ); memset( &keyAgreeParams, 0, sizeof( KEYAGREE_PARAMS ) ); if( isECC ) { status = readEcdhValue( stream, keyAgreeParams.publicValue, CRYPT_MAX_PKCSIZE, &keyAgreeParams.publicValueLen ); } else { status = readInteger16UChecked( stream, keyAgreeParams.publicValue, &keyAgreeParams.publicValueLen, MIN_PKCSIZE, CRYPT_MAX_PKCSIZE ); } if( cryptStatusError( status ) ) { sMemDisconnect( stream ); /* Some misconfigured clients may use very short keys, we perform a special-case check for these and return a more specific message than the generic bad-data error */ if( status == CRYPT_ERROR_NOSECURE ) { retExt( CRYPT_ERROR_NOSECURE, ( CRYPT_ERROR_NOSECURE, SESSION_ERRINFO, "Insecure key used in key exchange" ) ); } retExt( CRYPT_ERROR_BADDATA, ( CRYPT_ERROR_BADDATA, SESSION_ERRINFO, "Invalid DH/ECDH phase 2 key agreement data" ) ); } /* Perform phase 2 of the DH/ECDH key agreement */ status = krnlSendMessage( handshakeInfo->dhContext, IMESSAGE_CTX_DECRYPT, &keyAgreeParams, sizeof( KEYAGREE_PARAMS ) ); if( cryptStatusError( status ) ) { zeroise( &keyAgreeParams, sizeof( KEYAGREE_PARAMS ) ); sMemDisconnect( stream ); retExt( status, ( status, SESSION_ERRINFO, "Invalid DH/ECDH phase 2 key agreement value" ) ); } if( isECC ) { const int xCoordLen = ( keyAgreeParams.wrappedKeyLen - 1 ) / 2; /* The output of the ECDH operation is an ECC point, but for some unknown reason TLS only uses the x coordinate and not the full point. To work around this we have to rewrite the point as a standalone x coordinate, which is relatively easy because we're using an "uncompressed" point format: +---+---------------+---------------+ |04 | qx | qy | +---+---------------+---------------+ |<- fldSize --> |<- fldSize --> | */ REQUIRES( keyAgreeParams.wrappedKeyLen >= MIN_PKCSIZE_ECCPOINT && \ keyAgreeParams.wrappedKeyLen <= MAX_PKCSIZE_ECCPOINT && \ ( keyAgreeParams.wrappedKeyLen & 1 ) == 1 && \ keyAgreeParams.wrappedKey[ 0 ] == 0x04 ); memmove( keyAgreeParams.wrappedKey, keyAgreeParams.wrappedKey + 1, xCoordLen ); keyAgreeParams.wrappedKeyLen = xCoordLen; } ENSURES( rangeCheckZ( 0, keyAgreeParams.wrappedKeyLen, CRYPT_MAX_PKCSIZE + CRYPT_MAX_TEXTSIZE ) ); memcpy( handshakeInfo->premasterSecret, keyAgreeParams.wrappedKey, keyAgreeParams.wrappedKeyLen ); handshakeInfo->premasterSecretSize = keyAgreeParams.wrappedKeyLen; zeroise( &keyAgreeParams, sizeof( KEYAGREE_PARAMS ) ); } else { if( handshakeInfo->authAlgo == CRYPT_ALGO_NONE ) { const ATTRIBUTE_LIST *attributeListPtr; BYTE userID[ CRYPT_MAX_TEXTSIZE + 8 ]; /* Read the client user ID and make sure that it's a valid user. Handling non-valid users is somewhat problematic, we can either bail out immediately or invent a fake password for the (non-)user and continue with that. The problem with this is that it doesn't really help hide whether the user is valid or not due to the fact that we're still vulnerable to a timing attack because it takes considerably longer to generate the random password than it does to read a fixed password string from memory, so an attacker can tell from the timing whether the username is valid or not. In addition usability research on real-world users indicates that this actually reduces security while having little to no tangible benefit. Because of this we don't try and fake out the valid/invalid user name indication but just exit immediately if an invalid name is found */ length = readUint16( stream ); if( length < 1 || length > CRYPT_MAX_TEXTSIZE || \ cryptStatusError( sread( stream, userID, length ) ) ) { sMemDisconnect( stream ); retExt( CRYPT_ERROR_BADDATA, ( CRYPT_ERROR_BADDATA, SESSION_ERRINFO, "Invalid client user ID" ) ); } attributeListPtr = \ findSessionInfoEx( sessionInfoPtr->attributeList, CRYPT_SESSINFO_USERNAME, userID, length ); if( attributeListPtr == NULL ) { sMemDisconnect( stream ); retExt( CRYPT_ERROR_WRONGKEY, ( CRYPT_ERROR_WRONGKEY, SESSION_ERRINFO, "Unknown user name '%s'", sanitiseString( userID, CRYPT_MAX_TEXTSIZE, length ) ) ); } /* Select the attribute with the user ID and move on to the associated password */ sessionInfoPtr->attributeListCurrent = \ ( ATTRIBUTE_LIST * ) attributeListPtr; attributeListPtr = attributeListPtr->next; ENSURES( attributeListPtr != NULL && \ attributeListPtr->attributeID == CRYPT_SESSINFO_PASSWORD ); /* Create the shared premaster secret from the user password */ status = createSharedPremasterSecret( \ handshakeInfo->premasterSecret, CRYPT_MAX_PKCSIZE + CRYPT_MAX_TEXTSIZE, &handshakeInfo->premasterSecretSize, attributeListPtr->value, attributeListPtr->valueLength, ( attributeListPtr->flags & ATTR_FLAG_ENCODEDVALUE ) ? \ TRUE : FALSE ); if( cryptStatusError( status ) ) { sMemDisconnect( stream ); retExt( status, ( status, SESSION_ERRINFO, "Couldn't create master secret from shared " "secret/password value" ) ); } } else { BYTE wrappedKey[ CRYPT_MAX_PKCSIZE + 8 ]; if( sessionInfoPtr->version <= SSL_MINOR_VERSION_SSL ) { /* The original Netscape SSL implementation didn't provide a length for the encrypted key and everyone copied that so it became the de facto standard way to do it (sic faciunt omnes. The spec itself is ambiguous on the topic). This was fixed in TLS (although the spec is still ambigous) so the encoding differs slightly between SSL and TLS. To work around this we have to duplicate a certain amount of the integer-read code here */ if( isShortPKCKey( length ) ) status = CRYPT_ERROR_NOSECURE; else { if( length < MIN_PKCSIZE || length > CRYPT_MAX_PKCSIZE || \ cryptStatusError( sread( stream, wrappedKey, length ) ) ) status = CRYPT_ERROR_BADDATA; } } else { status = readInteger16UChecked( stream, wrappedKey, &length, MIN_PKCSIZE, CRYPT_MAX_PKCSIZE ); } if( cryptStatusError( status ) ) { sMemDisconnect( stream ); /* Some misconfigured clients may use very short keys, we perform a special-case check for these and return a more specific message than the generic bad-data */ if( status == CRYPT_ERROR_NOSECURE ) { retExt( CRYPT_ERROR_NOSECURE, ( CRYPT_ERROR_NOSECURE, SESSION_ERRINFO, "Insecure key used in key exchange" ) ); } retExt( CRYPT_ERROR_BADDATA, ( CRYPT_ERROR_BADDATA, SESSION_ERRINFO, "Invalid RSA encrypted key data" ) ); } /* Decrypt the pre-master secret */ status = unwrapPremasterSecret( sessionInfoPtr, handshakeInfo, wrappedKey, length ); if( cryptStatusError( status ) ) { sMemDisconnect( stream ); return( status ); } } } /* If we're expecting a client certificate, process the client certificate verify */ if( clientCertAuthRequired( sessionInfoPtr ) ) { const BOOLEAN isECC = isEccAlgo( handshakeInfo->keyexAlgo ); /* Since the client certificate-verify message requires the hash of all handshake packets up to this point, we have to interrupt the processing to calculate the hash before we continue */ status = createCertVerifyHash( sessionInfoPtr, handshakeInfo ); if( cryptStatusError( status ) ) return( status ); /* Read the next packet(s) if necessary */ status = refreshHSStream( sessionInfoPtr, handshakeInfo ); if( cryptStatusError( status ) ) return( status ); /* Process the client certificate verify packet: byte ID = SSL_HAND_CLIENT_CERTVERIFY uint24 len byte[] signature */ status = checkHSPacketHeader( sessionInfoPtr, stream, &length, SSL_HAND_CLIENT_CERTVERIFY, isECC ? MIN_PKCSIZE_ECCPOINT : \ MIN_PKCSIZE ); if( cryptStatusOK( status ) ) status = checkCertVerify( sessionInfoPtr, handshakeInfo, stream, length ); if( cryptStatusError( status ) ) { sMemDisconnect( stream ); return( status ); } } sMemDisconnect( stream ); return( CRYPT_OK ); }
static int readCheckClientCerts( INOUT SESSION_INFO *sessionInfoPtr, INOUT SSL_HANDSHAKE_INFO *handshakeInfo, INOUT STREAM *stream ) { #ifndef CONFIG_SUITEB_TESTS MESSAGE_KEYMGMT_INFO getkeyInfo; MESSAGE_DATA msgData; BYTE certID[ KEYID_SIZE + 8 ]; #endif /* !CONFIG_SUITEB_TESTS */ #ifdef CONFIG_SUITEB int length; #endif /* CONFIG_SUITEB */ int status; /* Read the client certificate chain */ status = readSSLCertChain( sessionInfoPtr, handshakeInfo, stream, &sessionInfoPtr->iKeyexAuthContext, TRUE ); if( cryptStatusError( status ) ) return( status ); /* Make sure that the client certificate is present in our certificate store. Since we've already got a copy of the certificate, we only do a presence check rather than actually fetching the certificate */ #ifndef CONFIG_SUITEB_TESTS setMessageData( &msgData, certID, KEYID_SIZE ); status = krnlSendMessage( sessionInfoPtr->iKeyexAuthContext, IMESSAGE_GETATTRIBUTE_S, &msgData, CRYPT_CERTINFO_FINGERPRINT_SHA1 ); if( cryptStatusOK( status ) ) { setMessageKeymgmtInfo( &getkeyInfo, CRYPT_IKEYID_CERTID, certID, KEYID_SIZE, NULL, 0, KEYMGMT_FLAG_CHECK_ONLY ); status = krnlSendMessage( sessionInfoPtr->cryptKeyset, IMESSAGE_KEY_GETKEY, &getkeyInfo, KEYMGMT_ITEM_PUBLICKEY ); } if( cryptStatusError( status ) ) { retExt( CRYPT_ERROR_INVALID, ( CRYPT_ERROR_INVALID, SESSION_ERRINFO, "Client certificate is not trusted for authentication " "purposes" ) ); } #endif /* CONFIG_SUITEB_TESTS */ /* Make sure that the key is of the appropriate size for the Suite B security level. At the 128-bit level both P256 and P384 are allowed, at the 256-bit level only P384 is allowed */ #ifdef CONFIG_SUITEB status = krnlSendMessage( sessionInfoPtr->iKeyexAuthContext, IMESSAGE_GETATTRIBUTE, &length, CRYPT_CTXINFO_KEYSIZE ); if( cryptStatusOK( status ) ) { const int suiteBtype = \ sessionInfoPtr->protocolFlags & SSL_PFLAG_SUITEB; if( suiteBtype == SSL_PFLAG_SUITEB_256 ) { if( length != bitsToBytes( 384 ) ) { retExt( CRYPT_ERROR_INVALID, ( CRYPT_ERROR_INVALID, SESSION_ERRINFO, "Client Suite B certificate uses %d-bit key at " "256-bit security level, should use 384-bit key", bytesToBits( length ) ) ); } } else { if( length != bitsToBytes( 256 ) && \ length != bitsToBytes( 384 ) ) { retExt( CRYPT_ERROR_INVALID, ( CRYPT_ERROR_INVALID, SESSION_ERRINFO, "Client Suite B certificate uses %d-bit key at " "128-bit security level, should use 256- or " "384-bit key", bytesToBits( length ) ) ); } } } #endif /* CONFIG_SUITEB */ return( CRYPT_OK ); }