sshkeyexchange.cpp 13.2 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
ck's avatar
ck committed
2
**
Eike Ziller's avatar
Eike Ziller committed
3 4
** Copyright (C) 2015 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing
ck's avatar
ck committed
5
**
hjk's avatar
hjk committed
6
** This file is part of Qt Creator.
ck's avatar
ck committed
7
**
hjk's avatar
hjk committed
8 9 10 11
** 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
Eike Ziller's avatar
Eike Ziller committed
12 13
** a written agreement between you and The Qt Company.  For licensing terms and
** conditions see http://www.qt.io/terms-conditions.  For further information
Eike Ziller's avatar
Eike Ziller committed
14
** use the contact form at http://www.qt.io/contact-us.
ck's avatar
ck committed
15 16
**
** GNU Lesser General Public License Usage
hjk's avatar
hjk committed
17
** Alternatively, this file may be used under the terms of the GNU Lesser
Eike Ziller's avatar
Eike Ziller committed
18 19 20 21 22 23
** General Public License version 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file.  Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
hjk's avatar
hjk committed
24
**
Eike Ziller's avatar
Eike Ziller committed
25 26
** In addition, as a special exception, The Qt Company gives you certain additional
** rights.  These rights are described in The Qt Company LGPL Exception
con's avatar
con committed
27 28
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
29
****************************************************************************/
ck's avatar
ck committed
30 31 32

#include "sshkeyexchange_p.h"

33
#include "ssh_global.h"
ck's avatar
ck committed
34 35 36 37 38 39 40 41
#include "sshbotanconversions_p.h"
#include "sshcapabilities_p.h"
#include "sshsendfacility_p.h"
#include "sshexception_p.h"
#include "sshincomingpacket_p.h"

#include <botan/botan.h>

42 43 44
#ifdef CREATOR_SSH_DEBUG
#include <iostream>
#endif
ck's avatar
ck committed
45 46 47 48
#include <string>

using namespace Botan;

49
namespace QSsh {
ck's avatar
ck committed
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
namespace Internal {

namespace {

