976 lines
30 KiB
C++
976 lines
30 KiB
C++
//
|
|
// Process input received from Server
|
|
//
|
|
|
|
#include "tls_client_recv.h"
|
|
#include "tls_cert_chain.h"
|
|
#include "tls_logger.h"
|
|
|
|
// check for malformed input
|
|
bool malformed(int rval,int maxm) {
|
|
if ((rval&1) == 1) { // if its odd -> error
|
|
return true;
|
|
}
|
|
if (rval/2 > maxm) { // if its too big -> error
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// First some functions for parsing values out of an octad string
|
|
// parse out an octad of length len from octad M into E
|
|
// ptr is a moving pointer through the octad M
|
|
ret parseoctad(octad *E,int len,octad *M,int &ptr)
|
|
{
|
|
ret r={0,BAD_RECORD};
|
|
if (ptr+len>M->len) return r; // not enough in M - probably need to read in some more
|
|
if (E==NULL)
|
|
{
|
|
ptr+=len;
|
|
} else {
|
|
E->len=len;
|
|
for (int i=0;i<len && i<E->max;i++ )
|
|
E->val[i]=M->val[ptr++];
|
|
}
|
|
r.val=len; r.err=0; // it looks OK
|
|
return r;
|
|
}
|
|
|
|
// parse out an array of bytes of length len
|
|
ret parsebytes(char *e,int len,octad *M,int &ptr)
|
|
{
|
|
ret r={0,BAD_RECORD};
|
|
if (ptr+len>M->len) return r; // not enough in M - probably need to read in some more
|
|
if (e==NULL)
|
|
{
|
|
ptr+=len;
|
|
} else {
|
|
for (int i=0;i<len;i++ )
|
|
e[i]=M->val[ptr++];
|
|
}
|
|
r.val=len; r.err=0; // it looks OK
|
|
return r;
|
|
}
|
|
|
|
// parse out an octad of length len from octad M
|
|
// ptr is a moving pointer through the octad M
|
|
// but now E is just a pointer into M
|
|
ret parseoctadptr(octad *E,int len,octad *M,int &ptr)
|
|
{
|
|
ret r={0,BAD_RECORD};
|
|
if (ptr+len>M->len) return r; // not enough in M - probably need to read in some more
|
|
E->len=len;
|
|
E->max=len;
|
|
E->val=&M->val[ptr];
|
|
ptr+=len;
|
|
r.val=len; r.err=0; // it looks OK
|
|
return r;
|
|
}
|
|
|
|
// parse out an integer of length len
|
|
ret parseInt(octad *M,int len,int &ptr)
|
|
{
|
|
ret r={0,BAD_RECORD};
|
|
if (ptr+len>M->len) return r;
|
|
r.val=0;
|
|
for (int i=0;i<len;i++)
|
|
r.val=256*r.val+(unsigned int)(unsigned char)M->val[ptr++];
|
|
r.err=0;
|
|
return r;
|
|
}
|
|
|
|
// ALL Server to Client records arrive via this function
|
|
// Basic function for reading in a record, which may be only contain a fragment of a larger message
|
|
|
|
// Protocol messages can be fragmented, and arrive as multiple records.
|
|
// Record contents are appended to the input buffer.
|
|
// Messages are read from the input buffer, and on reaching the end of the buffer,
|
|
// new records are pulled in to complete a message.
|
|
// Most records must be decrypted before being appended to the message buffer.
|
|
|
|
// get another fragment of server response, in the form of a record. Record type encoded in its header
|
|
// output message body to IBUFF
|
|
// If record is encrypted, decrypt and authenticate it
|
|
// append its contents to the end of IBUFF
|
|
// returns record type, ALERT, APPLICATION or HSHAKE (or pseudo type TIMED_OUT)
|
|
int getServerRecord(TLS_session *session)
|
|
{
|
|
int rtn,left,pos,rlen,taglen;
|
|
char rh[5];
|
|
octad RH={0,sizeof(rh),rh};
|
|
|
|
char tag[TLS_MAX_TAG_SIZE];
|
|
octad TAG={0,sizeof(tag),tag};
|
|
|
|
pos=session->IBUFF.len; // current end of IO
|
|
rtn=getOctad(session->sockptr,&RH,3); // Get record Header - should be something like 17 03 03 XX YY
|
|
|
|
// Need to check RH.val for correctness
|
|
if (rtn<0)
|
|
return TIMED_OUT;
|
|
if (RH.val[0]==ALERT)
|
|
{ // plaintext alert
|
|
left=getInt16(session->sockptr);
|
|
if (left!=2) return BAD_RECORD; // ** RM
|
|
rtn=getOctad(session->sockptr,&session->IBUFF,left);
|
|
if (rtn<0)
|
|
return TIMED_OUT;
|
|
return ALERT;
|
|
}
|
|
if (RH.val[0]==CHANGE_CIPHER)
|
|
{ // read it, and ignore it
|
|
char sccs[10];
|
|
left=getInt16(session->sockptr);
|
|
if (left!=1) return BAD_RECORD; // ** RM
|
|
rtn=getBytes(session->sockptr,sccs,left);
|
|
if (rtn<0)
|
|
return TIMED_OUT;
|
|
if (session->status!=TLS13_HANDSHAKING)
|
|
return WRONG_MESSAGE;
|
|
rtn=getOctad(session->sockptr,&RH,3); // get the next record and carry on
|
|
if (rtn<0)
|
|
return TIMED_OUT;
|
|
}
|
|
|
|
if (RH.val[0]!=HSHAKE && RH.val[0]!=APPLICATION && RH.val[0]!=HEART_BEAT)
|
|
return WRONG_MESSAGE;
|
|
|
|
left=getInt16(session->sockptr);
|
|
if (left>TLS_MAX_CIPHER_FRAG)
|
|
return MAX_EXCEEDED;
|
|
|
|
OCT_append_int(&RH,left,2);
|
|
if (left+pos>session->IBUFF.max)
|
|
{ // this commonly happens with big records of application data from server
|
|
log(IO_DEBUG,(char *)"Record received of length= ",(char *)"%d",left+pos,NULL);
|
|
return MEM_OVERFLOW; // record is too big - memory overflow
|
|
}
|
|
if (!session->K_recv.active)
|
|
{ // not encrypted
|
|
if (RH.val[0]==APPLICATION || RH.val[0]==HEART_BEAT){
|
|
return BAD_RECORD;
|
|
}
|
|
if (left>TLS_MAX_PLAIN_FRAG)
|
|
return MAX_EXCEEDED;
|
|
if (left==0)
|
|
return WRONG_MESSAGE;
|
|
rtn=getBytes(session->sockptr,&session->IBUFF.val[pos],left); // read in record body
|
|
if (rtn<0)
|
|
return TIMED_OUT;
|
|
session->IBUFF.len+=left;
|
|
return HSHAKE;
|
|
}
|
|
if (RH.val[0]==HSHAKE) {
|
|
return BAD_RECORD;
|
|
}
|
|
taglen=session->K_recv.taglen;
|
|
if (left < taglen)
|
|
return BAD_RECORD;
|
|
|
|
rlen=left-taglen; // plaintext record length
|
|
|
|
rtn=getBytes(session->sockptr,&session->IBUFF.val[pos],rlen); // read in record body
|
|
if (rtn<0)
|
|
return TIMED_OUT;
|
|
|
|
session->IBUFF.len+=(rlen); // place record into iobuff
|
|
rtn=getOctad(session->sockptr,&TAG,taglen); // extract TAG
|
|
if (rtn<0)
|
|
return TIMED_OUT;
|
|
bool success=SAL_aeadDecrypt(&session->K_recv,RH.len,RH.val,rlen,&session->IBUFF.val[pos],&TAG);
|
|
if (!success)
|
|
return AUTHENTICATION_FAILURE; // tag is wrong
|
|
|
|
incrementCryptoContext(&session->K_recv); // update IV
|
|
|
|
// get record ending - encodes real (disguised) record type. Could be an Alert.
|
|
int lb=session->IBUFF.val[session->IBUFF.len-1];
|
|
session->IBUFF.len--; rlen--; // remove it
|
|
while (lb==0 && rlen>0 /*session->IBUFF.len>0*/)
|
|
{ // could be zero padding
|
|
lb=session->IBUFF.val[session->IBUFF.len-1]; // need to track back through zero padding for this....
|
|
session->IBUFF.len--; rlen--;// remove it
|
|
}
|
|
|
|
if (rlen>TLS_MAX_PLAIN_FRAG) return MAX_EXCEEDED;
|
|
|
|
// rlen is Inner Plaintext length?
|
|
if ((lb==HSHAKE || lb==ALERT) && rlen==0)
|
|
return WRONG_MESSAGE; // Implementations MUST NOT send zero-length fragments of Handshake types
|
|
if (lb==HSHAKE)
|
|
return HSHAKE;
|
|
if (lb==APPLICATION)
|
|
return APPLICATION;
|
|
if (lb==HEART_BEAT)
|
|
return HEART_BEAT;
|
|
if (lb==ALERT)
|
|
{ // Disguised Alert record received, delete anything in IO prior to alert, and just return 2-byte alert
|
|
OCT_shift_left(&session->IBUFF,pos);
|
|
return ALERT;
|
|
}
|
|
return WRONG_MESSAGE;
|
|
}
|
|
|
|
// These functions read data from the input buffer, and pull more handshake records from a socket if it has to.
|
|
// ALL of these records SHOULD be of type HSHAKE
|
|
ret parseIntorPull(TLS_session *session,int len)
|
|
{
|
|
ret r=parseInt(&session->IBUFF,len,session->ptr);
|
|
while (r.err)
|
|
{ // not enough bytes in IO - Pull in some more
|
|
int rtn=getServerRecord(session);
|
|
if (rtn!=HSHAKE) { // Bad input from server (Authentication failure? Wrong record type?)
|
|
r.err=rtn; // probably negative error
|
|
if (rtn==ALERT) r.val=session->IBUFF.val[1];
|
|
if (rtn==APPLICATION) r.err=WRONG_MESSAGE;
|
|
break;
|
|
}
|
|
r=parseInt(&session->IBUFF,len,session->ptr);
|
|
}
|
|
return r;
|
|
}
|
|
|
|
// Get an octad O of length len from the IBUFF buffer. Create a copy.
|
|
ret parseoctadorPull(TLS_session *session,octad *O,int len)
|
|
{
|
|
ret r=parseoctad(O,len,&session->IBUFF,session->ptr);
|
|
while (r.err)
|
|
{ // not enough bytes in IBUFF - pull in another fragment
|
|
int rtn=getServerRecord(session);
|
|
if (rtn!=HSHAKE) {
|
|
r.err=rtn;
|
|
if (rtn==ALERT) r.val=session->IBUFF.val[1];
|
|
if (rtn==APPLICATION) r.err=WRONG_MESSAGE;
|
|
break;
|
|
}
|
|
r=parseoctad(O,len,&session->IBUFF,session->ptr);
|
|
}
|
|
return r;
|
|
}
|
|
|
|
// Get byte array o of length len from the IBUFF buffer. Create a copy.
|
|
ret parsebytesorPull(TLS_session *session,char *o,int len)
|
|
{
|
|
ret r=parsebytes(o,len,&session->IBUFF,session->ptr);
|
|
while (r.err)
|
|
{ // not enough bytes in IO - pull in another fragment
|
|
int rtn=getServerRecord(session);
|
|
if (rtn!=HSHAKE) {
|
|
r.err=rtn;
|
|
if (rtn==ALERT) r.val=session->IBUFF.val[1];
|
|
if (rtn==APPLICATION) r.err=WRONG_MESSAGE;
|
|
break;
|
|
}
|
|
r=parsebytes(o,len,&session->IBUFF,session->ptr);
|
|
}
|
|
return r;
|
|
}
|
|
|
|
// Get an octad O of length len from the IBUFF buffer, but this time the output octad is a pointer into the IBUFF buffer
|
|
ret parseoctadorPullptrX(TLS_session *session,octad *O,int len)
|
|
{
|
|
ret r=parseoctadptr(O,len,&session->IBUFF,session->ptr);
|
|
while (r.err)
|
|
{ // not enough bytes in IO - pull in another fragment
|
|
int rtn=getServerRecord(session);
|
|
if (rtn!=HSHAKE) {
|
|
r.err=rtn;
|
|
if (rtn==ALERT) r.val=session->IBUFF.val[1];
|
|
if (rtn==APPLICATION) r.err=WRONG_MESSAGE;
|
|
break;
|
|
}
|
|
r=parseoctadptr(O,len,&session->IBUFF,session->ptr);
|
|
}
|
|
return r;
|
|
}
|
|
|
|
// Could have (a) received an alert, or (b) had problem with response, so need to send an alert
|
|
// test for a bad response, log what happened and act accordingly
|
|
// Very probably requires sending an alert to the server, and aborting
|
|
// If Alert received, log it, send alert, abort
|
|
bool badResponse(TLS_session *session,ret r) //Socket *client,crypto *send,ret r)
|
|
{
|
|
logServerResponse(r);
|
|
if (r.err != 0)
|
|
{
|
|
log(IO_PROTOCOL,(char *)"Handshake failed\n",NULL,0,NULL);
|
|
}
|
|
if (r.err<0)
|
|
{ // send an alert to the Server, and abort
|
|
sendAlert(session,alert_from_cause(r.err));
|
|
return true;
|
|
}
|
|
if (r.err==ALERT)
|
|
{ // received an alert from the Server - abort
|
|
log(IO_PROTOCOL,(char *)"*** Alert received - ",NULL,0,NULL);
|
|
logAlert(r.val);
|
|
return true;
|
|
}
|
|
if (r.err) // some other error, maybe time out
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
// Function return convention. These functions return a "ret" structure
|
|
// if r.err +ve log unexpected event and abort
|
|
// if r.err -ve send alert and abort
|
|
// if r.err = ALERT, r.val=received alert code
|
|
// if all goes well, r.val returns message type, r.err=0
|
|
|
|
// Note: we are on a hair trigger here. Anything doesn't look right, we bomb out.
|
|
|
|
// Functions to process server responses
|
|
// Deals with any kind of fragmentation
|
|
// Build up server handshake response in IBUFF, decrypting each fragment in-place
|
|
// extract Encrypted Extensions, Certificate Chain, Server Certificate Signature and Server Verifier Data
|
|
// update transcript hash
|
|
// Bad actor Server could be throwing anything at us - so be careful
|
|
ret seeWhatsNext(TLS_session *session)
|
|
{
|
|
int nb;
|
|
ret r;
|
|
|
|
//session->ptr=0;
|
|
r=parseIntorPull(session,1);
|
|
if (r.err) return r;
|
|
session->ptr-=1;
|
|
|
|
nb=r.val;
|
|
if (nb==END_OF_EARLY_DATA || nb==KEY_UPDATE) { // Servers MUST NOT send this.... KEY_UPDATE should not happen at this stage
|
|
r.err=WRONG_MESSAGE;
|
|
return r;
|
|
}
|
|
return r;
|
|
}
|
|
|
|
// Get encrypted extensions
|
|
ret getServerEncryptedExtensions(TLS_session *session,ee_status *enc_ext_expt,ee_status *enc_ext_resp)
|
|
{
|
|
ret r;
|
|
int nb,left,ext,len,tlen,xlen,mfl,cct,sct,hbmode;//,ptr=0;
|
|
int unexp=0;
|
|
|
|
r=parseIntorPull(session,1);
|
|
if (r.err) return r;
|
|
nb=r.val;
|
|
|
|
if (nb!=ENCRYPTED_EXTENSIONS) {
|
|
r.err=WRONG_MESSAGE;
|
|
return r;
|
|
}
|
|
|
|
r=parseIntorPull(session,3); left=r.val; if (r.err) return r; // message length
|
|
|
|
enc_ext_resp->early_data=false;
|
|
enc_ext_resp->alpn=false;
|
|
enc_ext_resp->server_name=false;
|
|
enc_ext_resp->max_frag_length=false;
|
|
|
|
r=parseIntorPull(session,2); len=r.val; if (r.err) return r; // length of extensions
|
|
|
|
|
|
if (left!=len+2) {
|
|
r.err=BAD_MESSAGE;
|
|
return r;
|
|
}
|
|
|
|
// extension could include Servers preference for supported groups, which could be
|
|
// taken into account by the client for later connections. Here we will ignore it. From RFC:
|
|
// "Clients MUST NOT act upon any information found in "supported_groups" prior to successful completion of the handshake"
|
|
while (len>0)
|
|
{
|
|
r=parseIntorPull(session,2); ext=r.val; if (r.err) return r;
|
|
r=parseIntorPull(session,2); tlen=r.val; if (r.err) return r;
|
|
if (len<tlen+4) {
|
|
r.err=BAD_MESSAGE;
|
|
return r;
|
|
}
|
|
len-=(tlen+4);
|
|
switch (ext)
|
|
{
|
|
case EARLY_DATA :
|
|
if (tlen!=0) {
|
|
r.err=UNRECOGNIZED_EXT;
|
|
return r;
|
|
}
|
|
enc_ext_resp->early_data=true;
|
|
if (!enc_ext_expt->early_data) {
|
|
r.err=NOT_EXPECTED;
|
|
return r;
|
|
}
|
|
|
|
break;
|
|
case MAX_FRAG_LENGTH :
|
|
r=parseIntorPull(session,1); mfl=r.val; if (r.err) return r; // ideally this should the same as requested by client
|
|
// but server may have ignored this request... :( so we ignore this response
|
|
if (mfl!=TLS_MAX_FRAG || tlen!=1) {
|
|
r.err=UNRECOGNIZED_EXT;
|
|
return r;
|
|
}
|
|
enc_ext_resp->max_frag_length=true;
|
|
if (!enc_ext_expt->max_frag_length) {
|
|
r.err=NOT_EXPECTED;
|
|
return r;
|
|
}
|
|
break;
|
|
|
|
case CLIENT_CERT_TYPE :
|
|
r=parseIntorPull(session,1); cct=r.val; if (r.err) return r;
|
|
if (tlen!=1) {
|
|
r.err=UNRECOGNIZED_EXT;
|
|
return r;
|
|
}
|
|
#ifdef PREFER_RAW_CLIENT_PUBLIC_KEY
|
|
if (cct!=RAW_PUBLIC_KEY) {
|
|
session->client_cert_type=X509_CERT;
|
|
} else {
|
|
session->client_cert_type=RAW_PUBLIC_KEY;
|
|
}
|
|
#else
|
|
session->client_cert_type=X509_CERT;
|
|
#endif
|
|
break;
|
|
case SERVER_CERT_TYPE :
|
|
r=parseIntorPull(session,1); sct=r.val; if (r.err) return r;
|
|
if (tlen!=1) {
|
|
r.err=UNRECOGNIZED_EXT;
|
|
return r;
|
|
}
|
|
#ifdef PREFER_RAW_SERVER_PUBLIC_KEY
|
|
if (sct!=RAW_PUBLIC_KEY) {
|
|
session->server_cert_type=X509_CERT;
|
|
} else {
|
|
session->server_cert_type=RAW_PUBLIC_KEY;
|
|
}
|
|
#else
|
|
session->server_cert_type=X509_CERT;
|
|
#endif
|
|
break;
|
|
case RECORD_SIZE_LIMIT:
|
|
r=parseIntorPull(session,2); mfl=r.val; if (r.err) return r;
|
|
|
|
if (tlen!=2 || mfl<64) {
|
|
r.err=UNRECOGNIZED_EXT;
|
|
return r;
|
|
}
|
|
session->max_record=mfl;
|
|
break;
|
|
|
|
case HEARTBEAT:
|
|
r=parseIntorPull(session,1); hbmode=r.val; if (r.err) return r;
|
|
if (hbmode==0 || hbmode>2)
|
|
{
|
|
r.err=UNRECOGNIZED_EXT;
|
|
return r;
|
|
}
|
|
//printf("EXPECTING HEARTBEATs\n");
|
|
session->expect_heartbeats=true;
|
|
if (hbmode==1) {
|
|
//printf("ALLOWED TO HEARTBEAT\n");
|
|
session->allowed_to_heartbeat=true;
|
|
}
|
|
else session->allowed_to_heartbeat=false;
|
|
break;
|
|
|
|
case APP_PROTOCOL :
|
|
r=parseIntorPull(session,2); xlen=r.val; if (r.err) return r;
|
|
r=parseIntorPull(session,1); mfl=r.val; if (r.err) return r;
|
|
if (tlen!=xlen+2 || xlen!=mfl+1) // ** RM
|
|
{
|
|
r.err=UNRECOGNIZED_EXT;
|
|
return r;
|
|
}
|
|
r=parseoctadorPull(session,NULL,mfl); if (r.err) return r; // ALPN code - send to NULL -- assume its the one I asked for
|
|
|
|
enc_ext_resp->alpn=true;
|
|
if (!enc_ext_expt->alpn) {
|
|
r.err=NOT_EXPECTED;
|
|
return r;
|
|
}
|
|
break;
|
|
case SERVER_NAME:
|
|
enc_ext_resp->server_name=true;
|
|
if (tlen!=0) {
|
|
r.err=UNRECOGNIZED_EXT;
|
|
return r;
|
|
}
|
|
if (!enc_ext_expt->server_name) {
|
|
r.err=NOT_EXPECTED;
|
|
return r;
|
|
}
|
|
break;
|
|
case SIG_ALGS:
|
|
case SIG_ALGS_CERT:
|
|
case KEY_SHARE:
|
|
case PSK_MODE:
|
|
case PRESHARED_KEY:
|
|
case TLS_VER:
|
|
case COOKIE:
|
|
case PADDING:
|
|
session->ptr+=tlen; // skip over it
|
|
r.err=FORBIDDEN_EXTENSION;
|
|
return r;
|
|
default: // ignore all other extensions
|
|
session->ptr+=tlen; // skip over it
|
|
unexp++;
|
|
break;
|
|
}
|
|
if (r.err) return r;
|
|
}
|
|
|
|
// Update Transcript hash and rewind IO buffer
|
|
runningHashIOrewind(session);
|
|
|
|
if (unexp>0)
|
|
{
|
|
r.err=UNRECOGNIZED_EXT;
|
|
log(IO_DEBUG,(char *)"Unrecognized extensions received\n",NULL,0,NULL);
|
|
}
|
|
r.val=nb;
|
|
return r;
|
|
}
|
|
|
|
// check for overlap given server signature capabilities, and my client certificate
|
|
static bool overlap(int *serverSigAlgs,int nssa,int *serverCertSigAlgs,int nscsa) {
|
|
int clientCertReqs[TLS_MAX_SUPPORTED_SIGS];
|
|
int nsreq=getSigRequirements(clientCertReqs);
|
|
|
|
for (int i=0;i<nsreq;i++) {
|
|
bool itsthere=false;
|
|
int sig=clientCertReqs[i];
|
|
for (int j=0;j<nssa;j++) {
|
|
if (sig==serverSigAlgs[j]) {
|
|
itsthere=true;
|
|
}
|
|
}
|
|
for (int j=0;j<nscsa;j++) {
|
|
if (sig==serverCertSigAlgs[j]) {
|
|
itsthere=true;
|
|
}
|
|
}
|
|
if (!itsthere) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Receive a Certificate request
|
|
ret getCertificateRequest(TLS_session *session,bool context)
|
|
{
|
|
ret r;
|
|
int i,left,nb,ext,len,tlen,nssa,nscsa;//,ptr=0;
|
|
int unexp=0;
|
|
int sigalgs[TLS_MAX_SUPPORTED_SIGS]; // acceptable client signature types
|
|
int certsigalgs[TLS_MAX_SUPPORTED_SIGS];
|
|
|
|
r=parseIntorPull(session,1); // get message type
|
|
if (r.err!=0) {return r;}
|
|
nb=r.val;
|
|
if (nb != CERT_REQUEST) {
|
|
r.err=WRONG_MESSAGE;
|
|
return r;
|
|
}
|
|
|
|
r=parseIntorPull(session,3); left=r.val; if (r.err) return r; // message length
|
|
r=parseIntorPull(session,1); nb=r.val; if (r.err) return r;
|
|
if (context)
|
|
{
|
|
if (nb==0x00) {
|
|
r.err= MISSING_REQUEST_CONTEXT;// expecting Request context
|
|
return r;
|
|
}
|
|
r=parseoctadorPull(session,&session->CTX,nb); if (r.err) return r;
|
|
left-=nb;
|
|
} else {
|
|
if (nb!=0x00) {
|
|
r.err= MISSING_REQUEST_CONTEXT;// expecting 0x00 Request context
|
|
return r;
|
|
}
|
|
}
|
|
r=parseIntorPull(session,2); len=r.val; if (r.err) return r; // length of extensions
|
|
if (left!=len+3) {
|
|
r.err=BAD_MESSAGE;
|
|
return r;
|
|
}
|
|
|
|
nssa=nscsa=0;
|
|
// extension must include signature algorithms
|
|
while (len>0)
|
|
{
|
|
r=parseIntorPull(session,2); ext=r.val; if (r.err) return r;
|
|
r=parseIntorPull(session,2); tlen=r.val; if (r.err) return r;
|
|
if (len<tlen+4) {
|
|
r.err=BAD_MESSAGE;
|
|
return r;
|
|
}
|
|
len-=(4+tlen);
|
|
switch (ext)
|
|
{
|
|
case SIG_ALGS :
|
|
r=parseIntorPull(session,2); if (r.err) return r;
|
|
if (malformed(r.val,TLS_MAX_SUPPORTED_SIGS) || tlen!=2+r.val) {
|
|
r.err=UNRECOGNIZED_EXT;
|
|
return r;
|
|
}
|
|
nssa=r.val/2;
|
|
for (i=0;i<nssa;i++)
|
|
{
|
|
r=parseIntorPull(session,2); if (r.err) return r;
|
|
sigalgs[i]=r.val;
|
|
}
|
|
break;
|
|
|
|
case SIG_ALGS_CERT :
|
|
r=parseIntorPull(session,2); if (r.err) return r;
|
|
if (malformed(r.val,TLS_MAX_SUPPORTED_SIGS) || tlen!=2+r.val) {
|
|
r.err=UNRECOGNIZED_EXT;
|
|
return r;
|
|
}
|
|
nscsa=r.val/2;
|
|
for (i=0;i<nscsa;i++)
|
|
{
|
|
r=parseIntorPull(session,2); if (r.err) return r;
|
|
if (i<TLS_MAX_SUPPORTED_SIGS) certsigalgs[i]=r.val;
|
|
}
|
|
break;
|
|
|
|
default: // ignore all other extensions
|
|
session->ptr+=tlen; // skip over it
|
|
unexp++;
|
|
break;
|
|
}
|
|
if (r.err) return r;
|
|
}
|
|
|
|
// Update Transcript hash and rewind IO buffer
|
|
runningHashIOrewind(session);
|
|
r.val=CERT_REQUEST;
|
|
if (nssa==0) { // must specify at least one signature algorithm
|
|
r.err=MISSING_EXTENSIONS;
|
|
return r;
|
|
}
|
|
|
|
#if CLIENT_CERT == NOCERT
|
|
r.val=0;
|
|
#else
|
|
// just decline by sending NULL certificate, rather than an alert
|
|
if (!overlap(sigalgs,nssa,certsigalgs,nscsa)) {
|
|
r.val=0;
|
|
//log(IO_DEBUG,(char *)"Server cannot verify client certificate\n",NULL,0,NULL);
|
|
//r.err=BAD_HANDSHAKE;
|
|
//return r;
|
|
}
|
|
#endif
|
|
if (unexp>0)
|
|
log(IO_DEBUG,(char *)"Unrecognized extensions received\n",NULL,0,NULL);
|
|
|
|
return r;
|
|
}
|
|
|
|
// Get certificate chain, and check its validity
|
|
ret getCheckServerCertificateChain(TLS_session *session,octad *PUBKEY,octad *SIG)
|
|
{
|
|
ret r;
|
|
int nb,len,tlen;
|
|
octad CERTCHAIN; // // Clever re-use of memory - share memory rather than make a copy!
|
|
CERTCHAIN.len=0;
|
|
|
|
r=parseIntorPull(session,1); // get message type
|
|
if (r.err!=0) {return r;}
|
|
nb=r.val;
|
|
if (nb != CERTIFICATE) {
|
|
r.err=WRONG_MESSAGE;
|
|
return r;
|
|
}
|
|
|
|
r=parseIntorPull(session,3); len=r.val; if (r.err) return r; // message length
|
|
log(IO_DEBUG,(char *)"Certificate Chain Length= ",(char *)"%d",len,NULL);
|
|
|
|
if (len==0)
|
|
{
|
|
r.err=EMPTY_CERT_CHAIN;
|
|
return r;
|
|
}
|
|
|
|
r=parseIntorPull(session,1); nb=r.val; if (r.err) return r;
|
|
if (nb!=0x00) {
|
|
r.err=MISSING_REQUEST_CONTEXT;// expecting 0x00 Request context
|
|
return r;
|
|
}
|
|
r=parseIntorPull(session,3); tlen=r.val; if (r.err) return r; // get length of certificate chain list
|
|
|
|
if (tlen==0)
|
|
{
|
|
r.err=EMPTY_CERT_CHAIN;
|
|
return r;
|
|
}
|
|
if (tlen+4!=len)
|
|
{
|
|
r.err=BAD_CERT_CHAIN;
|
|
return r;
|
|
}
|
|
|
|
r=parseoctadorPullptrX(session,&CERTCHAIN,tlen); if (r.err) return r; // get pointer to certificate chain
|
|
|
|
// Update Transcript hash and rewind IO buffer
|
|
runningHashIO(session); // Got to do this here, as checkServerCertChain may modify IO buffer contents
|
|
r.err=checkServerCertChain(&CERTCHAIN,session->hostname,session->server_cert_type,PUBKEY,SIG);
|
|
|
|
#ifdef NO_CERT_CHECKS
|
|
r.err=0;
|
|
#endif
|
|
|
|
rewindIO(session); // now save to rewind
|
|
|
|
r.val=CERTIFICATE;
|
|
return r;
|
|
}
|
|
|
|
// Get Server proof that he owns the Certificate, by receiving its signature SCVSIG on transcript hash
|
|
ret getServerCertVerify(TLS_session *session,octad *SCVSIG,int &sigalg)
|
|
{
|
|
ret r;
|
|
int nb,left,len;//,ptr=0;
|
|
int sigAlgs[TLS_MAX_SUPPORTED_SIGS];
|
|
int nsa=SAL_sigs(sigAlgs);
|
|
r=parseIntorPull(session,1); // get message type
|
|
if (r.err!=0) {return r;}
|
|
nb=r.val;
|
|
if (nb != CERT_VERIFY) {
|
|
r.err=WRONG_MESSAGE;
|
|
return r;
|
|
}
|
|
|
|
r=parseIntorPull(session,3); left=r.val; if (r.err) return r; // message length
|
|
OCT_kill(SCVSIG);
|
|
r=parseIntorPull(session,2); sigalg=r.val; if (r.err) return r; // may for example be 0804 - RSA-PSS-RSAE-SHA256
|
|
|
|
bool offered=false;
|
|
for (int i=0;i<nsa;i++)
|
|
if (sigalg==sigAlgs[i]) offered=true;
|
|
if (!offered)
|
|
{
|
|
r.err=CERT_VERIFY_FAIL;
|
|
return r;
|
|
}
|
|
|
|
r=parseIntorPull(session,2); len=r.val; if (r.err) return r; // sig data follows
|
|
r=parseoctadorPull(session,SCVSIG,len); if (r.err) return r;
|
|
|
|
if (left!=len+4) {
|
|
r.err=BAD_MESSAGE;
|
|
return r;
|
|
}
|
|
|
|
// Update Transcript hash and rewind IO buffer
|
|
runningHashIOrewind(session);
|
|
|
|
r.val=CERT_VERIFY;
|
|
return r;
|
|
}
|
|
|
|
// Get handshake finish verifier data in HFIN
|
|
ret getServerFinished(TLS_session *session,octad *HFIN)
|
|
{
|
|
ret r;
|
|
int nb,len;
|
|
r=parseIntorPull(session,1); // get message type
|
|
if (r.err!=0) {return r;}
|
|
nb=r.val;
|
|
if (nb != FINISHED) {
|
|
r.err=WRONG_MESSAGE;
|
|
return r;
|
|
}
|
|
|
|
int hashtype=SAL_hashType(session->cipher_suite);
|
|
int hlen=SAL_hashLen(hashtype);
|
|
r=parseIntorPull(session,3); len=r.val; if (r.err) return r; // message length
|
|
OCT_kill(HFIN);
|
|
r=parseoctadorPull(session,HFIN,hlen); if (r.err) return r;
|
|
|
|
if (len!=hlen)
|
|
r.err=BAD_MESSAGE;
|
|
|
|
// Update Transcript hash and rewind IO buffer
|
|
runningHashIOrewind(session);
|
|
|
|
r.val=FINISHED;
|
|
return r;
|
|
}
|
|
|
|
// Handshake Retry Request
|
|
static const char *hrrh= (const char *)"CF21AD74E59A6111BE1D8C021E65B891C2A211167ABB8C5E079E09E2C8A8339C";
|
|
|
|
// Process initial serverHello - NOT encrypted
|
|
// pskid >=0 if Pre-Shared-Key is accepted
|
|
ret getServerHello(TLS_session *session,int &kex,octad *CK,octad *PK,int &pskid)
|
|
{
|
|
ret r;
|
|
int tls,left,silen,cmp,extLen,ext,tmplen,pklen,cipher;
|
|
bool retry=false;
|
|
char sid[32];
|
|
char srn[32];
|
|
octad SRN={0,sizeof(srn),srn};
|
|
char hrr[40];
|
|
octad HRR={0,sizeof(hrr),hrr};
|
|
|
|
// need this to check for Handshake Retry Request
|
|
OCT_from_hex(&HRR,(char *)hrrh);
|
|
|
|
kex=-1;
|
|
pskid=-1;
|
|
|
|
OCT_kill(CK); OCT_kill(PK);
|
|
// get first fragment - not encrypted
|
|
OCT_kill(&session->IBUFF);
|
|
|
|
// start parsing mandatory components
|
|
session->ptr=0;
|
|
|
|
r=parseIntorPull(session,1);
|
|
if (r.err) return r; // should be Server Hello
|
|
if (r.val!=SERVER_HELLO)
|
|
{
|
|
r.err=BAD_HELLO;
|
|
return r;
|
|
}
|
|
|
|
r=parseIntorPull(session,3); left=r.val; if (r.err) return r; // If not enough, pull in another fragment
|
|
r=parseIntorPull(session,2); if (r.err) return r;
|
|
|
|
if (left<72)
|
|
{
|
|
r.err=BAD_HELLO;
|
|
return r;
|
|
}
|
|
left-=2; // whats left in message
|
|
|
|
r= parseoctadorPull(session,&SRN,32); if (r.err) return r;
|
|
left-=32;
|
|
if (OCT_compare(&SRN,&HRR))
|
|
{
|
|
retry=true; // "random" data was not random at all - indicated Handshake Retry Request!
|
|
}
|
|
r=parseIntorPull(session,1); silen=r.val; if (silen!=32) r.err=BAD_HELLO; if (r.err) return r;
|
|
left-=1;
|
|
r=parsebytesorPull(session,sid,silen); if (r.err) return r;
|
|
left-=silen;
|
|
|
|
// Tricky one. According to the RFC (4.1.3) this check should be made, even though the session id is "legacy",
|
|
// Unfortunately it is not made clear if the same session ID should be use on a handshake resumption.
|
|
// We note that some servers echo the original id, not a new id associated with a new Client Hello
|
|
// Solution here is to use same id on resumption(?)
|
|
bool mismatch=false;
|
|
for (int i=0;i<32;i++)
|
|
{
|
|
if (session->id[i]!=sid[i])
|
|
mismatch=true;
|
|
}
|
|
if (mismatch) {
|
|
r.err=ID_MISMATCH; // check identities match
|
|
return r;
|
|
}
|
|
r=parseIntorPull(session,2); cipher=r.val; if (r.err) return r;
|
|
left-=2;
|
|
|
|
if (session->cipher_suite!=0)
|
|
{ // don't allow a change after initial assignment
|
|
if (cipher!=session->cipher_suite)
|
|
{
|
|
r.err=BAD_HELLO;
|
|
return r;
|
|
}
|
|
}
|
|
session->cipher_suite=cipher;
|
|
|
|
r=parseIntorPull(session,1); cmp=r.val; if (r.err) return r;
|
|
left-=1; // Compression not used in TLS1.3
|
|
if (cmp!=0x00) {
|
|
r.err=NOT_TLS1_3; // don't ask
|
|
return r;
|
|
}
|
|
|
|
r=parseIntorPull(session,2); extLen=r.val; if (r.err) return r;
|
|
left-=2;
|
|
if (left!=extLen) { // Check space left is size of extensions
|
|
r.err=BAD_HELLO;
|
|
return r;
|
|
}
|
|
|
|
int supported_version=0;
|
|
// process extensions
|
|
while (extLen>0)
|
|
{
|
|
r=parseIntorPull(session,2); ext=r.val; if (r.err) return r;
|
|
r=parseIntorPull(session,2); tmplen=r.val; if (r.err) break;
|
|
if (extLen<4+tmplen)
|
|
{
|
|
r.err=BAD_HELLO;
|
|
return r;
|
|
}
|
|
extLen-=(4+tmplen);
|
|
log(IO_DEBUG,(char *)"Server Hello Extension= ",(char *)"%x",ext,NULL);
|
|
switch (ext)
|
|
{
|
|
case KEY_SHARE :
|
|
{ // actually mandatory
|
|
int glen=2;
|
|
r=parseIntorPull(session,2); kex=r.val; if (r.err) break;
|
|
if (!retry)
|
|
{ // its not a retry request
|
|
r=parseIntorPull(session,2); pklen=r.val; if (r.err) break; // FIX this first for HRR
|
|
r=parseoctadorPull(session,PK,pklen);
|
|
glen+=(2+pklen);
|
|
}
|
|
if (tmplen!=glen)
|
|
{
|
|
r.err=BAD_HELLO;
|
|
return r;
|
|
}
|
|
break;
|
|
}
|
|
case PRESHARED_KEY :
|
|
{ // Indicate acceptance of pre-shared key
|
|
if (tmplen!=2)
|
|
{
|
|
r.err=BAD_HELLO;
|
|
return r;
|
|
}
|
|
r=parseIntorPull(session,2); pskid=r.val;
|
|
break;
|
|
}
|
|
case COOKIE :
|
|
{ // Pick up a cookie
|
|
r=parseoctadorPull(session,CK,tmplen);
|
|
break;
|
|
}
|
|
case TLS_VER :
|
|
{ // report TLS version
|
|
if (tmplen!=2)
|
|
{
|
|
r.err=BAD_HELLO;
|
|
return r;
|
|
}
|
|
r=parseIntorPull(session,2); tls=r.val; if (r.err) break; // get TLS version
|
|
supported_version=tls;
|
|
break;
|
|
}
|
|
default :
|
|
r.err=UNRECOGNIZED_EXT;
|
|
break;
|
|
}
|
|
if (r.err) return r;
|
|
}
|
|
|
|
if (supported_version==0 || supported_version!=TLS1_3)
|
|
r.err=NOT_TLS1_3;
|
|
|
|
if (retry)
|
|
r.val=HANDSHAKE_RETRY;
|
|
else
|
|
r.val=SERVER_HELLO;
|
|
return r;
|
|
}
|
|
|