Skip to content

Commit

Permalink
Store and verify SSH server fingerprints.
Browse files Browse the repository at this point in the history
This also moves thread creation for SSHLogin back into mosh_nacl.cc, which
allows better handing of data communication between MoshClientInstance and
SSHLogin. It also further generalizes SSHLogin; it shouldn't care that it is
run in a thread.
  • Loading branch information
rpwoodbu committed Mar 10, 2014
1 parent 421582f commit 4b44fab
Show file tree
Hide file tree
Showing 5 changed files with 158 additions and 59 deletions.
33 changes: 30 additions & 3 deletions src/app/mosh_window.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,11 +125,38 @@ mosh.CommandInstance.prototype.onMessage_ = function(e) {
}
this.io.print(output + '\r\n');
console.error(output);
} else if (type == 'get_ssh_key') {
} else if (type.match(/^get_.+/)) {
var thiz = this;
chrome.storage.local.get('ssh_key', function(o) {
thiz.moshNaCl_.postMessage({'ssh_key': o['ssh_key']});
var name = type.slice(4);
chrome.storage.local.get(name, function(o) {
var result = {};
result[name] = o[name];
thiz.moshNaCl_.postMessage(result);
});
} else if (type.match(/^set_.+/)) {
var name = type.slice(4);
var param = {};
param[name] = data;
// Wash out any string "objects" by going through JSON (hacky).
var j = JSON.stringify(param);
param = JSON.parse(j);
chrome.storage.local.set(param);
} else if (type.match(/^sync_get_.+/)) {
var thiz = this;
var name = type.slice(9);
chrome.storage.sync.get(name, function(o) {
var result = {};
result[name] = o[name];
thiz.moshNaCl_.postMessage(result);
});
} else if (type.match(/^sync_set_.+/)) {
var name = type.slice(9);
var param = {};
param[name] = data;
// Wash out any string "objects" by going through JSON (hacky).
var j = JSON.stringify(param);
param = JSON.parse(j);
chrome.storage.sync.set(param);
} else {
console.log('Unknown message type: ' + JSON.stringify(e.data));
}
Expand Down
60 changes: 50 additions & 10 deletions src/mosh_nacl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -208,15 +208,23 @@ void MoshClientInstance::HandleMessage(const pp::Var &var) {
if (key.is_undefined() == false) {
ssh_login_.set_key(key.AsString());
}
ssh_login_.set_addr(addr_);
ssh_login_.set_port(port_);
ssh_login_.Launch();
// Now get the known hosts.
Output(TYPE_GET_KNOWN_HOSTS, "");
} else if (dict.HasKey("known_hosts")) {
pp::Var known_hosts = dict.Get("known_hosts");
if (known_hosts.is_undefined() == false) {
ssh_login_.set_known_hosts(known_hosts);
}
// The assumption is that Output(TYPE_GET_SSH_KEY, "") was already
// called, which precipitated Output(TYPE_GET_KNOWN_HOSTS, ""), so now we
// are ready to do the SSH login.
LaunchSSHLogin();
} else {
Log("HandleMessage(): Got a message of an unexpected type.");
}
}

void MoshClientInstance::Output(OutputType t, const string &s) {
void MoshClientInstance::Output(OutputType t, const pp::Var &data) {
string type;
switch (t) {
case TYPE_DISPLAY:
Expand All @@ -231,15 +239,21 @@ void MoshClientInstance::Output(OutputType t, const string &s) {
case TYPE_GET_SSH_KEY:
type = "get_ssh_key";
break;
case TYPE_GET_KNOWN_HOSTS:
type = "sync_get_known_hosts";
break;
case TYPE_SET_KNOWN_HOSTS:
type = "sync_set_known_hosts";
break;
default:
// Bad type.
return;
}

pp::VarDictionary dict;
dict.Set(pp::Var("type"), pp::Var(type));
dict.Set(pp::Var("data"), pp::Var(s));
PostMessage(pp::Var(dict));
dict.Set("type", type);
dict.Set("data", data);
PostMessage(dict);
}

void MoshClientInstance::Logv(OutputType t, const char *format, va_list argp) {
Expand Down Expand Up @@ -343,13 +357,13 @@ void MoshClientInstance::Launch(int32_t result) {
}

void MoshClientInstance::LaunchMosh(int32_t unused) {
int thread_err = pthread_create(&thread_, NULL, Mosh, this);
int thread_err = pthread_create(&thread_, NULL, MoshThread, this);
if (thread_err != 0) {
Error("Failed to create Mosh thread: %s", strerror(thread_err));
}
}

void *MoshClientInstance::Mosh(void *data) {
void *MoshClientInstance::MoshThread(void *data) {
MoshClientInstance *thiz = reinterpret_cast<MoshClientInstance *>(data);

setenv("TERM", "xterm-256color", 1);
Expand All @@ -366,7 +380,33 @@ void *MoshClientInstance::Mosh(void *data) {

delete[] argv0;
exit(0);
return 0;
return NULL;
}

void MoshClientInstance::LaunchSSHLogin() {
ssh_login_.set_addr(addr_);
ssh_login_.set_port(port_);

int thread_err = pthread_create(&thread_, NULL, SSHLoginThread, this);
if (thread_err != 0) {
Error("Failed to create SSHLogin thread: %s", strerror(thread_err));
}
}

void *MoshClientInstance::SSHLoginThread(void *data) {
MoshClientInstance *thiz = reinterpret_cast<MoshClientInstance *>(data);

if (thiz->ssh_login_.Start() == false) {
thiz->Error("SSH Login failed.");
exit(1);
}

thiz->Output(TYPE_SET_KNOWN_HOSTS, thiz->ssh_login_.known_hosts());

pp::Module::Get()->core()->CallOnMainThread(
0, thiz->cc_factory_.NewCallback(&MoshClientInstance::LaunchMosh));

return NULL;
}

// Initialize static data for MoshClientInstance.
Expand Down
28 changes: 17 additions & 11 deletions src/mosh_nacl.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,12 @@ class MoshClientInstance : public pp::Instance {
TYPE_LOG,
TYPE_ERROR,
TYPE_GET_SSH_KEY,
TYPE_GET_KNOWN_HOSTS,
TYPE_SET_KNOWN_HOSTS,
};

// Low-level function to output data to Javascript.
void Output(OutputType t, const string &s);
void Output(OutputType t, const pp::Var &data);

// Sends messages to the Javascript console log.
void Logv(OutputType t, const char *format, va_list argp);
Expand All @@ -66,28 +68,31 @@ class MoshClientInstance : public pp::Instance {
// Sends error messages to the Javascript console log and terminal.
void Error(const char *format, ...);

// Launches Mosh in a new thread. Must be visible to SSHLogin. Must take one
// argument to be used as a completion callback.
void LaunchMosh(int32_t unused);

// Allow SSHLogin to set the port based on its findings. Takes ownership.
void set_port(char *port) { delete port_; port_ = port; }

// Pepper POSIX emulation.
PepperPOSIX::POSIX *posix_;

// Window change "file"; must be visible to sigaction().
class WindowChange *window_change_;

// Must be visible to SSHLogin.
// TODO: Clean up the interface between this class and SSHLogin.
pp::CompletionCallbackFactory<MoshClientInstance> cc_factory_;

private:
// Launcher that can be a callback.
void Launch(int32_t result);

// New thread entry point for Mosh.
static void *Mosh(void *data);
// Launches Mosh in a new thread. Must take one argument to be used as a
// completion callback.
void LaunchMosh(int32_t unused);

// New thread entry point for Mosh. |data| is |this|.
static void *MoshThread(void *data);

// Launches SSHLogin in a new thread.
void LaunchSSHLogin();

// New thread entry point for SSHLogin. |data| is |this|.
static void *SSHLoginThread(void *data);

static int num_instances_; // This needs to be a singleton.
pthread_t thread_;
Expand All @@ -102,6 +107,7 @@ class MoshClientInstance : public pp::Instance {
pp::InstanceHandle instance_handle_;
class Keyboard *keyboard_;
pp::HostResolver resolver_;
pp::CompletionCallbackFactory<MoshClientInstance> cc_factory_;

// Disable copy and assignment.
MoshClientInstance(const MoshClientInstance&);
Expand Down
73 changes: 49 additions & 24 deletions src/ssh_login.cc
Original file line number Diff line number Diff line change
Expand Up @@ -31,19 +31,6 @@ SSHLogin::~SSHLogin() {
delete session_;
}

void *SSHLogin::ThreadEntry(void *data) {
SSHLogin *thiz = reinterpret_cast<SSHLogin *>(data);
thiz->Start();
return NULL;
}

void SSHLogin::Launch() {
int thread_err = pthread_create(&thread_, NULL, ThreadEntry, this);
if (thread_err != 0) {
mosh_->Error("Failed to create SSH login thread: %s", strerror(thread_err));
}
}

void SSHLogin::GetKeyboardLine(char *buf, size_t len, bool echo) {
int i = 0;
while (i < len) {
Expand Down Expand Up @@ -73,17 +60,27 @@ void SSHLogin::GetKeyboardLine(char *buf, size_t len, bool echo) {
buf[i] = 0;
}

void SSHLogin::Start() {
if (!RealStart()) {
// TODO: Propagate errors more cleanly.
exit(1);
bool SSHLogin::AskYesNo(const string &prompt) {
for (int i = 0; i < RETRIES; ++i) {
mosh_->Output(MoshClientInstance::TYPE_DISPLAY, prompt + " (Yes/No): ");
char input_buf[INPUT_SIZE];
GetKeyboardLine(input_buf, sizeof(input_buf), true);
mosh_->Output(MoshClientInstance::TYPE_DISPLAY, "\r\n");
string input = input_buf;
if (input == "yes" || input == "Yes") {
return true;
}
if (input == "no" || input == "No") {
return false;
}
mosh_->Output(MoshClientInstance::TYPE_DISPLAY,
"Please specify Yes or No.\r\n");
}

pp::Module::Get()->core()->CallOnMainThread(
0, mosh_->cc_factory_.NewCallback(&MoshClientInstance::LaunchMosh));
return false;
}

bool SSHLogin::RealStart() {
bool SSHLogin::Start() {
setenv("HOME", "dummy", 1); // To satisfy libssh.

delete session_;
Expand Down Expand Up @@ -149,11 +146,39 @@ bool SSHLogin::RealStart() {
}

bool SSHLogin::CheckFingerprint() {
// TODO: Actually track known hosts and check with the user if unknown.
const string server_name = addr_ + ":" + port_;
const string server_fp = session_->GetPublicKey()->MD5();

mosh_->Output(MoshClientInstance::TYPE_DISPLAY,
"Fingerprint of remote ssh host (MD5): " +
session_->GetPublicKey()->MD5() + "\r\n");
return true;
"Fingerprint of remote ssh host (MD5):\r\n " + server_fp + "\r\n");

const pp::Var stored_fp_var = known_hosts_.Get(server_name);
if (stored_fp_var.is_undefined()) {
bool result = AskYesNo("Server fingerprint unknown. Store and continue?");
if (result == true) {
known_hosts_.Set(server_name, server_fp);
return true;
}
} else {
string stored_fp = stored_fp_var.AsString();
if (stored_fp == server_fp) {
return true;
}
mosh_->Output(MoshClientInstance::TYPE_DISPLAY,
"WARNING!!! Server fingerprint differs! "
"Possible man-in-the-middle attack.\r\n"
"Stored fingerprint (MD5):\r\n " + stored_fp + "\r\n");
bool result = AskYesNo("Connect anyway, and store new fingerprint?");
if (result == true) {
result = AskYesNo("Don't take this lightly. Are you really sure?");
if (result == true) {
known_hosts_.Set(server_name, server_fp);
return true;
}
}
}

return false;
}

vector<ssh::AuthenticationType> *SSHLogin::GetAuthTypes() {
Expand Down
23 changes: 12 additions & 11 deletions src/ssh_login.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,13 @@
#ifndef SSH_LOGIN_H
#define SSH_LOGIN_H

#include <pthread.h>
#include <stddef.h>
#include <string>
#include <vector>

#include "ssh.h"
#include "ppapi/cpp/var.h"
#include "ppapi/cpp/var_dictionary.h"

using ::std::string;
using ::std::vector;
Expand All @@ -40,8 +41,8 @@ class SSHLogin {
explicit SSHLogin(MoshClientInstance *mosh);
~SSHLogin();

// Launch login in new thread.
void Launch();
// Begin the SSH login session. Returns true iff SSH Login succeeded.
bool Start();

string addr() const { return addr_; }
void set_addr(const string &addr) { addr_ = addr; }
Expand All @@ -55,14 +56,10 @@ class SSHLogin {
string key() const { return key_; }
void set_key(const string &key) { key_ = key; }

private:
// Entry point after thread is created. Handles communication of the
// disposition of SSHLogin to MoshClientInstance.
void Start();

// The real work of the SSHLogin begins here; called from Start().
bool RealStart();
pp::VarDictionary known_hosts() const { return known_hosts_; }
void set_known_hosts(const pp::Var &var) { known_hosts_ = var; }

private:
// Passed as function pointer to pthread_create(). |data| is |this|.
static void *ThreadEntry(void *data);

Expand All @@ -77,6 +74,10 @@ class SSHLogin {
// caller.
vector<ssh::AuthenticationType> *GetAuthTypes();

// Ask a yes/no question to the user, and return the answer as a bool.
// Prefers to return false if input is not parseable.
bool AskYesNo(const string &prompt);

bool DoPasswordAuth();
bool DoInteractiveAuth();
bool DoPublicKeyAuth();
Expand All @@ -86,8 +87,8 @@ class SSHLogin {
string port_;
string user_;
string key_;
pp::VarDictionary known_hosts_;
MoshClientInstance *mosh_;
pthread_t thread_;
ssh::Session *session_;

// Disable copy and assignment.
Expand Down

0 comments on commit 4b44fab

Please sign in to comment.