    // For debugging
    void printNameList(const char *listName, const SshNameList &list)
    {
#ifdef CREATOR_SSH_DEBUG
        qDebug("%s:", listName);
        foreach (const QByteArray &name, list.names)
            qDebug("%s", name.constData());
#else
        Q_UNUSED(listName);
        Q_UNUSED(list);
#endif
    }
66 67 68 69 70 71 72 73 74 75 76 77

#ifdef CREATOR_SSH_DEBUG
    void printData(const char *name, const QByteArray &data)
    {
        std::cerr << std::hex;
        qDebug("The client thinks the %s has length %d and is:", name, data.count());
        for (int i = 0; i < data.count(); ++i)
            std::cerr << (static_cast<unsigned int>(data.at(i)) & 0xff) << ' ';
        std::cerr << std::endl;
    }
#endif

ck's avatar
ck committed
78 79
} // anonymous namespace

80 81 82
SshKeyExchange::SshKeyExchange(const SshConnectionParameters &connParams,
                               SshSendFacility &sendFacility)
    : m_connParams(connParams), m_sendFacility(sendFacility)
ck's avatar
ck committed
83 84 85 86 87 88 89 90
{
}

SshKeyExchange::~SshKeyExchange() {}

void SshKeyExchange::sendKexInitPacket(const QByteArray &serverId)
{
    m_serverId = serverId;
91
    m_clientKexInitPayload = m_sendFacility.sendKeyExchangeInitPacket();
ck's avatar
ck committed
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
}

bool SshKeyExchange::sendDhInitPacket(const SshIncomingPacket &serverKexInit)
{
#ifdef CREATOR_SSH_DEBUG
    qDebug("server requests key exchange");
#endif
    serverKexInit.printRawBytes();
    SshKeyExchangeInit kexInitParams
            = serverKexInit.extractKeyExchangeInitData();

    printNameList("Key Algorithms", kexInitParams.keyAlgorithms);
    printNameList("Server Host Key Algorithms", kexInitParams.serverHostKeyAlgorithms);
    printNameList("Encryption algorithms client to server", kexInitParams.encryptionAlgorithmsClientToServer);
    printNameList("Encryption algorithms server to client", kexInitParams.encryptionAlgorithmsServerToClient);
    printNameList("MAC algorithms client to server", kexInitParams.macAlgorithmsClientToServer);
    printNameList("MAC algorithms server to client", kexInitParams.macAlgorithmsServerToClient);
    printNameList("Compression algorithms client to server", kexInitParams.compressionAlgorithmsClientToServer);
    printNameList("Compression algorithms client to server", kexInitParams.compressionAlgorithmsClientToServer);
    printNameList("Languages client to server", kexInitParams.languagesClientToServer);
    printNameList("Languages server to client", kexInitParams.languagesServerToClient);
#ifdef CREATOR_SSH_DEBUG
    qDebug("First packet follows: %d", kexInitParams.firstKexPacketFollows);
#endif

117 118
    m_kexAlgoName = SshCapabilities::findBestMatch(SshCapabilities::KeyExchangeMethods,
                                                   kexInitParams.keyAlgorithms.names);
119 120
    m_serverHostKeyAlgo = SshCapabilities::findBestMatch(SshCapabilities::PublicKeyAlgorithms,
            kexInitParams.serverHostKeyAlgorithms.names);
121 122 123
    determineHashingAlgorithm(kexInitParams, true);
    determineHashingAlgorithm(kexInitParams, false);

ck's avatar
ck committed
124 125 126 127 128 129 130 131 132 133 134 135
    m_encryptionAlgo
        = SshCapabilities::findBestMatch(SshCapabilities::EncryptionAlgorithms,
              kexInitParams.encryptionAlgorithmsClientToServer.names);
    m_decryptionAlgo
        = SshCapabilities::findBestMatch(SshCapabilities::EncryptionAlgorithms,
              kexInitParams.encryptionAlgorithmsServerToClient.names);
    SshCapabilities::findBestMatch(SshCapabilities::CompressionAlgorithms,
        kexInitParams.compressionAlgorithmsClientToServer.names);
    SshCapabilities::findBestMatch(SshCapabilities::CompressionAlgorithms,
        kexInitParams.compressionAlgorithmsServerToClient.names);

    AutoSeeded_RNG rng;
136
    if (m_kexAlgoName.startsWith(SshCapabilities::EcdhKexNamePrefix)) {
137 138 139 140 141 142
        m_ecdhKey.reset(new ECDH_PrivateKey(rng, EC_Group(botanKeyExchangeAlgoName(m_kexAlgoName))));
        m_sendFacility.sendKeyEcdhInitPacket(convertByteArray(m_ecdhKey->public_value()));
    } else {
        m_dhKey.reset(new DH_PrivateKey(rng, DL_Group(botanKeyExchangeAlgoName(m_kexAlgoName))));
        m_sendFacility.sendKeyDhInitPacket(m_dhKey->get_y());
    }
ck's avatar
ck committed
143

144
    m_serverKexInitPayload = serverKexInit.payLoad();
ck's avatar
ck committed
145 146 147 148 149 150
    return kexInitParams.firstKexPacketFollows;
}

void SshKeyExchange::sendNewKeysPacket(const SshIncomingPacket &dhReply,
    const QByteArray &clientId)
{
151

ck's avatar
ck committed
152
    const SshKeyExchangeReply &reply
153
        = dhReply.extractKeyExchangeReply(m_kexAlgoName, m_serverHostKeyAlgo);
154
    if (m_dhKey && (reply.f <= 0 || reply.f >= m_dhKey->group_p())) {
ck's avatar
ck committed
155 156 157 158 159 160 161 162 163
        throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
            "Server sent invalid f.");
    }

