上接[haproxy集成國密ssl功能上
4. 源碼修改解析
??以下修改基本圍繞haproxy的ssl_sock.c進行修改來展開的,為了將整個實現邏輯能夠說明清楚,下述內容有部分可能就是直接摘抄haproxy的原有代碼沒有做任何修改,而大部分增加或者修改的內容則進行了特別的說明。
4.1 為bind指令的ssl子命令添加ntls選項解析能力
static struct bind_kw_list bind_kws = { "SSL", { }, {
......{ "force-tlsv10", bind_parse_tls_method_options, 0 }, /* force TLSv10 */{ "force-tlsv11", bind_parse_tls_method_options, 0 }, /* force TLSv11 */{ "force-tlsv12", bind_parse_tls_method_options, 0 }, /* force TLSv12 */{ "force-tlsv13", bind_parse_tls_method_options, 0 }, /* force TLSv13 */{ "force-ntlsv11", bind_parse_tls_method_options, 0 }, /* force NTLSv11 */{ "generate-certificates", bind_parse_generate_certs, 0 }, /* enable the server certificates generation */{ "no-ca-names", bind_parse_no_ca_names, 0 }, /* do not send ca names to clients (ca_file related) */{ "no-sslv3", bind_parse_tls_method_options, 0 }, /* disable SSLv3 */{ "no-tlsv10", bind_parse_tls_method_options, 0 }, /* disable TLSv10 */{ "no-tlsv11", bind_parse_tls_method_options, 0 }, /* disable TLSv11 */{ "no-tlsv12", bind_parse_tls_method_options, 0 }, /* disable TLSv12 */{ "no-ntlsv11", bind_parse_tls_method_options, 0 }, /* disable TLSv13 */{ "no-tlsv13", bind_parse_tls_method_options, 0 }, /* disable NTLSv11 */
......{ NULL, NULL, 0 },
}};
??這里添加了ntls、force-ntlsv11和no-ntlsv11三個指令選項。haproxy 通過INITCALL1(STG_REGISTER, bind_register_keywords, &bind_kws);將指令選項的解析回調函數注冊進去,在指令解析的時候將回調對應的選項解析函數。
4.1.1 ntls指令選項解析
??按照上面的配置,ntls的配置選項是交由bind_parse_ntls來解析的,源碼如下:
/* parse the "ntls" bind keyword. Returns a set of ERR_* flags possibly with an error in <err>. */
static int bind_parse_ntls(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err)
{char enc[MAXPATHLEN], sign[MAXPATHLEN], tls[MAXPATHLEN];int cfgerr = 0;int couple_cert = 0;const char *gm_cert_enc, *gm_cert_sign, *tls_cert;gm_cert_enc = gm_cert_sign = tls_cert = NULL;char* err2 = NULL;SSL_CTX *old_ctx = NULL;if (!*args[cur_arg + 1]) {memprintf(err, "'%s' : missing ntls enc certificate location", args[cur_arg]);return ERR_ALERT | ERR_FATAL;}else {gm_cert_enc = args[cur_arg + 1];}if (!*args[cur_arg + 2]) {memprintf(err, "'%s' : missing ntls sign certificate location", args[cur_arg]);return ERR_ALERT | ERR_FATAL;}else {gm_cert_sign = args[cur_arg + 2];}if (*args[cur_arg + 3]) {if (strstr(args[cur_arg + 3], ".pem") != NULL || strstr(args[cur_arg + 3], ".crt") != NULL) {/* 第三個參數必須是以.pem或.crt為擴展名的文件,這樣子才會啟用tls 和 ntls雙證書模式 */couple_cert = 1;tls_cert = args[cur_arg + 1];gm_cert_enc = args[cur_arg + 2];gm_cert_sign = args[cur_arg + 3];}}if (tls_cert) {if ((*tls_cert != '/' ) && global_ssl.crt_base) {if ((strlen(global_ssl.crt_base) + 1 + strlen(tls_cert) + 1) > MAXPATHLEN) {memprintf(err, "'%s' : tls certificate path too long", args[cur_arg]);return ERR_ALERT | ERR_FATAL;}snprintf(tls, sizeof(tls), "%s/%s", global_ssl.crt_base, tls_cert);}else {strcpy(tls, tls_cert);}/* 加載tls證書 */cfgerr = ssl_sock_load_cert(tls, conf, &err2);if (cfgerr != 0) {memprintf(err, "load enc certificate %s failed, error:%s", tls, err2 ? err2: "" );if (err2) free(err2);return cfgerr;}}else {/* 如果沒有tls證書,那么就沒有調用ssl_sock_load_cert而創建一個新的ctx,在這里需要將conf->default_ctx置為空,讓ssl_sock_load_ntls_cert新創建一個ctx,等ntls的enc和sign證書加載完成后,重新將conf->default_ctx設置回去。*/old_ctx = conf->default_ctx;conf->default_ctx = NULL;}if ((*gm_cert_enc != '/' ) && global_ssl.crt_base) {if ((strlen(global_ssl.crt_base) + 1 + strlen(gm_cert_enc) + 1) > MAXPATHLEN) {memprintf(err, "'%s' : enc certificate path too long", args[cur_arg]);return ERR_ALERT | ERR_FATAL;}snprintf(enc, sizeof(enc), "%s/%s", global_ssl.crt_base, gm_cert_enc);}else {strcpy(enc, gm_cert_enc);}/* 加載ntls_enc證書 */cfgerr = ssl_sock_load_ntls_cert(enc, conf, 0, &err2);if (cfgerr != 0) {memprintf(err, "load enc certificate %s failed, error:%s",enc, err2 ? err2 : "");if (err2) free(err2);return cfgerr;}if ((*gm_cert_sign != '/' ) && global_ssl.crt_base) {if ((strlen(global_ssl.crt_base) + 1 + strlen(gm_cert_sign) + 1) > MAXPATHLEN) {memprintf(err, "'%s' : sign certificate path too long", args[cur_arg]);return ERR_ALERT | ERR_FATAL;}snprintf(sign, sizeof(sign), "%s/%s", global_ssl.crt_base, gm_cert_sign);}else {strcpy(sign, gm_cert_sign);}/* 加載ntls_sign證書 */cfgerr = ssl_sock_load_ntls_cert(sign, conf, 1, &err2);if (cfgerr != 0) {memprintf(err, "load sign certificate %s failed, error:%s", sign, err2 ? err2 : "" );if (err2) free(err2);return cfgerr;}// 設置啟用國密TLCP協議conf->enable_ntls = 1;/* 重新將default_ctx改成之前的old_ctx */if (old_ctx) {conf->default_ctx = old_ctx;}if (tls_cert) return cfgerr | 0x30000000; /* 指明消耗3個參數 */elsereturn cfgerr | 0x20000000; /* 指明消耗2個參數 */
}
??以上代碼就是從配置指令中讀取加密證書和簽名證書,ntls可以同時支持國際和國密證書的加載,如果是雙證書的情況,那么需要解析三個參數,所以返回的時候我們需要告訴配置解析框架到底消耗了幾個參數,這個通過復用返回的錯誤值的高4個bit來實現。由于原來的haproxy框架是不支持可變個數參數的,因此,在兼容原始功能邏輯的基礎上,需要略微修改一下haproxy的配置解析框架的代碼,在cfg_parse-listen.c的cfg_parse_listen函數中,進行如下修改:
int cfg_parse_listen(const char *file, int linenum, char **args, int kwm)
{
......cur_arg = 2;while (*(args[cur_arg])) {static int bind_dumped;struct bind_kw *kw;char *err;kw = bind_find_kw(args[cur_arg]);if (kw) {char *err = NULL;int code, skip = 0;if (!kw->parse) {ha_alert("parsing [%s:%d] : '%s %s' : '%s' option is not implemented in this version (check build options).\n",file, linenum, args[0], args[1], args[cur_arg]);cur_arg += 1 + kw->skip ;err_code |= ERR_ALERT | ERR_FATAL;goto out;}code = kw->parse(args, cur_arg, curproxy, bind_conf, &err);/* skip表示回調函數在配置選項解析過程中,消耗了幾個配置參數 */skip = (code >> 28) & 0x0000000F; /* 從返回值中過濾出錯誤代碼 */ err_code |= code & 0x0FFFFFFF;/* 用來兼容原生邏輯,如果解析函數沒有返回了跳過多少個參數,則用配置中的設置 */if (skip == 0) {skip = kw->skip;}if (code & 0x0FFFFFFF) { /* 如果本次調用配置解析回調函數返回錯誤了, 那么下面進行錯誤處理 */if (err && *err) {indent_msg(&err, 2);if (((code & (ERR_WARN|ERR_ALERT)) == ERR_WARN))ha_warning("parsing [%s:%d] : '%s %s' : %s\n", file, linenum, args[0], args[1], err);elseha_alert("parsing [%s:%d] : '%s %s' : %s\n", file, linenum, args[0], args[1], err);}elseha_alert("parsing [%s:%d] : '%s %s' : error encountered while processing '%s'.\n",file, linenum, args[0], args[1], args[cur_arg]);if (code & ERR_FATAL) {free(err);cur_arg += 1 + skip;goto out;}}free(err);cur_arg += 1 + skip;continue;}err = NULL;if (!bind_dumped) {bind_dump_kws(&err);indent_msg(&err, 4);bind_dumped = 1;}ha_alert("parsing [%s:%d] : '%s %s' unknown keyword '%s'.%s%s\n",file, linenum, args[0], args[1], args[cur_arg],err ? " Registered keywords :" : "", err ? err : "");free(err);err_code |= ERR_ALERT | ERR_FATAL;goto out;}goto out;
......
}
??函數bind_parse_ntls是通過ssl_sock_load_ntls_cert來將這兩個證書加載進bind_conf->default_ctx(ssl配置上下文),ssl_sock_load_ntls_cert代碼如下:
#define FREE_CTX_RETURN(ctx, err) \if (ctx_new) { \SSL_CTX_free(ctx); \bind_conf->default_ctx = NULL; \} \return (err)/* enc_sign = 0 表示加載加密證書 否則表示加載簽名證書 */
static
int ssl_sock_load_ntls_cert(char