SSH2Connection.cs :  » Network-Clients » Granados » Routrek » SSHCV2 » C# / CSharp Open Source

Home
C# / CSharp Open Source
1.2.6.4 mono .net core
2.2.6.4 mono core
3.Aspect Oriented Frameworks
4.Bloggers
5.Build Systems
6.Business Application
7.Charting Reporting Tools
8.Chat Servers
9.Code Coverage Tools
10.Content Management Systems CMS
11.CRM ERP
12.Database
13.Development
14.Email
15.Forum
16.Game
17.GIS
18.GUI
19.IDEs
20.Installers Generators
21.Inversion of Control Dependency Injection
22.Issue Tracking
23.Logging Tools
24.Message
25.Mobile
26.Network Clients
27.Network Servers
28.Office
29.PDF
30.Persistence Frameworks
31.Portals
32.Profilers
33.Project Management
34.RSS RDF
35.Rule Engines
36.Script
37.Search Engines
38.Sound Audio
39.Source Control
40.SQL Clients
41.Template Engines
42.Testing
43.UML
44.Web Frameworks
45.Web Service
46.Web Testing
47.Wiki Engines
48.Windows Presentation Foundation
49.Workflows
50.XML Parsers
C# / C Sharp
C# / C Sharp by API
C# / CSharp Tutorial
C# / CSharp Open Source » Network Clients » Granados 
Granados » Routrek » SSHCV2 » SSH2Connection.cs
/* ---------------------------------------------------------------------------
 *
 * Copyright (c) Routrek Networks, Inc.    All Rights Reserved..
 * 
 * This file is a part of the Granados SSH Client Library that is subject to
 * the license included in the distributed package.
 * You may not use this file except in compliance with the license.
 * 
 * ---------------------------------------------------------------------------
 */
using System;
using System.Collections;
using System.IO;
using System.Threading;
using System.Diagnostics;
using System.Net.Sockets;
using System.Text;
using System.Security.Cryptography;

using Routrek.PKI;
using Routrek.SSHC;
using Routrek.Toolkit;

namespace Routrek.SSHCV2{
  public sealed class SSH2Connection : SSHConnection {
  
    //packet count for transmission and reception
    private int _tSequence;

    //MAC for transmission and reception
    private MAC _tMAC;
    private SSH2PacketBuilder _packetBuilder;

    //server info
    private SSH2ConnectionInfo _cInfo;

    private bool _waitingForPortForwardingResponse;

    private KeyExchanger _asyncKeyExchanger;

    public SSH2Connection(SSHConnectionParameter param, ISSHConnectionEventReceiver r, string serverversion, string clientversion) : base(param, r) {
      _cInfo = new SSH2ConnectionInfo();
      _cInfo._serverVersionString = serverversion;
      _cInfo._clientVersionString = clientversion;
      
      _packetBuilder = new SSH2PacketBuilder(new SynchronizedSSH2PacketHandler());
    }
    internal override IByteArrayHandler PacketBuilder {
      get {
        return _packetBuilder;
      }
    }
    public override SSHConnectionInfo ConnectionInfo {
      get {
        return _cInfo;
      }
    }

    internal override AuthenticationResult Connect(AbstractSocket s) {
      _stream = s;
      
      KeyExchanger kex = new KeyExchanger(this, null);
      if(!kex.SynchronousKexExchange()) {
        _stream.Close();
        return AuthenticationResult.Failure;
      }
      //Step3 user authentication
      ServiceRequest("ssh-userauth");
      _authenticationResult = UserAuth();
      return _authenticationResult;
    }


    private void ServiceRequest(string servicename) {
      SSH2DataWriter wr = new SSH2DataWriter();
      wr.WritePacketType(PacketType.SSH_MSG_SERVICE_REQUEST);
      wr.Write(servicename);
      TransmitPacket(wr.ToByteArray());

      byte[] response = ReceivePacket().Data;
      SSH2DataReader re = new SSH2DataReader(response);
      PacketType t = re.ReadPacketType();
      if(t!=PacketType.SSH_MSG_SERVICE_ACCEPT) {
        throw new SSHException("service establishment failed "+t);
      }

      string s = Encoding.ASCII.GetString(re.ReadString());
      if(servicename!=s)
        throw new SSHException("protocol error");
    }

