// // 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;imax;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;ival[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;ival[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 (lenearly_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;iCTX,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 (lenptr+=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;icipher_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; }