view ui/certificate.cpp @ 1310:60e481aa75ca

(issue152) Do not return CryptUIDlgViewContext's return value The return value is false if the user cancels the dialog. But as the certificate has been shown this is not really an error.
author Andre Heinecke <andre.heinecke@intevation.de>
date Mon, 13 Oct 2014 14:13:05 +0200
parents c2fd36cd4093
children
line wrap: on
line source
/* Copyright (C) 2014 by Bundesamt für Sicherheit in der Informationstechnik
 * Software engineering by Intevation GmbH
 *
 * This file is Free Software under the GNU GPL (v>=2)
 * and comes with ABSOLUTELY NO WARRANTY!
 * See LICENSE.txt for details.
 */
#include "certificate.h"
#include <QDebug>
#include <QFile>
#include <QStringList>
#include <QObject>
#include <QProcess>
#include <QTemporaryFile>

#include <polarssl/sha1.h>

#include "certhelp.h"
#include "listutil.h"

#ifdef WIN32
#include <cryptuiapi.h>
#endif

/* Qt wrapper around certhelp functions. */
QString getX509Value(x509_name *namebuf, unsigned char *oid) {
    QString retval;
    char * buf = get_oid_valstr(namebuf, oid);
    if (buf == NULL) {
        return retval;
    }
    retval = QString::fromUtf8(buf, -1);
    free(buf);
    return retval;
}

void Certificate::parseDetails(const QByteArray& cert) {
    x509_crt chain;

    x509_crt_init(&chain);
    if (x509_crt_parse_der(&chain, (const unsigned char *)cert.data(),
                cert.size()) != 0) {
        qDebug() << "Failed to parse cert..";
        return;
    }

    mValidFrom = QDateTime(QDate(chain.valid_from.year,
                                 chain.valid_from.mon,
                                 chain.valid_from.day),
                           QTime(chain.valid_from.hour,
                                 chain.valid_from.min,
                                 chain.valid_from.sec));

    mValidTo = QDateTime(QDate(chain.valid_to.year,
                               chain.valid_to.mon,
                               chain.valid_to.day),
                         QTime(chain.valid_to.hour,
                               chain.valid_to.min,
                               chain.valid_to.sec));

    mSubjectCN = getX509Value(&(chain.subject), CERT_OID_CN);
    mSubjectOU = getX509Value(&(chain.subject), CERT_OID_OU);
    mSubjectO = getX509Value(&(chain.subject), CERT_OID_O);
    mSubjectSN = getX509Value(&(chain.subject), CERT_OID_SN);

    /* Calculate sha1 fingerprint */
    unsigned char sha1sum[20];
    sha1(chain.raw.p, chain.raw.len, sha1sum);
    for (int i=0; i < 20; i++) {
        mFingerprint += QString("%1").arg(sha1sum[i], 0, 16).rightJustified(2, '0');
        if (i != 19) {
            mFingerprint += ":";
        }
        mFingerprint = mFingerprint.toUpper();
    }

    x509_crt_free(&chain);

    mDetails = QObject::tr("Certificate:\n"
            "    <bold>%1</bold>\n"
            "    %2, %3\n\n"
            "Serial number:\n"
            "%4\n"
            "Valid from: <bold>%5</bold> to <bold>%6</bold>\n\n"
            "Issued by: ..")
            .arg(mSubjectCN)
            .arg(mSubjectO)
            .arg(mSubjectOU)
            .arg(mSubjectSN)
            .arg(QLocale::system().toString(mValidFrom))
            .arg(QLocale::system().toString(mValidTo));

}

Certificate::Certificate(const QByteArray& derData) :
    mValid(false),
    mEditable(false)
{
    if (derData.isEmpty()) {
        return;
    }

    parseDetails(derData);

    mValid = !mSubjectCN.isEmpty();

    /* Default is installation for new certificates */
    mBaseLine = QString::fromLatin1("I:") + derData.toBase64();
}

Certificate::Certificate(const QString& b64Line) :
    mValid(false),
    mEditable(false)
{
    if (b64Line.isEmpty()) {
        return;
    }

    /* Cut of the first two chars (e.g. I: and decode) */
    QByteArray derData = QByteArray::fromBase64(
            b64Line.right(b64Line.size() - 2).toLatin1());

    parseDetails(derData);

    /* If the subject CN is set then at least one x509parse
     * in polarssl was successfull. And a root certificate
     * always needs to have a subject CN */
    mValid = !mSubjectCN.isEmpty();

    mBaseLine = b64Line;
}

