Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Socat patch to add hash verification to openSSL connections
commit 49d13df85993eda521002b9c197d68914c0c86e6
Author: gdm85 <gdm85@users.noreply.github.com>
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
You can’t perform that action at this time.