    private AuthenticationResult UserAuth() {
      string sn = "ssh-connection"; 

      SSH2DataWriter wr = new SSH2DataWriter();
      wr.WritePacketType(PacketType.SSH_MSG_USERAUTH_REQUEST);
      wr.Write(_param.UserName);
      if(_param.AuthenticationType==AuthenticationType.Password) {
        //Password authentication
        wr.Write(sn); 
        wr.Write("password");
        wr.Write(false);
        wr.Write(_param.Password);
      }
      else if(_param.AuthenticationType==AuthenticationType.KeyboardInteractive) {
        wr.Write(sn); 
        wr.Write("keyboard-interactive");
        wr.Write(""); //lang
        wr.Write(""); //submethod
      }
      else {
        //public key authentication
        SSH2UserAuthKey kp = SSH2UserAuthKey.FromSECSHStyleFile(_param.IdentityFile, _param.Password);
        SSH2DataWriter signsource = new SSH2DataWriter();
        signsource.WriteAsString(_sessionID);
        signsource.WritePacketType(PacketType.SSH_MSG_USERAUTH_REQUEST);
        signsource.Write(_param.UserName);
        signsource.Write(sn);
        signsource.Write("publickey");
        signsource.Write(true);
        signsource.Write(SSH2Util.PublicKeyAlgorithmName(kp.Algorithm));
        signsource.WriteAsString(kp.GetPublicKeyBlob());

        SSH2DataWriter signpack = new SSH2DataWriter();
        signpack.Write(SSH2Util.PublicKeyAlgorithmName(kp.Algorithm));
        signpack.WriteAsString(kp.Sign(signsource.ToByteArray()));

        wr.Write(sn);
        wr.Write("publickey");
        wr.Write(true);
        wr.Write(SSH2Util.PublicKeyAlgorithmName(kp.Algorithm));
        wr.WriteAsString(kp.GetPublicKeyBlob());
        wr.WriteAsString(signpack.ToByteArray());
      }
      TransmitPacket(wr.ToByteArray());

      _authenticationResult = ProcessAuthenticationResponse();
      if(_authenticationResult==AuthenticationResult.Failure)
        throw new SSHException(Strings.GetString("AuthenticationFailed"));
      return _authenticationResult;
    }
    private AuthenticationResult ProcessAuthenticationResponse() {
      do {
        SSH2DataReader response = new SSH2DataReader(ReceivePacket().Data);
        PacketType h = response.ReadPacketType();
        if(h==PacketType.SSH_MSG_USERAUTH_FAILURE) {
          string msg = Encoding.ASCII.GetString(response.ReadString());
          return AuthenticationResult.Failure;
        }
        else if(h==PacketType.SSH_MSG_USERAUTH_BANNER) {
          Debug.WriteLine("USERAUTH_BANNER");
        }
        else if(h==PacketType.SSH_MSG_USERAUTH_SUCCESS) {
          _packetBuilder.Handler = new CallbackSSH2PacketHandler(this);
          return AuthenticationResult.Success; //successfully exit
        }
        else if(h==PacketType.SSH_MSG_USERAUTH_INFO_REQUEST) {
          string name = Encoding.ASCII.GetString(response.ReadString());
          string inst = Encoding.ASCII.GetString(response.ReadString());
          string lang = Encoding.ASCII.GetString(response.ReadString());
          int num = response.ReadInt32();
          string[] prompts = new string[num];
          for(int i=0; i<num; i++) {
            prompts[i] = Encoding.ASCII.GetString(response.ReadString());
            bool echo = response.ReadBool();
          }
          _eventReceiver.OnAuthenticationPrompt(prompts);
          return AuthenticationResult.Prompt;
        }
        else
          throw new SSHException("protocol error: unexpected packet type "+h);
      } while(true);
    }
    public AuthenticationResult DoKeyboardInteractiveAuth(string[] input) {
      if(_param.AuthenticationType!=AuthenticationType.KeyboardInteractive)
        throw new SSHException("DoKeyboardInteractiveAuth() must be called with keyboard-interactive authentication");
      SSH2DataWriter re = new SSH2DataWriter();
      re.WritePacketType(PacketType.SSH_MSG_USERAUTH_INFO_RESPONSE);
      re.Write(input.Length);
      foreach(string t in input)
        re.Write(t);
      TransmitPacket(re.ToByteArray());

      _authenticationResult = ProcessAuthenticationResponse();
      //try again on failure
      if(_authenticationResult==AuthenticationResult.Failure) {
        SSH2DataWriter wr = new SSH2DataWriter();
        wr.WritePacketType(PacketType.SSH_MSG_USERAUTH_REQUEST);
        wr.Write(_param.UserName);
        wr.Write("ssh-connection"); 
        wr.Write("keyboard-interactive");
        wr.Write(""); //lang
        wr.Write(""); //submethod
        TransmitPacket(wr.ToByteArray());
        _authenticationResult = ProcessAuthenticationResponse();
      }
      return _authenticationResult;
    }
    
    public override SSHChannel OpenShell(ISSHChannelEventReceiver receiver) {
      //open channel
      SSH2DataWriter wr = new SSH2DataWriter();
      wr.WritePacketType(PacketType.SSH_MSG_CHANNEL_OPEN);
      wr.Write("session");
      int local_channel = this.RegisterChannelEventReceiver(null, receiver)._localID;

      wr.Write(local_channel);
      wr.Write(_param.WindowSize); //initial window size
      int windowsize = _param.WindowSize;
      wr.Write(_param.MaxPacketSize); //max packet size
      SSH2Channel channel = new SSH2Channel(this, ChannelType.Shell, local_channel);
      TransmitPacket(wr.ToByteArray());
      
      return channel;
    }

    public override SSHChannel ForwardPort(ISSHChannelEventReceiver receiver, string remote_host, int remote_port, string originator_host, int originator_port) {
      SSH2DataWriter wr = new SSH2DataWriter();
      wr.WritePacketType(PacketType.SSH_MSG_CHANNEL_OPEN);
      wr.Write("direct-tcpip");
      int local_id = RegisterChannelEventReceiver(null, receiver)._localID;
      wr.Write(local_id);
      wr.Write(_param.WindowSize); //initial window size
      int windowsize = _param.WindowSize;
      wr.Write(_param.MaxPacketSize); //max packet size
      wr.Write(remote_host);
      wr.Write(remote_port);
      wr.Write(originator_host);
      wr.Write(originator_port);

      SSH2Channel channel = new SSH2Channel(this, ChannelType.ForwardedLocalToRemote, local_id);
      
      TransmitPacket(wr.ToByteArray());
      
      return channel;
    }

    public override void ListenForwardedPort(string allowed_host, int bind_port) {
      SSH2DataWriter wr = new SSH2DataWriter();
      wr.WritePacketType(PacketType.SSH_MSG_GLOBAL_REQUEST);
      wr.Write("tcpip-forward");
      wr.Write(true);
      wr.Write(allowed_host);
      wr.Write(bind_port);

      _waitingForPortForwardingResponse = true;
      TransmitPacket(wr.ToByteArray());
    }

    public override void CancelForwardedPort(string host, int port) {
      SSH2DataWriter wr = new SSH2DataWriter();
      wr.WritePacketType(PacketType.SSH_MSG_GLOBAL_REQUEST);
      wr.Write("cancel-tcpip-forward");
      wr.Write(true);
      wr.Write(host);
      wr.Write(port);
      TransmitPacket(wr.ToByteArray());
    }

