diff options
Diffstat (limited to 'src/sshagent/SSHAgent.cpp')
-rw-r--r-- | src/sshagent/SSHAgent.cpp | 282 |
1 files changed, 282 insertions, 0 deletions
diff --git a/src/sshagent/SSHAgent.cpp b/src/sshagent/SSHAgent.cpp new file mode 100644 index 000000000..a65c6e4f1 --- /dev/null +++ b/src/sshagent/SSHAgent.cpp @@ -0,0 +1,282 @@ +/* + * Copyright (C) 2017 Toni Spets <toni.spets@iki.fi> + * Copyright (C) 2017 KeePassXC Team <team@keepassxc.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "SSHAgent.h" +#include "BinaryStream.h" +#include "KeeAgentSettings.h" + +#ifndef Q_OS_WIN +#include <QtNetwork> +#else +#include <windows.h> +#endif + +SSHAgent* SSHAgent::m_instance; + +SSHAgent::SSHAgent(QObject* parent) : QObject(parent) +{ +#ifndef Q_OS_WIN + m_socketPath = QProcessEnvironment::systemEnvironment().value("SSH_AUTH_SOCK"); +#endif +} + +SSHAgent::~SSHAgent() +{ + for (QSet<OpenSSHKey> keys : m_keys.values()) { + for (OpenSSHKey key : keys) { + removeIdentity(key); + } + } +} + +SSHAgent* SSHAgent::instance() +{ + if (m_instance == nullptr) { + qFatal("Race condition: instance wanted before it was initialized, this is a bug."); + } + + return m_instance; +} + +void SSHAgent::init(QObject* parent) +{ + m_instance = new SSHAgent(parent); +} + +bool SSHAgent::isAgentRunning() const +{ +#ifndef Q_OS_WIN + return !m_socketPath.isEmpty(); +#else + return (FindWindowA("Pageant", "Pageant") != nullptr); +#endif +} + +bool SSHAgent::sendMessage(const QByteArray& in, QByteArray& out) const +{ +#ifndef Q_OS_WIN + QLocalSocket socket; + BinaryStream stream(&socket); + + socket.connectToServer(m_socketPath); + if (!socket.waitForConnected(500)) { + return false; + } + + stream.writeString(in); + stream.flush(); + + if (!stream.readString(out)) { + return false; + } + + socket.close(); + + return true; +#else + HWND hWnd = FindWindowA("Pageant", "Pageant"); + + if (!hWnd) { + return false; + } + + if (static_cast<quint32>(in.length()) > AGENT_MAX_MSGLEN - 4) { + return false; + } + + QByteArray mapName = (QString("SSHAgentRequest") + reinterpret_cast<intptr_t>(QThread::currentThreadId())).toLatin1(); + + HANDLE handle = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, AGENT_MAX_MSGLEN, mapName.data()); + + if (!handle) { + return false; + } + + LPVOID ptr = MapViewOfFile(handle, FILE_MAP_WRITE, 0, 0, 0); + + if (!ptr) { + CloseHandle(handle); + return false; + } + + quint32 *requestLength = reinterpret_cast<quint32*>(ptr); + void *requestData = reinterpret_cast<void*>(reinterpret_cast<char*>(ptr) + 4); + + *requestLength = qToBigEndian<quint32>(in.length()); + memcpy(requestData, in.data(), in.length()); + + COPYDATASTRUCT data; + data.dwData = AGENT_COPYDATA_ID; + data.cbData = mapName.length() + 1; + data.lpData = reinterpret_cast<LPVOID>(mapName.data()); + + LRESULT res = SendMessageA(hWnd, WM_COPYDATA, 0, reinterpret_cast<LPARAM>(&data)); + + if (res) { + quint32 responseLength = qFromBigEndian<quint32>(*requestLength); + if (responseLength <= AGENT_MAX_MSGLEN) { + out.resize(responseLength); + memcpy(out.data(), requestData, responseLength); + } + } + + UnmapViewOfFile(ptr); + CloseHandle(handle); + + return (res > 0); +#endif +} + + +bool SSHAgent::addIdentity(OpenSSHKey& key, quint32 lifetime, bool confirm) const +{ + QByteArray requestData; + BinaryStream request(&requestData); + + request.write((lifetime > 0 || confirm) ? SSH_AGENTC_ADD_ID_CONSTRAINED : SSH_AGENTC_ADD_IDENTITY); + key.writePrivate(request); + + if (lifetime > 0) { + request.write(SSH_AGENT_CONSTRAIN_LIFETIME); + request.write(lifetime); + } + + if (confirm) { + request.write(SSH_AGENT_CONSTRAIN_CONFIRM); + } + + QByteArray responseData; + sendMessage(requestData, responseData); + + if (responseData.length() < 1 || static_cast<quint8>(responseData[0]) != SSH_AGENT_SUCCESS) { + return false; + } + + return true; +} + +bool SSHAgent::removeIdentity(OpenSSHKey& key) const +{ + QByteArray requestData; + BinaryStream request(&requestData); + + QByteArray keyData; + BinaryStream keyStream(&keyData); + key.writePublic(keyStream); + + request.write(SSH_AGENTC_REMOVE_IDENTITY); + request.writeString(keyData); + + QByteArray responseData; + sendMessage(requestData, responseData); + + if (responseData.length() < 1 || static_cast<quint8>(responseData[0]) != SSH_AGENT_SUCCESS) { + return false; + } + + return true; +} + +void SSHAgent::removeIdentityAtLock(const OpenSSHKey& key, const Uuid& uuid) +{ + OpenSSHKey copy = key; + copy.clearPrivate(); + m_keys[uuid.toHex()].insert(copy); +} + +void SSHAgent::databaseModeChanged(DatabaseWidget::Mode mode) +{ + DatabaseWidget* widget = qobject_cast<DatabaseWidget*>(sender()); + + if (widget == nullptr) { + return; + } + + Uuid uuid = widget->database()->uuid(); + + if (mode == DatabaseWidget::LockedMode && m_keys.contains(uuid.toHex())) { + QSet<OpenSSHKey> keys = m_keys.take(uuid.toHex()); + for (OpenSSHKey key : keys) { + removeIdentity(key); + } + } else if (mode == DatabaseWidget::ViewMode && !m_keys.contains(uuid.toHex())) { + for (Entry* e : widget->database()->rootGroup()->entriesRecursive()) { + + if (widget->database()->metadata()->recycleBinEnabled() + && e->group() == widget->database()->metadata()->recycleBin()) { + continue; + } + + if (!e->attachments()->hasKey("KeeAgent.settings")) { + continue; + } + + KeeAgentSettings settings; + settings.fromXml(e->attachments()->value("KeeAgent.settings")); + + if (!settings.allowUseOfSshKey()) { + continue; + } + + QByteArray keyData; + if (settings.selectedType() == "attachment") { + keyData = e->attachments()->value(settings.attachmentName()); + } else if (!settings.fileName().isEmpty()) { + QFile file(settings.fileName()); + + if (file.size() > 1024 * 1024) { + continue; + } + + if (!file.open(QIODevice::ReadOnly)) { + continue; + } + + keyData = file.readAll(); + } + + if (keyData.isEmpty()) { + continue; + } + + OpenSSHKey key; + + if (!key.parse(keyData)) { + continue; + } + + if (key.comment().isEmpty()) { + key.setComment(e->username()); + } + + if (settings.removeAtDatabaseClose()) { + removeIdentityAtLock(key, uuid); + } + + if (settings.addAtDatabaseOpen() && key.openPrivateKey(e->password())) { + int lifetime = 0; + + if (settings.useLifetimeConstraintWhenAdding()) { + lifetime = settings.lifetimeConstraintDuration(); + } + + addIdentity(key, lifetime, settings.useConfirmConstraintWhenAdding()); + } + } + } +} |