mirror of
https://github.com/mumble-voip/mumble.git
synced 2025-10-26 11:19:16 +00:00
221 lines
6.7 KiB
C++
221 lines
6.7 KiB
C++
// Copyright 2007-2023 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 <QtCore/QtGlobal>
|
|
|
|
#ifdef Q_OS_WIN
|
|
# include "win.h"
|
|
#endif
|
|
|
|
#include "Meta.h"
|
|
#include "SelfSignedCertificate.h"
|
|
#include "Server.h"
|
|
|
|
#include <openssl/err.h>
|
|
#include <openssl/evp.h>
|
|
#include <openssl/x509.h>
|
|
|
|
#ifdef Q_OS_WIN
|
|
# include <winsock2.h>
|
|
#endif
|
|
|
|
bool Server::isKeyForCert(const QSslKey &key, const QSslCertificate &cert) {
|
|
if (key.isNull() || cert.isNull() || (key.type() != QSsl::PrivateKey))
|
|
return false;
|
|
|
|
QByteArray qbaKey = key.toDer();
|
|
QByteArray qbaCert = cert.toDer();
|
|
|
|
X509 *x509 = nullptr;
|
|
EVP_PKEY *pkey = nullptr;
|
|
BIO *mem = nullptr;
|
|
|
|
mem = BIO_new_mem_buf(qbaKey.data(), static_cast< int >(qbaKey.size()));
|
|
Q_UNUSED(BIO_set_close(mem, BIO_NOCLOSE));
|
|
pkey = d2i_PrivateKey_bio(mem, nullptr);
|
|
BIO_free(mem);
|
|
|
|
mem = BIO_new_mem_buf(qbaCert.data(), static_cast< int >(qbaCert.size()));
|
|
Q_UNUSED(BIO_set_close(mem, BIO_NOCLOSE));
|
|
x509 = d2i_X509_bio(mem, nullptr);
|
|
BIO_free(mem);
|
|
mem = nullptr;
|
|
|
|
if (x509 && pkey && X509_check_private_key(x509, pkey)) {
|
|
EVP_PKEY_free(pkey);
|
|
X509_free(x509);
|
|
return true;
|
|
}
|
|
|
|
if (pkey)
|
|
EVP_PKEY_free(pkey);
|
|
if (x509)
|
|
X509_free(x509);
|
|
return false;
|
|
}
|
|
|
|
QSslKey Server::privateKeyFromPEM(const QByteArray &buf, const QByteArray &pass) {
|
|
QSslKey key;
|
|
key = QSslKey(buf, QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey, pass);
|
|
if (key.isNull())
|
|
key = QSslKey(buf, QSsl::Dsa, QSsl::Pem, QSsl::PrivateKey, pass);
|
|
#if QT_VERSION >= 0x050500
|
|
if (key.isNull())
|
|
key = QSslKey(buf, QSsl::Ec, QSsl::Pem, QSsl::PrivateKey, pass);
|
|
#endif
|
|
return key;
|
|
}
|
|
|
|
void Server::initializeCert() {
|
|
QByteArray crt, key, pass, dhparams;
|
|
|
|
// Clear all existing SSL settings
|
|
// for this server.
|
|
qscCert.clear();
|
|
qlIntermediates.clear();
|
|
qskKey.clear();
|
|
#if defined(USE_QSSLDIFFIEHELLMANPARAMETERS)
|
|
qsdhpDHParams = QSslDiffieHellmanParameters();
|
|
#endif
|
|
|
|
crt = getConf("certificate", QString()).toByteArray();
|
|
key = getConf("key", QString()).toByteArray();
|
|
pass = getConf("passphrase", QByteArray()).toByteArray();
|
|
dhparams = getConf("sslDHParams", Meta::mp.qbaDHParams).toByteArray();
|
|
|
|
QList< QSslCertificate > ql;
|
|
|
|
// Attempt to load the private key.
|
|
if (!key.isEmpty()) {
|
|
qskKey = Server::privateKeyFromPEM(key, pass);
|
|
}
|
|
|
|
// If we still can't load the key, try loading any keys from the certificate
|
|
if (qskKey.isNull() && !crt.isEmpty()) {
|
|
qskKey = Server::privateKeyFromPEM(crt);
|
|
}
|
|
|
|
// If have a key, walk the list of certs, find the one for our key,
|
|
// remove any certs for our key from the list, what's left is part of
|
|
// the CA certificate chain.
|
|
if (!qskKey.isNull()) {
|
|
ql << QSslCertificate::fromData(crt);
|
|
ql << QSslCertificate::fromData(key);
|
|
for (int i = 0; i < ql.size(); ++i) {
|
|
const QSslCertificate &c = ql.at(i);
|
|
if (isKeyForCert(qskKey, c)) {
|
|
qscCert = c;
|
|
ql.removeAt(i);
|
|
}
|
|
}
|
|
qlIntermediates = ql;
|
|
}
|
|
|
|
#if defined(USE_QSSLDIFFIEHELLMANPARAMETERS)
|
|
if (!dhparams.isEmpty()) {
|
|
QSslDiffieHellmanParameters qdhp = QSslDiffieHellmanParameters::fromEncoded(dhparams);
|
|
if (qdhp.isValid()) {
|
|
qsdhpDHParams = qdhp;
|
|
} else {
|
|
log(QString::fromLatin1("Unable to use specified Diffie-Hellman parameters (sslDHParams): %1")
|
|
.arg(qdhp.errorString()));
|
|
}
|
|
}
|
|
#else
|
|
if (!dhparams.isEmpty()) {
|
|
log("Diffie-Hellman parameters (sslDHParams) were specified, but will not be used. This version of Murmur does "
|
|
"not support Diffie-Hellman parameters.");
|
|
}
|
|
#endif
|
|
|
|
QString issuer;
|
|
|
|
QStringList issuerNames = qscCert.issuerInfo(QSslCertificate::CommonName);
|
|
if (!issuerNames.isEmpty()) {
|
|
issuer = issuerNames.first();
|
|
}
|
|
|
|
// Really old certs/keys are no good, throw them away so we can
|
|
// generate a new one below.
|
|
if (issuer == QString::fromUtf8("Murmur Autogenerated Certificate")) {
|
|
log("Old autogenerated certificate is unusable for registration, invalidating it");
|
|
qscCert = QSslCertificate();
|
|
qskKey = QSslKey();
|
|
}
|
|
|
|
// If we have a cert, and it's a self-signed one, but we're binding to
|
|
// all the same addresses as the Meta server is, use it's cert instead.
|
|
// This allows a self-signed certificate generated by Murmur to be
|
|
// replaced by a CA-signed certificate in the .ini file.
|
|
if (!qscCert.isNull() && issuer.startsWith(QString::fromUtf8("Murmur Autogenerated Certificate"))
|
|
&& !Meta::mp.qscCert.isNull() && !Meta::mp.qskKey.isNull() && (Meta::mp.qlBind == qlBind)) {
|
|
qscCert = Meta::mp.qscCert;
|
|
qskKey = Meta::mp.qskKey;
|
|
qlIntermediates = Meta::mp.qlIntermediates;
|
|
|
|
if (!qscCert.isNull() && !qskKey.isNull()) {
|
|
bUsingMetaCert = true;
|
|
}
|
|
}
|
|
|
|
// If we still don't have a certificate by now, try to load the one from Meta
|
|
if (qscCert.isNull() || qskKey.isNull()) {
|
|
if (!key.isEmpty() || !crt.isEmpty()) {
|
|
log("Certificate specified, but failed to load.");
|
|
}
|
|
|
|
qskKey = Meta::mp.qskKey;
|
|
qscCert = Meta::mp.qscCert;
|
|
qlIntermediates = Meta::mp.qlIntermediates;
|
|
|
|
if (!qscCert.isNull() && !qskKey.isNull()) {
|
|
bUsingMetaCert = true;
|
|
}
|
|
|
|
// If loading from Meta doesn't work, build+sign a new one
|
|
if (qscCert.isNull() || qskKey.isNull()) {
|
|
log("Generating new server certificate.");
|
|
|
|
if (!SelfSignedCertificate::generateMurmurV2Certificate(qscCert, qskKey)) {
|
|
log("Certificate or key generation failed");
|
|
}
|
|
|
|
setConf("certificate", qscCert.toPem());
|
|
setConf("key", qskKey.toPem());
|
|
}
|
|
}
|
|
|
|
// Drain OpenSSL's per-thread error queue
|
|
// to ensure that errors from the operations
|
|
// we've done in here do not leak out into
|
|
// Qt's SSL module.
|
|
//
|
|
// If an error leaks, it can break all connections
|
|
// to the server because each invocation of Qt's SSL
|
|
// read callback checks OpenSSL's per-thread error
|
|
// queue (albeit indirectly, via SSL_get_error()).
|
|
// Qt expects any errors returned from SSL_get_error()
|
|
// to be related to the QSslSocket it is currently
|
|
// processing -- which is the obvious thing to expect:
|
|
// SSL_get_error() takes a pointer to an SSL object
|
|
// and the return code of the failed operation.
|
|
// However, it is also documented as:
|
|
//
|
|
// "In addition to ssl and ret, SSL_get_error()
|
|
// inspects the current thread's OpenSSL error
|
|
// queue."
|
|
//
|
|
// So, if any OpenSSL operation on the main thread
|
|
// forgets to clear the error queue, those errors
|
|
// *will* leak into other things that *do* error
|
|
// checking. In our case, into Qt's SSL read callback,
|
|
// resulting in all clients being disconnected.
|
|
ERR_clear_error();
|
|
}
|
|
|
|
const QString Server::getDigest() const {
|
|
return QString::fromLatin1(qscCert.digest(QCryptographicHash::Sha1).toHex());
|
|
}
|