Created
December 25, 2017 17:08
-
-
Save gdm85/db9c060c06e390cf6832346e787ec190 to your computer and use it in GitHub Desktop.
Socat patch to add hash verification to openSSL connections
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
commit 49d13df85993eda521002b9c197d68914c0c86e6 | |
Author: gdm85 <[email protected]> | |
Date: Tue Nov 14 23:21:23 2017 +0100 | |
[PATCH] Add support for openssl-verify-hash | |
A new option is added, openssl-verify-hash with alias verify-hash; this option | |
specifies a SHA256 hash in hexadecimal format that will be required to match | |
the X509 hash of the OpenSSL peer certificate. | |
Documentation is updated to mention MiTM for the pre-existing 'verify' option | |
and clarify the role of the new 'verify-hash' option for self-signed | |
certificates (not mentioned explicitly) and certificate pinning. | |
diff --git a/doc/socat.yo b/doc/socat.yo | |
index 258686e..7bad574 100644 | |
--- a/doc/socat.yo | |
+++ b/doc/socat.yo | |
@@ -508,6 +508,7 @@ label(ADDRESS_OPENSSL_CONNECT)dit(bf(tt(OPENSSL:<host>:<port>))) | |
link(cipher)(OPTION_OPENSSL_CIPHERLIST), | |
link(method)(OPTION_OPENSSL_METHOD), | |
link(verify)(OPTION_OPENSSL_VERIFY), | |
+ link(verify-hash)(OPTION_OPENSSL_VERIFY_HASH), | |
link(commonname)(OPTION_OPENSSL_COMMONNAME) | |
link(cafile)(OPTION_OPENSSL_CAFILE), | |
link(capath)(OPTION_OPENSSL_CAPATH), | |
@@ -2646,8 +2647,12 @@ label(OPTION_OPENSSL_METHOD)dit(bf(tt(method=<ssl-method>))) | |
dit(tt(DTLS1)) Select DTLS protocol version 1. | |
enddit() | |
label(OPTION_OPENSSL_VERIFY)dit(bf(tt(verify=<bool>))) | |
- Controls check of the peer's certificate. Default is 1 (true). Disabling | |
- verify might open your socket for everyone, making the encryption useless! | |
+ Controls verification of the peer's certificate. Default is 1 (true). Disabling | |
+ verify makes you vulnerable to man-in-the-middle attacks! | |
+label(OPTION_OPENSSL_VERIFY_HASH)dit(bf(tt(verify-hash=<string>))) | |
+ Requires the OpenSSL peer certificate SHA256 hash to match specified string | |
+ (case-insensitive). This mitigates disabled link(OpenSSL certificate verification)(OPTION_OPENSSL_VERIFY) or | |
+ works as certificate pinning together with verification. | |
label(OPTION_OPENSSL_CERTIFICATE)dit(bf(tt(cert=<filename>))) | |
Specifies the file with the certificate and private key for authentication. | |
The certificate must be in OpenSSL format (*.pem). | |
diff --git a/doc/xio.help b/doc/xio.help | |
index 2053419..28f7539 100644 | |
--- a/doc/xio.help | |
+++ b/doc/xio.help | |
@@ -397,7 +397,7 @@ Note: this is currently only an experimental integration of openssl! | |
(it does not provide any trust between the peers because is does not check | |
certificates!) | |
Option groups: FD,SOCKET,SOCK_IP4,IP_TCP,OPENSSL,RETRY | |
-Useful options: cipher, method, verify, cafile, capath, certificate, bind, sourceport, retry | |
+Useful options: cipher, method, verify, verify-hash, cafile, capath, certificate, bind, sourceport, retry | |
OPENSSL-LISTEN:port | |
@@ -4514,6 +4514,19 @@ Platforms: (depends on openssl installation) | |
verify might open your socket for everyone! | |
+Option: openssl-verify-hash=string | |
+Aliases: verify-hash=string | |
+ | |
+Type: STRING_NULL | |
+Option group: OPENSSL | |
+Phase: SPEC | |
+Platforms: (depends on openssl installation) | |
+ | |
+Requires the OpenSSL peer certificate SHA256 hash to match specified string | |
+(case-insensitive). This mitigates disabled OpenSSL certificate verification or | |
+works as certificate pinning together with verification. | |
+ | |
+ | |
Option: openssl-certificate=file | |
Aliases: cert=file | |
diff --git a/xio-openssl.c b/xio-openssl.c | |
index e931983..135ec55 100644 | |
--- a/xio-openssl.c | |
+++ b/xio-openssl.c | |
@@ -51,9 +51,10 @@ static int openssl_SSL_ERROR_SSL(int level, const char *funcname); | |
static int openssl_handle_peer_certificate(struct single *xfd, | |
const char *peername, | |
bool opt_ver, | |
+ const char *opt_ver_hash, | |
int level); | |
static int xioSSL_set_fd(struct single *xfd, int level); | |
-static int xioSSL_connect(struct single *xfd, const char *opt_commonname, bool opt_ver, int level); | |
+static int xioSSL_connect(struct single *xfd, const char *opt_commonname, bool opt_ver, const char *opt_ver_hash, int level); | |
static int openssl_delete_cert_info(void); | |
@@ -103,6 +104,7 @@ const struct addrdesc addr_openssl_listen = { | |
const struct optdesc opt_openssl_cipherlist = { "openssl-cipherlist", "ciphers", OPT_OPENSSL_CIPHERLIST, GROUP_OPENSSL, PH_SPEC, TYPE_STRING, OFUNC_SPEC }; | |
const struct optdesc opt_openssl_method = { "openssl-method", "method", OPT_OPENSSL_METHOD, GROUP_OPENSSL, PH_SPEC, TYPE_STRING, OFUNC_SPEC }; | |
const struct optdesc opt_openssl_verify = { "openssl-verify", "verify", OPT_OPENSSL_VERIFY, GROUP_OPENSSL, PH_SPEC, TYPE_BOOL, OFUNC_SPEC }; | |
+const struct optdesc opt_openssl_verify_hash = { "openssl-verify-hash", "verify-hash", OPT_OPENSSL_VERIFY_HASH, GROUP_OPENSSL, PH_SPEC, TYPE_STRING_NULL, OFUNC_SPEC }; | |
const struct optdesc opt_openssl_certificate = { "openssl-certificate", "cert", OPT_OPENSSL_CERTIFICATE, GROUP_OPENSSL, PH_SPEC, TYPE_FILENAME, OFUNC_SPEC }; | |
const struct optdesc opt_openssl_key = { "openssl-key", "key", OPT_OPENSSL_KEY, GROUP_OPENSSL, PH_SPEC, TYPE_FILENAME, OFUNC_SPEC }; | |
const struct optdesc opt_openssl_dhparam = { "openssl-dhparam", "dh", OPT_OPENSSL_DHPARAM, GROUP_OPENSSL, PH_SPEC, TYPE_FILENAME, OFUNC_SPEC }; | |
@@ -195,6 +197,7 @@ static int | |
int level; | |
SSL_CTX* ctx; | |
bool opt_ver = true; /* verify peer certificate */ | |
+ char *opt_ver_hash = NULL; | |
char *opt_cert = NULL; /* file name of client certificate */ | |
const char *opt_commonname = NULL; /* for checking peer certificate */ | |
int result; | |
@@ -232,7 +235,7 @@ static int | |
} | |
result = | |
- _xioopen_openssl_prepare(opts, xfd, false, &opt_ver, opt_cert, &ctx); | |
+ _xioopen_openssl_prepare(opts, xfd, false, &opt_ver, &opt_ver_hash, opt_cert, &ctx); | |
if (result != STAT_OK) return STAT_NORETRY; | |
result = | |
@@ -289,7 +292,7 @@ static int | |
return result; | |
} | |
- result = _xioopen_openssl_connect(xfd, opt_ver, opt_commonname, ctx, level); | |
+ result = _xioopen_openssl_connect(xfd, opt_ver, opt_ver_hash, opt_commonname, ctx, level); | |
switch (result) { | |
case STAT_OK: break; | |
#if WITH_RETRY | |
@@ -357,6 +360,7 @@ static int | |
SSL connection from an FD and a CTX. */ | |
int _xioopen_openssl_connect(struct single *xfd, | |
bool opt_ver, | |
+ const char *opt_ver_hash, | |
const char *opt_commonname, | |
SSL_CTX *ctx, | |
int level) { | |
@@ -382,7 +386,7 @@ int _xioopen_openssl_connect(struct single *xfd, | |
return result; | |
} | |
- result = xioSSL_connect(xfd, opt_commonname, opt_ver, level); | |
+ result = xioSSL_connect(xfd, opt_commonname, opt_ver, opt_ver_hash, level); | |
if (result != STAT_OK) { | |
sycSSL_free(xfd->para.openssl.ssl); | |
xfd->para.openssl.ssl = NULL; | |
@@ -390,7 +394,7 @@ int _xioopen_openssl_connect(struct single *xfd, | |
} | |
result = openssl_handle_peer_certificate(xfd, opt_commonname, | |
- opt_ver, level); | |
+ opt_ver, opt_ver_hash, level); | |
if (result != STAT_OK) { | |
sycSSL_free(xfd->para.openssl.ssl); | |
xfd->para.openssl.ssl = NULL; | |
@@ -431,6 +435,7 @@ static int | |
int level; | |
SSL_CTX* ctx; | |
bool opt_ver = true; /* verify peer certificate - changed with 1.6.0 */ | |
+ char *unused_opt_ver_hash = NULL; | |
char *opt_cert = NULL; /* file name of server certificate */ | |
const char *opt_commonname = NULL; /* for checking peer certificate */ | |
int result; | |
@@ -470,7 +475,7 @@ static int | |
applyopts(-1, opts, PH_EARLY); | |
result = | |
- _xioopen_openssl_prepare(opts, xfd, true, &opt_ver, opt_cert, &ctx); | |
+ _xioopen_openssl_prepare(opts, xfd, true, &opt_ver, &unused_opt_ver_hash, opt_cert, &ctx); | |
if (result != STAT_OK) return STAT_NORETRY; | |
if (_xioopen_ipapp_listen_prepare(opts, &opts0, portname, &pf, ipproto, | |
@@ -641,7 +646,7 @@ int _xioopen_openssl_listen(struct single *xfd, | |
return STAT_RETRYLATER; | |
} | |
- if (openssl_handle_peer_certificate(xfd, opt_commonname, opt_ver, E_ERROR/*!*/) < 0) { | |
+ if (openssl_handle_peer_certificate(xfd, opt_commonname, opt_ver, NULL, E_ERROR/*!*/) < 0) { | |
return STAT_NORETRY; | |
} | |
@@ -711,6 +716,7 @@ int | |
*/ | |
bool server, /* SSL client: false */ | |
bool *opt_ver, | |
+ char **opt_ver_hash, | |
const char *opt_cert, | |
SSL_CTX **ctx) | |
{ | |
@@ -737,6 +743,7 @@ int | |
retropt_string(opts, OPT_OPENSSL_METHOD, &me_str); | |
retropt_string(opts, OPT_OPENSSL_CIPHERLIST, &ci_str); | |
retropt_bool(opts, OPT_OPENSSL_VERIFY, opt_ver); | |
+ retropt_string(opts, OPT_OPENSSL_VERIFY_HASH, opt_ver_hash); | |
retropt_string(opts, OPT_OPENSSL_CAFILE, &opt_cafile); | |
retropt_string(opts, OPT_OPENSSL_CAPATH, &opt_capath); | |
retropt_string(opts, OPT_OPENSSL_KEY, &opt_key); | |
@@ -1357,6 +1364,37 @@ static bool openssl_check_peername(X509_NAME *name, const char *peername) { | |
return openssl_check_name((const char *)text, peername); | |
} | |
+static bool xioSSL_verify_cert_sha256(X509* cert, const char *expected_fp) { | |
+ uint8_t hash[EVP_MAX_MD_SIZE]; | |
+ char cert_fp[256/8*3]; | |
+ uint32_t len = sizeof(hash); | |
+ uint32_t buflen = sizeof(cert_fp); | |
+ int i, ok; | |
+ int pos = 0; | |
+ | |
+ /* calculate certificate SHA256 hash */ | |
+ ok = X509_digest(cert, EVP_sha256(), hash, &len); | |
+ if (ok != 1) { | |
+ Error("failed to calculate certificate hash"); | |
+ return false; | |
+ } | |
+ | |
+ for(i = 0; i < len; ++i) { | |
+ if (i > 0) { | |
+ pos += snprintf(cert_fp + pos, buflen - pos, ":"); | |
+ } | |
+ pos += snprintf(cert_fp + pos, buflen - pos, "%02X", hash[i]); | |
+ } | |
+ cert_fp[pos] = 0; | |
+ | |
+ if (!strcasecmp(expected_fp, cert_fp)) { | |
+ return true; | |
+ } | |
+ | |
+ Error2("certificate hash \"%s\" does not match expected \"%s\"", cert_fp, expected_fp); | |
+ return false; | |
+} | |
+ | |
/* retrieves certificate provided by peer, sets env vars containing | |
certificates field values, and checks peername if provided by | |
calling function */ | |
@@ -1366,7 +1404,7 @@ static bool openssl_check_peername(X509_NAME *name, const char *peername) { | |
*/ | |
static int openssl_handle_peer_certificate(struct single *xfd, | |
const char *peername, | |
- bool opt_ver, int level) { | |
+ bool opt_ver, const char *opt_ver_hash, int level) { | |
X509 *peer_cert; | |
X509_NAME *subjectname, *issuername; | |
/*ASN1_TIME not_before, not_after;*/ | |
@@ -1460,12 +1498,19 @@ pName->d.ia5); | |
} | |
} | |
} | |
- } | |
+ } | |
+ } | |
+ } | |
+ | |
+ if (opt_ver_hash) { | |
+ if (false == xioSSL_verify_cert_sha256(peer_cert, opt_ver_hash)) { | |
+ X509_free(peer_cert); | |
+ return STAT_NORETRY; | |
} | |
} | |
if (!opt_ver) { | |
- Notice("option openssl-verify disabled, no check of certificate"); | |
+ Notice("option openssl-verify disabled, no further checks of certificate"); | |
X509_free(peer_cert); | |
return STAT_OK; | |
} | |
@@ -1513,7 +1558,7 @@ static int xioSSL_set_fd(struct single *xfd, int level) { | |
options and ev. sleeps an interval. It returns NORETRY when the caller | |
should not retry for any reason. */ | |
static int xioSSL_connect(struct single *xfd, const char *opt_commonname, | |
- bool opt_ver, int level) { | |
+ bool opt_ver, const char *opt_ver_hash, int level) { | |
char error_string[120]; | |
int errint, status, ret; | |
unsigned long err; | |
@@ -1558,7 +1603,7 @@ static int xioSSL_connect(struct single *xfd, const char *opt_commonname, | |
break; | |
case SSL_ERROR_SSL: | |
status = openssl_SSL_ERROR_SSL(level, "SSL_connect"); | |
- if (openssl_handle_peer_certificate(xfd, opt_commonname, opt_ver, level/*!*/) < 0) { | |
+ if (openssl_handle_peer_certificate(xfd, opt_commonname, opt_ver, opt_ver_hash, level/*!*/) < 0) { | |
return STAT_RETRYLATER; | |
} | |
break; | |
diff --git a/xio-openssl.h b/xio-openssl.h | |
index ca47233..9f425b1 100644 | |
--- a/xio-openssl.h | |
+++ b/xio-openssl.h | |
@@ -16,6 +16,7 @@ extern const struct addrdesc addr_openssl_listen; | |
extern const struct optdesc opt_openssl_cipherlist; | |
extern const struct optdesc opt_openssl_method; | |
extern const struct optdesc opt_openssl_verify; | |
+extern const struct optdesc opt_openssl_verify_hash; | |
extern const struct optdesc opt_openssl_certificate; | |
extern const struct optdesc opt_openssl_key; | |
extern const struct optdesc opt_openssl_dhparam; | |
@@ -33,10 +34,10 @@ extern const struct optdesc opt_openssl_commonname; | |
extern int | |
_xioopen_openssl_prepare(struct opt *opts, struct single *xfd, | |
- bool server, bool *opt_ver, const char *opt_cert, | |
+ bool server, bool *opt_ver, char **opt_ver_hash, const char *opt_cert, | |
SSL_CTX **ctx); | |
extern int | |
- _xioopen_openssl_connect(struct single *xfd, bool opt_ver, | |
+ _xioopen_openssl_connect(struct single *xfd, bool opt_ver, const char *opt_ver_hash, | |
const char *opt_commonname, | |
SSL_CTX *ctx, int level); | |
extern int | |
diff --git a/xioopts.c b/xioopts.c | |
index 9ea6ce4..ba418da 100644 | |
--- a/xioopts.c | |
+++ b/xioopts.c | |
@@ -1112,6 +1112,7 @@ const struct optname optionnames[] = { | |
IF_OPENSSL("openssl-method", &opt_openssl_method) | |
IF_OPENSSL("openssl-pseudo", &opt_openssl_pseudo) | |
IF_OPENSSL("openssl-verify", &opt_openssl_verify) | |
+ IF_OPENSSL("openssl-verify-hash", &opt_openssl_verify_hash) | |
IF_TERMIOS("opost", &opt_opost) | |
#if defined(HAVE_TERMIOS_ISPEED) && defined(OSPEED_OFFSET) && (OSPEED_OFFSET != -1) | |
IF_TERMIOS("ospeed", &opt_ospeed) | |
@@ -1687,6 +1688,7 @@ const struct optname optionnames[] = { | |
IF_TERMIOS("veol2", &opt_veol2) | |
IF_TERMIOS("verase", &opt_verase) | |
IF_OPENSSL("verify", &opt_openssl_verify) | |
+ IF_OPENSSL("verify-hash", &opt_openssl_verify_hash) | |
IF_TERMIOS("vintr", &opt_vintr) | |
IF_TERMIOS("vkill", &opt_vkill) | |
IF_TERMIOS("vlnext", &opt_vlnext) | |
diff --git a/xioopts.h b/xioopts.h | |
index 95a44a4..1b5fa76 100644 | |
--- a/xioopts.h | |
+++ b/xioopts.h | |
@@ -484,6 +484,7 @@ enum e_optcode { | |
OPT_OPENSSL_METHOD, | |
OPT_OPENSSL_PSEUDO, | |
OPT_OPENSSL_VERIFY, | |
+ OPT_OPENSSL_VERIFY_HASH, | |
OPT_OPOST, /* termios.c_oflag */ | |
OPT_OSPEED, /* termios.c_ospeed */ | |
OPT_O_APPEND, |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment