Meta, UnixMurmur: implement live certificate reloading via the USR1 signal.

This moves the SSL loading code from MetaParams::read() into a separate
method, loadSSLSettings(). It also hooks up the SIGUSR1 signal handler
to reload SSL settings and apply the setings to each suitable virtual
server.

A follow-up commit will change MetaParams::read() to also use loadSSLSettings(),
however it was left out of this commit to aid in reviewability.
This commit is contained in:
Mikkel Krautz 2017-02-28 03:08:30 +01:00
parent 9ae2a7f59e
commit fd78d6431e
3 changed files with 225 additions and 1 deletions

View File

@ -544,6 +544,188 @@ void MetaParams::read(QString fname) {
qmConfig.insert(QLatin1String("sslDHParams"), QString::fromLatin1(qbaDHParams.constData()));
}
bool MetaParams::loadSSLSettings() {
QSettings updatedSettings(qsAbsSettingsFilePath, QSettings::IniFormat);
#if QT_VERSION >= 0x040500
updatedSettings.setIniCodec("UTF-8");
#endif
QString tmpCiphersStr = typeCheckedFromSettings("sslCiphers", qsCiphers);
QString qsSSLCert = qsSettings->value("sslCert").toString();
QString qsSSLKey = qsSettings->value("sslKey").toString();
QString qsSSLCA = qsSettings->value("sslCA").toString();
QString qsSSLDHParams = qsSettings->value("sslDHParams").toString();
qbaPassPhrase = qsSettings->value("sslPassPhrase").toByteArray();
QSslCertificate tmpCert;
QList<QSslCertificate> tmpCA;
QList<QSslCertificate> tmpIntermediates;
QSslKey tmpKey;
QByteArray tmpDHParams;
QList<QSslCipher> tmpCiphers;
if (! qsSSLCA.isEmpty()) {
QFile pem(qsSSLCA);
if (pem.open(QIODevice::ReadOnly)) {
QByteArray qba = pem.readAll();
pem.close();
QList<QSslCertificate> ql = QSslCertificate::fromData(qba);
if (ql.isEmpty()) {
qCritical("MetaParams: Failed to parse any CA certificates from %s", qPrintable(qsSSLCA));
return false;
} else {
tmpCA = ql;
}
} else {
qCritical("MetaParams: Failed to read %s", qPrintable(qsSSLCA));
return false;
}
}
QByteArray crt, key;
if (! qsSSLCert.isEmpty()) {
QFile pem(qsSSLCert);
if (pem.open(QIODevice::ReadOnly)) {
crt = pem.readAll();
pem.close();
} else {
qCritical("MetaParams: Failed to read %s", qPrintable(qsSSLCert));
return false;
}
}
if (! qsSSLKey.isEmpty()) {
QFile pem(qsSSLKey);
if (pem.open(QIODevice::ReadOnly)) {
key = pem.readAll();
pem.close();
} else {
qCritical("MetaParams: Failed to read %s", qPrintable(qsSSLKey));
return false;
}
}
if (! key.isEmpty() || ! crt.isEmpty()) {
if (! key.isEmpty()) {
tmpKey = Server::privateKeyFromPEM(key, qbaPassPhrase);
}
if (tmpKey.isNull() && ! crt.isEmpty()) {
tmpKey = Server::privateKeyFromPEM(crt, qbaPassPhrase);
if (! tmpKey.isNull())
qCritical("MetaParams: Using private key found in certificate file.");
}
if (tmpKey.isNull()) {
qCritical("MetaParams: No private key found in certificate or key file.");
return false;
}
QList<QSslCertificate> ql = QSslCertificate::fromData(crt);
ql << QSslCertificate::fromData(key);
for (int i=0;i<ql.size(); ++i) {
const QSslCertificate &c = ql.at(i);
if (Server::isKeyForCert(tmpKey, c)) {
tmpCert = c;
ql.removeAt(i);
break;
}
}
if (tmpCert.isNull()) {
qCritical("MetaParams: Failed to find certificate matching private key.");
return false;
}
if (ql.size() > 0) {
tmpIntermediates = ql;
qCritical("MetaParams: Adding %d intermediate certificates from certificate file.", ql.size());
}
}
QByteArray dhparams;
#if defined(USE_QSSLDIFFIEHELLMANPARAMETERS)
if (! qsSSLDHParams.isEmpty()) {
QFile pem(qsSSLDHParams);
if (pem.open(QIODevice::ReadOnly)) {
dhparams = pem.readAll();
pem.close();
} else {
qCritical("MetaParams: Failed to read %s", qPrintable(qsSSLDHParams));
}
}
if (! dhparams.isEmpty()) {
QSslDiffieHellmanParameters qdhp = QSslDiffieHellmanParameters::fromEncoded(dhparams);
if (qdhp.isValid()) {
tmpDHParams = dhparams;
} else {
qCritical("MetaParams: Unable to use specified Diffie-Hellman parameters: %s", qPrintable(qdhp.errorString()));
return false;
}
}
#else
if (! qsSSLDHParams.isEmpty()) {
qCritical("MetaParams: This version of Murmur does not support Diffie-Hellman parameters (sslDHParams). Murmur will not start unless you remove the option from your murmur.ini file.");
return false;
}
#endif
{
QList<QSslCipher> ciphers = MumbleSSL::ciphersFromOpenSSLCipherString(tmpCiphersStr);
if (ciphers.isEmpty()) {
qCritical("MetaParams: Invalid sslCiphers option. Either the cipher string is invalid or none of the ciphers are available: \"%s\"", qPrintable(qsCiphers));
return false;
}
#if !defined(USE_QSSLDIFFIEHELLMANPARAMETERS)
// If the version of Qt we're building against doesn't support
// QSslDiffieHellmanParameters, then we must filter out Diffie-
// Hellman cipher suites in order to guarantee that we do not
// use Qt's default Diffie-Hellman parameters.
{
QList<QSslCipher> filtered;
foreach (QSslCipher c, ciphers) {
if (c.keyExchangeMethod() == QLatin1String("DH")) {
continue;
}
filtered << c;
}
if (ciphers.size() != filtered.size()) {
qWarning("MetaParams: Warning: all cipher suites in sslCiphers using Diffie-Hellman key exchange "
"have been removed. Qt %s does not support custom Diffie-Hellman parameters.",
qVersion());
}
tmpCiphers = filtered;
}
#else
tmpCiphers = ciphers;
#endif
QStringList pref;
foreach (QSslCipher c, tmpCiphers) {
pref << c.name();
}
qWarning("MetaParams: TLS cipher preference is \"%s\"", qPrintable(pref.join(QLatin1String(":"))));
}
qscCert = tmpCert;
qlCA = tmpCA;
qlIntermediates = tmpIntermediates;
qskKey = tmpKey;
qbaDHParams = tmpDHParams;
qsCiphers = tmpCiphersStr;
qlCiphers = tmpCiphers;
qmConfig.insert(QLatin1String("certificate"), qscCert.toPem());
qmConfig.insert(QLatin1String("key"), qskKey.toPem());
qmConfig.insert(QLatin1String("sslCiphers"), qsCiphers);
qmConfig.insert(QLatin1String("sslDHParams"), QString::fromLatin1(qbaDHParams.constData()));
return true;
}
Meta::Meta() {
#ifdef Q_OS_WIN
QOS_VERSION qvVer;
@ -574,6 +756,27 @@ Meta::~Meta() {
#endif
}
bool Meta::reloadSSLSettings() {
// Reload SSL settings.
if (!Meta::mp.loadSSLSettings()) {
return false;
}
// Re-initialize certificates for all
// virtual servers using the Meta server's
// certificate and private key.
foreach (Server *s, qhServers) {
if (s->bUsingMetaCert) {
s->log("Reloading certificates...");
s->initializeCert();
} else {
s->log("Not reloading certificates; server does not use Meta certificate");
}
}
return true;
}
void Meta::getOSInfo() {
qsOS = OSInfo::getOS();
qsOSVersion = OSInfo::getOSDisplayableVersion();

View File

@ -138,6 +138,12 @@ public:
~MetaParams();
void read(QString fname = QString("murmur.ini"));
/// Attempt to load SSL settings from murmur.ini.
/// Returns true if successful. Returns false if
/// the operation failed. On failure, the MetaParams
/// object is left 100% intact.
bool loadSSLSettings();
private:
template <class T>
T typeCheckedFromSettings(const QString &name, const T &variable, QSettings *settings = NULL);
@ -161,6 +167,13 @@ class Meta : public QObject {
Meta();
~Meta();
/// reloadSSLSettings reloads Murmur's MetaParams's
/// SSL settings, and updates the certificate and
/// private key for all virtual servers that use the
/// Meta server's certificate and private key.
bool reloadSSLSettings();
void bootAll();
bool boot(int);
bool banCheck(const QHostAddress &);

View File

@ -228,7 +228,15 @@ void UnixMurmur::handleSigUsr1() {
ssize_t len = ::read(iUsr1Fd[1], &tmp, sizeof(tmp));
Q_UNUSED(len);
qWarning("Received USR1 signal... Ignoring...");
if (meta) {
qWarning("UnixMurmur: Trying to reload SSL settings...");
bool ok = meta->reloadSSLSettings();
if (ok) {
qWarning("UnixMurmur: Done reloading SSL settings.");
} else {
qWarning("UnixMurmur: Failed to reload SSL settings. Server state is intact and fully operational. No configuration changes were made.");
}
}
qsnUsr1->setEnabled(true);
}