/* vi: set sw=4 ts=4: * * Copyright (C) 2009 - 2020 Christian Hohnstaedt. * * All rights reserved. */ #include #include "pki_key.h" #include "pki_x509super.h" #include "func.h" #include "pkcs11.h" #include "pki_export.h" #include "XcaWarningCore.h" #include #include builtin_curves builtinCurves; keyjob keyjob::defaultjob; pki_key::pki_key(const QString &name) :pki_base(name) { key = EVP_PKEY_new(); key_size = 0; isPub = true; useCount = -1; } pki_key::pki_key(const pki_key *pk) :pki_base(pk) { if (pk->key) { QByteArray ba = i2d_bytearray(I2D_VOID(i2d_PUBKEY), pk->key); key = NULL; d2i(ba); sqlItemId = pk->sqlItemId; } else { key = EVP_PKEY_new(); } key_size = pk->key_size; useCount = -1; } pki_key::~pki_key() { if (key) EVP_PKEY_free(key); } void pki_key::autoIntName(const QString &file) { pki_base::autoIntName(file); if (!getIntName().isEmpty()) return; setIntName(QString("%1 %2%3").arg(length(), getTypeString(), isPubKey() ? QString(" ") + tr("Public key") : QString())); } void pki_key::d2i(QByteArray &ba) { EVP_PKEY *k = (EVP_PKEY*)d2i_bytearray(D2I_VOID(d2i_PUBKEY), ba); pki_openssl_error(); if (k) { if (key) EVP_PKEY_free(key); key = k; } } void pki_key::d2i_old(QByteArray &ba, int type) { const unsigned char *p, *p1; p = p1 = (const unsigned char *)ba.constData(); EVP_PKEY *k = d2i_PublicKey(type, NULL, &p1, ba.size()); pki_openssl_error(); if (k) { if (key) EVP_PKEY_free(key); key = k; } ba = ba.mid(p1-p); } QByteArray pki_key::i2d() const { return i2d_bytearray(I2D_VOID(i2d_PUBKEY), key); } bool pki_key::pem(BioByteArray &b) { return pem(b, pki_export::by_id(Settings["KeyFormat"])); } bool pki_key::pem(BioByteArray &b, const pki_export *xport) { if (xport->match_all(F_PRIVATE)) return false; if (xport->match_all(F_SSH2)) b += SSH2publicQByteArray(); else if (xport->match_all(F_PEM)) PEM_write_bio_PUBKEY(b, key); return true; } QString pki_key::getJWKcrv() const { const char *name = nullptr; #ifndef OPENSSL_NO_EC if (getKeyType() == EVP_PKEY_EC) { const EC_KEY *ec = EVP_PKEY_get0_EC_KEY(key); int nid = EC_GROUP_get_curve_name(EC_KEY_get0_group(ec)); switch (nid) { case NID_X9_62_prime256v1: case NID_secp384r1: case NID_secp521r1: case NID_secp256k1: name = EC_curve_nid2nist(nid); if (!name) name = OBJ_nid2sn(nid); } qDebug() << name << OBJ_nid2sn(nid) << nid; } #endif return QString(name); } void pki_key::fillJWK(QJsonObject &json, const pki_export *) const { json["kid"] = getIntName(); switch (getKeyType()) { case EVP_PKEY_RSA: { const RSA *rsa = EVP_PKEY_get0_RSA(key); Q_CHECK_PTR(rsa); const BIGNUM *n, *e; RSA_get0_key(rsa, &n, &e, NULL); json["n"] = base64UrlEncode(n); json["e"] = base64UrlEncode(e); json["kty"] = "RSA"; break; } #ifndef OPENSSL_NO_EC case EVP_PKEY_EC: { const EC_KEY *ec = EVP_PKEY_get0_EC_KEY(key); BIGNUM *x = BN_new(), *y = BN_new(); Q_CHECK_PTR(x); Q_CHECK_PTR(y); if (EC_POINT_get_affine_coordinates_GFp(EC_KEY_get0_group(ec), EC_KEY_get0_public_key(ec), x, y, NULL)) { int bits = EVP_PKEY_bits(key); json["x"] = base64UrlEncode(x, bits); json["y"] = base64UrlEncode(y, bits); json["kty"] = "EC"; json["crv"] = getJWKcrv(); } BN_free(x); BN_free(y); break; } #endif } }; QString pki_key::length() const { bool dsa_unset = false; if (getKeyType() == EVP_PKEY_DSA) { const BIGNUM *p = NULL; const DSA *dsa = EVP_PKEY_get0_DSA(key); if (dsa) DSA_get0_pqg(dsa, &p, NULL, NULL); dsa_unset = p == NULL; } if (dsa_unset) return QString("???"); return QString("%1 bit").arg(EVP_PKEY_bits(key)); } /* getKeyTypeString() returns RSA * getTypeString() returns RSA or "Token RSA" for tokens */ QString pki_key::getKeyTypeString() const { return keytype::byPKEY(key).name; } QString pki_key::getTypeString() const { return getKeyTypeString(); } QString pki_key::getMsg(msg_type msg, int n) const { /* * We do not construct english sentences (just a little bit) * from fragments to allow proper translations. * The drawback are all the slightly different duplicated messages */ QString ktype = getTypeString(); if (isPubKey()) { switch (msg) { case msg_import: return tr("Successfully imported the %1 public key '%2'", "%1 will be replaced by 'RSA', 'DSA', 'EC'. %2 is the internal name of the key").arg(ktype); case msg_delete: return tr("Delete the %n %1 public key(s) '%2'?", "%1 will be replaced by 'RSA', 'DSA', 'EC'. %2 is/are the internal name(s) of the key(s)", n).arg(ktype); default: break; } } else { switch (msg) { case msg_import: return tr("Successfully imported the %1 private key '%2'", "%1 will be replaced by 'RSA', 'DSA', 'EC'. %2 is the internal name of the key").arg(ktype); case msg_delete: return tr("Delete the %n %1 private key(s) '%2'?", "%1 will be replaced by 'RSA', 'DSA', 'EC'. %2 is/are the internal name(s) of the key(s)", n).arg(ktype); case msg_create: return tr("Successfully created the %1 private key '%2'", "%1 will be replaced by 'RSA', 'DSA', 'EC'. %2 is the internal name of the key").arg(ktype); default: break; } } return pki_base::getMsg(msg); } QString pki_key::comboText() const { return QString("%1 (%2:%3%4)").arg(getIntName()).arg(getTypeString()). arg(length()).arg(isPubKey() ? QString(" ") + tr("Public key") : QString("")); } bool pki_key::isToken() { return false; } bool pki_key::isPrivKey() const { return !isPubKey(); } int pki_key::getUcount() const { XSqlQuery q; if (useCount != -1) return useCount; int size = -1; SQL_PREPARE(q, "SELECT COUNT(*) FROM x509super WHERE pkey=?"); q.bindValue(0, sqlItemId); q.exec(); if (q.first()) size = q.value(0).toInt(); else qDebug("Failed to get key count for %s", CCHAR(getIntName())); XCA_SQLERROR(q.lastError()); useCount = size; return size; } int pki_key::getKeyType() const { return EVP_PKEY_id(key); } QString pki_key::modulus() const { if (getKeyType() == EVP_PKEY_RSA) { const BIGNUM *n = NULL; const RSA *rsa = EVP_PKEY_get0_RSA(key); RSA_get0_key(rsa, &n, NULL, NULL); return BN2QString(n); } return QString(); } QString pki_key::pubEx() const { if (getKeyType() == EVP_PKEY_RSA) { const BIGNUM *e = NULL; const RSA *rsa = EVP_PKEY_get0_RSA(key); RSA_get0_key(rsa, NULL, &e, NULL); return BN2QString(e); } return QString(); } QString pki_key::subprime() const { if (getKeyType() == EVP_PKEY_DSA) { const BIGNUM *q = NULL; const DSA *dsa = EVP_PKEY_get0_DSA(key); if (dsa) DSA_get0_pqg(dsa, NULL, &q, NULL); return BN2QString(q); } return QString(); } QString pki_key::pubkey() const { if (getKeyType() == EVP_PKEY_DSA) { const BIGNUM *pubkey = NULL; const DSA *dsa = EVP_PKEY_get0_DSA(key); if (dsa) DSA_get0_key(dsa, &pubkey, NULL); return BN2QString(pubkey); } return QString(); } #ifndef OPENSSL_NO_EC int pki_key::ecParamNid() const { const EC_KEY *ec; if (getKeyType() != EVP_PKEY_EC) return NID_undef; ec = EVP_PKEY_get0_EC_KEY(key); return EC_GROUP_get_curve_name(EC_KEY_get0_group(ec)); } BIGNUM *pki_key::ecPubKeyBN() const { if (getKeyType() != EVP_PKEY_EC) return NULL; const EC_KEY *ec = EVP_PKEY_get0_EC_KEY(key); return EC_POINT_point2bn(EC_KEY_get0_group(ec), EC_KEY_get0_public_key(ec), EC_KEY_get_conv_form(ec), NULL, NULL); } QString pki_key::ecPubKey() const { QString pub; BIGNUM *pub_key = ecPubKeyBN(); if (pub_key) { pub = BN2QString(pub_key); BN_free(pub_key); } return pub; } #ifdef EVP_PKEY_ED25519 static QByteArray ed25519Key(int(*EVP_PKEY_get_raw) (const EVP_PKEY*, unsigned char *, size_t *), const EVP_PKEY *pkey) { unsigned char k[ED25519_KEYLEN]; size_t len = sizeof k; if (EVP_PKEY_id(pkey) == EVP_PKEY_ED25519 && EVP_PKEY_get_raw(pkey, k, &len)) return QByteArray((char*)k, len); return QByteArray(); } QByteArray pki_key::ed25519PubKey() const { return ed25519Key(EVP_PKEY_get_raw_public_key, key); } QByteArray pki_key::ed25519PrivKey(const EVP_PKEY *pkey) const { return ed25519Key(EVP_PKEY_get_raw_private_key, pkey); } #else QByteArray pki_key::ed25519PubKey() const { return QByteArray(); } QByteArray pki_key::ed25519PrivKey(const EVP_PKEY *) const { return QByteArray(); } #endif #endif QList pki_key::possibleHashNids() { QList nids; QList allSha2 = { NID_sha224, NID_sha256, NID_sha384, NID_sha512 }; #ifndef LIBRESSL_VERSION_NUMBER QList allSha3 = { NID_sha3_224, NID_sha3_256, NID_sha3_384, NID_sha3_512 }; #else QList allSha3; #endif switch (EVP_PKEY_type(getKeyType())) { case EVP_PKEY_RSA: nids << NID_md5 << NID_ripemd160 << NID_sha1; nids += allSha2 + allSha3; break; case EVP_PKEY_DSA: nids << NID_sha1 << NID_sha224 << NID_sha256; break; #ifndef OPENSSL_NO_EC case EVP_PKEY_EC: nids << NID_sha1; nids += allSha2 + allSha3; break; #ifdef EVP_PKEY_ED25519 case EVP_PKEY_ED25519: nids << NID_undef; #endif #endif } return nids; }; bool pki_key::compare(const pki_base *ref) const { const pki_key *kref = (pki_key *)ref; if (kref->getKeyType() != getKeyType()) return false; if (!kref || !kref->key || !key) return false; int r = EVP_PKEY_cmp(key, kref->key); pki_openssl_error(); return r == 1; } void pki_key::writePublic(XFile &file, bool pem) const { BioByteArray b; if (pem) { b += PEM_comment(); PEM_write_bio_PUBKEY(b, key); } else { i2d_PUBKEY_bio(b, key); } pki_openssl_error(); file.write(b); } QString pki_key::BN2QString(const BIGNUM *bn) const { QByteArray hex, ba = BioByteArray(bn); for(int i = 0; iid) { case HD_key_type: return QVariant(getTypeString()); case HD_key_size: return QVariant(length()); case HD_key_use: return QVariant(getUcount()); case HD_key_passwd: if (isPubKey()) return QVariant(tr("No password")); if (ownPass<0 || ownPass>3) return QVariant("Holla die Waldfee"); return QVariant(sl[ownPass]); case HD_key_curve: QString r; #ifndef OPENSSL_NO_EC if (getKeyType() == EVP_PKEY_EC) r = OBJ_nid2sn(ecParamNid()); #endif return QVariant(r); } return pki_base::column_data(hd); } QSqlError pki_key::insertSqlData() { unsigned myhash = hash(); XSqlQuery q; QList list; SQL_PREPARE(q, "SELECT item FROM x509super WHERE key_hash=? AND " "pkey IS NULL"); q.bindValue(0, myhash); q.exec(); if (q.lastError().isValid()) return q.lastError(); while (q.next()) { pki_x509super *x; x = Store.lookupPki(q.value(0)); if (!x) { qDebug("X509 Super class with id %d not found", q.value(0).toInt()); continue; } if (x->compareRefKey(this)) { x->setRefKey(this); list << x; } } q.finish(); SQL_PREPARE(q, "UPDATE x509super SET pkey=? WHERE item=?"); q.bindValue(0, sqlItemId); foreach(pki_x509super* x, list) { q.bindValue(1, x->getSqlItemId()); q.exec(); AffectedItems(x->getSqlItemId()); if (q.lastError().isValid()) return q.lastError(); } q.finish(); SQL_PREPARE(q, "INSERT INTO public_keys (item, type, hash, len, \"public\") " "VALUES (?, ?, ?, ?, ?)"); q.bindValue(0, sqlItemId); q.bindValue(1, getKeyTypeString().left(4)); q.bindValue(2, myhash); q.bindValue(3, EVP_PKEY_bits(key)); q.bindValue(4, i2d_b64()); q.exec(); return q.lastError(); } void pki_key::restoreSql(const QSqlRecord &rec) { pki_base::restoreSql(rec); QByteArray ba = QByteArray::fromBase64( rec.value(VIEW_public_keys_public).toByteArray()); d2i(ba); key_size = rec.value(VIEW_public_keys_len).toInt(); } QSqlError pki_key::deleteSqlData() { XSqlQuery q; QSqlError e; SQL_PREPARE(q, "DELETE FROM public_keys WHERE item=?"); q.bindValue(0, sqlItemId); q.exec(); e = q.lastError(); if (e.isValid()) return e; SQL_PREPARE(q, "UPDATE x509super SET pkey=NULL WHERE pkey=?"); q.bindValue(0, sqlItemId); AffectedItems(sqlItemId); q.exec(); return q.lastError(); } void pki_key::ssh_key_check_chunk(QByteArray *ba, const char *expect) const { QByteArray chunk = ssh_key_next_chunk(ba); if (chunk != expect) throw errorEx(tr("Unexpected SSH2 content: '%1'") .arg(QString(chunk))); } BIGNUM *pki_key::ssh_key_data2bn(QByteArray *ba) const { QByteArray chunk = ssh_key_next_chunk(ba); BIGNUM *bn = BN_bin2bn((const unsigned char *)chunk.constData(), chunk.size(), NULL); Q_CHECK_PTR(bn); return bn; } QByteArray pki_key::ssh_key_next_chunk(QByteArray *ba) const { QByteArray chunk; const char *d; int len; if (!ba || ba->size() < 4) throw errorEx(tr("Invalid SSH2 public key")); d = ba->constData(); len = (d[0] << 24) + (d[1] << 16) + (d[2] << 8) + d[3]; if (ba->size() < len + 4) throw errorEx(tr("Invalid SSH2 public key")); chunk = ba->mid(4, len); ba->remove(0, len +4); return chunk; } EVP_PKEY *pki_key::load_ssh2_key(const QByteArray &b) { /* See RFC 4253 Section 6.6 */ QStringList sl; EVP_PKEY *pk = NULL; QByteArray ba(b); #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) sl = QString(ba).split(" ", Qt::SkipEmptyParts); #else sl = QString(ba).split(" ", QString::SkipEmptyParts); #endif if (sl.size() < 2) return NULL; ba = QByteArray::fromBase64(sl[1].toLatin1()); if (sl[0].startsWith("ssh-rsa")) { ssh_key_check_chunk(&ba, "ssh-rsa"); BIGNUM *e = ssh_key_data2bn(&ba); BIGNUM *n = ssh_key_data2bn(&ba); RSA *rsa = RSA_new(); Q_CHECK_PTR(rsa); RSA_set0_key(rsa, n, e, NULL); pk = EVP_PKEY_new(); Q_CHECK_PTR(pk); EVP_PKEY_assign_RSA(pk, rsa); } else if (sl[0].startsWith("ssh-dss")) { ssh_key_check_chunk(&ba, "ssh-dss"); BIGNUM *p = ssh_key_data2bn(&ba); BIGNUM *q = ssh_key_data2bn(&ba); BIGNUM *g = ssh_key_data2bn(&ba); BIGNUM *pubkey = ssh_key_data2bn(&ba); DSA *dsa = DSA_new(); Q_CHECK_PTR(dsa); DSA_set0_pqg(dsa, p, q, g); DSA_set0_key(dsa, pubkey, NULL); pk = EVP_PKEY_new(); Q_CHECK_PTR(pk); EVP_PKEY_assign_DSA(pk, dsa); #ifndef OPENSSL_NO_EC } else if (sl[0].startsWith("ecdsa-sha2-nistp256")) { EC_KEY *ec; /* Skip "ecdsa-sha2..." */ ssh_key_check_chunk(&ba, "ecdsa-sha2-nistp256"); ssh_key_check_chunk(&ba, "nistp256"); BIGNUM *bn = ssh_key_data2bn(&ba); ec = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); Q_CHECK_PTR(ec); EC_KEY_set_asn1_flag(ec, OPENSSL_EC_NAMED_CURVE); EC_KEY_set_public_key(ec, EC_POINT_bn2point( EC_KEY_get0_group(ec), bn, NULL, NULL)); BN_free(bn); pki_openssl_error(); pk = EVP_PKEY_new(); Q_CHECK_PTR(pk); EVP_PKEY_assign_EC_KEY(pk, ec); #ifdef EVP_PKEY_ED25519 } else if (sl[0].startsWith("ssh-ed25519")) { ssh_key_check_chunk(&ba, "ssh-ed25519"); QByteArray pub = ssh_key_next_chunk(&ba); pk = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, NULL, (const unsigned char *)pub.constData(), pub.size()); pki_openssl_error(); #endif #endif } else { throw errorEx(tr("Unexpected SSH2 content: '%1'").arg(sl[0])); } if (sl.size() > 2 && pk) setComment(sl[2].section('\n', 0, 0)); return pk; } void pki_key::ssh_key_QBA2data(const QByteArray &ba, QByteArray *data) const { int size = ba.size(); unsigned char p[4]; p[0] = (size >> 24) & 0xff; p[1] = (size >> 16) & 0xff; p[2] = (size >> 8) & 0xff; p[3] = size & 0xff; data->append((char*)p, sizeof p); data->append(ba); } void pki_key::ssh_key_bn2data(const BIGNUM *bn, QByteArray *data) const { ssh_key_QBA2data(BioByteArray(bn), data); } bool pki_key::SSH2_compatible() const { switch (getKeyType()) { #ifndef OPENSSL_NO_EC case EVP_PKEY_EC: return ecParamNid() == NID_X9_62_prime256v1; #ifdef EVP_PKEY_ED25519 case EVP_PKEY_ED25519: #endif #endif case EVP_PKEY_RSA: case EVP_PKEY_DSA: return true; } return false; } QByteArray pki_key::SSH2publicQByteArray(bool raw) const { QByteArray txt, data; switch (getKeyType()) { case EVP_PKEY_RSA: txt = "ssh-rsa"; ssh_key_QBA2data(txt, &data); { const RSA *rsa = EVP_PKEY_get0_RSA(key); const BIGNUM *n, *e; RSA_get0_key(rsa, &n, &e, NULL); ssh_key_bn2data(e, &data); ssh_key_bn2data(n, &data); } break; case EVP_PKEY_DSA: txt = "ssh-dss"; ssh_key_QBA2data(txt, &data); { const DSA *dsa = EVP_PKEY_get0_DSA(key); const BIGNUM *p, *q, *g, *pubkey; DSA_get0_pqg(dsa, &p, &q, &g); DSA_get0_key(dsa, &pubkey, NULL); ssh_key_bn2data(p, &data); ssh_key_bn2data(q, &data); ssh_key_bn2data(g, &data); ssh_key_bn2data(pubkey, &data); } break; #ifndef OPENSSL_NO_EC case EVP_PKEY_EC: if (ecParamNid() != NID_X9_62_prime256v1) return QByteArray(); txt = "ecdsa-sha2-nistp256"; ssh_key_QBA2data(txt, &data); ssh_key_QBA2data("nistp256", &data); { BIGNUM *bn = ecPubKeyBN(); ssh_key_bn2data(bn, &data); BN_free(bn); } pki_openssl_error(); break; #ifdef EVP_PKEY_ED25519 case EVP_PKEY_ED25519: txt = "ssh-ed25519"; ssh_key_QBA2data(txt, &data); ssh_key_QBA2data(ed25519PubKey(), &data); break; #endif #endif default: return QByteArray(); } if (raw) return data; txt += " " + data.toBase64(); QString comm = comment.section('\n', 0, 0).simplified(); if (comm.size() > 0) txt += " " + comm.toUtf8(); return txt + "\n"; } void pki_key::writeSSH2public(XFile &file) const { QByteArray txt = SSH2publicQByteArray(); if (file.write(txt) != txt.size()) throw errorEx(tr("Failed writing to %1").arg(file.fileName())); } bool pki_key::verify(EVP_PKEY *pkey) const { #ifndef LIBRESSL_VERSION_NUMBER EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(pkey, NULL); Q_CHECK_PTR(ctx); int verify = EVP_PKEY_public_check(ctx); EVP_PKEY_CTX_free(ctx); if (verify == -2) { // Operation not supported assume true pki_ign_openssl_error(); } pki_openssl_error(); return verify != 0; #else (void)pkey; return true; #endif } QString pki_key::fingerprint(const QString &format) const { 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); X509_PUBKEY_get0_param(NULL, &p, &len, NULL, pk); QByteArray data((const char*)p, len); X509_PUBKEY_free(pk); return data; } QByteArray pki_key::PEM_comment() const { if (!pem_comment) return QByteArray(); return pki_base::PEM_comment() + QString("%1 %2\n").arg(length(), getTypeString()).toUtf8(); } void pki_key::collect_properties(QMap &prp) const { QStringList sl; sl << getTypeString() << length(); if (isPubKey()) sl << tr("Public key"); #ifndef OPENSSL_NO_EC if (getKeyType() == EVP_PKEY_EC) sl << QString(OBJ_nid2ln(ecParamNid())); #endif prp["Key"] = sl.join(" "); pki_base::collect_properties(prp); } void pki_key::print(BioByteArray &bba, enum print_opt opt) const { pki_base::print(bba, opt); switch (opt) { case print_openssl_txt: EVP_PKEY_print_public(bba, key, 0, NULL); break; case print_pem: PEM_write_bio_PUBKEY(bba, key); break; case print_coloured: break; } }