@@ -244,7 +244,7 @@ class Socket
244244 bool isConnected ()const ;
245245 void setDebugConnect (bool d);
246246
247- void setSSLConfig_OpenSSL (bool force_ssl, int certverify, const std::string& ca_path, const std::string& ca_file, const std::string& cert_file, const std::string& key_file, const std::string& key_pass);
247+ void setSSLConfig_OpenSSL (bool force_ssl, int certverify, const std::string& ca_path, const std::string& ca_file, const std::string& cert_file, const std::string& key_file, const std::string& key_pass, const std::string& certident_name );
248248 void setSSLConfig_NSS (bool force_ssl, int certverify, const std::string& certstore_path, const std::string& certstore_pass, const std::string& certstore_prefix, const std::string& certhost_name, const std::string& certident_name);
249249
250250 void startTLS ();
@@ -318,19 +318,45 @@ class Socket
318318# ifdef WITH_OPENSSL
319319SSL_CTX* Socket::_ssl_ctx = nullptr ;
320320
321- /* static*/ int Socket::openssl_password_callback (char *buf, int size, int rwflag, void *userdata) /* pem_passwd_cb, 1.1.0+ */
321+ # if (defined(HAVE_SSL_CTX_SET_DEFAULT_PASSWD_CB) && HAVE_SSL_CTX_SET_DEFAULT_PASSWD_CB) || (defined(HAVE_SSL_SET_DEFAULT_PASSWD_CB) && HAVE_SSL_SET_DEFAULT_PASSWD_CB)
322+ /* Note: availability of these methods seems to predate C++11, but still... */
323+ /* static*/ int Socket::openssl_password_callback (char *buf, int size, int rwflag, void *userdata) /* pem_passwd_cb, cca OpenSSL 1.1.0+ */
322324{
325+ /* See https://docs.openssl.org/1.0.2/man3/SSL_CTX_set_default_passwd_cb */
326+ /* is callback used for reading/decryption (rwflag=0) or writing/encryption (rwflag=1)? */
323327 NUT_UNUSED_VARIABLE (rwflag);
328+ /* "userdata" is generally the user-provided password, possibly cached
329+ * from an earlier loop (e.g. to check interactively typing it twice,
330+ * or to probe several items in a loop). For us, it should be this->_key_pass
331+ * via SSL_CTX_set_default_passwd_cb_userdata(), but most programs out
332+ * there do not have just one variable with one password to think about. */
333+
334+ if (!buf || size < 1 ) {
335+ /* Can not even set buf[0] */
336+ return 0 ;
337+ }
324338
325- if (size < 1 || !userdata || !*(static_cast <char *>(userdata))) {
339+ if (!userdata || !*(static_cast <char *>(userdata))) {
340+ # if (defined(HAVE_SSL_CTX_SET_DEFAULT_PASSWD_CB_USERDATA) && HAVE_SSL_CTX_SET_DEFAULT_PASSWD_CB_USERDATA) || (defined(HAVE_SSL_SET_DEFAULT_PASSWD_CB_USERDATA) && HAVE_SSL_SET_DEFAULT_PASSWD_CB_USERDATA)
341+ /* Use what we were told to use (or not), do not surprise
342+ * anyone by some hard-coded fallback to _key_pass here! */
326343 buf[0 ] = ' \0 ' ;
327344 return 0 ;
345+ # else
346+ userdata = static_cast <void *>(this ->_key_pass .c_str ());
347+ # endif
348+ }
349+
350+ if (strlen (static_cast <char *>(userdata)) >= (size_t )size) {
351+ /* Do not return truncated trash, just say we could not do it */
352+ return 0 ;
328353 }
329354
330355 strncpy (buf, static_cast <char *>(userdata), static_cast <size_t >(size));
331356 buf[size - 1 ] = ' \0 ' ;
332357 return static_cast <int >(strlen (buf));
333358}
359+ # endif /* ...SET_DEFAULT_PASSWD_CB */
334360# endif /* WITH_OPENSSL */
335361
336362# ifdef WITH_NSS
@@ -861,7 +887,7 @@ bool Socket::isSSL()const
861887#endif
862888}
863889
864- void Socket::setSSLConfig_OpenSSL (bool force_ssl, int certverify, const std::string& ca_path, const std::string& ca_file, const std::string& cert_file, const std::string& key_file, const std::string& key_pass)
890+ void Socket::setSSLConfig_OpenSSL (bool force_ssl, int certverify, const std::string& ca_path, const std::string& ca_file, const std::string& cert_file, const std::string& key_file, const std::string& key_pass, const std::string& certident_name )
865891{
866892 _force_ssl = force_ssl;
867893
@@ -875,6 +901,7 @@ void Socket::setSSLConfig_OpenSSL(bool force_ssl, int certverify, const std::str
875901 _cert_file = cert_file;
876902 _key_file = key_file;
877903 _key_pass = key_pass;
904+ _certident_name = certident_name;
878905
879906 _ssl_configured |= UPSCLI_SSL_CAPS_OPENSSL;
880907#else
@@ -884,6 +911,7 @@ void Socket::setSSLConfig_OpenSSL(bool force_ssl, int certverify, const std::str
884911 NUT_UNUSED_VARIABLE (cert_file);
885912 NUT_UNUSED_VARIABLE (key_file);
886913 NUT_UNUSED_VARIABLE (key_pass);
914+ NUT_UNUSED_VARIABLE (certident_name);
887915
888916 _ssl_configured &= ~UPSCLI_SSL_CAPS_OPENSSL;
889917#endif
@@ -978,32 +1006,123 @@ void Socket::startTLS()
9781006
9791007 if (!_ca_file.empty () || !_ca_path.empty ()) {
9801008 if (SSL_CTX_load_verify_locations (_ssl_ctx, _ca_file.empty () ? nullptr : _ca_file.c_str (), _ca_path.empty () ? nullptr : _ca_path.c_str ()) != 1 ) {
981- throw nut::SSLException_OpenSSL (" Failed to load CA verify locations" );
1009+ if (!(_ca_file.empty () && !_ca_path.empty ()
1010+ /* Retry in case CERTPATH pointed to PEM file */
1011+ && SSL_CTX_load_verify_locations (_ssl_ctx, _ca_path.c_str (), nullptr ) == 1 )
1012+ ) {
1013+ throw nut::SSLException_OpenSSL (" Failed to load CA verify locations" );
1014+ }
9821015 }
9831016 }
1017+
9841018 if (_certverify != -1 ) {
9851019 SSL_CTX_set_verify (_ssl_ctx, _certverify ? SSL_VERIFY_PEER : SSL_VERIFY_NONE, nullptr );
9861020 }
1021+
9871022 if (!_cert_file.empty ()) {
9881023 if (SSL_CTX_use_certificate_chain_file (_ssl_ctx, _cert_file.c_str ()) != 1 ) {
9891024 throw nut::SSLException_OpenSSL (" Failed to load client certificate file" );
9901025 }
1026+
9911027 if (!_key_pass.empty ()) {
992- # if OPENSSL_VERSION_NUMBER < 0x10100000L
993- throw nut::SSLException_OpenSSL (" Private key password support not implemented for OpenSSL < 1.1 yet" );
994- # else
995- /* OpenSSL 1.1.0+
1028+ /* Note: availability of these methods seems to predate C++11, but still... */
1029+ # if defined(HAVE_SSL_CTX_SET_DEFAULT_PASSWD_CB) && HAVE_SSL_CTX_SET_DEFAULT_PASSWD_CB
1030+ /* Roughly OpenSSL 1.1.0+ or 1.0.2+ with patched distros
9961031 * https://docs.openssl.org/3.5/man3/SSL_CTX_set_default_passwd_cb/#return-values
9971032 */
9981033 /* 1. Set the callback function */
9991034 SSL_CTX_set_default_passwd_cb (_ssl_ctx, openssl_password_callback);
1035+ # if defined(HAVE_SSL_CTX_SET_DEFAULT_PASSWD_CB_USERDATA) && HAVE_SSL_CTX_SET_DEFAULT_PASSWD_CB_USERDATA
10001036 /* 2. Set the userdata to the password string */
10011037 SSL_CTX_set_default_passwd_cb_userdata (_ssl_ctx, const_cast <void *>(static_cast <const void *>(_key_pass.c_str ())));
1002- # endif
1038+ # endif /* else callback uses class instance field */
1039+ # else /* Not SSL_CTX_* methods */
1040+ /* Per https://docs.openssl.org/3.5/man3/SSL_CTX_set_default_passwd_cb,
1041+ * the `SSL_CTX*` variants were added in 1.1.
1042+ * The SSL_set_default_passwd_cb() and SSL_set_default_passwd_cb_userdata()
1043+ * for `SSL*` argument were around since the turn of millennium, approx 0.9.6+
1044+ * per https://github.com/openssl/openssl/commit/66ebbb6a56bc1688fa37878e4feec985b0c260d7
1045+ *
1046+ * But to use those, we would need to get that SSL* (maybe from socket FD?);
1047+ * that would also unlock us using the ssl_error() elsewhere.
1048+ *
1049+ * Alternately load PEM "manually", see e.g. Apache httpd sources before 2015.
1050+ */
1051+ # if defined(HAVE_SSL_SET_DEFAULT_PASSWD_CB) && HAVE_SSL_SET_DEFAULT_PASSWD_CB
1052+ /* Theoretical solution - didn't find a build system where such methods
1053+ * would actually be available, so this could be tested and used */
1054+ SSL *ssl_tmp = SSL_new (ssl_ctx);
1055+ /* OpenSSL 0.9.6+ at least? */
1056+ SSL_set_default_passwd_cb (ssl_tmp, openssl_password_callback);
1057+ # if defined(HAVE_SSL_SET_DEFAULT_PASSWD_CB_USERDATA) && HAVE_SSL_SET_DEFAULT_PASSWD_CB_USERDATA
1058+ SSL_set_default_passwd_cb_userdata (ssl_tmp, const_cast <void *>(static_cast <const void *>(_key_pass.c_str ())));
1059+ # endif
1060+ SSL_free (ssl_tmp);
1061+
1062+ # else /* Not SSL_* methods either */
1063+
1064+ throw nut::SSLException_OpenSSL (" Private key password support not implemented for OpenSSL < 1.1 yet" );
1065+ # endif
1066+ # endif /* ...SET_DEFAULT_PASSWD_CB */
10031067 }
1068+
10041069 if (SSL_CTX_use_PrivateKey_file (_ssl_ctx, _key_file.empty () ? _cert_file.c_str () : _key_file.c_str (), SSL_FILETYPE_PEM) != 1 ) {
10051070 throw nut::SSLException_OpenSSL (" Failed to load client private key file" );
10061071 }
1072+
1073+ if (!_certident_name.empty ()) {
1074+ # if (defined(HAVE_SSL_CTX_GET0_CERTIFICATE) && HAVE_SSL_CTX_GET0_CERTIFICATE) && (defined(HAVE_X509_CHECK_HOST) && HAVE_X509_CHECK_HOST) && (defined(HAVE_X509_CHECK_IP_ASC) && HAVE_X509_CHECK_IP_ASC) && (defined(HAVE_X509_NAME_ONELINE) && HAVE_X509_NAME_ONELINE)
1075+ /* Roughly OpenSSL 1.0.2+ */
1076+ X509 *x509 = SSL_CTX_get0_certificate (_ssl_ctx);
1077+ if (x509) {
1078+ /* Check if _certident_name matches the host (CN or SAN) */
1079+ if (X509_check_host (x509, _certident_name.c_str (), 0 , 0 , nullptr ) != 1
1080+ && X509_check_ip_asc (x509, _certident_name.c_str (), 0 ) != 1
1081+ ) {
1082+ char *subject = X509_NAME_oneline (X509_get_subject_name (x509), nullptr , 0 );
1083+ char *subject_CN = (subject ? static_cast <char *>(strstr (subject, " CN=" )) + 3 : nullptr );
1084+ size_t certident_len = _certident_name.length ();
1085+
1086+ if (_debugConnect) std::cerr <<
1087+ " [D4] Socket::startTLS(): My certificate subject: '" << (subject ? subject : " unknown" ) <<
1088+ " '; CN: '" << (subject_CN ? subject_CN : " unknown" ) <<
1089+ " '; CERTIDENT: [" << certident_len << " ]'" << _certident_name << " '" <<
1090+ std::endl << std::flush;
1091+
1092+ /* Check if _certident_name matches the whole subject or just .../CN=.../ part as a string */
1093+ if (!subject || !(
1094+ strcmp (subject, _certident_name.c_str ()) == 0
1095+ || (subject_CN && !strncmp (subject_CN, _certident_name.c_str (), certident_len)
1096+ && (subject_CN[certident_len] == ' \0 ' || subject_CN[certident_len] == ' /' ) )
1097+ )) {
1098+ /* This way or that, the names differ */
1099+ std::string err = " Certificate subject (" + std::string (subject ? subject : " unknown" ) + " ) does not match CERTIDENT name (" + _certident_name + " )" ;
1100+ if (subject) {
1101+ OPENSSL_free (subject);
1102+ }
1103+ throw nut::SSLException_OpenSSL (err);
1104+ } else {
1105+ if (_debugConnect) std::cerr <<
1106+ " [D2] Socket::startTLS(): Certificate subject verified against CERTIDENT subject name (" << _certident_name << " )" <<
1107+ std::endl << std::flush;
1108+ }
1109+ if (subject) {
1110+ OPENSSL_free (subject);
1111+ }
1112+ } else {
1113+ if (_debugConnect) std::cerr <<
1114+ " [D2] Socket::startTLS(): Certificate subject verified against CERTIDENT host name (" << _certident_name << " )" <<
1115+ std::endl << std::flush;
1116+ }
1117+ }
1118+ # else /* Missing X509 methods wanted above */
1119+ throw nut::SSLException_OpenSSL (" Can not verify CERTIDENT '" + _certident_name + " ': not supported in this OpenSSL build (too old)" );
1120+ # endif /* Got ways to check CERTIDENT? */
1121+ }
1122+ } else {
1123+ if (!_certident_name.empty ()) {
1124+ throw nut::SSLException_OpenSSL (" Can not verify CERTIDENT '" + _certident_name + " ': no cert_file was provided" );
1125+ }
10071126 }
10081127
10091128 _ssl = SSL_new (_ssl_ctx);
@@ -1419,7 +1538,7 @@ void SSLConfig::apply(TcpClient& client) const
14191538
14201539void SSLConfig_OpenSSL::apply (TcpClient& client) const
14211540{
1422- client.setSSLConfig_OpenSSL (_force_ssl, _certverify, _ca_path, _ca_file, _cert_file, _key_file, _key_pass);
1541+ client.setSSLConfig_OpenSSL (_force_ssl, _certverify, _ca_path, _ca_file, _cert_file, _key_file, _key_pass, _certident_name );
14231542}
14241543
14251544void SSLConfig_NSS::apply (TcpClient& client) const
@@ -1432,7 +1551,7 @@ void TcpClient::setSSLConfig(const SSLConfig& config)
14321551 config.apply (*this );
14331552}
14341553
1435- void TcpClient::setSSLConfig_OpenSSL (bool force_ssl, int certverify, const char *ca_path, const char *ca_file, const char *cert_file, const char *key_file, const char *key_pass)
1554+ void TcpClient::setSSLConfig_OpenSSL (bool force_ssl, int certverify, const char *ca_path, const char *ca_file, const char *cert_file, const char *key_file, const char *key_pass, const char *certident_name )
14361555{
14371556 _force_ssl = force_ssl;
14381557 _certverify = certverify;
@@ -1441,9 +1560,10 @@ void TcpClient::setSSLConfig_OpenSSL(bool force_ssl, int certverify, const char
14411560 if (cert_file) _cert_file = cert_file;
14421561 if (key_file) _key_file = key_file;
14431562 if (key_pass) _key_pass = key_pass;
1563+ if (certident_name) _certident_name = certident_name;
14441564}
14451565
1446- void TcpClient::setSSLConfig_OpenSSL (bool force_ssl, int certverify, const std::string& ca_path, const std::string& ca_file, const std::string& cert_file, const std::string& key_file, const std::string& key_pass)
1566+ void TcpClient::setSSLConfig_OpenSSL (bool force_ssl, int certverify, const std::string& ca_path, const std::string& ca_file, const std::string& cert_file, const std::string& key_file, const std::string& key_pass, const std::string& certident_name )
14471567{
14481568 _force_ssl = force_ssl;
14491569 _certverify = certverify;
@@ -1452,6 +1572,7 @@ void TcpClient::setSSLConfig_OpenSSL(bool force_ssl, int certverify, const std::
14521572 _cert_file = cert_file;
14531573 _key_file = key_file;
14541574 _key_pass = key_pass;
1575+ _certident_name = certident_name;
14551576}
14561577
14571578void TcpClient::setSSLConfig_NSS (bool force_ssl, int certverify, const char *certstore_path, const char *certstore_pass, const char *certstore_prefix, const char *certhost_name, const char *certident_name)
@@ -1493,8 +1614,8 @@ void TcpClient::connect()
14931614{
14941615 _socket->connect (_host, _port);
14951616 if (_try_ssl || _force_ssl) {
1496- if (!_ca_path.empty () || !_ca_file.empty () || !_cert_file.empty () || !_key_file.empty ()) {
1497- _socket->setSSLConfig_OpenSSL (_force_ssl, _certverify, _ca_path, _ca_file, _cert_file, _key_file, _key_pass);
1617+ if (!_ca_path.empty () || !_ca_file.empty () || !_cert_file.empty () || !_key_file.empty () || !_certident_name. empty () ) {
1618+ _socket->setSSLConfig_OpenSSL (_force_ssl, _certverify, _ca_path, _ca_file, _cert_file, _key_file, _key_pass, _certident_name );
14981619 } else if (!_certstore_path.empty () || !_certstore_prefix.empty () || !_certhost_name.empty () || !_certident_name.empty ()) {
14991620 _socket->setSSLConfig_NSS (_force_ssl, _certverify, _certstore_path, _key_pass, _certstore_prefix, _certhost_name, _certident_name);
15001621 }
@@ -2931,13 +3052,13 @@ NUTCLIENT_TCP_t nutclient_tcp_create_client(const char* host, uint16_t port)
29313052
29323053int nutclient_tcp_get_ssl_caps (void ) { return nut::TcpClient::getSslCaps (); }
29333054
2934- NUTCLIENT_TCP_t nutclient_tcp_create_client_ssl_OpenSSL (const char * host, uint16_t port, int try_ssl, int force_ssl, int certverify, const char *ca_path, const char *ca_file, const char *cert_file, const char *key_file, const char *key_pass)
3055+ NUTCLIENT_TCP_t nutclient_tcp_create_client_ssl_OpenSSL (const char * host, uint16_t port, int try_ssl, int force_ssl, int certverify, const char *ca_path, const char *ca_file, const char *cert_file, const char *key_file, const char *key_pass, const char *certident_name )
29353056{
29363057 nut::TcpClient* client = new nut::TcpClient;
29373058 try
29383059 {
29393060 client->setSSLConfig (nut::SSLConfig_OpenSSL (
2940- (force_ssl > 0 ), certverify, ca_path, ca_file, cert_file, key_file, key_pass
3061+ (force_ssl > 0 ), certverify, ca_path, ca_file, cert_file, key_file, key_pass, certident_name
29413062 ));
29423063 client->connect (host, port, try_ssl != 0 );
29433064 return static_cast <NUTCLIENT_TCP_t>(client);
@@ -2950,15 +3071,15 @@ NUTCLIENT_TCP_t nutclient_tcp_create_client_ssl_OpenSSL(const char* host, uint16
29503071 }
29513072}
29523073
2953- void nutclient_tcp_set_ssl_config_OpenSSL (NUTCLIENT_TCP_t client, int force_ssl, int certverify, const char *ca_path, const char *ca_file, const char *cert_file, const char *key_file, const char *key_pass)
3074+ void nutclient_tcp_set_ssl_config_OpenSSL (NUTCLIENT_TCP_t client, int force_ssl, int certverify, const char *ca_path, const char *ca_file, const char *cert_file, const char *key_file, const char *key_pass, const char *certident_name )
29543075{
29553076 if (client)
29563077 {
29573078 nut::TcpClient* cl = dynamic_cast <nut::TcpClient*>(static_cast <nut::Client*>(client));
29583079 if (cl)
29593080 {
29603081 cl->setSSLConfig (nut::SSLConfig_OpenSSL ((
2961- force_ssl > 0 ), certverify, ca_path, ca_file, cert_file, key_file, key_pass
3082+ force_ssl > 0 ), certverify, ca_path, ca_file, cert_file, key_file, key_pass, certident_name
29623083 ));
29633084 }
29643085 }
0 commit comments