/* vi: set sw=4 ts=4: * * Copyright (C) 2001 - 2014 Christian Hohnstaedt. * * All rights reserved. */ #include "pki_x509.h" #include "pki_evp.h" #include "pki_scard.h" #include "func.h" #include "base.h" #include "exception.h" #include "pass_info.h" #include "widgets/MainWindow.h" #include #include #include #include "openssl_compat.h" bool pki_x509::dont_colorize_expiries = false; bool pki_x509::disable_netscape = false; QPixmap *pki_x509::icon[5]; pki_x509::pki_x509(X509 *c) :pki_x509super() { init(); cert = c; pki_openssl_error(); } pki_x509::pki_x509(const pki_x509 *crt) :pki_x509super(crt->desc) { init(); cert = X509_dup(crt->cert); pki_openssl_error(); psigner = crt->psigner; setRefKey(crt->getRefKey()); trust = crt->trust; efftrust = crt->efftrust; caSerial = crt->caSerial; caTemplate = crt->caTemplate; revocation = crt->revocation; crlDays = crt->crlDays; crlExpiry = crt->crlExpiry; pki_openssl_error(); } pki_x509::pki_x509(const QString name) :pki_x509super(name) { init(); cert = X509_new(); X509_set_version(cert, 2); pki_openssl_error(); } QString pki_x509::getMsg(msg_type msg) { /* * We do not construct english sentences from fragments * to allow proper translations. * The drawback are all the slightly different duplicated messages * * %1 will be replaced by the internal name of the certificate */ switch (msg) { case msg_import: return tr("Successfully imported the certificate '%1'"); case msg_delete: return tr("Delete the certificate '%1'?"); case msg_create: return tr("Successfully created the certificate '%1'"); /* %1: Number of certs; %2: list of cert ames */ case msg_delete_multi: return tr("Delete the %1 certificates: %2?"); } return pki_base::getMsg(msg); } void pki_x509::fromPEM_BIO(BIO *bio, QString name) { X509 *_cert; _cert = PEM_read_bio_X509(bio, NULL, NULL, NULL); pki_openssl_error(); X509_free(cert); cert = _cert; autoIntName(); if (getIntName().isEmpty()) setIntName(rmslashdot(name)); trust = 1; efftrust = 1; } void pki_x509::fload(const QString fname) { FILE *fp = fopen_read(fname); X509 *_cert; if (!fp) { fopen_error(fname); return; } _cert = PEM_read_X509(fp, NULL, NULL, NULL); if (!_cert) { pki_ign_openssl_error(); rewind(fp); _cert = d2i_X509_fp(fp, NULL); } fclose(fp); if (pki_ign_openssl_error() ) { if (_cert) X509_free(_cert); throw errorEx(tr("Unable to load the certificate in file %1. Tried PEM and DER certificate.").arg(fname)); } X509_free(cert); cert = _cert; autoIntName(); if (getIntName().isEmpty()) setIntName(rmslashdot(fname)); trust = 1; efftrust = 1; } pki_x509::~pki_x509() { if (cert) { X509_free(cert); } pki_openssl_error(); } void pki_x509::init() { psigner = NULL; trust = 0; efftrust = 0; caSerial = 1; caTemplate = ""; crlDays = 30; crlExpiry.setUndefined(); class_name = "pki_x509"; cert = NULL; dataVersion = 4; pkiType = x509; randomSerial = false; } void pki_x509::setSerial(const a1int &serial) { X509_set_serialNumber(cert, serial.get()); pki_openssl_error(); } a1int pki_x509::getSerial() const { a1int a(X509_get_serialNumber(cert)); pki_openssl_error(); return a; } pki_x509 *pki_x509::getBySerial(const a1int &a) const { foreach(pki_base *p, childItems) { pki_x509 *pki = static_cast(p); if (a == pki->getSerial()) return pki; } return NULL; } #define SERIAL_LEN 8 a1int pki_x509::getIncCaSerial() { unsigned char buf[SERIAL_LEN]; if (!randomSerial) return caSerial++; RAND_bytes(buf, SERIAL_LEN); a1int serial; serial.setRaw(buf, SERIAL_LEN); return serial; } a1int pki_x509::hashInfo(const EVP_MD *md) const { unsigned char digest[EVP_MAX_MD_SIZE]; unsigned len = 0; if (!X509_digest(cert, md, digest, &len)) pki_openssl_error(); a1int a; a.setRaw(digest,len); return a; } void pki_x509::load_token(pkcs11 &p11, CK_OBJECT_HANDLE object) { QString desc; pk11_attr_ulong type(CKA_CERTIFICATE_TYPE); p11.loadAttribute(type, object); if (type.getValue() != CKC_X_509) throw errorEx(QString("Unsupported Certificate type %1" ).arg(type.getValue())); try { pk11_attr_data label(CKA_LABEL); p11.loadAttribute(label, object); desc = label.getText(); } catch(errorEx &err) { printf("No Cert Label: %s\n", err.getCString()); // IGNORE } pk11_attr_data x509(CKA_VALUE); p11.loadAttribute(x509, object); QByteArray der = x509.getData(); d2i(der); if (desc.isEmpty()) { try { x509name xn; pk11_attr_data subj(CKA_SUBJECT); p11.loadAttribute(subj, object); QByteArray der = subj.getData(); xn.d2i(der); desc = xn.getMostPopular(); pki_openssl_error(); } catch(errorEx &err) { printf("No Cert Subject: %s\n", err.getCString()); // IGNORE } } setIntName(desc); pki_openssl_error(); } void pki_x509::d2i(QByteArray &ba) { X509 *c = (X509*)d2i_bytearray(D2I_VOID(d2i_X509), ba); if (c) { X509_free(cert); cert = c; } pki_openssl_error(); } QByteArray pki_x509::i2d() { return i2d_bytearray(I2D_VOID(i2d_X509), cert); } void pki_x509::store_token(bool alwaysSelect) { pki_scard *card = NULL; slotid slot; x509name xname; QList objects; pkcs11 p11; if (!privkey || !privkey->isToken() || alwaysSelect) { if (!p11.selectToken(&slot, NULL)) return; } else { card = (pki_scard *)privkey; if (!card->prepare_card(&slot)) return; } pk11_attlist p11_atts; p11_atts << pk11_attr_ulong(CKA_CLASS, CKO_CERTIFICATE) << pk11_attr_ulong(CKA_CERTIFICATE_TYPE, CKC_X_509) << pk11_attr_data(CKA_VALUE, i2d()); p11.startSession(slot, true); QList objs = p11.objectList(p11_atts); if (objs.count() != 0) { XCA_WARN(tr("This certificate is already on the security token")); return; } p11_atts << pk11_attr_bool(CKA_TOKEN, true) << pk11_attr_bool(CKA_PRIVATE, false) << pk11_attr_data(CKA_SUBJECT, getSubject().i2d()) << pk11_attr_data(CKA_ISSUER, getIssuer().i2d()) << pk11_attr_data(CKA_SERIAL_NUMBER, getSerial().i2d()) << pk11_attr_data(CKA_LABEL, desc.toUtf8()) << (card ? card->getIdAttr() : p11.findUniqueID(CKO_CERTIFICATE)); if (p11.tokenLogin(p11.tokenInfo().label(), false).isNull()) return; p11.createObject(p11_atts); } void pki_x509::deleteFromToken() { pki_scard *card = (pki_scard *)privkey; slotidList p11_slots; if (!pkcs11::loaded()) return; if (privkey && privkey->isToken()) { slotid slot; if (!card->prepare_card(&slot)) return; p11_slots << slot; } else { pkcs11 p11; p11_slots = p11.getSlotList(); } for (int i=0; i objs = p11.objectList(atts); if (!objs.count()) return; tkInfo ti = p11.tokenInfo(); if (!XCA_YESNO(tr("Delete the certificate '%1' from the token '%2 (#%3)'?"). arg(getIntName()).arg(ti.label()).arg(ti.serial()))) { return; } if (p11.tokenLogin(ti.label(), false).isNull()) return; p11.deleteObjects(objs); } int pki_x509::renameOnToken(slotid slot, QString name) { pkcs11 p11; p11.startSession(slot, true); pk11_attlist attrs = objectAttributes(); QList objs = p11.objectList(attrs); if (!objs.count()) return 0; pk11_attr_data label(CKA_LABEL, name.toUtf8()); tkInfo ti = p11.tokenInfo(); if (p11.tokenLogin(ti.label(), false).isNull()) return 0; p11.storeAttribute(label, objs[0]); return 1; } void pki_x509::setNotBefore(const a1time &a) { a1time t(a); X509_set_notBefore(cert, t.get_utc()); pki_openssl_error(); } void pki_x509::setNotAfter(const a1time &a) { a1time t(a); X509_set_notAfter(cert, t.get_utc()); pki_openssl_error(); } a1time pki_x509::getNotBefore() const { a1time a(X509_get_notBefore(cert)); return a; } a1time pki_x509::getNotAfter() const { a1time a(X509_get_notAfter(cert)); return a; } x509name pki_x509::getSubject() const { x509name x(X509_get_subject_name(cert)); pki_openssl_error(); return x; } x509name pki_x509::getIssuer() const { x509name x(X509_get_issuer_name(cert)); pki_openssl_error(); return x; } void pki_x509::setSubject(const x509name &n) { X509_set_subject_name(cert, n.get()); pki_openssl_error(); } void pki_x509::setIssuer(const x509name &n) { X509_set_issuer_name(cert, n.get()); pki_openssl_error(); } bool pki_x509::addV3ext(const x509v3ext &e, bool skip_existing) { if (!e.isValid()) return false; if (skip_existing && X509_get_ext_by_NID(cert, e.nid(), -1) != -1) return false; X509_EXTENSION *ext = e.get(); X509_add_ext(cert, ext, -1); X509_EXTENSION_free(ext); pki_openssl_error(); return true; } void pki_x509::delSigner(pki_base *s) { if (s == psigner) psigner = NULL; } bool pki_x509::canSign() { BASIC_CONSTRAINTS *bc; int crit; if (!privkey || privkey->isPubKey()) return false; if (privkey->isToken() && !pkcs11::loaded()) return false; bc = (BASIC_CONSTRAINTS *)X509_get_ext_d2i(cert, NID_basic_constraints, &crit, NULL); pki_openssl_error(); if (!bc || !bc->ca) return false; return true; } bool pki_x509::hasExtension(int nid) { return getV3ext().idxByNid(nid) != -1; } void pki_x509::sign(pki_key *signkey, const EVP_MD *digest) { EVP_PKEY *tkey; if (!signkey) { my_error(tr("There is no key for signing !")); } tkey = signkey->decryptKey(); pki_openssl_error(); X509_sign(cert, tkey, digest); pki_openssl_error(); EVP_PKEY_free(tkey); pki_openssl_error(); } void pki_x509::fromData(const unsigned char *p, db_header_t *head) { int version, size; bool isRevoked = false; version = head->version; size = head->len - sizeof(db_header_t); QByteArray ba((const char*)p, size); d2i(ba); pki_openssl_error(); trust = db::intFromData(ba); if (version < 4) { a1time revoked; isRevoked = db::boolFromData(ba); revoked.d2i(ba); pki_openssl_error(); if (isRevoked) { revocation.setDate(revoked); revocation.setSerial(getSerial()); } } caSerial.setHex(db::stringFromData(ba)); pki_openssl_error(); caTemplate = db::stringFromData(ba); crlDays = db::intFromData(ba); crlExpiry.d2i(ba); pki_openssl_error(); if (version > 1) randomSerial = db::boolFromData(ba); else randomSerial = false; if (version > 2) crlNumber.setHex(db::stringFromData(ba)); pki_openssl_error(); if (version > 2 && version < 4) { // load own revocation info, to tell daddy about it a1time invalDate; QString revoke_reason = db::stringFromData(ba); invalDate.d2i(ba); pki_openssl_error(); if (isRevoked) { revocation.setReason(revoke_reason); revocation.setInvalDate(invalDate); } } pki_openssl_error(); if (version > 3) { x509revList curr(revList); revList.fromBA(ba); pki_openssl_error(); revList.merge(curr); } if (ba.count() > 0) { my_error(tr("Wrong Size %1").arg(ba.count())); } pki_openssl_error(); } QByteArray pki_x509::toData() { QByteArray ba; ba += i2d(); // cert ba += db::intToData(trust); // version 4: don't store isrevoked, revoked // the serial if this is a CA ba += db::stringToData(caSerial.toHex()); // the name of the template to use for signing ba += db::stringToData(caTemplate); // version 3 ba += db::intToData(crlDays); // the CRL period ba += crlExpiry.i2d(); // last CRL date ba += db::boolToData(randomSerial); ba += db::stringToData(crlNumber.toHex()); // version 4: don't store own revocation but client revocations ba += revList.toBA(); pki_openssl_error(); return ba; } void pki_x509::writeDefault(const QString fname) { writeCert(fname + QDir::separator() + getIntName() + ".crt", true, false); } void pki_x509::writeCert(const QString fname, bool PEM, bool append) { FILE *fp; const char *p = "wb"; if (append) p = "ab"; fp = fopen(QString2filename(fname), p); if (fp != NULL) { if (cert){ if (PEM) PEM_write_X509(fp, cert); else i2d_X509_fp(fp, cert); } fclose(fp); pki_openssl_error(); } else fopen_error(fname); } void pki_x509::writeIndexEntry(FILE *fp) { QString flag = NULL; bool revoked = isRevoked(); if (revoked) flag = "R"; else if (checkDate()) flag = "V"; else flag = "E"; QString line = QString("%1\t%2\t%3\t%4\tunknown\t%5\n").arg( flag, getNotAfter().toPlainUTC(), (revoked ? revocation.getDate().toPlainUTC() : ""), getSerial().toHex(), QString(X509_NAME_oneline(getSubject().get(), NULL, 0))); QByteArray ba = line.toUtf8(); fwrite(ba.constData(), ba.size(), 1, fp); } void pki_x509::writeIndexEntry(const QString fname, bool append) { FILE *fp; const char *p = "w"; if (append) p = "a"; fp = fopen(QString2filename(fname), p); if (fp != NULL) { if (cert) writeIndexEntry(fp); fclose(fp); pki_openssl_error(); } else fopen_error(fname); } BIO *pki_x509::pem(BIO *b, int format) { (void)format; if (!b) b = BIO_new(BIO_s_mem()); PEM_write_bio_X509(b, cert); return b; } bool pki_x509::cmpIssuerAndSerial(pki_x509 *refcert) { bool ret = X509_issuer_and_serial_cmp(cert, refcert->cert); pki_openssl_error(); return ret; } bool pki_x509::verify_only(pki_x509 *signer) { X509_NAME *subject = X509_get_subject_name(signer->cert); X509_NAME *issuer = X509_get_issuer_name(cert); pki_openssl_error(); if (X509_NAME_cmp(subject, issuer)) { return false; } EVP_PKEY *pub = X509_get_pubkey(signer->cert); if (!pub) { pki_ign_openssl_error(); return false; } int i = X509_verify(cert, pub); pki_ign_openssl_error(); return i>0; } bool pki_x509::verify(pki_x509 *signer) { if (psigner == signer) return true; if ((psigner != NULL) || (signer == NULL)) return false; if (verify_only(signer)) { int idx; x509rev r; r.setSerial(getSerial()); psigner = signer; psigner->revList.merge(x509revList(revocation)); idx = psigner->revList.indexOf(r); if (idx != -1) revocation = psigner->revList[idx]; return true; } return false; } void pki_x509::setRevocations(const x509revList &rl) { revList = rl; x509rev rev; foreach(pki_base *p, childItems) { pki_x509 *pki = static_cast(p); rev.setSerial(pki->getSerial()); int idx = revList.indexOf(rev); if (idx != -1) pki->revocation = revList[idx]; else pki->revocation = x509rev(); } } pki_key *pki_x509::getPubKey() const { EVP_PKEY *pkey = X509_get_pubkey(cert); pki_ign_openssl_error(); if (pkey == NULL) return NULL; pki_evp *key = new pki_evp(pkey); pki_openssl_error(); return key; } bool pki_x509::compareNameAndKey(pki_x509 *other) { int r; X509_NAME *s1, *s2; EVP_PKEY *pub1, *pub2; if (!cert || !other->cert) return false; s1 = X509_get_subject_name(cert); s2 = X509_get_subject_name(other->cert); pki_openssl_error(); if (!s1 || !s2) return false; /* X509_NAME_cmp returns 0 if they match */ r = X509_NAME_cmp(s1, s2); pki_openssl_error(); if (r) return false; pub1 = X509_get_pubkey(cert); pub2 = X509_get_pubkey(other->cert); pki_ign_openssl_error(); if (!pub1 || !pub2) return false; /* EVP_PKEY_cmp() return 1 if the keys match */ r = EVP_PKEY_cmp(pub1, pub2); pki_openssl_error(); return r == 1; } void pki_x509::setPubKey(pki_key *key) { X509_set_pubkey(cert, key->getPubKey()); pki_openssl_error(); } QString pki_x509::fingerprint(const EVP_MD *digest) { QString fp=""; char zs[4]; unsigned int n, j; unsigned char md[EVP_MAX_MD_SIZE]; pki_openssl_error(); if (X509_digest(cert, digest, md, &n)) { for (j=0; j n) return false; pki_openssl_error(); return true; } extList pki_x509::getV3ext() { extList el; el.setStack(X509_get0_extensions(cert)); return el; } x509v3ext pki_x509::getExtByNid(int nid) { extList el = getV3ext(); int i = el.idxByNid(nid); try { pki_openssl_error(); } catch(errorEx &err) { XCA_WARN(err.getString()); } if (i == -1) return x509v3ext(); return el[i]; } int pki_x509::sigAlg() { return X509_get_signature_nid(cert); } pki_x509 *pki_x509::getSigner() { return (pki_x509 *)psigner; } int pki_x509::getTrust() { if (trust > 2) trust = 2; if (trust < 0) trust = 0; return trust; } void pki_x509::setTrust(int t) { if (t>=0 && t<=2) trust = t; } int pki_x509::getEffTrust() { if (efftrust > 2) efftrust = 2; if (efftrust < 0) efftrust = 0; return efftrust; } void pki_x509::setEffTrust(int t) { if (t>= 0 && t<= 2) efftrust = t; } bool pki_x509::isRevoked() { return revocation.isValid(); } void pki_x509::setRevoked(const x509rev &revok) { revocation = revok; if (revok.isValid()) { setEffTrust(0); setTrust(0); } } int pki_x509::calcEffTrust() { int mytrust = trust; if (mytrust != 1) { efftrust = mytrust; return mytrust; } if (isRevoked()) { efftrust = 0; return 0; } if (getSigner() == this && trust == 1) { // inherit trust, but self signed trust=0; efftrust=0; return 0; } //we must look at the parent certs pki_x509 *signer = getSigner(); while (mytrust == 1 && signer && signer != this) { mytrust = signer->getTrust(); signer = signer->getSigner(); } if (mytrust == 1) mytrust = 0; efftrust = mytrust; return mytrust; } void pki_x509::setCrlExpiry(const a1time &time) { crlExpiry = time; pki_openssl_error(); } bool pki_x509::caAndPathLen(bool *ca, a1int *pathlen, bool *hasLen) { x509v3ext e = getExtByNid(NID_basic_constraints); if (e.nid() != NID_basic_constraints) return false; BASIC_CONSTRAINTS *bc = (BASIC_CONSTRAINTS *)e.d2i(); if (hasLen) *hasLen = bc->pathlen ? true : false; if (pathlen && bc->pathlen) pathlen->set(bc->pathlen); if (ca) *ca = bc->ca; BASIC_CONSTRAINTS_free(bc); pki_openssl_error(); return true; } QVariant pki_x509::column_data(dbheader *hd) { QString truststatus[] = { tr("Not trusted"), tr("Trust inherited"), tr("Always Trusted") }; switch (hd->id) { case HD_cert_serial: return QVariant(getSerial().toHex()); case HD_cert_notBefore: return QVariant(getNotBefore().toSortable()); case HD_cert_notAfter: return QVariant(getNotAfter().toSortable()); case HD_cert_trust: return QVariant(truststatus[getTrust()]); case HD_cert_revocation: return QVariant(isRevoked() ? revocation.getDate().toSortable() : ""); case HD_cert_crl_expire: if (canSign() && !crlExpiry.isUndefined()) return QVariant(crlExpiry.toSortable()); else return QVariant(); case HD_cert_md5fp: return QVariant(fingerprint(EVP_md5())); case HD_cert_sha1fp: return QVariant(fingerprint(EVP_sha1())); case HD_cert_sha256fp: return QVariant(fingerprint(EVP_sha256())); case HD_cert_ca: { a1int len; bool ca, haslen; if (caAndPathLen(&ca, &len, &haslen)) { if (ca && haslen) return QVariant(len.toDec()); if (!ca) return QVariant(tr("No")); else return QVariant(tr("Yes")); } return QVariant(""); } } return pki_x509super::column_data(hd); } QVariant pki_x509::getIcon(dbheader *hd) { int pixnum = 0; bool ca; pki_key *k; switch (hd->id) { case HD_cert_ca: if (!caAndPathLen(&ca, NULL, NULL)) return QVariant(); if (!ca) return QVariant(); pixnum = 4; break; case HD_internal_name: k = getRefKey(); if (k && k->isPrivKey()) { pixnum += 1; } if (calcEffTrust() == 0){ pixnum += 2; } break; default: return QVariant(); } return QVariant(*icon[pixnum]); } bool pki_x509::visible() { if (pki_x509super::visible()) return true; if (getIssuer().search(limitPattern)) return true; if (fingerprint(EVP_md5()).contains(limitPattern)) return true; if (fingerprint(EVP_sha1()).contains(limitPattern)) return true; if (fingerprint(EVP_sha256()).contains(limitPattern)) return true; if (getSerial().toHex().contains(limitPattern)) return true; return false; } QVariant pki_x509::bg_color(dbheader *hd) { #define BG_RED QBrush(QColor(255, 0, 0)) #define BG_YELLOW QBrush(QColor(255,255, 0)) #define BG_CYAN QBrush(QColor(127,255,212)) if (dont_colorize_expiries) return QVariant(); a1time nb, na, now, certwarn; nb = getNotBefore(); na = getNotAfter(); now = a1time::now(); int lifetime = nb.secsTo(na); /* warn after 4/5 certificate lifetime */ certwarn = na.addSecs(- lifetime /5); switch (hd->id) { case HD_cert_notBefore: if (nb > now || !nb.isValid() || nb.isUndefined()) return QVariant(BG_RED); break; case HD_cert_notAfter: { if (na.isUndefined()) return QVariant(BG_CYAN); if (na < now) return QVariant(BG_RED); if (certwarn < now) return QVariant(BG_YELLOW); break; } case HD_cert_crl_expire: if (canSign()) { QDateTime crlwarn, crlex; crlex = crlExpiry; if (!crlExpiry.isUndefined()) { crlwarn = crlex.addSecs(-2 *60*60*24); if (crlex < now) return QVariant(BG_RED); if (crlwarn < now || !crlex.isValid()) return QVariant(BG_YELLOW); } } } return QVariant(); } void pki_x509::oldFromData(unsigned char *p, int size) { int version, sRev, sLastCrl; QByteArray ba((char*)p, size); X509 *cert_sik = cert; cert = NULL; version = intFromData(ba); if (version >=1 && version <= 5) { intFromData(ba); /* sCert */ d2i(ba); trust = intFromData(ba); sRev = intFromData(ba); if (sRev) { a1time r; r.d2i(ba); if (version != 3) { revocation.setSerial(getSerial()); revocation.setDate(r); } } else { revocation = x509rev(); } if (version == 1) { caTemplate=""; caSerial=1; crlExpiry.setUndefined(); crlDays=30; } if (version >= 2 ) { if (version >= 5) caSerial.setHex(db::stringFromData(ba)); else { int i = intFromData(ba); if (i>=0) caSerial = i; else { caSerial = getSerial(); ++caSerial; } } caTemplate = db::stringFromData(ba); } if (version >= 3 ) { crlDays = intFromData(ba); sLastCrl = intFromData(ba); if (sLastCrl) { crlExpiry.d2i(ba); } } // version 4 saves a NULL as revoked // version 3 did save a recent date :-(( } else { // old version d2i(ba); trust = 1; efftrust = 1; } if (cert) X509_free(cert_sik); else cert = cert_sik; if (ba.count() > 0) { my_error(tr("Wrong Size %1").arg(ba.count())); } }