[prev in list] [next in list] [prev in thread] [next in thread]
List: kde-core-devel
Subject: Re: Using system SSL certificates...
From: Thiago Macieira <thiago () kde ! org>
Date: 2010-01-29 21:21:17
Message-ID: 201001292221.17673.thiago () kde ! org
[Download RAW message or body]
[Attachment #2 (multipart/mixed)]
Em Sexta-feira 29. Janeiro 2010, ās 21.53.55, Andreas Hartmetz escreveu:
> On Friday 29 January 2010 14:30:37 Thiago Macieira wrote:
> > Em Sexta-feira 29 Janeiro 2010, ās 14:04:06, Pierre Schmitz escreveu:
> > > > I've already made a script to do that. Actually, a Qt program.
> > > >
> > > > I'll probably update Qt's certificate list with the Firefox ones for
> > > > the next Qt version.
> > > >
> > > > So all KDE has to do is stop overriding Qt's default certificate
> > > > bundle.
> > >
> > > I would appreciate if KDE and Qt would use the system wide cert bundle
> > > (optionally configurable at build time).
> >
> > The only thing that's holding me back in updating the Qt certificates is
> > to decide whether keeping expired certificates is a good thing.
> >
> > There are 81 certificates in Qt's bundle. One of them is repeated, so 80
> > are unique.
> >
> > However, from those 80, 8 have expired already.
> >
> > 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 are
> > added, the total increases to 120.
>
> 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
http://www.mozilla.org/projects/security/certs/included/. That's the only
resource I found in Mozilla. If there are more certificates, it would be nice
to know about them.
The Qt non-Firefox certificates contain the likes of VeriSign, Thawte and
Equifax. The question is: why are those well-known certificates in Qt but not
in Firefox?
--
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
["main.cpp" (text/x-c++src)]
#include <QtCore/QCoreApplication>
#include <QtCore/QDate>
#include <QtCore/QDebug>
#include <QtCore/QDir>
#include <QtCore/QFile>
#include <QtCore/QProcessEnvironment>
#include <QtCore/QMap>
#include <QtCore/QSet>
#include <QtCore/QUrl>
#include <QtCore/QXmlStreamReader>
#include <QtNetwork/QNetworkAccessManager>
#include <QtNetwork/QNetworkRequest>
#include <QtNetwork/QNetworkReply>
#include <QtNetwork/QSslCertificate>
#include <QtNetwork/QSslConfiguration>
#include <QtNetwork/QSslKey>
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<QByteArray, Certificate> Certificates;
QString bundleFile;
QNetworkAccessManager manager;
QSet<QNetworkReply *> 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<QSslCertificate> 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<QByteArray, Certificate> 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"
["signature.asc" (application/pgp-signature)]
[prev in list] [next in list] [prev in thread] [next in thread]
Configure |
About |
News |
Add a list |
Sponsored by KoreLogic