summaryrefslogtreecommitdiff
path: root/src/protocol/internal/fqterm_ssh2_kex.cpp
diff options
context:
space:
mode:
authoriroul <iroul@iroul-VirtualBox.(none)>2014-04-04 07:35:14 -0700
committeriroul <iroul@iroul-VirtualBox.(none)>2014-04-04 07:35:14 -0700
commitafd34f2893a06a3aecf17e8e83b1df6ed2ae91a2 (patch)
tree851102abc55d91a1b76e63e9e89f9a7733da95b5 /src/protocol/internal/fqterm_ssh2_kex.cpp
parentc4b028ad53f7b362a864de24828d7cc39ff67b0a (diff)
downloadfqterm-afd34f2893a06a3aecf17e8e83b1df6ed2ae91a2.tar.xz
move to my github.
Diffstat (limited to 'src/protocol/internal/fqterm_ssh2_kex.cpp')
-rw-r--r--src/protocol/internal/fqterm_ssh2_kex.cpp394
1 files changed, 394 insertions, 0 deletions
diff --git a/src/protocol/internal/fqterm_ssh2_kex.cpp b/src/protocol/internal/fqterm_ssh2_kex.cpp
new file mode 100644
index 0000000..acae08d
--- /dev/null
+++ b/src/protocol/internal/fqterm_ssh2_kex.cpp
@@ -0,0 +1,394 @@
+/***************************************************************************
+ * fqterm, a terminal emulator for both BBS and *nix. *
+ * Copyright (C) 2008 fqterm development group. *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the *
+ * Free Software Foundation, Inc., *
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. *
+ ***************************************************************************/
+
+#include <vector>
+#include <string>
+#include <openssl/bn.h>
+#include <openssl/sha.h>
+#include <openssl/des.h>
+#include <openssl/objects.h>
+#include <openssl/evp.h>
+
+#include "fqterm_ssh2_kex.h"
+#include "fqterm_ssh_md5.h"
+#include "fqterm_trace.h"
+
+namespace FQTerm {
+
+static const int g = 2;
+static const int q = 128;
+static const unsigned char p[q]={
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xC9, 0x0F, 0xDA, 0xA2, 0x21, 0x68, 0xC2, 0x34,
+ 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1,
+ 0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74,
+ 0x02, 0x0B, 0xBE, 0xA6, 0x3B, 0x13, 0x9B, 0x22,
+ 0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD,
+ 0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B,
+ 0x30, 0x2B, 0x0A, 0x6D, 0xF2, 0x5F, 0x14, 0x37,
+ 0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45,
+ 0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6,
+ 0xF4, 0x4C, 0x42, 0xE9, 0xA6, 0x37, 0xED, 0x6B,
+ 0x0B, 0xFF, 0x5C, 0xB6, 0xF4, 0x06, 0xB7, 0xED,
+ 0xEE, 0x38, 0x6B, 0xFB, 0x5A, 0x89, 0x9F, 0xA5,
+ 0xAE, 0x9F, 0x24, 0x11, 0x7C, 0x4B, 0x1F, 0xE6,
+ 0x49, 0x28, 0x66, 0x51, 0xEC, 0xE6, 0x53, 0x81,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
+};
+
+FQTermSSH2Kex::FQTermSSH2Kex(const char *V_C, const char *V_S)
+ : FQTermSSHKex(V_C, V_S) {
+ is_first_kex_ = true;
+ kex_state_ = FQTermSSH2Kex::BEFORE_KEXINIT;
+
+ I_C_len_ = 0;
+ I_C_ = NULL;
+ I_S_len_ = 0;
+ I_S_ = NULL;
+
+ bn_x_ = BN_new();
+ bn_e_ = BN_new();
+ bn_g_ = BN_new();
+ bn_p_ = BN_new();
+ ctx_ = BN_CTX_new();
+
+ bn_K_ = BN_new();
+ bn_f_ = BN_new();
+
+ BN_set_word(bn_g_, g);
+ BN_bin2bn(p, q, bn_p_);
+
+ session_id_ = NULL;
+}
+
+FQTermSSH2Kex::~FQTermSSH2Kex() {
+ delete[] I_C_;
+ delete[] I_S_;
+
+ BN_clear_free(bn_x_);
+ BN_clear_free(bn_e_);
+ BN_clear_free(bn_g_);
+ BN_clear_free(bn_p_);
+ BN_CTX_free(ctx_);
+
+ BN_clear_free(bn_K_);
+ BN_clear_free(bn_f_);
+
+ delete[] session_id_;
+}
+
+void FQTermSSH2Kex::initKex(FQTermSSHPacketReceiver *packetReceiver,
+ FQTermSSHPacketSender *packetSender) {
+ packet_receiver_ = packetReceiver;
+ packet_sender_ = packetSender;
+ packet_receiver_->disconnect(this);
+ FQ_VERIFY(connect(packet_receiver_, SIGNAL(packetAvaliable(int)),
+ this, SLOT(handlePacket(int))));
+ kex_state_ = FQTermSSH2Kex::BEFORE_KEXINIT;
+ emit reKex();
+}
+
+void FQTermSSH2Kex::handlePacket(int type) {
+ switch (kex_state_) {
+ case FQTermSSH2Kex::BEFORE_KEXINIT:
+ negotiateAlgorithms();
+ exchangeKey();
+ kex_state_ = FQTermSSH2Kex::WAIT_REPLY;
+ break;
+ case FQTermSSH2Kex::WAIT_REPLY:
+ if (verifyKey()) {
+ sendNewKeys();
+ kex_state_ = FQTermSSH2Kex::SESSIONKEY_SENT;
+ } else {
+ emit kexError(tr("Key exchange failed!"));
+ }
+ break;
+ case FQTermSSH2Kex::SESSIONKEY_SENT:
+ if (changeKeyAlg()) {
+ kex_state_ = FQTermSSH2Kex::KEYEX_OK;
+ emit kexOK();
+ }
+ break;
+ case FQTermSSH2Kex::KEYEX_OK:
+ // TODO: how about Key Re-Exchange (see RFC 4253, 9. Key Re-Exchange)
+ break;
+ }
+}
+
+void FQTermSSH2Kex::negotiateAlgorithms() {
+ FQ_FUNC_TRACE("ssh2kex", 10);
+
+ if (packet_receiver_->packetType() != SSH2_MSG_KEXINIT) {
+ emit kexError(tr("startKex: First packet is not SSH_MSG_KEXINIT"));
+ return ;
+ }
+
+ // 0. Backup the payload of this server packet.
+ I_S_len_ = packet_receiver_->packetDataLen() + 1; // add 1 bytes for packet type.
+ delete[] I_S_;
+ I_S_ = new char[I_S_len_];
+ I_S_[0] = SSH2_MSG_KEXINIT;
+ memcpy(I_S_ + 1, packet_receiver_->buffer_->data(), I_S_len_ - 1);
+
+ // 1. Parse server kex init packet
+ packet_receiver_->getRawData((char*)cookie_, 16);
+
+ std::vector<char> name_lists;
+ for (int i = 0; i < 10; ++i) {
+ int name_lists_len = packet_receiver_->getInt();
+ if (name_lists_len > 0) {
+ name_lists.resize(name_lists_len);
+ packet_receiver_->getRawData(&name_lists[0], name_lists_len);
+ FQ_TRACE("ssh2kex", 5) << "Algorithms: " << QString::fromAscii(&name_lists[0], name_lists_len);
+ } else {
+ FQ_TRACE("ssh2kex", 5) << "None Algorithms";
+ }
+ }
+
+ bool first_kex_packet_follows = packet_receiver_->getByte();
+ FQ_TRACE("ssh2kex", 5) << "first_kex_packet_follows: " << first_kex_packet_follows;
+
+ packet_receiver_->consume(4);
+
+ // 2. compose a kex init packet.
+ packet_sender_->startPacket(SSH2_MSG_KEXINIT);
+ packet_sender_->putRawData((const char*)cookie_, 16); // FIXME: generate new cookie_;
+ packet_sender_->putString("diffie-hellman-group1-sha1");
+ packet_sender_->putString("ssh-rsa");
+ packet_sender_->putString("3des-cbc");
+ packet_sender_->putString("3des-cbc");
+ packet_sender_->putString("hmac-sha1");
+ packet_sender_->putString("hmac-sha1");
+ packet_sender_->putString("none");
+ packet_sender_->putString("none");
+ packet_sender_->putString("");
+ packet_sender_->putString("");
+
+ packet_sender_->putByte(false);
+ packet_sender_->putInt(0);
+
+ // 3. backup the payload of this client packet.
+ I_C_len_ = packet_sender_->buffer_->len();
+ delete[] I_C_;
+ I_C_ = new char[I_C_len_];
+ memcpy(I_C_, packet_sender_->buffer_->data(), I_C_len_);
+
+ // 4. send packet to server
+ packet_sender_->write();
+}
+
+void FQTermSSH2Kex::exchangeKey() {
+ BN_rand(bn_x_, q, 0, -1);
+ BN_mod_exp(bn_e_, bn_g_, bn_x_, bn_p_, ctx_);
+
+ packet_sender_->startPacket(SSH2_MSG_KEXDH_INIT);
+ packet_sender_->putBN2(bn_e_);
+ packet_sender_->write();
+}
+
+static RSA *CreateRSAContext(unsigned char *host_key, int len);
+
+bool FQTermSSH2Kex::verifyKey() {
+ if (packet_receiver_->packetType() != SSH2_MSG_KEXDH_REPLY) {
+ emit kexError(tr("Expect a SSH_MSG_KEXDH_REPLY packet"));
+ return false;
+ }
+
+ // Extract data
+
+ int K_S_len = -1;
+ unsigned char *K_S = (unsigned char *)packet_receiver_->getString(&K_S_len);
+
+ packet_receiver_->getBN2(bn_f_);
+
+ int s_len = -1;
+ unsigned char *s = (unsigned char *)packet_receiver_->getString(&s_len);
+
+ BN_mod_exp(bn_K_, bn_f_, bn_x_, bn_p_, ctx_);
+
+ FQTermSSHBuffer *buffer = packet_sender_->output_buffer_;
+
+ buffer->clear();
+ buffer->putString(V_C_);
+ buffer->putString(V_S_);
+ buffer->putString(I_C_, I_C_len_);
+ buffer->putString(I_S_, I_S_len_);
+ buffer->putString((char *)K_S, K_S_len);
+ buffer->putSSH2BN(bn_e_);
+ buffer->putSSH2BN(bn_f_);
+ buffer->putSSH2BN(bn_K_);
+
+ SHA1(buffer->data(), buffer->len(), H_);
+
+ // Start verify
+ unsigned char s_H[SHA_DIGEST_LENGTH];
+ SHA1(H_, SHA_DIGEST_LENGTH, s_H);
+
+ // Ignore the first 15 bytes of the signature of H sent from server:
+ // algorithm_name_length[4], algorithm_name[7]("ssh-rsa") and signature_length[4].
+ RSA *rsactx = CreateRSAContext(K_S, K_S_len);
+ int sig_len = s_len - 15;
+ unsigned char *sig = s + 15;
+ int res = RSA_verify(NID_sha1, s_H, SHA_DIGEST_LENGTH,
+ sig, sig_len, rsactx);
+
+ RSA_free(rsactx);
+
+ delete K_S;
+ delete s;
+
+ return res == 1;
+}
+
+static RSA *CreateRSAContext(unsigned char *host_key, int len) {
+ FQTermSSHBuffer buffer(len);
+
+ buffer.putRawData((char *)host_key, len);
+
+ int algo_len = -1;
+ unsigned char *algo = (unsigned char *)buffer.getString(&algo_len);
+
+ FQ_VERIFY(std::string("ssh-rsa") == std::string((char *)algo));
+
+ int e_len = -1;
+ unsigned char *e = (unsigned char *)buffer.getString(&e_len);
+
+ int n_len = -1;
+ unsigned char *n = (unsigned char *)buffer.getString(&n_len);
+
+
+ RSA *rsa = RSA_new();
+ rsa->e = BN_new();
+ BN_bin2bn(e, e_len, rsa->e);
+
+ rsa->n = BN_new();
+ BN_bin2bn(n, n_len, rsa->n);
+
+ delete[] algo;
+ delete[] e;
+ delete[] n;
+
+ return rsa;
+}
+
+void FQTermSSH2Kex::sendNewKeys(){
+ packet_sender_->startPacket(SSH2_MSG_NEWKEYS);
+ packet_sender_->write();
+}
+
+bool FQTermSSH2Kex::changeKeyAlg() {
+ if (packet_receiver_->packetType() != SSH2_MSG_NEWKEYS) {
+ emit kexError(tr("Expect a SSH_MSG_NEWKEYS packet"));
+ return false;
+ }
+
+ if (session_id_ == NULL) {
+ session_id_ = new unsigned char[SHA_DIGEST_LENGTH];
+ memcpy(session_id_, H_, SHA_DIGEST_LENGTH);
+ }
+
+ packet_sender_->setEncryptionType(SSH_CIPHER_3DES);
+ packet_receiver_->setEncryptionType(SSH_CIPHER_3DES);
+
+ packet_sender_->setMacType(FQTERM_SSH_HMAC_SHA1);
+ packet_receiver_->setMacType(FQTERM_SSH_HMAC_SHA1);
+
+ // From RFC 4253 section 7.2:
+ // Initial IV client to server: HASH(K || H || "A" || session_id)
+ // (Here K is encoded as mpint and "A" as byte and session_id as raw
+ // data. "A" means the single character A, ASCII 65).
+ //
+ // Initial IV server to client: HASH(K || H || "B" || session_id)
+ //
+ // Encryption key client to server: HASH(K || H || "C" || session_id)
+ //
+ // Encryption key server to client: HASH(K || H || "D" || session_id)
+ //
+ // Integrity key client to server: HASH(K || H || "E" || session_id)
+ //
+ // Integrity key server to client: HASH(K || H || "F" || session_id)
+
+ int IV_c2s_len = packet_sender_->getIVSize();
+ unsigned char *IV_c2s = computeKey(IV_c2s_len, 'A');
+
+ int IV_s2c_len = packet_receiver_->getIVSize();
+ unsigned char *IV_s2c = computeKey(IV_s2c_len, 'B');
+
+ int key_c2s_len = packet_sender_->getKeySize();
+ unsigned char *key_c2s = computeKey(key_c2s_len, 'C');
+
+ int key_s2c_len = packet_receiver_->getKeySize();
+ unsigned char *key_s2c = computeKey(key_s2c_len, 'D');
+
+ int mac_key_c2s_len = packet_sender_->getMacKeySize();
+ unsigned char *mac_key_c2s = computeKey(mac_key_c2s_len, 'E');
+
+ int mac_key_s2c_len = packet_receiver_->getMacKeySize();
+ unsigned char *mac_key_s2c = computeKey(mac_key_s2c_len, 'F');
+
+
+ packet_sender_->startEncryption(key_c2s, IV_c2s);
+ packet_sender_->startMac(mac_key_c2s);
+
+ packet_receiver_->startEncryption(key_s2c, IV_s2c);
+ packet_receiver_->startMac(mac_key_s2c);
+
+
+ delete[] IV_c2s;
+ delete[] IV_s2c;
+ delete[] key_c2s;
+ delete[] key_s2c;
+ delete[] mac_key_c2s;
+ delete[] mac_key_s2c;
+
+ return true;
+}
+
+unsigned char *FQTermSSH2Kex::computeKey(int expected_len, char flag) {
+ unsigned char *key = new unsigned char[expected_len + SHA_DIGEST_LENGTH];
+
+ int len = 0;
+ SHA_CTX hash;
+
+ FQTermSSHBuffer K(BN_num_bytes(bn_K_) + 5);
+ K.putSSH2BN(bn_K_);
+
+ while (len < expected_len) {
+ SHA1_Init(&hash);
+ SHA1_Update(&hash, K.data(), K.len());
+ SHA1_Update(&hash, H_, SHA_DIGEST_LENGTH);
+
+ if (len == 0) {
+ SHA1_Update(&hash, &flag, 1);
+ SHA1_Update(&hash, session_id_, SHA_DIGEST_LENGTH);
+ } else {
+ SHA1_Update(&hash, key, len);
+ }
+
+ SHA1_Final(key + len, &hash);
+ len += SHA_DIGEST_LENGTH;
+ }
+
+ return key;
+}
+
+} // namespace FQTerm
+
+#include "fqterm_ssh2_kex.moc"