mumble/src/SSL.cpp

334 lines
9.4 KiB
C++

// Copyright 2005-2016 The Mumble Developers. All rights reserved.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file at the root of the
// Mumble source tree or at <https://www.mumble.info/LICENSE>.
#include "murmur_pch.h"
// Needed for MumbleSSL::qsslSanityCheck()
#ifdef Q_OS_LINUX
# include <dlfcn.h>
# include <link.h>
#endif
#include "SSL.h"
#include "Version.h"
void MumbleSSL::initialize() {
// Let Qt initialize OpenSSL...
QSslSocket::supportsSsl();
// Check that we aren't in a situation where
// Qt has dynamically loaded *another*
// version/soname of libssl and/or libcrypto
// alongside the copy we link against ourselves.
//
// This can happen when Qt is built without the
// -openssl-linked configure option. When Qt is
// built without -openssl-linked, it will try
// to dynamically load the OpenSSL libraries.
// When Qt dynamically loads OpenSSL and there
// are multiple version of libcrypto and libssl
// available on the system, it is possible for
// Qt to load another version of OpenSSL in
// addition to the one our binary is linked
// against. Typically, this will happen if
// Mumble or Murmur are linked against
// an older version, and a newer version is
// available on the system as well.
//
// If we're in a situation where two version of
// OpenSSL's libraries are loaded into our process,
// we abort immediately. Things WILL go wrong.
MumbleSSL::qsslSanityCheck();
}
// Check that we haven't loaded multiple copies of OpenSSL
// into our process by accident.
void MumbleSSL::qsslSanityCheck() {
#ifdef Q_OS_LINUX
struct link_map *lm = NULL;
void *self = dlopen(NULL, RTLD_NOW);
if (self == NULL) {
qFatal("SSL: could not dlopen program binary");
}
if (dlinfo(self, RTLD_DI_LINKMAP, &lm) == -1) {
qFatal("SSL: unable to acquire link_map: %s", dlerror());
}
if (lm == NULL) {
qFatal("SSL: link_map is NULL");
}
QStringList libssl;
QStringList libcrypto;
while (lm != NULL) {
QString name = QString::fromLocal8Bit(lm->l_name);
if (name.contains(QLatin1String("libssl"))) {
libssl << name;
} else if (name.contains(QLatin1String("libcrypto"))) {
libcrypto << name;
}
lm = lm->l_next;
}
bool ok = true;
if (libcrypto.size() == 0 || libssl.size() == 0) {
qFatal("SSL library query failed: %i libcrypto's and %i libssl's found.", libcrypto.size(), libssl.size());
}
if (libssl.size() > 1) {
qCritical("Found multiple libssl.so copies in binary: %s", qPrintable(libssl.join(QLatin1String(", "))));
ok = false;
}
if (libcrypto.size() > 1) {
qCritical("Found multiple libcrypto.so copies in binary: %s", qPrintable(libcrypto.join(QLatin1String(", "))));
ok = false;
}
if (!ok) {
qFatal("Aborting due to previous errors");
}
#endif
}
QString MumbleSSL::defaultOpenSSLCipherString() {
return QLatin1String("EECDH+AESGCM:EDH+aRSA+AESGCM:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:AES256-SHA:AES128-SHA");
}
QList<QSslCipher> MumbleSSL::ciphersFromOpenSSLCipherString(QString cipherString) {
QList<QSslCipher> chosenCiphers;
SSL_CTX *ctx = NULL;
SSL *ssl = NULL;
const SSL_METHOD *meth = NULL;
int i = 0;
QByteArray csbuf = cipherString.toLatin1();
const char *ciphers = csbuf.constData();
meth = SSLv23_server_method();
if (meth == NULL) {
qWarning("MumbleSSL: unable to get SSL method");
goto out;
}
// We use const_cast to be compatible with OpenSSL 0.9.8.
ctx = SSL_CTX_new(const_cast<SSL_METHOD *>(meth));
if (ctx == NULL) {
qWarning("MumbleSSL: unable to allocate SSL_CTX");
goto out;
}
if (!SSL_CTX_set_cipher_list(ctx, ciphers)) {
qWarning("MumbleSSL: error parsing OpenSSL cipher string in ciphersFromOpenSSLCipherString");
goto out;
}
ssl = SSL_new(ctx);
if (ssl == NULL) {
qWarning("MumbleSSL: unable to create SSL object in ciphersFromOpenSSLCipherString");
goto out;
}
while (1) {
const char *name = SSL_get_cipher_list(ssl, i);
if (name == NULL) {
break;
}
#if QT_VERSION >= 0x050300
QSslCipher c = QSslCipher(QString::fromLatin1(name));
if (!c.isNull()) {
chosenCiphers << c;
}
#else
foreach (const QSslCipher &c, QSslSocket::supportedCiphers()) {
if (c.name() == QString::fromLatin1(name)) {
chosenCiphers << c;
}
}
#endif
++i;
}
out:
SSL_CTX_free(ctx);
SSL_free(ssl);
return chosenCiphers;
}
void MumbleSSL::addSystemCA() {
#if QT_VERSION < 0x040700 && !defined(NO_SYSTEM_CA_OVERRIDE)
#if defined(Q_OS_WIN)
QStringList qsl;
qsl << QLatin1String("Ca");
qsl << QLatin1String("Root");
qsl << QLatin1String("AuthRoot");
foreach(const QString &store, qsl) {
HCERTSTORE hCertStore;
PCCERT_CONTEXT pCertContext = NULL;
bool found = false;
hCertStore = CertOpenSystemStore(NULL, store.utf16());
if (! hCertStore) {
qWarning("SSL: Failed to open CA store %s", qPrintable(store));
continue;
}
while (pCertContext = CertEnumCertificatesInStore(hCertStore, pCertContext)) {
QByteArray qba(reinterpret_cast<const char *>(pCertContext->pbCertEncoded), pCertContext->cbCertEncoded);
QList<QSslCertificate> ql = QSslCertificate::fromData(qba, QSsl::Pem);
ql += QSslCertificate::fromData(qba, QSsl::Der);
if (! ql.isEmpty()) {
found = true;
QSslSocket::addDefaultCaCertificates(ql);
}
}
if (found)
qWarning("SSL: Added CA certificates from system store '%s'", qPrintable(store));
CertCloseStore(hCertStore, 0);
}
#elif defined(Q_OS_MAC)
CFArrayRef certs = NULL;
bool found = false;
if (SecTrustCopyAnchorCertificates(&certs) == noErr) {
int ncerts = CFArrayGetCount(certs);
for (int i = 0; i < ncerts; i++) {
CFDataRef data = NULL;
SecCertificateRef cert = reinterpret_cast<SecCertificateRef>(const_cast<void *>(CFArrayGetValueAtIndex(certs, i)));
if (! cert)
continue;
if (SecKeychainItemExport(cert, kSecFormatX509Cert, kSecItemPemArmour, NULL, &data) == noErr) {
const char *ptr = reinterpret_cast<const char *>(CFDataGetBytePtr(data));
int len = CFDataGetLength(data);
QByteArray qba(ptr, len);
QList<QSslCertificate> ql = QSslCertificate::fromData(qba, QSsl::Pem);
if (! ql.isEmpty()) {
found = true;
QSslSocket::addDefaultCaCertificates(ql);
}
}
}
CFRelease(certs);
if (found)
qWarning("SSL: Added CA certificates from 'System Roots' store.");
}
#elif defined(Q_OS_UNIX)
QStringList qsl;
#ifdef SYSTEM_CA_DIR
QSslSocket::addDefaultCaCertificates(QLatin1String(MUMTEXT(SYSTEM_CA_DIR)));
#else
#ifdef SYSTEM_CA_BUNDLE
qsl << QLatin1String(MUMTEXT(SYSTEM_CA_BUNDLE));
#else
#ifdef __FreeBSD__
qsl << QLatin1String("/usr/local/share/certs/ca-root-nss.crt");
#else
qsl << QLatin1String("/etc/pki/tls/certs/ca-bundle.crt");
qsl << QLatin1String("/etc/ssl/certs/ca-certificates.crt");
#endif
#endif
foreach(const QString &filename, qsl) {
QFile f(filename);
if (f.exists() && f.open(QIODevice::ReadOnly)) {
QList<QSslCertificate> ql = QSslCertificate::fromDevice(&f, QSsl::Pem);
ql += QSslCertificate::fromDevice(&f, QSsl::Der);
if (! ql.isEmpty()) {
qWarning("SSL: Added CA certificates from '%s'", qPrintable(filename));
QSslSocket::addDefaultCaCertificates(ql);
}
}
}
#endif // SYSTEM_CA_DIR
#endif // Q_OS_UNIX
QSet<QByteArray> digests;
QList<QSslCertificate> ql;
foreach(const QSslCertificate &crt, QSslSocket::defaultCaCertificates()) {
QByteArray digest = crt.digest(QCryptographicHash::Sha1);
if (! digests.contains(digest) && crt.isValid()) {
ql << crt;
digests.insert(digest);
}
}
QSslSocket::setDefaultCaCertificates(ql);
#endif // NO_SYSTEM_CA_OVERRIDE
#if QT_VERSION >= 0x040800
// Don't perform on-demand loading of root certificates
QSslSocket::addDefaultCaCertificates(QSslSocket::systemCaCertificates());
#endif
#ifdef Q_OS_WIN
// Work around issue #1271.
// Skype's click-to-call feature creates an enormous
// amount of certificates in the Root CA store.
{
QSslConfiguration sslCfg = QSslConfiguration::defaultConfiguration();
QList<QSslCertificate> caList = sslCfg.caCertificates();
QList<QSslCertificate> filteredCaList;
foreach (QSslCertificate cert, caList) {
#if QT_VERSION >= 0x050000
QStringList orgs = cert.subjectInfo(QSslCertificate::Organization);
bool skip = false;
foreach (QString ou, orgs) {
if (ou.contains(QLatin1String("Skype"), Qt::CaseInsensitive)) {
skip = true;
break;
}
}
if (skip) {
continue;
}
#else
QString ou = cert.subjectInfo(QSslCertificate::Organization);
if (ou.contains(QLatin1String("Skype"), Qt::CaseInsensitive)) {
continue;
}
#endif
filteredCaList.append(cert);
}
sslCfg.setCaCertificates(filteredCaList);
QSslConfiguration::setDefaultConfiguration(sslCfg);
qWarning("SSL: CA certificate filter applied. Filtered size: %i, original size: %i", filteredCaList.size(), caList.size());
}
#endif
}
QString MumbleSSL::protocolToString(QSsl::SslProtocol protocol) {
switch(protocol) {
case QSsl::SslV3: return QLatin1String("SSL 3");
case QSsl::SslV2: return QLatin1String("SSL 2");
#if QT_VERSION >= 0x050000
case QSsl::TlsV1_0: return QLatin1String("TLS 1.0");
case QSsl::TlsV1_1: return QLatin1String("TLS 1.1");
case QSsl::TlsV1_2: return QLatin1String("TLS 1.2");
#else
case QSsl::TlsV1: return QLatin1String("TLS 1.0");
#endif
case QSsl::AnyProtocol: return QLatin1String("AnyProtocol");
#if QT_VERSION >= 0x040800
case QSsl::TlsV1SslV3: return QLatin1String("TlsV1SslV3");
case QSsl::SecureProtocols: return QLatin1String("SecureProtocols");
#endif
default:
case QSsl::UnknownProtocol: return QLatin1String("UnknownProtocol");
}
}