    QByteArray concatenatedData = AbstractSshPacket::encodeString(clientId);
    concatenatedData += AbstractSshPacket::encodeString(m_serverId);
    concatenatedData += AbstractSshPacket::encodeString(m_clientKexInitPayload);
    concatenatedData += AbstractSshPacket::encodeString(m_serverKexInitPayload);
    concatenatedData += reply.k_s;
164 165 166 167 168 169 170
    SecureVector<byte> encodedK;
    if (m_dhKey) {
        concatenatedData += AbstractSshPacket::encodeMpInt(m_dhKey->get_y());
        concatenatedData += AbstractSshPacket::encodeMpInt(reply.f);
        DH_KA_Operation dhOp(*m_dhKey);
        SecureVector<byte> encodedF = BigInt::encode(reply.f);
        encodedK = dhOp.agree(encodedF, encodedF.size());
171
        m_dhKey.reset(nullptr);
172 173 174 175 176 177 178
    } else {
        Q_ASSERT(m_ecdhKey);
        concatenatedData // Q_C.
                += AbstractSshPacket::encodeString(convertByteArray(m_ecdhKey->public_value()));
        concatenatedData += AbstractSshPacket::encodeString(reply.q_s);
        ECDH_KA_Operation ecdhOp(*m_ecdhKey);
        encodedK = ecdhOp.agree(convertByteArray(reply.q_s), reply.q_s.count());
179
        m_ecdhKey.reset(nullptr);
180
    }
181

182 183
    const BigInt k = BigInt::decode(encodedK);
    m_k = AbstractSshPacket::encodeMpInt(k); // Roundtrip, as Botan encodes BigInts somewhat differently.
ck's avatar
ck committed
184 185
    concatenatedData += m_k;

186 187 188
    m_hash.reset(get_hash(botanHMacAlgoName(hashAlgoForKexAlgo())));
    const SecureVector<byte> &hashResult = m_hash->process(convertByteArray(concatenatedData),
                                                           concatenatedData.size());
ck's avatar
ck committed
189 190
    m_h = convertByteArray(hashResult);

191 192 193 194 195 196 197 198 199 200 201 202 203
#ifdef CREATOR_SSH_DEBUG
    printData("Client Id", AbstractSshPacket::encodeString(clientId));
    printData("Server Id", AbstractSshPacket::encodeString(m_serverId));
    printData("Client Payload", AbstractSshPacket::encodeString(m_clientKexInitPayload));
    printData("Server payload", AbstractSshPacket::encodeString(m_serverKexInitPayload));
    printData("K_S", reply.k_s);
    printData("y", AbstractSshPacket::encodeMpInt(m_dhKey->get_y()));
    printData("f", AbstractSshPacket::encodeMpInt(reply.f));
    printData("K", m_k);
    printData("Concatenated data", concatenatedData);
    printData("H", m_h);
#endif // CREATOR_SSH_DEBUG

204
    QScopedPointer<Public_Key> sigKey;
ck's avatar
ck committed
205
    if (m_serverHostKeyAlgo == SshCapabilities::PubKeyDss) {
206 207
        const DL_Group group(reply.hostKeyParameters.at(0), reply.hostKeyParameters.at(1),
            reply.hostKeyParameters.at(2));
208
        DSA_PublicKey * const dsaKey
209
            = new DSA_PublicKey(group, reply.hostKeyParameters.at(3));
210
        sigKey.reset(dsaKey);
ck's avatar
ck committed
211
    } else if (m_serverHostKeyAlgo == SshCapabilities::PubKeyRsa) {
212
        RSA_PublicKey * const rsaKey
213
            = new RSA_PublicKey(reply.hostKeyParameters.at(1), reply.hostKeyParameters.at(0));
214
        sigKey.reset(rsaKey);
215
    } else {
216
        QSSH_ASSERT_AND_RETURN(m_serverHostKeyAlgo == SshCapabilities::PubKeyEcdsa256);
217
        const EC_Group domain("secp256r1");
218
        const PointGFp point = OS2ECP(convertByteArray(reply.q), reply.q.count(),
219 220
                                      domain.get_curve());
        ECDSA_PublicKey * const ecdsaKey = new ECDSA_PublicKey(domain, point);
221
        sigKey.reset(ecdsaKey);
ck's avatar
ck committed
222
    }
223

ck's avatar
ck committed
224
    const byte * const botanH = convertByteArray(m_h);
225
    const Botan::byte * const botanSig = convertByteArray(reply.signatureBlob);
226
    PK_Verifier verifier(*sigKey, botanEmsaAlgoName(m_serverHostKeyAlgo));
227
    if (!verifier.verify_message(botanH, m_h.size(), botanSig, reply.signatureBlob.size())) {
ck's avatar
ck committed
228
        throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
229
            "Invalid signature in key exchange reply packet.");
ck's avatar
ck committed
230 231
    }

232 233
    checkHostKey(reply.k_s);

ck's avatar
ck committed
234
    m_sendFacility.sendNewKeysPacket();
235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275
}

