Other: gRPC TLS server is generated for every session.

This commit is contained in:
Xavier Michelon
2022-10-02 13:05:35 +02:00
parent 20c802a1e5
commit ca7d7ab675
5 changed files with 76 additions and 70 deletions

View File

@ -37,7 +37,6 @@ Empty empty; // re-used across client calls.
int const maxConnectionTimeSecs = 60; ///< Amount of time after which we consider connection attempts to the server have failed.
int const maxCertificateWaitMsecs = 60 * 1000; ///< Amount of time we wait for he server to generate the certificate.
}
@ -91,60 +90,6 @@ GRPCConfig GRPCClient::waitAndRetrieveServiceConfig(qint64 timeoutMs)
}
//****************************************************************************************************************************************************
/// \brief wait for certificate generation by Bridge
/// \return server certificate generated by Bridge
//****************************************************************************************************************************************************
std::string GRPCClient::getServerCertificate()
{
QString const certPath = serverCertificatePath();
QString const certFolder = QFileInfo(certPath).absolutePath();
QFile file(certPath);
// 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(certFolder))
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()
{
if (QFileInfo::exists(serverCertificatePath()))
emit certIsReady();
}
//****************************************************************************************************************************************************
/// \param[in] log The log
//****************************************************************************************************************************************************
@ -163,7 +108,7 @@ bool GRPCClient::connectToServer(GRPCConfig const &config, QString &outError)
try
{
SslCredentialsOptions opts;
opts.pem_root_certs += this->getServerCertificate();
opts.pem_root_certs += config.cert.toStdString();
QString const address = QString("127.0.0.1:%1").arg(config.port);
channel_ = CreateChannel(address.toStdString(), grpc::SslCredentials(opts));

View File

@ -204,9 +204,6 @@ public:
grpc::Status runEventStreamReader(); ///< Retrieve and signal the events in the event stream.
grpc::Status stopEventStreamReader(); ///< Stop the event stream.
private slots:
void configFolderChanged();
private:
void logTrace(QString const &message); ///< Log an event.
void logError(QString const &message); ///< Log an event.
@ -223,8 +220,6 @@ private:
grpc::Status methodWithStringParam(StringParamMethod method, QString const &str); ///< Perform a gRPC call that takes a string as a parameter and returns an Empty.
SPUser parseGRPCUser(grpc::User const &grpcUser); ///< Parse a gRPC user struct and return a User.
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

@ -31,6 +31,7 @@ import (
"github.com/ProtonMail/proton-bridge/v2/internal/bridge"
"github.com/ProtonMail/proton-bridge/v2/internal/config/settings"
"github.com/ProtonMail/proton-bridge/v2/internal/config/tls"
"github.com/ProtonMail/proton-bridge/v2/internal/events"
"github.com/ProtonMail/proton-bridge/v2/internal/frontend/types"
"github.com/ProtonMail/proton-bridge/v2/internal/locations"
@ -76,6 +77,7 @@ type Service struct { // nolint:structcheck
initializationDone sync.Once
firstTimeAutostart sync.Once
locations *locations.Locations
pemCert string
}
// NewService returns a new instance of the service.
@ -108,23 +110,21 @@ func NewService(
// set to 1
s.initializing.Add(1)
config, err := bridge.GetTLSConfig()
config.ClientAuth = cryptotls.NoClientCert // skip client auth if the certificate allow it.
tlsConfig, pemCert, err := s.generateTLSConfig()
if err != nil {
s.log.WithError(err).Error("could not get TLS config")
panic(err)
s.log.WithError(err).Panic("could not generate gRPC TLS config")
}
s.initAutostart()
s.pemCert = string(pemCert)
s.grpcServer = grpc.NewServer(grpc.Creds(credentials.NewTLS(config)))
s.initAutostart()
s.grpcServer = grpc.NewServer(grpc.Creds(credentials.NewTLS(tlsConfig)))
RegisterBridgeServer(s.grpcServer, &s)
s.listener, err = net.Listen("tcp", "127.0.0.1:0") // Port 0 means that the port is randomly picked by the system.
if err != nil {
s.log.WithError(err).Panic("could not create listener")
s.log.WithError(err).Panic("could not create gRPC listener")
}
if err := s.saveGRPCServerConfigFile(); err != nil {
@ -404,13 +404,32 @@ func (s *Service) installUpdate() {
_ = s.SendEvent(NewUpdateSilentRestartNeededEvent())
}
func (s *Service) generateTLSConfig() (tlsConfig *cryptotls.Config, pemCert []byte, err error) {
pemCert, pemKey, err := tls.NewPEMKeyPair()
if err != nil {
return nil, nil, errors.New("Could not get TLS config")
}
tlsConfig, err = tls.GetConfigFromPEMKeyPair(pemCert, pemKey)
if err != nil {
return nil, nil, errors.New("Could not get TLS config")
}
tlsConfig.ClientAuth = cryptotls.NoClientCert // skip client auth if the certificate allow it.
return tlsConfig, pemCert, nil
}
func (s *Service) saveGRPCServerConfigFile() error {
address, ok := s.listener.Addr().(*net.TCPAddr)
if !ok {
return fmt.Errorf("could not retrieve gRPC service listener address")
}
sc := config{Port: address.Port}
sc := config{
Port: address.Port,
Cert: s.pemCert,
}
settingsPath, err := s.locations.ProvideSettingsPath()
if err != nil {