1

The public key of a given certificate as displayed by Windows when looking at the certificate details in Chrome differs from what Qt returns in a slot connected to the encrypted signal.

auto onEncrypt = [](QNetworkReply* rpl) {
    auto cert = rpl->sslConfiguration().peerCertificate();
    auto publicKey = cert.publicKey();

    QString winHexKey = "3082010a0282010100d8..."; // as displayed in cert info of Chrome on Windows for the Public Key
    auto windowsKey = QByteArray::fromHex(winHexKey.toUtf8());
    if (windowsKey == publicKey.toPem())
        std::cout << "PEM key matched\n";
    else if (windowsKey == publicKey.toDer())
        std::cout << "DER key matched\n";
    else if (winHexKey == publicKey.toPem().toHex())
        std::cout << "Hex PEM key matched\n";
    else if (winHexKey == publicKey.toDer().toHex())
        std::cout << "Hex DER key matched\n";
    else
        std::cout << "No match!\n";
    std::cout << publicKey.toPem().toHex().toStdString() << '\n'; // 902 characters worth starting with 2d2d2d2d2d
};
QNetworkAccessManager mgr;
QObject::connect(&mgr, &QNetworkAccessManager::encrypted, onEncrypt);
QNetworkRequest r(QUrl::fromUserInput("https://www.qt.io"));
mgr.get(r);

Always results in No Match. Interestingly the Hex output of the public key is much much larger than whatever Windows displays.

How can one get the public key of the certificate presented by the server, and verify it against what is present in the certificate?

Phoenix
  • 171
  • 2
  • 15

2 Answers2

0

How can one get the public key of the certificate presented by the server

The public key is part of the certificate. The certificate will also contain hashes, which enable some basic integrity checks on the certificate contents. To check if a server actually matches with the certificate you must try to establish an encrypted channel. Either the server bootstraps the encrypted channel in a way that is consistent with the certificate as observed from the client -- which is to say, the server matches with the certificate presented for it; or it does not -- which is cause for alarm.

verify it against what is present in the certificate?

X.509 certificates also contain hashes, which can be used for integrity checking.

The discrepancy between what Chrome shows and what your code does is in the fact that Chrome decodes the PEM/DER/BER. DER/BER add some additional metadata to describe things like field type and length; PEM is a specific encoding of the underlying X.509 certificate contents.

user268396
  • 11,576
  • 2
  • 31
  • 26
  • That doesn't really answer my question though. Yes the Public Key is part of the certificate, but I need to validate it to make sure it is the expected certificate (and hence the real server), not just some self-signed certificate (which too would be trusted if added to trust store) somewhere. Can't use the thumbprint either since that changes if the certificate is renewed, whereas the public key doesn't necessarily change. – Phoenix Mar 14 '17 at 22:35
  • It wasn't obvious from your question that you knew this. if you expect a certain public key, by extension you expect a certain value for the signature. Other fields such as CN should not change either after all. The point of an X.509 cert is that you don't really care what the particular key of the server is, but rather that any key which matches up with the cert will do and you have the external chain of trust from cert to CA to client truststore to validate *that fact* instead. – user268396 Mar 15 '17 at 07:04
0

If you want to send request with peer verification it's not necessary to verify manually. Use QSslConfiguration and set your certificate file from Chrome as CA certificate. Example:

QSslConfiguration config = QSslConfiguration::defaultConfiguration();
// Load certificate from file to QByteArray chromeCertificateByteArray
QSslCertificate ca = QSslCertificate(chromeCertificateByteArray); 
QList <QSslCertificate> caList;
caList.append(ca);
config.setCaCertificates(caList);

QNetworkRequest request(QUrl::fromUserInput("https://www.qt.io"));
request.setSslConfiguration(config);

QNetworkAccessManager networkManager;
QNetworkReply* reply = networkManager.get(request);
  • So just take the CA Chain and make that the only accepted CA for that connection? What if the certificate is renewed and the CA decides to use another intermediate certificate? Or changes their root certificate for signing? In that case it would fail if I got this right. – Phoenix Mar 16 '17 at 12:37