You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
343 lines
13 KiB
343 lines
13 KiB
/*************************************************************************** |
|
* Copyright (C) 2018 by Chinmoy Ranjan Pradhan <chinmoyrp65@gmail.com> * |
|
* * |
|
* This program is free software; you can redistribute it and/or modify * |
|
* it under the terms of the GNU General Public License as published by * |
|
* the Free Software Foundation; either version 2 of the License, or * |
|
* (at your option) any later version. * |
|
***************************************************************************/ |
|
|
|
#include "certificateviewer.h" |
|
|
|
#include <KMessageBox> |
|
#include <KLocalizedString> |
|
#include <KColumnResizer> |
|
|
|
#include <QDebug> |
|
#include <QLabel> |
|
#include <QTextEdit> |
|
#include <QTreeView> |
|
#include <QGroupBox> |
|
#include <QFormLayout> |
|
#include <QPushButton> |
|
#include <QVBoxLayout> |
|
#include <QFileDialog> |
|
#include <QDialogButtonBox> |
|
#include <QCryptographicHash> |
|
|
|
#include "signatureguiutils.h" |
|
|
|
// DN (DistinguishedName) attributes can be |
|
// C Country |
|
// CN Common name |
|
// DC Domain component |
|
// E E-mail address |
|
// EMAIL E-mail address (preferred) |
|
// EMAILADDRESS E-mail address |
|
// L Locality |
|
// O Organization name |
|
// OU Organizational unit name |
|
// PC Postal code |
|
// S State or province |
|
// SN Family name |
|
// SP State or province |
|
// ST State or province (preferred) |
|
// STREET Street |
|
// T Title |
|
|
|
|
|
// CN=James Hacker, |
|
// L=Basingstoke, |
|
// O=Widget Inc, |
|
// C=GB |
|
// CN=L. Eagle, O="Sue, Grabbit and Runn", C=GB |
|
// CN=L. Eagle, O=Sue\, Grabbit and Runn, C=GB |
|
|
|
|
|
// This is a poor man's attempt at parsing DN, if it fails it is not a problem since it's only for display in a list |
|
static QString splitDNAttributes(const QStringList &text) |
|
{ |
|
const QStringList attributes = { "C", "CN", "DC", "E", "EMAIL", "EMAILADDRESS", "L", "O", "OU", "PC", "S", "SN", "SP", "ST", "STREET", "T" }; |
|
|
|
for ( const QString &t : text ) |
|
{ |
|
for ( const QString &attribute : attributes ) |
|
{ |
|
const QRegularExpression re( QStringLiteral( "(.*),\\s*(%1=.*)" ).arg( attribute ), QRegularExpression::DotMatchesEverythingOption ); |
|
const QRegularExpressionMatch match = re.match( t ); |
|
if ( match.hasMatch() ) |
|
{ |
|
QStringList results = text; |
|
const int index = results.indexOf( t ); |
|
results.removeAt( index ); |
|
results.insert( index, match.captured( 2 ) ); |
|
results.insert( index, match.captured( 1 ) ); |
|
return splitDNAttributes( results ); |
|
} |
|
} |
|
} |
|
|
|
// Clean escaped commas |
|
QStringList result = text; |
|
for ( QString &t : result ) |
|
{ |
|
t.replace( QLatin1String("\\,"), QLatin1String(",") ); |
|
} |
|
|
|
// Clean up quoted attributes |
|
for ( QString &t : result ) |
|
{ |
|
for ( const QString &attribute : attributes ) |
|
{ |
|
const QRegularExpression re( QStringLiteral( "%1=\"(.*)\"" ).arg( attribute ) ); |
|
const QRegularExpressionMatch match = re.match( t ); |
|
if ( match.hasMatch() ) |
|
{ |
|
t = attribute + "=" + match.captured( 1 ); |
|
} |
|
} |
|
} |
|
|
|
return result.join(QStringLiteral("\n")); |
|
} |
|
|
|
static QString splitDNAttributes(const QString &text) |
|
{ |
|
return splitDNAttributes( QStringList{ text } ); |
|
} |
|
|
|
|
|
CertificateModel::CertificateModel( const Okular::CertificateInfo &certInfo, QObject * parent ) |
|
: QAbstractTableModel( parent ), m_certificateInfo( certInfo ) |
|
{ |
|
m_certificateProperties = { Version, SerialNumber, Issuer, IssuedOn, ExpiresOn, Subject, PublicKey, KeyUsage }; |
|
} |
|
|
|
|
|
int CertificateModel::columnCount( const QModelIndex & ) const |
|
{ |
|
return 2; |
|
} |
|
|
|
int CertificateModel::rowCount( const QModelIndex & ) const |
|
{ |
|
return m_certificateProperties.size(); |
|
} |
|
|
|
static QString propertyVisibleName( CertificateModel::Property p ) |
|
{ |
|
switch ( p ) |
|
{ |
|
case CertificateModel::Version: |
|
return i18n("Version"); |
|
case CertificateModel::SerialNumber: |
|
return i18n("Serial Number"); |
|
case CertificateModel::Issuer: |
|
return i18n("Issuer"); |
|
case CertificateModel::IssuedOn: |
|
return i18n("Issued On"); |
|
case CertificateModel::ExpiresOn: |
|
return i18n("Expires On"); |
|
case CertificateModel::Subject: |
|
return i18nc("The person/company that made the signature", "Subject"); |
|
case CertificateModel::PublicKey: |
|
return i18n("Public Key"); |
|
case CertificateModel::KeyUsage: |
|
return i18n("Key Usage"); |
|
} |
|
return QString(); |
|
} |
|
|
|
static QString propertyVisibleValue( CertificateModel::Property p, const Okular::CertificateInfo &certInfo ) |
|
{ |
|
switch ( p ) |
|
{ |
|
case CertificateModel::Version: |
|
return i18n("V%1", QString::number( certInfo.version() )); |
|
case CertificateModel::SerialNumber: |
|
return certInfo.serialNumber().toHex(' '); |
|
case CertificateModel::Issuer: |
|
return certInfo.issuerInfo(Okular::CertificateInfo::DistinguishedName); |
|
case CertificateModel::IssuedOn: |
|
return certInfo.validityStart().toString( Qt::DefaultLocaleLongDate ); |
|
case CertificateModel::ExpiresOn: |
|
return certInfo.validityEnd().toString( Qt::DefaultLocaleLongDate ); |
|
case CertificateModel::Subject: |
|
return certInfo.subjectInfo(Okular::CertificateInfo::DistinguishedName); |
|
case CertificateModel::PublicKey: |
|
return i18n("%1 (%2 bits)", SignatureGuiUtils::getReadablePublicKeyType( certInfo.publicKeyType() ), |
|
certInfo.publicKeyStrength()); |
|
case CertificateModel::KeyUsage: |
|
return SignatureGuiUtils::getReadableKeyUsageCommaSeparated( certInfo.keyUsageExtensions() ); |
|
} |
|
return QString(); |
|
} |
|
|
|
QVariant CertificateModel::data( const QModelIndex &index, int role ) const |
|
{ |
|
const int row = index.row(); |
|
if ( !index.isValid() || row < 0 || row >= m_certificateProperties.count() ) |
|
return QVariant(); |
|
|
|
switch ( role ) |
|
{ |
|
case Qt::DisplayRole: |
|
case Qt::ToolTipRole: |
|
switch ( index.column() ) |
|
{ |
|
case 0: |
|
return propertyVisibleName( m_certificateProperties[row] ); |
|
case 1: |
|
return propertyVisibleValue( m_certificateProperties[row], m_certificateInfo ); |
|
default: |
|
return QString(); |
|
} |
|
case PropertyKeyRole: |
|
return m_certificateProperties[row]; |
|
case PropertyVisibleValueRole: |
|
return propertyVisibleValue( m_certificateProperties[row], m_certificateInfo ); |
|
} |
|
|
|
return QVariant(); |
|
} |
|
|
|
QVariant CertificateModel::headerData( int section, Qt::Orientation orientation, int role ) const |
|
{ |
|
if ( role == Qt::TextAlignmentRole ) |
|
return QVariant( Qt::AlignLeft ); |
|
|
|
if ( orientation != Qt::Horizontal || role != Qt::DisplayRole) |
|
return QVariant(); |
|
|
|
switch ( section ) |
|
{ |
|
case 0: |
|
return i18n("Property"); |
|
case 1: |
|
return i18n("Value"); |
|
default: |
|
return QVariant(); |
|
} |
|
} |
|
|
|
CertificateViewer::CertificateViewer( const Okular::CertificateInfo &certInfo, QWidget *parent ) |
|
: KPageDialog( parent ), m_certificateInfo( certInfo ) |
|
{ |
|
setModal( true ); |
|
setMinimumSize( QSize( 500, 500 )); |
|
setFaceType( Tabbed ); |
|
setWindowTitle( i18n("Certificate Viewer") ); |
|
setStandardButtons( QDialogButtonBox::Close ); |
|
|
|
auto exportBtn = new QPushButton( i18n("Export...") ); |
|
connect( exportBtn, &QPushButton::clicked, this, &CertificateViewer::exportCertificate ); |
|
addActionButton( exportBtn ); |
|
|
|
// General tab |
|
auto generalPage = new QFrame( this ); |
|
addPage( generalPage, i18n("General") ); |
|
|
|
auto issuerBox = new QGroupBox( i18n("Issued By"), generalPage ); |
|
auto issuerFormLayout = new QFormLayout( issuerBox ); |
|
issuerFormLayout->setLabelAlignment( Qt::AlignLeft ); |
|
issuerFormLayout->addRow( i18n("Common Name(CN)"), new QLabel( m_certificateInfo.issuerInfo( Okular::CertificateInfo::CommonName ) ) ); |
|
issuerFormLayout->addRow( i18n("EMail"), new QLabel( m_certificateInfo.issuerInfo( Okular::CertificateInfo::EmailAddress ) ) ); |
|
issuerFormLayout->addRow( i18n("Organization(O)"), new QLabel( m_certificateInfo.issuerInfo( Okular::CertificateInfo::Organization ) ) ); |
|
|
|
auto subjectBox = new QGroupBox( i18n("Issued To"), generalPage ); |
|
auto subjectFormLayout = new QFormLayout( subjectBox ); |
|
subjectFormLayout->setLabelAlignment( Qt::AlignLeft ); |
|
subjectFormLayout->addRow( i18n("Common Name(CN)"), new QLabel( m_certificateInfo.subjectInfo( Okular::CertificateInfo::CommonName ) ) ); |
|
subjectFormLayout->addRow( i18n("EMail"), new QLabel( m_certificateInfo.subjectInfo( Okular::CertificateInfo::EmailAddress ) ) ); |
|
subjectFormLayout->addRow( i18n("Organization(O)"), new QLabel( m_certificateInfo.subjectInfo( Okular::CertificateInfo::Organization ) ) ); |
|
|
|
auto validityBox = new QGroupBox( i18n("Validity"), generalPage ); |
|
auto validityFormLayout = new QFormLayout( validityBox ); |
|
validityFormLayout->setLabelAlignment( Qt::AlignLeft ); |
|
validityFormLayout->addRow( i18n("Issued On"), new QLabel( m_certificateInfo.validityStart().toString( Qt::DefaultLocaleLongDate ) ) ); |
|
validityFormLayout->addRow( i18n("Expires On"), new QLabel( m_certificateInfo.validityEnd().toString( Qt::DefaultLocaleLongDate ) ) ); |
|
|
|
auto fingerprintBox = new QGroupBox( i18n("Fingerprints"), generalPage ); |
|
auto fingerprintFormLayout = new QFormLayout( fingerprintBox ); |
|
fingerprintFormLayout->setLabelAlignment( Qt::AlignLeft ); |
|
QByteArray certData = m_certificateInfo.certificateData(); |
|
auto sha1Label = new QLabel( QString( QCryptographicHash::hash( certData, QCryptographicHash::Sha1 ).toHex(' ') ) ); |
|
sha1Label->setWordWrap( true ); |
|
auto sha256Label = new QLabel( QString( QCryptographicHash::hash( certData, QCryptographicHash::Sha256 ).toHex(' ') ) ); |
|
sha256Label->setWordWrap( true ); |
|
fingerprintFormLayout->addRow( i18n("SHA-1 Fingerprint"), sha1Label ); |
|
fingerprintFormLayout->addRow( i18n("SHA-256 Fingerprint"), sha256Label ); |
|
|
|
auto generalPageLayout = new QVBoxLayout( generalPage ); |
|
generalPageLayout->addWidget( issuerBox ); |
|
generalPageLayout->addWidget( subjectBox ); |
|
generalPageLayout->addWidget( validityBox ); |
|
generalPageLayout->addWidget( fingerprintBox ); |
|
generalPageLayout->addStretch(); |
|
|
|
//force column 1 to have same width |
|
auto resizer = new KColumnResizer( this ); |
|
resizer->addWidgetsFromLayout( issuerBox->layout(), 0 ); |
|
resizer->addWidgetsFromLayout( subjectBox->layout(), 0 ); |
|
resizer->addWidgetsFromLayout( validityBox->layout(), 0 ); |
|
resizer->addWidgetsFromLayout( fingerprintBox->layout(), 0 ); |
|
|
|
// Details tab |
|
auto detailsFrame = new QFrame( this ); |
|
addPage( detailsFrame, i18n("Details") ); |
|
auto certDataLabel = new QLabel( i18n("Certificate Data:") ); |
|
auto certTree = new QTreeView( this ); |
|
certTree->setIndentation( 0 ); |
|
m_certificateModel = new CertificateModel( m_certificateInfo, this ); |
|
certTree->setModel( m_certificateModel ); |
|
connect( certTree->selectionModel(), &QItemSelectionModel::currentChanged, this, &CertificateViewer::updateText ); |
|
m_propertyText = new QTextEdit( this ); |
|
m_propertyText->setReadOnly( true ); |
|
|
|
auto detailsPageLayout = new QVBoxLayout( detailsFrame ); |
|
detailsPageLayout->addWidget( certDataLabel ); |
|
detailsPageLayout->addWidget( certTree ); |
|
detailsPageLayout->addWidget( m_propertyText ); |
|
} |
|
|
|
void CertificateViewer::updateText( const QModelIndex &index ) |
|
{ |
|
QString text; |
|
const CertificateModel::Property key = m_certificateModel->data( index, CertificateModel::PropertyKeyRole ).value<CertificateModel::Property>(); |
|
switch ( key ) |
|
{ |
|
case CertificateModel::SerialNumber: |
|
case CertificateModel::Version: |
|
case CertificateModel::IssuedOn: |
|
case CertificateModel::ExpiresOn: |
|
text = m_certificateModel->data( index, CertificateModel::PropertyVisibleValueRole ).toString(); |
|
break; |
|
case CertificateModel::Issuer: |
|
case CertificateModel::Subject: |
|
text = splitDNAttributes( m_certificateModel->data( index, CertificateModel::PropertyVisibleValueRole ).toString() ); |
|
break; |
|
case CertificateModel::PublicKey: |
|
text = m_certificateInfo.publicKey().toHex(' '); |
|
break; |
|
case CertificateModel::KeyUsage: |
|
text = SignatureGuiUtils::getReadableKeyUsageNewLineSeparated( m_certificateInfo.keyUsageExtensions() ); |
|
break; |
|
} |
|
m_propertyText->setText( text ); |
|
} |
|
|
|
void CertificateViewer::exportCertificate() |
|
{ |
|
const QString caption = i18n("Where do you want to save this certificate?"); |
|
const QString path = QFileDialog::getSaveFileName( this, caption, QStringLiteral("Certificate.cer"), i18n("Certificate File (*.cer)") ); |
|
if ( !path.isEmpty() ) |
|
{ |
|
QFile targetFile( path ); |
|
targetFile.open( QIODevice::WriteOnly ); |
|
if ( targetFile.write( m_certificateInfo.certificateData() ) == -1 ) |
|
{ |
|
KMessageBox::error( this, i18n("Unable to export certificate!") ); |
|
} |
|
targetFile.close(); |
|
} |
|
}
|
|
|