Skip to content

Commit 938fe94

Browse files
authored
Merge b66f1fb into 68fb8bd
2 parents 68fb8bd + b66f1fb commit 938fe94

21 files changed

Lines changed: 1084 additions & 224 deletions

Makefile.am

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -874,7 +874,8 @@ SET_PARMAKES_OPT = \
874874
fi ; \
875875
PARMAKES_OPT="-j $${MAXPARMAKES}" ; \
876876
;; \
877-
esac
877+
esac ; \
878+
if [ x"$${V}$(V)" = x ]; then PARMAKES_OPT="$${PARMAKES_OPT} V=0" ; fi
878879

879880
all-quick: @dotMAKE@
880881
+@$(SET_PARMAKES_OPT); \

NEWS.adoc

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,27 @@ https://github.com/networkupstools/nut/milestone/13
6060
query (for protocol version) to verify that handshake succeeded.
6161
This change impacted also the classic C `libupsclient` library.
6262
[issue #3387, PR #3402]
63+
* Updated OpenSSL code paths in the `libupsclient` C library to support
64+
features earlier only available with NSS builds, like specifying the
65+
client certificate+key, optionally with password, and pinning expected
66+
server certificates. For both backends such pinning should now honour
67+
the 'certverify' setting of the `CERTHOST` entry (e.g. not abort the
68+
connection attempt if that number is '0'). [issue #3331]
69+
* Updated SSL support in the `upsd` data server to handle `CERTREQUEST`
70+
(optional validation of clients identified by a certificate) also
71+
when built with OpenSSL, optionally using the `CERTPATH` with a
72+
collection of CA certificates (directory or a big PEM file).
73+
Also support `CERTIDENT` to provide a private key password and ensure
74+
that the certificate in `CERTFILE` has an expected subject name as an
75+
exact string, or that its CN or SAN match the provided string as a
76+
standard expression of host name (section 3.5 of RFC 1034) or IP address.
77+
[issue #3331]
78+
* The `libupsclient` API was extended with a `upscli_init2()` method which
79+
allows to pass the `certfile` argument needed for OpenSSL builds. [#3331]
80+
81+
- `upsmon` client updates:
82+
* Introduced support for `CERTFILE` option, so the client can identify
83+
itself to the data server also in OpenSSL builds. [issue #3331]
6384

6485

6586
Release notes for NUT 2.8.5 - what's new since 2.8.4

clients/nutclient.cpp

Lines changed: 99 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -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();
@@ -320,13 +320,28 @@ SSL_CTX* Socket::_ssl_ctx = nullptr;
320320

321321
/*static*/ int Socket::openssl_password_callback(char *buf, int size, int rwflag, void *userdata) /* pem_passwd_cb, 1.1.0+ */
322322
{
323+
/* See https://docs.openssl.org/1.0.2/man3/SSL_CTX_set_default_passwd_cb */
324+
/* is callback used for reading/decryption (rwflag=0) or writing/encryption (rwflag=1)? */
323325
NUT_UNUSED_VARIABLE(rwflag);
326+
/* "userdata" is generally the user-provided password, possibly cached
327+
* from an earlier loop (e.g. to check interactively typing it twice,
328+
* or to probe several items in a loop). */
324329

325-
if (size < 1 || !userdata || !*(static_cast<char *>(userdata))) {
330+
if (!buf || size < 1) {
331+
/* Can not even set buf[0] */
332+
return 0;
333+
}
334+
335+
if (!userdata || !*(static_cast<char *>(userdata))) {
326336
buf[0] = '\0';
327337
return 0;
328338
}
329339

340+
if (strlen((char*)userdata) >= (size_t)size) {
341+
/* Do not return truncated trash, just say we could not do it */
342+
return 0;
343+
}
344+
330345
strncpy(buf, static_cast<char *>(userdata), static_cast<size_t>(size));
331346
buf[size - 1] = '\0';
332347
return static_cast<int>(strlen(buf));
@@ -861,7 +876,7 @@ bool Socket::isSSL()const
861876
#endif
862877
}
863878

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)
879+
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)
865880
{
866881
_force_ssl = force_ssl;
867882

@@ -875,6 +890,7 @@ void Socket::setSSLConfig_OpenSSL(bool force_ssl, int certverify, const std::str
875890
_cert_file = cert_file;
876891
_key_file = key_file;
877892
_key_pass = key_pass;
893+
_certident_name = certident_name;
878894

879895
_ssl_configured |= UPSCLI_SSL_CAPS_OPENSSL;
880896
#else
@@ -884,6 +900,7 @@ void Socket::setSSLConfig_OpenSSL(bool force_ssl, int certverify, const std::str
884900
NUT_UNUSED_VARIABLE(cert_file);
885901
NUT_UNUSED_VARIABLE(key_file);
886902
NUT_UNUSED_VARIABLE(key_pass);
903+
NUT_UNUSED_VARIABLE(certident_name);
887904

888905
_ssl_configured &= ~UPSCLI_SSL_CAPS_OPENSSL;
889906
#endif
@@ -978,7 +995,12 @@ void Socket::startTLS()
978995

979996
if (!_ca_file.empty() || !_ca_path.empty()) {
980997
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");
998+
if (!(_ca_file.empty() && !_ca_path.empty()
999+
/* Retry in case CERTPATH pointed to PEM file */
1000+
&& SSL_CTX_load_verify_locations(_ssl_ctx, _ca_path.c_str(), nullptr) == 1)
1001+
) {
1002+
throw nut::SSLException_OpenSSL("Failed to load CA verify locations");
1003+
}
9821004
}
9831005
}
9841006
if (_certverify != -1) {
@@ -990,6 +1012,15 @@ void Socket::startTLS()
9901012
}
9911013
if (!_key_pass.empty()) {
9921014
# if OPENSSL_VERSION_NUMBER < 0x10100000L
1015+
/* Per https://docs.openssl.org/3.5/man3/SSL_CTX_set_default_passwd_cb,
1016+
* the `SSL_CTX*` variants were added in 1.1.
1017+
* The SSL_set_default_passwd_cb() and SSL_set_default_passwd_cb_userdata()
1018+
* for `SSL*` argument were around since the turn of millennium, approx 0.9.6+
1019+
* per https://github.com/openssl/openssl/commit/66ebbb6a56bc1688fa37878e4feec985b0c260d7
1020+
*
1021+
* But to use those, we would need to get that SSL* (maybe from socket FD?);
1022+
* that would also unlock us using the ssl_error() elsewhere.
1023+
*/
9931024
throw nut::SSLException_OpenSSL("Private key password support not implemented for OpenSSL < 1.1 yet");
9941025
# else
9951026
/* OpenSSL 1.1.0+
@@ -1004,6 +1035,59 @@ void Socket::startTLS()
10041035
if (SSL_CTX_use_PrivateKey_file(_ssl_ctx, _key_file.empty() ? _cert_file.c_str() : _key_file.c_str(), SSL_FILETYPE_PEM) != 1) {
10051036
throw nut::SSLException_OpenSSL("Failed to load client private key file");
10061037
}
1038+
1039+
if (!_certident_name.empty()) {
1040+
# if OPENSSL_VERSION_NUMBER >= 0x10002000L
1041+
X509 *x509 = SSL_CTX_get0_certificate(_ssl_ctx);
1042+
if (x509) {
1043+
/* Check if _certident_name matches the host (CN or SAN) */
1044+
if (X509_check_host(x509, _certident_name.c_str(), 0, 0, nullptr) != 1
1045+
&& X509_check_ip_asc(x509, _certident_name.c_str(), 0) != 1
1046+
) {
1047+
char *subject = X509_NAME_oneline(X509_get_subject_name(x509), nullptr, 0);
1048+
char *subject_CN = (subject ? static_cast<char*>(strstr(subject, "CN=")) + 3 : nullptr);
1049+
size_t certident_len = _certident_name.length();
1050+
1051+
if (_debugConnect) std::cerr <<
1052+
"[D4] Socket::startTLS(): My certificate subject: '" << (subject ? subject : "unknown") <<
1053+
"'; CN: '" << (subject_CN ? subject_CN : "unknown") <<
1054+
"'; CERTIDENT: [" << certident_len << "]'" << _certident_name << "'" <<
1055+
std::endl << std::flush;
1056+
1057+
/* Check if _certident_name matches the whole subject or just .../CN=.../ part as a string */
1058+
if (!subject || !(
1059+
strcmp(subject, _certident_name.c_str()) == 0
1060+
|| (subject_CN && !strncmp(subject_CN, _certident_name.c_str(), certident_len)
1061+
&& (subject_CN[certident_len] == '\0' || subject_CN[certident_len] == '/') )
1062+
)) {
1063+
/* This way or that, the names differ */
1064+
std::string err = "Certificate subject (" + std::string(subject ? subject : "unknown") + ") does not match CERTIDENT name (" + _certident_name + ")";
1065+
if (subject) {
1066+
OPENSSL_free(subject);
1067+
}
1068+
throw nut::SSLException_OpenSSL(err);
1069+
} else {
1070+
if (_debugConnect) std::cerr <<
1071+
"[D2] Socket::startTLS(): Certificate subject verified against CERTIDENT subject name (" << _certident_name << ")" <<
1072+
std::endl << std::flush;
1073+
}
1074+
if (subject) {
1075+
OPENSSL_free(subject);
1076+
}
1077+
} else {
1078+
if (_debugConnect) std::cerr <<
1079+
"[D2] Socket::startTLS(): Certificate subject verified against CERTIDENT host name (" << _certident_name << ")" <<
1080+
std::endl << std::flush;
1081+
}
1082+
}
1083+
# else
1084+
throw nut::SSLException_OpenSSL("Can not verify CERTIDENT '" + _certident_name + "': not supported in this OpenSSL build (too old)");
1085+
# endif
1086+
}
1087+
} else {
1088+
if (!_certident_name.empty()) {
1089+
throw nut::SSLException_OpenSSL("Can not verify CERTIDENT '" + _certident_name + "': no cert_file was provided");
1090+
}
10071091
}
10081092

10091093
_ssl = SSL_new(_ssl_ctx);
@@ -1419,7 +1503,7 @@ void SSLConfig::apply(TcpClient& client) const
14191503

14201504
void SSLConfig_OpenSSL::apply(TcpClient& client) const
14211505
{
1422-
client.setSSLConfig_OpenSSL(_force_ssl, _certverify, _ca_path, _ca_file, _cert_file, _key_file, _key_pass);
1506+
client.setSSLConfig_OpenSSL(_force_ssl, _certverify, _ca_path, _ca_file, _cert_file, _key_file, _key_pass, _certident_name);
14231507
}
14241508

14251509
void SSLConfig_NSS::apply(TcpClient& client) const
@@ -1432,7 +1516,7 @@ void TcpClient::setSSLConfig(const SSLConfig& config)
14321516
config.apply(*this);
14331517
}
14341518

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)
1519+
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)
14361520
{
14371521
_force_ssl = force_ssl;
14381522
_certverify = certverify;
@@ -1441,9 +1525,10 @@ void TcpClient::setSSLConfig_OpenSSL(bool force_ssl, int certverify, const char
14411525
if (cert_file) _cert_file = cert_file;
14421526
if (key_file) _key_file = key_file;
14431527
if (key_pass) _key_pass = key_pass;
1528+
if (certident_name) _certident_name = certident_name;
14441529
}
14451530

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)
1531+
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)
14471532
{
14481533
_force_ssl = force_ssl;
14491534
_certverify = certverify;
@@ -1452,6 +1537,7 @@ void TcpClient::setSSLConfig_OpenSSL(bool force_ssl, int certverify, const std::
14521537
_cert_file = cert_file;
14531538
_key_file = key_file;
14541539
_key_pass = key_pass;
1540+
_certident_name = certident_name;
14551541
}
14561542

14571543
void 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 +1579,8 @@ void TcpClient::connect()
14931579
{
14941580
_socket->connect(_host, _port);
14951581
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);
1582+
if (!_ca_path.empty() || !_ca_file.empty() || !_cert_file.empty() || !_key_file.empty() || !_certident_name.empty()) {
1583+
_socket->setSSLConfig_OpenSSL(_force_ssl, _certverify, _ca_path, _ca_file, _cert_file, _key_file, _key_pass, _certident_name);
14981584
} else if (!_certstore_path.empty() || !_certstore_prefix.empty() || !_certhost_name.empty() || !_certident_name.empty()) {
14991585
_socket->setSSLConfig_NSS(_force_ssl, _certverify, _certstore_path, _key_pass, _certstore_prefix, _certhost_name, _certident_name);
15001586
}
@@ -2931,13 +3017,13 @@ NUTCLIENT_TCP_t nutclient_tcp_create_client(const char* host, uint16_t port)
29313017

29323018
int nutclient_tcp_get_ssl_caps(void) { return nut::TcpClient::getSslCaps(); }
29333019

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)
3020+
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)
29353021
{
29363022
nut::TcpClient* client = new nut::TcpClient;
29373023
try
29383024
{
29393025
client->setSSLConfig(nut::SSLConfig_OpenSSL(
2940-
(force_ssl > 0), certverify, ca_path, ca_file, cert_file, key_file, key_pass
3026+
(force_ssl > 0), certverify, ca_path, ca_file, cert_file, key_file, key_pass, certident_name
29413027
));
29423028
client->connect(host, port, try_ssl != 0);
29433029
return static_cast<NUTCLIENT_TCP_t>(client);
@@ -2950,15 +3036,15 @@ NUTCLIENT_TCP_t nutclient_tcp_create_client_ssl_OpenSSL(const char* host, uint16
29503036
}
29513037
}
29523038

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)
3039+
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)
29543040
{
29553041
if(client)
29563042
{
29573043
nut::TcpClient* cl = dynamic_cast<nut::TcpClient*>(static_cast<nut::Client*>(client));
29583044
if(cl)
29593045
{
29603046
cl->setSSLConfig(nut::SSLConfig_OpenSSL((
2961-
force_ssl > 0), certverify, ca_path, ca_file, cert_file, key_file, key_pass
3047+
force_ssl > 0), certverify, ca_path, ca_file, cert_file, key_file, key_pass, certident_name
29623048
));
29633049
}
29643050
}

