From f8a8c6cac3f7e1955025e72dde451e00c10a1077 Mon Sep 17 00:00:00 2001 From: Christian Kandeler <christian.kandeler@theqtcompany.com> Date: Tue, 23 Jun 2015 12:53:30 +0200 Subject: [PATCH] SSH: Do not tie key exchange methods to specific host key algorithms. Contrary to what I believed, all key exchange methods are compatible with all types of host key (at least for everything we support). Task-number: QTCREATORBUG-14621 Change-Id: Iacae3d36170b9841cceb3705780a64aeb15e10b9 Reviewed-by: Benjamin Zeller <benjamin.zeller@canonical.com> Reviewed-by: Christian Kandeler <christian.kandeler@theqtcompany.com> --- src/libs/ssh/sshbotanconversions_p.h | 11 ++++-- src/libs/ssh/sshincomingpacket.cpp | 54 +++++++++++++++++----------- src/libs/ssh/sshincomingpacket_p.h | 3 +- src/libs/ssh/sshkeyexchange.cpp | 44 +++++++---------------- 4 files changed, 57 insertions(+), 55 deletions(-) diff --git a/src/libs/ssh/sshbotanconversions_p.h b/src/libs/ssh/sshbotanconversions_p.h index 31dc4899a72..75182881b91 100644 --- a/src/libs/ssh/sshbotanconversions_p.h +++ b/src/libs/ssh/sshbotanconversions_p.h @@ -118,11 +118,16 @@ inline const char *botanHMacAlgoName(const QByteArray &rfcAlgoName) inline quint32 botanHMacKeyLen(const QByteArray &rfcAlgoName) { - Q_ASSERT(rfcAlgoName == SshCapabilities::HMacSha1 - || rfcAlgoName == SshCapabilities::HMacSha256); if (rfcAlgoName == SshCapabilities::HMacSha1) return 20; - return 32; + if (rfcAlgoName == SshCapabilities::HMacSha256) + return 32; + if (rfcAlgoName == SshCapabilities::HMacSha384) + return 48; + if (rfcAlgoName == SshCapabilities::HMacSha512) + return 64; + throw SshClientException(SshInternalError, SSH_TR("Unexpected hashing algorithm \"%1\"") + .arg(QString::fromLatin1(rfcAlgoName))); } } // namespace Internal diff --git a/src/libs/ssh/sshincomingpacket.cpp b/src/libs/ssh/sshincomingpacket.cpp index f2101e54dea..1742c335a4a 100644 --- a/src/libs/ssh/sshincomingpacket.cpp +++ b/src/libs/ssh/sshincomingpacket.cpp @@ -30,6 +30,7 @@ #include "sshincomingpacket_p.h" +#include "ssh_global.h" #include "sshbotanconversions_p.h" #include "sshcapabilities_p.h" @@ -169,7 +170,32 @@ SshKeyExchangeInit SshIncomingPacket::extractKeyExchangeInitData() const return exchangeData; } -SshKeyExchangeReply SshIncomingPacket::extractKeyExchangeReply(const QByteArray &pubKeyAlgo) const +static void getHostKeySpecificReplyData(SshKeyExchangeReply &replyData, + const QByteArray &hostKeyAlgo, const QByteArray &input) +{ + quint32 offset = 0; + if (hostKeyAlgo == SshCapabilities::PubKeyDss || hostKeyAlgo == SshCapabilities::PubKeyRsa) { + // DSS: p and q, RSA: e and n + replyData.parameters << SshPacketParser::asBigInt(input, &offset); + replyData.parameters << SshPacketParser::asBigInt(input, &offset); + + // g and y + if (hostKeyAlgo == SshCapabilities::PubKeyDss) { + replyData.parameters << SshPacketParser::asBigInt(input, &offset); + replyData.parameters << SshPacketParser::asBigInt(input, &offset); + } + } else { + QSSH_ASSERT_AND_RETURN(hostKeyAlgo == SshCapabilities::PubKeyEcdsa); + if (SshPacketParser::asString(input, &offset) + != hostKeyAlgo.mid(11)) { // Without "ecdsa-sha2-" prefix. + throw SshPacketParseException(); + } + replyData.q = SshPacketParser::asString(input, &offset); + } +} + +SshKeyExchangeReply SshIncomingPacket::extractKeyExchangeReply(const QByteArray &kexAlgo, + const QByteArray &hostKeyAlgo) const { Q_ASSERT(isComplete()); Q_ASSERT(type() == SSH_MSG_KEXDH_REPLY); @@ -179,35 +205,23 @@ SshKeyExchangeReply SshIncomingPacket::extractKeyExchangeReply(const QByteArray quint32 topLevelOffset = TypeOffset + 1; replyData.k_s = SshPacketParser::asString(m_data, &topLevelOffset); quint32 k_sOffset = 0; - if (SshPacketParser::asString(replyData.k_s, &k_sOffset) != pubKeyAlgo) + if (SshPacketParser::asString(replyData.k_s, &k_sOffset) != hostKeyAlgo) throw SshPacketParseException(); + getHostKeySpecificReplyData(replyData, hostKeyAlgo, replyData.k_s.mid(k_sOffset)); - if (pubKeyAlgo == SshCapabilities::PubKeyDss || pubKeyAlgo == SshCapabilities::PubKeyRsa) { - - // DSS: p and q, RSA: e and n - replyData.parameters << SshPacketParser::asBigInt(replyData.k_s, &k_sOffset); - replyData.parameters << SshPacketParser::asBigInt(replyData.k_s, &k_sOffset); - - // g and y - if (pubKeyAlgo == SshCapabilities::PubKeyDss) { - replyData.parameters << SshPacketParser::asBigInt(replyData.k_s, &k_sOffset); - replyData.parameters << SshPacketParser::asBigInt(replyData.k_s, &k_sOffset); - } - + if (kexAlgo == SshCapabilities::DiffieHellmanGroup1Sha1) { replyData.f = SshPacketParser::asBigInt(m_data, &topLevelOffset); } else { - Q_ASSERT(pubKeyAlgo == SshCapabilities::PubKeyEcdsa); - if (SshPacketParser::asString(replyData.k_s, &k_sOffset) != pubKeyAlgo.mid(11)) // Without "ecdsa-sha2-" prefix. - throw SshPacketParseException(); - replyData.q = SshPacketParser::asString(replyData.k_s, &k_sOffset); + QSSH_ASSERT_AND_RETURN_VALUE(kexAlgo.startsWith(SshCapabilities::EcdhKexNamePrefix), + SshKeyExchangeReply()); replyData.q_s = SshPacketParser::asString(m_data, &topLevelOffset); } const QByteArray fullSignature = SshPacketParser::asString(m_data, &topLevelOffset); quint32 sigOffset = 0; - if (SshPacketParser::asString(fullSignature, &sigOffset) != pubKeyAlgo) + if (SshPacketParser::asString(fullSignature, &sigOffset) != hostKeyAlgo) throw SshPacketParseException(); replyData.signatureBlob = SshPacketParser::asString(fullSignature, &sigOffset); - if (pubKeyAlgo == SshCapabilities::PubKeyEcdsa) { + if (hostKeyAlgo == SshCapabilities::PubKeyEcdsa) { // Botan's PK_Verifier wants the signature in this format. quint32 blobOffset = 0; const Botan::BigInt r = SshPacketParser::asBigInt(replyData.signatureBlob, &blobOffset); diff --git a/src/libs/ssh/sshincomingpacket_p.h b/src/libs/ssh/sshincomingpacket_p.h index 9ea9d80278a..d2d17eed6ed 100644 --- a/src/libs/ssh/sshincomingpacket_p.h +++ b/src/libs/ssh/sshincomingpacket_p.h @@ -164,7 +164,8 @@ public: void reset(); SshKeyExchangeInit extractKeyExchangeInitData() const; - SshKeyExchangeReply extractKeyExchangeReply(const QByteArray &pubKeyAlgo) const; + SshKeyExchangeReply extractKeyExchangeReply(const QByteArray &kexAlgo, + const QByteArray &hostKeyAlgo) const; SshDisconnect extractDisconnect() const; SshUserAuthBanner extractUserAuthBanner() const; SshUserAuthInfoRequestPacket extractUserAuthInfoRequest() const; diff --git a/src/libs/ssh/sshkeyexchange.cpp b/src/libs/ssh/sshkeyexchange.cpp index c553ecc8b22..62071e4fc70 100644 --- a/src/libs/ssh/sshkeyexchange.cpp +++ b/src/libs/ssh/sshkeyexchange.cpp @@ -30,6 +30,7 @@ #include "sshkeyexchange_p.h" +#include "ssh_global.h" #include "sshbotanconversions_p.h" #include "sshcapabilities_p.h" #include "sshsendfacility_p.h" @@ -115,28 +116,8 @@ bool SshKeyExchange::sendDhInitPacket(const SshIncomingPacket &serverKexInit) m_kexAlgoName = SshCapabilities::findBestMatch(SshCapabilities::KeyExchangeMethods, kexInitParams.keyAlgorithms.names); - const QList<QByteArray> &commonHostKeyAlgos - = SshCapabilities::commonCapabilities(SshCapabilities::PublicKeyAlgorithms, - kexInitParams.serverHostKeyAlgorithms.names); - const bool ecdh = m_kexAlgoName.startsWith(SshCapabilities::EcdhKexNamePrefix); - foreach (const QByteArray &possibleHostKeyAlgo, commonHostKeyAlgos) { - if (ecdh && possibleHostKeyAlgo == SshCapabilities::PubKeyEcdsa) { - m_serverHostKeyAlgo = possibleHostKeyAlgo; - break; - } - if (!ecdh && (possibleHostKeyAlgo == SshCapabilities::PubKeyDss - || possibleHostKeyAlgo == SshCapabilities::PubKeyRsa)) { - m_serverHostKeyAlgo = possibleHostKeyAlgo; - break; - } - } - if (m_serverHostKeyAlgo.isEmpty()) { - throw SshServerException(SSH_DISCONNECT_KEY_EXCHANGE_FAILED, - "Invalid combination of key exchange and host key algorithms.", - QCoreApplication::translate("SshConnection", - "No matching host key algorithm available for key exchange algorithm \"%1\".") - .arg(QString::fromLatin1(m_kexAlgoName))); - } + m_serverHostKeyAlgo = SshCapabilities::findBestMatch(SshCapabilities::PublicKeyAlgorithms, + kexInitParams.serverHostKeyAlgorithms.names); determineHashingAlgorithm(kexInitParams, true); determineHashingAlgorithm(kexInitParams, false); @@ -152,7 +133,7 @@ bool SshKeyExchange::sendDhInitPacket(const SshIncomingPacket &serverKexInit) kexInitParams.compressionAlgorithmsServerToClient.names); AutoSeeded_RNG rng; - if (ecdh) { + if (m_kexAlgoName.startsWith(SshCapabilities::EcdhKexNamePrefix)) { m_ecdhKey.reset(new ECDH_PrivateKey(rng, EC_Group(botanKeyExchangeAlgoName(m_kexAlgoName)))); m_sendFacility.sendKeyEcdhInitPacket(convertByteArray(m_ecdhKey->public_value())); } else { @@ -169,7 +150,7 @@ void SshKeyExchange::sendNewKeysPacket(const SshIncomingPacket &dhReply, { const SshKeyExchangeReply &reply - = dhReply.extractKeyExchangeReply(m_serverHostKeyAlgo); + = dhReply.extractKeyExchangeReply(m_kexAlgoName, m_serverHostKeyAlgo); if (m_dhKey && (reply.f <= 0 || reply.f >= m_dhKey->group_p())) { throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_KEY_EXCHANGE_FAILED, "Server sent invalid f."); @@ -187,6 +168,7 @@ void SshKeyExchange::sendNewKeysPacket(const SshIncomingPacket &dhReply, DH_KA_Operation dhOp(*m_dhKey); SecureVector<byte> encodedF = BigInt::encode(reply.f); encodedK = dhOp.agree(encodedF, encodedF.size()); + m_dhKey.reset(nullptr); } else { Q_ASSERT(m_ecdhKey); concatenatedData // Q_C. @@ -194,7 +176,9 @@ void SshKeyExchange::sendNewKeysPacket(const SshIncomingPacket &dhReply, concatenatedData += AbstractSshPacket::encodeString(reply.q_s); ECDH_KA_Operation ecdhOp(*m_ecdhKey); encodedK = ecdhOp.agree(convertByteArray(reply.q_s), reply.q_s.count()); + m_ecdhKey.reset(nullptr); } + const BigInt k = BigInt::decode(encodedK); m_k = AbstractSshPacket::encodeMpInt(k); // Roundtrip, as Botan encodes BigInts somewhat differently. concatenatedData += m_k; @@ -228,13 +212,13 @@ void SshKeyExchange::sendNewKeysPacket(const SshIncomingPacket &dhReply, RSA_PublicKey * const rsaKey = new RSA_PublicKey(reply.parameters.at(1), reply.parameters.at(0)); sigKey.reset(rsaKey); - } else if (m_serverHostKeyAlgo == SshCapabilities::PubKeyEcdsa) { + } else { + QSSH_ASSERT_AND_RETURN(m_serverHostKeyAlgo == SshCapabilities::PubKeyEcdsa); + const EC_Group domain("secp256r1"); const PointGFp point = OS2ECP(convertByteArray(reply.q), reply.q.count(), - m_ecdhKey->domain().get_curve()); - ECDSA_PublicKey * const ecdsaKey = new ECDSA_PublicKey(m_ecdhKey->domain(), point); + domain.get_curve()); + ECDSA_PublicKey * const ecdsaKey = new ECDSA_PublicKey(domain, point); sigKey.reset(ecdsaKey); - } else { - Q_ASSERT(!"Impossible: Neither DSS nor RSA nor ECDSA!"); } const byte * const botanH = convertByteArray(m_h); @@ -248,8 +232,6 @@ void SshKeyExchange::sendNewKeysPacket(const SshIncomingPacket &dhReply, checkHostKey(reply.k_s); m_sendFacility.sendNewKeysPacket(); - m_dhKey.reset(nullptr); - m_ecdhKey.reset(nullptr); } QByteArray SshKeyExchange::hashAlgoForKexAlgo() const -- GitLab