diff --git a/TODO b/TODO index 6075a14a..73b27449 100644 --- a/TODO +++ b/TODO @@ -1,4 +1,11 @@ -* ssl/tls (also server support) +* ciphertest: segfault?? +* rsatest: segfault?? +* ssltest: segfault on exit?? + +* cipher: option to specify size for genkey +* cert: properly check hostnames with wildcards +* ssl: server support + * sasl (also server support) * dsa diff --git a/hashtest.pro b/hashtest.pro index 7cecc391..4348ca9e 100644 --- a/hashtest.pro +++ b/hashtest.pro @@ -8,6 +8,10 @@ UI_DIR = .ui INCLUDEPATH += src INCLUDEPATH += plugins -HEADERS += src/qca.h +HEADERS += src/qca.h src/qcaprovider.h SOURCES += hashtest.cpp src/qca.cpp +#DEFINES += USE_OPENSSL +#SOURCES += plugins/qcaopenssl.cpp +#LIBS += -lcrypto -lssl + diff --git a/plugins/qcaopenssl.cpp b/plugins/qcaopenssl.cpp index 710e7c16..4f2d1025 100644 --- a/plugins/qcaopenssl.cpp +++ b/plugins/qcaopenssl.cpp @@ -13,6 +13,8 @@ #include #include +#define NO_AES + // FIXME: use openssl for entropy instead of stdlib #include static bool seeded = false; @@ -316,6 +318,7 @@ public: } }; +#ifndef NO_AES class AES128Context : public EVPCipherContext { public: @@ -345,6 +348,7 @@ public: return 0; } }; +#endif class RSAKeyContext : public QCA_RSAKeyContext { @@ -751,7 +755,7 @@ public: reset(); } - QCA_CertContext *clone() + QCA_CertContext *clone() const { CertContext *c = new CertContext(*this); if(x) { @@ -915,6 +919,7 @@ public: static bool ssl_init = false; class SSLContext : public QCA_SSLContext { + Q_OBJECT public: enum { Success, TryAgain, Error }; enum { Idle, Connect, Handshake, Active }; @@ -951,6 +956,7 @@ public: recvQueue.resize(0); mode = Idle; cc.reset(); + vr = QCA::SSL::Unknown; } bool begin(const QString &_host, const QPtrList &list) @@ -1045,7 +1051,7 @@ public: } else if(ret == Error) { reset(); - // FIXME: handshaken(false); + handshaken(false); return; } } @@ -1073,27 +1079,28 @@ public: cc.reset(); code = QCA::SSL::NoCert; } - validityResult = code; + vr = code; mode = Active; - // FIXME: handshaken(true); + handshaken(true); } else if(ret == Error) { reset(); - // FIXME: handshaken(false); + handshaken(false); return; } } - //if(isOutgoingSSLData()) { - // outgoingSSLDataReady(); - //} + if(outgoingDataReady()) { + readyReadOutgoing(); + } // try to read incoming unencrypted data sslReadAll(); - //if(isRecvData()) - // readyRead(); + if(dataReady()) { + readyRead(); + } } int resultToCV(int ret) const @@ -1154,11 +1161,16 @@ public: return rc; } - QCA_CertContext *peerCertificate() + QCA_CertContext *peerCertificate() const { return cc.clone(); } + int validityResult() const + { + return vr; + } + bool dataReady() const { return (recvQueue.size() > 0) ? true: false; @@ -1169,27 +1181,22 @@ public: return (BIO_pending(wbio) > 0) ? true: false; } - /*void putIncomingSSLData(const QByteArray &a) + void writeIncoming(const QByteArray &a) { - BIO_write(d->rbio, a.data(), a.size()); + BIO_write(rbio, a.data(), a.size()); sslUpdate(); } - bool isOutgoingSSLData() - { - return (BIO_pending(d->wbio) > 0) ? true: false; - } - - QByteArray getOutgoingSSLData() + QByteArray readOutgoing() { QByteArray a; - int size = BIO_pending(d->wbio); + int size = BIO_pending(wbio); if(size <= 0) return a; a.resize(size); - int r = BIO_read(d->wbio, a.data(), size); + int r = BIO_read(wbio, a.data(), size); if(r <= 0) { a.resize(0); return a; @@ -1200,30 +1207,20 @@ public: return a; } - void send(const QByteArray &a) + void write(const QByteArray &a) { - if(d->mode != Active) + if(mode != Active) return; - - int oldsize = d->sendQueue.size(); - d->sendQueue.resize(oldsize + a.size()); - memcpy(d->sendQueue.data() + oldsize, a.data(), a.size()); - + appendArray(&sendQueue, a); processSendQueue(); } - bool isRecvData() + QByteArray read() { - return (d->recvQueue.size() > 0) ? true: false; - } - - QByteArray recv() - { - QByteArray a = d->recvQueue; - a.detach(); - d->recvQueue.resize(0); + QByteArray a = recvQueue.copy(); + recvQueue.resize(0); return a; - }*/ + } void sslReadAll() { @@ -1258,7 +1255,7 @@ public: BIO *rbio, *wbio; QString host; CertContext cc; - int validityResult; + int vr; }; class QCAOpenSSL : public QCAProvider @@ -1274,8 +1271,10 @@ public: QCA::CAP_MD5 | QCA::CAP_BlowFish | QCA::CAP_TripleDES | +#ifndef NO_AES QCA::CAP_AES128 | QCA::CAP_AES256 | +#endif QCA::CAP_RSA | QCA::CAP_X509 | QCA::CAP_SSL; @@ -1292,10 +1291,12 @@ public: return new BlowFishContext; else if(cap == QCA::CAP_TripleDES) return new TripleDESContext; +#ifndef NO_AES else if(cap == QCA::CAP_AES128) return new AES128Context; else if(cap == QCA::CAP_AES256) return new AES256Context; +#endif else if(cap == QCA::CAP_RSA) return new RSAKeyContext; else if(cap == QCA::CAP_X509) @@ -1314,3 +1315,5 @@ QCAProvider *createProviderOpenSSL() { return (new QCAOpenSSL); } + +#include"qcaopenssl.moc" diff --git a/plugins/qcaopenssl.pro b/plugins/qcaopenssl.pro index 33484032..54bd8472 100644 --- a/plugins/qcaopenssl.pro +++ b/plugins/qcaopenssl.pro @@ -6,16 +6,13 @@ TARGET = qcaopenssl INCLUDEPATH += ../src -# Justin -INCLUDEPATH += /usr/local/include - # RH 9 INCLUDEPATH += /usr/kerberos/include -HEADERS = qcaopenssl.h +HEADERS = ../src/qcaprovider.h qcaopenssl.h SOURCES = qcaopenssl.cpp DEFINES += QCA_PLUGIN # link with OpenSSL -LIBS += -L/usr/local/lib -lcrypto +LIBS += -lcrypto -lssl diff --git a/src/qca.cpp b/src/qca.cpp index 45a695b4..9f1bd599 100644 --- a/src/qca.cpp +++ b/src/qca.cpp @@ -599,6 +599,12 @@ Cert::~Cert() delete d; } +void Cert::fromContext(QCA_CertContext *ctx) +{ + delete d->c; + d->c = ctx; +} + bool Cert::isNull() const { return d->c->isNull(); @@ -715,6 +721,9 @@ public: SSL::SSL() { d = new Private; + connect(d->c, SIGNAL(handshaken(bool)), SLOT(ctx_handshaken(bool))); + connect(d->c, SIGNAL(readyRead()), SLOT(ctx_readyRead())); + connect(d->c, SIGNAL(readyReadOutgoing()), SLOT(ctx_readyReadOutgoing())); } SSL::~SSL() @@ -724,25 +733,41 @@ SSL::~SSL() bool SSL::begin(const QString &host, const QPtrList &store) { - return false; + d->cert = Cert(); + + // convert the cert list into a context list + QPtrList list; + QPtrListIterator it(store); + for(Cert *cert; (cert = it.current()); ++it) + list.append(cert->d->c); + + // begin! + if(!d->c->begin(host, list)) + return false; + + // we don't need this anymore + list.setAutoDelete(true); + return true; } void SSL::write(const QByteArray &a) { + d->c->write(a); } QByteArray SSL::read() { - return QByteArray(); + return d->c->read(); } void SSL::writeIncoming(const QByteArray &a) { + d->c->writeIncoming(a); } QByteArray SSL::readOutgoing() { - return QByteArray(); + return d->c->readOutgoing(); } const Cert & SSL::peerCertificate() const @@ -752,5 +777,25 @@ const Cert & SSL::peerCertificate() const int SSL::certificateValidityResult() const { - return NoCert; + return d->c->validityResult(); +} + +void SSL::ctx_handshaken(bool b) +{ + if(b) { + // read the cert + QCA_CertContext *cc = d->c->peerCertificate(); + d->cert.fromContext(cc); + } + handshaken(b); +} + +void SSL::ctx_readyRead() +{ + readyRead(); +} + +void SSL::ctx_readyReadOutgoing() +{ + readyReadOutgoing(); } diff --git a/src/qca.h b/src/qca.h index d35b4016..85819474 100644 --- a/src/qca.h +++ b/src/qca.h @@ -10,6 +10,7 @@ class QCA_HashContext; class QCA_CipherContext; +class QCA_CertContext; namespace QCA { @@ -254,6 +255,9 @@ namespace QCA private: class Private; Private *d; + + friend class SSL; + void fromContext(QCA_CertContext *); }; class SSL : public QObject @@ -298,6 +302,11 @@ namespace QCA void readyRead(); void readyReadOutgoing(); + private slots: + void ctx_handshaken(bool); + void ctx_readyRead(); + void ctx_readyReadOutgoing(); + private: class Private; Private *d; diff --git a/src/qcaprovider.h b/src/qcaprovider.h index 3a4150e2..4b3af203 100644 --- a/src/qcaprovider.h +++ b/src/qcaprovider.h @@ -4,6 +4,7 @@ #include #include #include +#include #include"qca.h" #ifdef Q_WS_WIN @@ -80,7 +81,7 @@ class QCA_CertContext public: virtual ~QCA_CertContext() {} - virtual QCA_CertContext *clone()=0; + virtual QCA_CertContext *clone() const=0; virtual bool isNull() const=0; virtual bool createFromDER(const char *in, unsigned int len)=0; virtual bool createFromPEM(const char *in, unsigned int len)=0; @@ -96,12 +97,25 @@ public: virtual QDateTime notAfter() const=0; }; -class QCA_SSLContext +class QCA_SSLContext : public QObject { + Q_OBJECT public: virtual ~QCA_SSLContext() {} virtual bool begin(const QString &host, const QPtrList &store)=0; + + virtual void writeIncoming(const QByteArray &a)=0; + virtual QByteArray readOutgoing()=0; + virtual void write(const QByteArray &a)=0; + virtual QByteArray read()=0; + virtual QCA_CertContext *peerCertificate() const=0; + virtual int validityResult() const=0; + +signals: + void handshaken(bool); + void readyRead(); + void readyReadOutgoing(); }; #endif diff --git a/ssltest.cpp b/ssltest.cpp new file mode 100644 index 00000000..a6eab6b0 --- /dev/null +++ b/ssltest.cpp @@ -0,0 +1,246 @@ +#include +#include +#include +#include +#include +#include"base64.h" +#include"qca.h" + +QCA::Cert readCertXml(const QDomElement &e) +{ + QCA::Cert cert; + // there should be one child data tag + QDomElement data = e.elementsByTagName("data").item(0).toElement(); + if(!data.isNull()) + cert.fromDER(Base64::stringToArray(data.text())); + return cert; +} + +void showCertInfo(const QCA::Cert &cert) +{ + printf("-- Cert --\n"); + printf(" CN: %s\n", cert.subject()["CN"].latin1()); + printf(" Valid from: %s, until %s\n", + cert.notBefore().toString().latin1(), + cert.notAfter().toString().latin1()); + printf(" PEM:\n%s\n", cert.toPEM().latin1()); +} + +QPtrList getRootCerts() +{ + QPtrList list; + + // open the Psi rootcerts file + QFile f("/usr/local/share/psi/certs/rootcert.xml"); + if(!f.open(IO_ReadOnly)) { + printf("unable to open %s\n", f.name().latin1()); + return list; + } + QDomDocument doc; + doc.setContent(&f); + f.close(); + + QDomElement base = doc.documentElement(); + if(base.tagName() != "store") { + printf("wrong format of %s\n", f.name().latin1()); + return list; + } + QDomNodeList cl = base.elementsByTagName("certificate"); + if(cl.count() == 0) { + printf("no certs found in %s\n", f.name().latin1()); + return list; + } + + int num = 0; + for(int n = 0; n < (int)cl.count(); ++n) { + QCA::Cert *cert = new QCA::Cert(readCertXml(cl.item(n).toElement())); + if(cert->isNull()) { + printf("error reading cert\n"); + delete cert; + continue; + } + + ++num; + list.append(cert); + } + printf("imported %d root certs\n", num); + + return list; +} + +QString resultToString(int result) +{ + QString s; + switch(result) { + case QCA::SSL::NoCert: + s = QObject::tr("The server did not present a certificate."); + break; + case QCA::SSL::Valid: + break; + case QCA::SSL::HostMismatch: + s = QObject::tr("The hostname does not match the one the certificate was issued to."); + break; + case QCA::SSL::Rejected: + s = QObject::tr("Root CA is marked to reject the specified purpose."); + break; + case QCA::SSL::Untrusted: + s = QObject::tr("Certificate not trusted for the required purpose."); + break; + case QCA::SSL::SignatureFailed: + s = QObject::tr("Invalid signature."); + break; + case QCA::SSL::InvalidCA: + s = QObject::tr("Invalid CA certificate."); + break; + case QCA::SSL::InvalidPurpose: + s = QObject::tr("Invalid certificate purpose."); + break; + case QCA::SSL::SelfSigned: + s = QObject::tr("Certificate is self-signed."); + break; + case QCA::SSL::Revoked: + s = QObject::tr("Certificate has been revoked."); + break; + case QCA::SSL::PathLengthExceeded: + s = QObject::tr("Maximum certificate chain length exceeded."); + break; + case QCA::SSL::Expired: + s = QObject::tr("Certificate has expired."); + break; + case QCA::SSL::Unknown: + default: + s = QObject::tr("General certificate validation error."); + break; + } + return s; +} + +class SecureTest : public QObject +{ + Q_OBJECT +public: + SecureTest() + { + sock = new QSocket; + connect(sock, SIGNAL(connected()), SLOT(sock_connected())); + connect(sock, SIGNAL(readyRead()), SLOT(sock_readyRead())); + connect(sock, SIGNAL(connectionClosed()), SLOT(sock_connectionClosed())); + + ssl = new QCA::SSL; + connect(ssl, SIGNAL(handshaken(bool)), SLOT(ssl_handshaken(bool))); + connect(ssl, SIGNAL(readyRead()), SLOT(ssl_readyRead())); + connect(ssl, SIGNAL(readyReadOutgoing()), SLOT(ssl_readyReadOutgoing())); + + rootCerts = getRootCerts(); + } + + ~SecureTest() + { + delete ssl; + rootCerts.setAutoDelete(true); + rootCerts.clear(); + delete sock; + } + + void start(const QString &_host) + { + host = _host; + printf("Trying %s:443...\n", host.latin1()); + sock->connectToHost(host, 443); + } + +signals: + void quit(); + +private slots: + void sock_connected() + { + printf("Connected, starting TLS handshake...\n"); + ssl->begin(host, rootCerts); + } + + void sock_readyRead() + { + QByteArray buf(sock->bytesAvailable()); + int num = sock->readBlock(buf.data(), buf.size()); + if(num < (int)buf.size()) + buf.resize(num); + ssl->writeIncoming(buf); + } + + void sock_connectionClosed() + { + printf("\nConnection closed.\n"); + quit(); + } + + void ssl_handshaken(bool b) + { + if(b) { + cert = ssl->peerCertificate(); + int vr = ssl->certificateValidityResult(); + + printf("Successful SSL handshake.\n"); + if(!cert.isNull()) + showCertInfo(cert); + if(vr == QCA::SSL::Valid) + printf("Valid certificate.\n"); + else + printf("Invalid certificate: %s\n", resultToString(vr).latin1()); + + printf("Let's try a GET request now.\n"); + QString req = "GET / HTTP/1.0\nHost: " + host + "\n\n"; + QCString cs = req.latin1(); + QByteArray buf(cs.length()); + memcpy(buf.data(), cs.data(), buf.size()); + ssl->write(buf); + } + else { + printf("SSL Handshake Error!\n"); + quit(); + } + } + + void ssl_readyRead() + { + QByteArray a = ssl->read(); + QCString cs; + cs.resize(a.size()+1); + memcpy(cs.data(), a.data(), a.size()); + printf("%s", cs.data()); + } + + void ssl_readyReadOutgoing() + { + QByteArray a = ssl->readOutgoing(); + sock->writeBlock(a.data(), a.size()); + } + +private: + QString host; + QSocket *sock; + QCA::SSL *ssl; + QCA::Cert cert; + QPtrList rootCerts; +}; + +#include"ssltest.moc" + +int main(int argc, char **argv) +{ + QApplication app(argc, argv); + QString host = argc > 1 ? argv[1] : "andbit.net"; + + if(!QCA::isSupported(QCA::CAP_SSL)) { + printf("SSL not supported!\n"); + return 1; + } + + SecureTest *s = new SecureTest; + QObject::connect(s, SIGNAL(quit()), &app, SLOT(quit())); + s->start(host); + app.exec(); + delete s; + + return 0; +} diff --git a/ssltest.pro b/ssltest.pro new file mode 100644 index 00000000..64ae012e --- /dev/null +++ b/ssltest.pro @@ -0,0 +1,13 @@ +TEMPLATE = app +CONFIG += thread +TARGET = ssltest + +MOC_DIR = .moc +OBJECTS_DIR = .obj +UI_DIR = .ui + +INCLUDEPATH += src +INCLUDEPATH += plugins +HEADERS += base64.h src/qca.h +SOURCES += base64.cpp ssltest.cpp src/qca.cpp +