clients/nutclient.h

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -109,22 +109,25 @@ class SSLConfig_OpenSSL : public SSLConfig
109109
SSLConfig_OpenSSL(bool force_ssl = false, int certverify = -1,
110110
const std::string& ca_path = "", const std::string& ca_file = "",
111111
const std::string& cert_file = "", const std::string& key_file = "",
112-
const std::string& key_pass = "")
112+
const std::string& key_pass = "", const std::string& certident_name = "")
113113
: SSLConfig(force_ssl, certverify), _ca_path(ca_path), _ca_file(ca_file),
114-
_cert_file(cert_file), _key_file(key_file), _key_pass(key_pass) {}
114+
_cert_file(cert_file), _key_file(key_file), _key_pass(key_pass),
115+
_certident_name(certident_name) {}
115116

116117
SSLConfig_OpenSSL(bool force_ssl, int certverify,
117118
const char *ca_path, const char *ca_file,
118119
const char *cert_file, const char *key_file,
119-
const char *key_pass)
120+
const char *key_pass, const char *certident_name = nullptr)
120121
: SSLConfig(force_ssl, certverify), _ca_path(ca_path), _ca_file(ca_file),
121-
_cert_file(cert_file), _key_file(key_file), _key_pass(key_pass) {}
122+
_cert_file(cert_file), _key_file(key_file), _key_pass(key_pass),
123+
_certident_name(certident_name) {}
122124

