/* * Copyright (C) 2003-2007 Justin Karneges * Copyright (C) 2004,2005 Brad Hards * * 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 "qca_securelayer.h" #include "qca_safeobj.h" #include "qca_safetimer.h" #include "qcaprovider.h" #include #include namespace QCA { Provider::Context *getContext(const QString &type, const QString &provider); enum ResetMode { ResetSession = 0, ResetSessionAndData = 1, ResetAll = 2 }; //---------------------------------------------------------------------------- // LayerTracker //---------------------------------------------------------------------------- class LayerTracker { private: struct Item { int plain; qint64 encoded; }; int p; QList list; public: LayerTracker() { p = 0; } void reset() { p = 0; list.clear(); } void addPlain(int plain) { p += plain; } void specifyEncoded(int encoded, int plain) { // can't specify more bytes than we have if (plain > p) plain = p; p -= plain; Item i; i.plain = plain; i.encoded = encoded; list += i; } int finished(qint64 encoded) { int plain = 0; for (QList::Iterator it = list.begin(); it != list.end();) { Item &i = *it; // not enough? if (encoded < i.encoded) { i.encoded -= encoded; break; } encoded -= i.encoded; plain += i.plain; it = list.erase(it); } return plain; } }; //---------------------------------------------------------------------------- // SecureLayer //---------------------------------------------------------------------------- SecureLayer::SecureLayer(QObject *parent) : QObject(parent) { } bool SecureLayer::isClosable() const { return false; } void SecureLayer::close() { } QByteArray SecureLayer::readUnprocessed() { return QByteArray(); } //---------------------------------------------------------------------------- // TLSSession //---------------------------------------------------------------------------- TLSSession::TLSSession() { } TLSSession::TLSSession(const TLSSession &from) : Algorithm(from) { } TLSSession::~TLSSession() { } TLSSession &TLSSession::operator=(const TLSSession &from) { Algorithm::operator=(from); return *this; } bool TLSSession::isNull() const { return (!context() ? true : false); } //---------------------------------------------------------------------------- // TLS //---------------------------------------------------------------------------- class TLS::Private : public QObject { Q_OBJECT public: enum { OpStart, OpUpdate }; enum State { Inactive, Initializing, Handshaking, Connected, Closing }; class Action { public: enum Type { ReadyRead, ReadyReadOutgoing, Handshaken, Close, CheckPeerCertificate, CertificateRequested, HostNameReceived }; int type; Action(int _type) : type(_type) { } }; TLS *q; TLSContext *c; TLS::Mode mode; // signal connected flags bool connect_hostNameReceived; bool connect_certificateRequested; bool connect_peerCertificateAvailable; bool connect_handshaken; // persistent settings (survives ResetSessionAndData) CertificateChain localCert; PrivateKey localKey; CertificateCollection trusted; bool con_ssfMode; int con_minSSF, con_maxSSF; QStringList con_cipherSuites; bool tryCompress; int packet_mtu; QList issuerList; TLSSession session; // session State state; bool blocked; bool server; QString host; TLSContext::SessionInfo sessionInfo; SafeTimer actionTrigger; int op; QList actionQueue; bool need_update; bool maybe_input; bool emitted_hostNameReceived; bool emitted_certificateRequested; bool emitted_peerCertificateAvailable; // data (survives ResetSession) CertificateChain peerCert; Validity peerValidity; bool hostMismatch; Error errorCode; // stream i/o QByteArray in, out; QByteArray to_net, from_net; QByteArray unprocessed; int out_pending; int to_net_encoded; LayerTracker layer; // datagram i/o QList packet_in, packet_out; QList packet_to_net, packet_from_net; int packet_out_pending; // packet count QList packet_to_net_encoded; Private(TLS *_q, TLS::Mode _mode) : QObject(_q) , q(_q) , mode(_mode) , actionTrigger(this) { // c is 0 during initial reset, so we don't redundantly reset it c = nullptr; connect_hostNameReceived = false; connect_certificateRequested = false; connect_peerCertificateAvailable = false; connect_handshaken = false; server = false; connect(&actionTrigger, &SafeTimer::timeout, this, &Private::doNextAction); actionTrigger.setSingleShot(true); reset(ResetAll); c = static_cast(q->context()); // parent the context to us, so that moveToThread works c->setParent(this); connect(c, &TLSContext::resultsReady, this, &Private::tls_resultsReady); connect(c, &TLSContext::dtlsTimeout, this, &Private::tls_dtlsTimeout); } ~Private() override { // context is owned by Algorithm, unparent so we don't double-delete c->setParent(nullptr); } void reset(ResetMode mode) { if (c) c->reset(); // if we reset while in client mode, then clear this list // (it should only persist when used for server mode) if (!server) issuerList.clear(); state = Inactive; blocked = false; server = false; host = QString(); sessionInfo = TLSContext::SessionInfo(); actionTrigger.stop(); op = -1; actionQueue.clear(); need_update = false; maybe_input = false; emitted_hostNameReceived = false; emitted_certificateRequested = false; emitted_peerCertificateAvailable = false; out.clear(); out_pending = 0; packet_out.clear(); packet_out_pending = 0; if (mode >= ResetSessionAndData) { peerCert = CertificateChain(); peerValidity = ErrorValidityUnknown; hostMismatch = false; errorCode = (TLS::Error)-1; in.clear(); to_net.clear(); from_net.clear(); unprocessed.clear(); to_net_encoded = 0; layer.reset(); packet_in.clear(); packet_to_net.clear(); packet_from_net.clear(); packet_to_net_encoded.clear(); } if (mode >= ResetAll) { localCert = CertificateChain(); localKey = PrivateKey(); trusted = CertificateCollection(); con_ssfMode = true; con_minSSF = 128; con_maxSSF = -1; con_cipherSuites = QStringList(); tryCompress = false; packet_mtu = -1; issuerList.clear(); session = TLSSession(); } } void start(bool serverMode) { state = Initializing; server = serverMode; c->setup(serverMode, host, tryCompress); if (con_ssfMode) c->setConstraints(con_minSSF, con_maxSSF); else c->setConstraints(con_cipherSuites); c->setCertificate(localCert, localKey); c->setTrustedCertificates(trusted); if (serverMode) c->setIssuerList(issuerList); if (!session.isNull()) { TLSSessionContext *sc = static_cast(session.context()); c->setSessionId(*sc); } c->setMTU(packet_mtu); QCA_logTextMessage(QStringLiteral("tls[%1]: c->start()").arg(q->objectName()), Logger::Information); op = OpStart; c->start(); } void close() { QCA_logTextMessage(QStringLiteral("tls[%1]: close").arg(q->objectName()), Logger::Information); if (state != Connected) return; state = Closing; c->shutdown(); } void continueAfterStep() { QCA_logTextMessage(QStringLiteral("tls[%1]: continueAfterStep").arg(q->objectName()), Logger::Information); if (!blocked) return; blocked = false; update(); } void processNextAction() { if (actionQueue.isEmpty()) { if (need_update) { QCA_logTextMessage(QStringLiteral("tls[%1]: need_update").arg(q->objectName()), Logger::Information); update(); } return; } const Action a = actionQueue.takeFirst(); // set up for the next one, if necessary if (!actionQueue.isEmpty() || need_update) { if (!actionTrigger.isActive()) actionTrigger.start(); } if (a.type == Action::ReadyRead) { emit q->readyRead(); } else if (a.type == Action::ReadyReadOutgoing) { emit q->readyReadOutgoing(); } else if (a.type == Action::Handshaken) { state = Connected; // write any app data waiting during handshake if (!out.isEmpty()) { need_update = true; if (!actionTrigger.isActive()) actionTrigger.start(); } QCA_logTextMessage(QStringLiteral("tls[%1]: handshaken").arg(q->objectName()), Logger::Information); if (connect_handshaken) { blocked = true; emit q->handshaken(); } } else if (a.type == Action::Close) { unprocessed = c->unprocessed(); reset(ResetSession); emit q->closed(); } else if (a.type == Action::CheckPeerCertificate) { peerCert = c->peerCertificateChain(); if (!peerCert.isEmpty()) { peerValidity = c->peerCertificateValidity(); if (peerValidity == ValidityGood && !host.isEmpty() && !peerCert.primary().matchesHostName(host)) hostMismatch = true; } if (connect_peerCertificateAvailable) { blocked = true; emitted_peerCertificateAvailable = true; emit q->peerCertificateAvailable(); } } else if (a.type == Action::CertificateRequested) { issuerList = c->issuerList(); if (connect_certificateRequested) { blocked = true; emitted_certificateRequested = true; emit q->certificateRequested(); } } else if (a.type == Action::HostNameReceived) { if (connect_hostNameReceived) { blocked = true; emitted_hostNameReceived = true; emit q->hostNameReceived(); } } } void update() { QCA_logTextMessage(QStringLiteral("tls[%1]: update").arg(q->objectName()), Logger::Information); if (blocked) { QCA_logTextMessage(QStringLiteral("tls[%1]: ignoring update while blocked").arg(q->objectName()), Logger::Information); return; } if (!actionQueue.isEmpty()) { QCA_logTextMessage(QStringLiteral("tls[%1]: ignoring update while processing actions").arg(q->objectName()), Logger::Information); need_update = true; return; } // only allow one operation at a time if (op != -1) { QCA_logTextMessage(QStringLiteral("tls[%1]: ignoring update while operation active").arg(q->objectName()), Logger::Information); need_update = true; return; } need_update = false; QByteArray arg_from_net, arg_from_app; if (state == Handshaking) { // during handshake, only send from_net (no app data) if (mode == TLS::Stream) { arg_from_net = from_net; from_net.clear(); } else { // note: there may not be a packet if (!packet_from_net.isEmpty()) arg_from_net = packet_from_net.takeFirst(); } } else { if (mode == TLS::Stream) { if (!from_net.isEmpty()) { arg_from_net = from_net; from_net.clear(); } if (!out.isEmpty()) { out_pending += out.size(); arg_from_app = out; out.clear(); } } else { if (!packet_from_net.isEmpty()) arg_from_net = packet_from_net.takeFirst(); if (!packet_out.isEmpty()) { arg_from_app = packet_out.takeFirst(); ++packet_out_pending; } } } if (arg_from_net.isEmpty() && arg_from_app.isEmpty() && !maybe_input) { QCA_logTextMessage( QStringLiteral("tls[%1]: ignoring update: no output and no expected input").arg(q->objectName()), Logger::Information); return; } // clear this flag maybe_input = false; QCA_logTextMessage(QStringLiteral("tls[%1]: c->update").arg(q->objectName()), Logger::Information); op = OpUpdate; c->update(arg_from_net, arg_from_app); } void start_finished() { const bool ok = c->result() == TLSContext::Success; if (!ok) { reset(ResetSession); errorCode = TLS::ErrorInit; emit q->error(); return; } state = Handshaking; // immediately update so we can get the first packet to send maybe_input = true; update(); } void update_finished() { const TLSContext::Result r = c->result(); if (r == TLSContext::Error) { if (state == Handshaking || state == Closing) { reset(ResetSession); errorCode = ErrorHandshake; } else { reset(ResetSession); errorCode = ErrorCrypt; } emit q->error(); return; } const QByteArray c_to_net = c->to_net(); if (!c_to_net.isEmpty()) { QCA_logTextMessage( QStringLiteral("tls[%1]: to_net %2").arg(q->objectName(), QString::number(c_to_net.size())), Logger::Information); } if (state == Closing) { if (mode == TLS::Stream) to_net += c_to_net; else packet_to_net += c_to_net; if (!c_to_net.isEmpty()) actionQueue += Action(Action::ReadyReadOutgoing); if (r == TLSContext::Success) actionQueue += Action(Action::Close); processNextAction(); return; } else if (state == Handshaking) { if (mode == TLS::Stream) to_net += c_to_net; else packet_to_net += c_to_net; if (!c_to_net.isEmpty()) actionQueue += Action(Action::ReadyReadOutgoing); bool clientHello = false; bool serverHello = false; if (server) clientHello = c->clientHelloReceived(); else serverHello = c->serverHelloReceived(); // client specifies a host? if (!emitted_hostNameReceived && clientHello) { host = c->hostName(); if (!host.isEmpty()) actionQueue += Action(Action::HostNameReceived); } // successful handshake or server hello means there might be a peer cert if (!emitted_peerCertificateAvailable && (r == TLSContext::Success || (!server && serverHello))) actionQueue += Action(Action::CheckPeerCertificate); // server requests a cert from us? if (!emitted_certificateRequested && (serverHello && c->certificateRequested())) actionQueue += Action(Action::CertificateRequested); if (r == TLSContext::Success) { sessionInfo = c->sessionInfo(); if (sessionInfo.id) { TLSSessionContext *sc = static_cast(sessionInfo.id->clone()); session.change(sc); } actionQueue += Action(Action::Handshaken); } processNextAction(); return; } else // Connected { const QByteArray c_to_app = c->to_app(); if (!c_to_app.isEmpty()) { QCA_logTextMessage( QStringLiteral("tls[%1]: to_app %2").arg(q->objectName(), QString::number(c_to_app.size())), Logger::Information); } const bool eof = c->eof(); int enc = -1; if (!c_to_net.isEmpty()) enc = c->encoded(); bool io_pending = false; if (mode == TLS::Stream) { if (!c_to_net.isEmpty()) out_pending -= enc; if (out_pending > 0) { maybe_input = true; io_pending = true; } if (!out.isEmpty()) io_pending = true; } else { if (!c_to_net.isEmpty()) --packet_out_pending; if (packet_out_pending > 0) { maybe_input = true; io_pending = true; } if (!packet_out.isEmpty()) io_pending = true; } if (mode == TLS::Stream) { to_net += c_to_net; in += c_to_app; to_net_encoded += enc; } else { packet_to_net += c_to_net; packet_in += c_to_app; } if (!c_to_net.isEmpty()) actionQueue += Action(Action::ReadyReadOutgoing); if (!c_to_app.isEmpty()) actionQueue += Action(Action::ReadyRead); if (eof) { close(); maybe_input = true; } if (eof || io_pending) { QCA_logTextMessage(QStringLiteral("tls[%1]: eof || io_pending").arg(q->objectName()), Logger::Information); update(); } processNextAction(); return; } } private Q_SLOTS: void tls_resultsReady() { QCA_logTextMessage(QStringLiteral("tls[%1]: c->resultsReady()").arg(q->objectName()), Logger::Information); Q_ASSERT(op != -1); int last_op = op; op = -1; if (last_op == OpStart) start_finished(); else // OpUpdate update_finished(); } void tls_dtlsTimeout() { QCA_logTextMessage(QStringLiteral("tls[%1]: c->dtlsTimeout()").arg(q->objectName()), Logger::Information); maybe_input = true; update(); } void doNextAction() { processNextAction(); } }; TLS::TLS(QObject *parent, const QString &provider) : SecureLayer(parent) , Algorithm(QStringLiteral("tls"), provider) { d = new Private(this, TLS::Stream); } TLS::TLS(Mode mode, QObject *parent, const QString &provider) : SecureLayer(parent) , Algorithm(mode == Stream ? QStringLiteral("tls") : QStringLiteral("dtls"), provider) { d = new Private(this, mode); } TLS::~TLS() { delete d; } void TLS::reset() { d->reset(ResetAll); } QStringList TLS::supportedCipherSuites( const Version &version) const // clazy:exclude=function-args-by-value TODO make it remove the & when we break ABI { return d->c->supportedCipherSuites(version); } void TLS::setCertificate(const CertificateChain &cert, const PrivateKey &key) { d->localCert = cert; d->localKey = key; if (d->state != TLS::Private::Inactive) d->c->setCertificate(cert, key); } void TLS::setCertificate(const KeyBundle &kb) { setCertificate(kb.certificateChain(), kb.privateKey()); } CertificateCollection TLS::trustedCertificates() const { return d->trusted; } void TLS::setTrustedCertificates(const CertificateCollection &trusted) { d->trusted = trusted; if (d->state != TLS::Private::Inactive) d->c->setTrustedCertificates(trusted); } void TLS::setConstraints(SecurityLevel s) { int min = 128; switch (s) { case SL_None: min = 0; break; case SL_Integrity: min = 1; break; case SL_Export: min = 40; break; case SL_Baseline: min = 128; break; case SL_High: min = 129; break; case SL_Highest: min = qMax(129, d->c->maxSSF()); break; } d->con_ssfMode = true; d->con_minSSF = min; d->con_maxSSF = -1; if (d->state != TLS::Private::Inactive) d->c->setConstraints(d->con_minSSF, d->con_maxSSF); } void TLS::setConstraints(int minSSF, int maxSSF) { d->con_ssfMode = true; d->con_minSSF = minSSF; d->con_maxSSF = maxSSF; if (d->state != TLS::Private::Inactive) d->c->setConstraints(d->con_minSSF, d->con_maxSSF); } void TLS::setConstraints(const QStringList &cipherSuiteList) { d->con_ssfMode = false; d->con_cipherSuites = cipherSuiteList; if (d->state != TLS::Private::Inactive) d->c->setConstraints(d->con_cipherSuites); } QList TLS::issuerList() const { return d->issuerList; } void TLS::setIssuerList(const QList &issuers) { d->issuerList = issuers; if (d->state != TLS::Private::Inactive) d->c->setIssuerList(issuers); } void TLS::setSession(const TLSSession &session) { d->session = session; } bool TLS::canCompress() const { return d->c->canCompress(); } bool TLS::canSetHostName() const { return d->c->canSetHostName(); } bool TLS::compressionEnabled() const { return d->tryCompress; } void TLS::setCompressionEnabled(bool b) { d->tryCompress = b; } void TLS::startClient(const QString &host) { d->reset(ResetSessionAndData); d->host = host; d->issuerList.clear(); // client mode d->start(false); } void TLS::startServer() { d->reset(ResetSessionAndData); // server mode d->start(true); } void TLS::continueAfterStep() { d->continueAfterStep(); } bool TLS::isHandshaken() const { if (d->state == TLS::Private::Connected || d->state == TLS::Private::Closing) return true; else return false; } bool TLS::isCompressed() const { return d->sessionInfo.isCompressed; } TLS::Version TLS::version() const { return d->sessionInfo.version; } QString TLS::cipherSuite() const { return d->sessionInfo.cipherSuite; } int TLS::cipherBits() const { return d->sessionInfo.cipherBits; } int TLS::cipherMaxBits() const { return d->sessionInfo.cipherMaxBits; } TLSSession TLS::session() const { return d->session; } TLS::Error TLS::errorCode() const { return d->errorCode; } TLS::IdentityResult TLS::peerIdentityResult() const { if (d->peerCert.isEmpty()) return NoCertificate; if (d->peerValidity != ValidityGood) return InvalidCertificate; if (d->hostMismatch) return HostMismatch; return Valid; } Validity TLS::peerCertificateValidity() const { return d->peerValidity; } CertificateChain TLS::localCertificateChain() const { return d->localCert; } PrivateKey TLS::localPrivateKey() const { return d->localKey; } CertificateChain TLS::peerCertificateChain() const { return d->peerCert; } bool TLS::isClosable() const { return true; } int TLS::bytesAvailable() const { if (d->mode == Stream) return d->in.size(); else return 0; } int TLS::bytesOutgoingAvailable() const { if (d->mode == Stream) return d->to_net.size(); else return 0; } void TLS::close() { d->close(); d->update(); } void TLS::write(const QByteArray &a) { if (d->mode == Stream) { d->out.append(a); d->layer.addPlain(a.size()); } else d->packet_out.append(a); QCA_logTextMessage(QStringLiteral("tls[%1]: write").arg(objectName()), Logger::Information); d->update(); } QByteArray TLS::read() { if (d->mode == Stream) { const QByteArray a = d->in; d->in.clear(); return a; } else { if (!d->packet_in.isEmpty()) return d->packet_in.takeFirst(); else return QByteArray(); } } void TLS::writeIncoming(const QByteArray &a) { if (d->mode == Stream) d->from_net.append(a); else d->packet_from_net.append(a); QCA_logTextMessage(QStringLiteral("tls[%1]: writeIncoming %2").arg(objectName(), QString::number(a.size())), Logger::Information); d->update(); } QByteArray TLS::readOutgoing(int *plainBytes) { if (d->mode == Stream) { const QByteArray a = d->to_net; d->to_net.clear(); if (plainBytes) *plainBytes = d->to_net_encoded; d->layer.specifyEncoded(a.size(), d->to_net_encoded); d->to_net_encoded = 0; return a; } else { if (!d->packet_to_net.isEmpty()) { const QByteArray a = d->packet_to_net.takeFirst(); const int x = d->packet_to_net_encoded.takeFirst(); if (plainBytes) *plainBytes = x; return a; } else { if (plainBytes) *plainBytes = 0; return QByteArray(); } } } QByteArray TLS::readUnprocessed() { if (d->mode == Stream) { const QByteArray a = d->unprocessed; d->unprocessed.clear(); return a; } else return QByteArray(); } int TLS::convertBytesWritten(qint64 bytes) { return d->layer.finished(bytes); } int TLS::packetsAvailable() const { return d->packet_in.count(); } int TLS::packetsOutgoingAvailable() const { return d->packet_to_net.count(); } int TLS::packetMTU() const { return d->packet_mtu; } void TLS::setPacketMTU(int size) const { d->packet_mtu = size; if (d->state != TLS::Private::Inactive) d->c->setMTU(size); } void TLS::connectNotify(const QMetaMethod &signal) { if (signal == QMetaMethod::fromSignal(&TLS::hostNameReceived)) d->connect_hostNameReceived = true; else if (signal == QMetaMethod::fromSignal(&TLS::certificateRequested)) d->connect_certificateRequested = true; else if (signal == QMetaMethod::fromSignal(&TLS::peerCertificateAvailable)) d->connect_peerCertificateAvailable = true; else if (signal == QMetaMethod::fromSignal(&TLS::handshaken)) d->connect_handshaken = true; } void TLS::disconnectNotify(const QMetaMethod &signal) { if (signal == QMetaMethod::fromSignal(&TLS::hostNameReceived)) d->connect_hostNameReceived = false; else if (signal == QMetaMethod::fromSignal(&TLS::certificateRequested)) d->connect_certificateRequested = false; else if (signal == QMetaMethod::fromSignal(&TLS::peerCertificateAvailable)) d->connect_peerCertificateAvailable = false; else if (signal == QMetaMethod::fromSignal(&TLS::handshaken)) d->connect_handshaken = false; } //---------------------------------------------------------------------------- // SASL::Params //---------------------------------------------------------------------------- class SASL::Params::Private { public: bool needUsername, canSendAuthzid, needPassword, canSendRealm; }; SASL::Params::Params() : d(new Private) { } SASL::Params::Params(bool user, bool authzid, bool pass, bool realm) : d(new Private) { d->needUsername = user; d->canSendAuthzid = authzid; d->needPassword = pass; d->canSendRealm = realm; } SASL::Params::Params(const SASL::Params &from) : d(new Private(*from.d)) { } SASL::Params::~Params() { delete d; } SASL::Params &SASL::Params::operator=(const SASL::Params &from) { *d = *from.d; return *this; } bool SASL::Params::needUsername() const { return d->needUsername; } bool SASL::Params::canSendAuthzid() const { return d->canSendAuthzid; } bool SASL::Params::needPassword() const { return d->needPassword; } bool SASL::Params::canSendRealm() const { return d->canSendRealm; } //---------------------------------------------------------------------------- // SASL //---------------------------------------------------------------------------- /* These don't map, but I don't think it matters much.. SASL_TRYAGAIN (-8) transient failure (e.g., weak key) SASL_BADMAC (-9) integrity check failed -- client only codes -- SASL_WRONGMECH (-11) mechanism doesn't support requested feature SASL_NEWSECRET (-12) new secret needed -- server only codes -- SASL_TRANS (-17) One time use of a plaintext password will enable requested mechanism for user SASL_PWLOCK (-21) password locked SASL_NOCHANGE (-22) requested change was not needed */ class SASL::Private : public QObject { Q_OBJECT public: enum { OpStart, OpServerFirstStep, OpNextStep, OpTryAgain, OpUpdate }; class Action { public: enum Type { ClientStarted, NextStep, Authenticated, ReadyRead, ReadyReadOutgoing }; int type; QByteArray stepData; bool haveInit; Action(int _type) : type(_type) { } Action(int _type, const QByteArray &_stepData) : type(_type) , stepData(_stepData) { } Action(int _type, bool _haveInit, const QByteArray &_stepData) : type(_type) , stepData(_stepData) , haveInit(_haveInit) { } }; SASL *q; SASLContext *c; // persistent settings (survives ResetSessionAndData) AuthFlags auth_flags; int ssfmin, ssfmax; QString ext_authid; int ext_ssf; bool localSet, remoteSet; SASLContext::HostPort local, remote; bool set_username, set_authzid, set_password, set_realm; QString username, authzid, realm; SecureArray password; // session bool server; QStringList mechlist; QString server_realm; bool allowClientSendFirst; bool disableServerSendLast; SafeTimer actionTrigger; int op; QList actionQueue; bool need_update; bool first; bool authed; // data (survives ResetSession) QString mech; // selected mech Error errorCode; // stream i/o QByteArray in, out; QByteArray to_net, from_net; int out_pending; int to_net_encoded; LayerTracker layer; Private(SASL *_q) : QObject(_q) , q(_q) , actionTrigger(this) { c = nullptr; set_username = false; set_authzid = false; set_password = false; set_realm = false; connect(&actionTrigger, &SafeTimer::timeout, this, &Private::doNextAction); actionTrigger.setSingleShot(true); reset(ResetAll); c = static_cast(q->context()); // parent the context to us, so that moveToThread works c->setParent(this); connect(c, &SASLContext::resultsReady, this, &Private::sasl_resultsReady); } ~Private() override { // context is owned by Algorithm, unparent so we don't double-delete c->setParent(nullptr); } void reset(ResetMode mode) { if (c) c->reset(); server = false; mechlist.clear(); server_realm = QString(); allowClientSendFirst = false; disableServerSendLast = true; actionTrigger.stop(); op = -1; actionQueue.clear(); need_update = false; first = false; authed = false; out.clear(); out_pending = 0; if (mode >= ResetSessionAndData) { mech = QString(); errorCode = (SASL::Error)-1; in.clear(); to_net.clear(); from_net.clear(); to_net_encoded = 0; layer.reset(); } if (mode >= ResetAll) { auth_flags = SASL::AuthFlagsNone; ssfmin = 0; ssfmax = 0; ext_authid = QString(); ext_ssf = 0; localSet = false; remoteSet = false; local = SASLContext::HostPort(); remote = SASLContext::HostPort(); set_username = false; username = QString(); set_authzid = false; authzid = QString(); set_password = false; password = SecureArray(); set_realm = false; realm = QString(); } } void setup(const QString &service, const QString &host) { c->setup(service, host, localSet ? &local : nullptr, remoteSet ? &remote : nullptr, ext_authid, ext_ssf); c->setConstraints(auth_flags, ssfmin, ssfmax); QString *p_username = nullptr; QString *p_authzid = nullptr; SecureArray *p_password = nullptr; QString *p_realm = nullptr; if (set_username) p_username = &username; if (set_authzid) p_authzid = &authzid; if (set_password) p_password = &password; if (set_realm) p_realm = &realm; c->setClientParams(p_username, p_authzid, p_password, p_realm); } void start() { op = OpStart; first = true; if (server) { QCA_logTextMessage(QStringLiteral("sasl[%1]: c->startServer()").arg(q->objectName()), Logger::Information); c->startServer(server_realm, disableServerSendLast); } else { QCA_logTextMessage(QStringLiteral("sasl[%1]: c->startClient()").arg(q->objectName()), Logger::Information); c->startClient(mechlist, allowClientSendFirst); } } void putServerFirstStep(const QString &mech, const QByteArray *clientInit) { if (op != -1) return; QCA_logTextMessage(QStringLiteral("sasl[%1]: c->serverFirstStep()").arg(q->objectName()), Logger::Information); op = OpServerFirstStep; c->serverFirstStep(mech, clientInit); } void putStep(const QByteArray &stepData) { if (op != -1) return; QCA_logTextMessage(QStringLiteral("sasl[%1]: c->nextStep()").arg(q->objectName()), Logger::Information); op = OpNextStep; c->nextStep(stepData); } void tryAgain() { if (op != -1) return; QCA_logTextMessage(QStringLiteral("sasl[%1]: c->tryAgain()").arg(q->objectName()), Logger::Information); op = OpTryAgain; c->tryAgain(); } void processNextAction() { if (actionQueue.isEmpty()) { if (need_update) update(); return; } const Action a = actionQueue.takeFirst(); // set up for the next one, if necessary if (!actionQueue.isEmpty() || need_update) { if (!actionTrigger.isActive()) actionTrigger.start(); } if (a.type == Action::ClientStarted) { emit q->clientStarted(a.haveInit, a.stepData); } else if (a.type == Action::NextStep) { emit q->nextStep(a.stepData); } else if (a.type == Action::Authenticated) { authed = true; // write any app data waiting during authentication if (!out.isEmpty()) { need_update = true; if (!actionTrigger.isActive()) actionTrigger.start(); } QCA_logTextMessage(QStringLiteral("sasl[%1]: authenticated").arg(q->objectName()), Logger::Information); emit q->authenticated(); } else if (a.type == Action::ReadyRead) { emit q->readyRead(); } else if (a.type == Action::ReadyReadOutgoing) { emit q->readyReadOutgoing(); } } void update() { // defer writes while authenticating if (!authed) { QCA_logTextMessage( QStringLiteral("sasl[%1]: ignoring update while not yet authenticated").arg(q->objectName()), Logger::Information); return; } if (!actionQueue.isEmpty()) { QCA_logTextMessage( QStringLiteral("sasl[%1]: ignoring update while processing actions").arg(q->objectName()), Logger::Information); need_update = true; return; } // only allow one operation at a time if (op != -1) { QCA_logTextMessage(QStringLiteral("sasl[%1]: ignoring update while operation active").arg(q->objectName()), Logger::Information); need_update = true; return; } need_update = false; QCA_logTextMessage(QStringLiteral("sasl[%1]: c->update()").arg(q->objectName()), Logger::Information); op = OpUpdate; out_pending += out.size(); c->update(from_net, out); from_net.clear(); out.clear(); } private Q_SLOTS: void sasl_resultsReady() { QCA_logTextMessage(QStringLiteral("sasl[%1]: c->resultsReady()").arg(q->objectName()), Logger::Information); int last_op = op; op = -1; const SASLContext::Result r = c->result(); if (last_op == OpStart) { if (server) { if (r != SASLContext::Success) { errorCode = SASL::ErrorInit; emit q->error(); return; } emit q->serverStarted(); return; } else // client { mech = c->mech(); // fall into this logic last_op = OpTryAgain; } } else if (last_op == OpServerFirstStep) { // fall into this logic last_op = OpTryAgain; } else if (last_op == OpNextStep) { // fall into this logic last_op = OpTryAgain; } if (last_op == OpTryAgain) { if (server) { if (r == SASLContext::Continue) { emit q->nextStep(c->stepData()); return; } else if (r == SASLContext::AuthCheck) { emit q->authCheck(c->username(), c->authzid()); return; } else if (r == SASLContext::Success) { if (!disableServerSendLast) actionQueue += Action(Action::NextStep, c->stepData()); actionQueue += Action(Action::Authenticated); processNextAction(); return; } else // error { errorCode = SASL::ErrorHandshake; emit q->error(); return; } } else // client { if (first) { if (r == SASLContext::Error) { if (first) errorCode = SASL::ErrorInit; else errorCode = SASL::ErrorHandshake; emit q->error(); return; } else if (r == SASLContext::Params) { const Params np = c->clientParams(); emit q->needParams(np); return; } first = false; actionQueue += Action(Action::ClientStarted, c->haveClientInit(), c->stepData()); if (r == SASLContext::Success) actionQueue += Action(Action::Authenticated); processNextAction(); return; } else { if (r == SASLContext::Error) { errorCode = ErrorHandshake; emit q->error(); return; } else if (r == SASLContext::Params) { const Params np = c->clientParams(); emit q->needParams(np); return; } else if (r == SASLContext::Continue) { emit q->nextStep(c->stepData()); return; } else if (r == SASLContext::Success) { actionQueue += Action(Action::NextStep, c->stepData()); actionQueue += Action(Action::Authenticated); processNextAction(); return; } } } } else if (last_op == OpUpdate) { if (r != SASLContext::Success) { errorCode = ErrorCrypt; emit q->error(); return; } const QByteArray c_to_net = c->to_net(); const QByteArray c_to_app = c->to_app(); int enc = -1; if (!c_to_net.isEmpty()) enc = c->encoded(); bool io_pending = false; if (!c_to_net.isEmpty()) out_pending -= enc; if (out_pending > 0) io_pending = true; if (!out.isEmpty()) io_pending = true; to_net += c_to_net; in += c_to_app; to_net_encoded += enc; if (!c_to_net.isEmpty()) actionQueue += Action(Action::ReadyReadOutgoing); if (!c_to_app.isEmpty()) actionQueue += Action(Action::ReadyRead); if (io_pending) update(); processNextAction(); return; } } void doNextAction() { processNextAction(); } }; SASL::SASL(QObject *parent, const QString &provider) : SecureLayer(parent) , Algorithm(QStringLiteral("sasl"), provider) { d = new Private(this); } SASL::~SASL() { delete d; } void SASL::reset() { d->reset(ResetAll); } SASL::Error SASL::errorCode() const { return d->errorCode; } SASL::AuthCondition SASL::authCondition() const { return d->c->authCondition(); } void SASL::setConstraints(AuthFlags f, SecurityLevel s) { int min = 0; if (s == SL_Integrity) min = 1; else if (s == SL_Export) min = 56; else if (s == SL_Baseline) min = 128; else if (s == SL_High) min = 192; else if (s == SL_Highest) min = 256; setConstraints(f, min, 256); } void SASL::setConstraints(AuthFlags f, int minSSF, int maxSSF) { d->auth_flags = f; d->ssfmin = minSSF; d->ssfmax = maxSSF; } void SASL::setExternalAuthId(const QString &authid) { d->ext_authid = authid; } void SASL::setExternalSSF(int strength) { d->ext_ssf = strength; } void SASL::setLocalAddress(const QString &addr, quint16 port) { d->localSet = true; d->local.addr = addr; d->local.port = port; } void SASL::setRemoteAddress(const QString &addr, quint16 port) { d->remoteSet = true; d->remote.addr = addr; d->remote.port = port; } void SASL::startClient(const QString &service, const QString &host, const QStringList &mechlist, ClientSendMode mode) { d->reset(ResetSessionAndData); d->setup(service, host); d->server = false; d->mechlist = mechlist; d->allowClientSendFirst = (mode == AllowClientSendFirst); d->start(); } void SASL::startServer(const QString &service, const QString &host, const QString &realm, ServerSendMode mode) { d->reset(ResetSessionAndData); d->setup(service, host); d->server = true; d->server_realm = realm; d->disableServerSendLast = (mode == DisableServerSendLast); d->start(); } void SASL::putServerFirstStep(const QString &mech) { d->putServerFirstStep(mech, nullptr); } void SASL::putServerFirstStep(const QString &mech, const QByteArray &clientInit) { d->putServerFirstStep(mech, &clientInit); } void SASL::putStep(const QByteArray &stepData) { d->putStep(stepData); } void SASL::setUsername(const QString &user) { d->set_username = true; d->username = user; d->c->setClientParams(&user, nullptr, nullptr, nullptr); } void SASL::setAuthzid(const QString &authzid) { d->set_authzid = true; d->authzid = authzid; d->c->setClientParams(nullptr, &authzid, nullptr, nullptr); } void SASL::setPassword(const SecureArray &pass) { d->set_password = true; d->password = pass; d->c->setClientParams(nullptr, nullptr, &pass, nullptr); } void SASL::setRealm(const QString &realm) { d->set_realm = true; d->realm = realm; d->c->setClientParams(nullptr, nullptr, nullptr, &realm); } void SASL::continueAfterParams() { d->tryAgain(); } void SASL::continueAfterAuthCheck() { d->tryAgain(); } QString SASL::mechanism() const { return d->mech; } QStringList SASL::mechanismList() const { return d->c->mechlist(); } QStringList SASL::realmList() const { return d->c->realmlist(); } int SASL::ssf() const { return d->c->ssf(); } int SASL::bytesAvailable() const { return d->in.size(); } int SASL::bytesOutgoingAvailable() const { return d->to_net.size(); } void SASL::write(const QByteArray &a) { d->out.append(a); d->layer.addPlain(a.size()); d->update(); } QByteArray SASL::read() { const QByteArray a = d->in; d->in.clear(); return a; } void SASL::writeIncoming(const QByteArray &a) { d->from_net.append(a); d->update(); } QByteArray SASL::readOutgoing(int *plainBytes) { const QByteArray a = d->to_net; d->to_net.clear(); if (plainBytes) *plainBytes = d->to_net_encoded; d->layer.specifyEncoded(a.size(), d->to_net_encoded); d->to_net_encoded = 0; return a; } int SASL::convertBytesWritten(qint64 bytes) { return d->layer.finished(bytes); } } #include "qca_securelayer.moc"