|  |  |  | #include "selfdrive/ui/qt/api.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <QDateTime>
 | 
					
						
							|  |  |  | #include <QDebug>
 | 
					
						
							|  |  |  | #include <QFile>
 | 
					
						
							|  |  |  | #include <QJsonDocument>
 | 
					
						
							|  |  |  | #include <QJsonObject>
 | 
					
						
							|  |  |  | #include <QNetworkReply>
 | 
					
						
							|  |  |  | #include <QNetworkRequest>
 | 
					
						
							|  |  |  | #include <QRandomGenerator>
 | 
					
						
							|  |  |  | #include <QString>
 | 
					
						
							|  |  |  | #include <QTimer>
 | 
					
						
							|  |  |  | #include <QWidget>
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include "selfdrive/common/params.h"
 | 
					
						
							|  |  |  | #include "selfdrive/common/util.h"
 | 
					
						
							|  |  |  | #include "selfdrive/hardware/hw.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const std::string private_key_path =
 | 
					
						
							|  |  |  |     Hardware::PC() ? util::getenv_default("HOME", "/.comma/persist/comma/id_rsa", "/persist/comma/id_rsa")
 | 
					
						
							|  |  |  |                    : "/persist/comma/id_rsa";
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | QByteArray CommaApi::rsa_sign(const QByteArray &data) {
 | 
					
						
							|  |  |  |   auto file = QFile(private_key_path.c_str());
 | 
					
						
							|  |  |  |   if (!file.open(QIODevice::ReadOnly)) {
 | 
					
						
							|  |  |  |     qDebug() << "No RSA private key found, please run manager.py or registration.py";
 | 
					
						
							|  |  |  |     return QByteArray();
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  |   auto key = file.readAll();
 | 
					
						
							|  |  |  |   file.close();
 | 
					
						
							|  |  |  |   file.deleteLater();
 | 
					
						
							|  |  |  |   BIO* mem = BIO_new_mem_buf(key.data(), key.size());
 | 
					
						
							|  |  |  |   assert(mem);
 | 
					
						
							|  |  |  |   RSA* rsa_private = PEM_read_bio_RSAPrivateKey(mem, NULL, NULL, NULL);
 | 
					
						
							|  |  |  |   assert(rsa_private);
 | 
					
						
							|  |  |  |   auto sig = QByteArray();
 | 
					
						
							|  |  |  |   sig.resize(RSA_size(rsa_private));
 | 
					
						
							|  |  |  |   unsigned int sig_len;
 | 
					
						
							|  |  |  |   int ret = RSA_sign(NID_sha256, (unsigned char*)data.data(), data.size(), (unsigned char*)sig.data(), &sig_len, rsa_private);
 | 
					
						
							|  |  |  |   assert(ret == 1);
 | 
					
						
							|  |  |  |   assert(sig_len == sig.size());
 | 
					
						
							|  |  |  |   BIO_free(mem);
 | 
					
						
							|  |  |  |   RSA_free(rsa_private);
 | 
					
						
							|  |  |  |   return sig;
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | QString CommaApi::create_jwt(const QVector<QPair<QString, QJsonValue>> &payloads, int expiry) {
 | 
					
						
							|  |  |  |   QString dongle_id = QString::fromStdString(Params().get("DongleId"));
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   QJsonObject header;
 | 
					
						
							|  |  |  |   header.insert("alg", "RS256");
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   QJsonObject payload;
 | 
					
						
							|  |  |  |   payload.insert("identity", dongle_id);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   auto t = QDateTime::currentSecsSinceEpoch();
 | 
					
						
							|  |  |  |   payload.insert("nbf", t);
 | 
					
						
							|  |  |  |   payload.insert("iat", t);
 | 
					
						
							|  |  |  |   payload.insert("exp", t + expiry);
 | 
					
						
							|  |  |  |   for (auto &load : payloads) {
 | 
					
						
							|  |  |  |     payload.insert(load.first, load.second);
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   auto b64_opts = QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals;
 | 
					
						
							|  |  |  |   QString jwt = QJsonDocument(header).toJson(QJsonDocument::Compact).toBase64(b64_opts) + '.' +
 | 
					
						
							|  |  |  |                 QJsonDocument(payload).toJson(QJsonDocument::Compact).toBase64(b64_opts);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   auto hash = QCryptographicHash::hash(jwt.toUtf8(), QCryptographicHash::Sha256);
 | 
					
						
							|  |  |  |   auto sig = rsa_sign(hash);
 | 
					
						
							|  |  |  |   jwt += '.' + sig.toBase64(b64_opts);
 | 
					
						
							|  |  |  |   return jwt;
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | HttpRequest::HttpRequest(QObject *parent, const QString &requestURL, const QString &cache_key, bool create_jwt_) : cache_key(cache_key), create_jwt(create_jwt_), QObject(parent) {
 | 
					
						
							|  |  |  |   networkAccessManager = new QNetworkAccessManager(this);
 | 
					
						
							|  |  |  |   reply = NULL;
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   networkTimer = new QTimer(this);
 | 
					
						
							|  |  |  |   networkTimer->setSingleShot(true);
 | 
					
						
							|  |  |  |   networkTimer->setInterval(20000);
 | 
					
						
							|  |  |  |   connect(networkTimer, &QTimer::timeout, this, &HttpRequest::requestTimeout);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   sendRequest(requestURL);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (!cache_key.isEmpty()) {
 | 
					
						
							|  |  |  |     if (std::string cached_resp = Params().get(cache_key.toStdString()); !cached_resp.empty()) {
 | 
					
						
							|  |  |  |       QTimer::singleShot(0, [=]() { emit receivedResponse(QString::fromStdString(cached_resp)); });
 | 
					
						
							|  |  |  |     }
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void HttpRequest::sendRequest(const QString &requestURL){
 | 
					
						
							|  |  |  |   QString token;
 | 
					
						
							|  |  |  |   if(create_jwt) {
 | 
					
						
							|  |  |  |     token = CommaApi::create_jwt();
 | 
					
						
							|  |  |  |   } else {
 | 
					
						
							|  |  |  |     QString token_json = QString::fromStdString(util::read_file(util::getenv_default("HOME", "/.comma/auth.json", "/.comma/auth.json")));
 | 
					
						
							|  |  |  |     QJsonDocument json_d = QJsonDocument::fromJson(token_json.toUtf8());
 | 
					
						
							|  |  |  |     token = json_d["access_token"].toString();
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   QNetworkRequest request;
 | 
					
						
							|  |  |  |   request.setUrl(QUrl(requestURL));
 | 
					
						
							|  |  |  |   request.setRawHeader(QByteArray("Authorization"), ("JWT " + token).toUtf8());
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   reply = networkAccessManager->get(request);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   networkTimer->start();
 | 
					
						
							|  |  |  |   connect(reply, &QNetworkReply::finished, this, &HttpRequest::requestFinished);
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void HttpRequest::requestTimeout(){
 | 
					
						
							|  |  |  |   reply->abort();
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // This function should always emit something
 | 
					
						
							|  |  |  | void HttpRequest::requestFinished(){
 | 
					
						
							|  |  |  |   if (reply->error() != QNetworkReply::OperationCanceledError) {
 | 
					
						
							|  |  |  |     networkTimer->stop();
 | 
					
						
							|  |  |  |     QString response = reply->readAll();
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (reply->error() == QNetworkReply::NoError) {
 | 
					
						
							|  |  |  |       // save to cache
 | 
					
						
							|  |  |  |       if (!cache_key.isEmpty()) {
 | 
					
						
							|  |  |  |         Params().put(cache_key.toStdString(), response.toStdString());
 | 
					
						
							|  |  |  |       }
 | 
					
						
							|  |  |  |       emit receivedResponse(response);
 | 
					
						
							|  |  |  |     } else {
 | 
					
						
							|  |  |  |       if (!cache_key.isEmpty()) {
 | 
					
						
							|  |  |  |         Params().remove(cache_key.toStdString());
 | 
					
						
							|  |  |  |       }
 | 
					
						
							|  |  |  |       qDebug() << reply->errorString();
 | 
					
						
							|  |  |  |       emit failedResponse(reply->errorString());
 | 
					
						
							|  |  |  |     }
 | 
					
						
							|  |  |  |   } else {
 | 
					
						
							|  |  |  |     emit timeoutResponse("timeout");
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  |   reply->deleteLater();
 | 
					
						
							|  |  |  |   reply = NULL;
 | 
					
						
							|  |  |  | }
 |