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.
This commit is contained in:
Christian Hohnstaedt 2019-03-12 21:31:07 +01:00
parent c41bbf9b23
commit 1bc340a0ef
13 changed files with 166 additions and 77 deletions

View File

@ -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 <openssl/objects.h>
@ -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)

View File

@ -16,6 +16,7 @@
#include "base.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <openssl/evp.h>
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);

View File

@ -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() <<

View File

@ -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)

View File

@ -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;
}

View File

@ -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;

View File

@ -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()

View File

@ -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";

View File

@ -193,6 +193,12 @@
<layout class="QGridLayout">
<item row="0" column="0">
<widget class="QLabel" name="TextLabel1_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>MD5</string>
</property>
@ -200,6 +206,12 @@
</item>
<item row="0" column="1">
<widget class="CopyLabel" name="fpMD5">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>An md5 hashsum of the certificate</string>
</property>
@ -207,6 +219,12 @@
</item>
<item row="1" column="0">
<widget class="QLabel" name="TextLabel2_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>SHA1</string>
</property>
@ -214,6 +232,12 @@
</item>
<item row="1" column="1">
<widget class="CopyLabel" name="fpSHA1">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>A SHA-1 hashsum of the certificate</string>
</property>
@ -221,6 +245,12 @@
</item>
<item row="2" column="0">
<widget class="QLabel" name="TextLabel2_3">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>SHA256</string>
</property>
@ -228,6 +258,12 @@
</item>
<item row="2" column="1">
<widget class="CopyLabel" name="fpSHA256">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>A SHA-256 hashsum of the certificate</string>
</property>
@ -355,8 +391,8 @@
<string notr="true">&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot;-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;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="acceptRichText">
<bool>false</bool>

View File

@ -101,7 +101,7 @@
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>1</number>
<number>2</number>
</property>
<widget class="QWidget" name="key">
<attribute name="title">
@ -325,6 +325,11 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="fingerprint">
<attribute name="title">
<string>Fingerprint</string>
</attribute>
</widget>
<widget class="QWidget" name="commenttab">
<attribute name="title">
<string>Comment</string>
@ -339,8 +344,8 @@
<string notr="true">&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot;-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;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>

View File

@ -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();

View File

@ -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

View File

@ -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