QByteArray SshKeyExchange::hashAlgoForKexAlgo() const
{
    if (m_kexAlgoName == SshCapabilities::EcdhNistp256)
        return SshCapabilities::HMacSha256;
    if (m_kexAlgoName == SshCapabilities::EcdhNistp384)
        return SshCapabilities::HMacSha384;
    if (m_kexAlgoName == SshCapabilities::EcdhNistp521)
        return SshCapabilities::HMacSha512;
    return SshCapabilities::HMacSha1;
}

void SshKeyExchange::determineHashingAlgorithm(const SshKeyExchangeInit &kexInit,
                                               bool serverToClient)
{
    QByteArray * const algo = serverToClient ? &m_s2cHMacAlgo : &m_c2sHMacAlgo;
    const QList<QByteArray> &serverCapabilities = serverToClient
            ? kexInit.macAlgorithmsServerToClient.names
            : kexInit.macAlgorithmsClientToServer.names;
    const QList<QByteArray> commonAlgos = SshCapabilities::commonCapabilities(
                SshCapabilities::MacAlgorithms, serverCapabilities);
    const QByteArray hashAlgo = hashAlgoForKexAlgo();
    foreach (const QByteArray &potentialAlgo, commonAlgos) {
        if (potentialAlgo == hashAlgo
                || !m_kexAlgoName.startsWith(SshCapabilities::EcdhKexNamePrefix)) {
            *algo = potentialAlgo;
            break;
        }
    }

    if (algo->isEmpty()) {
        throw SshServerException(SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
            "Invalid combination of key exchange and hashing algorithms.",
            QCoreApplication::translate("SshConnection",
                "Server requested invalid combination of key exchange and hashing algorithms. "
                "Key exchange algorithm list was: %1.\nHashing algorithm list was %2.")
                .arg(QString::fromLocal8Bit(kexInit.keyAlgorithms.names.join(", ")))
                .arg(QString::fromLocal8Bit(serverCapabilities.join(", "))));

    }
ck's avatar
ck committed
276 277
}

278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313
void SshKeyExchange::checkHostKey(const QByteArray &hostKey)
{
    if (m_connParams.hostKeyCheckingMode == SshHostKeyCheckingNone) {
        if (m_connParams.hostKeyDatabase)
            m_connParams.hostKeyDatabase->insertHostKey(m_connParams.host, hostKey);
        return;
    }

    if (!m_connParams.hostKeyDatabase) {
        throw SshClientException(SshInternalError,
                                 SSH_TR("Host key database must exist "
                                        "if host key checking is enabled."));
    }

    switch (m_connParams.hostKeyDatabase->matchHostKey(m_connParams.host, hostKey)) {
    case SshHostKeyDatabase::KeyLookupMatch:
        return; // Nothing to do.
    case SshHostKeyDatabase::KeyLookupMismatch:
        if (m_connParams.hostKeyCheckingMode != SshHostKeyCheckingAllowMismatch)
            throwHostKeyException();
        break;
    case SshHostKeyDatabase::KeyLookupNoMatch:
        if (m_connParams.hostKeyCheckingMode == SshHostKeyCheckingStrict)
            throwHostKeyException();
        break;
    }
    m_connParams.hostKeyDatabase->insertHostKey(m_connParams.host, hostKey);
}

void SshKeyExchange::throwHostKeyException()
{
    throw SshServerException(SSH_DISCONNECT_HOST_KEY_NOT_VERIFIABLE, "Host key changed",
                             SSH_TR("Host key of machine \"%1\" has changed.")
                             .arg(m_connParams.host));
}

ck's avatar
ck committed
314
} // namespace Internal
315
} // namespace QSsh