forked from qt-creator/qt-creator
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>
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
if (pubKeyAlgo == SshCapabilities::PubKeyDss || pubKeyAlgo == SshCapabilities::PubKeyRsa) {
|
||||
|
||||
// g and y
|
||||
if (pubKeyAlgo == SshCapabilities::PubKeyDss) {
|
||||
replyData.parameters << SshPacketParser::asBigInt(m_data, &offset);
|
||||
replyData.parameters << SshPacketParser::asBigInt(m_data, &offset);
|
||||
// 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.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user