123125
const std::string& getCAPath() const { return _ca_path; }
124126
const std::string& getCAFile() const { return _ca_file; }
125127
const std::string& getCertFile() const { return _cert_file; }
126128
const std::string& getKeyFile() const { return _key_file; }
127129
const std::string& getKeyPass() const { return _key_pass; }
130+
const std::string& getCertIdentName() const { return _certident_name; }
128131

129132
virtual void apply(TcpClient& client) const override;
130133

@@ -134,6 +137,7 @@ class SSLConfig_OpenSSL : public SSLConfig
134137
std::string _cert_file;
135138
std::string _key_file;
136139
std::string _key_pass;
140+
std::string _certident_name;
137141
};
138142

139143
/**
@@ -826,8 +830,9 @@ class TcpClient : public Client
826830
* \param cert_file Path to a client certificate file (PEM format for OpenSSL) or nickname (NSS).
827831
* \param key_file Path to a client private key file (PEM format for OpenSSL).
828832
* \param key_pass Optional passphrase to decrypt the private key.
833+
* \param certident_name Expected name in the client certificate (CN or SAN).
829834
*/
830-
void 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);
835+
void 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 = nullptr);
831836

832837
/**
833838
* Set SSL configuration for OpenSSL.
@@ -838,8 +843,9 @@ class TcpClient : public Client
838843
* \param cert_file Path to a client certificate file (PEM format for OpenSSL) or nickname (NSS).
839844
* \param key_file Path to a client private key file (PEM format for OpenSSL).
840845
* \param key_pass Optional passphrase to decrypt the private key.
846+
* \param certident_name Expected name in the client certificate (CN or SAN).
841847
*/
842-
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);
848+
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 = "");
843849

844850
/**
845851
* Set SSL configuration for Mozilla NSS
@@ -1486,11 +1492,13 @@ NUTCLIENT_TCP_t nutclient_tcp_create_client_ssl_OpenSSL(
14861492
const char* host, uint16_t port, int try_ssl,
14871493
int force_ssl, int certverify,
14881494
const char *ca_path, const char *ca_file,
1489-
const char *cert_file, const char *key_file, const char *key_pass);
1495+
const char *cert_file, const char *key_file,
1496+
const char *key_pass, const char *certident_name);
14901497
void nutclient_tcp_set_ssl_config_OpenSSL(NUTCLIENT_TCP_t client,
14911498
int force_ssl, int certverify,
14921499
const char *ca_path, const char *ca_file,
1493-
const char *cert_file, const char *key_file, const char *key_pass);
1500+
const char *cert_file, const char *key_file,
1501+
const char *key_pass, const char *certident_name);
14941502

14951503
NUTCLIENT_TCP_t nutclient_tcp_create_client_ssl_NSS(
14961504
const char* host, uint16_t port, int try_ssl,

0 commit comments

Comments
 (0)