summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Kandeler <christian.kandeler@theqtcompany.com>2016-07-20 18:04:56 +0200
committerChristian Kandeler <christian.kandeler@qt.io>2017-05-04 15:36:28 +0000
commit3ee2445fb143f3b9f8f7a7f80de202c1d811bb01 (patch)
tree236101db3d0fb0a0eec57d1f63eff1da9b5196d8
parentfcdc9342b554d30b5323aa026fd55659565ef71c (diff)
downloadqt-creator-3ee2445fb143f3b9f8f7a7f80de202c1d811bb01.tar.gz
SSH: Add support for ssh-agent
Task-number: QTCREATORBUG-16245 Change-Id: Ifd30c89d19e547d7657765790b7520e42b3741c3 Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
-rw-r--r--src/libs/ssh/ssh.pro6
-rw-r--r--src/libs/ssh/ssh.qbs1
-rw-r--r--src/libs/ssh/sshagent.cpp314
-rw-r--r--src/libs/ssh/sshagent_p.h125
-rw-r--r--src/libs/ssh/sshconnection.cpp176
-rw-r--r--src/libs/ssh/sshconnection.h1
-rw-r--r--src/libs/ssh/sshconnection_p.h15
-rw-r--r--src/libs/ssh/sshcryptofacility_p.h2
-rw-r--r--src/libs/ssh/ssherrors.h2
-rw-r--r--src/libs/ssh/sshincomingpacket.cpp17
-rw-r--r--src/libs/ssh/sshincomingpacket_p.h7
-rw-r--r--src/libs/ssh/sshoutgoingpacket.cpp38
-rw-r--r--src/libs/ssh/sshoutgoingpacket_p.h4
-rw-r--r--src/libs/ssh/sshpacketparser.cpp5
-rw-r--r--src/libs/ssh/sshpacketparser_p.h1
-rw-r--r--src/libs/ssh/sshsendfacility.cpp11
-rw-r--r--src/libs/ssh/sshsendfacility_p.h6
-rw-r--r--src/plugins/remotelinux/genericlinuxdeviceconfigurationwidget.cpp28
-rw-r--r--src/plugins/remotelinux/genericlinuxdeviceconfigurationwidget.ui7
-rw-r--r--src/plugins/remotelinux/genericlinuxdeviceconfigurationwizardpages.cpp15
-rw-r--r--src/plugins/remotelinux/genericlinuxdeviceconfigurationwizardsetuppage.ui7
21 files changed, 748 insertions, 40 deletions
diff --git a/src/libs/ssh/ssh.pro b/src/libs/ssh/ssh.pro
index defd894b35..27be1ec187 100644
--- a/src/libs/ssh/ssh.pro
+++ b/src/libs/ssh/ssh.pro
@@ -33,7 +33,8 @@ SOURCES = $$PWD/sshsendfacility.cpp \
$$PWD/sshhostkeydatabase.cpp \
$$PWD/sshtcpipforwardserver.cpp \
$$PWD/sshtcpiptunnel.cpp \
- $$PWD/sshforwardedtcpiptunnel.cpp
+ $$PWD/sshforwardedtcpiptunnel.cpp \
+ $$PWD/sshagent.cpp
HEADERS = $$PWD/sshsendfacility_p.h \
$$PWD/sshremoteprocess.h \
@@ -76,7 +77,8 @@ HEADERS = $$PWD/sshsendfacility_p.h \
$$PWD/sshtcpipforwardserver_p.h \
$$PWD/sshtcpiptunnel_p.h \
$$PWD/sshforwardedtcpiptunnel.h \
- $$PWD/sshforwardedtcpiptunnel_p.h
+ $$PWD/sshforwardedtcpiptunnel_p.h \
+ $$PWD/sshagent_p.h
FORMS = $$PWD/sshkeycreationdialog.ui
diff --git a/src/libs/ssh/ssh.qbs b/src/libs/ssh/ssh.qbs
index bc76933218..54dad5bf75 100644
--- a/src/libs/ssh/ssh.qbs
+++ b/src/libs/ssh/ssh.qbs
@@ -22,6 +22,7 @@ Project {
"sftpoperation.cpp", "sftpoperation_p.h",
"sftpoutgoingpacket.cpp", "sftpoutgoingpacket_p.h",
"sftppacket.cpp", "sftppacket_p.h",
+ "sshagent.cpp", "sshagent_p.h",
"sshbotanconversions_p.h",
"sshcapabilities_p.h", "sshcapabilities.cpp",
"sshchannel.cpp", "sshchannel_p.h",
diff --git a/src/libs/ssh/sshagent.cpp b/src/libs/ssh/sshagent.cpp
new file mode 100644
index 0000000000..1ada40fcd1
--- /dev/null
+++ b/src/libs/ssh/sshagent.cpp
@@ -0,0 +1,314 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+#include "sshagent_p.h"
+
+#include "sshlogging_p.h"
+#include "sshpacket_p.h"
+#include "sshpacketparser_p.h"
+#include "ssh_global.h"
+
+#include <QTimer>
+#include <QtEndian>
+
+#include <algorithm>
+
+namespace QSsh {
+namespace Internal {
+
+// https://github.com/openssh/openssh-portable/blob/V_7_2/PROTOCOL.agent
+enum PacketType {
+ SSH_AGENT_FAILURE = 5,
+ SSH2_AGENTC_REQUEST_IDENTITIES = 11,
+ SSH2_AGENTC_SIGN_REQUEST = 13,
+ SSH2_AGENT_IDENTITIES_ANSWER = 12,
+ SSH2_AGENT_SIGN_RESPONSE = 14,
+};
+
+// TODO: Remove once we require 5.7, where the endianness functions have a sane input type.
+template<typename T> static T fromBigEndian(const QByteArray &ba)
+{
+ return qFromBigEndian<T>(reinterpret_cast<const uchar *>(ba.constData()));
+}
+
+void SshAgent::refreshKeysImpl()
+{
+ if (state() != Connected)
+ return;
+ const auto keysRequestIt = std::find_if(m_pendingRequests.constBegin(),
+ m_pendingRequests.constEnd(), [this](const Request &r) { return r.isKeysRequest(); });
+ if (keysRequestIt != m_pendingRequests.constEnd()) {
+ qCDebug(sshLog) << "keys request already pending, not adding another one";
+ return;
+ }
+ qCDebug(sshLog) << "queueing keys request";
+ m_pendingRequests << Request();
+ sendNextRequest();
+}
+
+void SshAgent::requestSignatureImpl(const QByteArray &key, uint token)
+{
+ if (state() != Connected)
+ return;
+ const QByteArray data = m_dataToSign.take(qMakePair(key, token));
+ QSSH_ASSERT(!data.isEmpty());
+ qCDebug(sshLog) << "queueing signature request";
+ m_pendingRequests.enqueue(Request(key, data, token));
+ sendNextRequest();
+}
+
+void SshAgent::sendNextRequest()
+{
+ if (m_pendingRequests.isEmpty())
+ return;
+ if (m_outgoingPacket.isComplete())
+ return;
+ if (hasError())
+ return;
+ const Request &request = m_pendingRequests.head();
+ m_outgoingPacket = request.isKeysRequest() ? generateKeysPacket() : generateSigPacket(request);
+ sendPacket();
+}
+
+SshAgent::Packet SshAgent::generateKeysPacket()
+{
+ qCDebug(sshLog) << "requesting keys from agent";
+ Packet p;
+ p.size = 1;
+ p.data += char(SSH2_AGENTC_REQUEST_IDENTITIES);
+ return p;
+}
+
+SshAgent::Packet SshAgent::generateSigPacket(const SshAgent::Request &request)
+{
+ qCDebug(sshLog) << "requesting signature from agent for key" << request.key << "and token"
+ << request.token;
+ Packet p;
+ p.data += char(SSH2_AGENTC_SIGN_REQUEST);
+ p.data += AbstractSshPacket::encodeString(request.key);
+ p.data += AbstractSshPacket::encodeString(request.dataToSign);
+ p.data += AbstractSshPacket::encodeInt(quint32(0));
+ p.size = p.data.count();
+ return p;
+}
+
+SshAgent::~SshAgent()
+{
+ m_agentSocket.disconnect(this);
+}
+
+void SshAgent::storeDataToSign(const QByteArray &key, const QByteArray &data, uint token)
+{
+ instance().m_dataToSign.insert(qMakePair(key, token), data);
+}
+
+void SshAgent::removeDataToSign(const QByteArray &key, uint token)
+{
+ instance().m_dataToSign.remove(qMakePair(key, token));
+}
+
+SshAgent &QSsh::Internal::SshAgent::instance()
+{
+ static SshAgent agent;
+ return agent;
+}
+
+SshAgent::SshAgent()
+{
+ connect(&m_agentSocket, &QLocalSocket::connected, this, &SshAgent::handleConnected);
+ connect(&m_agentSocket, &QLocalSocket::disconnected, this, &SshAgent::handleDisconnected);
+ connect(&m_agentSocket,
+ static_cast<void(QLocalSocket::*)(QLocalSocket::LocalSocketError)>(&QLocalSocket::error),
+ this, &SshAgent::handleSocketError);
+ connect(&m_agentSocket, &QLocalSocket::readyRead, this, &SshAgent::handleIncomingData);
+ QTimer::singleShot(0, this, &SshAgent::connectToServer);
+}
+
+void SshAgent::connectToServer()
+{
+ const QByteArray serverAddress = qgetenv("SSH_AUTH_SOCK");
+ if (serverAddress.isEmpty()) {
+ qCDebug(sshLog) << "agent failure: socket address unknown";
+ m_error = tr("Cannot connect to ssh-agent: SSH_AUTH_SOCK is not set.");
+ emit errorOccurred();
+ return;
+ }
+ qCDebug(sshLog) << "connecting to ssh-agent socket" << serverAddress;
+ m_state = Connecting;
+ m_agentSocket.connectToServer(QString::fromLocal8Bit(serverAddress));
+}
+
+void SshAgent::handleConnected()
+{
+ m_state = Connected;
+ qCDebug(sshLog) << "connection to ssh-agent established";
+ refreshKeys();
+}
+
+void SshAgent::handleDisconnected()
+{
+ qCDebug(sshLog) << "lost connection to ssh-agent";
+ m_error = tr("Lost connection to ssh-agent for unknown reason.");
+ setDisconnected();
+}
+
+void SshAgent::handleSocketError()
+{
+ qCDebug(sshLog) << "agent socket error" << m_agentSocket.error();
+ m_error = m_agentSocket.errorString();
+ setDisconnected();
+}
+
+void SshAgent::handleIncomingData()
+{
+ qCDebug(sshLog) << "getting data from agent";
+ m_incomingData += m_agentSocket.readAll();
+ while (!hasError() && !m_incomingData.isEmpty()) {
+ if (m_incomingPacket.size == 0) {
+ if (m_incomingData.count() < int(sizeof m_incomingPacket.size))
+ break;
+ m_incomingPacket.size = fromBigEndian<quint32>(m_incomingData);
+ m_incomingData.remove(0, sizeof m_incomingPacket.size);
+ }
+ const int bytesToTake = qMin<quint32>(m_incomingPacket.size - m_incomingPacket.data.count(),
+ m_incomingData.count());
+ m_incomingPacket.data += m_incomingData.left(bytesToTake);
+ m_incomingData.remove(0, bytesToTake);
+ if (m_incomingPacket.isComplete())
+ handleIncomingPacket();
+ else
+ break;
+ }
+}
+
+void SshAgent::handleIncomingPacket()
+{
+ try {
+ qCDebug(sshLog) << "received packet from agent:" << m_incomingPacket.data.toHex();
+ const char messageType = m_incomingPacket.data.at(0);
+ switch (messageType) {
+ case SSH2_AGENT_IDENTITIES_ANSWER:
+ handleIdentitiesPacket();
+ break;
+ case SSH2_AGENT_SIGN_RESPONSE:
+ handleSignaturePacket();
+ break;
+ case SSH_AGENT_FAILURE:
+ if (m_pendingRequests.isEmpty()) {
+ qCWarning(sshLog) << "unexpected failure message from agent";
+ } else {
+ const Request request = m_pendingRequests.dequeue();
+ if (request.isSignatureRequest()) {
+ qCWarning(sshLog) << "agent failed to sign message for key"
+ << request.key.toHex();
+ emit signatureAvailable(request.key, QByteArray(), request.token);
+ } else {
+ qCWarning(sshLog) << "agent failed to retrieve key list";
+ if (m_keys.isEmpty()) {
+ m_error = tr("ssh-agent failed to retrieve keys.");
+ setDisconnected();
+ }
+ }
+ }
+ break;
+ default:
+ qCWarning(sshLog) << "unexpected message type from agent:" << messageType;
+ }
+ } catch (const SshPacketParseException &) {
+ qCWarning(sshLog()) << "received malformed packet from agent";
+ handleProtocolError();
+ }
+ m_incomingPacket.invalidate();
+ m_incomingPacket.size = 0;
+ m_outgoingPacket.invalidate();
+ sendNextRequest();
+}
+
+void SshAgent::handleIdentitiesPacket()
+{
+ qCDebug(sshLog) << "got keys packet from agent";
+ if (m_pendingRequests.isEmpty() || !m_pendingRequests.dequeue().isKeysRequest()) {
+ qCDebug(sshLog) << "packet was not requested";
+ handleProtocolError();
+ return;
+ }
+ quint32 offset = 1;
+ const auto keyCount = SshPacketParser::asUint32(m_incomingPacket.data, &offset);
+ qCDebug(sshLog) << "packet contains" << keyCount << "keys";
+ QList<QByteArray> newKeys;
+ for (quint32 i = 0; i < keyCount; ++i) {
+ const QByteArray key = SshPacketParser::asString(m_incomingPacket.data, &offset);
+ quint32 keyOffset = 0;
+ const QByteArray algoName = SshPacketParser::asString(key, &keyOffset);
+ SshPacketParser::asString(key, &keyOffset); // rest of key blob
+ SshPacketParser::asString(m_incomingPacket.data, &offset); // comment
+ qCDebug(sshLog) << "adding key of type" << algoName;
+ newKeys << key;
+ }
+
+ m_keys = newKeys;
+ emit keysUpdated();
+}
+
+void SshAgent::handleSignaturePacket()
+{
+ qCDebug(sshLog) << "got signature packet from agent";
+ if (m_pendingRequests.isEmpty()) {
+ qCDebug(sshLog) << "signature packet was not requested";
+ handleProtocolError();
+ return;
+ }
+ const Request request = m_pendingRequests.dequeue();
+ if (!request.isSignatureRequest()) {
+ qCDebug(sshLog) << "signature packet was not requested";
+ handleProtocolError();
+ return;
+ }
+ const QByteArray signature = SshPacketParser::asString(m_incomingPacket.data, 1);
+ qCDebug(sshLog) << "signature for key" << request.key.toHex() << "is" << signature.toHex();
+ emit signatureAvailable(request.key, signature, request.token);
+}
+
+void SshAgent::handleProtocolError()
+{
+ m_error = tr("Protocol error when talking to ssh-agent.");
+ setDisconnected();
+}
+
+void SshAgent::setDisconnected()
+{
+ m_state = Unconnected;
+ m_agentSocket.disconnect(this);
+ emit errorOccurred();
+}
+
+void SshAgent::sendPacket()
+{
+ const quint32 sizeMsb = qToBigEndian(m_outgoingPacket.size);
+ m_agentSocket.write(reinterpret_cast<const char *>(&sizeMsb), sizeof sizeMsb);
+ m_agentSocket.write(m_outgoingPacket.data);
+}
+
+} // namespace Internal
+} // namespace QSsh
diff --git a/src/libs/ssh/sshagent_p.h b/src/libs/ssh/sshagent_p.h
new file mode 100644
index 0000000000..346d9aab9d
--- /dev/null
+++ b/src/libs/ssh/sshagent_p.h
@@ -0,0 +1,125 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include <QByteArray>
+#include <QHash>
+#include <QList>
+#include <QLocalSocket>
+#include <QObject>
+#include <QPair>
+#include <QQueue>
+#include <QString>
+
+namespace QSsh {
+namespace Internal {
+
+class SshAgent : public QObject
+{
+ Q_OBJECT
+public:
+ enum State { Unconnected, Connecting, Connected, };
+
+ ~SshAgent();
+ static State state() { return instance().m_state; }
+ static bool hasError() { return !instance().m_error.isEmpty(); }
+ static QString errorString() { return instance().m_error; }
+ static QList<QByteArray> publicKeys() { return instance().m_keys; }
+
+ static void refreshKeys() { instance().refreshKeysImpl(); }
+ static void storeDataToSign(const QByteArray &key, const QByteArray &data, uint token);
+ static void removeDataToSign(const QByteArray &key, uint token);
+ static void requestSignature(const QByteArray &key, uint token) {
+ instance().requestSignatureImpl(key, token);
+ }
+
+ static SshAgent &instance();
+
+signals:
+ void errorOccurred();
+ void keysUpdated();
+
+ // Empty signature means signing failure.
+ void signatureAvailable(const QByteArray &key, const QByteArray &signature, uint token);
+
+private:
+ struct Request {
+ Request() { }
+ Request(const QByteArray &k, const QByteArray &d, uint t)
+ : key(k), dataToSign(d), token(t) { }
+
+ bool isKeysRequest() const { return !isSignatureRequest(); }
+ bool isSignatureRequest() const { return !key.isEmpty(); }
+
+ QByteArray key;
+ QByteArray dataToSign;
+ uint token;
+ };
+
+ struct Packet {
+ bool isComplete() const { return size != 0 && int(size) == data.count(); }
+ void invalidate() { size = 0; data.clear(); }
+
+ quint32 size = 0;
+ QByteArray data;
+ };
+
+ SshAgent();
+ void connectToServer();
+ void refreshKeysImpl();
+ void requestSignatureImpl(const QByteArray &key, uint token);
+
+ void sendNextRequest();
+ Packet generateKeysPacket();
+ Packet generateSigPacket(const Request &request);
+
+ void handleConnected();
+ void handleDisconnected();
+ void handleSocketError();
+ void handleIncomingData();
+ void handleIncomingPacket();
+ void handleIdentitiesPacket();
+ void handleSignaturePacket();
+
+ void handleProtocolError();
+ void setDisconnected();
+
+ void sendPacket();
+
+ State m_state = Unconnected;
+ QString m_error;
+ QList<QByteArray> m_keys;
+ QHash<QPair<QByteArray, uint>, QByteArray> m_dataToSign;
+ QLocalSocket m_agentSocket;
+ QByteArray m_incomingData;
+ Packet m_incomingPacket;
+ Packet m_outgoingPacket;
+
+ QQueue<Request> m_pendingRequests;
+};
+
+} // namespace Internal
+} // namespace QSsh
diff --git a/src/libs/ssh/sshconnection.cpp b/src/libs/ssh/sshconnection.cpp
index a98529371d..790ea40ebc 100644
--- a/src/libs/ssh/sshconnection.cpp
+++ b/src/libs/ssh/sshconnection.cpp
@@ -27,6 +27,7 @@
#include "sshconnection_p.h"
#include "sftpchannel.h"
+#include "sshagent_p.h"
#include "sshcapabilities_p.h"
#include "sshchannelmanager_p.h"
#include "sshcryptofacility_p.h"
@@ -270,6 +271,7 @@ void SshConnectionPrivate::setupPacketHandlers()
setupPacketHandler(SSH_MSG_USERAUTH_INFO_REQUEST, authReqList,
&This::handleUserAuthInfoRequestPacket);
}
+ setupPacketHandler(SSH_MSG_USERAUTH_PK_OK, authReqList, &This::handleUserAuthKeyOkPacket);
const StateList connectedList
= StateList() << ConnectionEstablished;
@@ -299,7 +301,7 @@ void SshConnectionPrivate::setupPacketHandlers()
setupPacketHandler(SSH_MSG_CHANNEL_CLOSE, connectedOrClosedList,
&This::handleChannelClose);
- setupPacketHandler(SSH_MSG_DISCONNECT, StateList() << SocketConnected
+ setupPacketHandler(SSH_MSG_DISCONNECT, StateList() << SocketConnected << WaitingForAgentKeys
<< UserAuthServiceRequested << UserAuthRequested
<< ConnectionEstablished, &This::handleDisconnect);
@@ -527,8 +529,18 @@ void SshConnectionPrivate::handleServiceAcceptPacket()
SshCapabilities::SshConnectionService);
break;
case SshConnectionParameters::AuthenticationTypePublicKey:
- m_sendFacility.sendUserAuthByPublicKeyRequestPacket(m_connParams.userName.toUtf8(),
- SshCapabilities::SshConnectionService);
+ authenticateWithPublicKey();
+ break;
+ case SshConnectionParameters::AuthenticationTypeAgent:
+ if (SshAgent::publicKeys().isEmpty()) {
+ if (m_agentKeysUpToDate)
+ throw SshClientException(SshAuthenticationError, tr("ssh-agent has no keys."));
+ qCDebug(sshLog) << "agent has no keys yet, waiting";
+ m_state = WaitingForAgentKeys;
+ return;
+ } else {
+ tryAllAgentKeys();
+ }
break;
}
m_state = UserAuthRequested;
@@ -596,6 +608,18 @@ void SshConnectionPrivate::handleUserAuthSuccessPacket()
void SshConnectionPrivate::handleUserAuthFailurePacket()
{
+ if (!m_pendingKeyChecks.isEmpty()) {
+ const QByteArray key = m_pendingKeyChecks.dequeue();
+ SshAgent::removeDataToSign(key, tokenForAgent());
+ qCDebug(sshLog) << "server rejected one of the keys supplied by the agent,"
+ << m_pendingKeyChecks.count() << "keys remaining";
+ if (m_pendingKeyChecks.isEmpty() && m_agentKeyToUse.isEmpty()) {
+ throw SshClientException(SshAuthenticationError, tr("The server rejected all keys "
+ "known to the ssh-agent."));
+ }
+ return;
+ }
+
// TODO: Evaluate "authentications that can continue" field and act on it.
if (m_connParams.authenticationType
== SshConnectionParameters::AuthenticationTypeTryAllPasswordBasedMethods
@@ -608,10 +632,45 @@ void SshConnectionPrivate::handleUserAuthFailurePacket()
}
m_timeoutTimer.stop();
- const QString errorMsg = m_connParams.authenticationType == SshConnectionParameters::AuthenticationTypePublicKey
- ? tr("Server rejected key.") : tr("Server rejected password.");
+ QString errorMsg;
+ switch (m_connParams.authenticationType) {
+ case SshConnectionParameters::AuthenticationTypePublicKey:
+ case SshConnectionParameters::AuthenticationTypeAgent:
+ errorMsg = tr("Server rejected key.");
+ break;
+ default:
+ errorMsg = tr("Server rejected password.");
+ break;
+ }
throw SshClientException(SshAuthenticationError, errorMsg);
}
+
+void SshConnectionPrivate::handleUserAuthKeyOkPacket()
+{
+ const SshUserAuthPkOkPacket &msg = m_incomingPacket.extractUserAuthPkOk();
+ qCDebug(sshLog) << "server accepted key of type" << msg.algoName;
+
+ if (m_pendingKeyChecks.isEmpty()) {
+ throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR, "Unexpected packet",
+ tr("Server sent unexpected SSH_MSG_USERAUTH_PK_OK packet."));
+ }
+ const QByteArray key = m_pendingKeyChecks.dequeue();
+ if (key != msg.keyBlob) {
+ // The server must answer the requests in the order we sent them.
+ throw SshServerException(SSH_DISCONNECT_PROTOCOL_ERROR, "Unexpected packet content",
+ tr("Server sent unexpected key in SSH_MSG_USERAUTH_PK_OK packet."));
+ }
+ const uint token = tokenForAgent();
+ if (!m_agentKeyToUse.isEmpty()) {
+ qCDebug(sshLog) << "another key has already been accepted, ignoring this one";
+ SshAgent::removeDataToSign(key, token);
+ return;
+ }
+ m_agentKeyToUse = key;
+ qCDebug(sshLog) << "requesting signature from agent";
+ SshAgent::requestSignature(key, token);
+}
+
void SshConnectionPrivate::handleDebugPacket()
{
const SshDebug &msg = m_incomingPacket.extractDebug();
@@ -710,6 +769,11 @@ void SshConnectionPrivate::sendData(const QByteArray &data)
m_socket->write(data);
}
+uint SshConnectionPrivate::tokenForAgent() const
+{
+ return qHash(m_sendFacility.sessionId());
+}
+
void SshConnectionPrivate::handleSocketDisconnected()
{
closeConnection(SSH_DISCONNECT_CONNECTION_LOST, SshClosedByServerError,
@@ -727,8 +791,10 @@ void SshConnectionPrivate::handleSocketError()
void SshConnectionPrivate::handleTimeout()
{
- closeConnection(SSH_DISCONNECT_BY_APPLICATION, SshTimeoutError, "",
- tr("Timeout waiting for reply from server."));
+ const QString errorMessage = m_state == WaitingForAgentKeys
+ ? tr("Timeout waiting for keys from ssh-agent.")
+ : tr("Timeout waiting for reply from server.");
+ closeConnection(SSH_DISCONNECT_BY_APPLICATION, SshTimeoutError, "", errorMessage);
}
void SshConnectionPrivate::sendKeepAlivePacket()
@@ -745,6 +811,66 @@ void SshConnectionPrivate::sendKeepAlivePacket()
m_timeoutTimer.start();
}
+void SshConnectionPrivate::handleAgentKeysUpdated()
+{
+ m_agentKeysUpToDate = true;
+ if (m_state == WaitingForAgentKeys) {
+ m_state = UserAuthRequested;
+ tryAllAgentKeys();
+ }
+}
+
+void SshConnectionPrivate::handleSignatureFromAgent(const QByteArray &key,
+ const QByteArray &signature, uint token)
+{
+ if (token != tokenForAgent()) {
+ qCDebug(sshLog) << "signature is for different connection, ignoring";
+ return;
+ }
+ QSSH_ASSERT(key == m_agentKeyToUse);
+ m_agentSignature = signature;
+ authenticateWithPublicKey();
+}
+
+void SshConnectionPrivate::tryAllAgentKeys()
+{
+ const QList<QByteArray> &keys = SshAgent::publicKeys();
+ if (keys.isEmpty())
+ throw SshClientException(SshAuthenticationError, tr("ssh-agent has no keys."));
+ qCDebug(sshLog) << "trying authentication with" << keys.count()
+ << "public keys received from agent";
+ foreach (const QByteArray &key, keys) {
+ m_sendFacility.sendQueryPublicKeyPacket(m_connParams.userName.toUtf8(),
+ SshCapabilities::SshConnectionService, key);
+ m_pendingKeyChecks.enqueue(key);
+ }
+}
+
+void SshConnectionPrivate::authenticateWithPublicKey()
+{
+ qCDebug(sshLog) << "sending actual authentication request";
+
+ QByteArray key;
+ QByteArray signature;
+ if (m_connParams.authenticationType == SshConnectionParameters::AuthenticationTypeAgent) {
+ // Agent is not needed anymore after this point.
+ disconnect(&SshAgent::instance(), 0, this, 0);
+
+ key = m_agentKeyToUse;
+ signature = m_agentSignature;
+ }
+
+ m_sendFacility.sendUserAuthByPublicKeyRequestPacket(m_connParams.userName.toUtf8(),
+ SshCapabilities::SshConnectionService, key, signature);
+}
+
+void SshConnectionPrivate::setAgentError()
+{
+ m_error = SshAgentError;
+ m_errorString = SshAgent::errorString();
+ emit error(m_error);
+}
+
void SshConnectionPrivate::connectToHost()
{
QSSH_ASSERT_AND_RETURN(m_state == SocketUnconnected);
@@ -757,15 +883,37 @@ void SshConnectionPrivate::connectToHost()
m_errorString.clear();
m_serverId.clear();
m_serverHasSentDataBeforeId = false;
+ m_agentSignature.clear();
+ m_agentKeysUpToDate = false;
+ m_pendingKeyChecks.clear();
+ m_agentKeyToUse.clear();
- try {
- if (m_connParams.authenticationType == SshConnectionParameters::AuthenticationTypePublicKey)
+ switch (m_connParams.authenticationType) {
+ case SshConnectionParameters::AuthenticationTypePublicKey:
+ try {
createPrivateKey();
- } catch (const SshClientException &ex) {
- m_error = ex.error;
- m_errorString = ex.errorString;
- emit error(m_error);
- return;
+ break;
+ } catch (const SshClientException &ex) {
+ m_error = ex.error;
+ m_errorString = ex.errorString;
+ emit error(m_error);
+ return;
+ }
+ case SshConnectionParameters::AuthenticationTypeAgent:
+ if (SshAgent::hasError()) {
+ setAgentError();
+ return;
+ }
+ connect(&SshAgent::instance(), &SshAgent::errorOccurred,
+ this, &SshConnectionPrivate::setAgentError);
+ connect(&SshAgent::instance(), &SshAgent::keysUpdated,
+ this, &SshConnectionPrivate::handleAgentKeysUpdated);
+ SshAgent::refreshKeys();
+ connect(&SshAgent::instance(), &SshAgent::signatureAvailable,
+ this, &SshConnectionPrivate::handleSignatureFromAgent);
+ break;
+ default:
+ break;
}
connect(m_socket, &QAbstractSocket::connected,
diff --git a/src/libs/ssh/sshconnection.h b/src/libs/ssh/sshconnection.h
index 071c97c0c7..7779192cdb 100644
--- a/src/libs/ssh/sshconnection.h
+++ b/src/libs/ssh/sshconnection.h
@@ -65,6 +65,7 @@ public:
enum AuthenticationType {
AuthenticationTypePassword,
AuthenticationTypePublicKey,
+ AuthenticationTypeAgent,
AuthenticationTypeKeyboardInteractive,
// Some servers disable "password", others disable "keyboard-interactive".
diff --git a/src/libs/ssh/sshconnection_p.h b/src/libs/ssh/sshconnection_p.h
index 7c8198bf20..9a7afe3552 100644
--- a/src/libs/ssh/sshconnection_p.h
+++ b/src/libs/ssh/sshconnection_p.h
@@ -32,6 +32,7 @@
#include <QHash>
#include <QList>
+#include <QQueue>
#include <QObject>
#include <QPair>
#include <QScopedPointer>
@@ -56,6 +57,7 @@ enum SshStateInternal {
SocketConnecting, // After connectToHost()
SocketConnected, // After socket's connected() signal
UserAuthServiceRequested,
+ WaitingForAgentKeys,
UserAuthRequested,
ConnectionEstablished // After service has been started
// ...
@@ -107,6 +109,12 @@ private:
void handleTimeout();
void sendKeepAlivePacket();
+ void handleAgentKeysUpdated();
+ void handleSignatureFromAgent(const QByteArray &key, const QByteArray &signature, uint token);
+ void tryAllAgentKeys();
+ void authenticateWithPublicKey();
+ void setAgentError();
+
void handleServerId();
void handlePackets();
void handleCurrentPacket();
@@ -118,6 +126,7 @@ private:
void handleUserAuthInfoRequestPacket();
void handleUserAuthSuccessPacket();
void handleUserAuthFailurePacket();
+ void handleUserAuthKeyOkPacket();
void handleUserAuthBannerPacket();
void handleUnexpectedPacket();
void handleGlobalRequest();
@@ -143,6 +152,8 @@ private:
void sendData(const QByteArray &data);
+ uint tokenForAgent() const;
+
typedef void (SshConnectionPrivate::*PacketHandler)();
typedef QList<SshStateInternal> StateList;
void setupPacketHandlers();
@@ -171,8 +182,12 @@ private:
SshConnection *m_conn;
quint64 m_lastInvalidMsgSeqNr;
QByteArray m_serverId;
+ QByteArray m_agentSignature;
+ QQueue<QByteArray> m_pendingKeyChecks;
+ QByteArray m_agentKeyToUse;
bool m_serverHasSentDataBeforeId;
bool m_triedAllPasswordBasedMethods;
+ bool m_agentKeysUpToDate;
};
} // namespace Internal
diff --git a/src/libs/ssh/sshcryptofacility_p.h b/src/libs/ssh/sshcryptofacility_p.h
index db008207d1..2f9b64c44c 100644
--- a/src/libs/ssh/sshcryptofacility_p.h
+++ b/src/libs/ssh/sshcryptofacility_p.h
@@ -45,13 +45,13 @@ public:
QByteArray generateMac(const QByteArray &data, quint32 dataSize) const;
quint32 cipherBlockSize() const { return m_cipherBlockSize; }
quint32 macLength() const { return m_macLength; }
+ QByteArray sessionId() const { return m_sessionId; }
protected:
enum Mode { CbcMode, CtrMode };
SshAbstractCryptoFacility();
void convert(QByteArray &data, quint32 offset, quint32 dataSize) const;
- QByteArray sessionId() const { return m_sessionId; }
Botan::Keyed_Filter *makeCtrCipherMode(Botan::BlockCipher *cipher,
const Botan::InitializationVector &iv, const Botan::SymmetricKey &key);
diff --git a/src/libs/ssh/ssherrors.h b/src/libs/ssh/ssherrors.h
index 4c85e071cf..caf89819f0 100644
--- a/src/libs/ssh/ssherrors.h
+++ b/src/libs/ssh/ssherrors.h
@@ -31,7 +31,7 @@ namespace QSsh {
enum SshError {
SshNoError, SshSocketError, SshTimeoutError, SshProtocolError,
SshHostKeyError, SshKeyFileError, SshAuthenticationError,
- SshClosedByServerError, SshInternalError
+ SshClosedByServerError, SshAgentError, SshInternalError
};
} // namespace QSsh
diff --git a/src/libs/ssh/sshincomingpacket.cpp b/src/libs/ssh/sshincomingpacket.cpp
index 5bb8391434..d8c6356a0b 100644
--- a/src/libs/ssh/sshincomingpacket.cpp
+++ b/src/libs/ssh/sshincomingpacket.cpp
@@ -296,6 +296,23 @@ SshUserAuthInfoRequestPacket SshIncomingPacket::extractUserAuthInfoRequest() con
}
}
+SshUserAuthPkOkPacket SshIncomingPacket::extractUserAuthPkOk() const
+{
+ Q_ASSERT(isComplete());
+ Q_ASSERT(type() == SSH_MSG_USERAUTH_PK_OK);
+
+ try {
+ SshUserAuthPkOkPacket msg;
+ quint32 offset = TypeOffset + 1;
+ msg.algoName= SshPacketParser::asString(m_data, &offset);
+ msg.keyBlob = SshPacketParser::asString(m_data, &offset);
+ return msg;
+ } catch (const SshPacketParseException &) {
+ throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_PROTOCOL_ERROR,
+ "Invalid SSH_MSG_USERAUTH_PK_OK.");
+ }
+}
+
SshDebug SshIncomingPacket::extractDebug() const
{
Q_ASSERT(isComplete());
diff --git a/src/libs/ssh/sshincomingpacket_p.h b/src/libs/ssh/sshincomingpacket_p.h
index bd7aea4d6e..ab8be6aae3 100644
--- a/src/libs/ssh/sshincomingpacket_p.h
+++ b/src/libs/ssh/sshincomingpacket_p.h
@@ -76,6 +76,12 @@ struct SshUserAuthBanner
QByteArray language;
};
+struct SshUserAuthPkOkPacket
+{
+ QByteArray algoName;
+ QByteArray keyBlob;
+};
+
struct SshUserAuthInfoRequestPacket
{
QString name;
@@ -176,6 +182,7 @@ public:
SshDisconnect extractDisconnect() const;
SshUserAuthBanner extractUserAuthBanner() const;
SshUserAuthInfoRequestPacket extractUserAuthInfoRequest() const;
+ SshUserAuthPkOkPacket extractUserAuthPkOk() const;
SshDebug extractDebug() const;
SshRequestSuccess extractRequestSuccess() const;
SshUnimplemented extractUnimplemented() const;
diff --git a/src/libs/ssh/sshoutgoingpacket.cpp b/src/libs/ssh/sshoutgoingpacket.cpp
index c74a8950f1..ae505700e0 100644
--- a/src/libs/ssh/sshoutgoingpacket.cpp
+++ b/src/libs/ssh/sshoutgoingpacket.cpp
@@ -25,9 +25,11 @@
#include "sshoutgoingpacket_p.h"
+#include "sshagent_p.h"
#include "sshcapabilities_p.h"
#include "sshcryptofacility_p.h"
#include "sshlogging_p.h"
+#include "sshpacketparser_p.h"
#include <QtEndian>
@@ -117,17 +119,41 @@ void SshOutgoingPacket::generateUserAuthByPasswordRequestPacket(const QByteArray
}
void SshOutgoingPacket::generateUserAuthByPublicKeyRequestPacket(const QByteArray &user,
- const QByteArray &service)
+ const QByteArray &service, const QByteArray &key, const QByteArray &signature)
{
init(SSH_MSG_USERAUTH_REQUEST).appendString(user).appendString(service)
- .appendString("publickey").appendBool(true)
- .appendString(m_encrypter.authenticationAlgorithmName())
- .appendString(m_encrypter.authenticationPublicKey());
- const QByteArray &dataToSign = m_data.mid(PayloadOffset);
- appendString(m_encrypter.authenticationKeySignature(dataToSign));
+ .appendString("publickey").appendBool(true);
+ if (!key.isEmpty()) {
+ appendString(SshPacketParser::asString(key, quint32(0)));
+ appendString(key);
+ appendString(signature);
+ } else {
+ appendString(m_encrypter.authenticationAlgorithmName());
+ appendString(m_encrypter.authenticationPublicKey());
+ const QByteArray &dataToSign = m_data.mid(PayloadOffset);
+ appendString(m_encrypter.authenticationKeySignature(dataToSign));
+ }
finalize();
}
+void SshOutgoingPacket::generateQueryPublicKeyPacket(const QByteArray &user,
+ const QByteArray &service, const QByteArray &publicKey)
+{
+ // Name extraction cannot fail, we already verified this when receiving the key
+ // from the agent.
+ const QByteArray algoName = SshPacketParser::asString(publicKey, quint32(0));
+ SshOutgoingPacket packetToSign(m_encrypter, m_seqNr);
+ packetToSign.init(SSH_MSG_USERAUTH_REQUEST).appendString(user).appendString(service)
+ .appendString("publickey").appendBool(true).appendString(algoName)
+ .appendString(publicKey);
+ const QByteArray &dataToSign
+ = encodeString(m_encrypter.sessionId()) + packetToSign.m_data.mid(PayloadOffset);
+ SshAgent::storeDataToSign(publicKey, dataToSign, qHash(m_encrypter.sessionId()));
+ init(SSH_MSG_USERAUTH_REQUEST).appendString(user).appendString(service)
+ .appendString("publickey").appendBool(false).appendString(algoName)
+ .appendString(publicKey).finalize();
+}
+
void SshOutgoingPacket::generateUserAuthByKeyboardInteractiveRequestPacket(const QByteArray &user,
const QByteArray &service)
{
diff --git a/src/libs/ssh/sshoutgoingpacket_p.h b/src/libs/ssh/sshoutgoingpacket_p.h
index d2d4f63f16..8e3da5aef6 100644
--- a/src/libs/ssh/sshoutgoingpacket_p.h
+++ b/src/libs/ssh/sshoutgoingpacket_p.h
@@ -53,7 +53,9 @@ public:
void generateUserAuthByPasswordRequestPacket(const QByteArray &user,
const QByteArray &service, const QByteArray &pwd);
void generateUserAuthByPublicKeyRequestPacket(const QByteArray &user,
- const QByteArray &service);
+ const QByteArray &service, const QByteArray &key, const QByteArray &signature);
+ void generateQueryPublicKeyPacket(const QByteArray &user, const QByteArray &service,
+ const QByteArray &publicKey);
void generateUserAuthByKeyboardInteractiveRequestPacket(const QByteArray &user,
const QByteArray &service);
void generateUserAuthInfoResponsePacket(const QStringList &responses);
diff --git a/src/libs/ssh/sshpacketparser.cpp b/src/libs/ssh/sshpacketparser.cpp
index 38f9c5a04d..6251f60244 100644
--- a/src/libs/ssh/sshpacketparser.cpp
+++ b/src/libs/ssh/sshpacketparser.cpp
@@ -98,6 +98,11 @@ quint64 SshPacketParser::asUint64(const QByteArray &data, quint32 *offset)
return val;
}
+QByteArray SshPacketParser::asString(const QByteArray &data, quint32 offset)
+{
+ return asString(data, &offset);
+}
+
QByteArray SshPacketParser::asString(const QByteArray &data, quint32 *offset)
{
const quint32 length = asUint32(data, offset);
diff --git a/src/libs/ssh/sshpacketparser_p.h b/src/libs/ssh/sshpacketparser_p.h
index 8dd70511d2..b57f22f084 100644
--- a/src/libs/ssh/sshpacketparser_p.h
+++ b/src/libs/ssh/sshpacketparser_p.h
@@ -62,6 +62,7 @@ public:
static quint64 asUint64(const QByteArray &data, quint32 *offset);
static quint32 asUint32(const QByteArray &data, quint32 offset);
static quint32 asUint32(const QByteArray &data, quint32 *offset);
+ static QByteArray asString(const QByteArray &data, quint32 offset);
static QByteArray asString(const QByteArray &data, quint32 *offset);
static QString asUserString(const QByteArray &data, quint32 *offset);
static SshNameList asNameList(const QByteArray &data, quint32 *offset);
diff --git a/src/libs/ssh/sshsendfacility.cpp b/src/libs/ssh/sshsendfacility.cpp
index 491c6981b9..9552d3e020 100644
--- a/src/libs/ssh/sshsendfacility.cpp
+++ b/src/libs/ssh/sshsendfacility.cpp
@@ -118,9 +118,16 @@ void SshSendFacility::sendUserAuthByPasswordRequestPacket(const QByteArray &user
}
void SshSendFacility::sendUserAuthByPublicKeyRequestPacket(const QByteArray &user,
- const QByteArray &service)
+ const QByteArray &service, const QByteArray &key, const QByteArray &signature)
{
- m_outgoingPacket.generateUserAuthByPublicKeyRequestPacket(user, service);
+ m_outgoingPacket.generateUserAuthByPublicKeyRequestPacket(user, service, key, signature);
+ sendPacket();
+}
+
+void SshSendFacility::sendQueryPublicKeyPacket(const QByteArray &user, const QByteArray &service,
+ const QByteArray &publicKey)
+{
+ m_outgoingPacket.generateQueryPublicKeyPacket(user, service, publicKey);
sendPacket();
}
diff --git a/src/libs/ssh/sshsendfacility_p.h b/src/libs/ssh/sshsendfacility_p.h
index 0eb92ae0b1..f54a2a9463 100644
--- a/src/libs/ssh/sshsendfacility_p.h
+++ b/src/libs/ssh/sshsendfacility_p.h
@@ -49,6 +49,8 @@ public:
void recreateKeys(const SshKeyExchange &keyExchange);
void createAuthenticationKey(const QByteArray &privKeyFileContents);
+ QByteArray sessionId() const { return m_encrypter.sessionId(); }
+
QByteArray sendKeyExchangeInitPacket();
void sendKeyDhInitPacket(const Botan::BigInt &e);
void sendKeyEcdhInitPacket(const QByteArray &clientQ);
@@ -60,7 +62,9 @@ public:
void sendUserAuthByPasswordRequestPacket(const QByteArray &user,
const QByteArray &service, const QByteArray &pwd);
void sendUserAuthByPublicKeyRequestPacket(const QByteArray &user,
- const QByteArray &service);
+ const QByteArray &service, const QByteArray &key, const QByteArray &signature);
+ void sendQueryPublicKeyPacket(const QByteArray &user, const QByteArray &service,
+ const QByteArray &publicKey);
void sendUserAuthByKeyboardInteractiveRequestPacket(const QByteArray &user,
const QByteArray &service);
void sendUserAuthInfoResponsePacket(const QStringList &responses);
diff --git a/src/plugins/remotelinux/genericlinuxdeviceconfigurationwidget.cpp b/src/plugins/remotelinux/genericlinuxdeviceconfigurationwidget.cpp
index 3bcb2c9587..cc46d1bcdd 100644
--- a/src/plugins/remotelinux/genericlinuxdeviceconfigurationwidget.cpp
+++ b/src/plugins/remotelinux/genericlinuxdeviceconfigurationwidget.cpp
@@ -58,6 +58,8 @@ GenericLinuxDeviceConfigurationWidget::GenericLinuxDeviceConfigurationWidget(
this, &GenericLinuxDeviceConfigurationWidget::keyFileEditingFinished);
connect(m_ui->keyButton, &QAbstractButton::toggled,
this, &GenericLinuxDeviceConfigurationWidget::authenticationTypeChanged);
+ connect(m_ui->agentButton, &QAbstractButton::toggled,
+ this, &GenericLinuxDeviceConfigurationWidget::authenticationTypeChanged);
connect(m_ui->timeoutSpinBox, &QAbstractSpinBox::editingFinished,
this, &GenericLinuxDeviceConfigurationWidget::timeoutEditingFinished);
connect(m_ui->timeoutSpinBox, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged),
@@ -89,14 +91,16 @@ void GenericLinuxDeviceConfigurationWidget::authenticationTypeChanged()
{
SshConnectionParameters sshParams = device()->sshParameters();
const bool usePassword = m_ui->passwordButton->isChecked();
- sshParams.authenticationType = usePassword
- ? SshConnectionParameters::AuthenticationTypeTryAllPasswordBasedMethods
- : SshConnectionParameters::AuthenticationTypePublicKey;
+ const bool useKeyFile = m_ui->keyButton->isChecked();
+ sshParams.authenticationType
+ = usePassword ? SshConnectionParameters::AuthenticationTypeTryAllPasswordBasedMethods
+ : useKeyFile ? SshConnectionParameters::AuthenticationTypePublicKey
+ : SshConnectionParameters::AuthenticationTypeAgent;
device()->setSshParameters(sshParams);
m_ui->pwdLineEdit->setEnabled(usePassword);
m_ui->passwordLabel->setEnabled(usePassword);
- m_ui->keyFileLineEdit->setEnabled(!usePassword);
- m_ui->keyLabel->setEnabled(!usePassword);
+ m_ui->keyFileLineEdit->setEnabled(useKeyFile);
+ m_ui->keyLabel->setEnabled(useKeyFile);
}
void GenericLinuxDeviceConfigurationWidget::hostNameEditingFinished()
@@ -214,10 +218,18 @@ void GenericLinuxDeviceConfigurationWidget::initGui()
const SshConnectionParameters &sshParams = device()->sshParameters();
- if (sshParams.authenticationType != SshConnectionParameters::AuthenticationTypePublicKey)
- m_ui->passwordButton->setChecked(true);
- else
+ switch (sshParams.authenticationType) {
+ case SshConnectionParameters::AuthenticationTypePublicKey:
m_ui->keyButton->setChecked(true);
+ break;
+ case SshConnectionParameters::AuthenticationTypeAgent:
+ m_ui->agentButton->setChecked(true);
+ break;
+ case SshConnectionParameters::AuthenticationTypePassword:
+ case SshConnectionParameters::AuthenticationTypeKeyboardInteractive:
+ case SshConnectionParameters::AuthenticationTypeTryAllPasswordBasedMethods:
+ m_ui->passwordButton->setChecked(true);
+ }
m_ui->timeoutSpinBox->setValue(sshParams.timeout);
m_ui->hostLineEdit->setEnabled(!device()->isAutoDetected());
m_ui->sshPortSpinBox->setEnabled(!device()->isAutoDetected());
diff --git a/src/plugins/remotelinux/genericlinuxdeviceconfigurationwidget.ui b/src/plugins/remotelinux/genericlinuxdeviceconfigurationwidget.ui
index 46ad3645f0..e2854e47cc 100644
--- a/src/plugins/remotelinux/genericlinuxdeviceconfigurationwidget.ui
+++ b/src/plugins/remotelinux/genericlinuxdeviceconfigurationwidget.ui
@@ -66,6 +66,13 @@
</widget>
</item>
<item>
+ <widget class="QRadioButton" name="agentButton">
+ <property name="text">
+ <string>Key via ssh-agent</string>
+ </property>
+ </widget>
+ </item>
+ <item>
<spacer name="horizontalSpacer_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
diff --git a/src/plugins/remotelinux/genericlinuxdeviceconfigurationwizardpages.cpp b/src/plugins/remotelinux/genericlinuxdeviceconfigurationwizardpages.cpp
index c9fe3be665..5bd8bf6c89 100644
--- a/src/plugins/remotelinux/genericlinuxdeviceconfigurationwizardpages.cpp
+++ b/src/plugins/remotelinux/genericlinuxdeviceconfigurationwizardpages.cpp
@@ -64,6 +64,10 @@ GenericLinuxDeviceConfigurationWizardSetupPage::GenericLinuxDeviceConfigurationW
this, &QWizardPage::completeChanged);
connect(d->ui.passwordButton, &QAbstractButton::toggled,
this, &GenericLinuxDeviceConfigurationWizardSetupPage::handleAuthTypeChanged);
+ connect(d->ui.keyButton, &QAbstractButton::toggled,
+ this, &GenericLinuxDeviceConfigurationWizardSetupPage::handleAuthTypeChanged);
+ connect(d->ui.agentButton, &QAbstractButton::toggled,
+ this, &GenericLinuxDeviceConfigurationWizardSetupPage::handleAuthTypeChanged);
}
GenericLinuxDeviceConfigurationWizardSetupPage::~GenericLinuxDeviceConfigurationWizardSetupPage()
@@ -107,8 +111,9 @@ QString GenericLinuxDeviceConfigurationWizardSetupPage::userName() const
SshConnectionParameters::AuthenticationType GenericLinuxDeviceConfigurationWizardSetupPage::authenticationType() const
{
return d->ui.passwordButton->isChecked()
- ? SshConnectionParameters::AuthenticationTypeTryAllPasswordBasedMethods
- : SshConnectionParameters::AuthenticationTypePublicKey;
+ ? SshConnectionParameters::AuthenticationTypeTryAllPasswordBasedMethods
+ : d->ui.keyButton->isChecked() ? SshConnectionParameters::AuthenticationTypePublicKey
+ : SshConnectionParameters::AuthenticationTypeAgent;
}
QString GenericLinuxDeviceConfigurationWizardSetupPage::password() const
@@ -143,8 +148,10 @@ QString GenericLinuxDeviceConfigurationWizardSetupPage::defaultPassWord() const
void GenericLinuxDeviceConfigurationWizardSetupPage::handleAuthTypeChanged()
{
- d->ui.passwordLineEdit->setEnabled(authenticationType() != SshConnectionParameters::AuthenticationTypePublicKey);
- d->ui.privateKeyPathChooser->setEnabled(!d->ui.passwordLineEdit->isEnabled());
+ d->ui.passwordLineEdit->setEnabled(authenticationType()
+ == SshConnectionParameters::AuthenticationTypeTryAllPasswordBasedMethods);
+ d->ui.privateKeyPathChooser->setEnabled(authenticationType()
+ == SshConnectionParameters::AuthenticationTypePublicKey);
emit completeChanged();
}
diff --git a/src/plugins/remotelinux/genericlinuxdeviceconfigurationwizardsetuppage.ui b/src/plugins/remotelinux/genericlinuxdeviceconfigurationwizardsetuppage.ui
index 6a1e8de8af..54ccf0d7b0 100644
--- a/src/plugins/remotelinux/genericlinuxdeviceconfigurationwizardsetuppage.ui
+++ b/src/plugins/remotelinux/genericlinuxdeviceconfigurationwizardsetuppage.ui
@@ -106,6 +106,13 @@
</widget>
</item>
<item>
+ <widget class="QRadioButton" name="agentButton">
+ <property name="text">
+ <string>Agent</string>
+ </property>
+ </widget>
+ </item>
+ <item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>