/* netssl.c - Interface to OpenSSL for upsd Copyright (C) 2002 Russell Kroll 2008 Arjen de Korte based on the original implementation: Copyright (C) 2002 Technorama Ltd. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include "upsd.h" #include "neterr.h" #include "netssl.h" #ifdef WITH_NSS #include #include #include #include #include #include #include #endif /* WITH_NSS */ char *certfile = NULL; char *certname = NULL; char *certpasswd = NULL; #ifdef WITH_CLIENT_CERTIFICATE_VALIDATION int certrequest = 0; #endif /* WITH_CLIENT_CERTIFICATE_VALIDATION */ static int ssl_initialized = 0; #ifndef WITH_SSL /* stubs for non-ssl compiles */ void net_starttls(nut_ctype_t *client, int numarg, const char **arg) { send_err(client, NUT_ERR_FEATURE_NOT_SUPPORTED); return; } int ssl_write(nut_ctype_t *client, const char *buf, size_t buflen) { upslogx(LOG_ERR, "ssl_write called but SSL wasn't compiled in"); return -1; } int ssl_read(nut_ctype_t *client, char *buf, size_t buflen) { upslogx(LOG_ERR, "ssl_read called but SSL wasn't compiled in"); return -1; } void ssl_init(void) { ssl_initialized = 0; /* keep gcc quiet */ } void ssl_finish(nut_ctype_t *client) { if (client->ssl) { upslogx(LOG_ERR, "ssl_finish found active SSL connection but SSL wasn't compiled in"); } } void ssl_cleanup(void) { } #else #ifdef WITH_OPENSSL static SSL_CTX *ssl_ctx = NULL; static void ssl_debug(void) { int e; char errmsg[SMALLBUF]; while ((e = ERR_get_error()) != 0) { ERR_error_string_n(e, errmsg, sizeof(errmsg)); upsdebugx(1, "ssl_debug: %s", errmsg); } } static int ssl_error(SSL *ssl, int ret) { int e; e = SSL_get_error(ssl, ret); switch (e) { case SSL_ERROR_WANT_READ: upsdebugx(1, "ssl_error() ret=%d SSL_ERROR_WANT_READ", ret); break; case SSL_ERROR_WANT_WRITE: upsdebugx(1, "ssl_error() ret=%d SSL_ERROR_WANT_WRITE", ret); break; case SSL_ERROR_SYSCALL: if (ret == 0 && ERR_peek_error() == 0) { upsdebugx(1, "ssl_error() EOF from client"); } else { upsdebugx(1, "ssl_error() ret=%d SSL_ERROR_SYSCALL", ret); } break; default: upsdebugx(1, "ssl_error() ret=%d SSL_ERROR %d", ret, e); ssl_debug(); } return -1; } #elif defined(WITH_NSS) /* WITH_OPENSSL */ static CERTCertificate *cert; static SECKEYPrivateKey *privKey; static char *nss_password_callback(PK11SlotInfo *slot, PRBool retry, void *arg) { if (retry) { /* Force not inted to retrieve password many times. */ return NULL; } upslogx(LOG_INFO, "Intend to retrieve password for %s / %s: password %sconfigured", PK11_GetSlotName(slot), PK11_GetTokenName(slot), certpasswd?"":"not "); return certpasswd ? PL_strdup(certpasswd) : NULL; } static void nss_error(const char* text) { char buffer[SMALLBUF]; PRInt32 length = PR_GetErrorText(buffer); if (length > 0 && length < SMALLBUF) { upsdebugx(1, "nss_error %ld in %s : %s", (long)PR_GetError(), text, buffer); }else{ upsdebugx(1, "nss_error %ld in %s", (long)PR_GetError(), text); } } static int ssl_error(PRFileDesc *ssl, int ret) { char buffer[256]; PRInt32 length; PRErrorCode e; e = PR_GetError(); length = PR_GetErrorText(buffer); if (length > 0 && length < 256) { upsdebugx(1, "ssl_error() ret=%d %*s", e, length, buffer); } else { upsdebugx(1, "ssl_error() ret=%d", e); } return -1; } static SECStatus AuthCertificate(CERTCertDBHandle *arg, PRFileDesc *fd, PRBool checksig, PRBool isServer) { nut_ctype_t *client = (nut_ctype_t *)SSL_RevealPinArg(fd); SECStatus status = SSL_AuthCertificate(arg, fd, checksig, isServer); upslogx(LOG_INFO, "Intend to authenticate client %s : %s.", client?client->addr:"(unnamed)", status==SECSuccess?"SUCCESS":"FAILED"); return status; } static SECStatus BadCertHandler(nut_ctype_t *arg, PRFileDesc *fd) { upslogx(LOG_WARNING, "Certificate validation failed for %s", (arg&&arg->addr)?arg->addr:""); #ifdef WITH_CLIENT_CERTIFICATE_VALIDATION /* BadCertHandler is called when the NSS certificate validation is failed. * If the certificate verification (user conf) is mandatory, reject authentication * else accept it. */ return certrequest==NETSSL_CERTREQ_REQUIRE?SECFailure:SECSuccess; #else /* WITH_CLIENT_CERTIFICATE_VALIDATION */ /* Always accept clients. */ return SECSuccess; #endif /* WITH_CLIENT_CERTIFICATE_VALIDATION */ } static void HandshakeCallback(PRFileDesc *fd, nut_ctype_t *client_data) { upslogx(LOG_INFO, "SSL handshake done successfully with client %s", client_data->addr); } #endif /* WITH_OPENSSL | WITH_NSS */ void net_starttls(nut_ctype_t *client, int numarg, const char **arg) { #ifdef WITH_OPENSSL int ret; #elif defined(WITH_NSS) /* WITH_OPENSSL */ SECStatus status; PRFileDesc *socket; #endif /* WITH_OPENSSL | WITH_NSS */ if (client->ssl) { send_err(client, NUT_ERR_ALREADY_SSL_MODE); return; } client->ssl_connected = 0; if ((!certfile) || (!ssl_initialized)) { send_err(client, NUT_ERR_FEATURE_NOT_CONFIGURED); return; } #ifdef WITH_OPENSSL if (!ssl_ctx) { #elif defined(WITH_NSS) /* WITH_OPENSSL */ if (!NSS_IsInitialized()) { #endif /* WITH_OPENSSL | WITH_NSS */ send_err(client, NUT_ERR_FEATURE_NOT_CONFIGURED); ssl_initialized = 0; return; } if (!sendback(client, "OK STARTTLS\n")) { return; } #ifdef WITH_OPENSSL client->ssl = SSL_new(ssl_ctx); if (!client->ssl) { upslog_with_errno(LOG_ERR, "SSL_new failed\n"); ssl_debug(); return; } if (SSL_set_fd(client->ssl, client->sock_fd) != 1) { upslog_with_errno(LOG_ERR, "SSL_set_fd failed\n"); ssl_debug(); return; } ret = SSL_accept(client->ssl); switch (ret) { case 1: client->ssl_connected = 1; upsdebugx(3, "SSL connected"); break; case 0: upslog_with_errno(LOG_ERR, "SSL_accept do not accept handshake."); ssl_error(client->ssl, ret); break; case -1: upslog_with_errno(LOG_ERR, "Unknown return value from SSL_accept"); ssl_error(client->ssl, ret); break; } #elif defined(WITH_NSS) /* WITH_OPENSSL */ socket = PR_ImportTCPSocket(client->sock_fd); if (socket == NULL){ upslogx(LOG_ERR, "Can not inialize SSL connection"); nss_error("net_starttls / PR_ImportTCPSocket"); return; } client->ssl = SSL_ImportFD(NULL, socket); if (client->ssl == NULL){ upslogx(LOG_ERR, "Can not inialize SSL connection"); nss_error("net_starttls / SSL_ImportFD"); return; } if (SSL_SetPKCS11PinArg(client->ssl, client) == -1){ upslogx(LOG_ERR, "Can not inialize SSL connection"); nss_error("net_starttls / SSL_SetPKCS11PinArg"); return; } /* Note cast to SSLAuthCertificate to prevent warning due to * bad function prototype in NSS. */ status = SSL_AuthCertificateHook(client->ssl, (SSLAuthCertificate)AuthCertificate, CERT_GetDefaultCertDB()); if (status != SECSuccess) { upslogx(LOG_ERR, "Can not inialize SSL connection"); nss_error("net_starttls / SSL_AuthCertificateHook"); return; } status = SSL_BadCertHook(client->ssl, (SSLBadCertHandler)BadCertHandler, client); if (status != SECSuccess) { upslogx(LOG_ERR, "Can not inialize SSL connection"); nss_error("net_starttls / SSL_BadCertHook"); return; } status = SSL_HandshakeCallback(client->ssl, (SSLHandshakeCallback)HandshakeCallback, client); if (status != SECSuccess) { upslogx(LOG_ERR, "Can not inialize SSL connection"); nss_error("net_starttls / SSL_HandshakeCallback"); return; } status = SSL_ConfigSecureServer(client->ssl, cert, privKey, NSS_FindCertKEAType(cert)); if (status != SECSuccess) { upslogx(LOG_ERR, "Can not inialize SSL connection"); nss_error("net_starttls / SSL_ConfigSecureServer"); return; } status = SSL_ResetHandshake(client->ssl, PR_TRUE); if (status != SECSuccess) { upslogx(LOG_ERR, "Can not inialize SSL connection"); nss_error("net_starttls / SSL_ResetHandshake"); return; } /* Note: this call can generate memory leaks not resolvable * by any release function. * Probably SSL session key object allocation. */ status = SSL_ForceHandshake(client->ssl); if (status != SECSuccess) { PRErrorCode code = PR_GetError(); if (code==SSL_ERROR_NO_CERTIFICATE) { upslogx(LOG_WARNING, "Client %s do not provide certificate.", client->addr); } else { nss_error("net_starttls / SSL_ForceHandshake"); /* TODO : Close the connection. */ return; } } client->ssl_connected = 1; #endif /* WITH_OPENSSL | WITH_NSS */ } void ssl_init(void) { #ifdef WITH_NSS SECStatus status; #elif defined(WITH_OPENSSL) #if OPENSSL_VERSION_NUMBER >= 0x10000000L const SSL_METHOD *ssl_method; #else SSL_METHOD *ssl_method; #endif #endif /* WITH_NSS|WITH_OPENSSL */ if (!certfile) { return; } check_perms(certfile); #ifdef WITH_OPENSSL SSL_load_error_strings(); SSL_library_init(); if ((ssl_method = TLSv1_server_method()) == NULL) { ssl_debug(); fatalx(EXIT_FAILURE, "TLSv1_server_method failed"); } if ((ssl_ctx = SSL_CTX_new(ssl_method)) == NULL) { ssl_debug(); fatalx(EXIT_FAILURE, "SSL_CTX_new failed"); } if (SSL_CTX_use_certificate_chain_file(ssl_ctx, certfile) != 1) { ssl_debug(); fatalx(EXIT_FAILURE, "SSL_CTX_use_certificate_chain_file(%s) failed", certfile); } if (SSL_CTX_use_PrivateKey_file(ssl_ctx, certfile, SSL_FILETYPE_PEM) != 1) { ssl_debug(); fatalx(EXIT_FAILURE, "SSL_CTX_use_PrivateKey_file(%s) failed", certfile); } if (SSL_CTX_check_private_key(ssl_ctx) != 1) { ssl_debug(); fatalx(EXIT_FAILURE, "SSL_CTX_check_private_key(%s) failed", certfile); } if (SSL_CTX_set_cipher_list(ssl_ctx, "HIGH:@STRENGTH") != 1) { ssl_debug(); fatalx(EXIT_FAILURE, "SSL_CTX_set_cipher_list failed"); } SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_NONE, NULL); ssl_initialized = 1; #elif defined(WITH_NSS) /* WITH_OPENSSL */ if (!certname || certname[0]==0 ) { upslogx(LOG_ERR, "The SSL certificate name is not specified."); return; } PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0); PK11_SetPasswordFunc(nss_password_callback); if (certfile) /* Note: this call can generate memory leaks not resolvable * by any release function. * Probably NSS key module object allocation and * probably NSS key db object allocation too. */ status = NSS_Init(certfile); else status = NSS_NoDB_Init(NULL); if (status != SECSuccess) { upslogx(LOG_ERR, "Can not initialize SSL context"); nss_error("upscli_init / NSS_[NoDB]_Init"); return; } status = NSS_SetDomesticPolicy(); if (status != SECSuccess) { upslogx(LOG_ERR, "Can not initialize SSL policy"); nss_error("upscli_init / NSS_SetDomesticPolicy"); return; } /* Default server cache config */ status = SSL_ConfigServerSessionIDCache(0, 0, 0, NULL); if (status != SECSuccess) { upslogx(LOG_ERR, "Can not initialize SSL server cache"); nss_error("upscli_init / SSL_ConfigServerSessionIDCache"); return; } status = SSL_OptionSetDefault(SSL_ENABLE_SSL3, PR_TRUE); if (status != SECSuccess) { upslogx(LOG_ERR, "Can not enable SSLv3"); nss_error("upscli_init / SSL_OptionSetDefault(SSL_ENABLE_SSL3)"); return; } status = SSL_OptionSetDefault(SSL_ENABLE_TLS, PR_TRUE); if (status != SECSuccess) { upslogx(LOG_ERR, "Can not enable TLSv1"); nss_error("upscli_init / SSL_OptionSetDefault(SSL_ENABLE_TLS)"); return; } #ifdef WITH_CLIENT_CERTIFICATE_VALIDATION if (certrequest < NETSSL_CERTREQ_NO && certrequest > NETSSL_CERTREQ_REQUEST) { upslogx(LOG_ERR, "Invalid certificate requirement"); return; } if (certrequest == NETSSL_CERTREQ_REQUEST || certrequest == NETSSL_CERTREQ_REQUIRE ) { status = SSL_OptionSetDefault(SSL_REQUEST_CERTIFICATE, PR_TRUE); if (status != SECSuccess) { upslogx(LOG_ERR, "Can not enable certificate request"); nss_error("upscli_init / SSL_OptionSetDefault(SSL_REQUEST_CERTIFICATE)"); return; } } if (certrequest == NETSSL_CERTREQ_REQUIRE ) { status = SSL_OptionSetDefault(SSL_REQUIRE_CERTIFICATE, PR_TRUE); if (status != SECSuccess) { upslogx(LOG_ERR, "Can not enable certificate requirement"); nss_error("upscli_init / SSL_OptionSetDefault(SSL_REQUIRE_CERTIFICATE)"); return; } } #endif /* WITH_CLIENT_CERTIFICATE_VALIDATION */ cert = PK11_FindCertFromNickname(certname, NULL); if(cert==NULL) { upslogx(LOG_ERR, "Can not find server certificate"); nss_error("upscli_init / PK11_FindCertFromNickname"); return; } privKey = PK11_FindKeyByAnyCert(cert, NULL); if(privKey==NULL){ upslogx(LOG_ERR, "Can not find private key associate to server certificate"); nss_error("upscli_init / PK11_FindKeyByAnyCert"); return; } ssl_initialized = 1; #else /* WITH_OPENSSL | WITH_NSS */ upslogx(LOG_ERR, "ssl_init called but SSL wasn't compiled in"); #endif /* WITH_OPENSSL | WITH_NSS */ } int ssl_read(nut_ctype_t *client, char *buf, size_t buflen) { int ret; if (!client->ssl_connected) { return -1; } #ifdef WITH_OPENSSL ret = SSL_read(client->ssl, buf, buflen); #elif defined(WITH_NSS) /* WITH_OPENSSL */ ret = PR_Read(client->ssl, buf, buflen); #endif /* WITH_OPENSSL | WITH_NSS */ if (ret < 1) { ssl_error(client->ssl, ret); return -1; } return ret; } int ssl_write(nut_ctype_t *client, const char *buf, size_t buflen) { int ret; if (!client->ssl_connected) { return -1; } #ifdef WITH_OPENSSL ret = SSL_write(client->ssl, buf, buflen); #elif defined(WITH_NSS) /* WITH_OPENSSL */ ret = PR_Write(client->ssl, buf, buflen); #endif /* WITH_OPENSSL | WITH_NSS */ upsdebugx(5, "ssl_write ret=%d", ret); return ret; } void ssl_finish(nut_ctype_t *client) { if (client->ssl) { #ifdef WITH_OPENSSL SSL_free(client->ssl); #elif defined(WITH_NSS) PR_Shutdown(client->ssl, PR_SHUTDOWN_BOTH); PR_Close(client->ssl); #endif /* WITH_OPENSSL | WITH_NSS */ client->ssl_connected = 0; client->ssl = NULL; } } void ssl_cleanup(void) { #ifdef WITH_OPENSSL if (ssl_ctx) { SSL_CTX_free(ssl_ctx); ssl_ctx = NULL; } #elif defined(WITH_NSS) /* WITH_OPENSSL */ CERT_DestroyCertificate(cert); SECKEY_DestroyPrivateKey(privKey); NSS_Shutdown(); PR_Cleanup(); /* Called to release memory arena used by NSS/NSPR. * Prevent to show all PL_ArenaAllocate mem alloc as leaks. * https://developer.mozilla.org/en/NSS_Memory_allocation */ PL_ArenaFinish(); #endif /* WITH_OPENSSL | WITH_NSS */ ssl_initialized = 0; } #endif /* WITH_SSL */