    private void ProcessPortforwardingRequest(ISSHConnectionEventReceiver receiver, SSH2DataReader reader) {
      string method = Encoding.ASCII.GetString(reader.ReadString());

      int remote_channel = reader.ReadInt32();
      int window_size = reader.ReadInt32(); //skip initial window size
      int servermaxpacketsize = reader.ReadInt32();
      string host = Encoding.ASCII.GetString(reader.ReadString());
      int port = reader.ReadInt32();
      string originator_ip = Encoding.ASCII.GetString(reader.ReadString());
      int originator_port = reader.ReadInt32();
      
      PortForwardingCheckResult r = receiver.CheckPortForwardingRequest(host,port,originator_ip,originator_port);
      SSH2DataWriter wr = new SSH2DataWriter();
      if(r.allowed) {
        //send OPEN_CONFIRMATION
        SSH2Channel channel = new SSH2Channel(this, ChannelType.ForwardedRemoteToLocal, RegisterChannelEventReceiver(null, r.channel)._localID, remote_channel, servermaxpacketsize);
        wr.WritePacketType(PacketType.SSH_MSG_CHANNEL_OPEN_CONFIRMATION);
        wr.Write(remote_channel);
        wr.Write(channel.LocalChannelID);
        wr.Write(_param.WindowSize); //initial window size
        wr.Write(_param.MaxPacketSize); //max packet size
        receiver.EstablishPortforwarding(r.channel, channel);
      }
      else {
        wr.WritePacketType(PacketType.SSH_MSG_CHANNEL_OPEN_FAILURE);
        wr.Write(remote_channel);
        wr.Write(r.reason_code);
        wr.Write(r.reason_message);
        wr.Write(""); //lang tag
      }
      TransmitPacket(wr.ToByteArray());
    }

    internal SSH2Packet TransmitPacket(byte[] payload) {
      lock(_tLockObject) { 
        SSH2Packet p = SSH2Packet.FromPlainPayload(payload, _tCipher==null? 8 : _tCipher.BlockSize, _param.Random);
        if(_tMAC!=null) p.CalcHash(_tMAC, _tSequence);

        _tSequence++;
        p.WriteTo(_stream, _tCipher);
        return p;
      }
    }

    //synchronous reception
    internal SSH2Packet ReceivePacket() {

      while(true) {
        SSH2Packet p = null;
        SynchronizedSSH2PacketHandler handler = (SynchronizedSSH2PacketHandler)_packetBuilder.Handler;
        if(!handler.HasPacket) {
          handler.Wait();
          if(handler.State==ReceiverState.Error)
            throw new SSHException(handler.ErrorMessage);
          else if(handler.State==ReceiverState.Closed)
            throw new SSHException("socket closed");
        }
        p = handler.PopPacket();

        SSH2DataReader r = new SSH2DataReader(p.Data);
        PacketType pt = r.ReadPacketType();
        if(pt==PacketType.SSH_MSG_IGNORE) {
          if(_eventReceiver!=null) _eventReceiver.OnIgnoreMessage(r.ReadString());
        }
        else if(pt==PacketType.SSH_MSG_DEBUG) {
          bool f = r.ReadBool();
          if(_eventReceiver!=null) _eventReceiver.OnDebugMessage(f, r.ReadString());
        }
        else
          return p;
      }
    }
    internal void AsyncReceivePacket(SSH2Packet packet) {
      try {
        ProcessPacket(packet);
      }
      catch(Exception ex) {
        //Debug.WriteLine(ex.StackTrace);
        if(!_closed)
          _eventReceiver.OnError(ex, ex.Message);
      }
    }

    private bool ProcessPacket(SSH2Packet packet) {
      SSH2DataReader r = new SSH2DataReader(packet.Data);
      PacketType pt = r.ReadPacketType();
      //Debug.WriteLine("ProcessPacket pt="+pt);

      if(pt==PacketType.SSH_MSG_DISCONNECT) {
        int errorcode = r.ReadInt32();
        //string description = Encoding.ASCII.GetString(r.ReadString());
        _eventReceiver.OnConnectionClosed();
        return false;
      }
      else if(_waitingForPortForwardingResponse) {
        if(pt!=PacketType.SSH_MSG_REQUEST_SUCCESS)
          _eventReceiver.OnUnknownMessage((byte)pt, r.Image);
        _waitingForPortForwardingResponse = false;
        return true;
      }
      else if(pt==PacketType.SSH_MSG_CHANNEL_OPEN) {
        ProcessPortforwardingRequest(_eventReceiver, r);
        return true;
      }
      else if(pt>=PacketType.SSH_MSG_CHANNEL_OPEN_CONFIRMATION && pt<=PacketType.SSH_MSG_CHANNEL_FAILURE) {
        int local_channel = r.ReadInt32();
        ChannelEntry e = FindChannelEntry(local_channel);
        if(e!=null) //throw new SSHException("Unknown channel "+local_channel);
          ((SSH2Channel)e._channel).ProcessPacket(e._receiver, pt, 5+r.Rest, r);
        else
          Debug.WriteLine("unexpected channel pt="+pt+" local_channel="+local_channel.ToString());
        return true;
      }
      else if(pt==PacketType.SSH_MSG_IGNORE) {
        _eventReceiver.OnIgnoreMessage(r.ReadString());
        return true;
      }
      else if(_asyncKeyExchanger!=null) {
        _asyncKeyExchanger.AsyncProcessPacket(packet);
        return true;
      }
      else if(pt==PacketType.SSH_MSG_KEXINIT) {
        Debug.WriteLine("Host sent KEXINIT");
        _asyncKeyExchanger = new KeyExchanger(this, _sessionID);
        _asyncKeyExchanger.AsyncProcessPacket(packet);
        return true;
      }
      else {
        _eventReceiver.OnUnknownMessage((byte)pt, r.Image);
        return false;
      }
    }

    public override void Disconnect(string msg) {
      if(_closed) return;
      SSH2DataWriter wr = new SSH2DataWriter();
      wr.WritePacketType(PacketType.SSH_MSG_DISCONNECT);
      wr.Write(0);
      wr.Write(msg);
      wr.Write(""); //language
      TransmitPacket(wr.ToByteArray());
      _stream.Flush();
      _closed = true;
      _stream.Close();
    }

