SSH: Add support for ssh-agent

Task-number: QTCREATORBUG-16245
Change-Id: Ifd30c89d19e547d7657765790b7520e42b3741c3
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
This commit is contained in:
Christian Kandeler
2016-07-20 18:04:56 +02:00
committed by Christian Kandeler
parent fcdc9342b5
commit 3ee2445fb1
21 changed files with 748 additions and 40 deletions

View File

@@ -33,7 +33,8 @@ SOURCES = $$PWD/sshsendfacility.cpp \
$$PWD/sshhostkeydatabase.cpp \ $$PWD/sshhostkeydatabase.cpp \
$$PWD/sshtcpipforwardserver.cpp \ $$PWD/sshtcpipforwardserver.cpp \
$$PWD/sshtcpiptunnel.cpp \ $$PWD/sshtcpiptunnel.cpp \
$$PWD/sshforwardedtcpiptunnel.cpp $$PWD/sshforwardedtcpiptunnel.cpp \
$$PWD/sshagent.cpp
HEADERS = $$PWD/sshsendfacility_p.h \ HEADERS = $$PWD/sshsendfacility_p.h \
$$PWD/sshremoteprocess.h \ $$PWD/sshremoteprocess.h \
@@ -76,7 +77,8 @@ HEADERS = $$PWD/sshsendfacility_p.h \
$$PWD/sshtcpipforwardserver_p.h \ $$PWD/sshtcpipforwardserver_p.h \
$$PWD/sshtcpiptunnel_p.h \ $$PWD/sshtcpiptunnel_p.h \
$$PWD/sshforwardedtcpiptunnel.h \ $$PWD/sshforwardedtcpiptunnel.h \
$$PWD/sshforwardedtcpiptunnel_p.h $$PWD/sshforwardedtcpiptunnel_p.h \
$$PWD/sshagent_p.h
FORMS = $$PWD/sshkeycreationdialog.ui FORMS = $$PWD/sshkeycreationdialog.ui

View File

