diff --git a/.gitignore b/.gitignore index b74800a..1f9b508 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,7 @@ Debug .project .settings/ SI +build/ +src/inc +src/lib64 +cmake-build-*/ diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 10d5e84..32f76f0 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -3,10 +3,13 @@ variables: GIT_STRATEGY: "clone" BUILD_PADDING_PREFIX: /tmp/padding_for_CPACK_RPM_BUILD_SOURCE_DIRS_PREFIX_PREFIX_PREFIX_PREFIX_PREFIX_PREFIX/ INSTALL_PREFIX: "/opt/MESA/lib/" - INSTALL_DEPENDENCY_LIBRARY: libMESA_handle_logger-devel libcjson-devel libMESA_field_stat2-devel sapp-devel framework_env libMESA_prof_load-devel sapp-devel openssl-devel libasan + INSTALL_DEPENDENCY_LIBRARY: libMESA_handle_logger-devel libcjson-devel libMESA_field_stat2-devel sapp-devel framework_env libMESA_prof_load-devel sapp-devel openssl-devel glib2-devel libasan libbreakpad_mini-devel libMESA_htable-devel systemd-devel stages: - build +- test +- package + .build_by_travis: before_script: @@ -21,6 +24,15 @@ stages: tags: - share +run_test: + stage: test + extends: .build_by_travis + script: + - yum makecache + - ./ci/travis.sh + - cd build + - ctest --verbose + branch_build_debug: stage: build extends: .build_by_travis @@ -42,7 +54,7 @@ branch_build_release: - tags develop_build_debug: - stage: build + stage: package extends: .build_by_travis variables: BUILD_TYPE: Debug @@ -61,7 +73,7 @@ develop_build_debug: - /^master.*$/i develop_build_release: - stage: build + stage: package extends: .build_by_travis variables: BUILD_TYPE: RelWithDebInfo @@ -79,7 +91,7 @@ develop_build_release: - /^master.*$/i release_build_debug: - stage: build + stage: package variables: BUILD_TYPE: Debug PACKAGE: 1 @@ -95,7 +107,7 @@ release_build_debug: - tags release_build_release: - stage: build + stage: package variables: BUILD_TYPE: RelWithDebInfo PACKAGE: 1 diff --git a/CMakeLists.txt b/CMakeLists.txt index e59a92b..24da4c2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,7 +7,6 @@ project (${lib_name}) set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) include(Version) -SET(CMAKE_C_COMPILER "/usr/bin/g++") set(CMAKE_MACOSX_RPATH 0) set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} -Wall) @@ -31,27 +30,34 @@ endif() set(CMAKE_INSTALL_PREFIX /home/mesasoft/sapp_run) +include_directories(inc) include_directories(/opt/MESA/include/MESA/) +include_directories(/usr/include/glib-2.0/) +#include_directories(/usr/include/glib-2.0/include/) +include_directories(/usr/lib64/glib-2.0/include) + +add_subdirectory(support) file(GLOB SRC - "src/*.c" "src/*.cpp" ) - -set(DNS_DEPEND_DYN_LIB ssl crypto MESA_handle_logger) +set(DEPEND_DYN_LIB ssl crypto MESA_handle_logger) # Shared Library Output add_library(quic SHARED ${SRC}) set_target_properties(quic PROPERTIES PREFIX "") -target_link_libraries(quic ${DNS_DEPEND_DYN_LIB}) +target_link_libraries(quic ${DNS_DEPEND_DYN_LIB} glib-2.0 pthread -Wl,--whole-archive libgpg-error-static -Wl,--no-whole-archive libgcrypt-static) set_target_properties(quic PROPERTIES OUTPUT_NAME ${lib_name}) +enable_testing() +add_subdirectory(test) + set(CPACK_RPM_USER_FILELIST "%config(noreplace) ${CMAKE_INSTALL_PREFIX}/plug/protocol/quic/quic.inf" "%config(noreplace) ${CMAKE_INSTALL_PREFIX}/conf/quic/quic.conf") install(TARGETS quic LIBRARY DESTINATION ${CMAKE_INSTALL_PREFIX}/plug/protocol/quic COMPONENT LIBRARIES) install(FILES bin/quic.inf DESTINATION ${CMAKE_INSTALL_PREFIX}/plug/protocol/quic COMPONENT PROFILE) install(FILES bin/quic.conf DESTINATION ${CMAKE_INSTALL_PREFIX}/conf/quic COMPONENT PROFILE) -install(FILES src/gquic.h DESTINATION /opt/MESA/include/MESA COMPONENT HEADER) +install(FILES inc/gquic.h DESTINATION /opt/MESA/include/MESA COMPONENT HEADER) include(Package) diff --git a/cmake/Version.cmake b/cmake/Version.cmake index 9b05d0b..a47944c 100644 --- a/cmake/Version.cmake +++ b/cmake/Version.cmake @@ -3,7 +3,7 @@ set(__SOURCE_AUTORESIVISION ${CMAKE_SOURCE_DIR}/autorevision.sh) set(__AUTORESIVISION ${CMAKE_BINARY_DIR}/autorevision.sh) -set(__VERSION_CACHE ${CMAKE_SOURCE_DIR}/version.txt) +set(__VERSION_CACHE ${CMAKE_BINARY_DIR}/version.txt) set(__VERSION_CONFIG ${CMAKE_BINARY_DIR}/version.cmake) file(COPY ${__SOURCE_AUTORESIVISION} DESTINATION ${CMAKE_BINARY_DIR} diff --git a/src/gquic.h b/inc/gquic.h similarity index 99% rename from src/gquic.h rename to inc/gquic.h index 0c0f182..a24f45f 100644 --- a/src/gquic.h +++ b/inc/gquic.h @@ -93,5 +93,4 @@ int quic_version_int2string(unsigned int version, char *buff, int buff_len); //ret: 0: not quic, >0: quic int quic_protocol_identify(struct streaminfo *a_stream, void *a_packet, char *out_sni, int out_sni_len); - #endif /* SRC_GQUIC_H_ */ diff --git a/src/gquic_process.c b/src/gquic_process.cpp similarity index 80% rename from src/gquic_process.c rename to src/gquic_process.cpp index cee031e..d68f262 100644 --- a/src/gquic_process.c +++ b/src/gquic_process.cpp @@ -13,11 +13,8 @@ #include "gquic_process.h" #include "quic_analysis.h" +#include "parser-quic.h" -int is_quant() -{ - -} int is_iquic(enum _QUIC_VERSION quic_version) { switch(quic_version) @@ -63,6 +60,38 @@ int is_iquic(enum _QUIC_VERSION quic_version) return FALSE; } +static int get_value(unsigned char *payload, int *offset, int len) +{ + switch(len) + { + case 1: + return (int)(payload[(*offset)++]); + break; + case 2: + (*offset)+=len; + return (int)ntohs(*(unsigned short *)(payload+*offset-len)); + break; + case 3: + (*offset)+=len; + return ((int)*(payload-2+*offset)<<16| + (int)*(payload-1+*offset)<<8| + (int)*(payload+*offset)<<0); + break; + case 4: + (*offset)+=len; + return (int)ntohl(*(unsigned int *)(payload+*offset-len)); + break; + case 32: + (*offset)+=len; + return 0; + break; + default: + break; + } + + return 0; +} + int quic_getLinkState(struct _quic_context *_context) { UCHAR state = 0; @@ -154,8 +183,8 @@ unsigned long long get_variable_length(char *p, int offset, int v_len) (unsigned long long)*((unsigned char *)(p)+2+offset)<<32| (unsigned long long)*((unsigned char *)(p)+3+offset)<<24| (unsigned long long)*((unsigned char *)(p)+4+offset)<<16| - (unsigned long long)*((unsigned char *)(p)+5+offset)<<8; - (unsigned long long)*((unsigned char *)(p)+6+offset)<<0; + (unsigned long long)*((unsigned char *)(p)+5+offset)<<8| + (unsigned long long)*((unsigned char *)(p)+6+offset)<<0; break; case 8: return (unsigned long long)*((unsigned char *)(p)+0+offset)<<56| @@ -270,7 +299,6 @@ unsigned long long get_packet_number(char* data, int offset, char pkn_len) // GQUIC version from 0 to 43 static enum _QUIC_VERSION parse_q0to43_header(struct streaminfo *pstream, struct _quic_context* _context, char *payload, int payload_len, int *used_len) { - int i=0,len=0; char public_flags=0; struct _quic_public_header *gquic_hdr=&(_context->quic_info.quic_hdr); @@ -288,18 +316,6 @@ static enum _QUIC_VERSION parse_q0to43_header(struct streaminfo *pstream, struct if(pstream->curdir==DIR_S2C && gquic_hdr->public_flags&GQUIC_PUBLIC_FLAG_VERSION) { - #if 0 - gquic_hdr->is_version_negotiation=TRUE; // Version Negotiation Packet - - gquic_hdr->negotiation_version_num=(payload_len-*used_len)/sizeof(int); - gquic_hdr->negotiation_version_list=(unsigned int *)dictator_malloc(pstream->threadnum, payload_len-*used_len); - - for(i=0; inegotiation_version_num; i++) - { - gquic_hdr->negotiation_version_list[i]=*(unsigned int *)(payload+*used_len); - *used_len+=sizeof(unsigned int); - } - #endif return QUIC_VERSION_UNKNOWN; } @@ -446,11 +462,6 @@ enum _QUIC_VERSION parse_quic_header(struct streaminfo *pstream, struct _quic_co enum _QUIC_VERSION is_quic_protocol(struct streaminfo *pstream, struct _quic_context* _context, char *payload, int payload_len, int *used_len) { - int i=0,len=0; - size_t s_id_len=0,d_id_len=0; - char d_CID[128]={0}, s_CID[128]={0}; - unsigned char s_connection_id[MAX_CONNECT_ID_LEN]={0}; - unsigned char d_connection_id[MAX_CONNECT_ID_LEN]={0}; enum _QUIC_VERSION quic_version=QUIC_VERSION_UNKNOWN; if(_context->quic_info.quic_hdr.quic_version!=QUIC_VERSION_UNKNOWN) @@ -516,7 +527,7 @@ enum _QUIC_VERSION is_quic_protocol(struct streaminfo *pstream, struct _quic_con int parse_extension_tag(struct streaminfo *pstream, struct _quic_stream **quic_stream, void *a_packet, char *payload, int payload_len, int *used_len, int tag_num) { int tag_used_num=0; - int tag_type,skip_tsg=0; + int tag_type=0; int total_tag_len=0,tag_len=0; int tag_offset_end=0,pre_tag_offset_end=0; @@ -612,7 +623,7 @@ int parse_extension_tag(struct streaminfo *pstream, struct _quic_stream **quic_s int gquic_frame_type_ack(struct streaminfo *pstream, struct _quic_context* _context, char *payload, int payload_len, int *used_len, char frame_type, void *a_packet) { unsigned char num_timestamp; - unsigned char num_blocks=0,gap_to_next_block=0;; + unsigned char num_blocks=0;; unsigned short largest_acked_delta_time=0; unsigned long long ack_block_length=0; unsigned long long largest_observed_ack=0; @@ -629,8 +640,7 @@ int gquic_frame_type_ack(struct streaminfo *pstream, struct _quic_context* _cont if(num_blocks>0) { - gap_to_next_block=*(unsigned char *)(payload+*used_len); - *used_len+=1; + *used_len+=1; //gap_to_next_block *used_len+=(num_blocks*sizeof(unsigned int)); //Ack block length } @@ -659,14 +669,12 @@ int gquic_frame_type_ack(struct streaminfo *pstream, struct _quic_context* _cont return 0; } -int gquic_frame_type_stream(struct streaminfo *pstream, struct _quic_context* _context, char *payload, int payload_len, int *used_len, char frame_type, void *a_packet) +int gquic_frame_type_stream(struct streaminfo *pstream, struct _quic_context* _context, char *payload, int payload_len, int *used_len, void *a_packet) { int ret=0; char state=APP_STATE_GIVEME; unsigned short tag_num = 0; - unsigned int stream_id, message_tag; - - stream_id=get_stream_id(pstream, _context, payload, frame_type, used_len); + unsigned int message_tag; message_tag=(unsigned int)ntohl(*(unsigned int *)(payload+*used_len)); *used_len+=4; @@ -830,12 +838,14 @@ int gquic_proc_unencrypt(struct streaminfo *pstream, struct _quic_context* _cont break; default: //Regular Frame Types if(frame_type&GQUIC_SPECIAL_FRAME_STREAM) - { - ret=gquic_frame_type_stream(pstream, _context, payload, payload_len, used_len, frame_type, a_packet); + { + stream_id=get_stream_id(pstream, _context, payload, frame_type, used_len); + ret=gquic_frame_type_stream(pstream, _context, payload, payload_len, used_len, a_packet); } else if((frame_type&0xC0)==GQUIC_SPECIAL_FRAME_ACK) // high bit set 0; (frame_type: 01nullmmB) - { - ret=gquic_frame_type_stream(pstream, _context, payload, payload_len, used_len, frame_type, a_packet); + { + stream_id=get_stream_id(pstream, _context, payload, frame_type, used_len); + ret=gquic_frame_type_stream(pstream, _context, payload, payload_len, used_len, a_packet); } else if((frame_type&0xE0)==GQUIC_SPECIAL_FRAME_CONGEST_FB) // high two bits set 0; (frame_type: 01nullmmB) { @@ -863,8 +873,6 @@ int parse_gquic_Q046(struct streaminfo *pstream, struct _quic_context* _context, { int ret=APP_STATE_GIVEME; unsigned char frame_type; - unsigned short tag_num=0; - unsigned int stream_id, message_tag; while(*used_len < payload_len) { @@ -872,8 +880,9 @@ int parse_gquic_Q046(struct streaminfo *pstream, struct _quic_context* _context, *used_len+=1; //skip frame_type if(frame_type&IQUIC_FRAME_STREAM_HEX08) - { - ret=gquic_frame_type_stream(pstream, _context, payload, payload_len, used_len, frame_type, a_packet); + { + get_stream_id(pstream, _context, payload, frame_type, used_len); + ret=gquic_frame_type_stream(pstream, _context, payload, payload_len, used_len, a_packet); } else { @@ -889,11 +898,176 @@ int parse_gquic_Q046(struct streaminfo *pstream, struct _quic_context* _context, return APP_STATE_GIVEME; } +int parse_encrypt_parameter(struct _quic_stream *quic_stream, unsigned char *payload, int payload_len, int thread_seq) +{ + int used_len=0,length=0; + while(payload_len>used_len) + { + if(payload[used_len]> 0x00 && payload[used_len]<=0x20) + { + get_value(payload, &used_len, 1); //type=1 + length=get_value(payload, &used_len, 1); // length=1 + used_len+=length; + + continue; + } + + if((*(unsigned short *)(payload+used_len)) == htons(EXT_QUIC_PARAM_USER_AGENT)) + { + quic_stream->ua_idx=quic_stream->ext_tag_num++; + get_value(payload, &used_len, 2); //type=2 + length=get_value(payload, &used_len, 1); // length=1 + get_quic_tlv((char *)payload+used_len, &(quic_stream->ext_tags[quic_stream->ua_idx]), length, EXT_QUIC_PARAM_USER_AGENT, thread_seq); + used_len+=length; + + continue; + } + + if(*(unsigned int *)(payload+used_len) == htonl(EXT_QUIC_PARAM_QUIC_VERSION)) + { + quic_stream->ver_idx=quic_stream->ext_tag_num++; + get_value(payload, &used_len, 4); //type=4 + length=get_value(payload, &used_len, 1); // length=1 + get_quic_tlv((char *)payload+used_len, &(quic_stream->ext_tags[quic_stream->ver_idx]), length, EXT_QUIC_PARAM_QUIC_VERSION, thread_seq); + *(unsigned int *)quic_stream->ext_tags[quic_stream->ver_idx].value=(unsigned int)htonl(*(unsigned int *)quic_stream->ext_tags[quic_stream->ver_idx].value); + used_len+=length; + + continue; + } + + if((*(unsigned int *)(payload+used_len))== htonl(EXT_QUIC_PARAM_GREASE_HIGH4) && (*(unsigned int *)(payload+used_len+4))== htonl(EXT_QUIC_PARAM_GREASE_LOW4)) + { + used_len+=8; //type=8 + length=get_value(payload, &used_len, 1); // length=1 + used_len+=length; + + continue; + } + + break; + } + + return 0; +} +int parse_encrypt_server_name(struct _quic_stream *quic_stream, unsigned char *payload, int payload_len, int thread_seq) +{ + int ext_len=0,used_len=0; + + quic_stream->sni_idx=quic_stream->ext_tag_num++; + get_value(payload, &used_len, 2); //Server Name List length + if(get_value(payload, &used_len, 1)==0) //Server Name type + { + ext_len=get_value(payload, &used_len, 2); //Server Name length + get_quic_tlv((char *)payload+used_len, &(quic_stream->ext_tags[quic_stream->sni_idx]), ext_len, EXTENSION_SERVER_NAME, thread_seq); + } + + return 1; +} + +int parse_encrypt_client_hello(struct streaminfo *pstream, struct _quic_stream *quic_stream, void *a_packet, unsigned char *payload, int payload_len) +{ + int skip_len=0; + int used_len=0; + int flags=0; + int ext_type=0, extension_total_len=0; + + get_value(payload, &used_len, 1); //handshake type + get_value(payload, &used_len, 3); //client hello length + get_value(payload, &used_len, 2); //ssl_version + + get_value(payload, &used_len, 32); //Random + + skip_len=(int)get_value(payload, &used_len, 1); //Session ID length + used_len+=skip_len; + + skip_len=(int)get_value(payload, &used_len, 2); //Ciper Suites length + used_len+=skip_len; + + skip_len=(int)get_value(payload, &used_len, 1); //Compression Methods + used_len+=skip_len; + + extension_total_len=(int)get_value(payload, &used_len, 2); //Extension length + + quic_stream->ext_tags=(quic_tlv_t *)dictator_malloc(pstream->threadnum, sizeof(quic_tlv_t)*3); + memset(quic_stream->ext_tags, 0, sizeof(quic_tlv_t)*3); + + while(extension_total_len>used_len) + { + ext_type=get_value(payload, &used_len, 2); //Extension type + skip_len=get_value(payload, &used_len, 2); //length + switch(ext_type) + { + case EXTENSION_SERVER_NAME: + parse_encrypt_server_name(quic_stream, payload+used_len, skip_len, pstream->threadnum); + flags=1; + break; + case EXTENSION_QUIC_PARAM: + parse_encrypt_parameter(quic_stream, payload+used_len, skip_len, pstream->threadnum); + break; + case EXTENSION_SUPPORT_GROUP: + case EXTENSION_APP_PROT_NEGO: + case EXTENSION_SIG_ALGORITHM: + case EXTENSION_KEY_SHARE: + case EXTENSION_PSK_EXCHANGE: + case EXTENSION_SUPP_SSL_VER: + case EXTENSION_COMPRESS_CERT: + break; + default: + break; + } + + used_len+=skip_len; + } + + return flags; +} + +int parse_decrypt_quic(struct streaminfo *pstream, struct _quic_context* _context, void *a_packet, unsigned char * payload, int payload_len, int *used_len) +{ + int ret=0,state=APP_STATE_GIVEME; + unsigned int quic_version=_context->quic_info.quic_hdr.quic_version; + + get_value(payload, used_len, 4); //Frame Type=1, offset=1, length=2 + + if( (quic_version>=MVFST_VERSION_00 && quic_version<=MVFST_VERSION_0F) || + (quic_version>=GQUIC_VERSION_T050 && quic_version<=GQUIC_VERSION_T059) || + (quic_version>=IQUIC_VERSION_I022 && quic_version<=IQUIC_VERSION_I029) + ) + { + if(payload[*used_len] == 0x01) + { + if(_context->quic_info.client_hello==NULL) + { + _context->quic_info.client_hello=(struct _quic_stream *)dictator_malloc(pstream->threadnum, sizeof(struct _quic_stream)); + memset(_context->quic_info.client_hello, 0, sizeof(struct _quic_stream)); + } + ret=parse_encrypt_client_hello(pstream, _context->quic_info.client_hello, a_packet, payload+*used_len, payload_len); //Frame Type=1, offset=1, length=2 + if(ret>0 && _context->call_business) + { + state=quic_callPlugins(pstream, _context, (void *)(_context->quic_info.client_hello), sizeof(void *), QUIC_CLIENT_HELLO_MASK, a_packet); + } + } + } + else if( (quic_version>=GQUIC_VERSION_Q047 && quic_version<=GQUIC_VERSION_Q059)) + { + state=gquic_frame_type_stream(pstream, _context, (char *)payload, payload_len, used_len, a_packet); + } + else + { + state=APP_STATE_DROPME; + } + + return state; +} + //cid->version->nounce->pkt num->ahn hash(12) int quic_process(struct streaminfo *pstream, struct _quic_context* _context, int thread_seq, void* a_packet) { int used_len=0; int ret=APP_STATE_GIVEME; + unsigned char decrypt_payload[1500]={0}; + unsigned int decrypt_payload_len=sizeof(decrypt_payload); + enum _QUIC_VERSION is_gquic=QUIC_VERSION_UNKNOWN; struct udpdetail *udp_detail=pstream->pudpdetail; @@ -914,7 +1088,7 @@ int quic_process(struct streaminfo *pstream, struct _quic_context* _context, int { _context->cb_version=1; ret=quic_callPlugins(pstream, _context, &(_context->quic_info.quic_hdr.quic_version), sizeof(_context->quic_info.quic_hdr.quic_version), QUIC_USEING_VERSION_MASK, a_packet); - if(ret&APP_STATE_DROPME | ret&APP_STATE_DROPPKT) + if((ret&APP_STATE_DROPME) || (ret&APP_STATE_DROPPKT)) { return ret; } @@ -929,15 +1103,34 @@ int quic_process(struct streaminfo *pstream, struct _quic_context* _context, int ret=parse_gquic_Q046(pstream, _context, a_packet, (char *)udp_detail->pdata, udp_detail->datalen, &used_len); break; default: + if( ((is_gquic>=MVFST_VERSION_00 && is_gquic<=MVFST_VERSION_0F) || + (is_gquic>=GQUIC_VERSION_Q047 && is_gquic<=GQUIC_VERSION_Q059) || + (is_gquic>=GQUIC_VERSION_T050 && is_gquic<=GQUIC_VERSION_T059) || + (is_gquic>=GQUIC_VERSION_T050 && is_gquic<=GQUIC_VERSION_T059) || + (is_gquic>=IQUIC_VERSION_I022 && is_gquic<=IQUIC_VERSION_I029) + ) + && _context->is_decrypt==0 + ) + { + _context->is_decrypt=1; + ret=dissect_quic((char *)udp_detail->pdata, udp_detail->datalen, decrypt_payload, &decrypt_payload_len); + if(ret!=1) + { + return APP_STATE_DROPME; + } + ret=parse_decrypt_quic(pstream, _context, a_packet, decrypt_payload, decrypt_payload_len, &used_len); + break; + } + ret=quic_callPlugins(pstream, _context, (char *)udp_detail->pdata, udp_detail->datalen, QUIC_APPLICATION_DATA_MASK, a_packet); - if(ret&APP_STATE_DROPME | ret&APP_STATE_DROPPKT) + if((ret&APP_STATE_DROPME) || (ret&APP_STATE_DROPPKT)) { return ret; } break; } - if(ret&APP_STATE_DROPME | ret&APP_STATE_DROPPKT) + if((ret&APP_STATE_DROPME) || (ret&APP_STATE_DROPPKT)) { return ret; } diff --git a/src/gquic_process.h b/src/gquic_process.h index a2b0df2..308cb41 100644 --- a/src/gquic_process.h +++ b/src/gquic_process.h @@ -152,6 +152,36 @@ #define TAG_RSEQ 0x52534551 #define TAG_CADR 0x43414452 +#define EXTENSION_SERVER_NAME 0x0000 +#define EXTENSION_SUPPORT_GROUP 0x000A +#define EXTENSION_APP_PROT_NEGO 0x0010 //application layer protocol negotiation +#define EXTENSION_SIG_ALGORITHM 0x000D +#define EXTENSION_KEY_SHARE 0x0033 +#define EXTENSION_PSK_EXCHANGE 0x002D +#define EXTENSION_SUPP_SSL_VER 0x002B +#define EXTENSION_QUIC_PARAM 0xFFA5 +#define EXTENSION_COMPRESS_CERT 0x001B + +#define EXT_QUIC_PARAM_MAX_IDLE_TIMEOUT 0x01 +#define EXT_QUIC_PARAM_MAX_UDP_PAYLOAD 0x03 +#define EXT_QUIC_PARAM_MAX_INIT_DATA 0x04 +#define EXT_QUIC_PARAM_MAX_STREAM_BIDI_LOCAL 0x05 +#define EXT_QUIC_PARAM_MAX_STREAM_BIDI_REMOTE 0x06 +#define EXT_QUIC_PARAM_MAX_STREAM_UNI 0x07 +#define EXT_QUIC_PARAM_MAX_STREAMS_BIDI 0x08 +#define EXT_QUIC_PARAM_MAX_STREAMS_UNI 0x09 +#define EXT_QUIC_PARAM_MAX_FRAME_SIZE 0x20 +#define EXT_QUIC_PARAM_INIT_SRC_CONN_ID 0x0F +#define EXT_QUIC_PARAM_USER_AGENT 0x7129 +#define EXT_QUIC_PARAM_NOT_YET_SUPPORTED 0x712B +#define EXT_QUIC_PARAM_QUIC_VERSION 0x80004752 +#define EXT_QUIC_PARAM_GREASE_LOW4 0x91D24E9B +#define EXT_QUIC_PARAM_GREASE_HIGH4 0xEA666DE7 + +#define EXTENSION_QUIC_PARAM_UA 0x7129 +#define EXTENSION_QUIC_PARAM_VERSION 0x4752 + + //https://github.com/quicwg/base-drafts/wiki/QUIC-Versions enum _QUIC_VERSION { @@ -237,6 +267,14 @@ enum _QUIC_VERSION //Google QUIC with TLS 50 - 59 (T050 - T059) GQUIC_VERSION_T050=0x54303530, + GQUIC_VERSION_T051=0x54303531, + GQUIC_VERSION_T052=0x54303532, + GQUIC_VERSION_T053=0x54303533, + GQUIC_VERSION_T054=0x54303534, + GQUIC_VERSION_T055=0x54303535, + GQUIC_VERSION_T056=0x54303536, + GQUIC_VERSION_T057=0x54303537, + GQUIC_VERSION_T058=0x54303538, GQUIC_VERSION_T059=0x54303539, //Google QUIC with TLS 99 (T099) @@ -263,6 +301,20 @@ enum _QUIC_VERSION //Facebook MVFST_VERSION_00=0xfaceb000, + MVFST_VERSION_01=0xfaceb001, + MVFST_VERSION_02=0xfaceb002, + MVFST_VERSION_03=0xfaceb003, + MVFST_VERSION_04=0xfaceb004, + MVFST_VERSION_05=0xfaceb005, + MVFST_VERSION_06=0xfaceb006, + MVFST_VERSION_07=0xfaceb007, + MVFST_VERSION_08=0xfaceb008, + MVFST_VERSION_09=0xfaceb009, + MVFST_VERSION_0A=0xfaceb00A, + MVFST_VERSION_0B=0xfaceb00B, + MVFST_VERSION_0C=0xfaceb00C, + MVFST_VERSION_0D=0xfaceb00D, + MVFST_VERSION_0E=0xfaceb00E, MVFST_VERSION_0F=0xfaceb00F, //IETF @@ -303,6 +355,7 @@ enum _QUIC_VERSION struct _quic_context { int is_quic; + int is_decrypt; int cb_version; int link_state; int call_business; diff --git a/src/parser-quic.cpp b/src/parser-quic.cpp new file mode 100644 index 0000000..cc90787 --- /dev/null +++ b/src/parser-quic.cpp @@ -0,0 +1,792 @@ +/** + * parser-quic.c + * + * Created on 2020-11-26 + * @author: qyc + * + * @explain: QUIC解析 + */ +#include +#include +#include + +#include "parser-quic.h" +#include "wsgcrypt.h" +#include "utils.h" +#include "pint.h" +#include "gcrypt.h" + +// #define DEBUG_PARSER_QUIC + +int gcry_init() +{ + //const char * tmp = gcry_check_version("1.8.7"); + //gcry_control(GCRYCTL_SET_THREAD_CBS,&gcry_threads_pthread); + return 0; +} + + +#define QUIC_LPT_INITIAL 0x0 +#define QUIC_LPT_0RTT 0x1 +#define QUIC_LPT_HANDSHAKE 0x2 +#define QUIC_LPT_RETRY 0x3 +/* Version Negotiation packets don't have any real packet type */ +#define QUIC_LPT_VER_NEG 0xfe +/* dummy value that is definitely not LPT */ +#define QUIC_SHORT_PACKET 0xff + + +/* + * Although the QUIC SCID/DCID length field can store at most 255, v1 limits the + * CID length to 20. + */ +#define QUIC_MAX_CID_LENGTH 20 +typedef struct _quic_cid { + unsigned char len; + unsigned char cid[QUIC_MAX_CID_LENGTH]; +} quic_cid_t; + +/* + * PROTECTED PAYLOAD DECRYPTION (done in first pass) + * + * Long packet types always use a single cipher depending on packet type. + * Short packet types always use 1-RTT secrets for packet protection (pp). + * + * Considerations: + * - QUIC packets might appear out-of-order (short packets before handshake + * message is captured), lost or retransmitted/duplicated. + * - During live capture, keys might not be immediately be available. 1-RTT + * client keys will be ready while client proceses Server Hello (Handshake). + * 1-RTT server keys will be ready while server creates Handshake message in + * response to Initial Handshake. + * - So delay cipher creation until first short packet is received. + * + * Required input from TLS dissector: TLS-Exporter 0-RTT/1-RTT secrets and + * cipher/hash algorithms. + * + * QUIC payload decryption requires proper reconstruction of the packet number + * which requires proper header decryption. The different states are: + * + * Packet type Packet number space Secrets + * Long: Initial Initial Initial secrets + * Long: Handshake Handshake Handshake + * Long: 0-RTT 0/1-RTT (appdata) 0-RTT + * Short header 0/1-RTT (appdata) 1-RTT (KP0 / KP1) + * + * Important to note is that Short Header decryption requires TWO ciphers (one + * for each key phase), but that header protection uses only KP0. Total state + * needed for each peer (client and server): + * - 3 packet number spaces: Initial, Handshake, 0/1-RTT (appdata). + * - 4 header protection ciphers: initial, 0-RTT, HS, 1-RTT. + * - 5 payload protection ciphers: initial, 0-RTT, HS, 1-RTT (KP0), 1-RTT (KP1). + */ + +typedef struct _quic_decrypt_result { + // Error message or NULL for success. + const guchar *error; + // Decrypted result on success (file-scoped). + const guint8 *data; + // Size of decrypted data. + guint data_len; +} quic_decrypt_result_t; + +/** QUIC decryption context. */ + +typedef struct _quic_hp_cipher { + // Header protection cipher. + gcry_cipher_hd_t hp_cipher; +} quic_hp_cipher; + +typedef struct _quic_pp_cipher { + // Packet protection cipher. + gcry_cipher_hd_t pp_cipher; + guint8 pp_iv[TLS13_AEAD_NONCE_LENGTH]; +} quic_pp_cipher; + +typedef struct _quic_ciphers { + quic_hp_cipher hp_cipher; + quic_pp_cipher pp_cipher; +} quic_ciphers; + +/** + * State for a single QUIC connection, identified by one or more Destination + * Connection IDs (DCID). + */ +typedef struct _quic_info_data { + guint32 version; + quic_ciphers client_initial_ciphers; + quic_ciphers server_initial_ciphers; + // Packet number spaces for Initial, Handshake and appdata. + guint64 max_client_pkn[3]; + guint64 max_server_pkn[3]; +} quic_info_data_t; + +/** Per-packet information about QUIC, populated on the first pass. */ +typedef struct _quic_packet_info { + // Reconstructed full packet number. + guint64 packet_number; + quic_decrypt_result_t decryption; + // Length of PKN (1/2/3/4) or unknown (0). + guint8 pkn_len; + // Decrypted flag byte, valid only if pkn_len is non-zero. + guint8 first_byte; +} quic_packet_info_t; + +/** + * Given a QUIC message (header + non-empty payload), the actual packet number, + * try to decrypt it using the PP cipher. + * As the header points to the original buffer with an encrypted packet number, + * the (encrypted) packet number length is also included. + * + * The actual packet number must be constructed according to + * https://tools.ietf.org/html/draft-ietf-quic-transport-22#section-12.3 + */ +static void quic_decrypt_message(quic_pp_cipher *pp_cipher, const char *payload, guint length, guint header_length, + guint8 first_byte, guint pkn_len, guint64 packet_number, quic_decrypt_result_t *result) +{ + gcry_error_t err; + guint8 *header; + guint8 nonce[TLS13_AEAD_NONCE_LENGTH]; + guint8 *buffer; + guint8 atag[16]; + guint buffer_length; + const guchar **error = &result->error; + + g_assert(pp_cipher != NULL); + g_assert(pp_cipher->pp_cipher != NULL); + g_assert(pkn_len < header_length); + g_assert(1 <= pkn_len && pkn_len <= 4); + // copy header, but replace encrypted first byte and PKN by plaintext. + header = (guint8 *)g_malloc(header_length); + memcpy(header, payload, header_length); + header[0] = first_byte; + guint i; + for (i = 0; i < pkn_len; i++) + header[header_length - 1 - i] = (guint8)(packet_number >> (8 * i)); + + // Input is "header || ciphertext (buffer) || auth tag (16 bytes)" + // buffer_length = length - (header_length + 16); + // buffer_length = 297 - (2 + 16); + buffer_length = length - (pkn_len + 16); + if (buffer_length == 0) { + *error = (const guchar *)"Decryption not possible, ciphertext is too short"; + return; + } + buffer = (guint8 *)g_malloc(buffer_length); + memcpy(buffer, payload + header_length, buffer_length); + memcpy(atag, payload + header_length + buffer_length, 16); + + memcpy(nonce, pp_cipher->pp_iv, TLS13_AEAD_NONCE_LENGTH); + // Packet number is left-padded with zeroes and XORed with write_iv + phton64(nonce + sizeof(nonce) - 8, pntoh64(nonce + sizeof(nonce) - 8) ^ packet_number); + + gcry_cipher_reset(pp_cipher->pp_cipher); + err = gcry_cipher_setiv(pp_cipher->pp_cipher, nonce, TLS13_AEAD_NONCE_LENGTH); + if (err) { + //printf("Decryption (setiv) failed: %s\n", gcry_strerror(err)); + *error = (const guchar *)"Decryption (setiv) failed"; + return; + } + + // associated data (A) is the contents of QUIC header + err = gcry_cipher_authenticate(pp_cipher->pp_cipher, header, header_length); + if (err) { + //printf("Decryption (authenticate) failed: %s\n", gcry_strerror(err)); + *error = (const guchar *)"Decryption (authenticate) failed"; + return; + } + + // Output ciphertext (C) + err = gcry_cipher_decrypt(pp_cipher->pp_cipher, buffer, buffer_length, NULL, 0); + if (err) { + //printf("Decryption (decrypt) failed: %s\n", gcry_strerror(err)); + *error = (const guchar *)"Decryption (decrypt) failed"; + return; + } + + err = gcry_cipher_checktag(pp_cipher->pp_cipher, atag, 16); + if (err) { + //printf("Decryption (checktag) failed: %s\n", gcry_strerror(err)); + *error = (const guchar *)"Decryption (checktag) failed"; + return; + } + + g_free(header); + + result->error = NULL; + result->data = buffer; + result->data_len = buffer_length; +} + +static gboolean quic_is_pp_cipher_initialized(quic_pp_cipher *pp_cipher) +{ + return pp_cipher && pp_cipher->pp_cipher; +} + +/** + * Process (protected) payload, adding the encrypted payload to the tree. If + * decryption is possible, frame dissection is also attempted. + * + * The given offset must correspond to the end of the QUIC header and begin of + * the (protected) payload. Dissected frames are appended to "tree" and expert + * info is attached to "ti" (the field with the encrypted payload). + */ +static void quic_process_payload(const char *payload, guint length, guint offset, quic_info_data_t *quic_info, + quic_packet_info_t *quic_packet, gboolean from_server, quic_pp_cipher *pp_cipher, guint8 first_byte, guint pkn_len) +{ + /* + * If no decryption error has occurred yet, try decryption on the first + * pass and store the result for later use. + */ + if (quic_is_pp_cipher_initialized(pp_cipher)) + quic_decrypt_message(pp_cipher, payload, length, offset, first_byte, pkn_len, quic_packet->packet_number, &quic_packet->decryption); +} + +/* Inspired from ngtcp2 */ +static guint64 quic_pkt_adjust_pkt_num(guint64 max_pkt_num, guint64 pkt_num, size_t n) +{ + guint64 k = max_pkt_num == G_MAXUINT64 ? max_pkt_num : max_pkt_num + 1; + guint64 u = k & ~((G_GUINT64_CONSTANT(1) << n) - 1); + guint64 a = u | pkt_num; + guint64 b = (u + (G_GUINT64_CONSTANT(1) << n)) | pkt_num; + guint64 a1 = k < a ? a - k : k - a; + guint64 b1 = k < b ? b - k : k - b; + + if (a1 < b1) + return a; + return b; +} + +/** + * Retrieve the maximum valid packet number space for a peer. + */ +static guint64 *quic_max_packet_number(quic_info_data_t *quic_info, gboolean from_server, guint8 first_byte) +{ + int pkn_space; + if ((first_byte & 0x80) && (first_byte & 0x30) >> 4 == QUIC_LPT_INITIAL) + // Long header, Initial + pkn_space = 0; + else if ((first_byte & 0x80) && (first_byte & 0x30) >> 4 == QUIC_LPT_HANDSHAKE) + // Long header, Handshake + pkn_space = 1; + else + // Long header (0-RTT) or Short Header (1-RTT appdata). + pkn_space = 2; + if (from_server) + return &quic_info->max_server_pkn[pkn_space]; + else + return &quic_info->max_client_pkn[pkn_space]; +} + +/** + * Calculate the full packet number and store it for later use. + */ +static void quic_set_full_packet_number(quic_info_data_t *quic_info, quic_packet_info_t *quic_packet, gboolean from_server, guint8 first_byte, guint32 pkn32) +{ + guint pkn_len = (first_byte & 3) + 1; + guint64 pkn_full; + guint64 max_pn = *quic_max_packet_number(quic_info, from_server, first_byte); + + // Sequential first pass, try to reconstruct full packet number. + pkn_full = quic_pkt_adjust_pkt_num(max_pn, pkn32, 8 * pkn_len); + quic_packet->pkn_len = pkn_len; + quic_packet->packet_number = pkn_full; +} + +/** + * Given a header protection cipher, a buffer and the packet number offset, + * return the unmasked first byte and packet number. + */ +static gboolean quic_decrypt_header(const char *payload, guint pn_offset, quic_hp_cipher *hp_cipher, int hp_cipher_algo, guint8 *first_byte, guint32 *pn) +{ + if (!hp_cipher->hp_cipher) + // need to know the cipher. + return FALSE; + gcry_cipher_hd_t h = hp_cipher->hp_cipher; + + // Sample is always 16 bytes and starts after PKN (assuming length 4). + // https://tools.ietf.org/html/draft-ietf-quic-tls-22#section-5.4.2 + guint8 sample[16]; + memcpy(sample, payload + pn_offset + 4, 16); + + guint8 mask[5] = { 0 }; + switch (hp_cipher_algo) { + case GCRY_CIPHER_AES128: + case GCRY_CIPHER_AES256: + // Encrypt in-place with AES-ECB and extract the mask. + if (gcry_cipher_encrypt(h, sample, sizeof(sample), NULL, 0)) + return FALSE; + memcpy(mask, sample, sizeof(mask)); + break; +#ifdef HAVE_LIBGCRYPT_CHACHA20 + case GCRY_CIPHER_CHACHA20: + // If Gcrypt receives a 16 byte IV, it will assume the buffer to be + // counter || nonce (in little endian), as desired. */ + if (gcry_cipher_setiv(h, sample, 16)) + return FALSE; + // Apply ChaCha20, encrypt in-place five zero bytes. + if (gcry_cipher_encrypt(h, mask, sizeof(mask), NULL, 0)) + return FALSE; + break; +#endif // HAVE_LIBGCRYPT_CHACHA20 + default: + return FALSE; + } + + // https://tools.ietf.org/html/draft-ietf-quic-tls-22#section-5.4.1 + guint8 packet0 = payload[0]; + if ((packet0 & 0x80) == 0x80) + // Long header: 4 bits masked + packet0 ^= mask[0] & 0x0f; + else + // Short header: 5 bits masked + packet0 ^= mask[0] & 0x1f; + guint pkn_len = (packet0 & 0x03) + 1; + + guint8 pkn_bytes[4]; + memcpy(pkn_bytes, payload + pn_offset, pkn_len); + guint32 pkt_pkn = 0; + guint i; + for (i = 0; i < pkn_len; i++) + pkt_pkn |= (pkn_bytes[i] ^ mask[1 + i]) << (8 * (pkn_len - 1 - i)); + *first_byte = packet0; + *pn = pkt_pkn; + + return TRUE; +} + +static gboolean quic_hkdf_expand_label(int hash_algo, guint8 *secret, guint secret_len, const char *label, guint8 *out, guint out_len) +{ + const StringInfo secret_si = { secret, secret_len }; + guchar *out_mem = NULL; + + if (tls13_hkdf_expand_label(hash_algo, &secret_si, "tls13 ", label, out_len, &out_mem)) { + memcpy(out, out_mem, out_len); + g_free(out_mem); + return TRUE; + } + + return FALSE; +} + +/** + * Expands the secret (length MUST be the same as the "hash_algo" digest size) + * and initialize cipher with the new key. + */ +static gboolean quic_hp_cipher_init(quic_hp_cipher *hp_cipher, int hash_algo, guint8 key_length, guint8 *secret) +{ + guchar hp_key[256/8]; + guint hash_len = gcry_md_get_algo_dlen(hash_algo); + + if (!quic_hkdf_expand_label(hash_algo, secret, hash_len, "quic hp", hp_key, key_length)) + return FALSE; + + return gcry_cipher_setkey(hp_cipher->hp_cipher, hp_key, key_length) == 0; +} + +static gboolean quic_pp_cipher_init(quic_pp_cipher *pp_cipher, int hash_algo, guint8 key_length, guint8 *secret) +{ + // Maximum key size is for AES256 cipher. + guchar write_key[256/8]; + guint hash_len = gcry_md_get_algo_dlen(hash_algo); + + if (key_length > sizeof(write_key)) + return FALSE; + + if (!quic_hkdf_expand_label(hash_algo, secret, hash_len, "quic key", write_key, key_length) || + !quic_hkdf_expand_label(hash_algo, secret, hash_len, "quic iv", pp_cipher->pp_iv, sizeof(pp_cipher->pp_iv))) + return FALSE; + + return gcry_cipher_setkey(pp_cipher->pp_cipher, write_key, key_length) == 0; +} + +static void quic_hp_cipher_reset(quic_hp_cipher *hp_cipher) +{ + gcry_cipher_close(hp_cipher->hp_cipher); + memset(hp_cipher, 0, sizeof(*hp_cipher)); +} + +static void quic_pp_cipher_reset(quic_pp_cipher *pp_cipher) +{ + gcry_cipher_close(pp_cipher->pp_cipher); + memset(pp_cipher, 0, sizeof(*pp_cipher)); +} + +/** + * Maps a Packet Protection cipher to the Packet Number protection cipher. + * See https://tools.ietf.org/html/draft-ietf-quic-tls-22#section-5.4.3 + */ +static gboolean quic_get_pn_cipher_algo(int cipher_algo, int *hp_cipher_mode) +{ + switch (cipher_algo) { + case GCRY_CIPHER_AES128: + case GCRY_CIPHER_AES256: + *hp_cipher_mode = GCRY_CIPHER_MODE_ECB; + return TRUE; +#ifdef HAVE_LIBGCRYPT_CHACHA20 + case GCRY_CIPHER_CHACHA20: + *hp_cipher_mode = GCRY_CIPHER_MODE_STREAM; + return TRUE; +#endif // HAVE_LIBGCRYPT_CHACHA20 + default: + return FALSE; + } +} + +/* + * (Re)initialize the PNE/PP ciphers using the given cipher algorithm. + * If the optional base secret is given, then its length MUST match the hash + * algorithm output. + */ +static gboolean quic_hp_cipher_prepare(quic_hp_cipher *hp_cipher, int hash_algo, int cipher_algo, guint8 *secret, const char **error) +{ + // Clear previous state (if any). + quic_hp_cipher_reset(hp_cipher); + + int hp_cipher_mode; + if (!quic_get_pn_cipher_algo(cipher_algo, &hp_cipher_mode)) { + *error = "Unsupported cipher algorithm"; + return FALSE; + } + + if (gcry_cipher_open(&hp_cipher->hp_cipher, cipher_algo, hp_cipher_mode, 0)) { + quic_hp_cipher_reset(hp_cipher); + *error = "Failed to create HP cipher"; + return FALSE; + } + + if (secret) { + guint cipher_keylen = (guint8)gcry_cipher_get_algo_keylen(cipher_algo); + if (!quic_hp_cipher_init(hp_cipher, hash_algo, cipher_keylen, secret)) { + quic_hp_cipher_reset(hp_cipher); + *error = "Failed to derive key material for HP cipher"; + return FALSE; + } + } + + return TRUE; +} + +static gboolean quic_pp_cipher_prepare(quic_pp_cipher *pp_cipher, int hash_algo, int cipher_algo, int cipher_mode, guint8 *secret, const char **error) +{ + // Clear previous state (if any). + quic_pp_cipher_reset(pp_cipher); + + int hp_cipher_mode; + if (!quic_get_pn_cipher_algo(cipher_algo, &hp_cipher_mode)) { + *error = "Unsupported cipher algorithm"; + return FALSE; + } + + if (gcry_cipher_open(&pp_cipher->pp_cipher, cipher_algo,cipher_mode, 0)) { + quic_pp_cipher_reset(pp_cipher); + *error = "Failed to create PP cipher"; + return FALSE; + } + + if (secret) { + guint cipher_keylen = (guint8) gcry_cipher_get_algo_keylen(cipher_algo); + if (!quic_pp_cipher_init(pp_cipher, hash_algo, cipher_keylen, secret)) { + quic_pp_cipher_reset(pp_cipher); + *error = "Failed to derive key material for PP cipher"; + return FALSE; + } + } + + return TRUE; +} + +static gboolean quic_ciphers_prepare(quic_ciphers *ciphers, int hash_algo, int cipher_algo, int cipher_mode, guint8 *secret, const char **error) +{ + return quic_hp_cipher_prepare(&ciphers->hp_cipher, hash_algo, cipher_algo, secret, error) && + quic_pp_cipher_prepare(&ciphers->pp_cipher, hash_algo, cipher_algo, cipher_mode, secret, error); +} + +/* Returns the QUIC draft version or 0 if not applicable. */ +static inline guint8 quic_draft_version(guint32 version) { + if ((version >> 8) == 0xff0000) + return (guint8) version; + + // Facebook mvfst, based on draft -22. + if (version == 0xfaceb001) + return 22; + + // Facebook mvfst, based on draft -27. + if (version == 0xfaceb002 || version == 0xfaceb00e) + return 27; + + // GQUIC Q050, T050 and T051: they are not really based on any drafts, + // but we must return a sensible value + if (version == 0x51303530 || version == 0x54303530 || version == 0x54303531) + return 27; + + /* + * https://tools.ietf.org/html/draft-ietf-quic-transport-32#section-15 + * "Versions that follow the pattern 0x?a?a?a?a are reserved for use in + * forcing version negotiation to be exercised" + * It is tricky to return a correct draft version: such number is primarly + * used to select a proper salt (which depends on the version itself), but + * we don't have a real version here! Let's hope that we need to handle + * only latest drafts... + */ + if ((version & 0x0F0F0F0F) == 0x0a0a0a0a) + return 29; + + return 0; +} + +static inline gboolean is_quic_draft_max(guint32 version, guint8 max_version) { + guint8 draft_version = quic_draft_version(version); + return draft_version && draft_version <= max_version; +} + +/** + * Compute the client and server initial secrets given Connection ID "cid". + * + * On success TRUE is returned and the two initial secrets are set. + * FALSE is returned on error (see "error" parameter for the reason). + */ +static gboolean quic_derive_initial_secrets(const quic_cid_t *cid, guint8 client_initial_secret[HASH_SHA2_256_LENGTH], guint8 server_initial_secret[HASH_SHA2_256_LENGTH], guint32 version, const gchar **error) +{ + /* + * https://tools.ietf.org/html/draft-ietf-quic-tls-29#section-5.2 + * + * initial_salt = 0xafbfec289993d24c9e9786f19c6111e04390a899 + * initial_secret = HKDF-Extract(initial_salt, client_dst_connection_id) + * + * client_initial_secret = HKDF-Expand-Label(initial_secret, + * "client in", "", Hash.length) + * server_initial_secret = HKDF-Expand-Label(initial_secret, + * "server in", "", Hash.length) + * + * Hash for handshake packets is SHA-256 (output size 32). + */ + static const guint8 handshake_salt_draft_22[20] = { + 0x7f, 0xbc, 0xdb, 0x0e, 0x7c, 0x66, 0xbb, 0xe9, 0x19, 0x3a, + 0x96, 0xcd, 0x21, 0x51, 0x9e, 0xbd, 0x7a, 0x02, 0x64, 0x4a + }; + static const guint8 handshake_salt_draft_23[20] = { + 0xc3, 0xee, 0xf7, 0x12, 0xc7, 0x2e, 0xbb, 0x5a, 0x11, 0xa7, + 0xd2, 0x43, 0x2b, 0xb4, 0x63, 0x65, 0xbe, 0xf9, 0xf5, 0x02, + }; + static const guint8 handshake_salt_draft_29[20] = { + 0xaf, 0xbf, 0xec, 0x28, 0x99, 0x93, 0xd2, 0x4c, 0x9e, 0x97, + 0x86, 0xf1, 0x9c, 0x61, 0x11, 0xe0, 0x43, 0x90, 0xa8, 0x99 + }; + static const guint8 hanshake_salt_draft_q50[20] = { + 0x50, 0x45, 0x74, 0xEF, 0xD0, 0x66, 0xFE, 0x2F, 0x9D, 0x94, + 0x5C, 0xFC, 0xDB, 0xD3, 0xA7, 0xF0, 0xD3, 0xB5, 0x6B, 0x45 + }; + static const guint8 hanshake_salt_draft_t50[20] = { + 0x7f, 0xf5, 0x79, 0xe5, 0xac, 0xd0, 0x72, 0x91, 0x55, 0x80, + 0x30, 0x4c, 0x43, 0xa2, 0x36, 0x7c, 0x60, 0x48, 0x83, 0x10 + }; + static const guint8 hanshake_salt_draft_t51[20] = { + 0x7a, 0x4e, 0xde, 0xf4, 0xe7, 0xcc, 0xee, 0x5f, 0xa4, 0x50, + 0x6c, 0x19, 0x12, 0x4f, 0xc8, 0xcc, 0xda, 0x6e, 0x03, 0x3d + }; + + gcry_error_t err; + guint8 secret[HASH_SHA2_256_LENGTH]; + + if (version == 0x51303530) + err = hkdf_extract(GCRY_MD_SHA256, hanshake_salt_draft_q50, sizeof(hanshake_salt_draft_q50), cid->cid, cid->len, secret); + else if (version == 0x54303530) + err = hkdf_extract(GCRY_MD_SHA256, hanshake_salt_draft_t50, sizeof(hanshake_salt_draft_t50), cid->cid, cid->len, secret); + else if (version == 0x54303531) + err = hkdf_extract(GCRY_MD_SHA256, hanshake_salt_draft_t51, sizeof(hanshake_salt_draft_t51), cid->cid, cid->len, secret); + else if (is_quic_draft_max(version, 22)) + err = hkdf_extract(GCRY_MD_SHA256, handshake_salt_draft_22, sizeof(handshake_salt_draft_22), cid->cid, cid->len, secret); + else if (is_quic_draft_max(version, 28)) + err = hkdf_extract(GCRY_MD_SHA256, handshake_salt_draft_23, sizeof(handshake_salt_draft_23), cid->cid, cid->len, secret); + else + err = hkdf_extract(GCRY_MD_SHA256, handshake_salt_draft_29, sizeof(handshake_salt_draft_29), cid->cid, cid->len, secret); + if (err) { + //printf("Failed to extract secrets: %s\n", gcry_strerror(err)); + *error = "Failed to extract secrets"; + return FALSE; + } + + if (!quic_hkdf_expand_label(GCRY_MD_SHA256, secret, sizeof(secret), "client in", client_initial_secret, HASH_SHA2_256_LENGTH)) { + *error = "Key expansion (client) failed"; + return FALSE; + } + + if (!quic_hkdf_expand_label(GCRY_MD_SHA256, secret, sizeof(secret), "server in", server_initial_secret, HASH_SHA2_256_LENGTH)) { + *error = "Key expansion (server) failed"; + return FALSE; + } + + *error = NULL; + + return TRUE; +} + +static gboolean quic_create_initial_decoders(const quic_cid_t *cid, const gchar **error, quic_info_data_t *quic_info) +{ + unsigned char client_secret[HASH_SHA2_256_LENGTH]; + unsigned char server_secret[HASH_SHA2_256_LENGTH]; + + if (!quic_derive_initial_secrets(cid, client_secret, server_secret, quic_info->version, error)) + return -1; + + // Packet numbers are protected with AES128-CTR, + // initial packets are protected with AEAD_AES_128_GCM. + if (!quic_ciphers_prepare(&quic_info->client_initial_ciphers, GCRY_MD_SHA256, GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_GCM, client_secret, error)) + { + return FALSE; + } + + if(!quic_ciphers_prepare(&quic_info->server_initial_ciphers, GCRY_MD_SHA256, GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_GCM, server_secret, error)) + { + quic_hp_cipher_reset(&quic_info->client_initial_ciphers.hp_cipher); + quic_pp_cipher_reset(&quic_info->client_initial_ciphers.pp_cipher); + + return FALSE; + } + + return TRUE; +} + +static int quic_extract_header(const char *payload, unsigned char *long_packet_type, unsigned int *version, quic_cid_t *dcid, quic_cid_t *scid) +{ + unsigned int offset = 0; + + unsigned char packet_type = payload[offset]; + unsigned char is_long_header = packet_type & 0x80; + if (is_long_header) + // long header form + *long_packet_type = (packet_type & 0x30) >> 4; + else + // short header form, store dummy value that is not a long packet type. + *long_packet_type = QUIC_SHORT_PACKET; + offset++; + + *version = pntoh32((unsigned int *)&payload[offset]); + + if (is_long_header) { + // VN packets don't have any real packet type field, + // even if they have a long header: use a dummy value */ + if (*version == 0x00000000) + *long_packet_type = QUIC_LPT_VER_NEG; + + // skip version + offset += 4; + + // read DCID and SCID (both are prefixed by a length byte). + unsigned char dcil = payload[offset]; + offset++; + + if (dcil && dcil <= QUIC_MAX_CID_LENGTH) { + memcpy(dcid->cid, &payload[offset], dcil); + dcid->len = dcil; + } + offset += dcil; + + unsigned char scil = payload[offset]; + offset++; + + if (scil && scil <= QUIC_MAX_CID_LENGTH) { + memcpy(scid->cid, &payload[offset], scil); + scid->len = scil; + } + offset += scil; + } + else { + // Definitely not draft -10, set version to dummy value. + *version = 0; + // For short headers, the DCID length is unknown and could be 0 or + // anything from 1 to 20 bytes. Copy the maximum possible and let the + // consumer truncate it as necessary. + memcpy(dcid->cid, &payload[offset], QUIC_MAX_CID_LENGTH); + dcid->len = QUIC_MAX_CID_LENGTH; + offset += QUIC_MAX_CID_LENGTH; + } + + return offset; +} + +int dissect_quic(const char *payload, unsigned int length, unsigned char *out, unsigned int *out_length) +{ + guint offset = 0; + quic_packet_info_t quic_packet; + quic_info_data_t conn; + unsigned char long_packet_type; + quic_cid_t dcid = {.len=0}, scid = {.len=0}; + guint64 token_length, payload_length; + const char *error = NULL; + guint8 first_byte = 0; + const gboolean from_server = FALSE; + quic_ciphers *ciphers = NULL; + int ret; + + memset(&quic_packet, 0, sizeof(quic_packet_info_t)); + memset(&conn, 0, sizeof(quic_info_data_t)); + + ret = quic_extract_header(payload, &long_packet_type, &conn.version, &dcid, &scid); + if (ret < 0) + { + return -1; + } + + if (long_packet_type == QUIC_LPT_INITIAL) + { + // Create new decryption context based on the Client Connection ID + // from the *very first* Client Initial packet. + quic_create_initial_decoders(&dcid, &error, &conn); + if (!error) + { + guint32 pkn32 = 0; + // PKN is after type(1) + version(4) + DCIL+DCID + SCIL+SCID + guint pn_offset = 1 + 4 + 1 + dcid.len + 1 + scid.len; + pn_offset += tvb_get_varint(payload, pn_offset, 8, &token_length, ENC_VARINT_QUIC); + pn_offset += (guint)token_length; + // printf("%d\n", token_length); + + pn_offset += tvb_get_varint(payload, pn_offset, 8, &payload_length, ENC_VARINT_QUIC); + // printf("%d\n", payload_length); + + // Assume failure unless proven otherwise. + ciphers = &conn.client_initial_ciphers; + error = "Header deprotection failed"; + if (quic_decrypt_header(payload, pn_offset, &ciphers->hp_cipher, GCRY_CIPHER_AES128, &first_byte, &pkn32)) + error = NULL; + if (!error) { + quic_set_full_packet_number(&conn, &quic_packet, from_server, first_byte, pkn32); + quic_packet.first_byte = first_byte; + } + + // Payload + // skip type(1) + version(4) + DCIL+DCID + SCIL+SCID + len_token_length + token_length + len_payload_length + len_packet_number + offset = pn_offset + quic_packet.pkn_len; + //quic_process_payload(payload, length, offset, &conn, &quic_packet, from_server, &ciphers->pp_cipher, first_byte, quic_packet.pkn_len); + quic_process_payload(payload, payload_length, offset, &conn, &quic_packet, from_server, &ciphers->pp_cipher, first_byte, quic_packet.pkn_len); + + // Out + if (!quic_packet.decryption.error) + { + memcpy(out, quic_packet.decryption.data, quic_packet.decryption.data_len); + *out_length = quic_packet.decryption.data_len; + + g_free((gpointer)quic_packet.decryption.data); + quic_packet.decryption.data = NULL; + + ret=1; + } + else + { + ret=0; + } + + quic_hp_cipher_reset(&conn.client_initial_ciphers.hp_cipher); + quic_pp_cipher_reset(&conn.client_initial_ciphers.pp_cipher); + quic_hp_cipher_reset(&conn.server_initial_ciphers.hp_cipher); + quic_pp_cipher_reset(&conn.server_initial_ciphers.pp_cipher); + + return ret; + } + } + + return 0; +} + diff --git a/src/parser-quic.h b/src/parser-quic.h new file mode 100644 index 0000000..a295bb5 --- /dev/null +++ b/src/parser-quic.h @@ -0,0 +1,24 @@ +/** + * parser-quic.h + * + * Created on 2020-11-26 + * @author: qyc + * + * + */ +#ifndef PARSER_QUIC_H +#define PARSER_QUIC_H + +#ifdef __cplusplus +extern "C" { +#endif + +/*ret: 1 sucess*/ +int dissect_quic(const char *payload, unsigned int length, unsigned char *out, unsigned int *out_length); + +int gcry_init(); +#ifdef __cplusplus +} +#endif + +#endif //PARSER_QUIC_H diff --git a/src/pint.h b/src/pint.h new file mode 100644 index 0000000..11ff8c9 --- /dev/null +++ b/src/pint.h @@ -0,0 +1,213 @@ +/* pint.h + * Definitions for extracting and translating integers safely and portably + * via pointers. + * + * Wireshark - Network traffic analyzer + * By Gerald Combs + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef __PINT_H__ +#define __PINT_H__ + +#include + +/* Routines that take a possibly-unaligned pointer to a 16-bit, 24-bit, + * 32-bit, 40-bit, ... 64-bit integral quantity, in a particular byte + * order, and fetch the value and return it in host byte order. + * + * The pntohN() routines fetch big-endian values; the pletohN() routines + * fetch little-endian values. + */ + +static inline guint16 pntoh16(const void *p) +{ + return (guint16)*((const guint8 *)(p)+0)<<8| + (guint16)*((const guint8 *)(p)+1)<<0; +} + +static inline guint32 pntoh24(const void *p) +{ + return (guint32)*((const guint8 *)(p)+0)<<16| + (guint32)*((const guint8 *)(p)+1)<<8| + (guint32)*((const guint8 *)(p)+2)<<0; +} + +static inline guint32 pntoh32(const void *p) +{ + return (guint32)*((const guint8 *)(p)+0)<<24| + (guint32)*((const guint8 *)(p)+1)<<16| + (guint32)*((const guint8 *)(p)+2)<<8| + (guint32)*((const guint8 *)(p)+3)<<0; +} + +static inline guint64 pntoh40(const void *p) +{ + return (guint64)*((const guint8 *)(p)+0)<<32| + (guint64)*((const guint8 *)(p)+1)<<24| + (guint64)*((const guint8 *)(p)+2)<<16| + (guint64)*((const guint8 *)(p)+3)<<8| + (guint64)*((const guint8 *)(p)+4)<<0; +} + +static inline guint64 pntoh48(const void *p) +{ + return (guint64)*((const guint8 *)(p)+0)<<40| + (guint64)*((const guint8 *)(p)+1)<<32| + (guint64)*((const guint8 *)(p)+2)<<24| + (guint64)*((const guint8 *)(p)+3)<<16| + (guint64)*((const guint8 *)(p)+4)<<8| + (guint64)*((const guint8 *)(p)+5)<<0; +} + +static inline guint64 pntoh56(const void *p) +{ + return (guint64)*((const guint8 *)(p)+0)<<48| + (guint64)*((const guint8 *)(p)+1)<<40| + (guint64)*((const guint8 *)(p)+2)<<32| + (guint64)*((const guint8 *)(p)+3)<<24| + (guint64)*((const guint8 *)(p)+4)<<16| + (guint64)*((const guint8 *)(p)+5)<<8| + (guint64)*((const guint8 *)(p)+6)<<0; +} + +static inline guint64 pntoh64(const void *p) +{ + return (guint64)*((const guint8 *)(p)+0)<<56| + (guint64)*((const guint8 *)(p)+1)<<48| + (guint64)*((const guint8 *)(p)+2)<<40| + (guint64)*((const guint8 *)(p)+3)<<32| + (guint64)*((const guint8 *)(p)+4)<<24| + (guint64)*((const guint8 *)(p)+5)<<16| + (guint64)*((const guint8 *)(p)+6)<<8| + (guint64)*((const guint8 *)(p)+7)<<0; +} + +static inline guint16 pletoh16(const void *p) +{ + return (guint16)*((const guint8 *)(p)+1)<<8| + (guint16)*((const guint8 *)(p)+0)<<0; +} + +static inline guint32 pletoh24(const void *p) +{ + return (guint32)*((const guint8 *)(p)+2)<<16| + (guint32)*((const guint8 *)(p)+1)<<8| + (guint32)*((const guint8 *)(p)+0)<<0; +} + +static inline guint32 pletoh32(const void *p) +{ + return (guint32)*((const guint8 *)(p)+3)<<24| + (guint32)*((const guint8 *)(p)+2)<<16| + (guint32)*((const guint8 *)(p)+1)<<8| + (guint32)*((const guint8 *)(p)+0)<<0; +} + +static inline guint64 pletoh40(const void *p) +{ + return (guint64)*((const guint8 *)(p)+4)<<32| + (guint64)*((const guint8 *)(p)+3)<<24| + (guint64)*((const guint8 *)(p)+2)<<16| + (guint64)*((const guint8 *)(p)+1)<<8| + (guint64)*((const guint8 *)(p)+0)<<0; +} + +static inline guint64 pletoh48(const void *p) +{ + return (guint64)*((const guint8 *)(p)+5)<<40| + (guint64)*((const guint8 *)(p)+4)<<32| + (guint64)*((const guint8 *)(p)+3)<<24| + (guint64)*((const guint8 *)(p)+2)<<16| + (guint64)*((const guint8 *)(p)+1)<<8| + (guint64)*((const guint8 *)(p)+0)<<0; +} + +static inline guint64 pletoh56(const void *p) +{ + return (guint64)*((const guint8 *)(p)+6)<<48| + (guint64)*((const guint8 *)(p)+5)<<40| + (guint64)*((const guint8 *)(p)+4)<<32| + (guint64)*((const guint8 *)(p)+3)<<24| + (guint64)*((const guint8 *)(p)+2)<<16| + (guint64)*((const guint8 *)(p)+1)<<8| + (guint64)*((const guint8 *)(p)+0)<<0; +} + +static inline guint64 pletoh64(const void *p) +{ + return (guint64)*((const guint8 *)(p)+7)<<56| + (guint64)*((const guint8 *)(p)+6)<<48| + (guint64)*((const guint8 *)(p)+5)<<40| + (guint64)*((const guint8 *)(p)+4)<<32| + (guint64)*((const guint8 *)(p)+3)<<24| + (guint64)*((const guint8 *)(p)+2)<<16| + (guint64)*((const guint8 *)(p)+1)<<8| + (guint64)*((const guint8 *)(p)+0)<<0; +} +/* Pointer routines to put items out in a particular byte order. + * These will work regardless of the byte alignment of the pointer. + */ + +static inline void phton16(guint8 *p, guint16 v) +{ + p[0] = (guint8)(v >> 8); + p[1] = (guint8)(v >> 0); +} + +static inline void phton32(guint8 *p, guint32 v) +{ + p[0] = (guint8)(v >> 24); + p[1] = (guint8)(v >> 16); + p[2] = (guint8)(v >> 8); + p[3] = (guint8)(v >> 0); +} + +static inline void phton64(guint8 *p, guint64 v) { + p[0] = (guint8)(v >> 56); + p[1] = (guint8)(v >> 48); + p[2] = (guint8)(v >> 40); + p[3] = (guint8)(v >> 32); + p[4] = (guint8)(v >> 24); + p[5] = (guint8)(v >> 16); + p[6] = (guint8)(v >> 8); + p[7] = (guint8)(v >> 0); +} + +static inline void phtole32(guint8 *p, guint32 v) { + p[0] = (guint8)(v >> 0); + p[1] = (guint8)(v >> 8); + p[2] = (guint8)(v >> 16); + p[3] = (guint8)(v >> 24); +} + +static inline void phtole64(guint8 *p, guint64 v) { + p[0] = (guint8)(v >> 0); + p[1] = (guint8)(v >> 8); + p[2] = (guint8)(v >> 16); + p[3] = (guint8)(v >> 24); + p[4] = (guint8)(v >> 32); + p[5] = (guint8)(v >> 40); + p[6] = (guint8)(v >> 48); + p[7] = (guint8)(v >> 56); +} + +/* Subtract two guint32s with respect to wraparound */ +#define guint32_wraparound_diff(higher, lower) ((higher>lower)?(higher-lower):(higher+0xffffffff-lower+1)) + +#endif /* PINT_H */ + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local Variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * ex: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/src/quic_analysis.c b/src/quic_analysis.cpp similarity index 100% rename from src/quic_analysis.c rename to src/quic_analysis.cpp diff --git a/src/utils.cpp b/src/utils.cpp new file mode 100644 index 0000000..9af0ffa --- /dev/null +++ b/src/utils.cpp @@ -0,0 +1,136 @@ +/** + * utils.c + * + * Created on 2020-11-27 + * @author: qyc + * + * @explain: + */ +#include +#include +#include + +#include "utils.h" +#include "wsgcrypt.h" +#include "pint.h" + +/* + * Computes HKDF-Expand-Label(Secret, Label, Hash(context_value), Length) with a + * custom label prefix. If "context_hash" is NULL, then an empty context is + * used. Otherwise it must have the same length as the hash algorithm output. + */ +static gboolean tls13_hkdf_expand_label_context(int md, const StringInfo *secret, const char *label_prefix, const char *label, const guint8 *context_hash, guint8 context_length, guint16 out_len, guchar **out) +{ + /* RFC 8446 Section 7.1: + * HKDF-Expand-Label(Secret, Label, Context, Length) = + * HKDF-Expand(Secret, HkdfLabel, Length) + * struct { + * uint16 length = Length; + * opaque label<7..255> = "tls13 " + Label; // "tls13 " is label prefix. + * opaque context<0..255> = Context; + * } HkdfLabel; + * + * RFC 5869 HMAC-based Extract-and-Expand Key Derivation Function (HKDF): + * HKDF-Expand(PRK, info, L) -> OKM + */ + gcry_error_t err; + const guint label_prefix_length = (guint)strlen(label_prefix); + const guint label_length = (guint)strlen(label); + + // Some sanity checks + g_assert(label_length > 0 && label_prefix_length + label_length <= 255); + + // info = HkdfLabel { length, label, context } + GByteArray *info = g_byte_array_new(); + const guint16 length = g_htons(out_len); + g_byte_array_append(info, (const guint8 *)&length, sizeof(length)); + + const guint8 label_vector_length = label_prefix_length + label_length; + g_byte_array_append(info, &label_vector_length, 1); + g_byte_array_append(info, (const guint8 *)label_prefix, label_prefix_length); + g_byte_array_append(info, (const guint8 *)label, label_length); + + g_byte_array_append(info, &context_length, 1); + if (context_length) + g_byte_array_append(info, context_hash, context_length); + + *out = (guchar *)g_malloc(out_len); + err = hkdf_expand(md, secret->data, secret->data_len, info->data, info->len, *out, out_len); + g_byte_array_free(info, TRUE); + + if (err) { + printf("%s failed %d: %s\n", G_STRFUNC, md, gcry_strerror(err)); + g_free(*out); + *out = NULL; + return FALSE; + } + + return TRUE; +} + +gboolean tls13_hkdf_expand_label(int md, const StringInfo *secret, const char *label_prefix, const char *label, guint16 out_len, guchar **out) +{ + return tls13_hkdf_expand_label_context(md, secret, label_prefix, label, NULL, 0, out_len, out); +} + +static guint8 tvb_get_guint8(const char *tvb, const gint offset) +{ + const guint8 *ptr; + + ptr = (guint8 *)tvb + offset; + return *ptr; +} + +static guint16 tvb_get_ntohs(const char *tvb, const gint offset) +{ + const guint8 *ptr; + + ptr = (guint8 *)tvb + offset; + return pntoh16(ptr); +} + +static guint32 tvb_get_ntohl(const char *tvb, const gint offset) +{ + const guint8 *ptr; + + ptr = (guint8 *)tvb + offset; + return pntoh32(ptr); +} + +static guint64 tvb_get_ntoh64(const char *tvb, const gint offset) +{ + const guint8 *ptr; + + ptr = (guint8 *)tvb + offset; + return pntoh64(ptr); +} + +guint tvb_get_varint(const char *tvb, guint offset, guint maxlen, guint64 *value, const guint encoding) +{ + *value = 0; + + if (encoding & ENC_VARINT_QUIC) { + // calculate variable length + *value = tvb_get_guint8(tvb, offset); + switch((*value) >> 6) { + case 0: /* 0b00 => 1 byte length (6 bits Usable) */ + (*value) &= 0x3F; + return 1; + case 1: /* 0b01 => 2 bytes length (14 bits Usable) */ + *value = tvb_get_ntohs(tvb, offset) & 0x3FFF; + return 2; + case 2: /* 0b10 => 4 bytes length (30 bits Usable) */ + *value = tvb_get_ntohl(tvb, offset) & 0x3FFFFFFF; + return 4; + case 3: /* 0b11 => 8 bytes length (62 bits Usable) */ + *value = tvb_get_ntoh64(tvb, offset) & G_GUINT64_CONSTANT(0x3FFFFFFFFFFFFFFF); + return 8; + default: /* No Possible */ + g_assert_not_reached(); + break; + } + } + + // 10 bytes scanned, but no bytes' msb is zero + return 0; +} diff --git a/src/utils.h b/src/utils.h new file mode 100644 index 0000000..58c8955 --- /dev/null +++ b/src/utils.h @@ -0,0 +1,43 @@ +/** + * utils.h + * + * Created on 2020-11-27 + * @author: qyc + * + * @explain: + */ +#ifndef UTILS_H +#define UTILS_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "glib.h" + +/* + * Decodes a variable-length integer used in QUIC protocol + * See https://tools.ietf.org/html/draft-ietf-quic-transport-08#section-8.1 + */ +#define ENC_VARINT_QUIC 0x00000004 + +/* Explicit and implicit nonce length (RFC 5116 - Section 3.2.1) */ +#define TLS13_AEAD_NONCE_LENGTH 12 + + +/* XXX Should we use GByteArray instead? */ +typedef struct _StringInfo { + // Backing storage which may be larger than data_len + guchar *data; + // Length of the meaningful part of data + guint data_len; +} StringInfo; + +gboolean tls13_hkdf_expand_label(int md, const StringInfo *secret, const char *label_prefix, const char *label, guint16 out_len, guchar **out); +guint tvb_get_varint(const char *tvb, guint offset, guint maxlen, guint64 *value, const guint encoding); + +#ifdef __cplusplus +} +#endif + +#endif //UTILS_H diff --git a/src/wsgcrypt.cpp b/src/wsgcrypt.cpp new file mode 100644 index 0000000..c6b89a5 --- /dev/null +++ b/src/wsgcrypt.cpp @@ -0,0 +1,69 @@ +/** + * wsgcrypt.c + * + * Created on 2020-11-26 + * @author: qyc + * + * @explain: + */ +#include +#include +#include + +#include "wsgcrypt.h" + +gcry_error_t ws_hmac_buffer(int algo, void *digest, const void *buffer, size_t length, const void *key, size_t keylen) +{ + gcry_md_hd_t hmac_handle; + gcry_error_t result = gcry_md_open(&hmac_handle, algo, GCRY_MD_FLAG_HMAC); + if (result) { + return result; + } + result = gcry_md_setkey(hmac_handle, key, keylen); + if (result) { + gcry_md_close(hmac_handle); + return result; + } + gcry_md_write(hmac_handle, buffer, length); + memcpy(digest, gcry_md_read(hmac_handle, 0), gcry_md_get_algo_dlen(algo)); + gcry_md_close(hmac_handle); + return GPG_ERR_NO_ERROR; +} + +gcry_error_t hkdf_expand(int hashalgo, const guint8 *prk, guint prk_len, const guint8 *info, guint info_len, guint8 *out, guint out_len) +{ + // Current maximum hash output size: 48 bytes for SHA-384. + guchar lastoutput[48]; + gcry_md_hd_t h; + gcry_error_t err; + const guint hash_len = gcry_md_get_algo_dlen(hashalgo); + + // Some sanity checks + if (!(out_len > 0 && out_len <= 255 * hash_len) || !(hash_len > 0 && hash_len <= sizeof(lastoutput))) + return GPG_ERR_INV_ARG; + + err = gcry_md_open(&h, hashalgo, GCRY_MD_FLAG_HMAC); + if (err) + return err; + + guint offset; + for (offset = 0; offset < out_len; offset += hash_len) { + gcry_md_reset(h); + // Set PRK + gcry_md_setkey(h, prk, prk_len); + if (offset > 0) + // T(1..N) + gcry_md_write(h, lastoutput, hash_len); + // info + gcry_md_write(h, info, info_len); + // constant 0x01..N + gcry_md_putc(h, (guint8)(offset / hash_len + 1)); + + memcpy(lastoutput, gcry_md_read(h, hashalgo), hash_len); + memcpy(out + offset, lastoutput, MIN(hash_len, out_len - offset)); + } + + gcry_md_close(h); + + return 0; +} diff --git a/src/wsgcrypt.h b/src/wsgcrypt.h new file mode 100644 index 0000000..1dc7416 --- /dev/null +++ b/src/wsgcrypt.h @@ -0,0 +1,78 @@ +/** + * wsgcrypt.h + * + * Created on 2020-11-26 + * @author: qyc + * + * @explain: + */ +#ifndef WSGCRYPT_H +#define WSGCRYPT_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "gcrypt.h" +#include "glib.h" + +/* + * Define HAVE_LIBGCRYPT_AEAD here, because it's used in several source + * files. + */ +#if GCRYPT_VERSION_NUMBER >= 0x010600 /* 1.6.0 */ +/* Whether to provide support for authentication in addition to decryption. */ +#define HAVE_LIBGCRYPT_AEAD +#endif + +/* + * Define some other "do we have?" items as well. + */ +#if GCRYPT_VERSION_NUMBER >= 0x010700 /* 1.7.0 */ +/* Whether ChaCh20 PNE can be supported. */ +#define HAVE_LIBGCRYPT_CHACHA20 +/* Whether AEAD_CHACHA20_POLY1305 can be supported. */ +#define HAVE_LIBGCRYPT_CHACHA20_POLY1305 +#endif + +#define HASH_SHA2_256_LENGTH 32 + +/* Convenience function to calculate the HMAC from the data in BUFFER + of size LENGTH with key KEY of size KEYLEN using the algorithm ALGO avoiding the creating of a + hash object. The hash is returned in the caller provided buffer + DIGEST which must be large enough to hold the digest of the given + algorithm. */ +gcry_error_t ws_hmac_buffer(int algo, void *digest, const void *buffer, size_t length, const void *key, size_t keylen); + +/** + * RFC 5869 HMAC-based Extract-and-Expand Key Derivation Function (HKDF): + * HKDF-Expand(PRK, info, L) -> OKM + * + * @param hashalgo [in] Libgcrypt hash algorithm identifier. + * @param prk [in] Pseudo-random key. + * @param prk_len [in] Length of prk. + * @param info [in] Optional context (can be NULL if info_len is zero). + * @param info_len [in] Length of info. + * @param out [out] Output keying material. + * @param out_len [in] Size of output keying material. + * @return 0 on success and an error code otherwise. + */ +gcry_error_t hkdf_expand(int hashalgo, const guint8 *prk, guint prk_len, const guint8 *info, guint info_len, guint8 *out, guint out_len); + +/* + * Calculate HKDF-Extract(salt, IKM) -> PRK according to RFC 5869. + * Caller MUST ensure that 'prk' is large enough to store the digest from hash + * algorithm 'hashalgo' (e.g. 32 bytes for SHA-256). + */ +static inline gcry_error_t hkdf_extract(int hashalgo, const guint8 *salt, size_t salt_len, const guint8 *ikm, size_t ikm_len, guint8 *prk) +{ + /* PRK = HMAC-Hash(salt, IKM) where salt is key, and IKM is input. */ + return ws_hmac_buffer(hashalgo, prk, ikm, ikm_len, salt, salt_len); +} + + +#ifdef __cplusplus +} +#endif + +#endif //WSGCRYPT_H diff --git a/support/CMakeLists.txt b/support/CMakeLists.txt new file mode 100644 index 0000000..460ca9b --- /dev/null +++ b/support/CMakeLists.txt @@ -0,0 +1,34 @@ +include(ExternalProject) + +### libgpg-error +ExternalProject_Add(libgpg-error PREFIX libgpg-error + URL ${CMAKE_CURRENT_SOURCE_DIR}/libgpg-error-1.42.tar.bz2 + URL_MD5 133fed221ba8f63f5842858a1ff67cb3 + BUILD_COMMAND "" + CONFIGURE_COMMAND CPPFLAGS=-fPIC ./configure --enable-static --prefix= CFLAGS=-fPIC CXXFLAGS=-fPIC LDFLAGS=-fPIC + BUILD_IN_SOURCE 1) + +ExternalProject_Get_Property(libgpg-error INSTALL_DIR) +file(MAKE_DIRECTORY ${INSTALL_DIR}/include) + +add_library(libgpg-error-static STATIC IMPORTED GLOBAL) +add_dependencies(libgpg-error-static libgpg-error) +set_property(TARGET libgpg-error-static PROPERTY IMPORTED_LOCATION ${INSTALL_DIR}/lib/libgpg-error.a) +set_property(TARGET libgpg-error-static PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${INSTALL_DIR}/include/) + +### libgcrypt +ExternalProject_Add(libgcrypt PREFIX libgcrypt + URL ${CMAKE_CURRENT_SOURCE_DIR}/libgcrypt-1.9.4.tar.bz2 + URL_MD5 edc7becfe09c75d8f95ff7623e40c52e + BUILD_COMMAND "" + DEPENDS libgpg-error-static + CONFIGURE_COMMAND CPPFLAGS=-fPIC ./configure --enable-static --disable-doc --prefix= --with-libgpg-error-prefix=${CMAKE_CURRENT_BINARY_DIR}/libgpg-error/ CFLAGS=-fPIC CXXFLAGS=-fPIC LDFLAGS=-fPIC + BUILD_IN_SOURCE 1) + +ExternalProject_Get_Property(libgcrypt INSTALL_DIR) +file(MAKE_DIRECTORY ${INSTALL_DIR}/include) + +add_library(libgcrypt-static STATIC IMPORTED GLOBAL) +add_dependencies(libgcrypt-static libgcrypt) +set_property(TARGET libgcrypt-static PROPERTY IMPORTED_LOCATION ${INSTALL_DIR}/lib/libgcrypt.a) +set_property(TARGET libgcrypt-static PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${INSTALL_DIR}/include/) diff --git a/support/libgcrypt-1.9.4.tar.bz2 b/support/libgcrypt-1.9.4.tar.bz2 new file mode 100644 index 0000000..0eb0863 Binary files /dev/null and b/support/libgcrypt-1.9.4.tar.bz2 differ diff --git a/support/libgpg-error-1.42.tar.bz2 b/support/libgpg-error-1.42.tar.bz2 new file mode 100644 index 0000000..e464892 Binary files /dev/null and b/support/libgpg-error-1.42.tar.bz2 differ diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..4f07859 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,41 @@ +cmake_minimum_required (VERSION 2.8) + +set(lib_name quic) +project(${lib_name}_test) + +include(ExternalProject) +#### Protoco_test_run + + +ExternalProject_Add(ProtoTest PREFIX ProtoTest + URL ${CMAKE_CURRENT_SOURCE_DIR}/test_protocol_run.zip + URL_MD5 71d8284b59af0286b5f31f0a3160bc44 + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX= -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + COMMAND ${CMAKE_COMMAND} -E make_directory /conf/${lib_name}/ + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_SOURCE_DIR}/bin/${lib_name}.conf /conf/${lib_name}/ + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_SOURCE_DIR}/conflist.inf /plug/ + COMMAND ${CMAKE_COMMAND} -E make_directory /plug/protocol/${lib_name}/ + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_SOURCE_DIR}/bin/${lib_name}.inf /plug/protocol/${lib_name}/ + COMMAND ${CMAKE_COMMAND} -E make_directory /plug/business/${lib_name}_test_plug/ + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_SOURCE_DIR}/test/${lib_name}_test_plug.inf /plug/business/${lib_name}_test_plug/) + +ExternalProject_Get_Property(ProtoTest INSTALL_DIR) +ExternalProject_Get_Property(ProtoTest SOURCE_DIR) +set(PROTO_TEST_RUN_DIR ${SOURCE_DIR}) + +add_executable(proto_test_main IMPORTED GLOBAL) +add_dependencies(proto_test_main ProtoTest) +set_property(TARGET proto_test_main PROPERTY IMPORTED_LOCATION ${SOURCE_DIR}/test_protocol_plug_main) + + +add_library(${lib_name}_test_plug SHARED ${lib_name}_test_plug.cpp) +target_link_libraries(${lib_name}_test_plug MESA_prof_load cjson) +set_target_properties(${lib_name}_test_plug PROPERTIES PREFIX "") + +add_test(NAME COPY_FTP_SO COMMAND sh -c "cp ${CMAKE_BINARY_DIR}/${lib_name}.so ${PROTO_TEST_RUN_DIR}/plug/protocol/${lib_name}/${lib_name}.so") +add_test(NAME COPY_TEST_SO COMMAND sh -c "cp ${CMAKE_CURRENT_BINARY_DIR}/${lib_name}_test_plug.so ${PROTO_TEST_RUN_DIR}/plug/business/${lib_name}_test_plug/${lib_name}_test_plug.so") +add_test(NAME QUIC_TEST COMMAND proto_test_main ${CMAKE_CURRENT_SOURCE_DIR}/${lib_name}_result.json -f "find ${CMAKE_CURRENT_SOURCE_DIR}/pcap/ -name *.pcap|sort -V" WORKING_DIRECTORY ${PROTO_TEST_RUN_DIR}) + diff --git a/test/Makefile b/test/Makefile deleted file mode 100644 index 124fb04..0000000 --- a/test/Makefile +++ /dev/null @@ -1,61 +0,0 @@ -CC = gcc -CCC = g++ - -INCLUDES += -I/opt/MESA/include/ -LIB = -L./opt/MESA/lib/ -lpthread -CFLAGS = -g3 -Wall -fPIC -CFLAGS += $(INCLUDES) - -TARGET = dpkt_plug_gquic.so -INF = dpkt_plug_gquic.inf -INSTALL_TARGET=$(TARGET) -LIB_FILE = $(wildcard ../lib/*.a) -SOURCES = $(wildcard *.cpp) -OBJECTS = $(SOURCES:.cpp=.o) -DEPS = $(SOURCES:.cpp=.d) - -INF=dpkt_plug_gquic.inf -INSTALL_TARGET=dpkt_plug_gquic.so -# $(CONF) -INSTALL_DIR=/home/mesasoft/sapp/plug/business/dpkt_plug_gquic/ - -all:$(TARGET) -$(TARGET):$(OBJECTS) $(LIB_FILE) - $(CCC) -shared $(CFLAGS) $(OBJECTS) $(LIB) -o $@ - mkdir -p $(INSTALL_DIR) - cp -r $(INSTALL_TARGET) $(INF) $(INSTALL_DIR) -f -# cp $(TARGET) ../bin/ - -.c.o: -%.d:%.c - $(CCC) $< -MM $(INCLUDES) > $@ - -%.o:%.cpp - $(CCC) -c -o $@ $(CFLAGS) $< $(INCLUDES) - --include $(DEPS) - -clean : - rm -f $(OBJECTS) $(DEPS) $(TARGET) - -PLUGIN_PATH=./plug/business -CONFLIST_NAME=conflist_business.inf -PLUGIN_DIR_NAME=dpkt_plug_gquic -PLUGIN_INF_NAME=dpkt_plug_gquic.inf -PAPP_PATH=/home/dk/gitFile/ceiec/sapp - -TARGET_DIR=$(PAPP_PATH)/$(PLUGIN_PATH)/$(PLUGIN_DIR_NAME)/ -INSERT_FILE=$(PAPP_PATH)/$(PLUGIN_PATH)/$(CONFLIST_NAME) -INSERT_CONTENT=$(PLUGIN_PATH)/$(PLUGIN_DIR_NAME)/$(PLUGIN_INF_NAME) -install: - mkdir -p $(TARGET_DIR) - cp -r ../bin/*.inf $(TARGET_DIR) - cp -r ../bin/*.so $(TARGET_DIR) - cp -r ../bin/*.conf $(TARGET_DIR) - @ret=`cat $(INSERT_FILE)|grep $(INSERT_CONTENT)|wc -l`;if [ $$ret -eq 0 ];then echo $(INSERT_CONTENT) >>$(INSERT_FILE);fi - -CONF_DIR=$(PAPP_PATH)/conf/ -conf: - mkdir -p $(CONF_DIR) - cp -r ../bin/quic $(CONF_DIR) - diff --git a/test/conflist.inf b/test/conflist.inf new file mode 100644 index 0000000..bf103c3 --- /dev/null +++ b/test/conflist.inf @@ -0,0 +1,8 @@ +[platform] + +[protocol] +./plug/protocol/quic/quic.inf + + +[business] +./plug/business/quic_test_plug/quic_test_plug.inf diff --git a/test/dpkt_plug_gquic.cpp b/test/dpkt_plug_gquic.cpp deleted file mode 100644 index 276f978..0000000 --- a/test/dpkt_plug_gquic.cpp +++ /dev/null @@ -1,118 +0,0 @@ - -#include "dpkt_plug_gquic.h" - -#include -#include -#include "gquic.h" - - -void a_ntoa( unsigned int in, char *buffer) -{ - unsigned char *bytes = (unsigned char *) ∈ - int i = snprintf( buffer, 15, "%d.%d.%d.%d", bytes[0], bytes[1], bytes[2], bytes[3] ); -} - -int DPKT_GQUIC_INIT() -{ - int plugid = DK_PLUGID_GQUIC; - return plugid; -} - -void DPKT_GQUIC_DESTROY() -{ - return; -} - - -char DPKT_GQUIC_ENTRY(stSessionInfo* session_info, void **pme, int _thread_num, struct streaminfo *pstream, void *a_packet) -{ - int thread_num = pstream->threadnum; - - if(session_info->session_state & SESSION_STATE_CLOSE) - { - return PROT_STATE_GIVEME; - } - - if(session_info->app_info ==NULL) - { - return PROT_STATE_GIVEME; - } - - if(session_info->prot_flag == QUIC_CLIENT_HELLO){ - printf("DPKT_QUIC_ENTRY\tQUIC_CLIENT_HELLO\n"); - struct quic_stream *quic = (struct quic_stream*)session_info->app_info; - if(quic){ - struct quic_client_hello client_hello = quic->st_client_hello; - - printf("BUSINESS PLUG:QUIC_CLIENT_HELLO ext_tag_num=%d--------------------\n",client_hello.ext_tag_num); - if(quic->version){ - printf("BUSINESS PLUG:QUIC_CLIENT_HELLO version=%d--------------------\n",quic->version); - - } - if(client_hello.server_name){ - printf("BUSINESS PLUG:QUIC_CLIENT_HELLO server_name=%s--------------------\n",client_hello.server_name); - - } - if(client_hello.user_agent){ - printf("BUSINESS PLUG:QUIC_CLIENT_HELLO user_agent=%s--------------------\n",client_hello.user_agent); - } - } - } - - - int i = 0, j = 0; - if(session_info->prot_flag == QUIC_VERSION){ - printf("DPKT_QUIC_ENTRY\tQUIC_VERSION\n"); - struct quic_stream *quic = (struct quic_stream*)session_info->app_info; - if(quic){ - printf("version:%d\n",quic->version); - } - } - - if(session_info->prot_flag == QUIC_SERVER_HELLO){ - printf("DPKT_QUIC_ENTRY\tQUIC_SERVER_HELLO\n"); - struct quic_stream *quic = (struct quic_stream*)session_info->app_info; - struct quic_server_hello server_hello = quic->st_server_hello; - printf("BUSINESS PLUG:QUIC_SERVER_HELLO ext_tag_num=%d--------------------\n",server_hello.ext_tag_num); - } - - if(session_info->prot_flag == QUIC_CACHED_CERT){ - printf("DPKT_QUIC_ENTRY\tQUIC_CACHED_CERT\n"); - struct quic_stream *quic = (struct quic_stream*)session_info->app_info; - quic_tlv_t cached_cert = quic->cached_cert; - printf("--------------------BUSINESS PLUG:QUIC_CACHED_CERT cached_cert_length=%d--------------------\n",cached_cert.length); - for(i = 0; i < cached_cert.length; i++){ - printf("%02X",((unsigned char*)cached_cert.ptr_value)[i]); - } - printf("----------------------------------------\n"); - } - - if(session_info->prot_flag == QUIC_COMM_CERT){ - printf("DPKT_QUIC_ENTRY\tQUIC_COMM_CERT\n"); - struct quic_stream *quic = (struct quic_stream*)session_info->app_info; - quic_tlv_t comm_cert = quic->common_cert; - printf("--------------------BUSINESS PLUG:QUIC_COMM_CERT common_cert_length=%d--------------------\n",comm_cert.length); - for(i = 0; i < comm_cert.length; i++){ - printf("%02X",((unsigned char*)comm_cert.ptr_value)[i]); - } - printf("--------------------T--------------------\n"); - } - - if(session_info->prot_flag == QUIC_CERT_CHAIN){ - printf("DPKT_QUIC_ENTRY\tQUIC_CERT_CHAIN\n"); - struct quic_stream *quic = (struct quic_stream*)session_info->app_info; - quic_tlv_t cert_chain = quic->cert_chain; - printf("--------------------BUSINESS PLUG:QUIC_CERT_CHAIN cert_chain_length=%d--------------------\n",cert_chain.length); - for(i = 0; i < cert_chain.length; i++){ - printf("%02X",((unsigned char*)cert_chain.ptr_value)[i]); - } - printf("----------------------------------------\n"); - } - - - return PROT_STATE_GIVEME; - -} - - - diff --git a/test/dpkt_plug_gquic.h b/test/dpkt_plug_gquic.h deleted file mode 100644 index 867f143..0000000 --- a/test/dpkt_plug_gquic.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * dk_plug_quic.h - * - * Created on: - * Author: root - */ - -#ifndef SRC_DPKT_PLUG_QUIC_H_ -#define SRC_DPKT_PLUG_QUIC_H_ - -#include "stream.h" - -#define DK_PLUGID_GQUIC 1003 -#ifdef __cplusplus -extern "C" { -#endif - -int DPKT_GQUIC_INIT(); -void DPKT_GQUIC_DESTROY(); -char DPKT_GQUIC_ENTRY(stSessionInfo* session_info, void **pme, int _thread_num, struct streaminfo *pstream, void *a_packet); - -#ifdef __cplusplus - } -#endif -#endif /* SRC_DPKT_PLUG_QUIC_H_ */ diff --git a/test/dpkt_plug_gquic.inf b/test/dpkt_plug_gquic.inf deleted file mode 100644 index cb2c2c4..0000000 --- a/test/dpkt_plug_gquic.inf +++ /dev/null @@ -1,13 +0,0 @@ -[PLUGINFO] -PLUGNAME=dpkt_plug_gquic -SO_PATH=./plug/business/dpkt_plug_gquic/dpkt_plug_gquic.so -INIT_FUNC=DPKT_GQUIC_INIT -DESTROY_FUNC=DPKT_GQUIC_DESTROY - -[QUIC] -#FUNC_FLAG=QUIC_CLIENT_HELLO,QUIC_SERVER_HELLO,QUIC_CACHED_CERT,QUIC_COMM_CERT,QUIC_CERT_CHAIN,QUIC_VERSION,QUIC_APPLICATION_DATA - -FUNC_FLAG=QUIC_CLIENT_HELLO -FUNC_NAME=DPKT_GQUIC_ENTRY - - diff --git a/test/empty_array.json b/test/empty_array.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/test/empty_array.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/test/pcap/1-iquic.pcap b/test/pcap/1-iquic.pcap new file mode 100755 index 0000000..dcd22db Binary files /dev/null and b/test/pcap/1-iquic.pcap differ diff --git a/test/quic_result.json b/test/quic_result.json new file mode 100644 index 0000000..b536765 --- /dev/null +++ b/test/quic_result.json @@ -0,0 +1,6 @@ +[{ + "Tuple4": "192.168.50.29.61891>31.13.77.35.443", + "VERSION": "IETF QUIC 29", + "SNI": "www.facebook.com", + "name": "QUIC_RESULT_1" + }] diff --git a/test/quic_test_plug.cpp b/test/quic_test_plug.cpp new file mode 100644 index 0000000..1c0ffcb --- /dev/null +++ b/test/quic_test_plug.cpp @@ -0,0 +1,91 @@ +/* + * author:yangwei + * create time:2021-8-21 + * + */ + + + +#include +#include +#include +#include + +#include "cJSON.h" +#include "gquic.h" +#include "MESA_prof_load.h" +#include + +extern "C" int commit_test_result_json(cJSON *node, const char *name); + +static int g_result_count = 1; + +extern "C" unsigned char QUIC_TEST_PLUG_ENTRY(stSessionInfo *session_info, void **pme, + int thread_seq, struct streaminfo *a_tcp, void *a_packet) +{ + assert(NULL != session_info || pme != NULL); + + cJSON *ctx = (cJSON *)*pme; + struct _quic_info *quic_info=NULL; + char version_str[128]={0}; + unsigned int version = 0; + + if (session_info->session_state & SESSION_STATE_PENDING) + { + if (*pme == NULL) + { + ctx = cJSON_CreateObject(); + *pme = (void *)ctx; + cJSON_AddStringToObject(ctx, "Tuple4", printaddr(&a_tcp->addr, a_tcp->threadnum)); + } + } + + switch (session_info->prot_flag) + { + case QUIC_CLIENT_HELLO: + if (session_info == NULL || session_info->app_info == NULL) + { + break; + } + quic_info = (struct _quic_info *)session_info->app_info; + cJSON_AddStringToObject(ctx, "SNI", (char *)(quic_info->client_hello->ext_tags[quic_info->client_hello->sni_idx].value)); + break; + case QUIC_USEING_VERSION: + version = *(unsigned int *)(session_info->buf); + quic_version_int2string(version, version_str, sizeof(version_str)); + cJSON_AddStringToObject(ctx, "VERSION", version_str); + break; + default: + break; + } + + if(session_info->session_state&SESSION_STATE_CLOSE) + { + if(ctx) + { + char result_name[16]=""; + sprintf(result_name,"QUIC_RESULT_%d", g_result_count); + commit_test_result_json(ctx, result_name); + g_result_count+=1; + } + *pme = NULL; + return PROT_STATE_DROPME; + + } + + return PROT_STATE_GIVEME; + +} + +extern "C" int QUIC_TEST_PLUG_INIT() +{ + return 0; +} + +extern "C" void QUIC_TEST_PLUG_DESTROY(void) +{ + return ; +}/*CHAR_DESTRORY*/ + + + diff --git a/test/quic_test_plug.inf b/test/quic_test_plug.inf new file mode 100644 index 0000000..53a66c1 --- /dev/null +++ b/test/quic_test_plug.inf @@ -0,0 +1,11 @@ +[PLUGINFO] +PLUGNAME=QUIC_TEST_PLUG +SO_PATH=./plug/business/quic_test_plug/quic_test_plug.so +INIT_FUNC=QUIC_TEST_PLUG_INIT +DESTROY_FUNC=QUIC_TEST_PLUG_DESTROY + +[QUIC] +FUNC_FLAG=QUIC_CLIENT_HELLO,QUIC_SERVER_HELLO,QUIC_CACHED_CERT,QUIC_COMM_CERT,QUIC_CERT_CHAIN,QUIC_VERSION,QUIC_APPLICATION_DATA +FUNC_NAME=QUIC_TEST_PLUG_ENTRY + + diff --git a/test/test_protocol_run.zip b/test/test_protocol_run.zip new file mode 100644 index 0000000..996f3fa Binary files /dev/null and b/test/test_protocol_run.zip differ