    public override void Close() {
      if(_closed) return;
      _closed = true;
      _stream.Close();
    } 
    public override void SendIgnorableData(string msg) {
      SSH2DataWriter w = new SSH2DataWriter();
      w.WritePacketType(PacketType.SSH_MSG_IGNORE);
      w.Write(msg);
      TransmitPacket(w.ToByteArray());
    }
    public void ReexchangeKeys() {
      _asyncKeyExchanger = new KeyExchanger(this, _sessionID);
      _asyncKeyExchanger.AsyncStartReexchange();
    }

    internal void LockCommunication() {
      _packetBuilder.SetSignal(false);
    }
    internal void UnlockCommunication() {
      _packetBuilder.SetSignal(true);
    }
    internal void RefreshKeys(byte[] sessionID, Cipher tc, Cipher rc, MAC tm, MAC rm) {
      _sessionID = sessionID;
      _tCipher = tc;
      _tMAC = tm;
      _packetBuilder.SetCipher(rc, _param.CheckMACError? rm : null);
      _asyncKeyExchanger = null;
    }

  }

  public class SSH2Channel : SSHChannel {
    //channel property
    protected int _windowSize;
    protected int _leftWindowSize;
    protected int _serverMaxPacketSize;

    //negotiation status
    protected int _negotiationStatus;

    public SSH2Channel(SSHConnection con, ChannelType type, int local_id) : base(con, type, local_id) {
      _windowSize = _leftWindowSize = con.Param.WindowSize;
      _negotiationStatus = type==ChannelType.Shell? 3 : type==ChannelType.ForwardedLocalToRemote? 1 : type==ChannelType.Session? 1 : 0;
    }
    public SSH2Channel(SSHConnection con, ChannelType type, int local_id, int remote_id, int maxpacketsize) : base(con, type, local_id) {
      _windowSize = _leftWindowSize = con.Param.WindowSize;
      Debug.Assert(type==ChannelType.ForwardedRemoteToLocal);
      _remoteID = remote_id;
      _serverMaxPacketSize = maxpacketsize;
      _negotiationStatus = 0;
    }

    public override void ResizeTerminal(int width, int height, int pixel_width, int pixel_height) {
      SSH2DataWriter wr = new SSH2DataWriter();
      wr.WritePacketType(PacketType.SSH_MSG_CHANNEL_REQUEST);
      wr.Write(_remoteID);
      wr.Write("window-change");
      wr.Write(false);
      wr.Write(width);
      wr.Write(height);
      wr.Write(pixel_width); //no graphics
      wr.Write(pixel_height);
      TransmitPacket(wr.ToByteArray());
    }
    public override void Transmit(byte[] data)  {
      //!!it is better idea that we wait a WINDOW_ADJUST if the left size is lack
      SSH2DataWriter wr = new SSH2DataWriter();
      wr.WritePacketType(PacketType.SSH_MSG_CHANNEL_DATA);
      wr.Write(_remoteID);
      wr.WriteAsString(data);

      TransmitPacket(wr.ToByteArray());
    }
    public override void Transmit(byte[] data, int offset, int length)  {
      SSH2DataWriter wr = new SSH2DataWriter();
      wr.WritePacketType(PacketType.SSH_MSG_CHANNEL_DATA);
      wr.Write(_remoteID);
      wr.WriteAsString(data, offset, length);

      TransmitPacket(wr.ToByteArray());
    }

    public override void SendEOF() {
      if(_connection.IsClosed) return;
      SSH2DataWriter wr = new SSH2DataWriter();
      wr.WritePacketType(PacketType.SSH_MSG_CHANNEL_EOF);
      wr.Write(_remoteID);
      TransmitPacket(wr.ToByteArray());
    }


    public override void Close() {
      if(_connection.IsClosed) return;
      SSH2DataWriter wr = new SSH2DataWriter();
      wr.WritePacketType(PacketType.SSH_MSG_CHANNEL_CLOSE);
      wr.Write(_remoteID);
      TransmitPacket(wr.ToByteArray());

    }

    //maybe this is SSH2 only feature
    public void SetEnvironmentVariable(string name, string value) {
      SSH2DataWriter wr = new SSH2DataWriter();
      wr.WritePacketType(PacketType.SSH_MSG_CHANNEL_REQUEST);
      wr.Write(_remoteID);
      wr.Write("env");
      wr.Write(false);
      wr.Write(name);
      wr.Write(value);
      TransmitPacket(wr.ToByteArray());
    }
    public void SendBreak(int time) {
      SSH2DataWriter wr = new SSH2DataWriter();
      wr.WritePacketType(PacketType.SSH_MSG_CHANNEL_REQUEST);
      wr.Write(_remoteID);
      wr.Write("break");
      wr.Write(true);
      wr.Write(time);
      TransmitPacket(wr.ToByteArray());
    }