QString Certificate::shortDescription() const {
    if (!isValid()) {
        return QObject::tr("Failed to parse certificate");
    }

    QString ret = mSubjectCN; /* Necessary by definition */
    /*
    if (!mSubjectO.isEmpty()) {
        ret += " - " + mSubjectO;
    }
    */
    return ret;
}

QList<Certificate> Certificate::fromFileName(const QString& file_name) {
    /* We read the file using Qt to avoid filename encoding problems
     * on Windows */

    QFile certificateFile(file_name);
    QByteArray fileContent;
    QList<Certificate> retval;
    x509_crt chain;
    int ret = 0;
    if (!certificateFile.open(QIODevice::ReadOnly)) {
        qDebug() << "Failed to read file.";
        return retval;
    }

    if (certificateFile.size() > MAX_LINE_LENGTH * MAX_LINES) {
        qDebug() << "File too large";
        return retval;
    }

    fileContent = certificateFile.readAll();

    x509_crt_init(&chain);

    ret = x509_crt_parse(&chain,
            reinterpret_cast<const unsigned char*>(fileContent.constData()),
            fileContent.size());

    if (ret < 0) {
        qDebug() << "Failed to parse certificates.";
        return retval;
    }

    if (ret > 0) {
        qDebug() << "Some certificates could not be parsed.";
    }

    x509_crt *iter = &chain;

    while (iter) {
        QByteArray derData(reinterpret_cast<const char*>(iter->raw.p),
                static_cast<int>(iter->raw.len));
        retval << Certificate(derData);
        iter = iter->next;
    }
    x509_crt_free(&chain);

    return retval;
}

void Certificate::setInstallCert(bool install)
{
    if (install && mBaseLine.startsWith("R:")) {
        mBaseLine.replace(0, 1, "I");
    }
    else if (!install && mBaseLine.startsWith("I:")) {
        mBaseLine.replace(0, 1, "R");
    }
}

#ifdef WIN32
bool Certificate::showNativeUI(void *parent)
{
    /* Cut of the first two chars (e.g. I: and decode) */
    bool retval = false;
    QByteArray pemData = QByteArray(
            mBaseLine.right(mBaseLine.size() - 2).toLatin1());
    PCCERT_CONTEXT pCert = b64_to_cert_context (pemData.data(), pemData.size());
    typedef BOOL (CALLBACK* LPFNVIEWDLG)(DWORD,const void *,HWND,LPCWSTR,DWORD,void *);
    LPFNVIEWDLG funcPtr;

    /* CryptUIDlgViewContext is not part of mingw 3.1.0
     * so we workaround this by geting the process address dynamically. */
    HMODULE hmod = LoadLibraryW(L"cryptui");

    if (!hmod) {
        qDebug() << "Failed to open Cryptui.dll";
        goto done;
    }

    funcPtr = (LPFNVIEWDLG) GetProcAddress (hmod, "CryptUIDlgViewContext");
    if (!funcPtr) {
        qDebug() << "Failed to find Address of CryptUIDlgViewContext";
        goto done;
    }

    if (pCert == NULL) {
        qDebug() << "Failed to parse certificate.";
        goto done;
    }

    funcPtr(CERT_STORE_CERTIFICATE_CONTEXT,
                 pCert,
                 (HWND) parent,
                 NULL, // Default Title
                 0,
                 NULL);

    retval = true;
done:

    if (pCert) {
        CertFreeCertificateContext(pCert);
    }
    if (hmod) {
        FreeLibrary(hmod);
    }
    return retval;
}
#else
bool Certificate::showNativeUI(void *parent)
{
    QTemporaryFile *tmpCert = new QTemporaryFile;
    tmpCert->open();
    tmpCert->write("-----BEGIN CERTIFICATE-----\n");
    tmpCert->write(mBaseLine.right(mBaseLine.size() - 2).toLatin1());
    tmpCert->write("-----END CERTIFICATE-----\n");
    tmpCert->close();
    QStringList args;
    args << tmpCert->fileName();
    QProcess *viewer = new QProcess();
    viewer->setProgram("gcr-viewer");
    viewer->setArguments(args);

    QObject::connect (viewer, SIGNAL(finished(int, QProcess::ExitStatus)), tmpCert, SLOT(deleteLater()));
    QObject::connect (viewer, SIGNAL(finished(int, QProcess::ExitStatus)), viewer, SLOT(deleteLater()));
    viewer->start();
    return !(!viewer->waitForStarted() || viewer->state() == QProcess::NotRunning);
}
#endif

http://wald.intevation.org/projects/trustbridge/