aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristian Kandeler <christian.kandeler@theqtcompany.com>2015-05-26 10:25:56 +0200
committerChristian Kandeler <christian.kandeler@theqtcompany.com>2015-05-28 09:07:24 +0000
commit3b8ea3fc1ea5a3be3e537fc6a6390886eaaff62e (patch)
tree9b79452d230296d9f41fd277c1f47b2a9d4d0111
parent244cdb7804e7e45f235836d4656644d5bf9135ee (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.h43
-rw-r--r--src/libs/ssh/sshcapabilities.cpp27
-rw-r--r--src/libs/ssh/sshcapabilities_p.h7
-rw-r--r--src/libs/ssh/sshincomingpacket.cpp59
-rw-r--r--src/libs/ssh/sshincomingpacket_p.h4
-rw-r--r--src/libs/ssh/sshkeyexchange.cpp138
-rw-r--r--src/libs/ssh/sshkeyexchange_p.h6
-rw-r--r--src/libs/ssh/sshoutgoingpacket.cpp5
-rw-r--r--src/libs/ssh/sshoutgoingpacket_p.h1
-rw-r--r--src/libs/ssh/sshpacket_p.h2
-rw-r--r--src/libs/ssh/sshsendfacility.cpp6
-rw-r--r--src/libs/ssh/sshsendfacility_p.h1
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);