From 1bc340a0efbc8f92fcbc66dbac4f380ad892f584 Mon Sep 17 00:00:00 2001 From: Christian Hohnstaedt Date: Tue, 12 Mar 2019 21:31:07 +0100 Subject: [PATCH] Close #104: Also show sha256 digests of public keys Print digest of public keys in the key details in different formats: Print SHA256 SSH digest as used to by SSH users: ssh-keygen -l -f ~/.ssh/id_rsa.pub Print SHA1 X509 key digest as shown in the Subject key identifier of a certificate Print SHA256 digest as in: openssl pkey -pubout -outform DER < key.pem | sha256sum as requested by this issue. Refactored digesting functionality by a generic Digest() function working with QByteArrays. The function formatHash() now also expects a QByteArray input. --- lib/func.cpp | 27 +++++++++++++++----- lib/func.h | 6 ++++- lib/pki_base.cpp | 5 +--- lib/pki_evp.cpp | 56 ++++++------------------------------------ lib/pki_key.cpp | 52 ++++++++++++++++++++++++++++++++++++++- lib/pki_key.h | 4 ++- lib/pki_x509.cpp | 8 +----- lib/settings.cpp | 2 ++ ui/CertDetail.ui | 40 ++++++++++++++++++++++++++++-- ui/KeyDetail.ui | 11 ++++++--- widgets/KeyDetail.cpp | 26 ++++++++++++++++++++ widgets/KeyDetail.h | 2 ++ widgets/clicklabel.cpp | 4 +-- 13 files changed, 166 insertions(+), 77 deletions(-) diff --git a/lib/func.cpp b/lib/func.cpp index b0276b26..775cf7fb 100644 --- a/lib/func.cpp +++ b/lib/func.cpp @@ -10,6 +10,7 @@ #include "func.h" #include "exception.h" #include "lib/asn1time.h" +#include "lib/settings.h" #include "widgets/validity.h" #include "widgets/XcaWarning.h" #include @@ -485,13 +486,27 @@ bool _ign_openssl_error(const QString txt, const char *file, int line) return !errtxt.isEmpty(); } -QString formatHash(const unsigned char *md, unsigned size, bool colon) +QString formatHash(const QByteArray &data, QString sep, int width) { - QString s, t; - for (unsigned j = 0; j < size; j++) - s += t.sprintf("%02X%s", md[j], - (j+1 == size) || !colon ? "" : ":"); - return s; + return QString(data.toHex()).toUpper() + .replace(QRegExp(QString("(.{%1})(?=.)").arg(width)), + QString("\\1") + sep); +} + +QByteArray Digest(const QByteArray &data, const EVP_MD *type) +{ + unsigned int n; + unsigned char m[EVP_MAX_MD_SIZE]; + + EVP_Digest(data.constData(), data.size(), m, &n, type, NULL); + openssl_error(); + return QByteArray((char*)m, (int)n); +} + +QString fingerprint(const QByteArray &data, const EVP_MD *type) +{ + return formatHash(Digest(data, type), + Settings["fp_separator"], Settings["fp_digits"]); } void inc_progress_bar(int, int, void *p) diff --git a/lib/func.h b/lib/func.h index 72e3ea3b..6f8cea0c 100644 --- a/lib/func.h +++ b/lib/func.h @@ -16,6 +16,7 @@ #include "base.h" #include #include +#include class Validity; extern QString currentDB; @@ -31,7 +32,7 @@ QString getFullFilename(const QString &filename, const QString &selectedFilter); QStringList getLibExtensions(); QString hostId(); -QString formatHash(const unsigned char *md, unsigned size, bool colon = true); +QString formatHash(const QByteArray &data, QString sep = ":", int width = 2); QByteArray filename2bytearray(const QString &fname); QString filename2QString(const char *fname); QString compressFilename(QString filename, int maxlen = 50); @@ -39,6 +40,9 @@ QString compressFilename(QString filename, int maxlen = 50); QString asn1ToQString(const ASN1_STRING *str, bool quote = false); ASN1_STRING *QStringToAsn1(QString s, int nid); +QByteArray Digest(const QByteArray &data, const EVP_MD *type); +QString fingerprint(const QByteArray &data, const EVP_MD *type); + const char *OBJ_ln2sn(const char *ln); const char *OBJ_sn2ln(const char *sn); const char *OBJ_obj2sn(ASN1_OBJECT *a); diff --git a/lib/pki_base.cpp b/lib/pki_base.cpp index a82d810c..03564cfc 100644 --- a/lib/pki_base.cpp +++ b/lib/pki_base.cpp @@ -403,10 +403,7 @@ static QString icsValue(QString s) QStringList pki_base::icsVEVENT(const a1time &expires, const QString &summary, const QString &description) const { - QByteArray ba = i2d(); - unsigned char md[MD5_DIGEST_LENGTH]; - MD5((const unsigned char *)ba.constData(), ba.length(), md); - QString uniqueid = formatHash(md, MD5_DIGEST_LENGTH, false); + QString uniqueid = formatHash(Digest(i2d(), EVP_sha1()), ""); QString desc = icsValue(description + "\n----------\n" + comment); QString alarm = Settings["ical_expiry"]; return QStringList() << diff --git a/lib/pki_evp.cpp b/lib/pki_evp.cpp index 8a8fe5bc..7416a1e1 100644 --- a/lib/pki_evp.cpp +++ b/lib/pki_evp.cpp @@ -834,63 +834,21 @@ QVariant pki_evp::getIcon(const dbheader *hd) const QString pki_evp::md5passwd(QByteArray pass) { - -#if OPENSSL_VERSION_NUMBER < 0x10100000L - EVP_MD_CTX mdctxbuf; -#endif - EVP_MD_CTX *mdctx; - int n; - unsigned char m[EVP_MAX_MD_SIZE]; - -#if OPENSSL_VERSION_NUMBER >= 0x10100000L - mdctx = EVP_MD_CTX_new(); -#else - mdctx = &mdctxbuf; -#endif - - EVP_DigestInit(mdctx, EVP_md5()); - EVP_DigestUpdate(mdctx, pass.constData(), pass.size()); - EVP_DigestFinal(mdctx, m, (unsigned*)&n); - -#if OPENSSL_VERSION_NUMBER >= 0x10100000L - EVP_MD_CTX_free(mdctx); -#endif - return formatHash(m, n); + return formatHash(Digest(pass, EVP_md5())); } QString pki_evp::_sha512passwd(QByteArray pass, QString salt, int size, int repeat) { -#if OPENSSL_VERSION_NUMBER < 0x10100000L - EVP_MD_CTX mdctxbuf; -#endif - EVP_MD_CTX *mdctx; - QString str; - int n; - unsigned char m[EVP_MAX_MD_SIZE]; + Q_ASSERT(salt.length() >= size); - if (salt.length() < size) { - abort(); - } - str = salt.left(size); - pass = str.toLatin1() + pass; + salt = salt.left(size); + pass = salt.toLatin1() + pass; -#if OPENSSL_VERSION_NUMBER >= 0x10100000L - mdctx = EVP_MD_CTX_new(); -#else - mdctx = &mdctxbuf; -#endif - while (repeat--) { - EVP_DigestInit(mdctx, EVP_sha512()); - EVP_DigestUpdate(mdctx, pass.constData(), pass.size()); - EVP_DigestFinal(mdctx, m, (unsigned*)&n); - pass = QByteArray((char*)m, n); + while (repeat--) + pass = Digest(pass, EVP_sha512()); - } -#if OPENSSL_VERSION_NUMBER >= 0x10100000L - EVP_MD_CTX_free(mdctx); -#endif - return str + formatHash(m, n, false); + return salt + formatHash(pass, ""); } QString pki_evp::sha512passwd(QByteArray pass, QString salt) diff --git a/lib/pki_key.cpp b/lib/pki_key.cpp index d1e9d41f..5130323a 100644 --- a/lib/pki_key.cpp +++ b/lib/pki_key.cpp @@ -622,7 +622,7 @@ void pki_key::ssh_key_bn2data(const BIGNUM *bn, QByteArray *data) ssh_key_QBA2data(big, data); } -QByteArray pki_key::SSH2publicQByteArray() +QByteArray pki_key::SSH2publicQByteArray(bool raw) { QByteArray txt, data; @@ -655,6 +655,8 @@ QByteArray pki_key::SSH2publicQByteArray() default: return QByteArray(); } + if (raw) + return data; return txt + " " + data.toBase64() + "\n"; } @@ -706,3 +708,51 @@ bool pki_key::verify_priv(EVP_PKEY *) const { return true; } + +QString pki_key::fingerprint(const QString format) +{ + const EVP_MD *md; + QByteArray data; + QStringList sl = format.toLower().split(" "); + + if (sl.size() < 2) + return QString("Invalid format: %1").arg(format); + if (sl[0] == "ssh") + data = SSH2publicQByteArray(true); + else if (sl[0] == "x509") + data = X509_PUBKEY_public_key(); + else if (sl[0] == "der") + data = i2d_bytearray(I2D_VOID(i2d_PUBKEY), key); + else + return QString("Invalid format: %1").arg(sl[0]); + + md = EVP_get_digestbyname(CCHAR(sl[1])); + if (!md) + return QString("Invalid hash: %1").arg(sl[1]); + + if (sl.size() > 2 && sl[2] == "b64") { + QString s(Digest(data, md).toBase64()); + s.chop(1); + return s; + } + return ::fingerprint(data, md); +} + +QByteArray pki_key::X509_PUBKEY_public_key() const +{ + X509_PUBKEY *pk = NULL; + const unsigned char *p; + int len; + + X509_PUBKEY_set(&pk, key); +#if OPENSSL_VERSION_NUMBER < 0x10000000L + p = pk->public_key->data; + len = pk->public_key->length; +#else + X509_PUBKEY_get0_param(NULL, &p, &len, NULL, pk); +#endif + + QByteArray data((const char*)p, len); + X509_PUBKEY_free(pk); + return data; +} diff --git a/lib/pki_key.h b/lib/pki_key.h index f606b1d3..4f40ef4d 100644 --- a/lib/pki_key.h +++ b/lib/pki_key.h @@ -35,7 +35,8 @@ class pki_key: public pki_base EVP_PKEY *key; QString BN2QString(const BIGNUM *bn) const; QString BNoneLine(BIGNUM *bn) const; - QByteArray SSH2publicQByteArray(); + QByteArray SSH2publicQByteArray(bool raw=false); + QByteArray X509_PUBKEY_public_key() const; private: BIGNUM *ssh_key_data2bn(QByteArray *ba, bool skip = false); @@ -93,6 +94,7 @@ class pki_key: public pki_base QByteArray i2d() const; EVP_PKEY *load_ssh2_key(FILE *fp); void writeSSH2public(QString fname); + QString fingerprint(const QString format); void resetUcount() { useCount = -1; diff --git a/lib/pki_x509.cpp b/lib/pki_x509.cpp index 042363f8..b977e3a3 100644 --- a/lib/pki_x509.cpp +++ b/lib/pki_x509.cpp @@ -838,13 +838,7 @@ void pki_x509::setPubKey(pki_key *key) QString pki_x509::fingerprint(const EVP_MD *digest) const { - unsigned int n; - unsigned char md[EVP_MAX_MD_SIZE]; - - pki_openssl_error(); - X509_digest(cert, digest, md, &n); - pki_openssl_error(); - return formatHash(md, n); + return ::fingerprint(i2d_bytearray(I2D_VOID(i2d_X509), cert), digest); } bool pki_x509::checkDate() diff --git a/lib/settings.cpp b/lib/settings.cpp index ae1a7b66..6306f27a 100644 --- a/lib/settings.cpp +++ b/lib/settings.cpp @@ -35,6 +35,8 @@ settings::settings() defaul["ical_expiry"] = "1W"; defaul["cert_expiry"] = "80%"; defaul["serial_len"] = "64"; + defaul["fp_separator"] = ":"; + defaul["fp_digits"] = "2"; hostspecific << "pkcs11path" << "workingdir" << "mw_geometry"; diff --git a/ui/CertDetail.ui b/ui/CertDetail.ui index 8c6d1ce8..eba5d9c0 100644 --- a/ui/CertDetail.ui +++ b/ui/CertDetail.ui @@ -193,6 +193,12 @@ + + + 0 + 0 + + MD5 @@ -200,6 +206,12 @@ + + + 0 + 0 + + An md5 hashsum of the certificate @@ -207,6 +219,12 @@ + + + 0 + 0 + + SHA1 @@ -214,6 +232,12 @@ + + + 0 + 0 + + A SHA-1 hashsum of the certificate @@ -221,6 +245,12 @@ + + + 0 + 0 + + SHA256 @@ -228,6 +258,12 @@ + + + 0 + 0 + + A SHA-256 hashsum of the certificate @@ -355,8 +391,8 @@ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;"> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"></p></body></html> +</style></head><body style=" font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:9pt;"><br /></p></body></html> false diff --git a/ui/KeyDetail.ui b/ui/KeyDetail.ui index b9c05d42..414ab581 100644 --- a/ui/KeyDetail.ui +++ b/ui/KeyDetail.ui @@ -101,7 +101,7 @@ - 1 + 2 @@ -325,6 +325,11 @@ + + + Fingerprint + + Comment @@ -339,8 +344,8 @@ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;"> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> +</style></head><body style=" font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:9pt;"><br /></p></body></html> diff --git a/widgets/KeyDetail.cpp b/widgets/KeyDetail.cpp index 18ca1007..0bd418d5 100644 --- a/widgets/KeyDetail.cpp +++ b/widgets/KeyDetail.cpp @@ -38,6 +38,30 @@ static QString CurveComment(int nid) } #endif +void KeyDetail::setupFingerprints(pki_key *key) +{ + int pos = 0; + QGridLayout *g = new QGridLayout(fingerprint); + QStringList sl; sl << + "ssh MD5" << "ssh SHA256 B64" << + "x509 SHA1" << "DER SHA256"; + + foreach(QString type, sl) { + qDebug() << type << key->fingerprint(type); + + QLabel *left = new QLabel(fingerprint); + CopyLabel *right = new CopyLabel(fingerprint); + + left->setTextFormat(Qt::PlainText); + left->setText(type); + right->setText(key->fingerprint(type)); + + g->addWidget(left, pos, 0); + g->addWidget(right, pos, 1); + pos++; + } +} + void KeyDetail::setKey(pki_key *key) { keyDesc->setText(key->getIntName()); @@ -49,6 +73,8 @@ void KeyDetail::setKey(pki_key *key) tlHeader->setText(tr("Details of the %1 key").arg(key->getTypeString())); comment->setPlainText(key->getComment()); + setupFingerprints(key); + if (key->isPubKey()) { keyPrivEx->setText(tr("Not available")); keyPrivEx->setRed(); diff --git a/widgets/KeyDetail.h b/widgets/KeyDetail.h index b4b9b8cc..1bd219d8 100644 --- a/widgets/KeyDetail.h +++ b/widgets/KeyDetail.h @@ -19,6 +19,8 @@ class KeyDetail: public QDialog, public Ui::KeyDetail public: KeyDetail(QWidget *parent); void setKey(pki_key *key); + void setupFingerprints(pki_key *key); + }; #endif diff --git a/widgets/clicklabel.cpp b/widgets/clicklabel.cpp index 21a3dae9..6a6bf116 100644 --- a/widgets/clicklabel.cpp +++ b/widgets/clicklabel.cpp @@ -68,12 +68,10 @@ void ClickLabel::disableToolTip() setToolTip(QString()); } - CopyLabel::CopyLabel(QWidget *parent) :DoubleClickLabel(parent) { - setFrameShape(QFrame::Panel); - setFrameShadow(QFrame::Sunken); + setFrameStyle(QFrame::StyledPanel); setTextFormat(Qt::PlainText); #if QT_VERSION >= 0x040200