--nextPart4241901.rcaI4tOsEf Content-Type: multipart/mixed; boundary="Boundary-01=_NF1YLRx4cNyoxtf" Content-Transfer-Encoding: 7bit --Boundary-01=_NF1YLRx4cNyoxtf Content-Type: Text/Plain; charset="iso-8859-15" Content-Transfer-Encoding: quoted-printable Content-Disposition: inline Em Sexta-feira 29. Janeiro 2010, =E0s 21.53.55, Andreas Hartmetz escreveu: > On Friday 29 January 2010 14:30:37 Thiago Macieira wrote: > > Em Sexta-feira 29 Janeiro 2010, =E0s 14:04:06, Pierre Schmitz escreveu: > > > > I've already made a script to do that. Actually, a Qt program. > > > >=20 > > > > I'll probably update Qt's certificate list with the Firefox ones for > > > > the next Qt version. > > > >=20 > > > > So all KDE has to do is stop overriding Qt's default certificate > > > > bundle. > > >=20 > > > I would appreciate if KDE and Qt would use the system wide cert bundle > > > (optionally configurable at build time). > >=20 > > The only thing that's holding me back in updating the Qt certificates is > > to decide whether keeping expired certificates is a good thing. > >=20 > > There are 81 certificates in Qt's bundle. One of them is repeated, so 80 > > are unique. > >=20 > > However, from those 80, 8 have expired already. > >=20 > > Of the 72 non-expired, unique certificates in Qt, 48 are *not* in the > > Firefox certificate store. But when the remainder of the Firefox ones a= re > > added, the total increases to 120. >=20 > I'd really want *only* the certificates from Firefox and no expired > certificates. Expired certificates generate an SSL error when connecting, > just like a missing certificate. So the only change for client code is a > different SSL error. > Can you do that - i.e. just sync with Firefox? > Or introduce a policy to remove expired certificates after n years and > otherwise sync with Firefox... As I mentioned, the type of SSL error won't > matter very much. > If you can't do that, would you mind posting the script to download the > certificates? :) Attaching the program. I'm going to add it to Qt's util/ dir. As you can see, it downloads the certificates from the XML file in=20 http://www.mozilla.org/projects/security/certs/included/. That's the only=20 resource I found in Mozilla. If there are more certificates, it would be ni= ce=20 to know about them. The Qt non-Firefox certificates contain the likes of VeriSign, Thawte and=20 Equifax. The question is: why are those well-known certificates in Qt but n= ot=20 in Firefox? =2D-=20 Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org Senior Product Manager - Nokia, Qt Development Frameworks PGP/GPG: 0x6EF45358; fingerprint: E067 918B B660 DBD1 105C 966C 33F5 F005 6EF4 5358 --Boundary-01=_NF1YLRx4cNyoxtf Content-Type: text/x-c++src; charset="UTF-8"; name="main.cpp" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="main.cpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static const char mozillaCertUrl[] = "http://www.mozilla.org/projects/security/certs/included/"; static const char additionalCerts[] = "qrc:/extra/additional.xml"; struct Certificate { enum TrustFlag { Web = 1, Email = 2 }; Q_DECLARE_FLAGS(Trust, TrustFlag); Certificate() : errorString(QLatin1String("Timed out")), trust(0), version(0) {} QSslCertificate cert; QUrl url; QString name; QByteArray sha1; QDate effectiveDate; QDate expiryDate; QString errorString; Trust trust; int version; }; Q_DECLARE_OPERATORS_FOR_FLAGS(Certificate::Trust) class SslBundleApp: public QObject { Q_OBJECT typedef QHash Certificates; QString bundleFile; QNetworkAccessManager manager; QSet pendingDownloads; Certificates certificates; public: SslBundleApp(); bool findQtDir(const QString &path); Certificate readCertificate(QXmlStreamReader &xml); void getCertificate(const QNetworkRequest &req); void parseCertificate(QNetworkReply *reply); void addQtDefaultCaCertificates(); void writeAllCertificates(); protected: void timerEvent(QTimerEvent *); public slots: void downloadFinished(QNetworkReply *reply); void masterXmlDownloaded(QNetworkReply *reply); void certDownloaded(QNetworkReply *reply); }; static QByteArray toHexDots(const QByteArray &input) { if (input.isEmpty()) return QByteArray(); QByteArray result; result.resize(input.size() * 3); char *dst = result.data(); const char *src = input.constData(); for (int i = 0; i < input.length(); ++i, ++src) { static const char hexdigits[] = "0123456789abcdef"; register uchar c = *src; *dst++ = hexdigits[c >> 4]; *dst++ = hexdigits[c & 0xF]; *dst++ = ':'; } result.chop(1); return result; } static inline bool isHexDigit(register const QChar &ch) { register ushort u = ch.unicode(); return (u >= '0' && u <= '9') || ((u|0x20) >= 'a' && (u|0x20) <= 'f'); } static inline uchar hexNibble(register const QChar &ch) { register ushort u = ch.unicode(); return u <= '9' ? u - '0' : (u|0x20) - 'a' + 0xa; } static QString decodeEscapedString(const QString &input) { QByteArray output; bool simple = true; const QChar *in = input.constData(); const QChar *end = in + input.length(); for (; in < end; ++in) { if (in[0].unicode() > 0x7f) return input; // it's not escaped! if (in[0].unicode() != '\\' || in + 3 >= end || in[1].unicode() != 'x' || !isHexDigit(in[2]) || !isHexDigit(in[3])) { if (!simple) output.append(in->unicode()); } else { if (simple) { // initialise the output up to here const QChar *begin = input.constData(); int pos = in - begin; output.reserve(input.length()); output.resize(pos); while (--pos >= 0) { output[pos] = begin[pos].unicode(); } simple = false; } in += 2; output.append((hexNibble(in[0]) << 4) | hexNibble(in[1])); ++in; } } return output.isEmpty() ? input : QString::fromUtf8(output); } bool SslBundleApp::findQtDir(const QString &path) { if (path.isEmpty()) return false; QDir dir(path); if (!dir.exists()) return false; QString subPath = QLatin1String("src/network/ssl/qt-ca-bundle.crt"); if (!dir.exists(subPath)) return false; bundleFile = dir.absoluteFilePath(subPath); return true; } SslBundleApp::SslBundleApp() { // find where the Qt source code is QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); if (!findQtDir(env.value(QLatin1String("QTSRCDIR")))) { if (!findQtDir(env.value(QLatin1String("QTDIR")))) { static const char qt_source_tree[] = QT_SOURCE_TREE; if (!findQtDir(QFile::decodeName(qt_source_tree))) { QString self = QCoreApplication::applicationDirPath() + QLatin1String("../.."); if (!findQtDir(self)) { qWarning() << "Cannot find qt-ca-bundle.crt file. Cannot continue."; ::exit(1); } } } } addQtDefaultCaCertificates(); connect(&manager, SIGNAL(finished(QNetworkReply*)), SLOT(downloadFinished(QNetworkReply*))); pendingDownloads << manager.get(QNetworkRequest(QUrl::fromEncoded(mozillaCertUrl))) << manager.get(QNetworkRequest(QUrl::fromEncoded(additionalCerts))); } void SslBundleApp::getCertificate(const QNetworkRequest &req) { QNetworkReply *reply = manager.get(req); QSslConfiguration sslconf = reply->sslConfiguration(); sslconf.setProtocol(QSsl::TlsV1); sslconf.setPeerVerifyDepth(0); sslconf.setPeerVerifyMode(QSslSocket::VerifyNone); reply->setSslConfiguration(sslconf); reply->ignoreSslErrors(); pendingDownloads << reply; } Certificate SslBundleApp::readCertificate(QXmlStreamReader &xml) { Certificate cert; cert.name = xml.attributes().value(QLatin1String("name")).toString(); QStringRef name = xml.name(); while (xml.readNextStartElement()) { name = xml.name(); //qDebug() << xml.name(); if (name == QLatin1String("data")) { QXmlStreamAttributes attr = xml.attributes(); cert.url = QUrl::fromEncoded(attr.value(QLatin1String("url")).toString().toUtf8()); cert.sha1 = QByteArray::fromHex(attr.value(QLatin1String("sha1")).toString().toLatin1()); cert.version = attr.value(QLatin1String("version")).toString().toInt(); cert.effectiveDate = QDate::fromString(attr.value(QLatin1String("from")).toString(), Qt::ISODate); cert.expiryDate = QDate::fromString(attr.value(QLatin1String("to")).toString(), Qt::ISODate); xml.skipCurrentElement(); } else if (name == QLatin1String("trust")) { while (xml.readNextStartElement()) { if (xml.name() == QLatin1String("flag")) { QStringRef type = xml.attributes().value(QLatin1String("type")); if (type == QLatin1String("web")) cert.trust |= Certificate::Web; else if (type == QLatin1String("email")) cert.trust |= Certificate::Email; } xml.skipCurrentElement(); } } else { xml.skipCurrentElement(); } } return cert; } void SslBundleApp::masterXmlDownloaded(QNetworkReply *reply) { int newDownloads = 0; // parse the XML reply and download all certificates QXmlStreamReader xml(reply); xml.readNextStartElement(); if (xml.name() != QLatin1String("certificates")) return; // malformed file while (xml.readNextStartElement()) { QStringRef name = xml.name(); //qDebug() << xml.name(); if (name == QLatin1String("authority")) { while (xml.readNextStartElement()) { name = xml.name(); //qDebug() << xml.name(); if (name == QLatin1String("certificate")) { Certificate cert = readCertificate(xml); Certificates::Iterator it = certificates.find(cert.sha1); if (cert.trust & Certificate::Web && it == certificates.end()) { qDebug() << "Downloading certificate" << cert.name << toHexDots(cert.sha1) << "from" << cert.url; QNetworkRequest req(cert.url); req.setAttribute(QNetworkRequest::User, cert.sha1); getCertificate(req); ++newDownloads; certificates.insert(cert.sha1, cert); } else if (it != certificates.end()) { //qDebug() << "Certificate" << cert.name << "already present in Qt's bundle, not downloading again"; if (it->url.scheme() == QLatin1String("qrc")) it->url = cert.url; if (it->name.isEmpty()) it->name = cert.name; } } else { xml.skipCurrentElement(); } } } else { xml.skipCurrentElement(); } } qDebug() << "Downloading" << newDownloads << "certificates from" << reply->url(); startTimer(45000); } void SslBundleApp::parseCertificate(QNetworkReply *reply) { QByteArray sha1 = reply->request().attribute(QNetworkRequest::User).toByteArray(); if (sha1.isEmpty()) return; // something else got done? Certificates::Iterator cit = certificates.find(sha1); if (cit == certificates.end()) return; // also something wrong // was this redirection? QVariant redirectionTarget = reply->attribute(QNetworkRequest::RedirectionTargetAttribute); if (redirectionTarget.isValid()) { qDebug() << "Downloading certificate" << cit->name << "from" << redirectionTarget.toUrl() << "(redirected)"; QNetworkRequest req(redirectionTarget.toUrl()); req.setAttribute(QNetworkRequest::User, sha1); getCertificate(req); return; } if (reply->error()) { bool retried = reply->request().attribute(QNetworkRequest::Attribute(QNetworkRequest::User+1)).toBool(); if (!retried) { // try again qDebug() << "Downloading certificate" << cit->name << "from" << reply->url() << "(retry)"; QNetworkRequest req(reply->url()); req.setAttribute(QNetworkRequest::User, sha1); req.setAttribute(QNetworkRequest::Attribute(QNetworkRequest::User+1), true); getCertificate(req); } else { cit->errorString = QString::fromLatin1("Failed to download certificate from %1: %2") .arg(QString::fromLatin1(reply->url().toEncoded()), reply->errorString()); } return; } qDebug() << "Downloaded certificate" << cit->name << "from" << reply->url(); // parse the certificate // try DER first QByteArray data = reply->readAll(); QSslCertificate cert = QSslCertificate(data, QSsl::Der); if (!cert.isValid()) { // try PEM cert = QSslCertificate(data, QSsl::Pem); if (!cert.isValid()) { cit->errorString = QString::fromLatin1("Failed to parse certificate from %1") .arg(QString::fromLatin1(reply->url().toEncoded())); return; } } // verify that the certificate data matches if (cert.version().toInt() != cit->version) { cit->errorString = QString::fromLatin1("Downloaded certificate has version %1, but %2 was expected") .arg(QLatin1String(cert.version())).arg(cit->version); return; } if (cert.digest(QCryptographicHash::Sha1) != cit->sha1) { cit->errorString = QString::fromLatin1("Downloaded certificate has SHA-1 %1, but %2 was expected") .arg(QString::fromLatin1(toHexDots(cert.digest(QCryptographicHash::Sha1))), QString::fromLatin1(toHexDots(cit->sha1))); return; } if (cert.effectiveDate().toUTC().date() != cit->effectiveDate) { qWarning() << "WARNING Certificate" << cit->name << "has effective date" << cert.effectiveDate().toString() << "expected" << cit->effectiveDate.toString(); } if (cert.expiryDate().toUTC().date() != cit->expiryDate) { qWarning() << "WARNING Certificate" << cit->name << "has effective date" << cert.expiryDate().toString() << "expected" << cit->expiryDate.toString(); } QString certName = decodeEscapedString(cert.subjectInfo(QSslCertificate::CommonName)); if (!certName.isEmpty() && certName != cit->name) { cit->name = certName; } // Certificate is valid cit->cert = cert; } void SslBundleApp::certDownloaded(QNetworkReply *reply) { parseCertificate(reply); } void SslBundleApp::downloadFinished(QNetworkReply *reply) { pendingDownloads -= reply; reply->deleteLater(); if (reply->request().attribute(QNetworkRequest::User).isValid()) certDownloaded(reply); else masterXmlDownloaded(reply); if (pendingDownloads.isEmpty()) writeAllCertificates(); } void SslBundleApp::timerEvent(QTimerEvent *) { qWarning() << "Timing out download with" << pendingDownloads.size() << "downloads pending:"; foreach (QNetworkReply *reply, pendingDownloads) qWarning() << " from" << reply->url(); pendingDownloads.clear(); writeAllCertificates(); } void SslBundleApp::addQtDefaultCaCertificates() { const QList list = QSslCertificate::fromPath(bundleFile); Certificate ours; QSslCertificate &cert = ours.cert; ours.url = QUrl::fromEncoded("qrc:/trolltech/network/ssl/qt-ca-bundle.crt"); qDebug() << "Loading" << list.size() << "certificates from Qt's current bundle"; QDateTime now = QDateTime::currentDateTime(); foreach (cert, list) { ours.sha1 = cert.digest(QCryptographicHash::Sha1); ours.name = decodeEscapedString(cert.subjectInfo(QSslCertificate::CommonName)); if (cert.expiryDate() < now) { qWarning() << "Certificate" << ours.name << toHexDots(ours.sha1) << "has expired, skipping"; continue; } if (!cert.isValid()) { qWarning() << "Certificate" << ours.name << toHexDots(ours.sha1) << "is invalid!! Skipping"; continue; } if (certificates.contains(ours.sha1)) { qWarning() << "Certificate" << ours.name << toHexDots(ours.sha1) << "is duplicated in the store. Skipping."; continue; } qDebug() << "Appending certificate from Qt's bundle:" << ours.name << toHexDots(ours.sha1); certificates.insert(ours.sha1, ours); } qDebug() << certificates.size() << "certificates loaded"; } void SslBundleApp::writeAllCertificates() { static const char boilerplate[] = "##\n" "## qt-ca-bundle.crt -- Bundle of CA Certificates\n" "##\n" "## This is a bundle of X.509 certificates of public\n" "## Certificate Authorities (CA)\n" "##\n" "## This file was last generated on: "; // tell the event loop to exit after we're done QCoreApplication::instance()->quit(); // Sort the certificates by name QMap sortedCertificates; QDateTime now = QDateTime::currentDateTime(); qDebug() << "Sorting" << certificates.size() << "certificates"; foreach (const Certificate &cert, certificates) { if (!cert.cert.isValid()) { qDebug() << "SKIPPED: Certificate" << cert.name << "from" << cert.url << cert.errorString; continue; } if (cert.cert.expiryDate() < now) { qDebug() << "SKIPPED: Certificate" << cert.name << "from" << cert.url << "expired on" << cert.cert.expiryDate(); continue; } QByteArray key = cert.name.toUtf8(); key += '\0'; key += cert.sha1; sortedCertificates.insert(key, cert); } qDebug() << sortedCertificates.size() << "certificates remaining after sorting"; QFile f(bundleFile); if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) { qWarning() << "Could not save to file" << bundleFile << ":" << f.errorString(); return; } QTextStream s(&f); s.setCodec("UTF-8"); // write boilerplate s << boilerplate << now.toUTC().toString(Qt::ISODate) << endl << "## Number of certificates on this file: " << sortedCertificates.size() << endl << endl; foreach (const Certificate &cert, sortedCertificates) { s << "# Certificate for: " << cert.name << endl; s << "# Source: " << cert.url.toEncoded() << endl; s << "# Data:" << endl; s << "# Version: " << cert.cert.version() << endl; s << "# Serial number: " << cert.cert.serialNumber() << endl; s << "# Subject: CN=" << decodeEscapedString(cert.cert.subjectInfo(QSslCertificate::CommonName)) << endl << "# C=" << decodeEscapedString(cert.cert.subjectInfo(QSslCertificate::CountryName)) << ", ST=" << decodeEscapedString(cert.cert.subjectInfo(QSslCertificate::StateOrProvinceName)) << ", L=" << decodeEscapedString(cert.cert.subjectInfo(QSslCertificate::LocalityName)) << ", O=" << decodeEscapedString(cert.cert.subjectInfo(QSslCertificate::Organization)) << ", OU=" << decodeEscapedString(cert.cert.subjectInfo(QSslCertificate::OrganizationalUnitName)) << endl; s << "# Issuer: CN=" << decodeEscapedString(cert.cert.issuerInfo(QSslCertificate::CommonName)) << endl << "# C=" << decodeEscapedString(cert.cert.issuerInfo(QSslCertificate::CountryName)) << ", ST=" << decodeEscapedString(cert.cert.issuerInfo(QSslCertificate::StateOrProvinceName)) << ", L=" << decodeEscapedString(cert.cert.issuerInfo(QSslCertificate::LocalityName)) << ", O=" << decodeEscapedString(cert.cert.issuerInfo(QSslCertificate::Organization)) << ", OU=" << decodeEscapedString(cert.cert.issuerInfo(QSslCertificate::OrganizationalUnitName)) << endl; s << "# SHA-1 checksum: " << toHexDots(cert.cert.digest(QCryptographicHash::Sha1)) << endl; s << "# Effective: " << cert.cert.effectiveDate().toUTC().toString(Qt::ISODate) << endl; s << "# Expires: " << cert.cert.expiryDate().toUTC().toString(Qt::ISODate) << endl; s << "# Public key size: " << cert.cert.publicKey().length() << " bits" << endl; s << cert.cert.toPem() << endl; } } int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); SslBundleApp app; return a.exec(); } #include "main.moc" --Boundary-01=_NF1YLRx4cNyoxtf-- --nextPart4241901.rcaI4tOsEf Content-Type: application/pgp-signature; name=signature.asc Content-Description: This is a digitally signed message part. -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.10 (GNU/Linux) iD8DBQBLY1FNM/XwBW70U1gRAs/5AJ9lVKXiP7u4HJ0ACAk92KBZkiTgpACeOiY2 FMLV/Kb9/xntUkq2cZIqDdc= =M1W1 -----END PGP SIGNATURE----- --nextPart4241901.rcaI4tOsEf--