From 60aefeac53f660436346c3f09db2d352bb12fbe1 Mon Sep 17 00:00:00 2001 From: Christian Hohnstaedt Date: Wed, 11 Sep 2024 22:58:59 +0200 Subject: [PATCH] Close #313: Support JWK export format of certificates The "x5t" and "x5t#256" properties are always present. The "x5c" Certificate chain can be optionally selected. --- lib/db_x509.cpp | 30 ++++++++++++++++++++++++------ lib/db_x509.h | 1 + lib/pki_export.cpp | 2 ++ lib/pki_export.h | 1 + lib/pki_x509.cpp | 26 ++++++++++++++++++++++++-- lib/pki_x509.h | 3 ++- 6 files changed, 54 insertions(+), 9 deletions(-) diff --git a/lib/db_x509.cpp b/lib/db_x509.cpp index 98a1f7d5..2c3b3289 100644 --- a/lib/db_x509.cpp +++ b/lib/db_x509.cpp @@ -563,6 +563,11 @@ int db_x509::exportFlags(const QModelIndex &idx) const if (!crt->isCA()) disable_flags |= F_CA; + pki_key *key = crt->getPubKey(); + if (key && key->getKeyType() != EVP_PKEY_RSA && key->getJWKcrv().isEmpty()) + disable_flags |= F_JWK; + delete key; + return disable_flags; } @@ -643,12 +648,8 @@ void db_x509::exportItems(const QModelIndexList &list, foreach(crt, certs) crt->writeCert(file, true); } - } else if (xport->match_all(F_DER)) { - crt->writeCert(file, false); } else if (xport->match_all(F_PKCS7)) { writePKCS7(crt, file, xport->flags, list); - } else if (xport->match_all(F_PKCS12)) { - writePKCS12(crt, file, xport->match_all(F_CHAIN)); } else if (xport->match_all(F_INDEX)) { writeIndex(file, certs); } else if (xport->match_all(F_CAL)) { @@ -658,10 +659,27 @@ void db_x509::exportItems(const QModelIndexList &list, crt->icsVEVENT_ca() : crt->icsVEVENT(); } writeVcalendar(file, vcal); - } else if (xport->match_all(F_CONFIG)) { - crt->opensslConf(file); } else if (xport->match_all(F_TAKEY)) { file.write(crt->getTaKey().toLatin1()); + } else { + qDebug() << "exportItems: db_base"; + db_base::exportItems(list, xport, file); + } +} + +void db_x509::exportItem(const QModelIndex &index, + const pki_export *xport, XFile &file) const +{ + pki_x509 *crt = fromIndex(index); + + if (xport->match_all(F_DER)) { + crt->writeCert(file, false); + } else if (xport->match_all(F_PKCS12)) { + writePKCS12(crt, file, xport->match_all(F_CHAIN)); + } else if (xport->match_all(F_CONFIG)) { + crt->opensslConf(file); + } else { + db_base::exportItem(index, xport, file); } } diff --git a/lib/db_x509.h b/lib/db_x509.h index ae80a7f9..daf85825 100644 --- a/lib/db_x509.h +++ b/lib/db_x509.h @@ -64,6 +64,7 @@ class db_x509: public db_x509super int exportFlags(const QModelIndex &idx) const; void exportItems(const QModelIndexList &indexes, const pki_export *xport, XFile &file) const; + void exportItem(const QModelIndex &, const pki_export *, XFile &) const; public slots: void newItem(); diff --git a/lib/pki_export.cpp b/lib/pki_export.cpp index 921dd82e..6b4eda77 100644 --- a/lib/pki_export.cpp +++ b/lib/pki_export.cpp @@ -80,6 +80,8 @@ new pki_export(17, x509, "ics", tr("vCalendar"), F_CAL, new pki_export(18, x509, "ics", tr("CA vCalendar"), F_CAL | F_CA, tr("vCalendar expiry reminder containing all issued, valid certificates, the CA itself and the latest CRL")), new pki_export(38, x509, "conf", tr("OpenSSL config"), F_SINGLE | F_CONFIG, tr("OpenSSL configuration file to create a certificate or request with the openssl commandline tool")), new pki_export(39, x509, "key", tr("OpenVPN tls-auth key"), F_SINGLE | F_TAKEY, tr("The OpenVPN tls-auth key is a secret key shared between endpoints")), +new pki_export(42, x509, "jwk", tr("JSON Web Kit"), F_JWK, tr("The public key of the certificate in JSON Web Kit format with X.509 Certificate Thumbprint (x5t)")), +new pki_export(43, x509, "jwk", tr("JSON Web Kit chain"), F_JWK | F_JWK_X5C, tr("The public key of the certificate in JSON Web Kit format with X.509 Certificate Thumbprint (x5t) and certificate chain (x5c)")), new pki_export(19, asym_key, "pem", tr("PEM public"), F_PEM | F_CLIPBOARD, tr("Text format of the public key in one PEM file")), new pki_export(20, asym_key, "pem", tr("PEM private"), F_PEM | F_PRIVATE | F_USUAL | F_CLIPBOARD, tr("Unencrypted private key in text format")), diff --git a/lib/pki_export.h b/lib/pki_export.h index c8c11490..bf7d72d7 100644 --- a/lib/pki_export.h +++ b/lib/pki_export.h @@ -54,6 +54,7 @@ enum { F_TRADITION = BIT(23),/* Traditional OpenSSL encrypted PEM */ F_UNUSABLE = BIT(24),/* Unusable certificates */ F_JWK = BIT(25),/* JSON Web Key (JWK) RFC-7517 */ + F_JWK_X5C = BIT(26),/* JSON Web Key (JWK) RFC-7517 X.509 Certificate Chain */ }; class pki_export : public QObject { diff --git a/lib/pki_x509.cpp b/lib/pki_x509.cpp index 5488e716..18d73c9e 100644 --- a/lib/pki_x509.cpp +++ b/lib/pki_x509.cpp @@ -739,6 +739,28 @@ bool pki_x509::pem(BioByteArray &b) return true; } +void pki_x509::fillJWK(QJsonObject &json, const pki_export *xport) const +{ + QByteArray der = i2d(); + pki_key *key = getPubKey(); + + key->fillJWK(json, xport); + json["kid"] = getIntName(); + json["x5t"] = BioByteArray(Digest(der, EVP_sha1())).base64UrlEncode(); + json["x5t#256"] = BioByteArray(Digest(der, EVP_sha256())).base64UrlEncode(); + if (xport->match_all(F_JWK_X5C)) { + QJsonArray x5c; + for (const pki_x509 *cert = this, *prev = nullptr; + cert && cert != prev; + prev = cert, cert = cert->getSigner()) + { + x5c.append(cert->i2d_b64()); + } + json["x5c"] = x5c; + } + delete key; +} + bool pki_x509::cmpIssuerAndSerial(pki_x509 *refcert) { bool ret = X509_issuer_and_serial_cmp(cert, refcert->cert); @@ -869,7 +891,7 @@ void pki_x509::setPubKey(pki_key *key) QString pki_x509::fingerprint(const EVP_MD *digest) const { - return ::fingerprint(i2d_bytearray(I2D_VOID(i2d_X509), cert), digest); + return ::fingerprint(i2d(), digest); } bool pki_x509::checkDate() @@ -918,7 +940,7 @@ int pki_x509::sigAlg() const return X509_get_signature_nid(cert); } -pki_x509 *pki_x509::getSigner() +pki_x509 *pki_x509::getSigner() const { return Store.lookupPki(issuerSqlId); } diff --git a/lib/pki_x509.h b/lib/pki_x509.h index e1cc657c..b2088e90 100644 --- a/lib/pki_x509.h +++ b/lib/pki_x509.h @@ -80,12 +80,13 @@ class pki_x509 : public pki_x509super bool isCA() const; bool canSign() const; void writeCert(XFile &file, bool PEM) const; + void fillJWK(QJsonObject &json, const pki_export *xport) const; QString getIndexEntry(); bool verify(pki_x509 *signer); bool verify_only(const pki_x509 *signer) const; pki_key *getPubKey() const; void setPubKey(pki_key *key); - pki_x509 *getSigner(); + pki_x509 *getSigner() const; void delSigner(pki_base *s); QString fingerprint(const EVP_MD *digest) const; extList getV3ext() const;