Skip to content

Commit

Permalink
Murmur.ice: add updateCertificate() method.
Browse files Browse the repository at this point in the history
This allows live certificate/private key reloading
for individual virtual servers.

This makes it possible to integrate Murmur with
an external Let's Encrypt script, without having
to fully restart the virtual server in the process.
  • Loading branch information
mkrautz committed Jun 6, 2016
1 parent 18e8014 commit ff234a2
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 1 deletion.
23 changes: 23 additions & 0 deletions src/murmur/Murmur.ice
Expand Up @@ -271,6 +271,8 @@ module Murmur
exception NestingLimitException extends MurmurException {};
/** This is thrown when you ask the server to disclose something that should be secret. */
exception WriteOnlyException extends MurmurException {};
/** This is thrown when invalid input data was specified. */
exception InvalidInputDataException extends MurmurException {};

/** Callback interface for servers. You can supply an implementation of this to receive notification
* messages from the server.
Expand Down Expand Up @@ -753,6 +755,27 @@ module Murmur
* @return Uptime of the virtual server in seconds
*/
idempotent int getUptime() throws ServerBootedException, InvalidSecretException;

/**
* Update the server's certificate information.
*
* Reconfigure the running server's TLS socket with the given
* certificate and private key.
*
* The certificate and and private key must be PEM formatted.
*
* New clients will see the new certificate.
* Existing clients will continue to see the certificate the server
* was using when they connected to it.
*
* This method throws InvalidInputDataException if any of the
* following errors happen:
* - Unable to decode the PEM certificate and/or private key.
* - Unable to decrypt the private key with the given passphrase.
* - The certificate and/or private key do not contain RSA keys.
* - The certificate is not usable with the given private key.
*/
idempotent void updateCertificate(string certificate, string privateKey, string passphrase) throws ServerBootedException, InvalidSecretException, InvalidInputDataException;
};

/** Callback interface for Meta. You can supply an implementation of this to receive notifications
Expand Down
6 changes: 6 additions & 0 deletions src/murmur/MurmurI.h
Expand Up @@ -187,6 +187,12 @@ namespace Murmur {
virtual void getUptime_async(const ::Murmur::AMD_Server_getUptimePtr&,
const Ice::Current&);

virtual void updateCertificate_async(const ::Murmur::AMD_Server_updateCertificatePtr&,
const std::string&,
const std::string&,
const std::string&,
const Ice::Current&);

virtual void ice_ping(const Ice::Current&) const;
};

Expand Down
66 changes: 66 additions & 0 deletions src/murmur/MurmurIce.cpp
Expand Up @@ -1534,6 +1534,72 @@ static void impl_Server_getUptime(const ::Murmur::AMD_Server_getUptimePtr cb, in
cb->ice_response(static_cast<int>(server->tUptime.elapsed()/1000000LL));
}

static void impl_Server_updateCertificate(const ::Murmur::AMD_Server_updateCertificatePtr cb, int server_id, const ::std::string& certificate, const ::std::string& privateKey, const ::std::string& passphrase) {
NEED_SERVER;

QByteArray certPem(certificate.c_str());
QByteArray privateKeyPem(privateKey.c_str());
QByteArray passphraseBytes(passphrase.c_str());

// Verify that we can load the certificate.
QSslCertificate cert(certPem);
if (cert.isNull()) {
ERR_clear_error();
cb->ice_exception(InvalidInputDataException());
return;
}

// Verify that we can load the private key.
QSslKey privKey(privateKeyPem, QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey, passphraseBytes);
if (privKey.isNull()) {
ERR_clear_error();
cb->ice_exception(InvalidInputDataException());
return;
}

// Ensure that the private key is usable with the given
// certificate.
//
// Right now, we only support RSA keys in Murmur.
//
// To determine if the private key matches the certificate,
// we acquire the public key from the certificate and check
// that the modulus (n) and the public exponent (e) match
// those of the private key.
QSslKey pubKey = cert.publicKey();

if (privKey.algorithm() != QSsl::Rsa || pubKey.algorithm() != QSsl::Rsa) {
ERR_clear_error();
cb->ice_exception(InvalidInputDataException());
return;
}

RSA *privRSA = reinterpret_cast<RSA *>(privKey.handle());
RSA *pubRSA = reinterpret_cast<RSA *>(pubKey.handle());

if (BN_cmp(pubRSA->n, privRSA->n) != 0) {
ERR_clear_error();
cb->ice_exception(InvalidInputDataException());
return;
}

if (BN_cmp(pubRSA->e, privRSA->e) != 0) {
ERR_clear_error();
cb->ice_exception(InvalidInputDataException());
return;
}

// All our sanity checks passed.
// The certificate and private key are usable, so
// update the server to use them.
server->setConf("certificate", u8(certificate));
server->setConf("key", u8(privateKey));
server->setConf("passphrase", u8(passphrase));
server->initializeCert();

cb->ice_response();
}

static void impl_Server_addUserToGroup(const ::Murmur::AMD_Server_addUserToGroupPtr cb, int server_id, ::Ice::Int channelid, ::Ice::Int session, const ::std::string& group) {
NEED_SERVER;
NEED_PLAYER;
Expand Down

0 comments on commit ff234a2

Please sign in to comment.