| 1 | /**************************************************************************** | 
| 2 |  ** | 
| 3 |  ** Copyright (C) 2018 The Qt Company Ltd. | 
| 4 |  ** Contact: https://www.qt.io/licensing/ | 
| 5 |  ** | 
| 6 |  ** This file is part of the test suite of the Qt Toolkit. | 
| 7 |  ** | 
| 8 |  ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ | 
| 9 |  ** Commercial License Usage | 
| 10 |  ** Licensees holding valid commercial Qt licenses may use this file in | 
| 11 |  ** accordance with the commercial license agreement provided with the | 
| 12 |  ** Software or, alternatively, in accordance with the terms contained in | 
| 13 |  ** a written agreement between you and The Qt Company. For licensing terms | 
| 14 |  ** and conditions see https://www.qt.io/terms-conditions. For further | 
| 15 |  ** information use the contact form at https://www.qt.io/contact-us. | 
| 16 |  ** | 
| 17 |  ** GNU General Public License Usage | 
| 18 |  ** Alternatively, this file may be used under the terms of the GNU | 
| 19 |  ** General Public License version 3 as published by the Free Software | 
| 20 |  ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT | 
| 21 |  ** included in the packaging of this file. Please review the following | 
| 22 |  ** information to ensure the GNU General Public License requirements will | 
| 23 |  ** be met: https://www.gnu.org/licenses/gpl-3.0.html. | 
| 24 |  ** | 
| 25 |  ** $QT_END_LICENSE$ | 
| 26 |  ** | 
| 27 |  ****************************************************************************/ | 
| 28 |  | 
| 29 | #include <QtTest/QtTest> | 
| 30 |  | 
| 31 | #include <QtNetwork/private/qtnetworkglobal_p.h> | 
| 32 |  | 
| 33 | #include <QtNetwork/private/qsslsocket_openssl_symbols_p.h> | 
| 34 | #include <QtNetwork/private/qsslsocket_openssl_p.h> | 
| 35 |  | 
| 36 | #include <QtNetwork/qsslcertificate.h> | 
| 37 | #include <QtNetwork/qtcpserver.h> | 
| 38 | #include <QtNetwork/qsslerror.h> | 
| 39 | #include <QtNetwork/qsslkey.h> | 
| 40 | #include <QtNetwork/qssl.h> | 
| 41 |  | 
| 42 | #include <QtCore/qsharedpointer.h> | 
| 43 | #include <QtCore/qbytearray.h> | 
| 44 | #include <QtCore/qfileinfo.h> | 
| 45 | #include <QtCore/qstring.h> | 
| 46 | #include <QtCore/qfile.h> | 
| 47 | #include <QtCore/qlist.h> | 
| 48 | #include <QtCore/qdir.h> | 
| 49 |  | 
| 50 | #include <openssl/ocsp.h> | 
| 51 |  | 
| 52 | #include <algorithm> | 
| 53 | #include <utility> | 
| 54 |  | 
| 55 | // NOTE: the word 'subject' in the code below means the subject of a status request, | 
| 56 | // so in general it's our peer's certificate we are asking about. | 
| 57 |  | 
| 58 | using SslError = QT_PREPEND_NAMESPACE(QSslError); | 
| 59 | using VectorOfErrors = QT_PREPEND_NAMESPACE(QVector<SslError>); | 
| 60 | using Latin1String = QT_PREPEND_NAMESPACE(QLatin1String); | 
| 61 |  | 
| 62 | Q_DECLARE_METATYPE(SslError) | 
| 63 | Q_DECLARE_METATYPE(VectorOfErrors) | 
| 64 | Q_DECLARE_METATYPE(Latin1String) | 
| 65 |  | 
| 66 | QT_BEGIN_NAMESPACE | 
| 67 |  | 
| 68 | namespace { | 
| 69 |  | 
| 70 | using OcspResponse = QSharedPointer<OCSP_RESPONSE>; | 
| 71 | using BasicResponse = QSharedPointer<OCSP_BASICRESP>; | 
| 72 | using SingleResponse = QSharedPointer<OCSP_SINGLERESP>; | 
| 73 | using CertId = QSharedPointer<OCSP_CERTID>; | 
| 74 | using EvpKey = QSharedPointer<EVP_PKEY>; | 
| 75 | using Asn1Time = QSharedPointer<ASN1_TIME>; | 
| 76 | using CertificateChain = QList<QSslCertificate>; | 
| 77 |  | 
| 78 | using NativeX509Ptr = X509 *; | 
| 79 |  | 
| 80 | class X509Stack { | 
| 81 | public: | 
| 82 |     explicit X509Stack(const QList<QSslCertificate> &chain); | 
| 83 |  | 
| 84 |     ~X509Stack(); | 
| 85 |  | 
| 86 |     int size() const; | 
| 87 |     X509 *operator[](int index) const; | 
| 88 |     operator STACK_OF(X509) *() const; | 
| 89 |  | 
| 90 | private: | 
| 91 |     OPENSSL_STACK *stack = nullptr; | 
| 92 |  | 
| 93 |     Q_DISABLE_COPY(X509Stack) | 
| 94 | }; | 
| 95 |  | 
| 96 | X509Stack::X509Stack(const QList<QSslCertificate> &chain) | 
| 97 | { | 
| 98 |     if (!chain.size()) | 
| 99 |         return; | 
| 100 |  | 
| 101 |     stack = q_OPENSSL_sk_new_null(); | 
| 102 |     if (!stack) | 
| 103 |         return; | 
| 104 |  | 
| 105 |     for (const QSslCertificate &cert : chain) { | 
| 106 |         X509 *nativeCert = NativeX509Ptr(cert.handle()); | 
| 107 |         if (!nativeCert) | 
| 108 |             continue; | 
| 109 |         q_OPENSSL_sk_push(st: stack, data: nativeCert); | 
| 110 |         q_X509_up_ref(a: nativeCert); | 
| 111 |     } | 
| 112 | } | 
| 113 |  | 
| 114 | X509Stack::~X509Stack() | 
| 115 | { | 
| 116 |     if (stack) | 
| 117 |         q_OPENSSL_sk_pop_free(a: stack, b: reinterpret_cast<void(*)(void*)>(q_X509_free)); | 
| 118 | } | 
| 119 |  | 
| 120 | int X509Stack::size() const | 
| 121 | { | 
| 122 |     if (stack) | 
| 123 |         return q_OPENSSL_sk_num(a: stack); | 
| 124 |     return 0; | 
| 125 | } | 
| 126 |  | 
| 127 | X509 *X509Stack::operator[](int index) const | 
| 128 | { | 
| 129 |     return NativeX509Ptr(q_OPENSSL_sk_value(a: stack, b: index)); | 
| 130 | } | 
| 131 |  | 
| 132 | X509Stack::operator STACK_OF(X509) *() const | 
| 133 | { | 
| 134 |     return reinterpret_cast<STACK_OF(X509)*>(stack); | 
| 135 | } | 
| 136 |  | 
| 137 | struct OcspTimeStamp | 
| 138 | { | 
| 139 |     OcspTimeStamp() = default; | 
| 140 |     OcspTimeStamp(long secondsBeforeNow, long secondsAfterNow); | 
| 141 |  | 
| 142 |     static Asn1Time timeToAsn1Time(long adjustment); | 
| 143 |  | 
| 144 |     Asn1Time thisUpdate; | 
| 145 |     Asn1Time nextUpdate; | 
| 146 | }; | 
| 147 |  | 
| 148 | OcspTimeStamp::OcspTimeStamp(long secondsBeforeNow, long secondsAfterNow) | 
| 149 | { | 
| 150 |     Asn1Time start = timeToAsn1Time(adjustment: secondsBeforeNow); | 
| 151 |     Asn1Time end = timeToAsn1Time(adjustment: secondsAfterNow); | 
| 152 |     if (start.data() && end.data()) { | 
| 153 |         thisUpdate.swap(other&: start); | 
| 154 |         nextUpdate.swap(other&: end); | 
| 155 |     } | 
| 156 | } | 
| 157 |  | 
| 158 | Asn1Time OcspTimeStamp::timeToAsn1Time(long adjustment) | 
| 159 | { | 
| 160 |     if (ASN1_TIME *adjusted = q_X509_gmtime_adj(s: nullptr, adj: adjustment)) | 
| 161 |         return Asn1Time(adjusted, q_ASN1_TIME_free); | 
| 162 |     return Asn1Time{}; | 
| 163 | } | 
| 164 |  | 
| 165 | struct OcspResponder | 
| 166 | { | 
| 167 |     OcspResponder(const OcspTimeStamp &stamp, const CertificateChain &subjs, | 
| 168 |                   const CertificateChain &respChain, const QSslKey &respPKey); | 
| 169 |  | 
| 170 |     QByteArray buildResponse(int responseStatus, int certificateStatus) const; | 
| 171 |     static EvpKey privateKeyToEVP_PKEY(const QSslKey &privateKey); | 
| 172 |     static CertId certificateToCertId(X509 *subject, X509 *issuer); | 
| 173 |     static QByteArray responseToDer(OCSP_RESPONSE *response); | 
| 174 |  | 
| 175 |     OcspTimeStamp timeStamp; | 
| 176 |     // Plural, we can send a 'wrong' BasicResponse containing more than | 
| 177 |     // 1 SingleResponse. | 
| 178 |     X509Stack subjects; | 
| 179 |     X509Stack responderChain; | 
| 180 |     QSslKey responderKey; | 
| 181 | }; | 
| 182 |  | 
| 183 | OcspResponder::OcspResponder(const OcspTimeStamp &stamp, const CertificateChain &subjs, | 
| 184 |                              const CertificateChain &respChain, const QSslKey &respPKey) | 
| 185 |     : timeStamp(stamp), | 
| 186 |       subjects(subjs), | 
| 187 |       responderChain(respChain), | 
| 188 |       responderKey(respPKey) | 
| 189 | { | 
| 190 | } | 
| 191 |  | 
| 192 | QByteArray OcspResponder::buildResponse(int responseStatus, int certificateStatus) const | 
| 193 | { | 
| 194 |     if (responseStatus != OCSP_RESPONSE_STATUS_SUCCESSFUL) { | 
| 195 |         OCSP_RESPONSE *response = q_OCSP_response_create(status: responseStatus, bs: nullptr); | 
| 196 |         if (!response) | 
| 197 |             return {}; | 
| 198 |         const OcspResponse rGuard(response, q_OCSP_RESPONSE_free); | 
| 199 |         return responseToDer(response); | 
| 200 |     } | 
| 201 |  | 
| 202 |     Q_ASSERT(subjects.size() && responderChain.size() && responderKey.handle()); | 
| 203 |  | 
| 204 |     const EvpKey nativeKey = privateKeyToEVP_PKEY(privateKey: responderKey); | 
| 205 |     if (!nativeKey.data()) | 
| 206 |         return {}; | 
| 207 |  | 
| 208 |     OCSP_BASICRESP *basicResponse = q_OCSP_BASICRESP_new(); | 
| 209 |     if (!basicResponse) | 
| 210 |         return {}; | 
| 211 |     const BasicResponse brGuard(basicResponse, q_OCSP_BASICRESP_free); | 
| 212 |  | 
| 213 |     for (int i = 0, e = subjects.size(); i < e; ++i) { | 
| 214 |         X509 *subject = subjects[i]; | 
| 215 |         Q_ASSERT(subject); | 
| 216 |         CertId certId = certificateToCertId(subject, issuer: responderChain[0]); | 
| 217 |         if (!certId.data()) | 
| 218 |             return {}; | 
| 219 |  | 
| 220 |         // NOTE: we do not own this 'singleResponse': | 
| 221 |         ASN1_TIME *revisionTime = certificateStatus == V_OCSP_CERTSTATUS_REVOKED ? | 
| 222 |                                                        timeStamp.thisUpdate.data() : nullptr; | 
| 223 |  | 
| 224 |         if (!q_OCSP_basic_add1_status(rsp: basicResponse, cid: certId.data(), status: certificateStatus, reason: 0, revtime: revisionTime, | 
| 225 |                                       thisupd: timeStamp.thisUpdate.data(), nextupd: timeStamp.nextUpdate.data())) { | 
| 226 |             return {}; | 
| 227 |         } | 
| 228 |     } | 
| 229 |  | 
| 230 |     if (q_OCSP_basic_sign(brsp: basicResponse, signer: responderChain[0], key: nativeKey.data(), dgst: q_EVP_sha1(), | 
| 231 |                           certs: responderChain, flags: 0) != 1) { | 
| 232 |         return {}; | 
| 233 |     } | 
| 234 |  | 
| 235 |     OCSP_RESPONSE *ocspResponse = q_OCSP_response_create(OCSP_RESPONSE_STATUS_SUCCESSFUL, bs: basicResponse); | 
| 236 |     if (!ocspResponse) | 
| 237 |         return {}; | 
| 238 |     const OcspResponse rGuard(ocspResponse, q_OCSP_RESPONSE_free); | 
| 239 |     return responseToDer(response: ocspResponse); | 
| 240 | } | 
| 241 |  | 
| 242 | EvpKey OcspResponder::privateKeyToEVP_PKEY(const QSslKey &privateKey) | 
| 243 | { | 
| 244 |     const EvpKey nullKey; | 
| 245 |     if (privateKey.isNull() || privateKey.algorithm() != QSsl::Rsa) { | 
| 246 |         // We use only RSA keys in this auto-test, since we test OCSP only, | 
| 247 |         // not handshake/TLS in general. | 
| 248 |         return nullKey; | 
| 249 |     } | 
| 250 |  | 
| 251 |     EVP_PKEY *nativeKey = q_EVP_PKEY_new(); | 
| 252 |     if (!nativeKey) | 
| 253 |         return nullKey; | 
| 254 |  | 
| 255 |     const EvpKey keyGuard(nativeKey, q_EVP_PKEY_free); | 
| 256 |     if (!q_EVP_PKEY_set1_RSA(a: nativeKey, b: reinterpret_cast<RSA *>(privateKey.handle()))) | 
| 257 |         return nullKey; | 
| 258 |  | 
| 259 |     return keyGuard; | 
| 260 | } | 
| 261 |  | 
| 262 | CertId OcspResponder::certificateToCertId(X509 *subject, X509 *issuer) | 
| 263 | { | 
| 264 |     const CertId nullId; | 
| 265 |     if (!subject || !issuer) | 
| 266 |         return nullId; | 
| 267 |  | 
| 268 |     const EVP_MD *digest = q_EVP_sha1(); | 
| 269 |     if (!digest) | 
| 270 |         return nullId; | 
| 271 |  | 
| 272 |     OCSP_CERTID *certId = q_OCSP_cert_to_id(dgst: digest, subject, issuer); | 
| 273 |     if (!certId) | 
| 274 |         return nullId; | 
| 275 |  | 
| 276 |     return CertId(certId, q_OCSP_CERTID_free); | 
| 277 | } | 
| 278 |  | 
| 279 | QByteArray OcspResponder::responseToDer(OCSP_RESPONSE *response) | 
| 280 | { | 
| 281 |     if (!response) | 
| 282 |         return {}; | 
| 283 |  | 
| 284 |     const int derSize = q_i2d_OCSP_RESPONSE(r: response, ppout: nullptr); | 
| 285 |     if (derSize <= 0) | 
| 286 |         return {}; | 
| 287 |  | 
| 288 |     QByteArray derData(derSize, Qt::Uninitialized); | 
| 289 |     unsigned char *pData = reinterpret_cast<unsigned char *>(derData.data()); | 
| 290 |     const int serializedSize = q_i2d_OCSP_RESPONSE(r: response, ppout: &pData); | 
| 291 |     if (serializedSize != derSize) | 
| 292 |         return {}; | 
| 293 |  | 
| 294 |     return derData; | 
| 295 | } | 
| 296 |  | 
| 297 | // The QTcpServer capable of sending OCSP status responses. | 
| 298 | class OcspServer : public QTcpServer | 
| 299 | { | 
| 300 |     Q_OBJECT | 
| 301 |  | 
| 302 | public: | 
| 303 |     OcspServer(const CertificateChain &serverChain, const QSslKey &privateKey); | 
| 304 |  | 
| 305 |     void configureResponse(const QByteArray &responseDer); | 
| 306 |     QString hostName() const; | 
| 307 |     QString peerVerifyName() const; | 
| 308 |  | 
| 309 | Q_SIGNALS: | 
| 310 |     void internalServerError(); | 
| 311 |  | 
| 312 | private: | 
| 313 |     void incomingConnection(qintptr descriptor) override; | 
| 314 |  | 
| 315 | public: | 
| 316 |     QSslConfiguration serverConfig; | 
| 317 |     QSslSocket serverSocket; | 
| 318 | }; | 
| 319 |  | 
| 320 | OcspServer::OcspServer(const CertificateChain &serverChain, const QSslKey &privateKey) | 
| 321 | { | 
| 322 |     Q_ASSERT(serverChain.size()); | 
| 323 |     Q_ASSERT(!privateKey.isNull()); | 
| 324 |  | 
| 325 |     serverConfig = QSslConfiguration::defaultConfiguration(); | 
| 326 |     serverConfig.setLocalCertificateChain(serverChain); | 
| 327 |     serverConfig.setPrivateKey(privateKey); | 
| 328 | } | 
| 329 |  | 
| 330 | void OcspServer::configureResponse(const QByteArray &responseDer) | 
| 331 | { | 
| 332 |     serverConfig.setBackendConfigurationOption(name: "Qt-OCSP-response" , value: responseDer); | 
| 333 | } | 
| 334 |  | 
| 335 | QString OcspServer::hostName() const | 
| 336 | { | 
| 337 |     // It's 'name' and not 'address' to be consistent with QSslSocket's naming style, | 
| 338 |     // where it's connectToHostEncrypted(hostName, ...) | 
| 339 |     const QHostAddress &addr = serverAddress(); | 
| 340 |     if (addr == QHostAddress::Any || addr == QHostAddress::AnyIPv4) | 
| 341 |         return QStringLiteral("127.0.0.1" ); | 
| 342 |     if (addr == QHostAddress::AnyIPv6) | 
| 343 |         return QStringLiteral("::1" ); | 
| 344 |     return addr.toString(); | 
| 345 | } | 
| 346 |  | 
| 347 | QString OcspServer::peerVerifyName() const | 
| 348 | { | 
| 349 |     const CertificateChain &localChain = serverConfig.localCertificateChain(); | 
| 350 |     if (localChain.isEmpty()) | 
| 351 |         return {}; | 
| 352 |     const auto cert = localChain.first(); | 
| 353 |     if (cert.isNull()) | 
| 354 |         return {}; | 
| 355 |  | 
| 356 |     const QStringList &names = cert.subjectInfo(info: QSslCertificate::CommonName); | 
| 357 |     return names.isEmpty() ? QString{} : names.first(); | 
| 358 | } | 
| 359 |  | 
| 360 | void OcspServer::incomingConnection(qintptr socketDescriptor) | 
| 361 | { | 
| 362 |     close(); | 
| 363 |  | 
| 364 |     if (!serverSocket.setSocketDescriptor(socketDescriptor)) { | 
| 365 |         emit internalServerError(); | 
| 366 |         return; | 
| 367 |     } | 
| 368 |  | 
| 369 |     serverSocket.setSslConfiguration(serverConfig); | 
| 370 |     // Since we test a client, not a server, we don't care about any | 
| 371 |     // possible errors on the server (QAbstractSocket or QSslSocket-related). | 
| 372 |     // Thus, we don't connect to any error signal. | 
| 373 |     serverSocket.startServerEncryption(); | 
| 374 | } | 
| 375 |  | 
| 376 | } // unnamed namespace | 
| 377 |  | 
| 378 | class tst_QOcsp : public QObject | 
| 379 | { | 
| 380 |     Q_OBJECT | 
| 381 |  | 
| 382 | public slots: | 
| 383 |     void initTestCase(); | 
| 384 |  | 
| 385 | private slots: | 
| 386 |     void connectSelfSigned(); | 
| 387 |     void badStatus_data(); | 
| 388 |     void badStatus(); | 
| 389 |     void multipleSingleResponses(); | 
| 390 |     void malformedResponse(); | 
| 391 |     void expiredResponse_data(); | 
| 392 |     void expiredResponse(); | 
| 393 |     void noNextUpdate(); | 
| 394 |     void wrongCertificateInResponse_data(); | 
| 395 |     void wrongCertificateInResponse(); | 
| 396 |     void untrustedResponder(); | 
| 397 |  | 
| 398 |     // OCSPTODO: more tests in future ... | 
| 399 |  | 
| 400 | private: | 
| 401 |     void setupOcspClient(QSslSocket &clientSocket, const CertificateChain &trustedCAs, | 
| 402 |                          const QString &peerName); | 
| 403 |     bool containsOcspErrors(const QList<QSslError> &errorsFound) const; | 
| 404 |     static bool containsError(const QList<QSslError> &errors, QSslError::SslError code); | 
| 405 |     static QByteArray goodResponse(const CertificateChain &subject, const CertificateChain &responder, | 
| 406 |                                    const QSslKey &privateKey, long beforeNow = -1000, long afterNow = 1000); | 
| 407 |     static bool loadPrivateKey(const QString &keyName, QSslKey &key); | 
| 408 |     static CertificateChain issuerToChain(const CertificateChain &chain); | 
| 409 |     static CertificateChain subjectToChain(const CertificateChain &chain); | 
| 410 |  | 
| 411 |     static QString certDirPath; | 
| 412 |  | 
| 413 |     void (QSslSocket::*)(const QList<QSslError> &) = &QSslSocket::sslErrors; | 
| 414 |     void (QTestEventLoop::*exitLoopSlot)() = &QTestEventLoop::exitLoop; | 
| 415 |  | 
| 416 |     const int handshakeTimeoutMS = 500; | 
| 417 |     QTestEventLoop loop; | 
| 418 |  | 
| 419 |     std::vector<QSslError::SslError> ocspErrorCodes = {QSslError::OcspNoResponseFound, | 
| 420 |                                                        QSslError::OcspMalformedRequest, | 
| 421 |                                                        QSslError::OcspMalformedResponse, | 
| 422 |                                                        QSslError::OcspInternalError, | 
| 423 |                                                        QSslError::OcspTryLater, | 
| 424 |                                                        QSslError::OcspSigRequred, | 
| 425 |                                                        QSslError::OcspUnauthorized, | 
| 426 |                                                        QSslError::OcspResponseCannotBeTrusted, | 
| 427 |                                                        QSslError::OcspResponseCertIdUnknown, | 
| 428 |                                                        QSslError::OcspResponseExpired, | 
| 429 |                                                        QSslError::OcspStatusUnknown}; | 
| 430 | }; | 
| 431 |  | 
| 432 | #define QCOMPARE_SINGLE_ERROR(sslSocket, expectedError) \ | 
| 433 |     const auto &tlsErrors = sslSocket.sslHandshakeErrors(); \ | 
| 434 |     QCOMPARE(tlsErrors.size(), 1); \ | 
| 435 |     QCOMPARE(tlsErrors[0].error(), expectedError) | 
| 436 |  | 
| 437 | #define QVERIFY_HANDSHAKE_WITHOUT_ERRORS(sslSocket) \ | 
| 438 |     QVERIFY(sslSocket.isEncrypted()); \ | 
| 439 |     QCOMPARE(sslSocket.state(), QAbstractSocket::ConnectedState); \ | 
| 440 |     QVERIFY(sslSocket.sslHandshakeErrors().isEmpty()) | 
| 441 |  | 
| 442 | #define QDECLARE_CHAIN(object, chainFileName) \ | 
| 443 |     CertificateChain object = QSslCertificate::fromPath(certDirPath + QLatin1String(chainFileName)); \ | 
| 444 |     QVERIFY(object.size()) | 
| 445 |  | 
| 446 | #define QDECLARE_PRIVATE_KEY(key, keyFileName) \ | 
| 447 |     QSslKey key; \ | 
| 448 |     QVERIFY(loadPrivateKey(QLatin1String(keyFileName), key)) | 
| 449 |  | 
| 450 | QString tst_QOcsp::certDirPath; | 
| 451 |  | 
| 452 | void tst_QOcsp::initTestCase() | 
| 453 | { | 
| 454 |     QVERIFY(QSslSocket::supportsSsl()); | 
| 455 |  | 
| 456 |     certDirPath = QFileInfo(QFINDTESTDATA("certs" )).absolutePath(); | 
| 457 |     QVERIFY(certDirPath.size() > 0); | 
| 458 |     certDirPath += QDir::separator() + QStringLiteral("certs" ) + QDir::separator(); | 
| 459 | } | 
| 460 |  | 
| 461 | void tst_QOcsp::connectSelfSigned() | 
| 462 | { | 
| 463 |     // This test may look a bit confusing, since we have essentially 1 | 
| 464 |     // self-signed certificate, which we trust for the purpose of this test, | 
| 465 |     // but we also request its (the certificate's) status and then we sign | 
| 466 |     // the status response using the same certificate and the corresponding | 
| 467 |     // private key. Anyway, we test the very basic things here: we send | 
| 468 |     // an OCSP status request, we verify the response (if server has sent it), | 
| 469 |     // and detect errors (if any). | 
| 470 |     QDECLARE_CHAIN(subjectChain, "ss1.crt" ); | 
| 471 |     QDECLARE_CHAIN(responderChain, "ss1.crt" ); | 
| 472 |     QDECLARE_PRIVATE_KEY(privateKey, "ss1-private.key" ); | 
| 473 |     { | 
| 474 |         // This server ignores our status request: | 
| 475 |         const QSslError::SslError expectedError = QSslError::OcspNoResponseFound; | 
| 476 |  | 
| 477 |         OcspServer server(subjectChain, privateKey); | 
| 478 |         QVERIFY(server.listen()); | 
| 479 |         connect(sender: &server, signal: &OcspServer::internalServerError, receiver: &loop, slot: exitLoopSlot); | 
| 480 |  | 
| 481 |         QSslSocket clientSocket; | 
| 482 |         QSslConfiguration clientConfig = QSslConfiguration::defaultConfiguration(); | 
| 483 |         auto roots = clientConfig.caCertificates(); | 
| 484 |         setupOcspClient(clientSocket, trustedCAs: issuerToChain(chain: subjectChain), peerName: server.peerVerifyName()); | 
| 485 |         clientSocket.connectToHostEncrypted(hostName: server.hostName(), port: server.serverPort()); | 
| 486 |         loop.enterLoopMSecs(ms: handshakeTimeoutMS); | 
| 487 |  | 
| 488 |         QVERIFY(!clientSocket.isEncrypted()); | 
| 489 |         QCOMPARE_SINGLE_ERROR(clientSocket, expectedError); | 
| 490 |     } | 
| 491 |     { | 
| 492 |         // Now the server will send a valid 'status: good' response. | 
| 493 |         OcspServer server(subjectChain, privateKey); | 
| 494 |         const QByteArray responseData(goodResponse(subject: subjectChain, responder: responderChain, privateKey)); | 
| 495 |         QVERIFY(responseData.size()); | 
| 496 |         server.configureResponse(responseDer: responseData); | 
| 497 |         QVERIFY(server.listen()); | 
| 498 |  | 
| 499 |         QSslSocket clientSocket; | 
| 500 |         setupOcspClient(clientSocket, trustedCAs: issuerToChain(chain: subjectChain), peerName: server.peerVerifyName()); | 
| 501 |         clientSocket.connectToHostEncrypted(hostName: server.hostName(), port: server.serverPort()); | 
| 502 |         loop.enterLoopMSecs(ms: handshakeTimeoutMS); | 
| 503 |  | 
| 504 |         QVERIFY_HANDSHAKE_WITHOUT_ERRORS(clientSocket); | 
| 505 |  | 
| 506 |         const auto responses = clientSocket.ocspResponses(); | 
| 507 |         QCOMPARE(responses.size(), 1); | 
| 508 |         const auto &response = responses.at(i: 0); | 
| 509 |         QVERIFY(response != QOcspResponse()); | 
| 510 |         const auto copy = response; | 
| 511 |         QCOMPARE(copy, response); | 
| 512 |         QVERIFY(qHash(response, 0) != 0); | 
| 513 |  | 
| 514 |         QCOMPARE(response.revocationReason(), QOcspRevocationReason::None); | 
| 515 |         QCOMPARE(response.certificateStatus(), QOcspCertificateStatus::Good); | 
| 516 |         QCOMPARE(response.subject(), clientSocket.peerCertificate()); | 
| 517 |         QCOMPARE(response.responder(), clientSocket.peerCertificate()); | 
| 518 |     } | 
| 519 | } | 
| 520 |  | 
| 521 | void tst_QOcsp::badStatus_data() | 
| 522 | { | 
| 523 |     QTest::addColumn<int>(name: "responseStatus" ); | 
| 524 |     QTest::addColumn<int>(name: "certificateStatus" ); | 
| 525 |     QTest::addColumn<QSslError>(name: "expectedError" ); | 
| 526 |  | 
| 527 |     QTest::addRow(format: "malformed-request" ) << OCSP_RESPONSE_STATUS_MALFORMEDREQUEST << 1 << QSslError(QSslError::OcspMalformedRequest); | 
| 528 |     QTest::addRow(format: "internal-error" ) << OCSP_RESPONSE_STATUS_INTERNALERROR << 2 << QSslError(QSslError::OcspInternalError); | 
| 529 |     QTest::addRow(format: "try-later" ) << OCSP_RESPONSE_STATUS_TRYLATER << 3 << QSslError(QSslError::OcspTryLater); | 
| 530 |     QTest::addRow(format: "signed-request-require" ) << OCSP_RESPONSE_STATUS_SIGREQUIRED << 2 << QSslError(QSslError::OcspSigRequred); | 
| 531 |     QTest::addRow(format: "unauthorized-request" ) << OCSP_RESPONSE_STATUS_UNAUTHORIZED << 1 <<QSslError(QSslError::OcspUnauthorized); | 
| 532 |  | 
| 533 |     QTest::addRow(format: "certificate-revoked" ) << OCSP_RESPONSE_STATUS_SUCCESSFUL << V_OCSP_CERTSTATUS_REVOKED | 
| 534 |                                          << QSslError(QSslError::CertificateRevoked); | 
| 535 |     QTest::addRow(format: "status-unknown" ) << OCSP_RESPONSE_STATUS_SUCCESSFUL << V_OCSP_CERTSTATUS_UNKNOWN | 
| 536 |                                     << QSslError(QSslError::OcspStatusUnknown); | 
| 537 | } | 
| 538 |  | 
| 539 | void tst_QOcsp::badStatus() | 
| 540 | { | 
| 541 |     // This test works with two types of 'bad' responses: | 
| 542 |     // 1. 'Error messages' (the response's status is anything but SUCCESSFUL, | 
| 543 |     // no information about the certificate itself, no signature); | 
| 544 |     // 2. 'REVOKED' or 'UNKNOWN' status for a certificate in question. | 
| 545 |     QFETCH(const int, responseStatus); | 
| 546 |     QFETCH(const int, certificateStatus); | 
| 547 |     QFETCH(const QSslError, expectedError); | 
| 548 |  | 
| 549 |     QDECLARE_CHAIN(subjectChain, "infbobchain.crt" ); | 
| 550 |     QCOMPARE(subjectChain.size(), 2); | 
| 551 |     QDECLARE_CHAIN(responderChain, "ca1.crt" ); | 
| 552 |     QDECLARE_PRIVATE_KEY(subjPrivateKey, "infbob.key" ); | 
| 553 |     QDECLARE_PRIVATE_KEY(respPrivateKey, "ca1.key" ); | 
| 554 |  | 
| 555 |     OcspServer server(subjectChain, subjPrivateKey); | 
| 556 |     const OcspTimeStamp stamp(-1000, 1000); | 
| 557 |     OcspResponder builder(stamp, subjectToChain(chain: subjectChain), responderChain, respPrivateKey); | 
| 558 |     const QByteArray response(builder.buildResponse(responseStatus, certificateStatus)); | 
| 559 |     QVERIFY(response.size()); | 
| 560 |     server.configureResponse(responseDer: response); | 
| 561 |     QVERIFY(server.listen()); | 
| 562 |     connect(sender: &server, signal: &OcspServer::internalServerError, receiver: &loop, slot: exitLoopSlot); | 
| 563 |  | 
| 564 |     QSslSocket clientSocket; | 
| 565 |     setupOcspClient(clientSocket, trustedCAs: issuerToChain(chain: subjectChain), peerName: server.peerVerifyName()); | 
| 566 |     clientSocket.connectToHostEncrypted(hostName: server.hostName(), port: server.serverPort()); | 
| 567 |     loop.enterLoopMSecs(ms: handshakeTimeoutMS); | 
| 568 |  | 
| 569 |     QVERIFY(!clientSocket.isEncrypted()); | 
| 570 |     QCOMPARE_SINGLE_ERROR(clientSocket, expectedError.error()); | 
| 571 | } | 
| 572 |  | 
| 573 | void tst_QOcsp::multipleSingleResponses() | 
| 574 | { | 
| 575 |     // We handle a response with more than one SingleResponse as malformed: | 
| 576 |     const QSslError::SslError expectedError = QSslError::OcspMalformedResponse; | 
| 577 |  | 
| 578 |     // Here we use subjectChain only to generate a response, the server | 
| 579 |     // is configured with the responder chain (it's the same cert after all). | 
| 580 |     QDECLARE_CHAIN(subjectChain, "ss1.crt" ); | 
| 581 |     QDECLARE_CHAIN(responderChain, "ss1.crt" ); | 
| 582 |     QDECLARE_PRIVATE_KEY(privateKey, "ss1-private.key" ); | 
| 583 |  | 
| 584 |     // Let's have more than 1 certificate in a chain: | 
| 585 |     subjectChain.append(t: subjectChain[0]); | 
| 586 |  | 
| 587 |     OcspServer server(responderChain, privateKey); | 
| 588 |     // Generate a BasicOCSPResponse containing 2 SingleResponses: | 
| 589 |     const QByteArray response(goodResponse(subject: subjectChain, responder: responderChain, privateKey)); | 
| 590 |     QVERIFY(response.size()); | 
| 591 |     server.configureResponse(responseDer: response); | 
| 592 |     QVERIFY(server.listen()); | 
| 593 |     connect(sender: &server, signal: &OcspServer::internalServerError, receiver: &loop, slot: exitLoopSlot); | 
| 594 |  | 
| 595 |     QSslSocket clientSocket; | 
| 596 |     setupOcspClient(clientSocket, trustedCAs: issuerToChain(chain: responderChain), peerName: server.peerVerifyName()); | 
| 597 |     clientSocket.connectToHostEncrypted(hostName: server.hostName(), port: server.serverPort()); | 
| 598 |     loop.enterLoopMSecs(ms: handshakeTimeoutMS); | 
| 599 |  | 
| 600 |     QVERIFY(!clientSocket.isEncrypted()); | 
| 601 |     QCOMPARE_SINGLE_ERROR(clientSocket, expectedError); | 
| 602 | } | 
| 603 |  | 
| 604 | void tst_QOcsp::malformedResponse() | 
| 605 | { | 
| 606 |     QDECLARE_CHAIN(serverChain, "ss1.crt" ); | 
| 607 |     QDECLARE_PRIVATE_KEY(privateKey, "ss1-private.key" ); | 
| 608 |  | 
| 609 |     OcspServer server(serverChain, privateKey); | 
| 610 |     // Let's send some arbitrary bytes instead of DER and see what happens next: | 
| 611 |     server.configureResponse(responseDer: "Sure, you can trust me, this cert was not revoked (I don't say it was issued at all)!" ); | 
| 612 |     QVERIFY(server.listen()); | 
| 613 |     connect(sender: &server, signal: &OcspServer::internalServerError, receiver: &loop, slot: exitLoopSlot); | 
| 614 |  | 
| 615 |     QSslSocket clientSocket; | 
| 616 |     setupOcspClient(clientSocket, trustedCAs: issuerToChain(chain: serverChain), peerName: server.peerVerifyName()); | 
| 617 |     clientSocket.connectToHostEncrypted(hostName: server.hostName(), port: server.serverPort()); | 
| 618 |     loop.enterLoopMSecs(ms: handshakeTimeoutMS); | 
| 619 |  | 
| 620 |     QVERIFY(!clientSocket.isEncrypted()); | 
| 621 |     QCOMPARE(clientSocket.error(), QAbstractSocket::SslHandshakeFailedError); | 
| 622 | } | 
| 623 |  | 
| 624 | void tst_QOcsp::expiredResponse_data() | 
| 625 | { | 
| 626 |     QTest::addColumn<long>(name: "beforeNow" ); | 
| 627 |     QTest::addColumn<long>(name: "afterNow" ); | 
| 628 |  | 
| 629 |     QTest::addRow(format: "expired" ) << -2000L << -1000L; | 
| 630 |     QTest::addRow(format: "not-valid-yet" ) << 5000L << 10000L; | 
| 631 |     QTest::addRow(format: "next-before-this" ) << -1000L << -2000L; | 
| 632 | } | 
| 633 |  | 
| 634 | void tst_QOcsp::expiredResponse() | 
| 635 | { | 
| 636 |     // We report different kinds of problems with [thisUpdate, nextUpdate] | 
| 637 |     // as 'expired' (to keep it simple): | 
| 638 |     const QSslError::SslError expectedError = QSslError::OcspResponseExpired; | 
| 639 |  | 
| 640 |     QFETCH(const long, beforeNow); | 
| 641 |     QFETCH(const long, afterNow); | 
| 642 |  | 
| 643 |     QDECLARE_CHAIN(subjectChain, "ss1.crt" ); | 
| 644 |     QDECLARE_CHAIN(responderChain, "ss1.crt" ); | 
| 645 |     QDECLARE_PRIVATE_KEY(privateKey, "ss1-private.key" ); | 
| 646 |  | 
| 647 |     OcspServer server(subjectChain, privateKey); | 
| 648 |     const QByteArray response(goodResponse(subject: subjectChain, responder: responderChain, privateKey, beforeNow, afterNow)); | 
| 649 |     QVERIFY(response.size()); | 
| 650 |     server.configureResponse(responseDer: response); | 
| 651 |     QVERIFY(server.listen()); | 
| 652 |     connect(sender: &server, signal: &OcspServer::internalServerError, receiver: &loop, slot: exitLoopSlot); | 
| 653 |  | 
| 654 |     QSslSocket clientSocket; | 
| 655 |     setupOcspClient(clientSocket, trustedCAs: issuerToChain(chain: subjectChain), peerName: server.peerVerifyName()); | 
| 656 |     clientSocket.connectToHostEncrypted(hostName: server.hostName(), port: server.serverPort()); | 
| 657 |     loop.enterLoopMSecs(ms: handshakeTimeoutMS); | 
| 658 |  | 
| 659 |     QVERIFY(!clientSocket.isEncrypted()); | 
| 660 |     QCOMPARE_SINGLE_ERROR(clientSocket, expectedError); | 
| 661 | } | 
| 662 |  | 
| 663 | void tst_QOcsp::noNextUpdate() | 
| 664 | { | 
| 665 |     // RFC2560, 2.4: | 
| 666 |     // "If nextUpdate is not set, the responder is indicating that newer | 
| 667 |     // revocation information is available all the time." | 
| 668 |     // | 
| 669 |     // This test is just to verify that we correctly handle such responses. | 
| 670 |     QDECLARE_CHAIN(subjectChain, "ss1.crt" ); | 
| 671 |     QDECLARE_CHAIN(responderChain, "ss1.crt" ); | 
| 672 |     QDECLARE_PRIVATE_KEY(privateKey, "ss1-private.key" ); | 
| 673 |  | 
| 674 |     OcspServer server(subjectChain, privateKey); | 
| 675 |     OcspTimeStamp openRange(-1000, 0); | 
| 676 |     openRange.nextUpdate.clear(); | 
| 677 |     const OcspResponder responder(openRange, subjectChain, responderChain, privateKey); | 
| 678 |     const QByteArray response(responder.buildResponse(OCSP_RESPONSE_STATUS_SUCCESSFUL, | 
| 679 |                                                       V_OCSP_CERTSTATUS_GOOD)); | 
| 680 |     QVERIFY(response.size()); | 
| 681 |     server.configureResponse(responseDer: response); | 
| 682 |     QVERIFY(server.listen()); | 
| 683 |     connect(sender: &server, signal: &OcspServer::internalServerError, receiver: &loop, slot: exitLoopSlot); | 
| 684 |  | 
| 685 |     QSslSocket clientSocket; | 
| 686 |     setupOcspClient(clientSocket, trustedCAs: issuerToChain(chain: subjectChain), peerName: server.peerVerifyName()); | 
| 687 |     clientSocket.connectToHostEncrypted(hostName: server.hostName(), port: server.serverPort()); | 
| 688 |     loop.enterLoopMSecs(ms: handshakeTimeoutMS); | 
| 689 |  | 
| 690 |     QVERIFY_HANDSHAKE_WITHOUT_ERRORS(clientSocket); | 
| 691 | } | 
| 692 |  | 
| 693 | void tst_QOcsp::wrongCertificateInResponse_data() | 
| 694 | { | 
| 695 |     QTest::addColumn<QLatin1String>(name: "respChainName" ); | 
| 696 |     QTest::addColumn<QLatin1String>(name: "respKeyName" ); | 
| 697 |     QTest::addColumn<QLatin1String>(name: "wrongChainName" ); | 
| 698 |  | 
| 699 |     QTest::addRow(format: "same-CA-wrong-subject" ) << QLatin1String("ca1.crt" ) << QLatin1String("ca1.key" ) | 
| 700 |                                            << QLatin1String("alice.crt" ); | 
| 701 |     QTest::addRow(format: "wrong-CA-same-subject" ) << QLatin1String("ss1.crt" ) << QLatin1String("ss1-private.key" ) | 
| 702 |                                            << QLatin1String("alice.crt" ); | 
| 703 |     QTest::addRow(format: "wrong-CA-wrong-subject" ) << QLatin1String("ss1.crt" ) << QLatin1String("ss1-private.key" ) | 
| 704 |                                             << QLatin1String("ss1.crt" ); | 
| 705 | } | 
| 706 |  | 
| 707 | void tst_QOcsp::wrongCertificateInResponse() | 
| 708 | { | 
| 709 |     QFETCH(const QLatin1String, respChainName); | 
| 710 |     QFETCH(const QLatin1String, respKeyName); | 
| 711 |     QFETCH(const QLatin1String, wrongChainName); | 
| 712 |     // In this test, the server will send a valid response (correctly signed | 
| 713 |     // by a trusted key/cert) but for a wrong certificate (not the one the | 
| 714 |     // server presented to the client in the server's 'Certificate' message). | 
| 715 |     const QSslError::SslError expectedError = QSslError::OcspResponseCertIdUnknown; | 
| 716 |  | 
| 717 |     QDECLARE_CHAIN(subjectChain, "infbobchain.crt" ); | 
| 718 |     QDECLARE_PRIVATE_KEY(subjectKey, "infbob.key" ); | 
| 719 |     QDECLARE_CHAIN(responderChain, respChainName); | 
| 720 |     QDECLARE_PRIVATE_KEY(responderKey, respKeyName); | 
| 721 |  | 
| 722 |     QDECLARE_CHAIN(wrongChain, wrongChainName); | 
| 723 |  | 
| 724 |     OcspServer server(subjectToChain(chain: subjectChain), subjectKey); | 
| 725 |     const QByteArray wrongResponse(goodResponse(subject: wrongChain, responder: responderChain, privateKey: responderKey)); | 
| 726 |     QVERIFY(wrongResponse.size()); | 
| 727 |     server.configureResponse(responseDer: wrongResponse); | 
| 728 |     QVERIFY(server.listen()); | 
| 729 |     connect(sender: &server, signal: &OcspServer::internalServerError, receiver: &loop, slot: exitLoopSlot); | 
| 730 |  | 
| 731 |     QSslSocket clientSocket; | 
| 732 |     setupOcspClient(clientSocket, trustedCAs: issuerToChain(chain: subjectChain), peerName: server.peerVerifyName()); | 
| 733 |     clientSocket.connectToHostEncrypted(hostName: server.hostName(), port: server.serverPort()); | 
| 734 |     loop.enterLoopMSecs(ms: handshakeTimeoutMS); | 
| 735 |  | 
| 736 |     QVERIFY(!clientSocket.isEncrypted()); | 
| 737 |     QVERIFY(containsError(clientSocket.sslHandshakeErrors(), expectedError)); | 
| 738 | } | 
| 739 |  | 
| 740 | void tst_QOcsp::untrustedResponder() | 
| 741 | { | 
| 742 |     const QSslError::SslError expectedError = QSslError::OcspResponseCannotBeTrusted; | 
| 743 |  | 
| 744 |     QDECLARE_CHAIN(subjectChain, "infbobchain.crt" ); | 
| 745 |     QDECLARE_PRIVATE_KEY(subjectKey, "infbob.key" ); | 
| 746 |     QDECLARE_CHAIN(responderChain, "ca1.crt" ); | 
| 747 |     QDECLARE_PRIVATE_KEY(responderKey, "ca1.key" ); | 
| 748 |  | 
| 749 |     OcspServer server(subjectChain, subjectKey); | 
| 750 |     const QByteArray response(goodResponse(subject: subjectToChain(chain: subjectChain), responder: responderChain, privateKey: responderKey)); | 
| 751 |     QVERIFY(response.size()); | 
| 752 |     server.configureResponse(responseDer: response); | 
| 753 |     QVERIFY(server.listen()); | 
| 754 |     connect(sender: &server, signal: &OcspServer::internalServerError, receiver: &loop, slot: exitLoopSlot); | 
| 755 |  | 
| 756 |     QSslSocket clientSocket; | 
| 757 |     setupOcspClient(clientSocket, trustedCAs: {}, peerName: server.peerVerifyName()); | 
| 758 |     clientSocket.connectToHostEncrypted(hostName: server.hostName(), port: server.serverPort()); | 
| 759 |     loop.enterLoopMSecs(ms: handshakeTimeoutMS); | 
| 760 |  | 
| 761 |     QVERIFY(!clientSocket.isEncrypted()); | 
| 762 |     QVERIFY(containsError(clientSocket.sslHandshakeErrors(), expectedError)); | 
| 763 | } | 
| 764 |  | 
| 765 | void tst_QOcsp::setupOcspClient(QSslSocket &clientSocket, const CertificateChain &caCerts, const QString &name) | 
| 766 | { | 
| 767 |     QSslConfiguration clientConfig = QSslConfiguration::defaultConfiguration(); | 
| 768 |     clientConfig.setOcspStaplingEnabled(true); | 
| 769 |  | 
| 770 |     if (caCerts.size()) { | 
| 771 |         auto roots = clientConfig.caCertificates(); | 
| 772 |         roots.append(t: caCerts); | 
| 773 |         clientConfig.setCaCertificates(roots); | 
| 774 |     } | 
| 775 |  | 
| 776 |     clientSocket.setSslConfiguration(clientConfig); | 
| 777 |     clientSocket.setPeerVerifyName(name); | 
| 778 |  | 
| 779 |     connect(sender: &clientSocket, signal: &QAbstractSocket::errorOccurred, receiver: &loop, slot: exitLoopSlot); | 
| 780 |     connect(sender: &clientSocket, signal: tlsErrorsSignal, receiver: &loop, slot: exitLoopSlot); | 
| 781 |     connect(sender: &clientSocket, signal: &QSslSocket::encrypted, receiver: &loop, slot: exitLoopSlot); | 
| 782 | } | 
| 783 |  | 
| 784 | bool tst_QOcsp::containsOcspErrors(const QList<QSslError> &errorsFound) const | 
| 785 | { | 
| 786 |     for (auto code : ocspErrorCodes) { | 
| 787 |         if (containsError(errors: errorsFound, code)) | 
| 788 |             return true; | 
| 789 |     } | 
| 790 |     return false; | 
| 791 | } | 
| 792 |  | 
| 793 | bool tst_QOcsp::containsError(const QList<QSslError> &errors, QSslError::SslError code) | 
| 794 | { | 
| 795 |     const auto it = std::find_if(first: errors.begin(), last: errors.end(), | 
| 796 |                                  pred: [&code](const QSslError &other){return other.error() == code;}); | 
| 797 |     return it != errors.end(); | 
| 798 | } | 
| 799 |  | 
| 800 | QByteArray tst_QOcsp::goodResponse(const CertificateChain &subject, const CertificateChain &responder, | 
| 801 |                                    const QSslKey &privateKey, long beforeNow, long afterNow) | 
| 802 | { | 
| 803 |     const OcspResponder builder(OcspTimeStamp(beforeNow, afterNow), subject, responder, privateKey); | 
| 804 |     return builder.buildResponse(OCSP_RESPONSE_STATUS_SUCCESSFUL, V_OCSP_CERTSTATUS_GOOD); | 
| 805 | } | 
| 806 |  | 
| 807 | bool tst_QOcsp::loadPrivateKey(const QString &keyFileName, QSslKey &key) | 
| 808 | { | 
| 809 |     QFile keyFile(certDirPath + keyFileName); | 
| 810 |     if (!keyFile.open(flags: QIODevice::ReadOnly)) | 
| 811 |         return false; | 
| 812 |     key = QSslKey(keyFile.readAll(), QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey); | 
| 813 |     return !key.isNull(); | 
| 814 | } | 
| 815 |  | 
| 816 | CertificateChain tst_QOcsp::issuerToChain(const CertificateChain &chain) | 
| 817 | { | 
| 818 |     // Here we presume that, if the chain isn't a single self-signed certificate, its second | 
| 819 |     // entry is the issuer. | 
| 820 |     const int length = chain.size(); | 
| 821 |     Q_ASSERT(length > 0); | 
| 822 |     return CertificateChain() << chain[length > 1 ? 1 : 0]; | 
| 823 | } | 
| 824 |  | 
| 825 | CertificateChain tst_QOcsp::subjectToChain(const CertificateChain &chain) | 
| 826 | { | 
| 827 |     Q_ASSERT(chain.size()); | 
| 828 |     return CertificateChain() << chain[0]; | 
| 829 | } | 
| 830 |  | 
| 831 | QT_END_NAMESPACE | 
| 832 |  | 
| 833 | QTEST_MAIN(tst_QOcsp) | 
| 834 |  | 
| 835 | #include "tst_qocsp.moc" | 
| 836 |  |