    internal void ProcessPacket(ISSHChannelEventReceiver receiver, PacketType pt, int data_length, SSH2DataReader re) {

      //NOTE: the offset of 're' is next to 'receipiant channel' field
      _leftWindowSize -= data_length;
      while(_leftWindowSize <= _windowSize) {
        SSH2DataWriter adj = new SSH2DataWriter();
        adj.WritePacketType(PacketType.SSH_MSG_CHANNEL_WINDOW_ADJUST);
        adj.Write(_remoteID);
        adj.Write(_windowSize);
        TransmitPacket(adj.ToByteArray());
        _leftWindowSize += _windowSize;
        //Debug.WriteLine("Window size is adjusted to " + _leftWindowSize);
      }

      if(pt==PacketType.SSH_MSG_CHANNEL_WINDOW_ADJUST) {
        int w = re.ReadInt32();
        //Debug.WriteLine(String.Format("Window Adjust +={0}",w));
      }
      else if(_negotiationStatus!=0) { //when the negotiation is not completed
        if(_type==ChannelType.Shell)
          OpenShell(receiver, pt, re);
        else if(_type==ChannelType.ForwardedLocalToRemote)
          ReceivePortForwardingResponse(receiver, pt, re);
        else if(_type==ChannelType.Session)
          EstablishSession(receiver, pt, re);
      }
      else {
        switch(pt) {
          case PacketType.SSH_MSG_CHANNEL_DATA: {
            int len = re.ReadInt32();
            receiver.OnData(re.Image, re.Offset, len);
          }
            break;
          case PacketType.SSH_MSG_CHANNEL_EXTENDED_DATA: {
            int t = re.ReadInt32();
            byte[] data = re.ReadString();
            receiver.OnExtendedData(t, data);
          }
            break;
          case PacketType.SSH_MSG_CHANNEL_REQUEST: {
            string request = Encoding.ASCII.GetString(re.ReadString());
            bool reply = re.ReadBool();
            if(request=="exit-status") {
              int status = re.ReadInt32();
            }
            else if(reply) { //we reject unknown requests including keep-alive check
              SSH2DataWriter wr = new SSH2DataWriter();
              wr.Write((byte)PacketType.SSH_MSG_CHANNEL_FAILURE);
              wr.Write(_remoteID);
              TransmitPacket(wr.ToByteArray());
            }
          }
            break;
          case PacketType.SSH_MSG_CHANNEL_EOF:
            receiver.OnChannelEOF();
            break;
          case PacketType.SSH_MSG_CHANNEL_CLOSE:
            _connection.UnregisterChannelEventReceiver(_localID);
            receiver.OnChannelClosed();
            break;
          case PacketType.SSH_MSG_CHANNEL_FAILURE:
          case PacketType.SSH_MSG_CHANNEL_SUCCESS:
            receiver.OnMiscPacket((byte)pt, re.Image, re.Offset, re.Rest);
            break;
          default:
            receiver.OnMiscPacket((byte)pt, re.Image, re.Offset, re.Rest);
            Debug.WriteLine("Unknown Packet "+pt);
            break;
        }      

      }

    }

    private void TransmitPacket(byte[] data) {
      ((SSH2Connection)_connection).TransmitPacket(data);
    }

    private void OpenShell(ISSHChannelEventReceiver receiver, PacketType pt, SSH2DataReader reader) {
      if(_negotiationStatus==3) {
        if(pt!=PacketType.SSH_MSG_CHANNEL_OPEN_CONFIRMATION) {
          if(pt!=PacketType.SSH_MSG_CHANNEL_OPEN_FAILURE)
            receiver.OnChannelError(null, "opening channel failed; packet type="+pt);
          else {
            int errcode = reader.ReadInt32();
            string msg = Encoding.ASCII.GetString(reader.ReadString());
            receiver.OnChannelError(null, msg);
          }
          Close();
        }
        else {
          _remoteID = reader.ReadInt32();
          _serverMaxPacketSize = reader.ReadInt32();
        
          //open pty
          SSH2DataWriter wr = new SSH2DataWriter();
          wr.WritePacketType(PacketType.SSH_MSG_CHANNEL_REQUEST);
          wr.Write(_remoteID);
          wr.Write("pty-req");
          wr.Write(true);
          wr.Write(_connection.Param.TerminalName);
          wr.Write(_connection.Param.TerminalWidth);
          wr.Write(_connection.Param.TerminalHeight);
          wr.Write(_connection.Param.TerminalPixelWidth);
          wr.Write(_connection.Param.TerminalPixelHeight);
          wr.WriteAsString(new byte[0]);
          TransmitPacket(wr.ToByteArray());

          _negotiationStatus = 2;
        }
      }
      else if(_negotiationStatus==2) {
        if(pt!=PacketType.SSH_MSG_CHANNEL_SUCCESS) {
          receiver.OnChannelError(null, "opening pty failed");
          Close();
        }
        else {
          //open shell
          SSH2DataWriter wr = new SSH2DataWriter();
          wr.Write((byte)PacketType.SSH_MSG_CHANNEL_REQUEST);
          wr.Write(_remoteID);
          wr.Write("shell");
          wr.Write(true);
          TransmitPacket(wr.ToByteArray());

          _negotiationStatus = 1;
        }
      }
      else if(_negotiationStatus==1) {
        if(pt!=PacketType.SSH_MSG_CHANNEL_SUCCESS) {
          receiver.OnChannelError(null, "Opening shell failed: packet type="+pt.ToString());
          Close();
        }
        else {
          receiver.OnChannelReady();
          _negotiationStatus = 0; //goal!
        }
      }
      else
        Debug.Assert(false);
    }

    private void ReceivePortForwardingResponse(ISSHChannelEventReceiver receiver, PacketType pt, SSH2DataReader reader) {
      if(_negotiationStatus==1) {
        if(pt!=PacketType.SSH_MSG_CHANNEL_OPEN_CONFIRMATION) {
          if(pt!=PacketType.SSH_MSG_CHANNEL_OPEN_FAILURE)
            receiver.OnChannelError(null, "opening channel failed; packet type="+pt);
          else {
            int errcode = reader.ReadInt32();
            string msg = Encoding.ASCII.GetString(reader.ReadString());
            receiver.OnChannelError(null, msg);
          }
          Close();
        }
        else {
          _remoteID = reader.ReadInt32();
          _serverMaxPacketSize = reader.ReadInt32();
          _negotiationStatus = 0;
          receiver.OnChannelReady();
        }
      }
      else
        Debug.Assert(false);
    }
    private void EstablishSession(ISSHChannelEventReceiver receiver, PacketType pt, SSH2DataReader reader) {
      if(_negotiationStatus==1) {
        if(pt!=PacketType.SSH_MSG_CHANNEL_OPEN_CONFIRMATION) {
          if(pt!=PacketType.SSH_MSG_CHANNEL_OPEN_FAILURE)
            receiver.OnChannelError(null, "opening channel failed; packet type="+pt);
          else {
            int remote_id = reader.ReadInt32();
            int errcode = reader.ReadInt32();
            string msg = Encoding.ASCII.GetString(reader.ReadString());
            receiver.OnChannelError(null, msg);
          }
          Close();
        }
        else {
          _remoteID = reader.ReadInt32();
          _serverMaxPacketSize = reader.ReadInt32();
          _negotiationStatus = 0;
          receiver.OnChannelReady();
        }
      }
      else
        Debug.Assert(false);
    }
  }