@@ -22,6 +22,7 @@ Project {
"sftpoperation.cpp", "sftpoperation_p.h", "sftpoperation.cpp", "sftpoperation_p.h",
"sftpoutgoingpacket.cpp", "sftpoutgoingpacket_p.h", "sftpoutgoingpacket.cpp", "sftpoutgoingpacket_p.h",
"sftppacket.cpp", "sftppacket_p.h", "sftppacket.cpp", "sftppacket_p.h",
"sshagent.cpp", "sshagent_p.h",
"sshbotanconversions_p.h", "sshbotanconversions_p.h",
"sshcapabilities_p.h", "sshcapabilities.cpp", "sshcapabilities_p.h", "sshcapabilities.cpp",
"sshchannel.cpp", "sshchannel_p.h", "sshchannel.cpp", "sshchannel_p.h",

314
src/libs/ssh/sshagent.cpp Normal file
View File

@@ -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

125
src/libs/ssh/sshagent_p.h Normal file
View File

@@ -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

View File

@@ -27,6 +27,7 @@
#include "sshconnection_p.h" #include "sshconnection_p.h"
#include "sftpchannel.h" #include "sftpchannel.h"
#include "sshagent_p.h"
#include "sshcapabilities_p.h" #include "sshcapabilities_p.h"
#include "sshchannelmanager_p.h" #include "sshchannelmanager_p.h"
#include "sshcryptofacility_p.h" #include "sshcryptofacility_p.h"
@@ -270,6 +271,7 @@ void SshConnectionPrivate::setupPacketHandlers()
setupPacketHandler(SSH_MSG_USERAUTH_INFO_REQUEST, authReqList, setupPacketHandler(SSH_MSG_USERAUTH_INFO_REQUEST, authReqList,
&This::handleUserAuthInfoRequestPacket); &This::handleUserAuthInfoRequestPacket);
} }
setupPacketHandler(SSH_MSG_USERAUTH_PK_OK, authReqList, &This::handleUserAuthKeyOkPacket);
const StateList connectedList const StateList connectedList
= StateList() << ConnectionEstablished; = StateList() << ConnectionEstablished;
@@ -299,7 +301,7 @@ void SshConnectionPrivate::setupPacketHandlers()
setupPacketHandler(SSH_MSG_CHANNEL_CLOSE, connectedOrClosedList, setupPacketHandler(SSH_MSG_CHANNEL_CLOSE, connectedOrClosedList,
&This::handleChannelClose); &This::handleChannelClose);
setupPacketHandler(SSH_MSG_DISCONNECT, StateList() << SocketConnected setupPacketHandler(SSH_MSG_DISCONNECT, StateList() << SocketConnected << WaitingForAgentKeys
<< UserAuthServiceRequested << UserAuthRequested << UserAuthServiceRequested << UserAuthRequested
<< ConnectionEstablished, &This::handleDisconnect); << ConnectionEstablished, &This::handleDisconnect);
@@ -527,8 +529,18 @@ void SshConnectionPrivate::handleServiceAcceptPacket()
SshCapabilities::SshConnectionService); SshCapabilities::SshConnectionService);
break; break;
case SshConnectionParameters::AuthenticationTypePublicKey: case SshConnectionParameters::AuthenticationTypePublicKey:
m_sendFacility.sendUserAuthByPublicKeyRequestPacket(m_connParams.userName.toUtf8(), authenticateWithPublicKey();
SshCapabilities::SshConnectionService); 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; break;
} }
m_state = UserAuthRequested; m_state = UserAuthRequested;
@@ -596,6 +608,18 @@ void SshConnectionPrivate::handleUserAuthSuccessPacket()
void SshConnectionPrivate::handleUserAuthFailurePacket() 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. // TODO: Evaluate "authentications that can continue" field and act on it.
if (m_connParams.authenticationType if (m_connParams.authenticationType
== SshConnectionParameters::AuthenticationTypeTryAllPasswordBasedMethods == SshConnectionParameters::AuthenticationTypeTryAllPasswordBasedMethods
@@ -608,10 +632,45 @@ void SshConnectionPrivate::handleUserAuthFailurePacket()
} }
m_timeoutTimer.stop(); m_timeoutTimer.stop();
const QString errorMsg = m_connParams.authenticationType == SshConnectionParameters::AuthenticationTypePublicKey QString errorMsg;
? tr("Server rejected key.") : tr("Server rejected password."); 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); 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() void SshConnectionPrivate::handleDebugPacket()
{ {
const SshDebug &msg = m_incomingPacket.extractDebug(); const SshDebug &msg = m_incomingPacket.extractDebug();
@@ -710,6 +769,11 @@ void SshConnectionPrivate::sendData(const QByteArray &data)
m_socket->write(data); m_socket->write(data);
} }
uint SshConnectionPrivate::tokenForAgent() const
{
return qHash(m_sendFacility.sessionId());
}
void SshConnectionPrivate::handleSocketDisconnected() void SshConnectionPrivate::handleSocketDisconnected()
{ {
closeConnection(SSH_DISCONNECT_CONNECTION_LOST, SshClosedByServerError, closeConnection(SSH_DISCONNECT_CONNECTION_LOST, SshClosedByServerError,
@@ -727,8 +791,10 @@ void SshConnectionPrivate::handleSocketError()
void SshConnectionPrivate::handleTimeout() void SshConnectionPrivate::handleTimeout()
{ {
closeConnection(SSH_DISCONNECT_BY_APPLICATION, SshTimeoutError, "", const QString errorMessage = m_state == WaitingForAgentKeys
tr("Timeout waiting for reply from server.")); ? 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() void SshConnectionPrivate::sendKeepAlivePacket()
@@ -745,6 +811,66 @@ void SshConnectionPrivate::sendKeepAlivePacket()
m_timeoutTimer.start(); 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() void SshConnectionPrivate::connectToHost()
{ {
QSSH_ASSERT_AND_RETURN(m_state == SocketUnconnected); QSSH_ASSERT_AND_RETURN(m_state == SocketUnconnected);
@@ -757,16 +883,38 @@ void SshConnectionPrivate::connectToHost()
m_errorString.clear(); m_errorString.clear();
m_serverId.clear(); m_serverId.clear();
m_serverHasSentDataBeforeId = false; m_serverHasSentDataBeforeId = false;
m_agentSignature.clear();
m_agentKeysUpToDate = false;
m_pendingKeyChecks.clear();
m_agentKeyToUse.clear();
switch (m_connParams.authenticationType) {
case SshConnectionParameters::AuthenticationTypePublicKey:
try { try {
if (m_connParams.authenticationType == SshConnectionParameters::AuthenticationTypePublicKey)
createPrivateKey(); createPrivateKey();
break;
} catch (const SshClientException &ex) { } catch (const SshClientException &ex) {
m_error = ex.error; m_error = ex.error;
m_errorString = ex.errorString; m_errorString = ex.errorString;
emit error(m_error); emit error(m_error);
return; 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, connect(m_socket, &QAbstractSocket::connected,
this, &SshConnectionPrivate::handleSocketConnected); this, &SshConnectionPrivate::handleSocketConnected);

View File

@@ -65,6 +65,7 @@ public:
enum AuthenticationType { enum AuthenticationType {
AuthenticationTypePassword, AuthenticationTypePassword,
AuthenticationTypePublicKey, AuthenticationTypePublicKey,
AuthenticationTypeAgent,
AuthenticationTypeKeyboardInteractive, AuthenticationTypeKeyboardInteractive,
// Some servers disable "password", others disable "keyboard-interactive". // Some servers disable "password", others disable "keyboard-interactive".

View File

@@ -32,6 +32,7 @@
#include <QHash> #include <QHash>
#include <QList> #include <QList>
#include <QQueue>
#include <QObject> #include <QObject>
#include <QPair> #include <QPair>
#include <QScopedPointer> #include <QScopedPointer>
@@ -56,6 +57,7 @@ enum SshStateInternal {
SocketConnecting, // After connectToHost() SocketConnecting, // After connectToHost()
SocketConnected, // After socket's connected() signal SocketConnected, // After socket's connected() signal
UserAuthServiceRequested, UserAuthServiceRequested,
WaitingForAgentKeys,
UserAuthRequested, UserAuthRequested,
ConnectionEstablished // After service has been started ConnectionEstablished // After service has been started
// ... // ...
@@ -107,6 +109,12 @@ private:
void handleTimeout(); void handleTimeout();
void sendKeepAlivePacket(); void sendKeepAlivePacket();
void handleAgentKeysUpdated();
void handleSignatureFromAgent(const QByteArray &key, const QByteArray &signature, uint token);
void tryAllAgentKeys();
void authenticateWithPublicKey();
void setAgentError();
void handleServerId(); void handleServerId();
void handlePackets(); void handlePackets();
void handleCurrentPacket(); void handleCurrentPacket();
@@ -118,6 +126,7 @@ private:
void handleUserAuthInfoRequestPacket(); void handleUserAuthInfoRequestPacket();
void handleUserAuthSuccessPacket(); void handleUserAuthSuccessPacket();
void handleUserAuthFailurePacket(); void handleUserAuthFailurePacket();
void handleUserAuthKeyOkPacket();
void handleUserAuthBannerPacket(); void handleUserAuthBannerPacket();
void handleUnexpectedPacket(); void handleUnexpectedPacket();
void handleGlobalRequest(); void handleGlobalRequest();
@@ -143,6 +152,8 @@ private:
void sendData(const QByteArray &data); void sendData(const QByteArray &data);
uint tokenForAgent() const;
typedef void (SshConnectionPrivate::*PacketHandler)(); typedef void (SshConnectionPrivate::*PacketHandler)();
typedef QList<SshStateInternal> StateList; typedef QList<SshStateInternal> StateList;
void setupPacketHandlers(); void setupPacketHandlers();
@@ -171,8 +182,12 @@ private:
SshConnection *m_conn; SshConnection *m_conn;
quint64 m_lastInvalidMsgSeqNr; quint64 m_lastInvalidMsgSeqNr;
QByteArray m_serverId; QByteArray m_serverId;
QByteArray m_agentSignature;
QQueue<QByteArray> m_pendingKeyChecks;
QByteArray m_agentKeyToUse;
bool m_serverHasSentDataBeforeId; bool m_serverHasSentDataBeforeId;
bool m_triedAllPasswordBasedMethods; bool m_triedAllPasswordBasedMethods;
bool m_agentKeysUpToDate;
}; };
} // namespace Internal } // namespace Internal

View File

@@ -45,13 +45,13 @@ public:
QByteArray generateMac(const QByteArray &data, quint32 dataSize) const; QByteArray generateMac(const QByteArray &data, quint32 dataSize) const;
quint32 cipherBlockSize() const { return m_cipherBlockSize; } quint32 cipherBlockSize() const { return m_cipherBlockSize; }
quint32 macLength() const { return m_macLength; } quint32 macLength() const { return m_macLength; }
QByteArray sessionId() const { return m_sessionId; }
protected: protected:
enum Mode { CbcMode, CtrMode }; enum Mode { CbcMode, CtrMode };
SshAbstractCryptoFacility(); SshAbstractCryptoFacility();
void convert(QByteArray &data, quint32 offset, quint32 dataSize) const; void convert(QByteArray &data, quint32 offset, quint32 dataSize) const;
QByteArray sessionId() const { return m_sessionId; }
Botan::Keyed_Filter *makeCtrCipherMode(Botan::BlockCipher *cipher, Botan::Keyed_Filter *makeCtrCipherMode(Botan::BlockCipher *cipher,
const Botan::InitializationVector &iv, const Botan::SymmetricKey &key); const Botan::InitializationVector &iv, const Botan::SymmetricKey &key);

View File

@@ -31,7 +31,7 @@ namespace QSsh {
enum SshError { enum SshError {
SshNoError, SshSocketError, SshTimeoutError, SshProtocolError, SshNoError, SshSocketError, SshTimeoutError, SshProtocolError,
SshHostKeyError, SshKeyFileError, SshAuthenticationError, SshHostKeyError, SshKeyFileError, SshAuthenticationError,
SshClosedByServerError, SshInternalError SshClosedByServerError, SshAgentError, SshInternalError
}; };
} // namespace QSsh } // namespace QSsh

View File

@@ -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 SshDebug SshIncomingPacket::extractDebug() const
{ {
Q_ASSERT(isComplete()); Q_ASSERT(isComplete());

View File

@@ -76,6 +76,12 @@ struct SshUserAuthBanner
QByteArray language; QByteArray language;
}; };
struct SshUserAuthPkOkPacket
{
QByteArray algoName;
QByteArray keyBlob;
};
struct SshUserAuthInfoRequestPacket struct SshUserAuthInfoRequestPacket
{ {
QString name; QString name;
@@ -176,6 +182,7 @@ public:
SshDisconnect extractDisconnect() const; SshDisconnect extractDisconnect() const;
SshUserAuthBanner extractUserAuthBanner() const; SshUserAuthBanner extractUserAuthBanner() const;
SshUserAuthInfoRequestPacket extractUserAuthInfoRequest() const; SshUserAuthInfoRequestPacket extractUserAuthInfoRequest() const;
SshUserAuthPkOkPacket extractUserAuthPkOk() const;
SshDebug extractDebug() const; SshDebug extractDebug() const;
SshRequestSuccess extractRequestSuccess() const; SshRequestSuccess extractRequestSuccess() const;
SshUnimplemented extractUnimplemented() const; SshUnimplemented extractUnimplemented() const;

View File

@@ -25,9 +25,11 @@
#include "sshoutgoingpacket_p.h" #include "sshoutgoingpacket_p.h"
#include "sshagent_p.h"
#include "sshcapabilities_p.h" #include "sshcapabilities_p.h"
#include "sshcryptofacility_p.h" #include "sshcryptofacility_p.h"
#include "sshlogging_p.h" #include "sshlogging_p.h"
#include "sshpacketparser_p.h"
#include <QtEndian> #include <QtEndian>
@@ -117,17 +119,41 @@ void SshOutgoingPacket::generateUserAuthByPasswordRequestPacket(const QByteArray
} }
void SshOutgoingPacket::generateUserAuthByPublicKeyRequestPacket(const QByteArray &user, 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) init(SSH_MSG_USERAUTH_REQUEST).appendString(user).appendString(service)
.appendString("publickey").appendBool(true) .appendString("publickey").appendBool(true);
.appendString(m_encrypter.authenticationAlgorithmName()) if (!key.isEmpty()) {
.appendString(m_encrypter.authenticationPublicKey()); 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); const QByteArray &dataToSign = m_data.mid(PayloadOffset);
appendString(m_encrypter.authenticationKeySignature(dataToSign)); appendString(m_encrypter.authenticationKeySignature(dataToSign));
}
finalize(); 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, void SshOutgoingPacket::generateUserAuthByKeyboardInteractiveRequestPacket(const QByteArray &user,
const QByteArray &service) const QByteArray &service)
{ {

View File

@@ -53,7 +53,9 @@ public:
void generateUserAuthByPasswordRequestPacket(const QByteArray &user, void generateUserAuthByPasswordRequestPacket(const QByteArray &user,
const QByteArray &service, const QByteArray &pwd); const QByteArray &service, const QByteArray &pwd);
void generateUserAuthByPublicKeyRequestPacket(const QByteArray &user, 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, void generateUserAuthByKeyboardInteractiveRequestPacket(const QByteArray &user,
const QByteArray &service); const QByteArray &service);
void generateUserAuthInfoResponsePacket(const QStringList &responses); void generateUserAuthInfoResponsePacket(const QStringList &responses);

View File

@@ -98,6 +98,11 @@ quint64 SshPacketParser::asUint64(const QByteArray &data, quint32 *offset)
return val; return val;
} }
QByteArray SshPacketParser::asString(const QByteArray &data, quint32 offset)
{
return asString(data, &offset);
}
QByteArray SshPacketParser::asString(const QByteArray &data, quint32 *offset) QByteArray SshPacketParser::asString(const QByteArray &data, quint32 *offset)
{ {
const quint32 length = asUint32(data, offset); const quint32 length = asUint32(data, offset);

View File

@@ -62,6 +62,7 @@ public:
static quint64 asUint64(const QByteArray &data, quint32 *offset); 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 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 QByteArray asString(const QByteArray &data, quint32 *offset);
static QString asUserString(const QByteArray &data, quint32 *offset); static QString asUserString(const QByteArray &data, quint32 *offset);
static SshNameList asNameList(const QByteArray &data, quint32 *offset); static SshNameList asNameList(const QByteArray &data, quint32 *offset);

View File

@@ -118,9 +118,16 @@ void SshSendFacility::sendUserAuthByPasswordRequestPacket(const QByteArray &user
} }
void SshSendFacility::sendUserAuthByPublicKeyRequestPacket(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(); sendPacket();
} }

View File

@@ -49,6 +49,8 @@ public:
void recreateKeys(const SshKeyExchange &keyExchange); void recreateKeys(const SshKeyExchange &keyExchange);
void createAuthenticationKey(const QByteArray &privKeyFileContents); void createAuthenticationKey(const QByteArray &privKeyFileContents);
QByteArray sessionId() const { return m_encrypter.sessionId(); }
QByteArray sendKeyExchangeInitPacket(); QByteArray sendKeyExchangeInitPacket();
void sendKeyDhInitPacket(const Botan::BigInt &e); void sendKeyDhInitPacket(const Botan::BigInt &e);
void sendKeyEcdhInitPacket(const QByteArray &clientQ); void sendKeyEcdhInitPacket(const QByteArray &clientQ);
@@ -60,7 +62,9 @@ public:
void sendUserAuthByPasswordRequestPacket(const QByteArray &user, void sendUserAuthByPasswordRequestPacket(const QByteArray &user,
const QByteArray &service, const QByteArray &pwd); const QByteArray &service, const QByteArray &pwd);
void sendUserAuthByPublicKeyRequestPacket(const QByteArray &user, 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, void sendUserAuthByKeyboardInteractiveRequestPacket(const QByteArray &user,
const QByteArray &service); const QByteArray &service);
void sendUserAuthInfoResponsePacket(const QStringList &responses); void sendUserAuthInfoResponsePacket(const QStringList &responses);

View File

@@ -58,6 +58,8 @@ GenericLinuxDeviceConfigurationWidget::GenericLinuxDeviceConfigurationWidget(
this, &GenericLinuxDeviceConfigurationWidget::keyFileEditingFinished); this, &GenericLinuxDeviceConfigurationWidget::keyFileEditingFinished);
connect(m_ui->keyButton, &QAbstractButton::toggled, connect(m_ui->keyButton, &QAbstractButton::toggled,
this, &GenericLinuxDeviceConfigurationWidget::authenticationTypeChanged); this, &GenericLinuxDeviceConfigurationWidget::authenticationTypeChanged);
connect(m_ui->agentButton, &QAbstractButton::toggled,
this, &GenericLinuxDeviceConfigurationWidget::authenticationTypeChanged);
connect(m_ui->timeoutSpinBox, &QAbstractSpinBox::editingFinished, connect(m_ui->timeoutSpinBox, &QAbstractSpinBox::editingFinished,
this, &GenericLinuxDeviceConfigurationWidget::timeoutEditingFinished); this, &GenericLinuxDeviceConfigurationWidget::timeoutEditingFinished);
connect(m_ui->timeoutSpinBox, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), connect(m_ui->timeoutSpinBox, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged),
@@ -89,14 +91,16 @@ void GenericLinuxDeviceConfigurationWidget::authenticationTypeChanged()
{ {
SshConnectionParameters sshParams = device()->sshParameters(); SshConnectionParameters sshParams = device()->sshParameters();
const bool usePassword = m_ui->passwordButton->isChecked(); const bool usePassword = m_ui->passwordButton->isChecked();
sshParams.authenticationType = usePassword const bool useKeyFile = m_ui->keyButton->isChecked();
? SshConnectionParameters::AuthenticationTypeTryAllPasswordBasedMethods sshParams.authenticationType
: SshConnectionParameters::AuthenticationTypePublicKey; = usePassword ? SshConnectionParameters::AuthenticationTypeTryAllPasswordBasedMethods
: useKeyFile ? SshConnectionParameters::AuthenticationTypePublicKey
: SshConnectionParameters::AuthenticationTypeAgent;
device()->setSshParameters(sshParams); device()->setSshParameters(sshParams);
m_ui->pwdLineEdit->setEnabled(usePassword); m_ui->pwdLineEdit->setEnabled(usePassword);
m_ui->passwordLabel->setEnabled(usePassword); m_ui->passwordLabel->setEnabled(usePassword);
m_ui->keyFileLineEdit->setEnabled(!usePassword); m_ui->keyFileLineEdit->setEnabled(useKeyFile);
m_ui->keyLabel->setEnabled(!usePassword); m_ui->keyLabel->setEnabled(useKeyFile);
} }
void GenericLinuxDeviceConfigurationWidget::hostNameEditingFinished() void GenericLinuxDeviceConfigurationWidget::hostNameEditingFinished()
@@ -214,10 +218,18 @@ void GenericLinuxDeviceConfigurationWidget::initGui()
const SshConnectionParameters &sshParams = device()->sshParameters(); const SshConnectionParameters &sshParams = device()->sshParameters();
if (sshParams.authenticationType != SshConnectionParameters::AuthenticationTypePublicKey) switch (sshParams.authenticationType) {
m_ui->passwordButton->setChecked(true); case SshConnectionParameters::AuthenticationTypePublicKey:
else
m_ui->keyButton->setChecked(true); 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->timeoutSpinBox->setValue(sshParams.timeout);
m_ui->hostLineEdit->setEnabled(!device()->isAutoDetected()); m_ui->hostLineEdit->setEnabled(!device()->isAutoDetected());
m_ui->sshPortSpinBox->setEnabled(!device()->isAutoDetected()); m_ui->sshPortSpinBox->setEnabled(!device()->isAutoDetected());

View File

@@ -65,6 +65,13 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QRadioButton" name="agentButton">
<property name="text">
<string>Key via ssh-agent</string>
</property>
</widget>
</item>
<item> <item>
<spacer name="horizontalSpacer_4"> <spacer name="horizontalSpacer_4">
<property name="orientation"> <property name="orientation">

View File

@@ -64,6 +64,10 @@ GenericLinuxDeviceConfigurationWizardSetupPage::GenericLinuxDeviceConfigurationW
this, &QWizardPage::completeChanged); this, &QWizardPage::completeChanged);
connect(d->ui.passwordButton, &QAbstractButton::toggled, connect(d->ui.passwordButton, &QAbstractButton::toggled,
this, &GenericLinuxDeviceConfigurationWizardSetupPage::handleAuthTypeChanged); this, &GenericLinuxDeviceConfigurationWizardSetupPage::handleAuthTypeChanged);
connect(d->ui.keyButton, &QAbstractButton::toggled,
this, &GenericLinuxDeviceConfigurationWizardSetupPage::handleAuthTypeChanged);
connect(d->ui.agentButton, &QAbstractButton::toggled,
this, &GenericLinuxDeviceConfigurationWizardSetupPage::handleAuthTypeChanged);
} }
GenericLinuxDeviceConfigurationWizardSetupPage::~GenericLinuxDeviceConfigurationWizardSetupPage() GenericLinuxDeviceConfigurationWizardSetupPage::~GenericLinuxDeviceConfigurationWizardSetupPage()
@@ -108,7 +112,8 @@ SshConnectionParameters::AuthenticationType GenericLinuxDeviceConfigurationWizar
{ {
return d->ui.passwordButton->isChecked() return d->ui.passwordButton->isChecked()
? SshConnectionParameters::AuthenticationTypeTryAllPasswordBasedMethods ? SshConnectionParameters::AuthenticationTypeTryAllPasswordBasedMethods
: SshConnectionParameters::AuthenticationTypePublicKey; : d->ui.keyButton->isChecked() ? SshConnectionParameters::AuthenticationTypePublicKey
: SshConnectionParameters::AuthenticationTypeAgent;
} }
QString GenericLinuxDeviceConfigurationWizardSetupPage::password() const QString GenericLinuxDeviceConfigurationWizardSetupPage::password() const
@@ -143,8 +148,10 @@ QString GenericLinuxDeviceConfigurationWizardSetupPage::defaultPassWord() const
void GenericLinuxDeviceConfigurationWizardSetupPage::handleAuthTypeChanged() void GenericLinuxDeviceConfigurationWizardSetupPage::handleAuthTypeChanged()
{ {
d->ui.passwordLineEdit->setEnabled(authenticationType() != SshConnectionParameters::AuthenticationTypePublicKey); d->ui.passwordLineEdit->setEnabled(authenticationType()
d->ui.privateKeyPathChooser->setEnabled(!d->ui.passwordLineEdit->isEnabled()); == SshConnectionParameters::AuthenticationTypeTryAllPasswordBasedMethods);
d->ui.privateKeyPathChooser->setEnabled(authenticationType()
== SshConnectionParameters::AuthenticationTypePublicKey);
emit completeChanged(); emit completeChanged();
} }

View File

@@ -105,6 +105,13 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QRadioButton" name="agentButton">
<property name="text">
<string>Agent</string>
</property>
</widget>
</item>
<item> <item>
<spacer name="horizontalSpacer_3"> <spacer name="horizontalSpacer_3">
<property name="orientation"> <property name="orientation">