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.
This commit is contained in:
Christian Hohnstaedt 2024-09-11 22:58:59 +02:00
parent 2e70aa6329
commit 60aefeac53
6 changed files with 54 additions and 9 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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