  internal class KeyExchanger {
    private SSH2Connection _con;
    private SSHConnectionParameter _param;
    private SSH2ConnectionInfo _cInfo;
    //payload of KEXINIT message
    private byte[] _serverKEXINITPayload;
    private byte[] _clientKEXINITPayload;

    //true if the host sent KEXINIT first
    private bool _startedByHost;

    private ManualResetEvent _newKeyEvent;

    //status
    private enum Status {
      INITIAL,
      WAIT_KEXINIT,
      WAIT_KEXDH_REPLY,
      WAIT_NEWKEYS,
      FINISHED
    }
    private Status _status;

    private BigInteger _x;
    private BigInteger _e;
    private BigInteger _k;
    private byte[] _hash;
    private byte[] _sessionID;

    //results
    Cipher _rc;
    Cipher _tc;
    MAC _rm;
    MAC _tm;

    private void TransmitPacket(byte[] payload) {
      _con.TransmitPacket(payload);
    }

    public KeyExchanger(SSH2Connection con, byte[] sessionID) {
      _con = con;
      _param = con.Param;
      _cInfo = (SSH2ConnectionInfo)con.ConnectionInfo;
      _sessionID = sessionID;
      _status = Status.INITIAL;
    }

    public bool SynchronousKexExchange() {
      SendKEXINIT();
      ProcessKEXINIT(_con.ReceivePacket());
      SendKEXDHINIT();
      if(!ProcessKEXDHREPLY(_con.ReceivePacket())) return false;
      SendNEWKEYS();
      ProcessNEWKEYS(_con.ReceivePacket());
      return true;
    }
    public void AsyncStartReexchange() {
      _startedByHost = false;
      _status = Status.WAIT_KEXINIT;
      SendKEXINIT();
    }
    public void AsyncProcessPacket(SSH2Packet packet) {
      switch(_status) {
        case Status.INITIAL:
          _startedByHost = true;
          ProcessKEXINIT(packet);
          SendKEXINIT();
          SendKEXDHINIT();
          break;
        case Status.WAIT_KEXINIT:
          ProcessKEXINIT(packet);
          SendKEXDHINIT();
          break;
        case Status.WAIT_KEXDH_REPLY:
          ProcessKEXDHREPLY(packet);
          SendNEWKEYS();
          break;
        case Status.WAIT_NEWKEYS:
          ProcessNEWKEYS(packet);
          Debug.Assert(_status==Status.FINISHED);
          break;
      }
    }
          
    private void SendKEXINIT() {
      SSH2DataWriter wr = new SSH2DataWriter();
      wr.WritePacketType(PacketType.SSH_MSG_KEXINIT);
      byte[] cookie = new byte[16];
      _param.Random.NextBytes(cookie);
      wr.Write(cookie);
      wr.Write("diffie-hellman-group1-sha1"); //    kex_algorithms
      wr.Write(FormatHostKeyAlgorithmDescription());            //    server_host_key_algorithms
      wr.Write(FormatCipherAlgorithmDescription());      //    encryption_algorithms_client_to_server
      wr.Write(FormatCipherAlgorithmDescription());      //    encryption_algorithms_server_to_client
      wr.Write("hmac-sha1");                  //    mac_algorithms_client_to_server
      wr.Write("hmac-sha1");                  //    mac_algorithms_server_to_client
      wr.Write("none");                       //    compression_algorithms_client_to_server
      wr.Write("none");                       //    compression_algorithms_server_to_client
      wr.Write("");                           //    languages_client_to_server
      wr.Write("");                           //    languages_server_to_client
      wr.Write(false); //Indicates whether a guessed key exchange packet follows
      wr.Write(0);       //reserved for future extension

      _clientKEXINITPayload = wr.ToByteArray();
      _status = Status.WAIT_KEXINIT;
      TransmitPacket(_clientKEXINITPayload);
    }
    private void ProcessKEXINIT(SSH2Packet packet) {
      _serverKEXINITPayload = packet.Data;
      SSH2DataReader re = new SSH2DataReader(_serverKEXINITPayload);
      byte[] head = re.Read(17); //Type and cookie
      if(head[0]!=(byte)PacketType.SSH_MSG_KEXINIT) throw new SSHException(String.Format("Server response is not SSH_MSG_KEXINIT but {0}", head[0]));
      Encoding enc = Encoding.ASCII;
      
      string kex = enc.GetString(re.ReadString());
      _cInfo._supportedKEXAlgorithms = kex;
      CheckAlgorithmSupport("keyexchange", kex, "diffie-hellman-group1-sha1");
      
      string host_key = enc.GetString(re.ReadString());
      _cInfo._supportedHostKeyAlgorithms = host_key;
      _cInfo._algorithmForHostKeyVerification = DecideHostKeyAlgorithm(host_key);
      
      string enc_cs = enc.GetString(re.ReadString());
      _cInfo._supportedCipherAlgorithms = enc_cs;
      _cInfo._algorithmForTransmittion = DecideCipherAlgorithm(enc_cs);
      
      string enc_sc = enc.GetString(re.ReadString());
      _cInfo._algorithmForReception = DecideCipherAlgorithm(enc_sc);

      string mac_cs = enc.GetString(re.ReadString());
      CheckAlgorithmSupport("mac", mac_cs, "hmac-sha1");
      
      string mac_sc = enc.GetString(re.ReadString());
      CheckAlgorithmSupport("mac", mac_sc, "hmac-sha1");
      
      string comp_cs = enc.GetString(re.ReadString());
      CheckAlgorithmSupport("compression", comp_cs, "none");
      string comp_sc = enc.GetString(re.ReadString());
      CheckAlgorithmSupport("compression", comp_sc, "none");
      
      string lang_cs = enc.GetString(re.ReadString());
      string lang_sc = enc.GetString(re.ReadString());
      bool flag = re.ReadBool();
      int reserved = re.ReadInt32();
      Debug.Assert(re.Rest==0);
      if(flag) throw new SSHException("Algorithm negotiation failed"); 
    }


