GODT-1673: TLS certs generation for gRPC service

Wait for Bridge certificate and use it for gRPC connection

Other: add README file for Bridge-GUI prerequisites

GODT-1673: Configure Client/Server to make use of the bridge cert

Other : comments + todo on known issue

Other: fix go import alias [skip-ci]
This commit is contained in:
Romain LE JEUNE
2022-07-15 17:46:11 +02:00
committed by Jakub
parent 72708d6e2c
commit 8f2e616e07
8 changed files with 131 additions and 105 deletions

View File

@ -30,37 +30,94 @@ using namespace grpc;
namespace
{
/// \todo GODT-1673 Decide how to generate/store/share this certificate.
std::string const cert = R"(-----BEGIN CERTIFICATE-----
MIIC5TCCAc2gAwIBAgIJAMUQK0VGexMsMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV
BAMMCWxvY2FsaG9zdDAeFw0yMjA2MTQxNjUyNTVaFw0yMjA3MTQxNjUyNTVaMBQx
EjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
ggEBAL6T1JQ0jptq512PBLASpCLFB0px7KIzEml0oMUCkVgUF+2cayrvdBXJZnaO
SG+/JPnHDcQ/ecgqkh2Ii6a2x2kWA5KqWiV+bSHp0drXyUGJfM85muLsnrhYwJ83
HHtweoUVebRZvHn66KjaH8nBJ+YVWyYbSUhJezcg6nBSEtkW+I/XUHu4S2C7FUc5
DXPO3yWWZuZ22OZz70DY3uYE/9COuilotuKdj7XgeKDyKIvRXjPFyqGxwnnp6bXC
vWvrQdcxy0wM+vZxew3QtA/Ag9uKJU9owP6noauXw95l49lEVIA5KXVNtdaldVht
MO/QoelLZC7h79PK22zbii3x930CAwEAAaM6MDgwFAYDVR0RBA0wC4IJbG9jYWxo
b3N0MAsGA1UdDwQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDATANBgkqhkiG9w0B
AQsFAAOCAQEAW/9PE8dcAN+0C3K96Xd6Y3qOOtQhRw+WlZXhtiqMtlJfTjvuGKs9
58xuKcTvU5oobxLv+i5+4gpqLjUZZ9FBnYXZIACNVzq4PEXf+YdzcA+y6RS/rqT4
dUjsuYrScAmdXK03Duw3HWYrTp8gsJzIaYGTltUrOn0E4k/TsZb/tZ6z+oH7Fi+p
wdsI6Ut6Zwm3Z7WLn5DDk8KvFjHjZkdsCb82SFSAUVrzWo5EtbLIY/7y3A5rGp9D
t0AVpuGPo5Vn+MW1WA9HT8lhjz0v5wKGMOBi3VYW+Yx8FWHDpacvbZwVM0MjMSAd
M7SXYbNDiLF4LwPLsunoLsW133Ky7s99MA==
-----END CERTIFICATE-----)";
Empty empty; // re-used across client calls.
QString const configFolder = "protonmail/bridge";
QString const certFile = "cert.pem";
int const maxConnectionTimeSecs = 60; ///< Amount of time after which we consider connection attemps to the server have failed.
int const maxCertificateWaitMsecs = 60 * 1000; ///< Ammount of time we wait for he server to generate the certificate.
}
//****************************************************************************************************************************************************
/// \return user configuration directory used by bridge (based on Golang OS/File::UserConfigDir).
//****************************************************************************************************************************************************
static const QString _userConfigDir(){
QString dir;
#ifdef Q_OS_WIN
dir = qgetenv ("AppData");
if (dir.isEmpty())
throw Exception("%AppData% is not defined.");
#elif defined(Q_OS_IOS) || defined(Q_OS_DARWIN)
dir = qgetenv ("HOME");
if (dir.isEmpty())
throw Exception("$HOME is not defined.");
dir += "/Library/Application Support";
#else
dir = qgetenv ("XDG_CONFIG_HOME");
if (dir.isEmpty())
dir = qgetenv ("HOME");
if (dir.isEmpty())
throw Exception("neither $XDG_CONFIG_HOME nor $HOME are defined");
dir += "/.config";
#endif
QString folder = dir + "/" + configFolder;
QDir().mkpath(folder);
return folder;
}
//****************************************************************************************************************************************************
/// \brief wait for certificate generation by Bridge
/// \return server certificate generated by Bridge
//****************************************************************************************************************************************************
std::string GRPCClient::getServerCertificate()
{
const QString filename = _userConfigDir() + "/" + certFile;
QFile file(filename);
// TODO : the certificate can exist but still be invalid.
// If the certificate is close to its limit, the bridge will generate a new one.
// If we read the certificate before the bridge rewrites it the certificate will be invalid.
if (!file.exists())
{
// wait for file creation
QFileSystemWatcher watcher(this);
if (!watcher.addPath(_userConfigDir()))
throw Exception("Failed to watch User Config Directory");
connect(&watcher, &QFileSystemWatcher::directoryChanged, this, &GRPCClient::configFolderChanged);
// set up an eventLoop to wait for the certIsReady signal or timeout.
QTimer timer;
timer.setSingleShot(true);
QEventLoop loop;
connect(this, &GRPCClient::certIsReady, &loop, &QEventLoop::quit);
connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);
timer.start(maxCertificateWaitMsecs);
loop.exec();
// timeout case.
if(!timer.isActive())
throw Exception("Server failed to generate certificate on time");
//else certIsReadySignal.
}
if (!file.open(QFile::ReadOnly))
throw Exception("Failed to read the server certificate");
QByteArray qbaCert = file.readAll();
std::string cert(qbaCert.constData(), qbaCert.length());
file.close();
return cert;
}
//****************************************************************************************************************************************************
/// \brief Action on UserConfig directory changes, looking for the certificate creation
//****************************************************************************************************************************************************
void GRPCClient::configFolderChanged()
{
QFile cert(_userConfigDir() + "/" + certFile);
if (cert.exists())
emit certIsReady();
}
//****************************************************************************************************************************************************
/// \param[out] outError If the function returns false, this variable contains a description of the error.
@ -71,9 +128,9 @@ bool GRPCClient::connectToServer(QString &outError)
try
{
SslCredentialsOptions opts;
opts.pem_root_certs += cert;
opts.pem_root_certs += this->getServerCertificate();
channel_ = CreateChannel("localhost:9292", grpc::SslCredentials(opts));
channel_ = CreateChannel("127.0.0.1:9292", grpc::SslCredentials(opts));
if (!channel_)
throw Exception("Channel creation failed.");

View File

@ -171,6 +171,7 @@ signals:
void changeKeychainFinished();
void hasNoKeychain();
void rebuildKeychain();
void certIsReady();
signals: // mail releated events
void noActiveKeyForRecipient(QString const &email); // _ func(email string) `signal:noActiveKeyForRecipient`
@ -182,7 +183,8 @@ public:
grpc::Status startEventStream(); ///< Retrieve and signal the events in the event stream.
grpc::Status stopEventStream(); ///< Stop the event stream.
private slots:
void configFolderChanged();
private:
grpc::Status simpleMethod(SimpleMethod method); ///< perform a gRPC call to a bool setter.
@ -196,6 +198,7 @@ private:
grpc::Status getURL(StringGetter getter, QUrl& outValue); ///< Perform a gRPC call to a string getter, with resulted converted to QUrl.
grpc::Status methodWithStringParam(StringParamMethod method, QString const& str); ///< Perfom a gRPC call that takes a string as a parameter and returns an Empty.
std::string getServerCertificate(); ///< Wait until server certificates is generated and retrieve it.
void processAppEvent(grpc::AppEvent const &event); ///< Process an 'App' event.
void processLoginEvent(grpc::LoginEvent const &event); ///< Process a 'Login' event.
void processUpdateEvent(grpc::UpdateEvent const &event); ///< Process an 'Update' event.

View File

@ -0,0 +1,28 @@
## Prerequisite
```` bash
sudo apt install build-essential
sudo apt install tar curl zip unzip
sudo apt install linux-headers-$(uname -r)
sudo apt install mesa-common-dev libglu1-mesa-dev
````
## Define Qt5DIR
```` bash
export QT5DIR=/opt/Qt/5.13.0/gcc_64
````
## install vcpkg and define VCPKG_ROOT
```` bash
git clone https://github.com/Microsoft/vcpkg.git
./vcpkg/bootstrap-vcpkg.sh
export VCPKG_ROOT=$PWD/vcpkg
````
## install grpc & protobuf
```` bash
./vcpkg install grpc
````