/* * Copyright (C) 2005-2007 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * */ #include #include #define VERSION "0.0.1" static QStringList wrapstring(const QString &str, int width) { QStringList out; QString simp = str.simplified(); QString rest = simp; while(1) { int lastSpace = -1; int n; for(n = 0; n < rest.length(); ++n) { if(rest[n].isSpace()) lastSpace = n; if(n == width) break; } if(n == rest.length()) { out += rest; break; } QString line; if(lastSpace != -1) { line = rest.mid(0, lastSpace); rest = rest.mid(lastSpace + 1); } else { line = rest.mid(0, n); rest = rest.mid(n); } out += line; } return out; } class AnimatedKeyGen : public QObject { Q_OBJECT public: static QCA::PrivateKey makeKey(QCA::PKey::Type type, int bits, QCA::DLGroupSet set) { AnimatedKeyGen kg; kg.type = type; kg.bits = bits; kg.set = set; QEventLoop eventLoop; kg.eventLoop = &eventLoop; QTimer::singleShot(0, &kg, SLOT(start())); eventLoop.exec(); QCA::PrivateKey key = kg.key; return key; } private: QCA::PKey::Type type; int bits; QCA::DLGroupSet set; QEventLoop *eventLoop; QCA::KeyGenerator gen; QCA::DLGroup group; QCA::PrivateKey key; QTimer t; int x; AnimatedKeyGen() { gen.setBlocking(false); connect(&gen, SIGNAL(finished()), SLOT(gen_finished())); connect(&t, SIGNAL(timeout()), SLOT(t_timeout())); } private slots: void start() { printf("Generating Key ... "); fflush(stdout); x = 0; t.start(125); if(type == QCA::PKey::RSA) gen.createRSA(bits); else gen.createDLGroup(set); } void gen_finished() { if(type == QCA::PKey::DSA || type == QCA::PKey::DH) { if(group.isNull()) { group = gen.dlGroup(); if(type == QCA::PKey::DSA) gen.createDSA(group); else gen.createDH(group); return; } } key = gen.key(); printf("\b"); if(!key.isNull()) printf("Done\n"); else printf("Error\n"); eventLoop->exit(); } void t_timeout() { if(x == 0) printf("\b/"); else if(x == 1) printf("\b-"); else if(x == 2) printf("\b\\"); else if(x == 3) printf("\b|"); fflush(stdout); ++x; x %= 4; } }; // TODO: support auto-discovering token during token request event // TODO: add a special watching mode to qcatool to monitor keystore activity? class PassphrasePrompt : public QObject { Q_OBJECT public: QCA::EventHandler handler; bool allowPrompt; bool warned; bool have_pass; bool used_pass; QCA::SecureArray pass; PassphrasePrompt() { allowPrompt = true; warned = false; have_pass = false; connect(&handler, SIGNAL(eventReady(int, const QCA::Event &)), SLOT(ph_eventReady(int, const QCA::Event &))); handler.start(); } void setExplicitPassword(const QCA::SecureArray &_pass) { have_pass = true; used_pass = false; pass = _pass; } private slots: void ph_eventReady(int id, const QCA::Event &e) { if(have_pass) { // only allow using an explicit passphrase once if(used_pass) { handler.reject(id); return; } used_pass = true; handler.submitPassword(id, pass); return; } if(!allowPrompt) { if(!have_pass && !warned) { warned = true; fprintf(stderr, "Error: no passphrase specified (use '--pass=' for none).\n"); } handler.reject(id); return; } if(e.type() == QCA::Event::Password) { QString type = "password"; if(e.passwordStyle() == QCA::Event::StylePassphrase) type = "passphrase"; else if(e.passwordStyle() == QCA::Event::StylePIN) type = "PIN"; QString str; if(e.source() == QCA::Event::KeyStore) { QString name; QCA::KeyStoreEntry entry = e.keyStoreEntry(); if(!entry.isNull()) { name = entry.name(); } else { QCA::KeyStoreManager ksm; QCA::KeyStore store(e.keyStoreId(), &ksm); if(!store.isValid()) { fprintf(stderr, "Error: received password request from unknown keystore.\n"); handler.reject(id); return; } if(store.type() == QCA::KeyStore::SmartCard) name = QString("the '") + store.name() + "' token"; else name = store.name(); } str = QString("Enter %1 for %2").arg(type).arg(name); } else if(!e.fileName().isEmpty()) str = QString("Enter %1 for %2").arg(type).arg(e.fileName()); else str = QString("Enter %1").arg(type); QCA::SecureArray result = QCA::ConsolePrompt::getHidden(str); handler.submitPassword(id, result); } else if(e.type() == QCA::Event::Token) { QCA::KeyStoreEntry entry = e.keyStoreEntry(); QString name; if(!entry.isNull()) { name = QString("the '") + entry.storeName() + "' token for " + entry.name(); } else { QCA::KeyStoreManager ksm; QCA::KeyStore store(e.keyStoreId(), &ksm); if(!store.isValid()) { fprintf(stderr, "Error: received token request from unknown keystore.\n"); handler.reject(id); return; } name = QString("the '") + store.name() + "' token"; } printf("Please insert %s and press Enter ...\n", qPrintable(name)); QCA::ConsolePrompt::waitForEnter(); handler.tokenOkay(id); } } }; class PassphrasePromptThread : public QCA::SyncThread { Q_OBJECT public: PassphrasePrompt *pp; PassphrasePromptThread() { start(); } ~PassphrasePromptThread() { stop(); } protected: virtual void atStart() { pp = new PassphrasePrompt; } virtual void atEnd() { delete pp; } }; static bool promptForNewPassphrase(QCA::SecureArray *result) { QCA::SecureArray out = QCA::ConsolePrompt::getHidden("Enter new passphrase"); if(QCA::ConsolePrompt::getHidden("Confirm new passphrase") != out) { fprintf(stderr, "Error: confirmation does not match original entry.\n"); return false; } *result = out; return true; } static QString line_encode(const QString &in) { QString out; for(int n = 0; n < in.length(); ++n) { if(in[n] == '\\') out += "\\\\"; else if(in[n] == '\n') out += "\\n"; else out += in[n]; } return out; } static QString line_decode(const QString &in) { QString out; for(int n = 0; n < in.length(); ++n) { if(in[n] == '\\') { if(n + 1 < in.length()) { if(in[n + 1] == '\\') out += '\\'; else if(in[n + 1] == 'n') out += '\n'; ++n; } } else out += in[n]; } return out; } static QString make_ksentry_string(const QString &id) { QString out; out += "QCATOOL_KEYSTOREENTRY_1\n"; out += line_encode(id) + '\n'; return out; } /*static bool write_ksentry_file(const QString &id, const QString &fileName) { QFile f(fileName); if(!f.open(QFile::WriteOnly | QFile::Truncate)) return false; f.write(make_ksentry_string(id).toUtf8()); return true; }*/ static QString read_ksentry_file(const QString &fileName) { QString out; QFile f(fileName); if(!f.open(QFile::ReadOnly)) return out; QTextStream ts(&f); int linenum = 0; while(!ts.atEnd()) { QString line = ts.readLine(); if(linenum == 0) { if(line != "QCATOOL_KEYSTOREENTRY_1") return out; } else { out = line_decode(line); break; } ++linenum; } return out; } static QByteArray read_der_file(const QString &fileName) { QFile f(fileName); if(!f.open(QFile::ReadOnly)) return QByteArray(); return f.readAll(); } class InfoType { public: QCA::CertificateInfoType type; QString varname; QString shortname; QString name; QString desc; InfoType() { } InfoType(QCA::CertificateInfoType _type, const QString &_varname, const QString &_shortname, const QString &_name, const QString &_desc) :type(_type), varname(_varname), shortname(_shortname), name(_name), desc(_desc) { } }; static QList makeInfoTypeList(bool legacyEmail = false) { QList out; out += InfoType(QCA::CommonName, "CommonName", "CN", "Common Name (CN)", "Full name, domain, anything"); out += InfoType(QCA::Email, "Email", "", "Email Address", ""); if(legacyEmail) out += InfoType(QCA::EmailLegacy, "EmailLegacy", "", "PKCS#9 Email Address", ""); out += InfoType(QCA::Organization, "Organization", "O", "Organization (O)", "Company, group, etc"); out += InfoType(QCA::OrganizationalUnit, "OrganizationalUnit", "OU", "Organizational Unit (OU)", "Division/branch of organization"); out += InfoType(QCA::Locality, "Locality", "", "Locality (L)", "City, shire, part of a state"); out += InfoType(QCA::State, "State", "", "State (ST)", "State within the country"); out += InfoType(QCA::Country, "Country", "C", "Country Code (C)", "2-letter code"); out += InfoType(QCA::IncorporationLocality, "IncorporationLocality", "", "Incorporation Locality", "For EV certificates"); out += InfoType(QCA::IncorporationState, "IncorporationState", "", "Incorporation State", "For EV certificates"); out += InfoType(QCA::IncorporationCountry, "IncorporationCountry", "", "Incorporation Country", "For EV certificates"); out += InfoType(QCA::URI, "URI", "", "URI", ""); out += InfoType(QCA::DNS, "DNS", "", "Domain Name", "Domain (dnsName)"); out += InfoType(QCA::IPAddress, "IPAddress", "", "IP Adddress", ""); out += InfoType(QCA::XMPP, "XMPP", "", "XMPP Address (JID)", "From RFC 3920 (id-on-xmppAddr)"); return out; } class MyConstraintType { public: QCA::ConstraintType type; QString varname; QString name; QString desc; MyConstraintType() { } MyConstraintType(QCA::ConstraintType _type, const QString &_varname, const QString &_name, const QString &_desc) :type(_type), varname(_varname), name(_name), desc(_desc) { } }; static QList makeConstraintTypeList() { QList out; out += MyConstraintType(QCA::DigitalSignature, "DigitalSignature", "Digital Signature", "Can be used for signing"); out += MyConstraintType(QCA::NonRepudiation, "NonRepudiation", "Non-Repudiation", "Usage is legally binding"); out += MyConstraintType(QCA::KeyEncipherment, "KeyEncipherment", "Key Encipherment", "Can encrypt other keys"); out += MyConstraintType(QCA::DataEncipherment, "DataEncipherment", "Data Encipherment", "Can encrypt arbitrary data"); out += MyConstraintType(QCA::KeyAgreement, "KeyAgreement", "Key Agreement", "Can perform key agreement (DH)"); out += MyConstraintType(QCA::KeyCertificateSign, "KeyCertificateSign", "Certificate Sign", "Can sign other certificates"); out += MyConstraintType(QCA::CRLSign, "CRLSign", "CRL Sign", "Can sign CRLs"); out += MyConstraintType(QCA::EncipherOnly, "EncipherOnly", "Encipher Only", "Can be used for encrypting"); out += MyConstraintType(QCA::DecipherOnly, "DecipherOnly", "Decipher Only", "Can be used for decrypting"); out += MyConstraintType(QCA::ServerAuth, "ServerAuth", "Server Authentication", "TLS Server"); out += MyConstraintType(QCA::ClientAuth, "ClientAuth", "Client Authentication", "TLS Client"); out += MyConstraintType(QCA::CodeSigning, "CodeSigning", "Code Signing", ""); out += MyConstraintType(QCA::EmailProtection, "EmailProtection", "Email Protection", "S/MIME"); out += MyConstraintType(QCA::IPSecEndSystem, "IPSecEndSystem", "IPSec End-System", ""); out += MyConstraintType(QCA::IPSecTunnel, "IPSecTunnel", "IPSec Tunnel", ""); out += MyConstraintType(QCA::IPSecUser, "IPSecUser", "IPSec User", ""); out += MyConstraintType(QCA::TimeStamping, "TimeStamping", "Time Stamping", ""); out += MyConstraintType(QCA::OCSPSigning, "OCSPSigning", "OCSP Signing", ""); return out; } static bool validOid(const QString &in) { for(int n = 0; n < in.length(); ++n) { if(!in[n].isDigit() && in[n] != '.') return false; } return true; } class ValidityLength { public: int years, months, days; }; static int vl_getnext(const QString &in, int offset = 0) { if(offset >= in.length()) return in.length(); int n = offset; bool lookForNonDigit; if(in[n].isDigit()) lookForNonDigit = true; else lookForNonDigit = false; for(++n; n < in.length(); ++n) { if(in[n].isDigit() != lookForNonDigit) break; } return n; } static QStringList vl_getparts(const QString &in) { QStringList out; int offset = 0; while(1) { int n = vl_getnext(in, offset); if(n == offset) break; out += in.mid(offset, n - offset); offset = n; } return out; } static bool parseValidityLength(const QString &in, ValidityLength *vl) { vl->years = -1; vl->months = -1; vl->days = -1; QStringList parts = vl_getparts(in); while(1) { // first part should be a number if(parts.count() < 1) break; QString str = parts.takeFirst(); bool ok; int x = str.toInt(&ok); if(!ok) return false; // next part should be 1 letter plus any amount of space if(parts.count() < 1) return false; str = parts.takeFirst(); if(!str[0].isLetter()) return false; str = str.trimmed(); // remove space if(str == "y") { if(vl->years != -1) return false; vl->years = x; } if(str == "m") { if(vl->months != -1) return false; vl->months = x; } if(str == "d") { if(vl->days != -1) return false; vl->days = x; } } if(vl->years == -1) vl->years = 0; if(vl->months == -1) vl->months = 0; if(vl->days == -1) vl->days = 0; return true; } static QString prompt_for(const QString &prompt) { printf("%s: ", prompt.toLatin1().data()); fflush(stdout); QByteArray result(256, 0); fgets((char *)result.data(), result.size(), stdin); return QString::fromLocal8Bit(result).trimmed(); } static QCA::CertificateOptions promptForCertAttributes(bool advanced, bool req) { QCA::CertificateOptions opts; if(advanced) { if(!req) { while(1) { QString str = prompt_for("Create an end user ('user') certificate or a CA ('ca') certificate? [user]"); if(str.isEmpty()) str = "user"; if(str != "user" && str != "ca") { printf("'%s' is not a valid entry.\n", qPrintable(str)); continue; } if(str == "ca") opts.setAsCA(); break; } printf("\n"); while(1) { QString str = prompt_for("Serial Number"); QCA::BigInteger num; if(str.isEmpty() || !num.fromString(str)) { printf("'%s' is not a valid entry.\n", qPrintable(str)); continue; } opts.setSerialNumber(num); break; } printf("\n"); } { QCA::CertificateInfoOrdered info; printf("Choose the information attributes to add to the certificate. They will be\n" "added in the order they are entered.\n\n"); printf("Available information attributes:\n"); QList list = makeInfoTypeList(); for(int n = 0; n < list.count(); ++n) { const InfoType &i = list[n]; char c = 'a' + n; printf(" %c) %-32s %s\n", c, qPrintable(i.name), qPrintable(i.desc)); } printf("\n"); while(1) { int index; while(1) { QString str = prompt_for("Select an attribute to add, or enter to move on"); if(str.isEmpty()) { index = -1; break; } if(str.length() == 1) { index = str[0].toLatin1() - 'a'; if(index >= 0 && index < list.count()) break; } printf("'%s' is not a valid entry.\n", qPrintable(str)); } if(index == -1) break; QString val = prompt_for(list[index].name); info += QCA::CertificateInfoPair(list[index].type, val); printf("Added attribute.\n\n"); } opts.setInfoOrdered(info); } { QCA::Constraints constraints; printf("\n"); printf("Choose the constraint attributes to add to the certificate.\n\n"); printf("Available attributes:\n"); QList list = makeConstraintTypeList(); for(int n = 0; n < list.count(); ++n) { const MyConstraintType &i = list[n]; char c = 'a' + n; printf(" %c) %-32s %s\n", c, qPrintable(i.name), qPrintable(i.desc)); } printf("\n"); printf("If no constraints are added, then the certificate may be used for any purpose.\n\n"); while(1) { int index; while(1) { QString str = prompt_for("Select an attribute to add, or enter to move on"); if(str.isEmpty()) { index = -1; break; } if(str.length() == 1) { index = str[0].toLatin1() - 'a'; if(index >= 0 && index < list.count()) break; } printf("'%s' is not a valid entry.\n\n", qPrintable(str)); } if(index == -1) break; if(constraints.contains(list[index].type)) { printf("You have already added '%s'.\n\n", qPrintable(list[index].name)); continue; } constraints += list[index].type; printf("Added attribute.\n\n"); } opts.setConstraints(constraints); } { QStringList policies; printf("\n"); printf("Are there any policy OID attributes that you wish to add? Use the dotted\n" "string format.\n\n"); while(1) { QString str = prompt_for("Enter a policy OID to add, or enter to move on"); if(str.isEmpty()) break; if(!validOid(str)) { printf("'%s' is not a valid entry.\n\n", qPrintable(str)); continue; } if(policies.contains(str)) { printf("You have already added '%s'.\n\n", qPrintable(str)); continue; } policies += str; printf("Added attribute.\n\n"); } opts.setPolicies(policies); } printf("\n"); } else { // self-signed alone still needs this for validation to work opts.setAsCA(); QCA::CertificateInfo info; info.insert(QCA::CommonName, prompt_for("Common Name")); info.insert(QCA::Country, prompt_for("Country Code (2 letters)")); info.insert(QCA::Organization, prompt_for("Organization")); info.insert(QCA::Email, prompt_for("Email")); opts.setInfo(info); printf("\n"); } if(!req) { while(1) { QString str = prompt_for("How long should the certificate be valid? (e.g. '1y2m3d')"); ValidityLength vl; if(!parseValidityLength(str, &vl)) { printf("'%s' is not a valid entry.\n\n", qPrintable(str)); continue; } if(vl.years == 0 && vl.months == 0 && vl.days == 0) { printf("The certificate must be valid for at least one day.\n\n"); continue; } QDateTime start = QDateTime::currentDateTime().toUTC(); QDateTime end = start; if(vl.years > 0) end = end.addYears(vl.years); if(vl.months > 0) end = end.addMonths(vl.months); if(vl.days > 0) end = end.addDays(vl.days); opts.setValidityPeriod(start, end); QStringList parts; if(vl.years > 0) parts += QString("%1 year(s)").arg(vl.years); if(vl.months > 0) parts += QString("%1 month(s)").arg(vl.months); if(vl.days > 0) parts += QString("%1 day(s)").arg(vl.days); QString out; if(parts.count() == 1) out = parts[0]; else if(parts.count() == 2) out = parts[0] + " and " + parts[1]; else if(parts.count() == 3) out = parts[0] + ", " + parts[1] + ", and " + parts[2]; printf("Certificate will be valid for %s.\n", qPrintable(out)); break; } printf("\n"); } return opts; } static QString kstype_to_string(QCA::KeyStore::Type _type) { QString type; switch(_type) { case QCA::KeyStore::System: type = "Sys "; break; case QCA::KeyStore::User: type = "User"; break; case QCA::KeyStore::Application: type = "App "; break; case QCA::KeyStore::SmartCard: type = "Card"; break; case QCA::KeyStore::PGPKeyring: type = "PGP "; break; default: type = "XXXX"; break; } return type; } static QString ksentrytype_to_string(QCA::KeyStoreEntry::Type _type) { QString type; switch(_type) { case QCA::KeyStoreEntry::TypeKeyBundle: type = "Key "; break; case QCA::KeyStoreEntry::TypeCertificate: type = "Cert"; break; case QCA::KeyStoreEntry::TypeCRL: type = "CRL "; break; case QCA::KeyStoreEntry::TypePGPSecretKey: type = "PSec"; break; case QCA::KeyStoreEntry::TypePGPPublicKey: type = "PPub"; break; default: type = "XXXX"; break; } return type; } static void try_print_info(const QString &name, const QStringList &values) { if(!values.isEmpty()) { QString value = values.join(", "); printf(" %s: %s\n", qPrintable(name), value.toUtf8().data()); } } static void print_info(const QString &title, const QCA::CertificateInfo &info) { QList list = makeInfoTypeList(); printf("%s\n", title.toLatin1().data()); foreach(const InfoType &t, list) try_print_info(t.name, info.values(t.type)); } static void print_info_ordered(const QString &title, const QCA::CertificateInfoOrdered &info) { QList list = makeInfoTypeList(true); printf("%s\n", title.toLatin1().data()); foreach(const QCA::CertificateInfoPair &pair, info) { QCA::CertificateInfoType type = pair.type(); QString name; int at = -1; for(int n = 0; n < list.count(); ++n) { if(list[n].type == type) { at = n; break; } } // known type? if(at != -1) { name = list[at].name; } else { if(pair.section() == QCA::CertificateInfoPair::DN) name = QString("DN:") + pair.oid(); else name = QString("AN:") + pair.oid(); } printf(" %s: %s\n", qPrintable(name), pair.value().toUtf8().data()); } } static QString constraint_to_string(QCA::ConstraintType t) { QList list = makeConstraintTypeList(); for(int n = 0; n < list.count(); ++n) { if(list[n].type == t) return list[n].name; } return QString("Unknown Constraint"); } static QString sigalgo_to_string(QCA::SignatureAlgorithm algo) { QString str; switch(algo) { case QCA::EMSA1_SHA1: str = "EMSA1(SHA1)"; break; case QCA::EMSA3_SHA1: str = "EMSA3(SHA1)"; break; case QCA::EMSA3_MD5: str = "EMSA3(MD5)"; break; case QCA::EMSA3_MD2: str = "EMSA3(MD2)"; break; case QCA::EMSA3_RIPEMD160: str = "EMSA3(RIPEMD160)"; break; case QCA::EMSA3_Raw: str = "EMSA3(raw)"; break; default: str = "Unknown"; break; } return str; } static void print_cert(const QCA::Certificate &cert, bool ordered = false) { printf("Serial Number: %s\n", qPrintable(cert.serialNumber().toString())); if(ordered) { print_info_ordered("Subject", cert.subjectInfoOrdered()); print_info_ordered("Issuer", cert.issuerInfoOrdered()); } else { print_info("Subject", cert.subjectInfo()); print_info("Issuer", cert.issuerInfo()); } printf("Validity\n"); printf(" Not before: %s\n", qPrintable(cert.notValidBefore().toString())); printf(" Not after: %s\n", qPrintable(cert.notValidAfter().toString())); printf("Constraints\n"); QCA::Constraints constraints = cert.constraints(); int n; if(!constraints.isEmpty()) { for(n = 0; n < constraints.count(); ++n) printf(" %s\n", qPrintable(constraint_to_string(constraints[n]))); } else printf(" No constraints\n"); printf("Policies\n"); QStringList policies = cert.policies(); if(!policies.isEmpty()) { for(n = 0; n < policies.count(); ++n) printf(" %s\n", qPrintable(policies[n])); } else printf(" No policies\n"); QByteArray id; printf("Issuer Key ID: "); id = cert.issuerKeyId(); if(!id.isEmpty()) printf("%s\n", qPrintable(QCA::arrayToHex(id))); else printf("None\n"); printf("Subject Key ID: "); id = cert.subjectKeyId(); if(!id.isEmpty()) printf("%s\n", qPrintable(QCA::arrayToHex(id))); else printf("None\n"); printf("CA: %s\n", cert.isCA() ? "Yes": "No"); printf("Signature Algorithm: %s\n", qPrintable(sigalgo_to_string(cert.signatureAlgorithm()))); QCA::PublicKey key = cert.subjectPublicKey(); printf("Public Key:\n%s", key.toPEM().toLatin1().data()); } static void print_certreq(const QCA::CertificateRequest &cert, bool ordered = false) { if(ordered) print_info_ordered("Subject", cert.subjectInfoOrdered()); else print_info("Subject", cert.subjectInfo()); printf("Constraints\n"); QCA::Constraints constraints = cert.constraints(); int n; if(!constraints.isEmpty()) { for(n = 0; n < constraints.count(); ++n) printf(" %s\n", qPrintable(constraint_to_string(constraints[n]))); } else printf(" No constraints\n"); printf("Policies\n"); QStringList policies = cert.policies(); if(!policies.isEmpty()) { for(n = 0; n < policies.count(); ++n) printf(" %s\n", qPrintable(policies[n])); } else printf(" No policies\n"); printf("CA: %s\n", cert.isCA() ? "Yes": "No"); printf("Signature Algorithm: %s\n", qPrintable(sigalgo_to_string(cert.signatureAlgorithm()))); QCA::PublicKey key = cert.subjectPublicKey(); printf("Public Key:\n%s", key.toPEM().toLatin1().data()); } static void print_pgp(const QCA::PGPKey &key) { printf("Key ID: %s\n", qPrintable(key.keyId())); printf("User IDs:\n"); foreach(const QString &s, key.userIds()) printf(" %s\n", qPrintable(s)); printf("Validity\n"); printf(" Not before: %s\n", qPrintable(key.creationDate().toString())); printf(" Not after: %s\n", qPrintable(key.expirationDate().toString())); printf("In Keyring: %s\n", key.inKeyring() ? "Yes": "No"); printf("Secret Key: %s\n", key.isSecret() ? "Yes": "No"); printf("Trusted: %s\n", key.isTrusted() ? "Yes": "No"); printf("Fingerprint: %s\n", qPrintable(key.fingerprint())); } static QString validityToString(QCA::Validity v) { QString s; switch(v) { case QCA::ValidityGood: s = "Validated"; break; case QCA::ErrorRejected: s = "Root CA is marked to reject the specified purpose"; break; case QCA::ErrorUntrusted: s = "Certificate not trusted for the required purpose"; break; case QCA::ErrorSignatureFailed: s = "Invalid signature"; break; case QCA::ErrorInvalidCA: s = "Invalid CA certificate"; break; case QCA::ErrorInvalidPurpose: s = "Invalid certificate purpose"; break; case QCA::ErrorSelfSigned: s = "Certificate is self-signed"; break; case QCA::ErrorRevoked: s = "Certificate has been revoked"; break; case QCA::ErrorPathLengthExceeded: s = "Maximum certificate chain length exceeded"; break; case QCA::ErrorExpired: s = "Certificate has expired"; break; case QCA::ErrorExpiredCA: s = "CA has expired"; break; case QCA::ErrorValidityUnknown: default: s = "General certificate validation error"; break; } return s; } static QString smErrorToString(QCA::SecureMessage::Error e) { QMap map; map[QCA::SecureMessage::ErrorPassphrase] = "ErrorPassphrase"; map[QCA::SecureMessage::ErrorFormat] = "ErrorFormat"; map[QCA::SecureMessage::ErrorSignerExpired] = "ErrorSignerExpired"; map[QCA::SecureMessage::ErrorSignerInvalid] = "ErrorSignerInvalid"; map[QCA::SecureMessage::ErrorEncryptExpired] = "ErrorEncryptExpired"; map[QCA::SecureMessage::ErrorEncryptUntrusted] = "ErrorEncryptUntrusted"; map[QCA::SecureMessage::ErrorEncryptInvalid] = "ErrorEncryptInvalid"; map[QCA::SecureMessage::ErrorNeedCard] = "ErrorNeedCard"; map[QCA::SecureMessage::ErrorCertKeyMismatch] = "ErrorCertKeyMismatch"; map[QCA::SecureMessage::ErrorUnknown] = "ErrorUnknown"; return map[e]; } const char *mime_signpart = "Content-Type: text/plain; charset=UTF-8\r\n" "Content-Transfer-Encoding: 8bit\r\n" "\r\n" "%1"; const char *mime_signed = "Content-Type: multipart/signed;\r\n" " micalg=%1;\r\n" " boundary=QCATOOL-0001;\r\n" " protocol=\"application/pkcs7-signature\"\r\n" "\r\n" "\r\n" "--QCATOOL-0001\r\n" "%2\r\n" "--QCATOOL-0001\r\n" "Content-Transfer-Encoding: base64\r\n" "Content-Type: application/pkcs7-signature;\r\n" " name=smime.p7s\r\n" "Content-Disposition: attachment;\r\n" " filename=smime.p7s\r\n" "\r\n" "%3\r\n" "\r\n" "--QCATOOL-0001--\r\n"; const char *mime_enveloped = "Mime-Version: 1.0\r\n" "Content-Transfer-Encoding: base64\r\n" "Content-Type: application/pkcs7-mime;\r\n" " name=smime.p7m;\r\n" " smime-type=enveloped-data\r\n" "Content-Disposition: attachment;\r\n" " filename=smime.p7m\r\n" "\r\n" "%1\r\n"; static QString add_cr(const QString &in) { QString out = in; int at = 0; while(1) { at = out.indexOf('\n', at); if(at == -1) break; if(at - 1 >= 0 && out[at - 1] != '\r') { out.insert(at, '\r'); ++at; } ++at; } return out; } static QString rem_cr(const QString &in) { QString out = in; out.replace("\r\n", "\n"); return out; } static int indexOf_newline(const QString &in, int offset = 0) { for(int n = offset; n < in.length(); ++n) { if(n + 1 < in.length() && in[n] == '\r' && in[n + 1] == '\n') return n; if(in[n] == '\n') return n; } return -1; } static int indexOf_doublenewline(const QString &in, int offset = 0) { int at = -1; while(1) { int n = indexOf_newline(in, offset); if(n == -1) return -1; if(at != -1) { if(n == offset) break; } at = n; if(in[n] == '\n') offset = n + 1; else offset = n + 2; } return at; } // this is so gross static int newline_len(const QString &in, int offset = 0) { if(in[offset] == '\r') return 2; else return 1; } // FIXME: all of this mime stuff is a total hack static QString open_mime_envelope(const QString &in) { int n = indexOf_doublenewline(in); if(n == -1) return QString(); return in.mid(n + (newline_len(in, n) * 2)); // good lord } static bool open_mime_data_sig(const QString &in, QString *data, QString *sig) { int n = in.indexOf("boundary="); if(n == -1) return false; n += 9; int i = indexOf_newline(in, n); if(i == -1) return false; QString boundary; QString bregion = in.mid(n, i - n); n = bregion.indexOf(';'); if(n != -1) boundary = bregion.mid(0, n); else boundary = bregion; if(boundary[0] == '\"') boundary.remove(0, 1); if(boundary[boundary.length() - 1] == '\"') boundary.remove(boundary.length() - 1, 1); //printf("boundary: [%s]\n", qPrintable(boundary)); QString boundary_end = QString("--") + boundary; boundary = QString("--") + boundary; QString work = open_mime_envelope(in); //printf("work: [%s]\n", qPrintable(work)); n = work.indexOf(boundary); if(n == -1) return false; n += boundary.length(); i = indexOf_newline(work, n); if(i == -1) return false; n += newline_len(work, i); int data_start = n; n = work.indexOf(boundary, data_start); if(n == -1) return false; int data_end = n; n = data_end + boundary.length(); i = indexOf_newline(work, n); if(i == -1) return false; n += newline_len(work, i); int next = n; QString tmp_data = work.mid(data_start, data_end - data_start); n = work.indexOf(boundary_end, next); if(n == -1) return false; QString tmp_sig = work.mid(next, n - next); // nuke some newlines if(tmp_data.right(2) == "\r\n") tmp_data.truncate(tmp_data.length() - 2); else if(tmp_data.right(1) == "\n") tmp_data.truncate(tmp_data.length() - 1); if(tmp_sig.right(2) == "\r\n") tmp_sig.truncate(tmp_sig.length() - 2); else if(tmp_sig.right(1) == "\n") tmp_sig.truncate(tmp_sig.length() - 1); tmp_sig = open_mime_envelope(tmp_sig); *data = tmp_data; *sig = tmp_sig; return true; } static QString idHash(const QString &id) { // hash the id and take the rightmost 4 hex characters return QCA::Hash("md5").hashToString(id.toUtf8()).right(4); } // first = ids, second = names static QPair getKeyStoreStrings(const QStringList &list, QCA::KeyStoreManager *ksm) { QPair out; for(int n = 0; n < list.count(); ++n) { QCA::KeyStore ks(list[n], ksm); out.first.append(ks.id()); out.second.append(ks.name()); } return out; } static QPair getKeyStoreEntryStrings(const QList &list) { QPair out; for(int n = 0; n < list.count(); ++n) { out.first.append(idHash(list[n].id())); out.second.append(list[n].name()); } return out; } static QList getPartialMatches(const QStringList &list, const QString &str) { QList out; for(int n = 0; n < list.count(); ++n) { if(list[n].contains(str, Qt::CaseInsensitive)) out += n; } return out; } static int findByString(const QPair &in, const QString &str) { // exact id match int n = in.first.indexOf(str); if(n != -1) return n; // partial id match QList ret = getPartialMatches(in.first, str); if(!ret.isEmpty()) return ret.first(); // partial name match ret = getPartialMatches(in.second, str); if(!ret.isEmpty()) return ret.first(); return -1; } static QString getKeyStore(const QString &name) { QCA::KeyStoreManager ksm; QStringList storeList = ksm.keyStores(); int n = findByString(getKeyStoreStrings(storeList, &ksm), name); if(n != -1) return storeList[n]; return QString(); } static QCA::KeyStoreEntry getKeyStoreEntry(QCA::KeyStore *store, const QString &name) { QList list = store->entryList(); int n = findByString(getKeyStoreEntryStrings(list), name); if(n != -1) return list[n]; return QCA::KeyStoreEntry(); } // here are a bunch of get_Foo functions for the various types // E - generic entry // K - private key // C - cert // X - keybundle // P - pgp public key // S - pgp secret key // in all cases but K, the store:obj notation can be used. if there // is no colon present, then we treat the input as a filename. we // try the file as an exported passive entry id, and if the type // is C or X, we'll fall back to regular files if necessary. static QCA::KeyStoreEntry get_E(const QString &name, bool nopassiveerror = false) { QCA::KeyStoreEntry entry; int n = name.indexOf(':'); if(n != -1) { // store:obj lookup QString storeName = name.mid(0, n); QString objectName = name.mid(n + 1); QCA::KeyStoreManager ksm; QCA::KeyStore store(getKeyStore(storeName), &ksm); if(!store.isValid()) { fprintf(stderr, "Error: no such store [%s].\n", qPrintable(storeName)); return entry; } entry = getKeyStoreEntry(&store, objectName); if(entry.isNull()) { fprintf(stderr, "Error: no such object [%s].\n", qPrintable(objectName)); return entry; } } else { // TODO: users of this function assume objects will also exist // exported id QString serialized = read_ksentry_file(name); entry = QCA::KeyStoreEntry(serialized); if(entry.isNull()) { if(!nopassiveerror) fprintf(stderr, "Error: invalid/unknown entry [%s].\n", qPrintable(name)); return entry; } } return entry; } static QCA::PrivateKey get_K(const QString &name, const QCA::SecureArray &pass) { QCA::PrivateKey key; int n = name.indexOf(':'); if(n != -1) { fprintf(stderr, "Error: cannot use store:obj notation for raw private keys.\n"); return key; } QCA::ConvertResult result; key = QCA::PrivateKey::fromPEMFile(name, pass, &result); if(result == QCA::ErrorDecode) { key = QCA::PrivateKey::fromDER(read_der_file(name), pass); if(key.isNull()) { printf("Error: unable to read/process private key file.\n"); return key; } } return key; } static QCA::Certificate get_C(const QString &name) { QCA::KeyStoreEntry entry = get_E(name, true); if(!entry.isNull()) { if(entry.type() != QCA::KeyStoreEntry::TypeCertificate) { printf("Error: entry is not a certificate.\n"); return QCA::Certificate(); } return entry.certificate(); } // try file QCA::Certificate cert = QCA::Certificate::fromPEMFile(name); if(cert.isNull()) { cert = QCA::Certificate::fromDER(read_der_file(name)); if(cert.isNull()) { printf("Error: unable to read/process certificate file.\n"); return cert; } } return cert; } static QCA::KeyBundle get_X(const QString &name) { QCA::KeyStoreEntry entry = get_E(name, true); if(!entry.isNull()) { if(entry.type() != QCA::KeyStoreEntry::TypeKeyBundle) { printf("Error: entry is not a keybundle.\n"); return QCA::KeyBundle(); } return entry.keyBundle(); } // try file // TODO: remove passphrase arg after api update QCA::KeyBundle key = QCA::KeyBundle::fromFile(name, QCA::SecureArray()); if(key.isNull()) { printf("Error: unable to read/process keybundle file.\n"); return key; } return key; } static QCA::PGPKey get_P(const QString &name) { QCA::PGPKey key; QCA::KeyStoreEntry entry = get_E(name); if(!entry.isNull()) { if(entry.type() != QCA::KeyStoreEntry::TypePGPPublicKey && entry.type() != QCA::KeyStoreEntry::TypePGPSecretKey) { printf("Error: entry is not a pgp public key.\n"); return key; } return entry.pgpPublicKey(); } return key; } static QPair get_S(const QString &name) { QPair key; QCA::KeyStoreEntry entry = get_E(name); if(!entry.isNull()) { if(entry.type() != QCA::KeyStoreEntry::TypePGPSecretKey) { printf("Error: entry is not a pgp secret key.\n"); return key; } key.first = entry.pgpSecretKey(); key.second = entry.pgpPublicKey(); return key; } return key; } static void usage() { printf("qcatool: simple qca utility\n"); printf("usage: qcatool (options) [command]\n"); printf(" options: --pass=x, --newpass=x, --nonroots=x, --roots=x, --nosys,\n"); printf(" --noprompt, --ordered, --debug\n"); printf("\n"); printf(" help|--help|-h This help text\n"); printf(" version|--version|-v Print version information\n"); printf(" plugins List available plugins\n"); printf(" config [command]\n"); printf(" save [provider] Save default provider config\n"); printf(" key [command]\n"); printf(" make rsa|dsa [bits] Create a key pair\n"); printf(" changepass [K] Add/change/remove passphrase of a key\n"); printf(" cert [command]\n"); printf(" makereq [K] Create certificate request (CSR)\n"); printf(" makeself [K] Create self-signed certificate\n"); printf(" makereqadv [K] Advanced version of 'makereq'\n"); printf(" makeselfadv [K] Advanced version of 'makeself'\n"); printf(" validate [C] Validate certificate\n"); printf(" keybundle [command]\n"); printf(" make [K] [C] Create a keybundle\n"); printf(" extract [X] Extract certificate(s) and key\n"); printf(" changepass [X] Change passphrase of a keybundle\n"); printf(" keystore [command]\n"); printf(" list-stores List all available keystores\n"); printf(" list [storeName] List content of a keystore\n"); printf(" export [E] Export a keystore entry's content\n"); printf(" exportref [E] Export a keystore entry reference\n"); printf(" addkb [storeName] [cert.p12] Add a keybundle into a keystore\n"); printf(" addpgp [storeName] [key.asc] Add a PGP key into a keystore\n"); printf(" remove [E] Remove an object from a keystore\n"); printf(" show [command]\n"); printf(" cert [C] Examine a certificate\n"); printf(" req [req.pem] Examine a certificate request (CSR)\n"); printf(" pgp [P|S] Examine a PGP key\n"); printf(" message [command]\n"); printf(" sign pgp|pgpdetach|smime [X|S] Sign a message\n"); printf(" encrypt pgp|smime [C|P] Encrypt a message\n"); printf(" signencrypt [S] [P] PGP sign & encrypt a message\n"); printf(" verify pgp|smime Verify a message\n"); printf(" decrypt pgp|smime (X) Decrypt a message (S/MIME needs X)\n"); printf(" exportcerts Export certs from S/MIME message\n"); printf("\n"); printf("Object types: K = private key, C = certificate, X = key bundle,\n"); printf(" P = PGP public key, S = PGP secret key, E = generic entry\n"); printf("\n"); printf("An object must be either a filename or a keystore reference (\"store:obj\").\n"); printf("\n"); } int main(int argc, char **argv) { QCA::Initializer qcaInit; QCoreApplication app(argc, argv); QStringList args; for(int n = 1; n < argc; ++n) args.append(QString::fromLocal8Bit(argv[n])); if(args.count() < 1) { usage(); return 1; } bool have_pass = false; bool have_newpass = false; QCA::SecureArray pass, newpass; bool allowprompt = true; bool ordered = false; bool debug = false; bool nosys = false; QString rootsFile, nonRootsFile; for(int n = 0; n < args.count(); ++n) { QString s = args[n]; if(!s.startsWith("--")) continue; QString var; QString val; int x = s.indexOf('='); if(x != -1) { var = s.mid(2, x - 2); val = s.mid(x + 1); } else { var = s.mid(2); } bool known = true; if(var == "pass") { have_pass = true; pass = val.toUtf8(); } else if(var == "newpass") { have_newpass = true; newpass = val.toUtf8(); } else if(var == "noprompt") allowprompt = false; else if(var == "ordered") ordered = true; else if(var == "debug") debug = true; else if(var == "roots") rootsFile = val; else if(var == "nonroots") nonRootsFile = val; else if(var == "nosys") nosys = true; else known = false; if(known) { args.removeAt(n); --n; // adjust position } } // TODO: instead of printing full usage at every wrong turn, we might // try to print something closer to the context. // TODO: use --debug for more stuff besides plugins // help if(args[0] == "help" || args[0] == "--help" || args[0] == "-h") { usage(); return 0; } // version if(args[0] == "version" || args[0] == "--version" || args[0] == "-v") { int ver = qcaVersion(); int maj = (ver >> 16) & 0xff; int min = (ver >> 8) & 0xff; int bug = ver & 0xff; printf("qcatool version %s by Justin Karneges\n", VERSION); printf("Using QCA version %d.%d.%d\n", maj, min, bug); return 0; } // show plugins if(args[0] == "plugins") { printf("Qt Library Paths:\n"); QStringList paths = QCoreApplication::libraryPaths(); if(!paths.isEmpty()) { for(int n = 0; n < paths.count(); ++n) { printf(" %s\n", qPrintable(paths[n])); } } else printf(" (none)\n"); QCA::ProviderList list = QCA::providers(); if(debug) { QString str = QCA::pluginDiagnosticText(); QCA::clearPluginDiagnosticText(); QStringList lines = str.split('\n', QString::SkipEmptyParts); for(int n = 0; n < lines.count(); ++n) printf("qca: %s\n", qPrintable(lines[n])); } printf("Available Providers:\n"); if(!list.isEmpty()) { for(int n = 0; n < list.count(); ++n) { printf(" %s\n", qPrintable(list[n]->name())); QString credit = list[n]->credit(); if(!credit.isEmpty()) { QStringList lines = wrapstring(credit, 74); foreach(QString s, lines) printf(" %s\n", qPrintable(s)); } } } else printf(" (none)\n"); QCA::unloadAllPlugins(); if(debug) { QString str = QCA::pluginDiagnosticText(); QCA::clearPluginDiagnosticText(); QStringList lines = str.split('\n', QString::SkipEmptyParts); for(int n = 0; n < lines.count(); ++n) printf("qca: %s\n", qPrintable(lines[n])); } return 0; } // config stuff if(args[0] == "config") { if(args.count() < 2) { usage(); return 1; } if(args[1] == "save") { if(args.count() < 3) { usage(); return 1; } QString name = args[2]; if(!QCA::findProvider(name)) { fprintf(stderr, "Error: no such provider '%s'.\n", qPrintable(name)); return 1; } QVariantMap map1 = QCA::getProviderConfig(name); if(map1.isEmpty()) { fprintf(stderr, "Error: provider does not support configuration.\n"); return 1; } // set and save QCA::setProviderConfig(name, map1); QCA::saveProviderConfig(name); printf("Done.\n"); return 0; } else { usage(); return 1; } } // for all other commands, we set up keystore/prompter: // enable console passphrase prompt PassphrasePromptThread passphrasePrompt; if(!allowprompt) passphrasePrompt.pp->allowPrompt = false; if(have_pass) passphrasePrompt.pp->setExplicitPassword(pass); // activate the KeyStoreManager and block until ready QCA::KeyStoreManager::start(); { QCA::KeyStoreManager ksm; ksm.waitForBusyFinished(); } // TODO: for each kind of operation, we need to check for support first!! if(args[0] == "key") { if(args.count() < 2) { usage(); return 1; } if(args[1] == "make") { if(args.count() < 4) { usage(); return 1; } bool genrsa; int bits; if(args[2] == "rsa") { genrsa = true; bits = args[3].toInt(); if(bits < 512) { fprintf(stderr, "Error: RSA bits must be at least 512.\n"); return 1; } } else if(args[2] == "dsa") { genrsa = false; bits = args[3].toInt(); if(bits != 512 && bits != 768 && bits != 1024) { fprintf(stderr, "Error: DSA bits must be 512, 768, or 1024.\n"); return 1; } } else { usage(); return 1; } if(!allowprompt && !have_newpass) { fprintf(stderr, "Error: no passphrase specified (use '--newpass=' for none).\n"); return 1; } QCA::PrivateKey priv; QString pubFileName, privFileName; if(genrsa) { // note: third arg is bogus, doesn't apply to RSA priv = AnimatedKeyGen::makeKey(QCA::PKey::RSA, bits, QCA::DSA_512); pubFileName = "rsapub.pem"; privFileName = "rsapriv.pem"; } else // dsa { QCA::DLGroupSet set; if(bits == 512) set = QCA::DSA_512; else if(bits == 768) set = QCA::DSA_768; else // 1024 set = QCA::DSA_1024; // note: second arg is bogus, doesn't apply to DSA priv = AnimatedKeyGen::makeKey(QCA::PKey::DSA, 0, set); pubFileName = "dsapub.pem"; privFileName = "dsapriv.pem"; } if(priv.isNull()) { fprintf(stderr, "Error: unable to generate key.\n"); return 1; } QCA::PublicKey pub = priv.toPublicKey(); // prompt for new passphrase if necessary if(!have_newpass) { while(!promptForNewPassphrase(&newpass)) { } have_newpass = true; } if(pub.toPEMFile(pubFileName)) printf("Public key saved to %s\n", qPrintable(pubFileName)); else { fprintf(stderr, "Error: can't encode/write %s\n", qPrintable(pubFileName)); return 1; } bool ok; if(!newpass.isEmpty()) ok = priv.toPEMFile(privFileName, newpass); else ok = priv.toPEMFile(privFileName); if(ok) printf("Private key saved to %s\n", qPrintable(privFileName)); else { fprintf(stderr, "Error: can't encode/write %s\n", qPrintable(privFileName)); return 1; } } else if(args[1] == "changepass") { if(args.count() < 3) { usage(); return 1; } // TODO: is it weird passing 'pass' here? QCA::PrivateKey priv = get_K(args[2], pass); if(priv.isNull()) return 1; if(!allowprompt && !have_newpass) { fprintf(stderr, "Error: no passphrase specified (use '--newpass=' for none).\n"); return 1; } // prompt for new passphrase if necessary if(!have_newpass) { while(!promptForNewPassphrase(&newpass)) { } have_newpass = true; } QString out; if(!newpass.isEmpty()) out = priv.toPEM(newpass); else out = priv.toPEM(); if(!out.isEmpty()) printf("%s", qPrintable(out)); else { fprintf(stderr, "Error: can't encode key.\n"); return 1; } } else { usage(); return 1; } } else if(args[0] == "cert") { if(args.count() < 2) { usage(); return 1; } if(args[1] == "makereq" || args[1] == "makereqadv") { if(args.count() < 3) { usage(); return 1; } // TODO: same as before QCA::PrivateKey priv = get_K(args[2], pass); if(priv.isNull()) return 1; printf("\n"); bool advanced = (args[1] == "makereqadv") ? true: false; QCA::CertificateOptions opts = promptForCertAttributes(advanced, true); QCA::CertificateRequest req(opts, priv); QString reqname = "certreq.pem"; if(req.toPEMFile(reqname)) printf("Certificate request saved to %s\n", qPrintable(reqname)); else { fprintf(stderr, "Error: can't encode/write %s\n", qPrintable(reqname)); return 1; } } else if(args[1] == "makeself" || args[1] == "makeselfadv") { if(args.count() < 3) { usage(); return 1; } // TODO: same as before QCA::PrivateKey priv = get_K(args[2], pass); if(priv.isNull()) return 1; printf("\n"); bool advanced = (args[1] == "makeselfadv") ? true: false; QCA::CertificateOptions opts = promptForCertAttributes(advanced, false); QCA::Certificate cert(opts, priv); QString certname = "cert.pem"; if(cert.toPEMFile(certname)) printf("Certificate saved to %s\n", qPrintable(certname)); else { fprintf(stderr, "Error: can't encode/write %s\n", qPrintable(certname)); return 1; } } else if(args[1] == "validate") { if(args.count() < 3) { usage(); return 1; } QCA::Certificate target = get_C(args[2]); if(target.isNull()) return 1; // get roots QCA::CertificateCollection roots; if(!nosys) roots += QCA::systemStore(); if(!rootsFile.isEmpty()) roots += QCA::CertificateCollection::fromFlatTextFile(rootsFile); // get nonroots QCA::CertificateCollection nonroots; if(!nonRootsFile.isEmpty()) nonroots = QCA::CertificateCollection::fromFlatTextFile(nonRootsFile); QCA::Validity v = target.validate(roots, nonroots); if(v == QCA::ValidityGood) printf("Certificate is valid\n"); else { printf("Certificate is NOT valid: %s\n", qPrintable(validityToString(v))); return 1; } } else { usage(); return 1; } } else if(args[0] == "keybundle") { if(args.count() < 2) { usage(); return 1; } if(args[1] == "make") { if(args.count() < 4) { usage(); return 1; } // TODO: same as before QCA::PrivateKey priv = get_K(args[2], pass); if(priv.isNull()) return 1; QCA::Certificate cert = get_C(args[3]); if(cert.isNull()) return 1; // get roots QCA::CertificateCollection roots; if(!nosys) roots += QCA::systemStore(); if(!rootsFile.isEmpty()) roots += QCA::CertificateCollection::fromFlatTextFile(rootsFile); // get nonroots QCA::CertificateCollection nonroots; if(!nonRootsFile.isEmpty()) nonroots = QCA::CertificateCollection::fromFlatTextFile(nonRootsFile); QList issuer_pool = roots.certificates() + nonroots.certificates(); QCA::CertificateChain chain; chain += cert; chain = chain.complete(issuer_pool); QCA::KeyBundle key; key.setName(chain.primary().commonName()); key.setCertificateChainAndKey(chain, priv); if(!allowprompt && !have_newpass) { fprintf(stderr, "Error: no passphrase specified (use '--newpass=' for none).\n"); return 1; } // prompt for new passphrase if necessary if(!have_newpass) { while(!promptForNewPassphrase(&newpass)) { } have_newpass = true; } if(newpass.isEmpty()) { fprintf(stderr, "Error: keybundles cannot have empty passphrases.\n"); return 1; } QString newFileName = "cert.p12"; if(key.toFile(newFileName, newpass)) printf("Keybundle saved to %s\n", qPrintable(newFileName)); else { fprintf(stderr, "Error: can't encode keybundle.\n"); return 1; } } else if(args[1] == "extract") { if(args.count() < 3) { usage(); return 1; } QCA::KeyBundle key = get_X(args[2]); if(key.isNull()) return 1; QCA::PrivateKey priv = key.privateKey(); bool export_priv = priv.canExport(); if(export_priv) { fprintf(stderr, "You will need to create a passphrase for the extracted private key.\n"); if(!allowprompt && !have_newpass) { fprintf(stderr, "Error: no passphrase specified (use '--newpass=' for none).\n"); return 1; } // prompt for new passphrase if necessary if(!have_newpass) { while(!promptForNewPassphrase(&newpass)) { } have_newpass = true; } } printf("Certs: (first is primary)\n"); QCA::CertificateChain chain = key.certificateChain(); for(int n = 0; n < chain.count(); ++n) printf("%s", qPrintable(chain[n].toPEM())); printf("Private Key:\n"); if(export_priv) { QString out; if(!newpass.isEmpty()) out = priv.toPEM(newpass); else out = priv.toPEM(); printf("%s", qPrintable(out)); } else { printf("(Key is not exportable)\n"); } } else if(args[1] == "changepass") { if(args.count() < 3) { usage(); return 1; } QCA::KeyBundle key = get_X(args[2]); if(key.isNull()) return 1; if(!key.privateKey().canExport()) { fprintf(stderr, "Error: private key not exportable.\n"); return 1; } if(!allowprompt && !have_newpass) { fprintf(stderr, "Error: no passphrase specified (use '--newpass=' for none).\n"); return 1; } // prompt for new passphrase if necessary if(!have_newpass) { while(!promptForNewPassphrase(&newpass)) { } have_newpass = true; } if(newpass.isEmpty()) { fprintf(stderr, "Error: keybundles cannot have empty passphrases.\n"); return 1; } QFileInfo fi(args[2]); QString newFileName = fi.baseName() + "_new.p12"; if(key.toFile(newFileName, newpass)) printf("Keybundle saved to %s\n", qPrintable(newFileName)); else { fprintf(stderr, "Error: can't encode keybundle.\n"); return 1; } } else { usage(); return 1; } } else if(args[0] == "keystore") { if(args.count() < 2) { usage(); return 1; } if(args[1] == "list-stores") { QCA::KeyStoreManager ksm; QStringList storeList = ksm.keyStores(); // find longest id int longest_id = -1; for(int n = 0; n < storeList.count(); ++n) { if(longest_id == -1 || storeList[n].length() > longest_id) longest_id = storeList[n].length(); } for(int n = 0; n < storeList.count(); ++n) { QCA::KeyStore ks(storeList[n], &ksm); QString type = kstype_to_string(ks.type()); // give all ids the same width QString id = ks.id(); QString str; str.fill(' ', longest_id); str.replace(0, id.length(), id); printf("%s %s [%s]\n", qPrintable(type), qPrintable(str), qPrintable(ks.name())); } } else if(args[1] == "list") { if(args.count() < 3) { usage(); return 1; } QCA::KeyStoreManager ksm; QCA::KeyStore store(getKeyStore(args[2]), &ksm); if(!store.isValid()) { fprintf(stderr, "Error: no such store\n"); return 1; } QList list = store.entryList(); for(int n = 0; n < list.count(); ++n) { QCA::KeyStoreEntry i = list[n]; QString type = ksentrytype_to_string(i.type()); printf("%s %s [%s]\n", qPrintable(type), qPrintable(idHash(i.id())), qPrintable(i.name())); } } else if(args[1] == "export") { if(args.count() < 3) { usage(); return 1; } QCA::KeyStoreEntry entry = get_E(args[2]); if(entry.isNull()) return 1; if(entry.type() == QCA::KeyStoreEntry::TypeCertificate) printf("%s", qPrintable(entry.certificate().toPEM())); else if(entry.type() == QCA::KeyStoreEntry::TypeCRL) printf("%s", qPrintable(entry.crl().toPEM())); else if(entry.type() == QCA::KeyStoreEntry::TypePGPPublicKey || entry.type() == QCA::KeyStoreEntry::TypePGPSecretKey) printf("%s", qPrintable(entry.pgpPublicKey().toString())); else if(entry.type() == QCA::KeyStoreEntry::TypeKeyBundle) fprintf(stderr, "Error: use 'keybundle extract' command instead.\n"); else fprintf(stderr, "Error: cannot export type '%d'.\n", entry.type()); } else if(args[1] == "exportref") { if(args.count() < 3) { usage(); return 1; } QCA::KeyStoreEntry entry = get_E(args[2]); if(entry.isNull()) return 1; printf("%s", make_ksentry_string(entry.toString()).toUtf8().data()); } else if(args[1] == "addkb") { if(args.count() < 4) { usage(); return 1; } QCA::KeyStoreManager ksm; QCA::KeyStore store(getKeyStore(args[2]), &ksm); if(!store.isValid()) { fprintf(stderr, "Error: no such store\n"); return 1; } QCA::Certificate cert = get_C(args[3]); if(cert.isNull()) return 1; if(!store.writeEntry(cert).isEmpty()) printf("Entry written.\n"); else { fprintf(stderr, "Error: unable to write entry.\n"); return 1; } } else if(args[1] == "addpgp") { if(args.count() < 4) { usage(); return 1; } QCA::KeyStoreManager ksm; QCA::KeyStore store(getKeyStore(args[2]), &ksm); if(!store.isValid()) { fprintf(stderr, "Error: no such store\n"); return 1; } QCA::PGPKey pub = QCA::PGPKey::fromFile(args[3]); if(pub.isNull()) return 1; if(!store.writeEntry(pub).isEmpty()) printf("Entry written.\n"); else { fprintf(stderr, "Error: unable to write entry.\n"); return 1; } } else if(args[1] == "remove") { if(args.count() < 3) { usage(); return 1; } QCA::KeyStoreEntry entry = get_E(args[2]); if(entry.isNull()) return 1; QCA::KeyStoreManager ksm; QCA::KeyStore store(entry.storeId(), &ksm); if(!store.isValid()) { fprintf(stderr, "Error: no such store\n"); return 1; } if(store.removeEntry(entry.id())) printf("Entry removed.\n"); else { fprintf(stderr, "Error: unable to remove entry.\n"); return 1; } } else { usage(); return 1; } } else if(args[0] == "show") { if(args.count() < 2) { usage(); return 1; } if(args[1] == "cert") { if(args.count() < 3) { usage(); return 1; } QCA::Certificate cert = get_C(args[2]); if(cert.isNull()) return 1; print_cert(cert, ordered); } else if(args[1] == "req") { if(args.count() < 3) { usage(); return 1; } QCA::CertificateRequest req(args[2]); if(req.isNull()) { fprintf(stderr, "Error: can't read/decode certificate request file.\n"); return 1; } print_certreq(req, ordered); } else if(args[1] == "pgp") { if(args.count() < 3) { usage(); return 1; } // FIXME: isSecret will always print false here QCA::PGPKey pub = get_P(args[2]); if(pub.isNull()) return 1; print_pgp(pub); } else { usage(); return 1; } } else if(args[0] == "message") { if(args.count() < 2) { usage(); return 1; } if(args[1] == "sign") { if(args.count() < 4) { usage(); return 1; } QCA::SecureMessageSystem *sms; QCA::SecureMessageKey skey; QCA::SecureMessage::SignMode mode; bool pgp = false; if(args[2] == "pgp") { QPair key = get_S(args[3]); if(key.first.isNull()) return 1; sms = new QCA::OpenPGP; skey.setPGPSecretKey(key.first); mode = QCA::SecureMessage::Clearsign; pgp = true; } else if(args[2] == "pgpdetach") { QPair key = get_S(args[3]); if(key.first.isNull()) return 1; sms = new QCA::OpenPGP; skey.setPGPSecretKey(key.first); mode = QCA::SecureMessage::Detached; pgp = true; } else if(args[2] == "smime") { QCA::KeyBundle key = get_X(args[3]); if(key.isNull()) return 1; // get nonroots QCA::CertificateCollection nonroots; if(!nonRootsFile.isEmpty()) nonroots = QCA::CertificateCollection::fromFlatTextFile(nonRootsFile); QList issuer_pool = nonroots.certificates(); QCA::CertificateChain chain = key.certificateChain(); chain = chain.complete(issuer_pool); sms = new QCA::CMS; skey.setX509CertificateChain(chain); skey.setX509PrivateKey(key.privateKey()); mode = QCA::SecureMessage::Detached; } else { usage(); return 1; } // TODO: support streaming someday ? we need support in // the provider as well as our smime envelope stuff // read input data from stdin all at once QByteArray plain; while(!feof(stdin)) { QByteArray block(1024, 0); int n = fread(block.data(), 1, 1024, stdin); if(n < 0) break; block.resize(n); plain += block; } // smime envelope if(!pgp) { QString text = add_cr(QString::fromUtf8(plain)); plain = QString(mime_signpart).arg(text).toUtf8(); } QCA::SecureMessage *msg = new QCA::SecureMessage(sms); msg->setSigner(skey); // pgp should always be ascii if(pgp) msg->setFormat(QCA::SecureMessage::Ascii); msg->startSign(mode); msg->update(plain); msg->end(); msg->waitForFinished(-1); if(!msg->success()) { QString errstr = smErrorToString(msg->errorCode()); delete msg; delete sms; fprintf(stderr, "Error: unable to sign: %s\n", qPrintable(errstr)); return 1; } QString hashName = msg->hashName(); QByteArray output; if(mode == QCA::SecureMessage::Detached) output = msg->signature(); else output = msg->read(); delete msg; delete sms; // smime envelope if(!pgp) { QCA::Base64 enc; enc.setLineBreaksEnabled(true); enc.setLineBreaksColumn(76); QString sigtext = add_cr(enc.arrayToString(output)); QString str = QString(mime_signed).arg(hashName).arg(QString::fromUtf8(plain)).arg(sigtext); output = str.toUtf8(); } printf("%s", output.data()); } else if(args[1] == "encrypt") { if(args.count() < 4) { usage(); return 1; } QCA::SecureMessageSystem *sms; QCA::SecureMessageKey skey; bool pgp = false; if(args[2] == "pgp") { QCA::PGPKey key = get_P(args[3]); if(key.isNull()) return 1; sms = new QCA::OpenPGP; skey.setPGPPublicKey(key); pgp = true; } else if(args[2] == "smime") { QCA::Certificate cert = get_C(args[3]); if(cert.isNull()) return 1; sms = new QCA::CMS; skey.setX509CertificateChain(cert); } else { usage(); return 1; } // read input data from stdin all at once QByteArray plain; while(!feof(stdin)) { QByteArray block(1024, 0); int n = fread(block.data(), 1, 1024, stdin); if(n < 0) break; block.resize(n); plain += block; } QCA::SecureMessage *msg = new QCA::SecureMessage(sms); msg->setRecipient(skey); // pgp should always be ascii if(pgp) msg->setFormat(QCA::SecureMessage::Ascii); msg->startEncrypt(); msg->update(plain); msg->end(); msg->waitForFinished(-1); if(!msg->success()) { QString errstr = smErrorToString(msg->errorCode()); delete msg; delete sms; fprintf(stderr, "Error: unable to encrypt: %s\n", qPrintable(errstr)); return 1; } QByteArray output = msg->read(); delete msg; delete sms; // smime envelope if(!pgp) { QCA::Base64 enc; enc.setLineBreaksEnabled(true); enc.setLineBreaksColumn(76); QString enctext = add_cr(enc.arrayToString(output)); QString str = QString(mime_enveloped).arg(enctext); output = str.toUtf8(); } printf("%s", output.data()); } else if(args[1] == "signencrypt") { if(args.count() < 4) { usage(); return 1; } QCA::SecureMessageSystem *sms; QCA::SecureMessageKey skey; QCA::SecureMessageKey rkey; { QPair sec = get_S(args[2]); if(sec.first.isNull()) return 1; QCA::PGPKey pub = get_P(args[3]); if(pub.isNull()) return 1; sms = new QCA::OpenPGP; skey.setPGPSecretKey(sec.first); rkey.setPGPPublicKey(pub); } // read input data from stdin all at once QByteArray plain; while(!feof(stdin)) { QByteArray block(1024, 0); int n = fread(block.data(), 1, 1024, stdin); if(n < 0) break; block.resize(n); plain += block; } QCA::SecureMessage *msg = new QCA::SecureMessage(sms); if(!msg->canSignAndEncrypt()) { delete msg; delete sms; fprintf(stderr, "Error: cannot perform integrated sign and encrypt.\n"); return 1; } msg->setSigner(skey); msg->setRecipient(rkey); msg->setFormat(QCA::SecureMessage::Ascii); msg->startSignAndEncrypt(); msg->update(plain); msg->end(); msg->waitForFinished(-1); if(!msg->success()) { QString errstr = smErrorToString(msg->errorCode()); delete msg; delete sms; fprintf(stderr, "Error: unable to sign and encrypt: %s\n", qPrintable(errstr)); return 1; } QByteArray output = msg->read(); delete msg; delete sms; printf("%s", output.data()); } else if(args[1] == "verify") { if(args.count() < 3) { usage(); return 1; } // TODO: CMS: allow verifying with --nonroots, in case the message // doesn't have the issuers in it. (also allow verifying if // if there is no cert at all (have to specify possible certs then)). QCA::SecureMessageSystem *sms; bool pgp = false; if(args[2] == "pgp") { sms = new QCA::OpenPGP; pgp = true; } else if(args[2] == "smime") { // get roots QCA::CertificateCollection roots; if(!nosys) roots += QCA::systemStore(); if(!rootsFile.isEmpty()) roots += QCA::CertificateCollection::fromFlatTextFile(rootsFile); sms = new QCA::CMS; ((QCA::CMS *)sms)->setTrustedCertificates(roots); } else { usage(); return 1; } QByteArray data, sig; QString smime_text; { // read input data from stdin all at once QByteArray plain; while(!feof(stdin)) { QByteArray block(1024, 0); int n = fread(block.data(), 1, 1024, stdin); if(n < 0) break; block.resize(n); plain += block; } if(pgp) { // TODO: ensure the plugin actually outputs the signed data // pgp can be either a detached signature followed // by data, or an integrated message. // detached signature? if(plain.startsWith("-----BEGIN PGP SIGNATURE-----")) { QString footer = "-----END PGP SIGNATURE-----\n"; int n = plain.indexOf(footer); if(n == -1) { delete sms; fprintf(stderr, "Error: pgp signature header, but no footer.\n"); return 1; } n += footer.length(); sig = plain.mid(0, n); data = plain.mid(n); } else { data = plain; } } else { // smime envelope QString in = QString::fromUtf8(plain); in = add_cr(in); // change the line endings?! QString str, sigtext; if(!open_mime_data_sig(in, &str, &sigtext)) { fprintf(stderr, "Error: can't parse message file.\n"); return 1; } data = str.toUtf8(); smime_text = str; QCA::Base64 dec; dec.setLineBreaksEnabled(true); sig = dec.stringToArray(rem_cr(sigtext)).toByteArray(); } } QCA::SecureMessage *msg = new QCA::SecureMessage(sms); if(pgp) msg->setFormat(QCA::SecureMessage::Ascii); msg->startVerify(sig); msg->update(data); msg->end(); msg->waitForFinished(-1); if(!msg->success()) { QString errstr = smErrorToString(msg->errorCode()); delete msg; delete sms; fprintf(stderr, "Error: verify failed: %s\n", qPrintable(errstr)); return 1; } QByteArray output; if(pgp && sig.isEmpty()) output = msg->read(); // TODO: support multiple signers? QCA::SecureMessageSignature signer = msg->signer(); QCA::SecureMessageSignature::IdentityResult r = signer.identityResult(); delete msg; delete sms; // for pgp clearsign, pgp signed (non-detached), and smime, // the signed content was inside of the message. we need // to print that content now if(pgp) { printf("%s", output.data()); } else { QString str = open_mime_envelope(smime_text); printf("%s", str.toUtf8().data()); } QString rs; if(r == QCA::SecureMessageSignature::Valid) rs = "Valid"; else if(r == QCA::SecureMessageSignature::InvalidSignature) rs = "InvalidSignature"; else if(r == QCA::SecureMessageSignature::InvalidKey) rs = "InvalidKey"; else if(r == QCA::SecureMessageSignature::NoKey) rs = "NoKey"; fprintf(stderr, "IdentityResult: %s\n", qPrintable(rs)); QCA::SecureMessageKey key = signer.key(); if(!key.isNull()) { if(pgp) { QCA::PGPKey pub = key.pgpPublicKey(); fprintf(stderr, "From: %s (%s)\n", qPrintable(pub.primaryUserId()), qPrintable(pub.keyId())); } else { QCA::Certificate cert = key.x509CertificateChain().primary(); QString emailStr; QCA::CertificateInfo info = cert.subjectInfo(); if(info.contains(QCA::Email)) emailStr = QString(" (%1)").arg(info.value(QCA::Email)); fprintf(stderr, "From: %s%s\n", qPrintable(cert.commonName()), qPrintable(emailStr)); } } } else if(args[1] == "decrypt") { if(args.count() < 3) { usage(); return 1; } QCA::SecureMessageSystem *sms; QCA::SecureMessageKey skey; bool pgp = false; if(args[2] == "pgp") { sms = new QCA::OpenPGP; pgp = true; } else if(args[2] == "smime") { if(args.count() < 4) { usage(); return 1; } QCA::KeyBundle key = get_X(args[3]); if(key.isNull()) return 1; sms = new QCA::CMS; skey.setX509CertificateChain(key.certificateChain()); skey.setX509PrivateKey(key.privateKey()); // TODO: support more than one decrypt key ((QCA::CMS*)sms)->setPrivateKeys(QCA::SecureMessageKeyList() << skey); } // read input data from stdin all at once QByteArray plain; while(!feof(stdin)) { QByteArray block(1024, 0); int n = fread(block.data(), 1, 1024, stdin); if(n < 0) break; block.resize(n); plain += block; } // smime envelope if(!pgp) { QString in = QString::fromUtf8(plain); QString str = open_mime_envelope(in); if(str.isEmpty()) { delete sms; fprintf(stderr, "Error: can't parse message file.\n"); return 1; } QCA::Base64 dec; dec.setLineBreaksEnabled(true); plain = dec.stringToArray(rem_cr(str)).toByteArray(); } QCA::SecureMessage *msg = new QCA::SecureMessage(sms); if(pgp) msg->setFormat(QCA::SecureMessage::Ascii); msg->startDecrypt(); msg->update(plain); msg->end(); msg->waitForFinished(-1); if(!msg->success()) { QString errstr = smErrorToString(msg->errorCode()); delete msg; delete sms; fprintf(stderr, "Error: decrypt failed: %s\n", qPrintable(errstr)); return 1; } QByteArray output = msg->read(); // TODO: support multiple signers? QCA::SecureMessageSignature signer; bool wasSigned = false; if(msg->wasSigned()) { signer = msg->signer(); wasSigned = true; } delete msg; delete sms; printf("%s", output.data()); if(wasSigned) { fprintf(stderr, "Message was also signed:\n"); QCA::SecureMessageSignature::IdentityResult r = signer.identityResult(); QString rs; if(r == QCA::SecureMessageSignature::Valid) rs = "Valid"; else if(r == QCA::SecureMessageSignature::InvalidSignature) rs = "InvalidSignature"; else if(r == QCA::SecureMessageSignature::InvalidKey) rs = "InvalidKey"; else if(r == QCA::SecureMessageSignature::NoKey) rs = "NoKey"; fprintf(stderr, "IdentityResult: %s\n", qPrintable(rs)); QCA::SecureMessageKey key = signer.key(); if(!key.isNull()) { QCA::PGPKey pub = key.pgpPublicKey(); fprintf(stderr, "From: %s (%s)\n", qPrintable(pub.primaryUserId()), qPrintable(pub.keyId())); } } } else if(args[1] == "exportcerts") { // TODO: can we do this with PKCS7 (certcollection) rather than smime verify? QCA::SecureMessageSystem *sms; // get roots QCA::CertificateCollection roots; if(!nosys) roots += QCA::systemStore(); if(!rootsFile.isEmpty()) roots += QCA::CertificateCollection::fromFlatTextFile(rootsFile); sms = new QCA::CMS; ((QCA::CMS *)sms)->setTrustedCertificates(roots); QByteArray data, sig; QString smime_text; { // read input data from stdin all at once QByteArray plain; while(!feof(stdin)) { QByteArray block(1024, 0); int n = fread(block.data(), 1, 1024, stdin); if(n < 0) break; block.resize(n); plain += block; } // smime envelope QString in = QString::fromUtf8(plain); QString str, sigtext; if(!open_mime_data_sig(in, &str, &sigtext)) { delete sms; fprintf(stderr, "Error: can't parse message file.\n"); return 1; } data = str.toUtf8(); smime_text = str; QCA::Base64 dec; dec.setLineBreaksEnabled(true); sig = dec.stringToArray(rem_cr(sigtext)).toByteArray(); } QCA::SecureMessage *msg = new QCA::SecureMessage(sms); msg->startVerify(sig); msg->update(data); msg->end(); msg->waitForFinished(-1); if(!msg->success()) { QString errstr = smErrorToString(msg->errorCode()); delete msg; delete sms; // TODO: wrong error message for export fprintf(stderr, "Error: verify failed: %s\n", qPrintable(errstr)); return 1; } // TODO: support multiple signers? QCA::SecureMessageSignature signer = msg->signer(); delete msg; delete sms; QCA::SecureMessageKey key = signer.key(); if(!key.isNull()) { foreach(const QCA::Certificate &c, key.x509CertificateChain()) printf("%s", qPrintable(c.toPEM())); } } else { usage(); return 1; } } else { usage(); return 1; } return 0; } #include "main.moc"