    private void SendKEXDHINIT() {
      //Round1 computes and sends [e]
      byte[] sx = new byte[16];
      _param.Random.NextBytes(sx);
      _x = new BigInteger(sx);
      _e = new BigInteger(2).modPow(_x, DH_PRIME);
      SSH2DataWriter wr = new SSH2DataWriter();
      wr.WritePacketType(PacketType.SSH_MSG_KEXDH_INIT);
      wr.Write(_e);
      _status = Status.WAIT_KEXDH_REPLY;
      TransmitPacket(wr.ToByteArray());
    }
    private bool ProcessKEXDHREPLY(SSH2Packet packet) {
      //Round2 receives response
      SSH2DataReader re = new SSH2DataReader(packet.Data);
      PacketType h = re.ReadPacketType();
      if(h!=PacketType.SSH_MSG_KEXDH_REPLY) throw new SSHException(String.Format("KeyExchange response is not KEXDH_REPLY but {0}", h));
      byte[] key_and_cert = re.ReadString();
      BigInteger f = re.ReadMPInt();
      byte[] signature = re.ReadString();
      Debug.Assert(re.Rest==0);

      //Round3 calc hash H
      SSH2DataWriter wr = new SSH2DataWriter();
      _k = f.modPow(_x, DH_PRIME);
      wr = new SSH2DataWriter();
      wr.Write(_cInfo._clientVersionString);
      wr.Write(_cInfo._serverVersionString);
      wr.WriteAsString(_clientKEXINITPayload);
      wr.WriteAsString(_serverKEXINITPayload);
      wr.WriteAsString(key_and_cert);
      wr.Write(_e);
      wr.Write(f);
      wr.Write(_k);
      _hash = new SHA1CryptoServiceProvider().ComputeHash(wr.ToByteArray());

      if(!VerifyHostKey(key_and_cert, signature, _hash)) return false;

      //Debug.WriteLine("hash="+DebugUtil.DumpByteArray(hash));
      if(_sessionID==null) _sessionID = _hash;
      return true;
    }
    private void SendNEWKEYS() {
      _status = Status.WAIT_NEWKEYS;
      _newKeyEvent = new ManualResetEvent(false);
      TransmitPacket(new byte[1] {(byte)PacketType.SSH_MSG_NEWKEYS});

      //establish Ciphers
      _tc = CipherFactory.CreateCipher(SSHProtocol.SSH2, _cInfo._algorithmForTransmittion,
        DeriveKey(_k, _hash, 'C', CipherFactory.GetKeySize(_cInfo._algorithmForTransmittion)), DeriveKey(_k, _hash, 'A', CipherFactory.GetBlockSize(_cInfo._algorithmForTransmittion)));
      _rc = CipherFactory.CreateCipher(SSHProtocol.SSH2, _cInfo._algorithmForReception,
        DeriveKey(_k, _hash, 'D', CipherFactory.GetKeySize(_cInfo._algorithmForReception)), DeriveKey(_k, _hash, 'B', CipherFactory.GetBlockSize(_cInfo._algorithmForReception)));

      //establish MACs
      MACAlgorithm ma = MACAlgorithm.HMACSHA1;
      _tm = MACFactory.CreateMAC(MACAlgorithm.HMACSHA1, DeriveKey(_k, _hash, 'E', MACFactory.GetSize(ma)));
      _rm = MACFactory.CreateMAC(MACAlgorithm.HMACSHA1, DeriveKey(_k, _hash, 'F', MACFactory.GetSize(ma)));
      _newKeyEvent.Set();
    }
    private void ProcessNEWKEYS(SSH2Packet packet) {
      //confirms new key
      try {
        byte[] response = packet.Data;
        if(response.Length!=1 || response[0]!=(byte)PacketType.SSH_MSG_NEWKEYS) throw new SSHException("SSH_MSG_NEWKEYS failed");

        _newKeyEvent.WaitOne();
        _newKeyEvent.Close();
        _con.LockCommunication();
        _con.RefreshKeys(_sessionID, _tc, _rc, _tm, _rm);
        _status = Status.FINISHED;
      }
      finally {
        _con.UnlockCommunication();
      }
    }

    private bool VerifyHostKey(byte[] K_S, byte[] signature, byte[] hash) {
      SSH2DataReader re1 = new SSH2DataReader(K_S);
      string algorithm = Encoding.ASCII.GetString(re1.ReadString());
      if(algorithm!=SSH2Util.PublicKeyAlgorithmName(_cInfo._algorithmForHostKeyVerification))
        throw new SSHException("Protocol Error: Host Key Algorithm Mismatch");

      SSH2DataReader re2 = new SSH2DataReader(signature);
      algorithm = Encoding.ASCII.GetString(re2.ReadString());
      if(algorithm!=SSH2Util.PublicKeyAlgorithmName(_cInfo._algorithmForHostKeyVerification))
        throw new SSHException("Protocol Error: Host Key Algorithm Mismatch");
      byte[] sigbody = re2.ReadString();
      Debug.Assert(re2.Rest==0);

      if(_cInfo._algorithmForHostKeyVerification==PublicKeyAlgorithm.RSA)
        VerifyHostKeyByRSA(re1, sigbody, hash);
      else if(_cInfo._algorithmForHostKeyVerification==PublicKeyAlgorithm.DSA)
        VerifyHostKeyByDSS(re1, sigbody, hash);
      else
        throw new SSHException("Bad host key algorithm "+_cInfo._algorithmForHostKeyVerification);

      //ask the client whether he accepts the host key
      if(!_startedByHost && _param.KeyCheck!=null && !_param.KeyCheck(_cInfo))
        return false;
      else
        return true;
    }

