diff options
author | Christian Kandeler <christian.kandeler@theqtcompany.com> | 2015-05-26 10:25:56 +0200 |
---|---|---|
committer | Christian Kandeler <christian.kandeler@theqtcompany.com> | 2015-05-28 09:07:24 +0000 |
commit | 3b8ea3fc1ea5a3be3e537fc6a6390886eaaff62e (patch) | |
tree | 9b79452d230296d9f41fd277c1f47b2a9d4d0111 | |
parent | 244cdb7804e7e45f235836d4656644d5bf9135ee (diff) |
SSH: Support ECDH key exchange.
As per RFC 5656.
Task-number: QTCREATORBUG-14025
Change-Id: I623c9f0808967f140cdfb40e11234c2e523484e6
Reviewed-by: Joerg Bornemann <joerg.bornemann@theqtcompany.com>
Reviewed-by: Christian Kandeler <christian.kandeler@theqtcompany.com>
-rw-r--r-- | src/libs/ssh/sshbotanconversions_p.h | 43 | ||||
-rw-r--r-- | src/libs/ssh/sshcapabilities.cpp | 27 | ||||
-rw-r--r-- | src/libs/ssh/sshcapabilities_p.h | 7 | ||||
-rw-r--r-- | src/libs/ssh/sshincomingpacket.cpp | 59 | ||||
-rw-r--r-- | src/libs/ssh/sshincomingpacket_p.h | 4 | ||||
-rw-r--r-- | src/libs/ssh/sshkeyexchange.cpp | 138 | ||||
-rw-r--r-- | src/libs/ssh/sshkeyexchange_p.h | 6 | ||||
-rw-r--r-- | src/libs/ssh/sshoutgoingpacket.cpp | 5 | ||||
-rw-r--r-- | src/libs/ssh/sshoutgoingpacket_p.h | 1 | ||||
-rw-r--r-- | src/libs/ssh/sshpacket_p.h | 2 | ||||
-rw-r--r-- | src/libs/ssh/sshsendfacility.cpp | 6 | ||||
-rw-r--r-- | src/libs/ssh/sshsendfacility_p.h | 1 |
12 files changed, 225 insertions, 74 deletions
diff --git a/src/libs/ssh/sshbotanconversions_p.h b/src/libs/ssh/sshbotanconversions_p.h index d7f63cebcd..31dc4899a7 100644 --- a/src/libs/ssh/sshbotanconversions_p.h +++ b/src/libs/ssh/sshbotanconversions_p.h @@ -56,10 +56,18 @@ inline QByteArray convertByteArray(const Botan::SecureVector<Botan::byte> &v) inline const char *botanKeyExchangeAlgoName(const QByteArray &rfcAlgoName) { - Q_ASSERT(rfcAlgoName == SshCapabilities::DiffieHellmanGroup1Sha1 - || rfcAlgoName == SshCapabilities::DiffieHellmanGroup14Sha1); - return rfcAlgoName == SshCapabilities::DiffieHellmanGroup1Sha1 - ? "modp/ietf/1024" : "modp/ietf/2048"; + if (rfcAlgoName == SshCapabilities::DiffieHellmanGroup1Sha1) + return "modp/ietf/1024"; + if (rfcAlgoName == SshCapabilities::DiffieHellmanGroup14Sha1) + return "modp/ietf/2048"; + if (rfcAlgoName == SshCapabilities::EcdhNistp256) + return "secp256r1"; + if (rfcAlgoName == SshCapabilities::EcdhNistp384) + return "secp384r1"; + if (rfcAlgoName == SshCapabilities::EcdhNistp521) + return "secp521r1"; + throw SshClientException(SshInternalError, SSH_TR("Unexpected key exchange algorithm \"%1\"") + .arg(QString::fromLatin1(rfcAlgoName))); } inline const char *botanCryptAlgoName(const QByteArray &rfcAlgoName) @@ -84,21 +92,28 @@ inline const char *botanCryptAlgoName(const QByteArray &rfcAlgoName) inline const char *botanEmsaAlgoName(const QByteArray &rfcAlgoName) { - Q_ASSERT(rfcAlgoName == SshCapabilities::PubKeyDss - || rfcAlgoName == SshCapabilities::PubKeyRsa); - return rfcAlgoName == SshCapabilities::PubKeyDss - ? "EMSA1(SHA-1)" : "EMSA3(SHA-1)"; + if (rfcAlgoName == SshCapabilities::PubKeyDss) + return "EMSA1(SHA-1)"; + if (rfcAlgoName == SshCapabilities::PubKeyRsa) + return "EMSA3(SHA-1)"; + if (rfcAlgoName == SshCapabilities::PubKeyEcdsa) + return "EMSA1_BSI(SHA-256)"; + throw SshClientException(SshInternalError, SSH_TR("Unexpected host key algorithm \"%1\"") + .arg(QString::fromLatin1(rfcAlgoName))); } -inline const char *botanSha1Name() { return "SHA-1"; } - inline const char *botanHMacAlgoName(const QByteArray &rfcAlgoName) { - Q_ASSERT(rfcAlgoName == SshCapabilities::HMacSha1 - || rfcAlgoName == SshCapabilities::HMacSha256); if (rfcAlgoName == SshCapabilities::HMacSha1) - return botanSha1Name(); - return "SHA-256"; + return "SHA-1"; + if (rfcAlgoName == SshCapabilities::HMacSha256) + return "SHA-256"; + if (rfcAlgoName == SshCapabilities::HMacSha384) + return "SHA-384"; + if (rfcAlgoName == SshCapabilities::HMacSha512) + return "SHA-512"; + throw SshClientException(SshInternalError, SSH_TR("Unexpected hashing algorithm \"%1\"") + .arg(QString::fromLatin1(rfcAlgoName))); } inline quint32 botanHMacKeyLen(const QByteArray &rfcAlgoName) diff --git a/src/libs/ssh/sshcapabilities.cpp b/src/libs/ssh/sshcapabilities.cpp index 6bad151fcd..2677768c3a 100644 --- a/src/libs/ssh/sshcapabilities.cpp +++ b/src/libs/ssh/sshcapabilities.cpp @@ -52,15 +52,24 @@ namespace { const QByteArray SshCapabilities::DiffieHellmanGroup1Sha1("diffie-hellman-group1-sha1"); const QByteArray SshCapabilities::DiffieHellmanGroup14Sha1("diffie-hellman-group14-sha1"); -const QList<QByteArray> SshCapabilities::KeyExchangeMethods - = QList<QByteArray>() << SshCapabilities::DiffieHellmanGroup1Sha1 - << SshCapabilities::DiffieHellmanGroup14Sha1; +const QByteArray SshCapabilities::EcdhKexNamePrefix("ecdh-sha2-nistp"); +const QByteArray SshCapabilities::EcdhNistp256 = EcdhKexNamePrefix + "256"; +const QByteArray SshCapabilities::EcdhNistp384 = EcdhKexNamePrefix + "384"; +const QByteArray SshCapabilities::EcdhNistp521 = EcdhKexNamePrefix + "521"; +const QList<QByteArray> SshCapabilities::KeyExchangeMethods = QList<QByteArray>() + << SshCapabilities::EcdhNistp256 + << SshCapabilities::EcdhNistp384 + << SshCapabilities::EcdhNistp521 + << SshCapabilities::DiffieHellmanGroup1Sha1 + << SshCapabilities::DiffieHellmanGroup14Sha1; const QByteArray SshCapabilities::PubKeyDss("ssh-dss"); const QByteArray SshCapabilities::PubKeyRsa("ssh-rsa"); -const QList<QByteArray> SshCapabilities::PublicKeyAlgorithms - = QList<QByteArray>() << SshCapabilities::PubKeyRsa - << SshCapabilities::PubKeyDss; +const QByteArray SshCapabilities::PubKeyEcdsa("ecdsa-sha2-nistp256"); +const QList<QByteArray> SshCapabilities::PublicKeyAlgorithms = QList<QByteArray>() + << SshCapabilities::PubKeyEcdsa + << SshCapabilities::PubKeyRsa + << SshCapabilities::PubKeyDss; const QByteArray SshCapabilities::CryptAlgo3DesCbc("3des-cbc"); const QByteArray SshCapabilities::CryptAlgo3DesCtr("3des-ctr"); @@ -79,9 +88,13 @@ const QList<QByteArray> SshCapabilities::EncryptionAlgorithms const QByteArray SshCapabilities::HMacSha1("hmac-sha1"); const QByteArray SshCapabilities::HMacSha196("hmac-sha1-96"); const QByteArray SshCapabilities::HMacSha256("hmac-sha2-256"); +const QByteArray SshCapabilities::HMacSha384("hmac-sha2-384"); +const QByteArray SshCapabilities::HMacSha512("hmac-sha2-512"); const QList<QByteArray> SshCapabilities::MacAlgorithms = QList<QByteArray>() /* << SshCapabilities::HMacSha196 */ - << SshCapabilities::HMacSha256 // Recommended as per RFC 6668 + << SshCapabilities::HMacSha256 + << SshCapabilities::HMacSha384 + << SshCapabilities::HMacSha512 << SshCapabilities::HMacSha1; const QList<QByteArray> SshCapabilities::CompressionAlgorithms diff --git a/src/libs/ssh/sshcapabilities_p.h b/src/libs/ssh/sshcapabilities_p.h index 1a82ae1ef2..a893ccecbe 100644 --- a/src/libs/ssh/sshcapabilities_p.h +++ b/src/libs/ssh/sshcapabilities_p.h @@ -42,10 +42,15 @@ class SshCapabilities public: static const QByteArray DiffieHellmanGroup1Sha1; static const QByteArray DiffieHellmanGroup14Sha1; + static const QByteArray EcdhKexNamePrefix; + static const QByteArray EcdhNistp256; + static const QByteArray EcdhNistp384; + static const QByteArray EcdhNistp521; // sic static const QList<QByteArray> KeyExchangeMethods; static const QByteArray PubKeyDss; static const QByteArray PubKeyRsa; + static const QByteArray PubKeyEcdsa; static const QList<QByteArray> PublicKeyAlgorithms; static const QByteArray CryptAlgo3DesCbc; @@ -59,6 +64,8 @@ public: static const QByteArray HMacSha1; static const QByteArray HMacSha196; static const QByteArray HMacSha256; + static const QByteArray HMacSha384; + static const QByteArray HMacSha512; static const QList<QByteArray> MacAlgorithms; static const QList<QByteArray> CompressionAlgorithms; diff --git a/src/libs/ssh/sshincomingpacket.cpp b/src/libs/ssh/sshincomingpacket.cpp index bf2d220bb5..f2101e54de 100644 --- a/src/libs/ssh/sshincomingpacket.cpp +++ b/src/libs/ssh/sshincomingpacket.cpp @@ -30,6 +30,7 @@ #include "sshincomingpacket_p.h" +#include "sshbotanconversions_p.h" #include "sshcapabilities_p.h" namespace QSsh { @@ -175,35 +176,51 @@ SshKeyExchangeReply SshIncomingPacket::extractKeyExchangeReply(const QByteArray try { SshKeyExchangeReply replyData; - quint32 offset = TypeOffset + 1; - const quint32 k_sLength - = SshPacketParser::asUint32(m_data, &offset); - if (offset + k_sLength > currentDataSize()) - throw SshPacketParseException(); - replyData.k_s = m_data.mid(offset - 4, k_sLength + 4); - if (SshPacketParser::asString(m_data, &offset) != pubKeyAlgo) + 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) throw SshPacketParseException(); - // DSS: p and q, RSA: e and n - replyData.parameters << SshPacketParser::asBigInt(m_data, &offset); - replyData.parameters << SshPacketParser::asBigInt(m_data, &offset); - - // g and y - if (pubKeyAlgo == SshCapabilities::PubKeyDss) { - replyData.parameters << SshPacketParser::asBigInt(m_data, &offset); - replyData.parameters << SshPacketParser::asBigInt(m_data, &offset); + 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); + } + + 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); + replyData.q_s = SshPacketParser::asString(m_data, &topLevelOffset); } - - replyData.f = SshPacketParser::asBigInt(m_data, &offset); - offset += 4; - if (SshPacketParser::asString(m_data, &offset) != pubKeyAlgo) + const QByteArray fullSignature = SshPacketParser::asString(m_data, &topLevelOffset); + quint32 sigOffset = 0; + if (SshPacketParser::asString(fullSignature, &sigOffset) != pubKeyAlgo) throw SshPacketParseException(); - replyData.signatureBlob = SshPacketParser::asString(m_data, &offset); + replyData.signatureBlob = SshPacketParser::asString(fullSignature, &sigOffset); + if (pubKeyAlgo == SshCapabilities::PubKeyEcdsa) { + // Botan's PK_Verifier wants the signature in this format. + quint32 blobOffset = 0; + const Botan::BigInt r = SshPacketParser::asBigInt(replyData.signatureBlob, &blobOffset); + const Botan::BigInt s = SshPacketParser::asBigInt(replyData.signatureBlob, &blobOffset); + replyData.signatureBlob = convertByteArray(Botan::BigInt::encode(r)); + replyData.signatureBlob += convertByteArray(Botan::BigInt::encode(s)); + } + replyData.k_s.prepend(m_data.mid(TypeOffset + 1, 4)); return replyData; } catch (const SshPacketParseException &) { throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_KEY_EXCHANGE_FAILED, "Key exchange failed: " - "Server sent invalid SSH_MSG_KEXDH_REPLY packet."); + "Server sent invalid key exchange reply packet."); } } diff --git a/src/libs/ssh/sshincomingpacket_p.h b/src/libs/ssh/sshincomingpacket_p.h index 81ea6a1124..9ea9d80278 100644 --- a/src/libs/ssh/sshincomingpacket_p.h +++ b/src/libs/ssh/sshincomingpacket_p.h @@ -63,7 +63,9 @@ struct SshKeyExchangeReply { QByteArray k_s; QList<Botan::BigInt> parameters; // DSS: p, q, g, y. RSA: e, n. - Botan::BigInt f; + Botan::BigInt f; // For DH only. + QByteArray q_s; // For ECDH only. + QByteArray q; // For ECDH only. QByteArray signatureBlob; }; diff --git a/src/libs/ssh/sshkeyexchange.cpp b/src/libs/ssh/sshkeyexchange.cpp index 1f90417c1f..528a117908 100644 --- a/src/libs/ssh/sshkeyexchange.cpp +++ b/src/libs/ssh/sshkeyexchange.cpp @@ -113,44 +113,64 @@ bool SshKeyExchange::sendDhInitPacket(const SshIncomingPacket &serverKexInit) qDebug("First packet follows: %d", kexInitParams.firstKexPacketFollows); #endif - const QByteArray &keyAlgo - = SshCapabilities::findBestMatch(SshCapabilities::KeyExchangeMethods, - kexInitParams.keyAlgorithms.names); - m_serverHostKeyAlgo - = SshCapabilities::findBestMatch(SshCapabilities::PublicKeyAlgorithms, - kexInitParams.serverHostKeyAlgorithms.names); + 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))); + } + determineHashingAlgorithm(kexInitParams, true); + determineHashingAlgorithm(kexInitParams, false); + m_encryptionAlgo = SshCapabilities::findBestMatch(SshCapabilities::EncryptionAlgorithms, kexInitParams.encryptionAlgorithmsClientToServer.names); m_decryptionAlgo = SshCapabilities::findBestMatch(SshCapabilities::EncryptionAlgorithms, kexInitParams.encryptionAlgorithmsServerToClient.names); - m_c2sHMacAlgo - = SshCapabilities::findBestMatch(SshCapabilities::MacAlgorithms, - kexInitParams.macAlgorithmsClientToServer.names); - m_s2cHMacAlgo - = SshCapabilities::findBestMatch(SshCapabilities::MacAlgorithms, - kexInitParams.macAlgorithmsServerToClient.names); SshCapabilities::findBestMatch(SshCapabilities::CompressionAlgorithms, kexInitParams.compressionAlgorithmsClientToServer.names); SshCapabilities::findBestMatch(SshCapabilities::CompressionAlgorithms, kexInitParams.compressionAlgorithmsServerToClient.names); AutoSeeded_RNG rng; - m_dhKey.reset(new DH_PrivateKey(rng, - DL_Group(botanKeyExchangeAlgoName(keyAlgo)))); + if (ecdh) { + 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()); + } m_serverKexInitPayload = serverKexInit.payLoad(); - m_sendFacility.sendKeyDhInitPacket(m_dhKey->get_y()); return kexInitParams.firstKexPacketFollows; } void SshKeyExchange::sendNewKeysPacket(const SshIncomingPacket &dhReply, const QByteArray &clientId) { + const SshKeyExchangeReply &reply = dhReply.extractKeyExchangeReply(m_serverHostKeyAlgo); - if (reply.f <= 0 || reply.f >= m_dhKey->group_p()) { + 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."); } @@ -160,19 +180,28 @@ void SshKeyExchange::sendNewKeysPacket(const SshIncomingPacket &dhReply, concatenatedData += AbstractSshPacket::encodeString(m_clientKexInitPayload); concatenatedData += AbstractSshPacket::encodeString(m_serverKexInitPayload); concatenatedData += reply.k_s; - 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); - SecureVector<byte> encodedK = dhOp.agree(encodedF, encodedF.size()); + 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()); + } 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()); + } const BigInt k = BigInt::decode(encodedK); m_k = AbstractSshPacket::encodeMpInt(k); // Roundtrip, as Botan encodes BigInts somewhat differently. concatenatedData += m_k; - m_hash.reset(get_hash(botanSha1Name())); - const SecureVector<byte> &hashResult - = m_hash->process(convertByteArray(concatenatedData), - concatenatedData.size()); + m_hash.reset(get_hash(botanHMacAlgoName(hashAlgoForKexAlgo()))); + const SecureVector<byte> &hashResult = m_hash->process(convertByteArray(concatenatedData), + concatenatedData.size()); m_h = convertByteArray(hashResult); #ifdef CREATOR_SSH_DEBUG @@ -199,22 +228,69 @@ 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) { + 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); + sigKey.reset(ecdsaKey); } else { - Q_ASSERT(!"Impossible: Neither DSS nor RSA!"); + Q_ASSERT(!"Impossible: Neither DSS nor RSA nor ECDSA!"); } + const byte * const botanH = convertByteArray(m_h); - const Botan::byte * const botanSig - = convertByteArray(reply.signatureBlob); + const Botan::byte * const botanSig = convertByteArray(reply.signatureBlob); PK_Verifier verifier(*sigKey, botanEmsaAlgoName(m_serverHostKeyAlgo)); - if (!verifier.verify_message(botanH, m_h.size(), botanSig, - reply.signatureBlob.size())) { + if (!verifier.verify_message(botanH, m_h.size(), botanSig, reply.signatureBlob.size())) { throw SSH_SERVER_EXCEPTION(SSH_DISCONNECT_KEY_EXCHANGE_FAILED, - "Invalid signature in SSH_MSG_KEXDH_REPLY packet."); + "Invalid signature in key exchange reply packet."); } checkHostKey(reply.k_s); m_sendFacility.sendNewKeysPacket(); + m_dhKey.reset(nullptr); + m_ecdhKey.reset(nullptr); +} + +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(", ")))); + + } } void SshKeyExchange::checkHostKey(const QByteArray &hostKey) diff --git a/src/libs/ssh/sshkeyexchange_p.h b/src/libs/ssh/sshkeyexchange_p.h index ae149cc982..8ed9a62d87 100644 --- a/src/libs/ssh/sshkeyexchange_p.h +++ b/src/libs/ssh/sshkeyexchange_p.h @@ -38,12 +38,14 @@ namespace Botan { class DH_PrivateKey; +class ECDH_PrivateKey; class HashFunction; } namespace QSsh { namespace Internal { +class SshKeyExchangeInit; class SshSendFacility; class SshIncomingPacket; @@ -70,6 +72,8 @@ public: QByteArray hMacAlgoServerToClient() const { return m_s2cHMacAlgo; } private: + QByteArray hashAlgoForKexAlgo() const; + void determineHashingAlgorithm(const SshKeyExchangeInit &kexInit, bool serverToClient); void checkHostKey(const QByteArray &hostKey); Q_NORETURN void throwHostKeyException(); @@ -77,6 +81,8 @@ private: QByteArray m_clientKexInitPayload; QByteArray m_serverKexInitPayload; QScopedPointer<Botan::DH_PrivateKey> m_dhKey; + QScopedPointer<Botan::ECDH_PrivateKey> m_ecdhKey; + QByteArray m_kexAlgoName; QByteArray m_k; QByteArray m_h; QByteArray m_serverHostKeyAlgo; diff --git a/src/libs/ssh/sshoutgoingpacket.cpp b/src/libs/ssh/sshoutgoingpacket.cpp index 8b4ef6048c..7a4a66f853 100644 --- a/src/libs/ssh/sshoutgoingpacket.cpp +++ b/src/libs/ssh/sshoutgoingpacket.cpp @@ -89,6 +89,11 @@ void SshOutgoingPacket::generateKeyDhInitPacket(const Botan::BigInt &e) init(SSH_MSG_KEXDH_INIT).appendMpInt(e).finalize(); } +void SshOutgoingPacket::generateKeyEcdhInitPacket(const QByteArray &clientQ) +{ + init(SSH_MSG_KEX_ECDH_INIT).appendString(clientQ).finalize(); +} + void SshOutgoingPacket::generateNewKeysPacket() { init(SSH_MSG_NEWKEYS).finalize(); diff --git a/src/libs/ssh/sshoutgoingpacket_p.h b/src/libs/ssh/sshoutgoingpacket_p.h index 42c3dfdd9e..a0389de235 100644 --- a/src/libs/ssh/sshoutgoingpacket_p.h +++ b/src/libs/ssh/sshoutgoingpacket_p.h @@ -50,6 +50,7 @@ public: QByteArray generateKeyExchangeInitPacket(); // Returns payload. void generateKeyDhInitPacket(const Botan::BigInt &e); + void generateKeyEcdhInitPacket(const QByteArray &clientQ); void generateNewKeysPacket(); void generateDisconnectPacket(SshErrorCode reason, const QByteArray &reasonString); diff --git a/src/libs/ssh/sshpacket_p.h b/src/libs/ssh/sshpacket_p.h index 0c25a8cc30..b4af450c5e 100644 --- a/src/libs/ssh/sshpacket_p.h +++ b/src/libs/ssh/sshpacket_p.h @@ -53,7 +53,9 @@ enum SshPacketType { SSH_MSG_KEXINIT = 20, SSH_MSG_NEWKEYS = 21, SSH_MSG_KEXDH_INIT = 30, + SSH_MSG_KEX_ECDH_INIT = 30, SSH_MSG_KEXDH_REPLY = 31, + SSH_MSG_KEX_ECDH_REPLY = 31, SSH_MSG_USERAUTH_REQUEST = 50, SSH_MSG_USERAUTH_FAILURE = 51, diff --git a/src/libs/ssh/sshsendfacility.cpp b/src/libs/ssh/sshsendfacility.cpp index 8a4faec433..18c3dde1d0 100644 --- a/src/libs/ssh/sshsendfacility.cpp +++ b/src/libs/ssh/sshsendfacility.cpp @@ -85,6 +85,12 @@ void SshSendFacility::sendKeyDhInitPacket(const Botan::BigInt &e) sendPacket(); } +void SshSendFacility::sendKeyEcdhInitPacket(const QByteArray &clientQ) +{ + m_outgoingPacket.generateKeyEcdhInitPacket(clientQ); + sendPacket(); +} + void SshSendFacility::sendNewKeysPacket() { m_outgoingPacket.generateNewKeysPacket(); diff --git a/src/libs/ssh/sshsendfacility_p.h b/src/libs/ssh/sshsendfacility_p.h index 42df521b1f..04f0dff13f 100644 --- a/src/libs/ssh/sshsendfacility_p.h +++ b/src/libs/ssh/sshsendfacility_p.h @@ -57,6 +57,7 @@ public: QByteArray sendKeyExchangeInitPacket(); void sendKeyDhInitPacket(const Botan::BigInt &e); + void sendKeyEcdhInitPacket(const QByteArray &clientQ); void sendNewKeysPacket(); void sendDisconnectPacket(SshErrorCode reason, const QByteArray &reasonString); |