Torc  0.1
torcwebsocketthread.cpp
Go to the documentation of this file.
1 /* Class TorcWebSocketThread
2 *
3 * This file is part of the Torc project.
4 *
5 * Copyright (C) Mark Kendall 2018
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
20 * USA.
21 */
22 
23 // Qt
24 #include <QFile>
25 #include <QMutex>
26 #include <QSslKey>
27 #include <QSslCipher>
28 #include <QSslConfiguration>
29 
30 // Torc
31 #include "torclogging.h"
32 #include "torclocaldefs.h"
33 #include "torcdirectories.h"
34 #include "torcwebsocketthread.h"
35 
36 // SSL
37 #include <openssl/pem.h>
38 #include <openssl/x509.h>
39 #include <openssl/bn.h>
40 
41 // STL - for chmod
42 #include <sys/stat.h>
43 
62 TorcWebSocketThread::TorcWebSocketThread(qintptr SocketDescriptor, bool Secure)
63  : TorcQThread(QStringLiteral("SocketIn")),
64  m_webSocket(nullptr),
65  m_secure(Secure),
66  m_socketDescriptor(SocketDescriptor),
67  m_address(QHostAddress::Null),
68  m_port(0),
69  m_protocol(TorcWebSocketReader::SubProtocolNone)
70 {
71 }
72 
73 TorcWebSocketThread::TorcWebSocketThread(const QHostAddress &Address, quint16 Port, bool Secure, TorcWebSocketReader::WSSubProtocol Protocol)
74  : TorcQThread(QStringLiteral("SocketOut")),
75  m_webSocket(nullptr),
76  m_secure(Secure),
77  m_socketDescriptor(0),
78  m_address(Address),
79  m_port(Port),
80  m_protocol(Protocol)
81 {
82 }
83 
84 int SSLCallback(int, int, BN_GENCB*)
85 {
86  static int count = 0;
87  if (!(++count % 20))
88  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("Key generation..."));
89  return 1;
90 }
91 
92 bool TorcWebSocketThread::CreateCerts(const QString &CertFile, const QString &KeyFile)
93 {
94  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("Generating RSA key"));
95 
96  RSA *rsa = RSA_new();
97 
98 #if OPENSSL_VERSION_NUMBER < 0x10100000L
99  BN_GENCB cb;
100  BN_GENCB_set(&cb, &SSLCallback, nullptr);
101 #else
102  BN_GENCB *cb = BN_GENCB_new();
103  BN_GENCB_set(cb, &SSLCallback, nullptr);
104 #endif
105  BIGNUM *e;
106  e = BN_new();
107  BN_set_word(e, RSA_F4);
108 #if OPENSSL_VERSION_NUMBER < 0x10100000L
109  RSA_generate_key_ex(rsa, 4096, e, &cb);
110 #else
111  RSA_generate_key_ex(rsa, 4096, e, cb);
112 #endif
113  BN_free(e);
114 #if OPENSSL_VERSION_NUMBER >= 0x10100000L
115  BN_GENCB_free(cb);
116 #endif
117  if (nullptr == rsa)
118  {
119  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Failed to generate RSA key"));
120  return false;
121  }
122 
123  EVP_PKEY *privatekey = EVP_PKEY_new();
124  if(!EVP_PKEY_assign_RSA(privatekey, rsa))
125  {
126  EVP_PKEY_free(privatekey);
127  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Failed to create RSA key"));
128  return false;
129  }
130 
131  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("Generating X509 certificate"));
132  X509 *x509 = X509_new();
133  // we need a unique serial number (or more strictly a unique combination of CA and serial number)
134  QString timenow = QString::number(QDateTime::currentMSecsSinceEpoch());
135  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("New cert serial number: %1").arg(timenow));
136  BIGNUM *bn = BN_new();
137  if (BN_dec2bn(&bn, timenow.toLatin1().constData()) != timenow.size())
138  LOG(VB_GENERAL, LOG_WARNING, QStringLiteral("Conversion error"));
139  ASN1_INTEGER *sno = ASN1_INTEGER_new();
140  sno = BN_to_ASN1_INTEGER(bn, sno);
141  X509_set_serialNumber(x509, sno);
142  BN_free(bn);
143  ASN1_INTEGER_free(sno);
144  X509_gmtime_adj(X509_get_notBefore(x509), 0);
145  X509_gmtime_adj(X509_get_notAfter(x509), 315360000L); // valid for 10 years!
146  X509_set_pubkey(x509, privatekey);
147  X509_NAME *name = X509_get_subject_name(x509);
148  X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, (unsigned char *)"GB", -1, -1, 0);
149  X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC, (unsigned char *)"SelfSignedCo", -1, -1, 0);
150  X509_NAME_add_entry_by_txt(name, "OU", MBSTRING_ASC, (unsigned char *)"SelfSignedCo", -1, -1, 0);
151  X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (unsigned char *)"localhost", -1, -1, 0);
152  X509_set_issuer_name(x509, name);
153  if(!X509_sign(x509, privatekey, EVP_sha256()))
154  {
155  X509_free(x509);
156  EVP_PKEY_free(privatekey);
157  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Failed to sign certificate"));
158  return false;
159  }
160 
161  FILE* certfile = fopen(CertFile.toLocal8Bit().constData(), "wb");
162  if (!certfile)
163  {
164  X509_free(x509);
165  EVP_PKEY_free(privatekey);
166  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Failed to open '%1' for writing").arg(CertFile));
167  return false;
168  }
169  bool success = PEM_write_X509(certfile, x509);
170  fclose(certfile);
171  if (!success)
172  {
173  X509_free(x509);
174  EVP_PKEY_free(privatekey);
175  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Failed to write to '%1'").arg(CertFile));
176  return false;
177  }
178 
179  FILE* keyfile = fopen(KeyFile.toLocal8Bit().constData(), "wb");
180  if (!keyfile)
181  {
182  X509_free(x509);
183  EVP_PKEY_free(privatekey);
184  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Failed to open '%1' for writing").arg(KeyFile));
185  return false;
186  }
187 
188  success = PEM_write_PrivateKey(keyfile, privatekey, nullptr, nullptr, 0, nullptr, nullptr);
189  fclose(keyfile);
190  X509_free(x509);
191  EVP_PKEY_free(privatekey);
192 
193  if (!success)
194  {
195  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Failed to write to '%1'").arg(KeyFile));
196  return false;
197  }
198 
199  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("Cert file saved as '%1'").arg(CertFile));
200  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("Key file saved as '%1'").arg(KeyFile));
201 
202  if (chmod(CertFile.toLocal8Bit().constData(), S_IRUSR | S_IWUSR) != 0)
203  LOG(VB_GENERAL, LOG_WARNING, QStringLiteral("Failed to set permissions for '%1' - this is not fatal but may present a security risk").arg(CertFile));
204  if (chmod(KeyFile.toLocal8Bit().constData(), S_IRUSR | S_IWUSR) != 0)
205  LOG(VB_GENERAL, LOG_WARNING, QStringLiteral("Failed to set permissions for '%1' - this is not fatal but may present a security risk").arg(KeyFile));
206  return true;
207 }
208 
209 void TorcWebSocketThread::SetupSSL(void)
210 {
211  static bool SSLDefaultsSet = false;
212  static QMutex SSLDefaultsLock(QMutex::Recursive);
213 
214  // block any incoming sockets until cert creation is complete - it can take a few seconds
215  QMutexLocker locker(&SSLDefaultsLock);
216  if (SSLDefaultsSet)
217  return;
218 
219  SSLDefaultsSet = true;
220  QSslConfiguration config;
221 
222 #if (QT_VERSION >= QT_VERSION_CHECK(5, 5, 0))
223  config.setProtocol(QSsl::TlsV1_2OrLater);
224  config.setCiphers(QSslConfiguration::supportedCiphers());
225 #else
226  config.setProtocol(QSsl::TlsV1_2);
227  config.setCiphers(QSslSocket::supportedCiphers());
228 #endif
229 
230  QString certlocation = GetTorcConfigDir() + "/" + TORC_TORC + ".cert";
231  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("SSL: looking for cert in '%1'").arg(certlocation));
232  QString keylocation = GetTorcConfigDir() + "/" + TORC_TORC + ".key";
233  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("SSL: looking for key in '%1'").arg(keylocation));
234 
235  bool create = false;
236  if (!QFile::exists(certlocation))
237  {
238  create = true;
239  LOG(VB_GENERAL, LOG_WARNING, QStringLiteral("Failed to find cert"));
240  }
241  if (!QFile::exists(keylocation))
242  {
243  create = true;
244  LOG(VB_GENERAL, LOG_WARNING, QStringLiteral("Failed to find key"));
245  }
246 
247  if (create && !CreateCerts(certlocation, keylocation))
248  {
249  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("SSL key/cert creation failed - server connections will fail"));
250  return;
251  }
252 
253  QFile certFile(certlocation);
254  certFile.open(QIODevice::ReadOnly);
255  if (certFile.isOpen())
256  {
257  QSslCertificate certificate(&certFile, QSsl::Pem);
258  if (!certificate.isNull())
259  {
260  config.setLocalCertificate(certificate);
261  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("SSL: cert loaded"));
262  }
263  else
264  {
265  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("SSL: error loading/reading cert file"));
266  }
267  certFile.close();
268  }
269  else
270  {
271  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("SSL: failed to open cert file for reading"));
272  }
273 
274  QFile keyFile(keylocation);
275  keyFile.open(QIODevice::ReadOnly);
276  if (keyFile.isOpen())
277  {
278  QSslKey key(&keyFile, QSsl::Rsa, QSsl::Pem);
279  if (!key.isNull())
280  {
281  config.setPrivateKey(key);
282  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("SSL: key loaded"));
283  }
284  else
285  {
286  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("SSL: error loading/reading key file"));
287  }
288  keyFile.close();
289  }
290  else
291  {
292  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("SSL: failed to open key file for reading"));
293  }
294  QSslConfiguration::setDefaultConfiguration(config);
295 }
296 
298 {
299  // one off SSL default configuration when needed
300  if (m_secure && m_socketDescriptor)
301  SetupSSL();
302 
303  if (m_socketDescriptor)
304  m_webSocket = new TorcWebSocket(this, m_socketDescriptor, m_secure);
305  else
306  m_webSocket = new TorcWebSocket(this, m_address, m_port, m_secure, m_protocol);
307 
310  connect(m_webSocket, &TorcWebSocket::Disconnected, this, &TorcWebSocketThread::quit);
311  // the websocket is created in its own thread so these signals will be delivered into the correct thread.
314  m_webSocket->Start();
315 }
316 
318 {
319  if (m_webSocket)
320  delete m_webSocket;
321  m_webSocket = nullptr;
322 }
323 
325 {
326  return m_secure;
327 }
328 
330 {
331  emit RemoteRequestSignal(Request);
332 }
333 
335 {
336  emit CancelRequestSignal(Request);
337 }
void Finish(void) override
void ConnectionUpgraded(void)
void ConnectionUpgraded(void)
void Start(void) override
int SSLCallback(int, int, BN_GENCB *)
TorcWebSocketThread(qintptr SocketDescriptor, bool Secure)
void CancelRequest(TorcRPCRequest *Request)
Cancel a Remote Procedure Call.
A class encapsulating a Remote Procedure Call.
TorcWebSocket(TorcWebSocketThread *Parent, qintptr SocketDescriptor, bool Secure)
void Disconnected(void)
void ConnectionEstablished(void)
#define TORC_TORC
Definition: torclocaldefs.h:8
void CancelRequestSignal(TorcRPCRequest *Request)
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: torclogging.h:20
void CancelRequest(TorcRPCRequest *Request)
A Torc specific wrapper around QThread.
Definition: torcqthread.h:7
void RemoteRequest(TorcRPCRequest *Request)
Initiate a Remote Procedure Call.
QString GetTorcConfigDir(void)
Return the path to the application configuration directory.
void Start(void)
Initialise the websocket once its parent thread is ready.
void ConnectionEstablished(void)
void RemoteRequest(TorcRPCRequest *Request)
void RemoteRequestSignal(TorcRPCRequest *Request)