forked from qt-creator/qt-creator
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:
committed by
Christian Kandeler
parent
fcdc9342b5
commit
3ee2445fb1
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
314
src/libs/ssh/sshagent.cpp
Normal 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
125
src/libs/ssh/sshagent_p.h
Normal 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
|
||||||
@@ -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);
|
||||||
|
|||||||
@@ -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".
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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());
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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());
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
Reference in New Issue
Block a user