Move the http_port cert= and key= options logic to libsecurity and add GnuTLS implementation for PEM file loading. Also adds some extra debugging to clarify listening port initialization problems with the PEM files.
Enable most of the http(s)_port listening socket logic to always build except where OpenSSL-specific dependency still exists. It may seem reasonable to leave it optionally excluded for minimal builds, however a minimal proxy that does not support HTTPS in any way is increasingly useless in the modern web so preference is given to building the generic TLS related code. This also simplifies the required testing to detect code portability issues.
GnuTLS implementation is added for https_port configured with static cert=/key= parameters and the resulting TLS handshake behaviour. Squid built with GnuTLS can now act as useful parent proxies behind a SSL-Bump'ing frontend or for other clients which require a TLS explicit proxy.
Also fixes the definitions for the CertPointer and PrivateKeyPointer.
## by testing for a 3.1.5+ function which we use
AC_CHECK_LIB(gnutls,gnutls_certificate_verify_peers3,[LIBGNUTLS_LIBS="-lgnutls"])
])
- AC_CHECK_HEADERS(gnutls/gnutls.h gnutls/x509.h)
+ AC_CHECK_HEADERS(gnutls/gnutls.h gnutls/x509.h gnutls/abstract.h)
SQUID_STATE_ROLLBACK(squid_gnutls_state) #de-pollute LIBS
for (AnyP::PortCfgPointer s = HttpPortList; s != NULL; s = s->next) {
if (!s->secure.encryptTransport)
continue;
- debugs(3, DBG_IMPORTANT, "Initializing " << AnyP::UriScheme(s->transport.protocol) << "_port " << s->s << " TLS context");
- s->secure.createSigningContexts(*s);
+ debugs(3, DBG_IMPORTANT, "Initializing " << AnyP::UriScheme(s->transport.protocol) << "_port " << s->s << " TLS contexts");
+ s->secure.initServerContexts(*s);
}
// prevent infinite fetch loops in the request parser
TLS / SSL Options:
- cert= Path to SSL certificate (PEM format).
+ tls-cert= Path to file containing an X.509 certificate (PEM format)
+ to be used in the TLS handshake ServerHello.
- key= Path to SSL private key file (PEM format)
- if not specified, the certificate file is
- assumed to be a combined certificate and
- key file.
+ If this certificate is constrained by KeyUsage TLS
+ feature it must allow HTTP server usage, along with
+ any additional restrictions imposed by your choice
+ of options= settings.
+
+ When OpenSSL is used this file may also contain a
+ chain of intermediate CA certificates to send in the
+ TLS handshake.
+
+ When GnuTLS is used this option (and any paired
+ tls-key= option) may be repeated to load multiple
+ certificates for different domains.
+
+ Also, when generate-host-certificates=on is configured
+ the first tls-cert= option must be a CA certificate
+ capable of signing the automatically generated
+ certificates.
+
+ tls-key= Path to a file containing private key file (PEM format)
+ for the previous tls-cert= option.
+
+ If tls-key= is not specified tls-cert= is assumed to
+ reference a PEM file containing both the certificate
+ and private key.
cipher= Colon separated list of supported ciphers.
NOTE: some ciphers such as EDH ciphers depend on
DEFAULT: none
LOC: HttpPortList
DOC_START
- Usage: [ip:]port [mode] cert=certificate.pem [options]
+ Usage: [ip:]port [mode] tls-cert=certificate.pem [options]
The socket address where Squid will listen for client requests made
over TLS or SSL connections. Commonly referred to as HTTPS.
This is most useful for situations where you are running squid in
- accelerator mode and you want to do the TLS work at the accelerator level.
+ accelerator mode and you want to do the TLS work at the accelerator
+ level.
You may specify multiple socket addresses on multiple lines,
each with their own certificate and/or options.
- The TLS cert= option is mandatory on HTTPS ports.
+ The tls-cert= option is mandatory on HTTPS ports.
See http_port for a list of modes and options.
DOC_END
disable Do not support https:// URLs.
cert=/path/to/client/certificate
- A client TLS certificate to use when connecting.
+ A client X.509 certificate to use when connecting.
key=/path/to/client/private_key
- The private TLS key corresponding to the cert= above.
- If key= is not specified cert= is assumed to reference
- a PEM file containing both the certificate and the key.
+ The private key corresponding to the cert= above.
+
+ If key= is not specified cert= is assumed to
+ reference a PEM file containing both the certificate
+ and private key.
cipher=... The list of valid TLS ciphers to use.
tls Encrypt connections to this peer with TLS.
sslcert=/path/to/ssl/certificate
- A client SSL certificate to use when connecting to
+ A client X.509 certificate to use when connecting to
this peer.
sslkey=/path/to/ssl/key
- The private SSL key corresponding to sslcert above.
- If 'sslkey' is not specified 'sslcert' is assumed to
- reference a combined file containing both the
- certificate and the key.
+ The private key corresponding to sslcert above.
+
+ If sslkey= is not specified sslcert= is assumed to
+ reference a PEM file containing both the certificate
+ and private key.
sslcipher=... The list of valid SSL ciphers to use when connecting
to this peer.
These options are used for Secure ICAP (icaps://....) services only.
tls-cert=/path/to/ssl/certificate
- A client SSL certificate to use when connecting to
- this icap server.
+ A client X.509 certificate to use when connecting to
+ this ICAP server.
tls-key=/path/to/ssl/key
- The private TLS/SSL key corresponding to sslcert above.
- If 'tls-key' is not specified 'tls-cert' is assumed to
- reference a combined PEM format file containing both the
- certificate and the key.
+ The private key corresponding to the previous
+ tls-cert= option.
+
+ If tls-key= is not specified tls-cert= is assumed to
+ reference a PEM file containing both the certificate
+ and private key.
tls-cipher=... The list of valid TLS/SSL ciphers to use when connecting
to this icap server.
static void clientListenerConnectionOpened(AnyP::PortCfgPointer &s, const Ipc::FdNoteId portTypeNote, const Subscription::Pointer &sub);
static IOACB httpAccept;
-#if USE_OPENSSL
-static IOACB httpsAccept;
-#endif
static CTCB clientLifetimeTimeout;
#if USE_IDENT
static IDCB clientIdentDone;
AsyncJob::Start(srv); // usually async-calls readSomeData()
}
-#if USE_OPENSSL
/// Create TLS connection structure and update fd_table
static bool
httpsCreate(const Comm::ConnectionPointer &conn, const Security::ContextPointer &ctx)
return true;
}
+ debugs(33, DBG_IMPORTANT, "ERROR: could not create TLS server context for " << conn);
conn->close();
return false;
}
/**
*
- * \retval 1 on success
- * \retval 0 when needs more data
- * \retval -1 on error
+ * \retval true on success
+ * \retval false when needs more data
+ * \retval false when an error occurred (and closes the TCP connection)
*/
-static int
-Squid_SSL_accept(ConnStateData *conn, PF *callback)
+static bool
+tlsAttemptHandshake(ConnStateData *conn, PF *callback)
{
+ // TODO: maybe throw instead of just closing the TCP connection.
+ // see https://github.com/squid-cache/squid/pull/81#discussion_r153053278
int fd = conn->clientConnection->fd;
- auto ssl = fd_table[fd].ssl.get();
- int ret;
+ auto session = fd_table[fd].ssl.get();
errno = 0;
- if ((ret = SSL_accept(ssl)) <= 0) {
- const int xerrno = errno;
- const int ssl_error = SSL_get_error(ssl, ret);
- switch (ssl_error) {
+#if USE_OPENSSL
+ const auto ret = SSL_accept(session);
+ if (ret > 0)
+ return true;
- case SSL_ERROR_WANT_READ:
- Comm::SetSelect(fd, COMM_SELECT_READ, callback, (callback != NULL ? conn : NULL), 0);
- return 0;
+ const int xerrno = errno;
+ const auto ssl_error = SSL_get_error(session, ret);
- case SSL_ERROR_WANT_WRITE:
- Comm::SetSelect(fd, COMM_SELECT_WRITE, callback, (callback != NULL ? conn : NULL), 0);
- return 0;
+ switch (ssl_error) {
- case SSL_ERROR_SYSCALL:
- if (ret == 0) {
- debugs(83, 2, "Error negotiating SSL connection on FD " << fd << ": Aborted by client: " << ssl_error);
- } else {
- debugs(83, (xerrno == ECONNRESET) ? 1 : 2, "Error negotiating SSL connection on FD " << fd << ": " <<
- (xerrno == 0 ? Security::ErrorString(ssl_error) : xstrerr(xerrno)));
- }
- return -1;
+ case SSL_ERROR_WANT_READ:
+ Comm::SetSelect(fd, COMM_SELECT_READ, callback, (callback ? conn : nullptr), 0);
+ return false;
- case SSL_ERROR_ZERO_RETURN:
- debugs(83, DBG_IMPORTANT, "Error negotiating SSL connection on FD " << fd << ": Closed by client");
- return -1;
+ case SSL_ERROR_WANT_WRITE:
+ Comm::SetSelect(fd, COMM_SELECT_WRITE, callback, (callback ? conn : nullptr), 0);
+ return false;
- default:
- debugs(83, DBG_IMPORTANT, "Error negotiating SSL connection on FD " <<
- fd << ": " << Security::ErrorString(ERR_get_error()) <<
- " (" << ssl_error << "/" << ret << ")");
- return -1;
+ case SSL_ERROR_SYSCALL:
+ if (ret == 0) {
+ debugs(83, 2, "Error negotiating SSL connection on FD " << fd << ": Aborted by client: " << ssl_error);
+ } else {
+ debugs(83, (xerrno == ECONNRESET) ? 1 : 2, "Error negotiating SSL connection on FD " << fd << ": " <<
+ (xerrno == 0 ? Security::ErrorString(ssl_error) : xstrerr(xerrno)));
}
+ break;
+
+ case SSL_ERROR_ZERO_RETURN:
+ debugs(83, DBG_IMPORTANT, "Error negotiating SSL connection on FD " << fd << ": Closed by client");
+ break;
+
+ default:
+ debugs(83, DBG_IMPORTANT, "Error negotiating SSL connection on FD " <<
+ fd << ": " << Security::ErrorString(ssl_error) <<
+ " (" << ssl_error << "/" << ret << ")");
+ }
+
+#elif USE_GNUTLS
+
+ const auto x = gnutls_handshake(session);
+ if (x == GNUTLS_E_SUCCESS)
+ return true;
+
+ if (gnutls_error_is_fatal(x)) {
+ debugs(83, 2, "Error negotiating TLS on " << conn->clientConnection << ": Aborted by client: " << Security::ErrorString(x));
- /* NOTREACHED */
+ } else if (x == GNUTLS_E_INTERRUPTED || x == GNUTLS_E_AGAIN) {
+ const auto ioAction = (gnutls_record_get_direction(session)==0 ? COMM_SELECT_READ : COMM_SELECT_WRITE);
+ Comm::SetSelect(fd, ioAction, callback, (callback ? conn : nullptr), 0);
+ return false;
}
- return 1;
+
+#else
+ // Performing TLS handshake should never be reachable without a TLS/SSL library.
+ (void)session; // avoid compiler and static analysis complaints
+ fatal("FATAL: HTTPS not supported by this Squid.");
+#endif
+
+ conn->clientConnection->close();
+ return false;
}
/** negotiate an SSL connection */
clientNegotiateSSL(int fd, void *data)
{
ConnStateData *conn = (ConnStateData *)data;
- int ret;
- if ((ret = Squid_SSL_accept(conn, clientNegotiateSSL)) <= 0) {
- if (ret < 0) // An error
- conn->clientConnection->close();
+
+ if (!tlsAttemptHandshake(conn, clientNegotiateSSL))
return;
- }
Security::SessionPointer session(fd_table[fd].ssl);
+
+#if USE_OPENSSL
if (Security::SessionIsResumed(session)) {
debugs(83, 2, "Session " << SSL_get_session(session.get()) <<
" reused on FD " << fd << " (" << fd_table[fd].ipaddr <<
" on FD " << fd << " (" << fd_table[fd].ipaddr << ":" <<
fd_table[fd].remote_port << ")");
}
+#else
+ debugs(83, 2, "TLS session reuse not yet implemented.");
+#endif
// Connection established. Retrieve TLS connection parameters for logging.
conn->clientConnection->tlsNegotiations()->retrieveNegotiatedInfo(session);
+#if USE_OPENSSL
X509 *client_cert = SSL_get_peer_certificate(session.get());
if (client_cert) {
X509_free(client_cert);
} else {
- debugs(83, 5, "FD " << fd << " has no certificate.");
+ debugs(83, 5, "FD " << fd << " has no client certificate.");
}
+#else
+ debugs(83, 2, "Client certificate requesting not yet implemented.");
+#endif
conn->readSomeData();
}
Comm::SetSelect(details->fd, COMM_SELECT_READ, clientNegotiateSSL, connState, 0);
}
+#if USE_OPENSSL
/**
* A callback function to use with the ACLFilledChecklist callback.
*/
if (!connState->fakeAConnectRequest("ssl-bump", connState->inBuf))
connState->clientConnection->close();
}
+#endif
/** handle a new HTTPS connection */
static void
ConnStateData::postHttpsAccept()
{
if (port->flags.tunnelSslBumping) {
+#if USE_OPENSSL
debugs(33, 5, "accept transparent connection: " << clientConnection);
if (!Config.accessList.ssl_bump) {
acl_checklist->al->request = request;
HTTPMSGLOCK(acl_checklist->al->request);
acl_checklist->nonBlockingCheck(httpsSslBumpAccessCheckDone, this);
+#else
+ fatal("FATAL: SSL-Bump requires --with-openssl");
+#endif
return;
} else {
httpsEstablish(this, port->secure.staticContext);
}
}
+#if USE_OPENSSL
void
ConnStateData::sslCrtdHandleReplyWrapper(void *data, const Helper::Reply &reply)
{
assert(certProperties.signAlgorithm != Ssl::algSignEnd);
if (certProperties.signAlgorithm == Ssl::algSignUntrusted) {
- assert(port->secure.untrustedSigningCert);
- certProperties.signWithX509.resetAndLock(port->secure.untrustedSigningCert.get());
- certProperties.signWithPkey.resetAndLock(port->secure.untrustedSignPkey.get());
+ assert(port->secure.untrustedSigningCa.cert);
+ certProperties.signWithX509.resetAndLock(port->secure.untrustedSigningCa.cert.get());
+ certProperties.signWithPkey.resetAndLock(port->secure.untrustedSigningCa.pkey.get());
} else {
- assert(port->secure.signingCert.get());
- certProperties.signWithX509.resetAndLock(port->secure.signingCert.get());
+ assert(port->secure.signingCa.cert.get());
+ certProperties.signWithX509.resetAndLock(port->secure.signingCa.cert.get());
- if (port->secure.signPkey)
- certProperties.signWithPkey.resetAndLock(port->secure.signPkey.get());
+ if (port->secure.signingCa.pkey)
+ certProperties.signWithPkey.resetAndLock(port->secure.signingCa.pkey.get());
}
signAlgorithm = certProperties.signAlgorithm;
}
// will call httpsPeeked() with certificate and connection, eventually
- Security::ContextPointer unConfiguredCTX(Ssl::createSSLContext(port->secure.signingCert, port->secure.signPkey, port->secure));
+ Security::ContextPointer unConfiguredCTX(Ssl::createSSLContext(port->secure.signingCa.cert, port->secure.signingCa.pkey, port->secure));
fd_table[clientConnection->fd].dynamicTlsContext = unConfiguredCTX;
if (!httpsCreate(clientConnection, unConfiguredCTX))
bio->hold(true);
// Here squid should have all of the client hello message so the
- // Squid_SSL_accept should return 0;
+ // tlsAttemptHandshake() should return false;
// This block exist only to force openSSL parse client hello and detect
// ERR_SECURE_ACCEPT_FAIL error, which should be checked and splice if required.
- int ret = 0;
- if ((ret = Squid_SSL_accept(this, NULL)) < 0) {
- debugs(83, 2, "SSL_accept failed.");
+ if (!tlsAttemptHandshake(this, nullptr)) {
+ debugs(83, 2, "TLS handshake failed.");
HttpRequest::Pointer request(http ? http->request : nullptr);
if (!clientTunnelOnError(this, context, request, HttpRequestMethod(), ERR_SECURE_ACCEPT_FAIL))
clientConnection->close();
Ssl::TheGlobalContextStorage.addLocalStorage(s->s, s->secure.dynamicCertMemCacheSize);
}
}
+#endif
if (s->secure.encryptTransport && !s->secure.staticContext) {
debugs(1, DBG_CRITICAL, "ERROR: Ignoring " << scheme << "_port " << s->s << " due to TLS context initialization failure.");
continue;
}
-#endif
// Fill out a Comm::Connection which IPC will open as a listener for us
// then pass back when active so we can start a TcpAcceptor subscription.
ListeningStartedDialer(&clientListenerConnectionOpened, s, Ipc::fdnHttpSocket, sub));
Ipc::StartListening(SOCK_STREAM, IPPROTO_TCP, s->listenConn, Ipc::fdnHttpSocket, listenCall);
-#if USE_OPENSSL
} else if (s->transport.protocol == AnyP::PROTO_HTTPS) {
// setup the subscriptions such that new connections accepted by listenConn are handled by HTTPS
RefCount<AcceptCall> subCall = commCbCall(5, 5, "httpsAccept", CommAcceptCbPtrFun(httpsAccept, CommAcceptCbParams(NULL)));
ListeningStartedDialer(&clientListenerConnectionOpened,
s, Ipc::fdnHttpsSocket, sub));
Ipc::StartListening(SOCK_STREAM, IPPROTO_TCP, s->listenConn, Ipc::fdnHttpsSocket, listenCall);
-#endif
}
HttpSockets[NHttpSockets] = -1; // set in clientListenerConnectionOpened
/// The caller assumes responsibility for connection closure detection.
void stopPinnedConnectionMonitoring();
-#if USE_OPENSSL
/// the second part of old httpsAccept, waiting for future HttpsServer home
void postHttpsAccept();
+#if USE_OPENSSL
/// Initializes and starts a peek-and-splice negotiation with the SSL client
void startPeekAndSplice();
--- /dev/null
+/*
+ * Copyright (C) 1996-2017 The Squid Software Foundation and contributors
+ *
+ * Squid software is distributed under GPLv2+ license and includes
+ * contributions from numerous individuals and organizations.
+ * Please see the COPYING and CONTRIBUTORS files for details.
+ */
+
+#include "squid.h"
+#include "anyp/PortCfg.h"
+#include "fatal.h"
+#include "security/KeyData.h"
+#include "SquidConfig.h"
+#include "ssl/bio.h"
+
+/**
+ * Read certificate from file.
+ * See also: Ssl::ReadX509Certificate function, gadgets.cc file
+ */
+bool
+Security::KeyData::loadX509CertFromFile()
+{
+ debugs(83, DBG_IMPORTANT, "Using certificate in " << certFile);
+
+#if USE_OPENSSL
+ const char *certFilename = certFile.c_str();
+ Ssl::BIO_Pointer bio(BIO_new(BIO_s_file()));
+ if (!bio || !BIO_read_filename(bio.get(), certFilename)) {
+ const auto x = ERR_get_error();
+ debugs(83, DBG_IMPORTANT, "ERROR: unable to load certificate file '" << certFile << "': " << ErrorString(x));
+ return false;
+ }
+
+ if (X509 *certificate = PEM_read_bio_X509(bio.get(), nullptr, nullptr, nullptr)) {
+ if (X509_check_issued(certificate, certificate) == X509_V_OK)
+ debugs(83, 5, "Certificate is self-signed, will not be chained");
+ else
+ cert.resetWithoutLocking(certificate);
+ }
+
+#elif USE_GNUTLS
+ const char *certFilename = certFile.c_str();
+ gnutls_datum_t data;
+ Security::ErrorCode x = gnutls_load_file(certFilename, &data);
+ if (x != GNUTLS_E_SUCCESS) {
+ debugs(83, DBG_IMPORTANT, "ERROR: unable to load certificate file '" << certFile << "': " << ErrorString(x));
+ return false;
+ }
+
+ gnutls_pcert_st pcrt;
+ x = gnutls_pcert_import_x509_raw(&pcrt, &data, GNUTLS_X509_FMT_PEM, 0);
+ if (x != GNUTLS_E_SUCCESS) {
+ debugs(83, DBG_IMPORTANT, "ERROR: unable to import certificate from '" << certFile << "': " << ErrorString(x));
+ return false;
+ }
+ gnutls_free(data.data);
+
+ gnutls_x509_crt_t certificate;
+ x = gnutls_pcert_export_x509(&pcrt, &certificate);
+ if (x != GNUTLS_E_SUCCESS) {
+ debugs(83, DBG_IMPORTANT, "ERROR: unable to X.509 convert certificate from '" << certFile << "': " << ErrorString(x));
+ return false;
+ }
+
+ if (certificate) {
+ cert = Security::CertPointer(certificate, [](gnutls_x509_crt_t p) {
+ debugs(83, 5, "gnutls_x509_crt_deinit cert=" << (void*)p);
+ gnutls_x509_crt_deinit(p);
+ });
+ } else {
+ cert.reset(); // paranoid: ensure cert is unset
+ }
+
+#else
+ // do nothing.
+#endif
+
+ if (!cert) {
+ debugs(83, DBG_IMPORTANT, "ERROR: unable to load certificate from '" << certFile << "'");
+ }
+
+ return bool(cert);
+}
+
+/**
+ * Read certificate from file.
+ * See also: Ssl::ReadX509Certificate function, gadgets.cc file
+ */
+void
+Security::KeyData::loadX509ChainFromFile()
+{
+#if USE_OPENSSL
+ // XXX: This BIO loads the public cert as first chain cert,
+ // so the code appending chains sends it twice in handshakes.
+ const char *certFilename = certFile.c_str();
+ Ssl::BIO_Pointer bio(BIO_new(BIO_s_file()));
+ if (!bio || !BIO_read_filename(bio.get(), certFilename)) {
+ const auto x = ERR_get_error();
+ debugs(83, DBG_IMPORTANT, "ERROR: unable to load chain file '" << certFile << "': " << ErrorString(x));
+ return;
+ }
+
+ if (X509_check_issued(cert.get(), cert.get()) == X509_V_OK)
+ debugs(83, 5, "Certificate is self-signed, will not be chained");
+ else {
+ // and add to the chain any other certificate exist in the file
+ while (X509 *ca = PEM_read_bio_X509(bio.get(), nullptr, nullptr, nullptr)) {
+ // XXX: self-signed check should be applied to all certs loaded.
+ // XXX: missing checks that the chained certs are actually part of a chain for validating cert.
+ chain.emplace_front(Security::CertPointer(ca));
+ }
+ }
+
+#elif USE_GNUTLS
+ // XXX: implement chain loading
+ debugs(83, 2, "Loading certificate chain from PEM files not implemented in this Squid.");
+
+#else
+ // nothing to do.
+#endif
+}
+
+/**
+ * Read X.509 private key from file.
+ */
+bool
+Security::KeyData::loadX509PrivateKeyFromFile()
+{
+ debugs(83, DBG_IMPORTANT, "Using key in " << privateKeyFile);
+
+#if USE_OPENSSL
+ const char *keyFilename = privateKeyFile.c_str();
+ // XXX: Ssl::AskPasswordCb needs SSL_CTX_set_default_passwd_cb_userdata()
+ // so this may not fully work iff Config.Program.ssl_password is set.
+ pem_password_cb *cb = ::Config.Program.ssl_password ? &Ssl::AskPasswordCb : nullptr;
+ Ssl::ReadPrivateKeyFromFile(keyFilename, pkey, cb);
+
+ if (pkey && !X509_check_private_key(cert.get(), pkey.get())) {
+ debugs(83, DBG_IMPORTANT, "WARNING: '" << privateKeyFile << "' X509_check_private_key() failed");
+ pkey.reset();
+ }
+
+#elif USE_GNUTLS
+ const char *keyFilename = privateKeyFile.c_str();
+ gnutls_datum_t data;
+ if (gnutls_load_file(keyFilename, &data) == GNUTLS_E_SUCCESS) {
+ gnutls_privkey_t key;
+ (void)gnutls_privkey_init(&key);
+ Security::ErrorCode x = gnutls_privkey_import_x509_raw(key, &data, GNUTLS_X509_FMT_PEM, nullptr, 0);
+ if (x == GNUTLS_E_SUCCESS) {
+ gnutls_x509_privkey_t xkey;
+ gnutls_privkey_export_x509(key, &xkey);
+ gnutls_privkey_deinit(key);
+ pkey = Security::PrivateKeyPointer(xkey, [](gnutls_x509_privkey_t p) {
+ debugs(83, 5, "gnutls_x509_privkey_deinit pkey=" << (void*)p);
+ gnutls_x509_privkey_deinit(p);
+ });
+ }
+ }
+ gnutls_free(data.data);
+
+#else
+ // nothing to do.
+#endif
+
+ return bool(pkey);
+}
+
+void
+Security::KeyData::loadFromFiles(const AnyP::PortCfg &port, const char *portType)
+{
+ char buf[128];
+ if (!loadX509CertFromFile()) {
+ debugs(83, DBG_IMPORTANT, "WARNING: '" << portType << "_port " << port.s.toUrl(buf, sizeof(buf)) << "' missing certificate in '" << certFile << "'");
+ return;
+ }
+
+ // certificate chain in the PEM file is optional
+ loadX509ChainFromFile();
+
+ // pkey is mandatory, not having it makes cert and chain pointless.
+ if (!loadX509PrivateKeyFromFile()) {
+ debugs(83, DBG_IMPORTANT, "WARNING: '" << portType << "_port " << port.s.toUrl(buf, sizeof(buf)) << "' missing private key in '" << privateKeyFile << "'");
+ cert.reset();
+ chain.clear();
+ }
+}
#ifndef SQUID_SRC_SECURITY_KEYDATA_H
#define SQUID_SRC_SECURITY_KEYDATA_H
+#include "anyp/forward.h"
#include "sbuf/SBuf.h"
#include "security/forward.h"
/// TLS certificate and private key details from squid.conf
class KeyData
{
+public:
+ /// load the contents of certFile and privateKeyFile into memory cert, pkey and chain
+ void loadFromFiles(const AnyP::PortCfg &, const char *portType);
+
public:
SBuf certFile; ///< path of file containing PEM format X.509 certificate
SBuf privateKeyFile; ///< path of file containing private key in PEM format
+
+ /// public X.509 certificate from certFile
+ Security::CertPointer cert;
+ /// private key from privateKeyFile
+ Security::PrivateKeyPointer pkey;
+ /// any certificates which must be chained from cert
+ Security::CertList chain;
+
+private:
+ bool loadX509CertFromFile();
+ void loadX509ChainFromFile();
+ bool loadX509PrivateKeyFromFile();
};
} // namespace Security
Handshake.cc \
Handshake.h \
forward.h \
+ KeyData.cc \
KeyData.h \
LockingPointer.h \
NegotiationHistory.cc \
void parseOptions(); ///< parsed value of sslOptions
long parseFlags();
void loadCrlFile();
+ void loadKeysFile();
public:
SBuf sslOptions; ///< library-specific options string
staticContextSessionId = old.staticContextSessionId;
generateHostCertificates = old.generateHostCertificates;
- signingCert = old.signingCert;
- signPkey = old.signPkey;
- certsToChain = old.certsToChain;
- untrustedSigningCert = old.untrustedSigningCert;
- untrustedSignPkey = old.untrustedSignPkey;
+ signingCa = old.signingCa;
+ untrustedSigningCa = old.untrustedSigningCa;
dynamicCertMemCacheSize = old.dynamicCertMemCacheSize;
}
return *this;
return ctx;
}
+void
+Security::ServerOptions::initServerContexts(AnyP::PortCfg &port)
+{
+ const char *portType = AnyP::ProtocolType_str[port.transport.protocol];
+ for (auto &keyData : certs) {
+ keyData.loadFromFiles(port, portType);
+ }
+
+ if (generateHostCertificates) {
+ createSigningContexts(port);
+
+ } else if (!createStaticServerContext(port)) {
+ char buf[128];
+ fatalf("%s_port %s initialization error", portType, port.s.toUrl(buf, sizeof(buf)));
+ }
+}
+
bool
Security::ServerOptions::createStaticServerContext(AnyP::PortCfg &port)
{
Security::ContextPointer t(createBlankContext());
if (t) {
+
#if USE_OPENSSL
- if (!Ssl::InitServerContext(t, port))
+ if (certs.size() > 1) {
+ // NOTE: calling SSL_CTX_use_certificate() repeatedly _replaces_ the previous cert details.
+ // so we cannot use it and support multiple server certificates with OpenSSL.
+ debugs(83, DBG_CRITICAL, "ERROR: OpenSSL does not support multiple server certificates. Ignoring addional cert= parameters.");
+ }
+
+ const auto &keys = certs.front();
+
+ if (!SSL_CTX_use_certificate(t.get(), keys.cert.get())) {
+ const auto x = ERR_get_error();
+ debugs(83, DBG_CRITICAL, "ERROR: Failed to acquire TLS certificate '" << keys.certFile << "': " << Security::ErrorString(x));
return false;
+ }
+
+ if (!SSL_CTX_use_PrivateKey(t.get(), keys.pkey.get())) {
+ const auto x = ERR_get_error();
+ debugs(83, DBG_CRITICAL, "ERROR: Failed to acquire TLS private key '" << keys.privateKeyFile << "': " << Security::ErrorString(x));
+ return false;
+ }
+
+ for (auto cert : keys.chain) {
+ if (SSL_CTX_add_extra_chain_cert(t.get(), cert.get())) {
+ // increase the certificate lock
+ X509_up_ref(cert.get());
+ } else {
+ const auto error = ERR_get_error();
+ debugs(83, DBG_IMPORTANT, "WARNING: can not add certificate to SSL context chain: " << Security::ErrorString(error));
+ }
+ }
+
+#elif USE_GNUTLS
+ for (auto &keys : certs) {
+ gnutls_x509_crt_t crt = keys.cert.get();
+ gnutls_x509_privkey_t xkey = keys.pkey.get();
+ const auto x = gnutls_certificate_set_x509_key(t.get(), &crt, 1, xkey);
+ if (x != GNUTLS_E_SUCCESS) {
+ SBuf whichFile = keys.certFile;
+ if (keys.certFile != keys.privateKeyFile) {
+ whichFile.appendf(" and ");
+ whichFile.append(keys.privateKeyFile);
+ }
+ debugs(83, DBG_CRITICAL, "ERROR: Failed to initialize server context with keys from " << whichFile << ": " << Security::ErrorString(x));
+ return false;
+ }
+ // XXX: add cert chain to the context
+ }
#endif
+
+ if (!updateContextConfig(t)) {
+ debugs(83, DBG_CRITICAL, "ERROR: Configuring static TLS context");
+ return false;
+ }
+
if (!loadClientCaFile())
return false;
}
}
void
-Security::ServerOptions::createSigningContexts(AnyP::PortCfg &port)
+Security::ServerOptions::createSigningContexts(const AnyP::PortCfg &port)
{
- const char *portType = AnyP::ProtocolType_str[port.transport.protocol];
- if (!certs.empty()) {
-#if USE_OPENSSL
- Security::KeyData &keys = certs.front();
- Ssl::readCertChainAndPrivateKeyFromFiles(signingCert, signPkey, certsToChain, keys.certFile.c_str(), keys.privateKeyFile.c_str());
-#else
- char buf[128];
- fatalf("Directive '%s_port %s' requires --with-openssl.", portType, port.s.toUrl(buf, sizeof(buf)));
-#endif
- }
+ // For signing we do not have a pre-initialized context object. Instead
+ // contexts are generated as needed. This method initializes the cert
+ // and key pointers used to sign those contexts later.
- if (!signingCert) {
+ signingCa = certs.front();
+
+ const char *portType = AnyP::ProtocolType_str[port.transport.protocol];
+ if (!signingCa.cert) {
char buf[128];
- fatalf("No valid signing SSL certificate configured for %s_port %s", portType, port.s.toUrl(buf, sizeof(buf)));
+ // XXX: we never actually checked that the cert is capable of signing!
+ fatalf("No valid signing certificate configured for %s_port %s", portType, port.s.toUrl(buf, sizeof(buf)));
}
- if (!signPkey)
- debugs(3, DBG_IMPORTANT, "No SSL private key configured for " << portType << "_port " << port.s);
+ if (!signingCa.pkey)
+ debugs(3, DBG_IMPORTANT, "No TLS private key configured for " << portType << "_port " << port.s);
#if USE_OPENSSL
- Ssl::generateUntrustedCert(untrustedSigningCert, untrustedSignPkey, signingCert, signPkey);
+ Ssl::generateUntrustedCert(untrustedSigningCa.cert, untrustedSigningCa.pkey, signingCa.cert, signingCa.pkey);
+#elif USE_GNUTLS
+ // TODO: implement for GnuTLS. Just a warning for now since generate is implicitly on for all crypto builds.
+ signingCa.cert.reset();
+ signingCa.pkey.reset();
+ debugs(83, DBG_CRITICAL, "WARNING: Dynamic TLS certificate generation requires --with-openssl.");
+ return;
+#else
+ debugs(83, DBG_CRITICAL, "ERROR: Dynamic TLS certificate generation requires --with-openssl.");
+ return;
#endif
- if (!untrustedSigningCert) {
+ if (!untrustedSigningCa.cert) {
char buf[128];
- fatalf("Unable to generate signing SSL certificate for untrusted sites for %s_port %s", portType, port.s.toUrl(buf, sizeof(buf)));
- }
-
- if (!createStaticServerContext(port)) {
- char buf[128];
- fatalf("%s_port %s initialization error", portType, port.s.toUrl(buf, sizeof(buf)));
+ fatalf("Unable to generate signing certificate for untrusted sites for %s_port %s", portType, port.s.toUrl(buf, sizeof(buf)));
}
}
virtual Security::ContextPointer createBlankContext() const;
virtual void dumpCfg(Packable *, const char *pfx) const;
- /// generate a security server-context from these configured options
- /// the resulting context is stored in staticContext
- /// \returns true if a context could be created
- bool createStaticServerContext(AnyP::PortCfg &);
-
- /// initialize contexts for signing dynamic TLS certificates (if needed)
- /// the resulting context is stored in signingCert, signPKey, untrustedSigningCert, untrustedSignPKey
- void createSigningContexts(AnyP::PortCfg &);
+ /// initialize all server contexts as-needed
+ void initServerContexts(AnyP::PortCfg &);
/// update the given TLS security context using squid.conf settings
bool updateContextConfig(Security::ContextPointer &);
Security::ContextPointer staticContext;
SBuf staticContextSessionId; ///< "session id context" for staticContext
+#if USE_OPENSSL
+ bool generateHostCertificates = true; ///< dynamically make host cert
+#elif USE_GNUTLS
+ // TODO: GnuTLS does implement TLS server connections so the cert
+ // generate vs static choice can be reached in the code now.
+ // But this feature is not fully working implemented so must not
+ // be enabled by default for production installations.
+ bool generateHostCertificates = false; ///< dynamically make host cert
+#else
+ // same as OpenSSL so config errors show up easily
bool generateHostCertificates = true; ///< dynamically make host cert
+#endif
- Security::CertPointer signingCert; ///< x509 certificate for signing generated certificates
- Security::PrivateKeyPointer signPkey; ///< private key for signing generated certificates
- Security::CertList certsToChain; ///< x509 certificates to send with the generated cert
- Security::CertPointer untrustedSigningCert; ///< x509 certificate for signing untrusted generated certificates
- Security::PrivateKeyPointer untrustedSignPkey; ///< private key for signing untrusted generated certificates
+ Security::KeyData signingCa; ///< x509 certificate and key for signing generated certificates
+ Security::KeyData untrustedSigningCa; ///< x509 certificate and key for signing untrusted generated certificates
/// max size of generated certificates memory cache (4 MB default)
size_t dynamicCertMemCacheSize = 4*1024*1024;
bool loadClientCaFile();
void loadDhParams();
+ /// generate a security server-context from these configured options
+ /// the resulting context is stored in staticContext
+ /// \returns true if a context could be created
+ bool createStaticServerContext(AnyP::PortCfg &);
+
+ /// initialize contexts for signing dynamic TLS certificates (if needed)
+ /// the resulting keys are stored in signingCa and untrustedSigningCa
+ void createSigningContexts(const AnyP::PortCfg &);
+
private:
SBuf clientCaFile; ///< name of file to load client CAs from
#if USE_OPENSSL
#include "security/Context.h"
#include "security/Session.h"
-#if USE_GNUTLS && HAVE_GNUTLS_X509_H
-#include <gnutls/x509.h>
+#if USE_GNUTLS && HAVE_GNUTLS_ABSTRACT_H
+#include <gnutls/abstract.h>
#endif
#include <list>
#if USE_OPENSSL && HAVE_OPENSSL_ERR_H
CtoCpp1(X509_free, X509 *)
typedef Security::LockingPointer<X509, X509_free_cpp, HardFun<int, X509 *, X509_up_ref> > CertPointer;
#elif USE_GNUTLS
-CtoCpp1(gnutls_x509_crt_deinit, gnutls_x509_crt_t)
-typedef Security::LockingPointer<struct gnutls_x509_crt_int, gnutls_x509_crt_deinit> CertPointer;
+typedef std::shared_ptr<struct gnutls_x509_crt_int> CertPointer;
#else
-typedef void * CertPointer;
+typedef std::shared_ptr<void> CertPointer;
#endif
#if USE_OPENSSL
#if USE_OPENSSL
CtoCpp1(EVP_PKEY_free, EVP_PKEY *)
typedef Security::LockingPointer<EVP_PKEY, EVP_PKEY_free_cpp, HardFun<int, EVP_PKEY *, EVP_PKEY_up_ref> > PrivateKeyPointer;
+#elif USE_GNUTLS
+typedef std::shared_ptr<struct gnutls_x509_privkey_int> PrivateKeyPointer;
#else
-// XXX: incompatible with the other PrivateKeyPointer declaration (lacks self-initialization)
-typedef void *PrivateKeyPointer;
+typedef std::shared_ptr<void> PrivateKeyPointer;
#endif
class ServerOptions;
{
ConnStateData::start();
-#if USE_OPENSSL
// XXX: Until we create an HttpsServer class, use this hack to allow old
// client_side.cc code to manipulate ConnStateData object directly
if (isHttpsServer) {
postHttpsAccept();
return;
}
-#endif
typedef CommCbMemFunT<Server, CommTimeoutCbParams> TimeoutDialer;
AsyncCall::Pointer timeoutCall = JobCallback(33, 5,
#include "FadingCounter.h"
#include "fd.h"
+#include "MemBuf.h"
#include "security/Handshake.h"
+#include "ssl/support.h"
#include <iosfwd>
#include <list>
\ingroup ServerProtocolSSLAPI
*/
-/// \ingroup ServerProtocolSSLInternal
-static int
-ssl_ask_password_cb(char *buf, int size, int rwflag, void *userdata)
+int
+Ssl::AskPasswordCb(char *buf, int size, int rwflag, void *userdata)
{
FILE *in;
int len = 0;
char cmdline[1024];
- snprintf(cmdline, sizeof(cmdline), "\"%s\" \"%s\"", Config.Program.ssl_password, (const char *)userdata);
+ snprintf(cmdline, sizeof(cmdline), "\"%s\" \"%s\"", ::Config.Program.ssl_password, (const char *)userdata);
in = popen(cmdline, "r");
if (fgets(buf, size, in))
ssl_ask_password(SSL_CTX * context, const char * prompt)
{
if (Config.Program.ssl_password) {
- SSL_CTX_set_default_passwd_cb(context, ssl_ask_password_cb);
+ SSL_CTX_set_default_passwd_cb(context, Ssl::AskPasswordCb);
SSL_CTX_set_default_passwd_cb_userdata(context, (void *)prompt);
}
}
if (!ctx)
return false;
- if (!SSL_CTX_use_certificate(ctx.get(), port.secure.signingCert.get())) {
- const int ssl_error = ERR_get_error();
- const auto &keys = port.secure.certs.front();
- debugs(83, DBG_CRITICAL, "ERROR: Failed to acquire TLS certificate '" << keys.certFile << "': " << Security::ErrorString(ssl_error));
- return false;
- }
-
- if (!SSL_CTX_use_PrivateKey(ctx.get(), port.secure.signPkey.get())) {
- const int ssl_error = ERR_get_error();
- const auto &keys = port.secure.certs.front();
- debugs(83, DBG_CRITICAL, "ERROR: Failed to acquire TLS private key '" << keys.privateKeyFile << "': " << Security::ErrorString(ssl_error));
- return false;
- }
-
- Ssl::addChainToSslContext(ctx, port.secure.certsToChain);
-
- if (!port.secure.updateContextConfig(ctx)) {
- debugs(83, DBG_CRITICAL, "ERROR: Configuring static SSL context");
- return false;
- }
-
return true;
}
{
assert(ctx);
// Add signing certificate to the certificates chain
- X509 *signingCert = options.signingCert.get();
+ X509 *signingCert = options.signingCa.cert.get();
if (SSL_CTX_add_extra_chain_cert(ctx.get(), signingCert)) {
// increase the certificate lock
X509_up_ref(signingCert);
const int ssl_error = ERR_get_error();
debugs(33, DBG_IMPORTANT, "WARNING: can not add signing certificate to SSL context chain: " << Security::ErrorString(ssl_error));
}
- Ssl::addChainToSslContext(ctx, options.certsToChain);
+
+ for (auto cert : options.signingCa.chain) {
+ if (SSL_CTX_add_extra_chain_cert(ctx.get(), cert.get())) {
+ // increase the certificate lock
+ X509_up_ref(cert.get());
+ } else {
+ const auto error = ERR_get_error();
+ debugs(83, DBG_IMPORTANT, "WARNING: can not add certificate to SSL dynamic context chain: " << Security::ErrorString(error));
+ }
+ }
}
void
#endif
}
-void
-Ssl::addChainToSslContext(Security::ContextPointer &ctx, Security::CertList &chain)
-{
- if (chain.empty())
- return;
-
- for (auto cert : chain) {
- if (SSL_CTX_add_extra_chain_cert(ctx.get(), cert.get())) {
- // increase the certificate lock
- X509_up_ref(cert.get());
- } else {
- const int ssl_error = ERR_get_error();
- debugs(83, DBG_IMPORTANT, "WARNING: can not add certificate to SSL context chain: " << Security::ErrorString(ssl_error));
- }
- }
-}
-
static const char *
hasAuthorityInfoAccessCaIssuers(X509 *cert)
{
}
}
-/**
- \ingroup ServerProtocolSSLInternal
- * Read certificate from file.
- * See also: static readSslX509Certificate function, gadgets.cc file
- */
-static X509 * readSslX509CertificatesChain(char const * certFilename, Security::CertList &chain)
-{
- if (!certFilename)
- return NULL;
- Ssl::BIO_Pointer bio(BIO_new(BIO_s_file()));
- if (!bio)
- return NULL;
- if (!BIO_read_filename(bio.get(), certFilename))
- return NULL;
- X509 *certificate = PEM_read_bio_X509(bio.get(), NULL, NULL, NULL);
-
- if (certificate) {
-
- if (X509_check_issued(certificate, certificate) == X509_V_OK)
- debugs(83, 5, "Certificate is self-signed, will not be chained");
- else {
- // and add to the chain any other certificate exist in the file
- while (X509 *ca = PEM_read_bio_X509(bio.get(), nullptr, nullptr, nullptr))
- chain.emplace_front(Security::CertPointer(ca));
- }
- }
-
- return certificate;
-}
-
-void
-Ssl::readCertChainAndPrivateKeyFromFiles(Security::CertPointer & cert, Security::PrivateKeyPointer & pkey, Security::CertList &chain, char const * certFilename, char const * keyFilename)
-{
- if (keyFilename == NULL)
- keyFilename = certFilename;
-
- if (certFilename == NULL)
- certFilename = keyFilename;
-
- debugs(83, DBG_IMPORTANT, "Using certificate in " << certFilename);
-
- // XXX: ssl_ask_password_cb needs SSL_CTX_set_default_passwd_cb_userdata()
- // so this may not fully work iff Config.Program.ssl_password is set.
- pem_password_cb *cb = ::Config.Program.ssl_password ? &ssl_ask_password_cb : NULL;
- Ssl::ReadPrivateKeyFromFile(keyFilename, pkey, cb);
- cert.resetWithoutLocking(readSslX509CertificatesChain(certFilename, chain));
- if (!cert) {
- debugs(83, DBG_IMPORTANT, "WARNING: missing cert in '" << certFilename << "'");
- } else if (!pkey) {
- debugs(83, DBG_IMPORTANT, "WARNING: missing private key in '" << keyFilename << "'");
- } else if (!X509_check_private_key(cert.get(), pkey.get())) {
- debugs(83, DBG_IMPORTANT, "WARNING: X509_check_private_key() failed to verify signing cert");
- } else
- return; // everything is okay
-
- pkey.reset();
- cert.reset();
-}
-
bool Ssl::generateUntrustedCert(Security::CertPointer &untrustedCert, Security::PrivateKeyPointer &untrustedPkey, Security::CertPointer const &cert, Security::PrivateKeyPointer const & pkey)
{
// Generate the self-signed certificate, using a hard-coded subject prefix
namespace Ssl
{
+
+/// callback for receiving password to access password secured PEM files
+/// XXX: Requires SSL_CTX_set_default_passwd_cb_userdata()!
+int AskPasswordCb(char *buf, int size, int rwflag, void *userdata);
+
/// initialize the SSL library global state.
/// call before generating any SSL context
void Initialize();
*/
bool configureSSLUsingPkeyAndCertFromMemory(SSL *ssl, const char *data, AnyP::PortCfg &port);
-/**
- \ingroup ServerProtocolSSLAPI
- * Adds the certificates in certList to the certificate chain of the SSL context
- */
-void addChainToSslContext(Security::ContextPointer &, Security::CertList &);
-
/**
\ingroup ServerProtocolSSLAPI
* Configures sslContext to use squid untrusted certificates internal list
*/
void useSquidUntrusted(SSL_CTX *sslContext);
-/**
- \ingroup ServerProtocolSSLAPI
- * Read certificate, private key and any certificates which must be chained from files.
- * See also: Ssl::readCertAndPrivateKeyFromFiles function, defined in gadgets.h
- * \param certFilename name of file with certificate and certificates which must be chainned.
- * \param keyFilename name of file with private key.
- */
-void readCertChainAndPrivateKeyFromFiles(Security::CertPointer & cert, Security::PrivateKeyPointer & pkey, Security::CertList &chain, char const * certFilename, char const * keyFilename);
-
/**
\ingroup ServerProtocolSSLAPI
* Iterates over the X509 common and alternate names and to see if matches with given data
Security::HandshakeParser::HandshakeParser() STUB
bool Security::HandshakeParser::parseHello(const SBuf &) STUB_RETVAL(false)
+#include "security/KeyData.h"
+namespace Security
+{
+void KeyData::loadFromFiles(const AnyP::PortCfg &, const char *) STUB
+}
+
#include "security/NegotiationHistory.h"
Security::NegotiationHistory::NegotiationHistory() STUB
void Security::NegotiationHistory::retrieveNegotiatedInfo(const Security::SessionPointer &) STUB
void Security::ServerOptions::parse(const char *) STUB
void Security::ServerOptions::dumpCfg(Packable *, const char *) const STUB
Security::ContextPointer Security::ServerOptions::createBlankContext() const STUB_RETVAL(Security::ContextPointer())
+void Security::ServerOptions::initServerContexts(AnyP::PortCfg&) STUB
bool Security::ServerOptions::createStaticServerContext(AnyP::PortCfg &) STUB_RETVAL(false)
-void Security::ServerOptions::createSigningContexts(AnyP::PortCfg &) STUB
+void Security::ServerOptions::createSigningContexts(const AnyP::PortCfg &) STUB
bool Security::ServerOptions::updateContextConfig(Security::ContextPointer &) STUB_RETVAL(false)
void Security::ServerOptions::updateContextEecdh(Security::ContextPointer &) STUB
void Security::ServerOptions::updateContextClientCa(Security::ContextPointer &) STUB
#include "ssl/support.h"
namespace Ssl
{
+int AskPasswordCb(char *, int, int, void *) STUB_RETVAL(0)
bool InitServerContext(Security::ContextPointer &, AnyP::PortCfg &) STUB_RETVAL(false)
bool InitClientContext(Security::ContextPointer &, Security::PeerOptions &, const char *) STUB_RETVAL(false)
void SetupVerifyCallback(Security::ContextPointer &) STUB
Security::ContextPointer GenerateSslContext(CertificateProperties const &, Security::ServerOptions &, bool) STUB_RETVAL(Security::ContextPointer())
bool verifySslCertificate(Security::ContextPointer &, CertificateProperties const &) STUB_RETVAL(false)
Security::ContextPointer GenerateSslContextUsingPkeyAndCertFromMemory(const char *, Security::ServerOptions &, bool) STUB_RETVAL(Security::ContextPointer())
-void addChainToSslContext(Security::ContextPointer &, STACK_OF(X509) *) STUB
-void readCertChainAndPrivateKeyFromFiles(Security::CertPointer &, Security::PrivateKeyPointer &, Security::CertList &, char const *, char const *) STUB
int matchX509CommonNames(X509 *peer_cert, void *check_data, int (*check_func)(void *check_data, ASN1_STRING *cn_data)) STUB_RETVAL(0)
bool checkX509ServerValidity(X509 *cert, const char *server) STUB_RETVAL(false)
int asn1timeToString(ASN1_TIME *tm, char *buf, int len) STUB_RETVAL(0)