mirror of
https://github.com/mumble-voip/mumble.git
synced 2025-10-26 11:19:16 +00:00
262 lines
8.3 KiB
C++
262 lines
8.3 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"
|
|
|
|
#include "Meta.h"
|
|
#include "Server.h"
|
|
|
|
#define SSL_STRING(x) QString::fromLatin1(x).toUtf8().data()
|
|
|
|
static int add_ext(X509 * crt, int nid, char *value) {
|
|
X509_EXTENSION *ex;
|
|
X509V3_CTX ctx;
|
|
X509V3_set_ctx_nodb(&ctx);
|
|
X509V3_set_ctx(&ctx, crt, crt, NULL, NULL, 0);
|
|
ex = X509V3_EXT_conf_nid(NULL, &ctx, nid, value);
|
|
if (!ex)
|
|
return 0;
|
|
|
|
X509_add_ext(crt, ex, -1);
|
|
X509_EXTENSION_free(ex);
|
|
return 1;
|
|
}
|
|
|
|
#if defined(USE_QSSLDIFFIEHELLMANPARAMETERS)
|
|
// dh_progress is a status callback for DH_generate_parameters_ex.
|
|
// We use it to run the event loop while generating DH params, in
|
|
// order to keep the Murmur GUI on Windows responsive during the
|
|
// process.
|
|
static int dh_progress(int, int, BN_GENCB *) {
|
|
qApp->processEvents();
|
|
return 1;
|
|
}
|
|
#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 = NULL;
|
|
EVP_PKEY *pkey = NULL;
|
|
BIO *mem = NULL;
|
|
|
|
mem = BIO_new_mem_buf(qbaKey.data(), qbaKey.size());
|
|
Q_UNUSED(BIO_set_close(mem, BIO_NOCLOSE));
|
|
pkey = d2i_PrivateKey_bio(mem, NULL);
|
|
BIO_free(mem);
|
|
|
|
mem = BIO_new_mem_buf(qbaCert.data(), qbaCert.size());
|
|
Q_UNUSED(BIO_set_close(mem, BIO_NOCLOSE));
|
|
x509 = d2i_X509_bio(mem, NULL);
|
|
BIO_free(mem);
|
|
mem = NULL;
|
|
|
|
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;
|
|
}
|
|
|
|
void Server::initializeCert() {
|
|
QByteArray crt, key, pass, dhparams;
|
|
|
|
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 key as an RSA key or a DSA key
|
|
if (! key.isEmpty()) {
|
|
qskKey = QSslKey(key, QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey, pass);
|
|
if (qskKey.isNull())
|
|
qskKey = QSslKey(key, QSsl::Dsa, QSsl::Pem, QSsl::PrivateKey, pass);
|
|
}
|
|
|
|
// If we still can't load the key, try loading any keys from the certificate
|
|
if (qskKey.isNull() && ! crt.isEmpty()) {
|
|
qskKey = QSslKey(crt, QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey, pass);
|
|
if (qskKey.isNull())
|
|
qskKey = QSslKey(crt, QSsl::Dsa, QSsl::Pem, QSsl::PrivateKey, pass);
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
}
|
|
qlCA = ql;
|
|
}
|
|
|
|
#if defined(USE_QSSLDIFFIEHELLMANPARAMETERS)
|
|
if (! dhparams.isEmpty()) {
|
|
QSslDiffieHellmanParameters qdhp = QSslDiffieHellmanParameters(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;
|
|
#if QT_VERSION >= 0x050000
|
|
QStringList issuerNames = qscCert.issuerInfo(QSslCertificate::CommonName);
|
|
if (! issuerNames.isEmpty()) {
|
|
issuer = issuerNames.first();
|
|
}
|
|
#else
|
|
issuer = qscCert.issuerInfo(QSslCertificate::CommonName);
|
|
#endif
|
|
|
|
// 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 == QString::fromUtf8("Murmur Autogenerated Certificate v2") && ! Meta::mp.qscCert.isNull() && ! Meta::mp.qskKey.isNull() && (Meta::mp.qlBind == qlBind)) {
|
|
qscCert = Meta::mp.qscCert;
|
|
qskKey = Meta::mp.qskKey;
|
|
}
|
|
|
|
// 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;
|
|
|
|
// If loading from Meta doesn't work, build+sign a new one
|
|
if (qscCert.isNull() || qskKey.isNull()) {
|
|
log("Generating new server certificate.");
|
|
|
|
CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_ON);
|
|
|
|
X509 *x509 = X509_new();
|
|
EVP_PKEY *pkey = EVP_PKEY_new();
|
|
RSA *rsa = RSA_generate_key(2048,RSA_F4,NULL,NULL);
|
|
EVP_PKEY_assign_RSA(pkey, rsa);
|
|
|
|
X509_set_version(x509, 2);
|
|
ASN1_INTEGER_set(X509_get_serialNumber(x509),1);
|
|
X509_gmtime_adj(X509_get_notBefore(x509),0);
|
|
X509_gmtime_adj(X509_get_notAfter(x509),60*60*24*365*20);
|
|
X509_set_pubkey(x509, pkey);
|
|
|
|
X509_NAME *name=X509_get_subject_name(x509);
|
|
|
|
X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, reinterpret_cast<unsigned char *>(const_cast<char *>("Murmur Autogenerated Certificate v2")), -1, -1, 0);
|
|
X509_set_issuer_name(x509, name);
|
|
add_ext(x509, NID_basic_constraints, SSL_STRING("critical,CA:FALSE"));
|
|
add_ext(x509, NID_ext_key_usage, SSL_STRING("serverAuth,clientAuth"));
|
|
add_ext(x509, NID_subject_key_identifier, SSL_STRING("hash"));
|
|
add_ext(x509, NID_netscape_comment, SSL_STRING("Generated from murmur"));
|
|
|
|
X509_sign(x509, pkey, EVP_sha1());
|
|
|
|
crt.resize(i2d_X509(x509, NULL));
|
|
unsigned char *dptr=reinterpret_cast<unsigned char *>(crt.data());
|
|
i2d_X509(x509, &dptr);
|
|
|
|
qscCert = QSslCertificate(crt, QSsl::Der);
|
|
if (qscCert.isNull())
|
|
log("Certificate generation failed");
|
|
|
|
key.resize(i2d_PrivateKey(pkey, NULL));
|
|
dptr=reinterpret_cast<unsigned char *>(key.data());
|
|
i2d_PrivateKey(pkey, &dptr);
|
|
|
|
qskKey = QSslKey(key, QSsl::Rsa, QSsl::Der);
|
|
if (qskKey.isNull())
|
|
log("Key generation failed");
|
|
|
|
setConf("certificate", qscCert.toPem());
|
|
setConf("key", qskKey.toPem());
|
|
}
|
|
}
|
|
|
|
#if defined(USE_QSSLDIFFIEHELLMANPARAMETERS)
|
|
if (qsdhpDHParams.isEmpty()) {
|
|
log("Generating new server 2048-bit Diffie-Hellman parameters. This could take a while...");
|
|
|
|
DH *dh = DH_new();
|
|
if (dh == NULL) {
|
|
qFatal("DH_new failed: unable to generate Diffie-Hellman parameters for virtual server");
|
|
}
|
|
|
|
// Generate DH params.
|
|
// We register a status callback in order to update the UI
|
|
// for Murmur on Windows. We don't show the actual status,
|
|
// but we do it to keep Murmur on Windows responsive while
|
|
// generating the parameters.
|
|
BN_GENCB cb;
|
|
memset(&cb, 0, sizeof(BN_GENCB));
|
|
BN_GENCB_set(&cb, dh_progress, NULL);
|
|
if (DH_generate_parameters_ex(dh, 2048, 2, &cb) == 0) {
|
|
qFatal("DH_generate_parameters_ex failed: unable to generate Diffie-Hellman parameters for virtual server");
|
|
}
|
|
|
|
BIO *mem = BIO_new(BIO_s_mem());
|
|
if (PEM_write_bio_DHparams(mem, dh) == 0) {
|
|
qFatal("PEM_write_bio_DHparams failed: unable to write generated Diffie-Hellman parameters to memory");
|
|
}
|
|
|
|
char *pem = NULL;
|
|
long len = BIO_get_mem_data(mem, &pem);
|
|
if (len <= 0) {
|
|
qFatal("BIO_get_mem_data returned an empty or invalid buffer");
|
|
}
|
|
|
|
QByteArray pemdh(pem, len);
|
|
QSslDiffieHellmanParameters qdhp(pemdh);
|
|
if (!qdhp.isValid()) {
|
|
qFatal("QSslDiffieHellmanParameters: unable to import generated Diffie-HellmanParameters: %s", qdhp.errorString().toStdString().c_str());
|
|
}
|
|
|
|
qsdhpDHParams = qdhp;
|
|
setConf("sslDHParams", pemdh);
|
|
|
|
BIO_free(mem);
|
|
DH_free(dh);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
const QString Server::getDigest() const {
|
|
return QString::fromLatin1(qscCert.digest(QCryptographicHash::Sha1).toHex());
|
|
}
|