    private void VerifyHostKeyByRSA(SSH2DataReader pubkey, byte[] sigbody, byte[] hash) {
      BigInteger exp = pubkey.ReadMPInt();
      BigInteger mod = pubkey.ReadMPInt();
      Debug.Assert(pubkey.Rest==0);

      //Debug.WriteLine(exp.ToHexString());
      //Debug.WriteLine(mod.ToHexString());

      RSAPublicKey pk = new RSAPublicKey(exp, mod);
      pk.VerifyWithSHA1(sigbody, new SHA1CryptoServiceProvider().ComputeHash(hash));
      _cInfo._hostkey = pk;
    }

    private void VerifyHostKeyByDSS(SSH2DataReader pubkey, byte[] sigbody, byte[] hash) {
      BigInteger p = pubkey.ReadMPInt();
      BigInteger q = pubkey.ReadMPInt();
      BigInteger g = pubkey.ReadMPInt();
      BigInteger y = pubkey.ReadMPInt();
      Debug.Assert(pubkey.Rest==0);

      //Debug.WriteLine(p.ToHexString());
      //Debug.WriteLine(q.ToHexString());
      //Debug.WriteLine(g.ToHexString());
      //Debug.WriteLine(y.ToHexString());


      DSAPublicKey pk = new DSAPublicKey(p,g,q,y);
      pk.Verify(sigbody, new SHA1CryptoServiceProvider().ComputeHash(hash));
      _cInfo._hostkey = pk;
    }

    private byte[] DeriveKey(BigInteger key, byte[] hash, char ch, int length) {
      byte[] result = new byte[length];

      SSH2DataWriter wr = new SSH2DataWriter();
      wr.Write(key);
      wr.Write(hash);
      wr.Write((byte)ch);
      wr.Write(_sessionID);
      byte[] h1 = new SHA1CryptoServiceProvider().ComputeHash(wr.ToByteArray());
      if(h1.Length >= length) {
        Array.Copy(h1, 0, result, 0, length);
        return result;
      }
      else {
        wr = new SSH2DataWriter();
        wr.Write(key);
        wr.Write(_sessionID);
        wr.Write(h1);
        byte[] h2 = new SHA1CryptoServiceProvider().ComputeHash(wr.ToByteArray());
        if(h1.Length+h2.Length >= length) {
          Array.Copy(h1, 0, result, 0, h1.Length);
          Array.Copy(h2, 0, result, h1.Length, length-h1.Length);
          return result;
        }
        else
          throw new SSHException("necessary key length is too big"); //long key is not supported
      }
    }

    private static void CheckAlgorithmSupport(string title, string data, string algorithm_name) {
      string[] t = data.Split(',');
      foreach(string s in t) {
        if(s==algorithm_name) return; //found!
      }
      throw new SSHException("Server does not support "+algorithm_name+" for "+title);
    }
    private PublicKeyAlgorithm DecideHostKeyAlgorithm(string data) {
      string[] t = data.Split(',');
      foreach(PublicKeyAlgorithm a in _param.PreferableHostKeyAlgorithms) {
        if(SSHUtil.ContainsString(t, SSH2Util.PublicKeyAlgorithmName(a))) {
          return a;
        }
      }
      throw new SSHException("The negotiation of host key verification algorithm is failed");
    }
    private CipherAlgorithm DecideCipherAlgorithm(string data) {
      string[] t = data.Split(',');
      foreach(CipherAlgorithm a in _param.PreferableCipherAlgorithms) {
        if(SSHUtil.ContainsString(t, CipherFactory.AlgorithmToSSH2Name(a))) {
          return a;
        }
      }
      throw new SSHException("The negotiation of encryption algorithm is failed");
    }
    private string FormatHostKeyAlgorithmDescription() {
      StringBuilder b = new StringBuilder();
      if(_param.PreferableHostKeyAlgorithms.Length==0) throw new SSHException("HostKeyAlgorithm is not set");
      b.Append(SSH2Util.PublicKeyAlgorithmName(_param.PreferableHostKeyAlgorithms[0]));
      for(int i=1; i<_param.PreferableHostKeyAlgorithms.Length; i++) {
        b.Append(',');
        b.Append(SSH2Util.PublicKeyAlgorithmName(_param.PreferableHostKeyAlgorithms[i]));
      }
      return b.ToString();
    }
    private string FormatCipherAlgorithmDescription() {
      StringBuilder b = new StringBuilder();
      if(_param.PreferableCipherAlgorithms.Length==0) throw new SSHException("CipherAlgorithm is not set");
      b.Append(CipherFactory.AlgorithmToSSH2Name(_param.PreferableCipherAlgorithms[0]));
      for(int i=1; i<_param.PreferableCipherAlgorithms.Length; i++) {
        b.Append(',');
        b.Append(CipherFactory.AlgorithmToSSH2Name(_param.PreferableCipherAlgorithms[i]));
      }
      return b.ToString();
    }

    /*
     * the seed of diffie-hellman KX defined in the spec of SSH2
     */ 
    private static BigInteger _dh_prime = null;
    private static BigInteger DH_PRIME {
      get {
        if(_dh_prime==null) {
          StringBuilder sb = new StringBuilder();
          sb.Append("FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1");
          sb.Append("29024E088A67CC74020BBEA63B139B22514A08798E3404DD");
          sb.Append("EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245");
          sb.Append("E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED");
          sb.Append("EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381");
          sb.Append("FFFFFFFFFFFFFFFF");
          _dh_prime = new BigInteger(sb.ToString(), 16);
        }
        return _dh_prime;
      }
    }

  }

}
www.java2v.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.