Advertisement
kotoroshinoto

Untitled

Oct 22nd, 2014
271
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. /* Copyright 2013 MultiMC Contributors
  2.  *
  3.  * Licensed under the Apache License, Version 2.0 (the "License");
  4.  * you may not use this file except in compliance with the License.
  5.  * You may obtain a copy of the License at
  6.  *
  7.  *     http://www.apache.org/licenses/LICENSE-2.0
  8.  *
  9.  * Unless required by applicable law or agreed to in writing, software
  10.  * distributed under the License is distributed on an "AS IS" BASIS,
  11.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12.  * See the License for the specific language governing permissions and
  13.  * limitations under the License.
  14.  */
  15.  
  16. #include <logic/auth/YggdrasilTask.h>
  17.  
  18. #include <QObject>
  19. #include <QString>
  20. #include <QJsonObject>
  21. #include <QJsonDocument>
  22. #include <QNetworkReply>
  23. #include <QByteArray>
  24.  
  25. #include <MultiMC.h>
  26. #include <logic/auth/MojangAccount.h>
  27. #include <logic/net/URLConstants.h>
  28. #include <QtCore/qdebug.h>
  29. #include <QtNetwork/qsslkey.h>
  30.  
  31. YggdrasilTask::YggdrasilTask(MojangAccount *account, QObject *parent)
  32.     : Task(parent), m_account(account)
  33. {
  34.     changeState(STATE_CREATED);
  35. }
  36. QSslConfiguration FilterOutSha1RSACertificates(){
  37.     QSslConfiguration filtered;
  38.     QList<QSslCertificate> certs(QSslSocket::systemCaCertificates());
  39.     qDebug()<< QString("Checking Starfield Certificates for sha1-rsa");
  40.     qDebug()<< certs.size();
  41.     for(QList<QSslCertificate>::iterator it=certs.begin();it != certs.end();it++) {
  42.  
  43.         QStringList info_O=it->issuerInfo(QByteArray("O"));
  44.         if (info_O.size() > 0 && info_O[0] == "Starfield Technologies, Inc.") {
  45.             if (it->toText().indexOf("Signature Algorithm: sha1WithRSAEncryption") != -1) {
  46.                 qDebug()<<it->toText();
  47.             }
  48.         }
  49.     }
  50.     qDebug()<< QString("Removing Starfield Certificates using sha1-rsa");
  51.     bool eraseit;
  52.     for(QList<QSslCertificate>::iterator it=certs.begin();it != certs.end();) {
  53.         eraseit=false;
  54.         QStringList info_O=it->issuerInfo(QByteArray("O"));
  55.         if (info_O.size() > 0 && info_O[0] == "Starfield Technologies, Inc.") {
  56.             if(it->toText().indexOf("Signature Algorithm: sha1WithRSAEncryption") != -1){
  57.                 eraseit=true;
  58. //                QList<QByteArray> infoAttrib=it->subjectInfoAttributes();
  59. //                for(QList<QByteArray>::iterator it2=infoAttrib.begin();it2 != infoAttrib.end();it2++){
  60. //                    qDebug()<<(*it2)<<(it->issuerInfo(*it2));
  61. //
  62.             }
  63.         }
  64.         if(eraseit){it=certs.erase(it);}else{it++;}
  65.     }
  66.     qDebug()<< QString("Checking if Starfield Certificates using sha1-rsa are still in the list");
  67.     for(QList<QSslCertificate>::iterator it=certs.begin();it != certs.end();it++) {
  68.  
  69.         QStringList info_O=it->issuerInfo(QByteArray("O"));
  70.         if (info_O.size() > 0 && info_O[0] == "Starfield Technologies, Inc.") {
  71.             if (it->toText().indexOf("Signature Algorithm: sha1WithRSAEncryption") != -1) {
  72.                 qDebug()<<it->toText();
  73.             }
  74.         }
  75.     }
  76.     filtered.setCaCertificates(certs);
  77.     qDebug()<< QString("returning configuration");
  78.     return filtered;
  79. }
  80. void YggdrasilTask::executeTask()
  81. {
  82.     changeState(STATE_SENDING_REQUEST);
  83.  
  84.     // Get the content of the request we're going to send to the server.
  85.     QJsonDocument doc(getRequestContent());
  86.  
  87.     auto worker = MMC->qnam();
  88.     QUrl reqUrl("https://" + URLConstants::AUTH_BASE + getEndpoint());
  89.     QNetworkRequest netRequest(reqUrl);
  90.  
  91.     //FIXME avoiding use of the SHA1-RSA certificates that causes issues on some linux machines
  92.  
  93.     netRequest.setSslConfiguration(FilterOutSha1RSACertificates());
  94.  
  95.     netRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
  96.  
  97.     QByteArray requestData = doc.toJson();
  98.     m_netReply = worker->post(netRequest, requestData);
  99.     connect(m_netReply, &QNetworkReply::finished, this, &YggdrasilTask::processReply);
  100.     connect(m_netReply, &QNetworkReply::uploadProgress, this, &YggdrasilTask::refreshTimers);
  101.     connect(m_netReply, &QNetworkReply::downloadProgress, this, &YggdrasilTask::refreshTimers);
  102.     connect(m_netReply, &QNetworkReply::sslErrors, this, &YggdrasilTask::sslErrors);
  103.     timeout_keeper.setSingleShot(true);
  104.     timeout_keeper.start(timeout_max);
  105.     counter.setSingleShot(false);
  106.     counter.start(time_step);
  107.     progress(0, timeout_max);
  108.     connect(&timeout_keeper, &QTimer::timeout, this, &YggdrasilTask::abortByTimeout);
  109.     connect(&counter, &QTimer::timeout, this, &YggdrasilTask::heartbeat);
  110. }
  111.  
  112. void YggdrasilTask::refreshTimers(qint64, qint64)
  113. {
  114.     timeout_keeper.stop();
  115.     timeout_keeper.start(timeout_max);
  116.     progress(count = 0, timeout_max);
  117. }
  118. void YggdrasilTask::heartbeat()
  119. {
  120.     count += time_step;
  121.     progress(count, timeout_max);
  122. }
  123.  
  124. void YggdrasilTask::abort()
  125. {
  126.     progress(timeout_max, timeout_max);
  127.     // TODO: actually use this in a meaningful way
  128.     m_aborted = YggdrasilTask::BY_USER;
  129.     m_netReply->abort();
  130. }
  131.  
  132. void YggdrasilTask::abortByTimeout()
  133. {
  134.     progress(timeout_max, timeout_max);
  135.     // TODO: actually use this in a meaningful way
  136.     m_aborted = YggdrasilTask::BY_TIMEOUT;
  137.     m_netReply->abort();
  138. }
  139.  
  140. void YggdrasilTask::sslErrors(QList<QSslError> errors)
  141. {
  142.     int i = 1;
  143.     for (auto error : errors)
  144.     {
  145.         QLOG_ERROR() << "LOGIN SSL Error #" << i << " : " << error.errorString();
  146.         auto cert = error.certificate();
  147.         QLOG_ERROR() << "Certificate in question:\n" << cert.toText();
  148.         i++;
  149.     }
  150. }
  151.  
  152. void YggdrasilTask::processReply()
  153. {
  154.     changeState(STATE_PROCESSING_RESPONSE);
  155.  
  156.     switch (m_netReply->error())
  157.     {
  158.     case QNetworkReply::NoError:
  159.         break;
  160.     case QNetworkReply::TimeoutError:
  161.         changeState(STATE_FAILED_SOFT, tr("Authentication operation timed out."));
  162.         return;
  163.     case QNetworkReply::OperationCanceledError:
  164.         changeState(STATE_FAILED_SOFT, tr("Authentication operation cancelled."));
  165.         return;
  166.     case QNetworkReply::SslHandshakeFailedError:
  167.         changeState(
  168.             STATE_FAILED_SOFT,
  169.             tr("<b>SSL Handshake failed.</b><br/>There might be a few causes for it:<br/>"
  170.                "<ul>"
  171.                "<li>You use Windows XP and need to <a "
  172.                "href=\"http://www.microsoft.com/en-us/download/details.aspx?id=38918\">update "
  173.                "your root certificates</a></li>"
  174.                "<li>Some device on your network is interfering with SSL traffic. In that case, "
  175.                "you have bigger worries than Minecraft not starting.</li>"
  176.                "<li>Possibly something else. Check the MultiMC log file for details</li>"
  177.                "</ul>"));
  178.         return;
  179.     // used for invalid credentials and similar errors. Fall through.
  180.     case QNetworkReply::ContentOperationNotPermittedError:
  181.         break;
  182.     default:
  183.         changeState(STATE_FAILED_SOFT,
  184.                     tr("Authentication operation failed due to a network error: %1 (%2)")
  185.                         .arg(m_netReply->errorString()).arg(m_netReply->error()));
  186.         return;
  187.     }
  188.  
  189.     // Try to parse the response regardless of the response code.
  190.     // Sometimes the auth server will give more information and an error code.
  191.     QJsonParseError jsonError;
  192.     QByteArray replyData = m_netReply->readAll();
  193.     QJsonDocument doc = QJsonDocument::fromJson(replyData, &jsonError);
  194.     // Check the response code.
  195.     int responseCode = m_netReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
  196.  
  197.     if (responseCode == 200)
  198.     {
  199.         // If the response code was 200, then there shouldn't be an error. Make sure
  200.         // anyways.
  201.         // Also, sometimes an empty reply indicates success. If there was no data received,
  202.         // pass an empty json object to the processResponse function.
  203.         if (jsonError.error == QJsonParseError::NoError || replyData.size() == 0)
  204.         {
  205.             processResponse(replyData.size() > 0 ? doc.object() : QJsonObject());
  206.             return;
  207.         }
  208.         else
  209.         {
  210.             changeState(STATE_FAILED_SOFT, tr("Failed to parse authentication server response "
  211.                                               "JSON response: %1 at offset %2.")
  212.                                                .arg(jsonError.errorString())
  213.                                                .arg(jsonError.offset));
  214.             QLOG_ERROR() << replyData;
  215.         }
  216.         return;
  217.     }
  218.  
  219.     // If the response code was not 200, then Yggdrasil may have given us information
  220.     // about the error.
  221.     // If we can parse the response, then get information from it. Otherwise just say
  222.     // there was an unknown error.
  223.     if (jsonError.error == QJsonParseError::NoError)
  224.     {
  225.         // We were able to parse the server's response. Woo!
  226.         // Call processError. If a subclass has overridden it then they'll handle their
  227.         // stuff there.
  228.         QLOG_DEBUG() << "The request failed, but the server gave us an error message. "
  229.                         "Processing error.";
  230.         processError(doc.object());
  231.     }
  232.     else
  233.     {
  234.         // The server didn't say anything regarding the error. Give the user an unknown
  235.         // error.
  236.         QLOG_DEBUG()
  237.             << "The request failed and the server gave no error message. Unknown error.";
  238.         changeState(STATE_FAILED_SOFT,
  239.                     tr("An unknown error occurred when trying to communicate with the "
  240.                        "authentication server: %1").arg(m_netReply->errorString()));
  241.     }
  242. }
  243.  
  244. void YggdrasilTask::processError(QJsonObject responseData)
  245. {
  246.     QJsonValue errorVal = responseData.value("error");
  247.     QJsonValue errorMessageValue = responseData.value("errorMessage");
  248.     QJsonValue causeVal = responseData.value("cause");
  249.  
  250.     if (errorVal.isString() && errorMessageValue.isString())
  251.     {
  252.         m_error = std::shared_ptr<Error>(new Error{
  253.             errorVal.toString(""), errorMessageValue.toString(""), causeVal.toString("")});
  254.         changeState(STATE_FAILED_HARD, m_error->m_errorMessageVerbose);
  255.     }
  256.     else
  257.     {
  258.         // Error is not in standard format. Don't set m_error and return unknown error.
  259.         changeState(STATE_FAILED_HARD, tr("An unknown Yggdrasil error occurred."));
  260.     }
  261. }
  262.  
  263. QString YggdrasilTask::getStateMessage() const
  264. {
  265.     switch (m_state)
  266.     {
  267.     case STATE_CREATED:
  268.         return "Waiting...";
  269.     case STATE_SENDING_REQUEST:
  270.         return tr("Sending request to auth servers...");
  271.     case STATE_PROCESSING_RESPONSE:
  272.         return tr("Processing response from servers...");
  273.     case STATE_SUCCEEDED:
  274.         return tr("Authentication task succeeded.");
  275.     case STATE_FAILED_SOFT:
  276.         return tr("Failed to contact the authentication server.");
  277.     case STATE_FAILED_HARD:
  278.         return tr("Failed to authenticate.");
  279.     default:
  280.         return tr("...");
  281.     }
  282. }
  283.  
  284. void YggdrasilTask::changeState(YggdrasilTask::State newState, QString reason)
  285. {
  286.     m_state = newState;
  287.     setStatus(getStateMessage());
  288.     if (newState == STATE_SUCCEEDED)
  289.     {
  290.         emitSucceeded();
  291.     }
  292.     else if (newState == STATE_FAILED_HARD || newState == STATE_FAILED_SOFT)
  293.     {
  294.         emitFailed(reason);
  295.     }
  296. }
  297.  
  298. YggdrasilTask::State YggdrasilTask::state()
  299. {
  300.     return m_state;
  301. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement