├── codeclean.sh ├── test ├── cppsshtestutil.h ├── CMakeLists.txt ├── cppsshtestutil.cpp ├── cppsshtestalgos.cpp ├── expectedResults │ ├── testoutput.txt │ └── testlog.txt ├── cppsshtestkeys.cpp └── testalgos.py ├── examples ├── CMakeLists.txt └── cppsshexample.cpp ├── README.md ├── include ├── export.h └── cppssh.h ├── src ├── transport.h ├── win │ ├── transportwin.h │ └── transportwin.cpp ├── debug.h ├── posix │ ├── transportposix.h │ └── transportposix.cpp ├── transportcrypto.h ├── x11channel.h ├── transportthreaded.h ├── CMakeLists.txt ├── kex.h ├── connection.h ├── session.h ├── impl.h ├── messages.h ├── transportimpl.h ├── subchannel.h ├── packet.h ├── keys.h ├── channel.h ├── transportcrypto.cpp ├── transportthreaded.cpp ├── crypto.h ├── x11channel.cpp ├── cppssh.cpp ├── cryptoalgos.h ├── impl.cpp ├── subchannel.cpp ├── transportimpl.cpp ├── packet.cpp ├── kex.cpp ├── connection.cpp ├── channel.cpp └── keys.cpp ├── CMakeLists.txt └── .gitignore /codeclean.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ~/Downloads/cppcheck-1.69/cppcheck -Iinclude/ -I../botan/install/include/botan-1.11/ -I../CDLogger/include/ --std=c++11 --enable=all --force . 2> err.txt 3 | call_Uncrustify.sh . cpp 4 | call_Uncrustify.sh . h 5 | cat err.txt 6 | -------------------------------------------------------------------------------- /test/cppsshtestutil.h: -------------------------------------------------------------------------------- 1 | #ifndef CPPSSH_TEST_UTIL_Hxx 2 | #define CPPSSH_TEST_UTIL_Hxx 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | void sendCmdList(int channel, const std::vector& cmdList, const int periodMs, std::ofstream& remoteOutput); 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include_directories ( ../include ${CMAKE_CURRENT_SOURCE_DIR}/../../install/include) 2 | add_definitions(-DCPPSSH_STATIC) 3 | add_executable(cppsshexample cppsshexample.cpp) 4 | target_link_libraries(cppsshexample cppssh) 5 | set_property(TARGET cppsshexample PROPERTY CXX_STANDARD 11) 6 | install(TARGETS cppsshexample DESTINATION bin) 7 | 8 | 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | To get cppssh and all dependant software: 2 | 3 | ``` 4 | repo init -u https://github.com/cdesjardins/cppsshManifest.git 5 | repo sync 6 | cd build 7 | [./]makebotan.py 8 | [./]build.py --CDLogger --cppssh 9 | ``` 10 | 11 | 12 | cppssh uses the Botan library, latest version
13 | https://github.com/randombit/botan
14 | http://botan.randombit.net/
15 | 16 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include_directories ( ../include ${CMAKE_CURRENT_SOURCE_DIR}/../../install/include) 2 | add_definitions(-DCPPSSH_STATIC) 3 | add_executable(cppsshtestalgos cppsshtestalgos.cpp cppsshtestutil.cpp) 4 | add_executable(cppsshtestkeys cppsshtestkeys.cpp cppsshtestutil.cpp) 5 | target_link_libraries(cppsshtestalgos cppssh) 6 | target_link_libraries(cppsshtestkeys cppssh) 7 | set_property(TARGET cppsshtestalgos PROPERTY CXX_STANDARD 11) 8 | set_property(TARGET cppsshtestkeys PROPERTY CXX_STANDARD 11) 9 | install(TARGETS cppsshtestalgos cppsshtestkeys DESTINATION bin) 10 | 11 | 12 | -------------------------------------------------------------------------------- /test/cppsshtestutil.cpp: -------------------------------------------------------------------------------- 1 | #include "cppsshtestutil.h" 2 | #include "cppssh.h" 3 | #include 4 | 5 | void sendCmdList(int channel, const std::vector& cmdList, const int periodMs, std::ofstream& remoteOutput) 6 | { 7 | std::chrono::steady_clock::time_point txTime = std::chrono::steady_clock::now(); 8 | size_t txIndex = 0; 9 | 10 | while ((Cppssh::isConnected(channel) == true) && 11 | (std::chrono::steady_clock::now() < (txTime + std::chrono::seconds(1)))) 12 | { 13 | CppsshMessage message; 14 | if (Cppssh::read(channel, &message) == true) 15 | { 16 | remoteOutput << message.message(); 17 | } 18 | 19 | if (std::chrono::steady_clock::now() > (txTime + std::chrono::milliseconds(periodMs)) && 20 | (txIndex < cmdList.size())) 21 | { 22 | Cppssh::writeString(channel, cmdList[txIndex].c_str()); 23 | txTime = std::chrono::steady_clock::now(); 24 | txIndex++; 25 | } 26 | std::this_thread::sleep_for(std::chrono::milliseconds(1)); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /include/export.h: -------------------------------------------------------------------------------- 1 | /* 2 | cppssh - C++ ssh library 3 | Copyright (C) 2015 Chris Desjardins 4 | http://blog.chrisd.info cjd@chrisd.info 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | */ 19 | #ifndef _EXPORT_Hxx 20 | #define _EXPORT_Hxx 21 | 22 | #if defined(WIN32) || defined(__MINGW32) 23 | #ifdef CPPSSH_EXPORTS 24 | #define CPPSSH_EXPORT __declspec(dllexport) 25 | #else 26 | #ifndef CPPSSH_STATIC 27 | #define CPPSSH_EXPORT __declspec(dllimport) 28 | #endif 29 | #endif 30 | #endif 31 | 32 | #ifndef CPPSSH_EXPORT 33 | #define CPPSSH_EXPORT 34 | #endif 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /src/transport.h: -------------------------------------------------------------------------------- 1 | /* 2 | cppssh - C++ ssh library 3 | Copyright (C) 2015 Chris Desjardins 4 | http://blog.chrisd.info cjd@chrisd.info 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | */ 19 | #ifndef _TRANSPORT_Hxx 20 | #define _TRANSPORT_Hxx 21 | 22 | #ifdef WIN32 23 | class CppsshTransportWin; 24 | typedef class CppsshTransportWin CppsshTransport; 25 | #include 26 | #else 27 | class CppsshTransportPosix; 28 | typedef class CppsshTransportPosix CppsshTransport; 29 | #define SOCKET int 30 | #endif 31 | 32 | #include "transportimpl.h" 33 | 34 | #ifdef WIN32 35 | #include "win/transportwin.h" 36 | #else 37 | #include "posix/transportposix.h" 38 | #endif 39 | 40 | #endif 41 | -------------------------------------------------------------------------------- /src/win/transportwin.h: -------------------------------------------------------------------------------- 1 | /* 2 | cppssh - C++ ssh library 3 | Copyright (C) 2015 Chris Desjardins 4 | http://blog.chrisd.info cjd@chrisd.info 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | */ 19 | #ifndef _TRANSPORT_WIN_Hxx 20 | #define _TRANSPORT_WIN_Hxx 21 | 22 | /* 23 | ** Note: Do not include this file directly, include transport.h instead 24 | */ 25 | 26 | class CppsshTransportWin : public CppsshTransportImpl 27 | { 28 | public: 29 | CppsshTransportWin(const std::shared_ptr& session) 30 | : CppsshTransportImpl(session) 31 | { 32 | } 33 | 34 | protected: 35 | virtual bool isConnectInProgress(); 36 | virtual bool establishLocalX11(const std::string& display); 37 | virtual bool setNonBlocking(bool on); 38 | 39 | private: 40 | }; 41 | 42 | #endif 43 | -------------------------------------------------------------------------------- /src/debug.h: -------------------------------------------------------------------------------- 1 | /* 2 | cppssh - C++ ssh library 3 | Copyright (C) 2015 Chris Desjardins 4 | http://blog.chrisd.info cjd@chrisd.info 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | */ 19 | #ifndef _DEBUG_Hxx 20 | #define _DEBUG_Hxx 21 | 22 | #ifndef WIN32 23 | #include 24 | #endif 25 | 26 | class CppsshDebug 27 | { 28 | public: 29 | static void dumpStack(int connectionId) 30 | { 31 | cdLog(LogLevel::Debug) << "dumpStack[" << connectionId << "]"; 32 | #ifdef WIN32 33 | #else 34 | void* buffer[100]; 35 | int size; 36 | size = backtrace(buffer, sizeof(buffer) / sizeof(buffer[0])); 37 | char** stack = backtrace_symbols(buffer, size); 38 | for (int i = 0; i < size; i++) 39 | { 40 | cdLog(LogLevel::Debug) << stack[i]; 41 | } 42 | #endif 43 | } 44 | }; 45 | 46 | #endif 47 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.1) 2 | project(cppssh CXX) 3 | 4 | set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/../include") 5 | include(compilerflags) 6 | set(CMAKE_INSTALL_PREFIX "${CMAKE_SOURCE_DIR}/../install") 7 | set(BOTAN_BASE_DIR "${CMAKE_SOURCE_DIR}/../botan/install") 8 | 9 | find_path(HAVE_BOTAN "botan-2" PATHS "${BOTAN_BASE_DIR}/include/") 10 | if (NOT HAVE_BOTAN) 11 | MESSAGE(FATAL_ERROR "Need to run the makebotan.py script, could not find Botan includes") 12 | endif() 13 | set(HAVE_BOTAN "${HAVE_BOTAN}/botan-2") 14 | 15 | find_library(HAVE_BOTAN_DBG_LIB NAMES botand botan botan-1.11 botan-2 PATHS "${BOTAN_BASE_DIR}/lib/botan" "${BOTAN_BASE_DIR}/lib/botan/debug") 16 | find_library(HAVE_BOTAN_LIB NAMES botan botan-1.11 botan-2 PATHS "${BOTAN_BASE_DIR}/lib/botan" "${BOTAN_BASE_DIR}/lib/botan/release") 17 | if ((NOT HAVE_BOTAN_LIB) OR (NOT HAVE_BOTAN_DBG_LIB)) 18 | MESSAGE(FATAL_ERROR "Need to run the makebotan.py script, could not find Botan library") 19 | endif() 20 | 21 | find_library(HAVE_CDLOGGER_DBG_LIB NAMES CDLoggerd PATHS "${CMAKE_INSTALL_PREFIX}/lib") 22 | find_library(HAVE_CDLOGGER_LIB NAMES CDLogger PATHS "${CMAKE_INSTALL_PREFIX}/lib") 23 | if ((NOT HAVE_CDLOGGER_LIB) AND (NOT HAVE_CDLOGGER_DBG_LIB)) 24 | MESSAGE(FATAL_ERROR "Could not find CDLogger library") 25 | endif() 26 | 27 | file(GLOB cppssh_INCLUDES "include/*.h") 28 | install(FILES ${cppssh_INCLUDES} DESTINATION include/cppssh/) 29 | 30 | add_subdirectory (src) 31 | add_subdirectory (examples) 32 | add_subdirectory (test) 33 | ########### install files ############### 34 | 35 | -------------------------------------------------------------------------------- /src/posix/transportposix.h: -------------------------------------------------------------------------------- 1 | /* 2 | cppssh - C++ ssh library 3 | Copyright (C) 2015 Chris Desjardins 4 | http://blog.chrisd.info cjd@chrisd.info 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | */ 19 | #ifndef _TRANSPORT_POSIX_Hxx 20 | #define _TRANSPORT_POSIX_Hxx 21 | 22 | /* 23 | ** Note: Do not include this file directly, include transport.h instead 24 | */ 25 | 26 | #include 27 | 28 | class CppsshSession; 29 | 30 | class CppsshTransportPosix : public CppsshTransportImpl 31 | { 32 | public: 33 | CppsshTransportPosix(const std::shared_ptr& session) 34 | : CppsshTransportImpl(session) 35 | { 36 | } 37 | 38 | virtual ~CppsshTransportPosix() 39 | { 40 | } 41 | 42 | protected: 43 | virtual bool isConnectInProgress(); 44 | virtual bool establishLocalX11(const std::string& display); 45 | virtual bool setNonBlocking(bool on); 46 | 47 | private: 48 | }; 49 | 50 | #endif 51 | -------------------------------------------------------------------------------- /src/transportcrypto.h: -------------------------------------------------------------------------------- 1 | /* 2 | cppssh - C++ ssh library 3 | Copyright (C) 2015 Chris Desjardins 4 | http://blog.chrisd.info cjd@chrisd.info 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | */ 19 | #ifndef _TRANSPORT_CRYPTO_Hxx 20 | #define _TRANSPORT_CRYPTO_Hxx 21 | 22 | #include "transportthreaded.h" 23 | 24 | class CppsshTransportCrypto : public CppsshTransportThreaded 25 | { 26 | public: 27 | CppsshTransportCrypto() = delete; 28 | CppsshTransportCrypto(const std::shared_ptr& session, SOCKET sock); 29 | virtual ~CppsshTransportCrypto(); 30 | 31 | protected: 32 | virtual bool sendMessage(const Botan::secure_vector& buffer); 33 | bool computeMac(const Botan::secure_vector& packet, uint32_t* cryptoLen); 34 | 35 | private: 36 | virtual void rxThread(); 37 | 38 | uint32_t _txSeq; 39 | uint32_t _rxSeq; 40 | Botan::secure_vector _in; 41 | }; 42 | 43 | #endif 44 | -------------------------------------------------------------------------------- /src/x11channel.h: -------------------------------------------------------------------------------- 1 | /* 2 | cppssh - C++ ssh library 3 | Copyright (C) 2015 Chris Desjardins 4 | http://blog.chrisd.info cjd@chrisd.info 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | */ 19 | #ifndef _X11_CHANNEL_Hxx 20 | #define _X11_CHANNEL_Hxx 21 | 22 | #include "channel.h" 23 | #include "subchannel.h" 24 | #include 25 | 26 | class CppsshX11Channel : public CppsshSubChannel 27 | { 28 | public: 29 | CppsshX11Channel(const std::shared_ptr& session, const std::string& channelName); 30 | CppsshX11Channel() = delete; 31 | CppsshX11Channel(const CppsshX11Channel&) = delete; 32 | ~CppsshX11Channel(); 33 | virtual bool startChannel(); 34 | static void getDisplay(std::string* display); 35 | static bool runXauth(const std::string& display, std::string* method, Botan::secure_vector* cookie); 36 | 37 | protected: 38 | void disconnect(); 39 | void x11RxThread(); 40 | void x11TxThread(); 41 | std::unique_ptr _x11transport; 42 | 43 | std::thread _x11RxThread; 44 | std::thread _x11TxThread; 45 | private: 46 | }; 47 | 48 | #endif 49 | -------------------------------------------------------------------------------- /src/transportthreaded.h: -------------------------------------------------------------------------------- 1 | /* 2 | cppssh - C++ ssh library 3 | Copyright (C) 2015 Chris Desjardins 4 | http://blog.chrisd.info cjd@chrisd.info 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | */ 19 | #ifndef _TRANSPORT_THREADED_Hxx 20 | #define _TRANSPORT_THREADED_Hxx 21 | 22 | #include "transport.h" 23 | #include 24 | 25 | class CppsshTransportThreaded : public CppsshTransport 26 | { 27 | public: 28 | CppsshTransportThreaded(const std::shared_ptr& session); 29 | virtual ~CppsshTransportThreaded(); 30 | bool startThreads() override; 31 | virtual bool sendMessage(const Botan::secure_vector& buffer); 32 | 33 | protected: 34 | bool processIncomingData(Botan::secure_vector* inBuf, const Botan::secure_vector& incoming, uint32_t dataLen) const; 35 | bool setupMessage(const Botan::secure_vector& buffer, Botan::secure_vector* outBuf); 36 | void stopThreads(); 37 | 38 | virtual void rxThread(); 39 | virtual void txThread(); 40 | 41 | std::thread _rxThread; 42 | std::thread _txThread; 43 | }; 44 | 45 | #endif 46 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | file(GLOB cppssh_LIB_SRCS 2 | ${SRCS} 3 | "*.h" 4 | "*.cpp" 5 | "../include/*.h" 6 | ) 7 | 8 | if(${CMAKE_SYSTEM_NAME} MATCHES "Windows") 9 | file(GLOB platform_LIB_SRCS 10 | "win/*.h" 11 | "win/*.cpp") 12 | else() 13 | file(GLOB platform_LIB_SRCS 14 | "posix/*.h" 15 | "posix/*.cpp") 16 | endif() 17 | 18 | 19 | include_directories (${HAVE_BOTAN} ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/../include ${CMAKE_CURRENT_SOURCE_DIR}/../../install/include ${CMAKE_CURRENT_SOURCE_DIR}/../../include) 20 | 21 | find_file(HAVE_GIT git) 22 | if (HAVE_GIT) 23 | exec_program( 24 | "git" 25 | ${CMAKE_CURRENT_SOURCE_DIR} 26 | ARGS "describe --dirty --always" 27 | OUTPUT_VARIABLE FULL_VERSION ) 28 | string(FIND ${FULL_VERSION} "-" index) 29 | string(SUBSTRING ${FULL_VERSION} 0 ${index} SHORT_VERSION) 30 | else() 31 | set(FULL_VERSION "0.x") 32 | set(SHORT_VERSION "0.x") 33 | endif() 34 | message(STATUS "Using version numbers: ${FULL_VERSION} and ${SHORT_VERSION}") 35 | 36 | add_definitions(-DCPPSSH_EXPORTS -DCPPSSH_FULL_VERSION="${FULL_VERSION}" -DCPPSSH_SHORT_VERSION="${SHORT_VERSION}") 37 | add_library(cppssh STATIC ${cppssh_LIB_SRCS} ${platform_LIB_SRCS}) 38 | if(MSVC) 39 | target_link_libraries(cppssh ws2_32) 40 | endif() 41 | if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") 42 | target_link_libraries(cppssh rt pthread) 43 | endif() 44 | target_link_libraries(cppssh optimized ${HAVE_BOTAN_LIB} debug ${HAVE_BOTAN_DBG_LIB}) 45 | 46 | if (HAVE_CDLOGGER_LIB) 47 | target_link_libraries(cppssh optimized ${HAVE_CDLOGGER_LIB}) 48 | endif() 49 | if (HAVE_CDLOGGER_DBG_LIB) 50 | target_link_libraries(cppssh debug ${HAVE_CDLOGGER_DBG_LIB}) 51 | endif() 52 | 53 | include(deffilename) 54 | define_file_basename_for_sources(cppssh) 55 | set_property(TARGET cppssh PROPERTY CXX_STANDARD 11) 56 | install(TARGETS cppssh DESTINATION lib) 57 | 58 | -------------------------------------------------------------------------------- /src/kex.h: -------------------------------------------------------------------------------- 1 | /* 2 | cppssh - C++ ssh library 3 | Copyright (C) 2015 Chris Desjardins 4 | http://blog.chrisd.info cjd@chrisd.info 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | */ 19 | #ifndef _KEX_Hxx 20 | #define _KEX_Hxx 21 | 22 | #include "session.h" 23 | #include "packet.h" 24 | #include "cryptoalgos.h" 25 | #include 26 | 27 | class CppsshKex 28 | { 29 | public: 30 | CppsshKex(const std::shared_ptr& session); 31 | bool handleInit(); 32 | bool handleKexDHReply(); 33 | bool sendKexNewKeys(); 34 | 35 | private: 36 | bool sendInit(Botan::secure_vector& packet); 37 | bool sendKexDHInit(Botan::secure_vector& packet); 38 | void constructLocalKex(); 39 | void makeH(Botan::secure_vector* hVector); 40 | template T runAgreement(const CppsshConstPacket& remoteKexAlgosPacket, const CppsshAlgos& algorithms, const std::string& tag) const; 41 | 42 | std::shared_ptr _session; 43 | Botan::secure_vector _localKex; 44 | Botan::secure_vector _remoteKex; 45 | Botan::secure_vector _hostKey; 46 | Botan::secure_vector _e; 47 | Botan::secure_vector _f; 48 | Botan::secure_vector _k; 49 | }; 50 | 51 | #endif 52 | -------------------------------------------------------------------------------- /src/connection.h: -------------------------------------------------------------------------------- 1 | /* 2 | cppssh - C++ ssh library 3 | Copyright (C) 2015 Chris Desjardins 4 | http://blog.chrisd.info cjd@chrisd.info 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | */ 19 | #ifndef _CONNECTION_Hxx 20 | #define _CONNECTION_Hxx 21 | 22 | #include "session.h" 23 | #include "channel.h" 24 | #include "cppssh.h" 25 | #include 26 | 27 | class CppsshConnection 28 | { 29 | public: 30 | CppsshConnection(int connectionId, unsigned int timeout); 31 | ~CppsshConnection(); 32 | CppsshConnectStatus_t connect(const char* host, const short port, const char* username, const char* privKeyFile, const char* password, const bool x11Forwarded, const bool keepAlives, const char* term); 33 | 34 | bool write(const uint8_t* data, uint32_t bytes); 35 | bool read(CppsshMessage* data); 36 | bool windowChange(const uint32_t cols, const uint32_t rows); 37 | bool isConnected(); 38 | bool closeConnection(); 39 | private: 40 | bool checkRemoteVersion(); 41 | bool sendLocalVersion(); 42 | bool requestService(const std::string& service); 43 | bool authWithPassword(const std::string& username, const std::string& password); 44 | bool authWithKey(const std::string& username, const std::string& privKeyFileName, const char* keyPassword); 45 | bool authenticate(const Botan::secure_vector& userAuthRequest); 46 | 47 | std::shared_ptr _session; 48 | bool _connected; 49 | }; 50 | 51 | #endif 52 | -------------------------------------------------------------------------------- /test/cppsshtestalgos.cpp: -------------------------------------------------------------------------------- 1 | #include "cppsshtestutil.h" 2 | #include "cppssh.h" 3 | #include "CDLogger/Logger.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | void runConnectionTest(const std::string& hostname, const std::string& username, const std::string& password, 12 | const char* keyfile) 13 | { 14 | int channel; 15 | if (Cppssh::connect(&channel, hostname.c_str(), 22, username.c_str(), keyfile, password.c_str(), 16 | 10000) == CPPSSH_CONNECT_OK) 17 | { 18 | std::vector cmdList {"env\n", "mkdir cppsshTestDir\n", "ls -l cppsshTestDir\n", 19 | "rmdir cppsshTestDir\n"}; 20 | std::ofstream remoteOutput; 21 | remoteOutput.open("testoutput.txt"); 22 | if (!remoteOutput) 23 | { 24 | cdLog(LogLevel::Error) << "Unable to open testoutput.txt"; 25 | } 26 | else 27 | { 28 | sendCmdList(channel, cmdList, 700, remoteOutput); 29 | remoteOutput.close(); 30 | } 31 | Cppssh::close(channel); 32 | } 33 | else 34 | { 35 | cdLog(LogLevel::Error) << "Did not connect " << channel; 36 | } 37 | } 38 | 39 | int main(int argc, char** argv) 40 | { 41 | if ((argc != 6) && (argc != 7)) 42 | { 43 | std::cerr << "Error: Five or Six arguments required: " << argv[0] << 44 | " " << std::endl; 45 | } 46 | else 47 | { 48 | Cppssh::create(); 49 | Logger::getLogger().addStream("testlog.txt"); 50 | //Logger::getLogger().addStream(std::shared_ptr(&std::cout, [](void*) {})); 51 | try 52 | { 53 | Logger::getLogger().setMinLogLevel(LogLevel::Debug); 54 | std::string hostname(argv[1]); 55 | std::string username(argv[2]); 56 | std::string password(argv[3]); 57 | std::string cipher(argv[4]); 58 | std::string hmac(argv[5]); 59 | 60 | Cppssh::setPreferredCipher(cipher.c_str()); 61 | Cppssh::setPreferredHmac(hmac.c_str()); 62 | 63 | char* keyfile = nullptr; 64 | if (argc == 7) 65 | { 66 | keyfile = argv[6]; 67 | } 68 | runConnectionTest(hostname, username, password, keyfile); 69 | } 70 | catch (const std::exception& ex) 71 | { 72 | cdLog(LogLevel::Error) << "Exception: " << ex.what() << std::endl; 73 | } 74 | Cppssh::destroy(); 75 | } 76 | return 0; 77 | } 78 | -------------------------------------------------------------------------------- /src/session.h: -------------------------------------------------------------------------------- 1 | /* 2 | cppssh - C++ ssh library 3 | Copyright (C) 2015 Chris Desjardins 4 | http://blog.chrisd.info cjd@chrisd.info 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | */ 19 | #ifndef _SESSION_Hxx 20 | #define _SESSION_Hxx 21 | 22 | #include "transport.h" 23 | #include "CDLogger/Logger.h" 24 | #include 25 | #include 26 | 27 | class CppsshCrypto; 28 | class CppsshChannel; 29 | 30 | #define CPPSSH_EXCEPTION "Exception: " << __FILENAME__ << "(" << __LINE__ << "): " << ex.what() 31 | 32 | class CppsshSession 33 | { 34 | public: 35 | CppsshSession(int connectionId, unsigned int timeout) 36 | : _timeout(timeout), 37 | _connectionId(connectionId) 38 | { 39 | } 40 | 41 | ~CppsshSession() 42 | { 43 | } 44 | 45 | void setRemoteVersion(const std::string& remoteVer) 46 | { 47 | _remoteVer = remoteVer; 48 | } 49 | 50 | const std::string& getRemoteVersion() const 51 | { 52 | return _remoteVer; 53 | } 54 | 55 | void setLocalVersion(const std::string& localVer) 56 | { 57 | _localVer = localVer; 58 | } 59 | 60 | const std::string& getLocalVersion() const 61 | { 62 | return _localVer; 63 | } 64 | 65 | void setSessionID(const Botan::secure_vector& session) 66 | { 67 | _sessionID = session; 68 | } 69 | 70 | const Botan::secure_vector& getSessionID() const 71 | { 72 | return _sessionID; 73 | } 74 | 75 | unsigned int getTimeout() const 76 | { 77 | return _timeout; 78 | } 79 | 80 | int getConnectionId() const 81 | { 82 | return _connectionId; 83 | } 84 | 85 | std::shared_ptr _transport; 86 | std::shared_ptr _crypto; 87 | std::shared_ptr _channel; 88 | private: 89 | std::string _remoteVer; 90 | std::string _localVer; 91 | Botan::secure_vector _sessionID; 92 | unsigned int _timeout; 93 | const int _connectionId; 94 | CppsshSession& operator=(const CppsshSession&) = delete; 95 | }; 96 | 97 | #endif 98 | -------------------------------------------------------------------------------- /test/expectedResults/testoutput.txt: -------------------------------------------------------------------------------- 1 | Debian GNU/Linux 8 2 | rcn-ee.net console Debian Image 2015-09-11 3 | Support/FAQ: http://elinux.org/BeagleBoardDebian 4 | default username:password is [debian:temppwd] 5 | 6 | The programs included with the Debian GNU/Linux system are free software; 7 | the exact distribution terms for each program are described in the 8 | individual files in /usr/share/doc/*/copyright. 9 | 10 | Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent 11 | permitted by applicable law. 12 | Last login: Sat Oct 3 13:21:20 2015 from 192.168.1.16 13 | ]0;algotester@arm: ~algotester@arm:~$ env 14 | TERM=xterm-color 15 | SHELL=/bin/bash 16 | SSH_CLIENT=192.168.1.16 18963 22 17 | SSH_TTY=/dev/pts/1 18 | USER=algotester 19 | LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.m4a=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.axa=00;36:*.oga=00;36:*.spx=00;36:*.xspf=00;36: 20 | MAIL=/var/mail/algotester 21 | PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games 22 | PWD=/home/algotester 23 | LANG=en_US.UTF-8 24 | SHLVL=1 25 | HOME=/home/algotester 26 | LOGNAME=algotester 27 | SSH_CONNECTION=192.168.1.16 18963 192.168.1.19 22 28 | DISPLAY=localhost:11.0 29 | _=/usr/bin/env 30 | ]0;algotester@arm: ~algotester@arm:~$ mkdir cppsshTestDir 31 | ]0;algotester@arm: ~algotester@arm:~$ ls -l cppsshTestDir 32 | total 0 33 | ]0;algotester@arm: ~algotester@arm:~$ rmdir cppsshTestDir 34 | ]0;algotester@arm: ~algotester@arm:~$ -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################# 2 | ## Eclipse 3 | ################# 4 | 5 | *.pydevproject 6 | .project 7 | .metadata 8 | bin/ 9 | tmp/ 10 | *.tmp 11 | *.bak 12 | *.swp 13 | *~.nib 14 | local.properties 15 | .classpath 16 | .settings/ 17 | .loadpath 18 | 19 | # External tool builders 20 | .externalToolBuilders/ 21 | 22 | # Locally stored "Eclipse launch configurations" 23 | *.launch 24 | 25 | # CDT-specific 26 | .cproject 27 | 28 | # PDT-specific 29 | .buildpath 30 | 31 | 32 | ################# 33 | ## Visual Studio 34 | ################# 35 | 36 | ## Ignore Visual Studio temporary files, build results, and 37 | ## files generated by popular Visual Studio add-ons. 38 | 39 | # User-specific files 40 | *.suo 41 | *.user 42 | *.sln.docstates 43 | 44 | # Build results 45 | [Dd]ebug/ 46 | [Rr]elease/ 47 | *_i.c 48 | *_p.c 49 | *.ilk 50 | *.meta 51 | *.obj 52 | *.pch 53 | *.pdb 54 | *.pgc 55 | *.pgd 56 | *.rsp 57 | *.sbr 58 | *.tlb 59 | *.tli 60 | *.tlh 61 | *.tmp 62 | *.vspscc 63 | .builds 64 | *.dotCover 65 | 66 | ## TODO: If you have NuGet Package Restore enabled, uncomment this 67 | #packages/ 68 | 69 | # Visual C++ cache files 70 | ipch/ 71 | *.aps 72 | *.ncb 73 | *.opensdf 74 | *.sdf 75 | 76 | # Visual Studio profiler 77 | *.psess 78 | *.vsp 79 | 80 | # ReSharper is a .NET coding add-in 81 | _ReSharper* 82 | 83 | # Installshield output folder 84 | [Ee]xpress 85 | 86 | # DocProject is a documentation generator add-in 87 | DocProject/buildhelp/ 88 | DocProject/Help/*.HxT 89 | DocProject/Help/*.HxC 90 | DocProject/Help/*.hhc 91 | DocProject/Help/*.hhk 92 | DocProject/Help/*.hhp 93 | DocProject/Help/Html2 94 | DocProject/Help/html 95 | 96 | # Click-Once directory 97 | publish 98 | 99 | # Others 100 | [Bb]in 101 | [Oo]bj 102 | sql 103 | TestResults 104 | *.Cache 105 | ClientBin 106 | stylecop.* 107 | ~$* 108 | *.dbmdl 109 | Generated_Code #added for RIA/Silverlight projects 110 | 111 | # Backup & report files from converting an old project file to a newer 112 | # Visual Studio version. Backup files are not needed, because we have git ;-) 113 | _UpgradeReport_Files/ 114 | Backup*/ 115 | UpgradeLog*.XML 116 | 117 | 118 | 119 | ############ 120 | ## Windows 121 | ############ 122 | 123 | # Windows image file caches 124 | Thumbs.db 125 | 126 | # Folder config file 127 | Desktop.ini 128 | 129 | 130 | ############# 131 | ## Python 132 | ############# 133 | 134 | *.py[co] 135 | 136 | # Packages 137 | *.egg 138 | *.egg-info 139 | dist 140 | build 141 | eggs 142 | parts 143 | bin 144 | var 145 | sdist 146 | develop-eggs 147 | .installed.cfg 148 | 149 | # Installer logs 150 | pip-log.txt 151 | 152 | # Unit test / coverage reports 153 | .coverage 154 | .tox 155 | 156 | #Translations 157 | *.mo 158 | 159 | #Mr Developer 160 | .mr.developer.cfg 161 | 162 | # Mac crap 163 | .DS_Store 164 | boost_1_56_0* 165 | v.h 166 | 167 | test/actualResults 168 | test/keys 169 | -------------------------------------------------------------------------------- /src/impl.h: -------------------------------------------------------------------------------- 1 | /* 2 | cppssh - C++ ssh library 3 | Copyright (C) 2015 Chris Desjardins 4 | http://blog.chrisd.info cjd@chrisd.info 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | */ 19 | #ifndef _IMPL_Hxx 20 | #define _IMPL_Hxx 21 | 22 | #include "botan/auto_rng.h" 23 | #include "cryptoalgos.h" 24 | #include "connection.h" 25 | #include "cppssh.h" 26 | #include 27 | #include 28 | 29 | class CppsshImpl 30 | { 31 | public: 32 | static bool setPreferredCipher(const char* prefCipher); 33 | static bool setPreferredHmac(const char* prefHmac); 34 | static size_t getSupportedCiphers(char* ciphers); 35 | static size_t getSupportedHmacs(char* hmacs); 36 | 37 | static bool generateRsaKeyPair(const char* fqdn, const char* privKeyFileName, const char* pubKeyFileName, short keySize); 38 | static bool generateDsaKeyPair(const char* fqdn, const char* privKeyFileName, const char* pubKeyFileName, short keySize); 39 | CppsshImpl(); 40 | ~CppsshImpl(); 41 | CppsshConnectStatus_t connect(int* connectionId, const char* host, const short port, const char* username, const char* privKeyFile, const char* password, unsigned int timeout, const bool x11Forwarded, const bool keepAlives, const char* term); 42 | bool isConnected(const int connectionId); 43 | bool write(const int connectionId, const uint8_t* data, size_t bytes); 44 | bool read(const int connectionId, CppsshMessage* data); 45 | bool windowChange(const int connectionId, const uint32_t cols, const uint32_t rows); 46 | bool close(const int connectionId); 47 | 48 | static CppsshMacAlgos MAC_ALGORITHMS; 49 | static CppsshCryptoAlgos CIPHER_ALGORITHMS; 50 | static CppsshKexAlgos KEX_ALGORITHMS; 51 | static CppsshHostkeyAlgos HOSTKEY_ALGORITHMS; 52 | static CppsshCompressionAlgos COMPRESSION_ALGORITHMS; 53 | 54 | static std::shared_ptr RNG; 55 | private: 56 | bool checkConnectionId(const int connectionId); 57 | template static size_t getSupportedAlogs(const T& algos, char* list); 58 | std::shared_ptr getConnection(const int connectionId); 59 | std::map > _connections; 60 | std::mutex _connectionsMutex; 61 | static std::mutex _optionsMutex; 62 | int _connectionId; 63 | }; 64 | 65 | #endif 66 | -------------------------------------------------------------------------------- /src/messages.h: -------------------------------------------------------------------------------- 1 | /* 2 | cppssh - C++ ssh library 3 | Copyright (C) 2015 Chris Desjardins 4 | http://blog.chrisd.info cjd@chrisd.info 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | */ 19 | #ifndef _MESSAGES_Hxx 20 | #define _MESSAGES_Hxx 21 | 22 | #define SSH2_MSG_DISCONNECT 1 23 | #define SSH2_MSG_IGNORE 2 24 | 25 | #define SSH2_MSG_KEXINIT 20 26 | #define SSH2_MSG_NEWKEYS 21 27 | 28 | #define SSH2_MSG_KEXDH_INIT 30 29 | #define SSH2_MSG_KEXDH_REPLY 31 30 | 31 | #define SSH2_MSG_DEBUG 4 32 | #define SSH2_MSG_SERVICE_REQUEST 5 33 | #define SSH2_MSG_SERVICE_ACCEPT 6 34 | 35 | #define SSH2_MSG_USERAUTH_REQUEST 50 36 | #define SSH2_MSG_USERAUTH_FAILURE 51 37 | #define SSH2_MSG_USERAUTH_SUCCESS 52 38 | #define SSH2_MSG_USERAUTH_BANNER 53 39 | #define SSH2_MSG_USERAUTH_PK_OK 60 40 | 41 | #define SSH2_MSG_GLOBAL_REQUEST 80 42 | #define SSH2_MSG_REQUEST_SUCCESS 81 43 | #define SSH2_MSG_REQUEST_FAILURE 82 44 | 45 | #define SSH2_MSG_CHANNEL_OPEN 90 46 | #define SSH2_MSG_CHANNEL_OPEN_CONFIRMATION 91 47 | #define SSH2_MSG_CHANNEL_OPEN_FAILURE 92 48 | #define SSH2_MSG_CHANNEL_WINDOW_ADJUST 93 49 | #define SSH2_MSG_CHANNEL_DATA 94 50 | #define SSH2_MSG_CHANNEL_EXTENDED_DATA 95 51 | #define SSH2_MSG_CHANNEL_EOF 96 52 | #define SSH2_MSG_CHANNEL_CLOSE 97 53 | #define SSH2_MSG_CHANNEL_REQUEST 98 54 | #define SSH2_MSG_CHANNEL_SUCCESS 99 55 | #define SSH2_MSG_CHANNEL_FAILURE 100 56 | 57 | enum CppsshOpenFailureReason 58 | { 59 | SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED = 1, 60 | SSH2_OPEN_CONNECT_FAILED = 2, 61 | SSH2_OPEN_UNKNOWN_CHANNEL_TYPE = 3, 62 | SSH2_OPEN_RESOURCE_SHORTAGE = 4, 63 | }; 64 | #endif 65 | -------------------------------------------------------------------------------- /src/transportimpl.h: -------------------------------------------------------------------------------- 1 | /* 2 | cppssh - C++ ssh library 3 | Copyright (C) 2015 Chris Desjardins 4 | http://blog.chrisd.info cjd@chrisd.info 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | */ 19 | #ifndef _TRANSPORT_IMPL_Hxx 20 | #define _TRANSPORT_IMPL_Hxx 21 | 22 | /* 23 | ** Note: Do not include this file directly, include transport.h instead 24 | */ 25 | #include "botan/secmem.h" 26 | #include 27 | #include 28 | 29 | #define CPPSSH_MAX_PACKET_LEN 0x4000 30 | class CppsshSession; 31 | 32 | class CppsshTransportImpl 33 | { 34 | public: 35 | CppsshTransportImpl() = delete; 36 | CppsshTransportImpl(const CppsshTransportImpl&) = delete; 37 | CppsshTransportImpl(const std::shared_ptr& session); 38 | virtual ~CppsshTransportImpl(); 39 | bool receiveMessage(Botan::secure_vector* buffer, size_t numBytes); 40 | virtual bool receiveMessage(Botan::secure_vector* buffer); 41 | virtual bool sendMessage(const Botan::secure_vector& buffer); 42 | 43 | bool establish(const std::string& host, short port); 44 | bool establishX11(); 45 | void disconnect(); 46 | SOCKET getSocket() 47 | { 48 | return _sock; 49 | } 50 | 51 | static bool parseDisplay(const std::string& display, int* displayNum, int* screenNum); 52 | bool isRunning() const 53 | { 54 | return _running; 55 | } 56 | 57 | virtual bool startThreads() 58 | { 59 | return false; 60 | } 61 | 62 | void enableKeepAlives() 63 | { 64 | _sendKeepAlives = true; 65 | } 66 | 67 | bool sendKeepAlive() 68 | { 69 | bool ret = true; 70 | if (_sendKeepAlives == true) 71 | { 72 | ret = doSendKeepAlive(); 73 | } 74 | return ret; 75 | } 76 | 77 | protected: 78 | virtual bool establishLocalX11(const std::string& display) = 0; 79 | virtual bool setNonBlocking(bool on) = 0; 80 | void setupFd(fd_set* fd); 81 | bool makeConnection(void* remoteAddr); 82 | virtual bool isConnectInProgress() = 0; 83 | bool doSendKeepAlive(); 84 | 85 | std::shared_ptr _session; 86 | bool wait(bool isWrite); 87 | SOCKET _sock; 88 | volatile bool _running; 89 | bool _sendKeepAlives; 90 | std::chrono::steady_clock::time_point _lastMsgTime; 91 | }; 92 | 93 | #endif 94 | -------------------------------------------------------------------------------- /src/posix/transportposix.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | cppssh - C++ ssh library 3 | Copyright (C) 2015 Chris Desjardins 4 | http://blog.chrisd.info cjd@chrisd.info 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | */ 19 | 20 | #include "transport.h" 21 | #include "CDLogger/Logger.h" 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #define SOCKET_BUFFER_TYPE void 31 | #define SOCK_CAST (void*) 32 | 33 | bool CppsshTransportPosix::isConnectInProgress() 34 | { 35 | return (errno == EINPROGRESS) ? true : false; 36 | } 37 | 38 | bool CppsshTransportPosix::establishLocalX11(const std::string& display) 39 | { 40 | bool ret = false; 41 | struct sockaddr_un addr; 42 | 43 | _sock = socket(AF_UNIX, SOCK_STREAM, 0); 44 | if (_sock < 0) 45 | { 46 | cdLog(LogLevel::Error) << "Unable to open to X11 socket"; 47 | } 48 | else 49 | { 50 | int displayNum; 51 | int screenNum; 52 | parseDisplay(display, &displayNum, &screenNum); 53 | std::stringstream path; 54 | path << "/tmp/.X11-unix/X" << displayNum; 55 | 56 | memset(&addr, 0, sizeof(addr)); 57 | addr.sun_family = AF_UNIX; 58 | strncpy(addr.sun_path, path.str().c_str(), sizeof(addr.sun_path)); 59 | int connectRet = connect(_sock, (struct sockaddr*)&addr, sizeof(addr)); 60 | if (connectRet == 0) 61 | { 62 | // success 63 | ret = true; 64 | setNonBlocking(true); 65 | } 66 | else 67 | { 68 | cdLog(LogLevel::Error) << "Unable to connect to X11 socket " << path.str() << " " << strerror(errno); 69 | disconnect(); 70 | } 71 | } 72 | return ret; 73 | } 74 | 75 | bool CppsshTransportPosix::setNonBlocking(bool on) 76 | { 77 | bool ret = true; 78 | int options; 79 | if ((options = fcntl(_sock, F_GETFL)) < 0) 80 | { 81 | cdLog(LogLevel::Error) << "Cannot read options of the socket."; 82 | ret = false; 83 | } 84 | else 85 | { 86 | if (on == true) 87 | { 88 | options = (options | O_NONBLOCK); 89 | } 90 | else 91 | { 92 | options = (options & ~O_NONBLOCK); 93 | } 94 | fcntl(_sock, F_SETFL, options); 95 | } 96 | return ret; 97 | } 98 | -------------------------------------------------------------------------------- /test/expectedResults/testlog.txt: -------------------------------------------------------------------------------- 1 | 2015-09-13 12:50:53.097 (Debug/connection.cpp) CppsshConnection 2 | 2015-09-13 12:50:53.098 (Debug/channel.cpp) createNewSubChannel session rxChannel: 100 3 | 2015-09-13 12:50:53.227 (Debug/transportthreaded.cpp) starting rx thread 4 | 2015-09-13 12:50:53.227 (Debug/transportthreaded.cpp) starting tx thread 5 | 2015-09-13 12:50:53.238 (Debug/kex.cpp) Kex algos: ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group-exchange-sha256,diffie-hellman-group-exchange-sha1,diffie-hellman-group14-sha1,diffie-hellman-group1-sha1 6 | 2015-09-13 12:50:53.239 (Debug/crypto.cpp) agreed on: diffie-hellman-group1-sha1 7 | 2015-09-13 12:50:53.239 (Debug/kex.cpp) Hostkey algos: ssh-rsa,ssh-dss,ecdsa-sha2-nistp256 8 | 2015-09-13 12:50:53.239 (Debug/crypto.cpp) agreed on: ssh-dss 9 | 2015-09-13 12:50:53.239 (Debug/kex.cpp) C2S Cipher algos: aes128-ctr,aes192-ctr,aes256-ctr,arcfour256,arcfour128,aes128-cbc,3des-cbc,blowfish-cbc,cast128-cbc,aes192-cbc,aes256-cbc,arcfour,rijndael-cbc@lysator.liu.se 10 | 2015-09-13 12:50:53.239 (Debug/crypto.cpp) agreed on: 3des-cbc 11 | 2015-09-13 12:50:53.239 (Debug/kex.cpp) S2C Cipher algos: aes128-ctr,aes192-ctr,aes256-ctr,arcfour256,arcfour128,aes128-cbc,3des-cbc,blowfish-cbc,cast128-cbc,aes192-cbc,aes256-cbc,arcfour,rijndael-cbc@lysator.liu.se 12 | 2015-09-13 12:50:53.239 (Debug/crypto.cpp) agreed on: 3des-cbc 13 | 2015-09-13 12:50:53.239 (Debug/kex.cpp) C2S MAC algos: hmac-md5,hmac-sha1,umac-64@openssh.com,hmac-sha2-256,hmac-sha2-256-96,hmac-sha2-512,hmac-sha2-512-96,hmac-ripemd160,hmac-ripemd160@openssh.com,hmac-sha1-96,hmac-md5-96 14 | 2015-09-13 12:50:53.240 (Debug/crypto.cpp) agreed on: hmac-md5 15 | 2015-09-13 12:50:53.240 (Debug/kex.cpp) S2C MAC algos: hmac-md5,hmac-sha1,umac-64@openssh.com,hmac-sha2-256,hmac-sha2-256-96,hmac-sha2-512,hmac-sha2-512-96,hmac-ripemd160,hmac-ripemd160@openssh.com,hmac-sha1-96,hmac-md5-96 16 | 2015-09-13 12:50:53.240 (Debug/crypto.cpp) agreed on: hmac-md5 17 | 2015-09-13 12:50:53.240 (Debug/kex.cpp) C2S Compression algos: none,zlib@openssh.com 18 | 2015-09-13 12:50:53.240 (Debug/crypto.cpp) agreed on: none 19 | 2015-09-13 12:50:53.240 (Debug/kex.cpp) S2C Compression algos: none,zlib@openssh.com 20 | 2015-09-13 12:50:53.240 (Debug/crypto.cpp) agreed on: none 21 | 2015-09-13 12:50:53.695 (Debug/transportthreaded.cpp) ~CppsshTransportThreaded 22 | 2015-09-13 12:50:53.695 (Debug/transportthreaded.cpp) tx thread done 23 | 2015-09-13 12:50:53.695 (Debug/transportthreaded.cpp) rx thread done 24 | 2015-09-13 12:50:53.695 (Debug/transportcrypto.cpp) starting crypto rx thread 25 | 2015-09-13 12:50:53.695 (Debug/transportthreaded.cpp) starting tx thread 26 | 2015-09-13 12:50:59.043 (Debug/channel.cpp) handleWindowAdjust 100 2097152 27 | 2015-09-13 12:51:02.049 (Info/transportimpl.cpp) CppsshTransport::disconnect 28 | 2015-09-13 12:51:02.049 (Debug/connection.cpp) ~CppsshConnection 29 | 2015-09-13 12:51:02.049 (Debug/channel.cpp) disconnect[1] 30 | 2015-09-13 12:51:02.049 (Debug/transportcrypto.cpp) ~CppsshTransportCrypto 31 | 2015-09-13 12:51:02.049 (Debug/transportthreaded.cpp) ~CppsshTransportThreaded 32 | 2015-09-13 12:51:02.050 (Debug/transportthreaded.cpp) tx thread done 33 | 2015-09-13 12:51:02.050 (Debug/transportcrypto.cpp) crypto rx thread done 34 | 2015-09-13 12:51:02.050 (Debug/channel.cpp) disconnect[1] 35 | -------------------------------------------------------------------------------- /src/subchannel.h: -------------------------------------------------------------------------------- 1 | /* 2 | cppssh - C++ ssh library 3 | Copyright (C) 2015 Chris Desjardins 4 | http://blog.chrisd.info cjd@chrisd.info 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | */ 19 | #ifndef _SUBCHANNEL_Hxx 20 | #define _SUBCHANNEL_Hxx 21 | 22 | #include "packet.h" 23 | #include "transport.h" 24 | #include "threadsafequeue.h" 25 | #include 26 | 27 | class CppsshSubChannel 28 | { 29 | public: 30 | CppsshSubChannel(const std::shared_ptr& session, const std::string& channelName); 31 | CppsshSubChannel() = delete; 32 | CppsshSubChannel(const CppsshSubChannel&) = delete; 33 | 34 | virtual ~CppsshSubChannel() 35 | { 36 | } 37 | 38 | virtual bool startChannel() 39 | { 40 | return true; 41 | } 42 | 43 | void reduceWindowRecv(uint32_t bytes) 44 | { 45 | _windowRecv -= bytes; 46 | } 47 | 48 | void increaseWindowSend(uint32_t bytes) 49 | { 50 | _windowSend += bytes; 51 | } 52 | 53 | uint32_t getWindowRecv() const 54 | { 55 | return _windowRecv; 56 | } 57 | 58 | const std::string& getChannelName() const 59 | { 60 | return _channelName; 61 | } 62 | 63 | uint32_t getTxChannel() const 64 | { 65 | return _txChannel; 66 | } 67 | 68 | virtual bool doChannelRequest(const std::string& req, const Botan::secure_vector& request, bool wantReply = true); 69 | virtual void handleIncomingChannelData(const Botan::secure_vector& buf); 70 | virtual void handleIncomingControlData(const Botan::secure_vector& buf); 71 | virtual bool handleChannelConfirm(); 72 | void handleChannelRequest(const Botan::secure_vector& buf); 73 | virtual void handleEof(); 74 | virtual void handleClose(); 75 | void sendAdjustWindow(); 76 | bool flushOutgoingChannelData(); 77 | bool writeChannel(const uint8_t* data, uint32_t bytes); 78 | bool readChannel(CppsshMessage* data); 79 | bool windowChange(const uint32_t cols, const uint32_t rows); 80 | void setParameters(uint32_t windowSend, uint32_t txChannel, uint32_t maxPacket); 81 | void handleBanner(const std::shared_ptr& banner); 82 | static uint32_t getRxWindowSize(); 83 | 84 | protected: 85 | ThreadSafeQueue > > _outgoingChannelData; 86 | ThreadSafeQueue > _incomingChannelData; 87 | ThreadSafeQueue > _incomingControlData; 88 | 89 | std::shared_ptr _session; 90 | uint32_t _windowRecv; 91 | uint32_t _windowSend; 92 | uint32_t _txChannel; 93 | uint32_t _maxPacket; 94 | std::string _channelName; 95 | }; 96 | #endif 97 | -------------------------------------------------------------------------------- /src/packet.h: -------------------------------------------------------------------------------- 1 | /* 2 | cppssh - C++ ssh library 3 | Copyright (C) 2015 Chris Desjardins 4 | http://blog.chrisd.info cjd@chrisd.info 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | */ 19 | #ifndef _PACKET_Hxx 20 | #define _PACKET_Hxx 21 | 22 | #include "botan/bigint.h" 23 | #include 24 | #include 25 | 26 | class CppsshMessage; 27 | 28 | class CppsshConstPacket 29 | { 30 | public: 31 | CppsshConstPacket() = delete; 32 | CppsshConstPacket(const CppsshConstPacket&) = delete; 33 | CppsshConstPacket& operator=(const CppsshConstPacket& data) = delete; 34 | 35 | CppsshConstPacket(const Botan::secure_vector* const data); 36 | 37 | static void bn2vector(Botan::secure_vector* result, const Botan::BigInt& bi); 38 | 39 | uint32_t getPacketLength() const; 40 | uint32_t getCryptoLength() const; 41 | Botan::byte getPadLength() const; 42 | Botan::byte getCommand() const; 43 | Botan::secure_vector::const_iterator getPayloadBegin() const; 44 | Botan::secure_vector::const_iterator getPayloadEnd() const; 45 | 46 | bool getString(Botan::secure_vector* result) const; 47 | bool getString(std::string* result) const; 48 | bool getBigInt(Botan::BigInt* result) const; 49 | void getChannelData(CppsshMessage* result) const; 50 | uint8_t getByte() const; 51 | uint32_t getInt() const; 52 | void skipHeader() const; 53 | 54 | size_t size() const; 55 | void dumpPacket(const std::string& tag) const; 56 | 57 | private: 58 | void dumpAscii(Botan::secure_vector::const_iterator it, size_t len, std::stringstream* ss) const; 59 | 60 | const Botan::secure_vector* const _cdata; 61 | mutable int _index; 62 | }; 63 | 64 | class CppsshPacket : public CppsshConstPacket 65 | { 66 | public: 67 | CppsshPacket(Botan::secure_vector* data); 68 | 69 | void addVectorField(const Botan::secure_vector& vec); 70 | void addVector(const Botan::secure_vector& vec); 71 | void addRawData(const uint8_t* data, uint32_t bytes); 72 | void addString(const std::string& str); 73 | void addInt(const uint32_t var); 74 | void addByte(const uint8_t ch); 75 | void addBigInt(const Botan::BigInt& bn); 76 | bool addFile(const std::string& fileName); 77 | void removeWhitespace(); 78 | void copy(const Botan::secure_vector& src); 79 | void replace(size_t startingPos, const Botan::secure_vector& src); 80 | void clear(); 81 | 82 | private: 83 | CppsshPacket(); 84 | CppsshPacket(const CppsshPacket&); 85 | CppsshPacket& operator=(const CppsshPacket&); 86 | 87 | Botan::secure_vector* _data; 88 | }; 89 | 90 | #endif 91 | -------------------------------------------------------------------------------- /src/win/transportwin.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | cppssh - C++ ssh library 3 | Copyright (C) 2015 Chris Desjardins 4 | http://blog.chrisd.info cjd@chrisd.info 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | */ 19 | #include "CDLogger/Logger.h" 20 | #include "transport.h" 21 | #include "unparam.h" 22 | 23 | class WSockInitializer 24 | { 25 | public: 26 | WSockInitializer() 27 | { 28 | static WSADATA wsaData; 29 | WSAStartup(MAKEWORD(2, 2), &wsaData); 30 | } 31 | 32 | ~WSockInitializer() 33 | { 34 | WSACleanup(); 35 | } 36 | }; 37 | 38 | WSockInitializer _wsock32_; 39 | 40 | bool CppsshTransportWin::isConnectInProgress() 41 | { 42 | bool ret = false; 43 | int lastError = WSAGetLastError(); 44 | if (lastError == WSAEWOULDBLOCK) 45 | { 46 | ret = true; 47 | } 48 | return ret; 49 | } 50 | 51 | bool CppsshTransportWin::establishLocalX11(const std::string& display) 52 | { 53 | bool ret = false; 54 | UNREF_PARAM(display); 55 | _sock = socket(AF_INET, SOCK_STREAM, 0); 56 | if (_sock < 0) 57 | { 58 | cdLog(LogLevel::Error) << "Unable to open to X11 socket"; 59 | } 60 | else 61 | { 62 | SOCKADDR_IN addr; 63 | memset(&addr, 0, sizeof(addr)); 64 | 65 | addr.sin_family = AF_INET; 66 | addr.sin_addr.s_addr = htonl(INADDR_ANY); 67 | addr.sin_port = htons((short)0); 68 | 69 | int bindRet = bind(_sock, (struct sockaddr*) &addr, sizeof(addr)); 70 | if (bindRet == 0) 71 | { 72 | memset(&addr, 0, sizeof(addr)); 73 | addr.sin_family = AF_INET; 74 | addr.sin_addr.s_addr = htonl(0x7f000001); 75 | addr.sin_port = htons((short)6000); 76 | int connectRet = connect(_sock, (struct sockaddr*)&addr, sizeof(addr)); 77 | if (connectRet == 0) 78 | { 79 | // success 80 | ret = true; 81 | setNonBlocking(true); 82 | } 83 | else 84 | { 85 | cdLog(LogLevel::Error) << "Unable to connect to X11 socket " << WSAGetLastError(); 86 | disconnect(); 87 | } 88 | } 89 | else 90 | { 91 | cdLog(LogLevel::Error) << "Unable to bind to X11 socket " << strerror(errno); 92 | disconnect(); 93 | } 94 | } 95 | return ret; 96 | } 97 | 98 | bool CppsshTransportWin::setNonBlocking(bool on) 99 | { 100 | unsigned long options = on; 101 | bool ret = true; 102 | if (ioctlsocket(_sock, FIONBIO, &options)) 103 | { 104 | cdLog(LogLevel::Error) << "Cannot set asynch I/O on the socket."; 105 | ret = false; 106 | } 107 | return ret; 108 | } 109 | -------------------------------------------------------------------------------- /src/keys.h: -------------------------------------------------------------------------------- 1 | /* 2 | cppssh - C++ ssh library 3 | Copyright (C) 2015 Chris Desjardins 4 | http://blog.chrisd.info cjd@chrisd.info 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | */ 19 | #ifndef _KEYS_Hxx 20 | #define _KEYS_Hxx 21 | 22 | #include "session.h" 23 | #include "crypto.h" 24 | #include 25 | 26 | class CppsshKeys 27 | { 28 | public: 29 | CppsshKeys() 30 | : _keyAlgo(hostkeyMethods::MAX_VALS) 31 | { 32 | } 33 | 34 | bool getKeyPairFromFile(const std::string& privKeyFileName, const char* keyPassword); 35 | const Botan::secure_vector& generateSignature(const Botan::secure_vector& sessionID, const Botan::secure_vector& signingData); 36 | Botan::secure_vector generateRSASignature(const Botan::secure_vector& sessionID, const Botan::secure_vector& signingData); 37 | Botan::secure_vector generateDSASignature(const Botan::secure_vector& sessionID, const Botan::secure_vector& signingData); 38 | 39 | static bool generateRsaKeyPair(const char* fqdn, const char* privKeyFileName, const char* pubKeyFileName, short keySize); 40 | static bool generateDsaKeyPair(const char* fqdn, const char* privKeyFileName, const char* pubKeyFileName, short keySize); 41 | 42 | hostkeyMethods getKeyAlgo() 43 | { 44 | return _keyAlgo; 45 | } 46 | 47 | const Botan::secure_vector& getPublicKeyBlob() 48 | { 49 | return _publicKeyBlob; 50 | } 51 | 52 | private: 53 | bool isKey(const Botan::secure_vector& buf, std::string header, std::string footer); 54 | bool getRSAKeys(const std::shared_ptr& privKey); 55 | bool getDSAKeys(const std::shared_ptr& privKey); 56 | bool getUnencryptedRSAKeys(Botan::secure_vector privateKey); 57 | bool getUnencryptedDSAKeys(Botan::secure_vector privateKey); 58 | bool checkPrivKeyFile(const std::string& privKeyFileName); 59 | 60 | static Botan::secure_vector::const_iterator findKeyBegin(const Botan::secure_vector& privateKey, const std::string& header); 61 | static Botan::secure_vector::const_iterator findKeyEnd(const Botan::secure_vector& privateKey, const std::string& footer); 62 | 63 | static const std::string HEADER_DSA; 64 | static const std::string FOOTER_DSA; 65 | static const std::string HEADER_RSA; 66 | static const std::string FOOTER_RSA; 67 | static const std::string PROC_TYPE; 68 | static const std::string DEK_INFO; 69 | 70 | hostkeyMethods _keyAlgo; 71 | std::shared_ptr _rsaPrivateKey; 72 | std::shared_ptr _dsaPrivateKey; 73 | Botan::secure_vector _publicKeyBlob; 74 | Botan::secure_vector _signature; 75 | }; 76 | 77 | #endif 78 | -------------------------------------------------------------------------------- /src/channel.h: -------------------------------------------------------------------------------- 1 | /* 2 | cppssh - C++ ssh library 3 | Copyright (C) 2015 Chris Desjardins 4 | http://blog.chrisd.info cjd@chrisd.info 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | */ 19 | #ifndef _CHANNEL_Hxx 20 | #define _CHANNEL_Hxx 21 | 22 | #include "packet.h" 23 | #include "session.h" 24 | #include "messages.h" 25 | #include "transport.h" 26 | #include "threadsafemap.h" 27 | #include "threadsafequeue.h" 28 | 29 | class CppsshSubChannel; 30 | 31 | class CppsshChannel 32 | { 33 | public: 34 | CppsshChannel(const std::shared_ptr& session); 35 | ~CppsshChannel(); 36 | bool establish(const std::string& host, short port); 37 | bool openChannel(); 38 | bool writeMainChannel(const uint8_t* data, uint32_t bytes); 39 | bool readMainChannel(CppsshMessage* data); 40 | bool windowChange(const uint32_t rows, const uint32_t cols); 41 | bool getShell(const char* term); 42 | bool getX11(); 43 | void handleReceived(const Botan::secure_vector& buf); 44 | bool flushOutgoingChannelData(); 45 | void disconnect(); 46 | bool isConnected(); 47 | bool waitForGlobalMessage(Botan::secure_vector& buf); 48 | static bool getRandomString(const int size, std::string* randomString); 49 | private: 50 | void handleIncomingChannelData(const Botan::secure_vector& buf); 51 | void handleIncomingControlData(const Botan::secure_vector& buf); 52 | void handleWindowAdjust(const Botan::secure_vector& buf); 53 | void handleIncomingGlobalData(const Botan::secure_vector& buf); 54 | void handleChannelRequest(const Botan::secure_vector& buf); 55 | void handleBanner(const Botan::secure_vector& buf); 56 | void handleEof(const Botan::secure_vector& buf); 57 | void handleClose(const Botan::secure_vector& buf); 58 | 59 | void handleDebug(const CppsshConstPacket& packet); 60 | void handleDisconnect(const CppsshConstPacket& packet); 61 | void handleOpen(const Botan::secure_vector& buf); 62 | bool runXauth(const char* display, std::string* method, Botan::secure_vector* cookie) const; 63 | bool createNewSubChannel(const std::string& channelName, uint32_t windowSend, uint32_t maxPacket, uint32_t txChannel, uint32_t* rxChannel); 64 | bool createNewSubChannel(const std::string& channelName, uint32_t* rxChannel); 65 | void sendOpenFailure(uint32_t txChannel, CppsshOpenFailureReason reason); 66 | void sendOpenConfirmation(uint32_t rxChannel); 67 | 68 | std::shared_ptr _session; 69 | std::string _X11Method; 70 | Botan::secure_vector _realX11Cookie; 71 | std::string _fakeX11Cookie; 72 | 73 | ThreadSafeQueue > _incomingGlobalData; 74 | ThreadSafeMap > _channels; 75 | uint32_t _mainChannel; 76 | bool _x11ReqSuccess; 77 | friend class CppsshX11Channel; 78 | }; 79 | 80 | #endif 81 | -------------------------------------------------------------------------------- /test/cppsshtestkeys.cpp: -------------------------------------------------------------------------------- 1 | #include "cppsshtestutil.h" 2 | #include "cppssh.h" 3 | #include "CDLogger/Logger.h" 4 | #include 5 | #include 6 | 7 | #ifdef WIN32 8 | #define DIR_SEP "\\" 9 | #else 10 | #define DIR_SEP "/" 11 | #endif 12 | 13 | inline bool endsWith(std::string const& value, std::string const& ending) 14 | { 15 | if (ending.size() > value.size()) 16 | { 17 | return false; 18 | } 19 | return std::equal(ending.rbegin(), ending.rend(), value.rbegin()); 20 | } 21 | 22 | void getPublicKeys(const char* keydir, std::vector* publicKeys) 23 | { 24 | std::vector keyFiles = 25 | { 26 | "testkey_dsa.pub", 27 | "testkey_rsa.pub", 28 | "testkey_dsa_pw.pub", 29 | "testkey_rsa_pw.pub" 30 | }; 31 | for (std::vector::iterator it = keyFiles.begin(); it < keyFiles.end(); it++) 32 | { 33 | std::string name(keydir); 34 | name.append(DIR_SEP); 35 | name.append(*it); 36 | std::ifstream t(name); 37 | if (t) 38 | { 39 | std::string str((std::istreambuf_iterator(t)), std::istreambuf_iterator()); 40 | publicKeys->push_back(str); 41 | } 42 | else 43 | { 44 | cdLog(LogLevel::Error) << "Unable to open file " << name; 45 | } 46 | } 47 | } 48 | 49 | void installPublicKeys(const char* hostname, const char* username, const char* password, const char* keydir) 50 | { 51 | std::vector publicKeys; 52 | getPublicKeys(keydir, &publicKeys); 53 | if (publicKeys.size() > 0) 54 | { 55 | int channel; 56 | if (Cppssh::connect(&channel, hostname, 22, username, nullptr, password, 10000) == CPPSSH_CONNECT_OK) 57 | { 58 | std::vector cmdList { "mkdir -p ~/.ssh\n", "rm ~/.ssh/authorized_keys\n", 59 | "touch ~/.ssh/authorized_keys\n", "chmod 600 ~/.ssh/authorized_keys\n"}; 60 | for (std::vector::iterator it = publicKeys.begin(); it < publicKeys.end(); it++) 61 | { 62 | std::string cmd("echo \""); 63 | cmd.append(*it); 64 | cmd.append("\" >> ~/.ssh/authorized_keys\n"); 65 | cmdList.push_back(cmd); 66 | } 67 | std::ofstream remoteOutput; 68 | remoteOutput.open("testoutput.txt"); 69 | if (!remoteOutput) 70 | { 71 | cdLog(LogLevel::Error) << "Unable to open testoutput.txt"; 72 | } 73 | else 74 | { 75 | sendCmdList(channel, cmdList, 500, remoteOutput); 76 | remoteOutput.close(); 77 | } 78 | Cppssh::close(channel); 79 | } 80 | else 81 | { 82 | cdLog(LogLevel::Error) << "Did not connect " << channel; 83 | } 84 | } 85 | } 86 | 87 | int main(int argc, char** argv) 88 | { 89 | if (argc != 5) 90 | { 91 | std::cerr << "Error: Four arguments required: " << argv[0] << " " << 92 | std::endl; 93 | } 94 | else 95 | { 96 | Cppssh::create(); 97 | Logger::getLogger().addStream("testlog.txt"); 98 | //Logger::getLogger().addStream(std::shared_ptr(&std::cout, [](void*) {})); 99 | try 100 | { 101 | Logger::getLogger().setMinLogLevel(LogLevel::Debug); 102 | 103 | installPublicKeys(argv[1], argv[2], argv[3], argv[4]); 104 | } 105 | catch (const std::exception& ex) 106 | { 107 | cdLog(LogLevel::Error) << "Exception: " << ex.what() << std::endl; 108 | } 109 | Cppssh::destroy(); 110 | } 111 | return 0; 112 | } 113 | -------------------------------------------------------------------------------- /examples/cppsshexample.cpp: -------------------------------------------------------------------------------- 1 | #include "cppssh.h" 2 | #include "CDLogger/Logger.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #define NUM_THREADS 10 11 | 12 | void getOutFile(int channel, std::ofstream& outfile) 13 | { 14 | std::stringstream filename; 15 | filename << "channel" << channel << ".log"; 16 | outfile.open(filename.str()); 17 | } 18 | 19 | void runConnectionTest(char* hostname, char* username, char* password) 20 | { 21 | int channel; 22 | if (Cppssh::connect(&channel, hostname, 22, username, password, password, NUM_THREADS * 10000) == CPPSSH_CONNECT_OK) 23 | { 24 | std::ofstream output; 25 | getOutFile(channel, output); 26 | cdLog(LogLevel::Info) << "Connected " << channel; 27 | std::chrono::steady_clock::time_point txTime = std::chrono::steady_clock::now(); 28 | int txCount = 0; 29 | bool sentGvim = false; 30 | 31 | Cppssh::writeString(channel, "env\n"); 32 | //Cppssh::windowSize(channel, 80, 40); 33 | 34 | while ((Cppssh::isConnected(channel) == true) && 35 | (std::chrono::steady_clock::now() < (txTime + std::chrono::seconds(1)))) 36 | { 37 | CppsshMessage message; 38 | if (Cppssh::read(channel, &message) == true) 39 | { 40 | output << message.message(); 41 | } 42 | 43 | if ((txCount < 20) && (std::chrono::steady_clock::now() > (txTime + std::chrono::milliseconds(100)))) 44 | { 45 | // send ls -l every 100 milliseconds 46 | Cppssh::writeString(channel, "ls -l\n"); 47 | txTime = std::chrono::steady_clock::now(); 48 | 49 | if ((sentGvim == false) && (txCount > 5)) 50 | { 51 | Cppssh::writeString(channel, "xterm&\n"); 52 | sentGvim = true; 53 | } 54 | 55 | txCount++; 56 | } 57 | std::this_thread::sleep_for(std::chrono::milliseconds(1)); 58 | } 59 | } 60 | else 61 | { 62 | cdLog(LogLevel::Error) << "Did not connect " << channel; 63 | } 64 | Cppssh::close(channel); 65 | } 66 | 67 | int main(int argc, char** argv) 68 | { 69 | std::cout << "Test program for cppssh API level: " << Cppssh::getCppsshVersion(true) << std::endl; 70 | if (argc != 4) 71 | { 72 | std::cerr << "Error: Three arguments required: " << argv[0] << " " << std::endl; 73 | return -1; 74 | } 75 | 76 | try 77 | { 78 | Cppssh::create(); 79 | Logger::getLogger().addStream(std::shared_ptr(&std::cout, [](void*) { 80 | })); 81 | Logger::getLogger().setMinLogLevel(LogLevel::Debug); 82 | size_t cipherLen = Cppssh::getSupportedCiphers(nullptr); 83 | size_t hmacLen = Cppssh::getSupportedHmacs(nullptr); 84 | std::shared_ptr ciphers(new char[cipherLen + 1]); 85 | std::shared_ptr hmacs(new char[hmacLen + 1]); 86 | Cppssh::getSupportedCiphers(ciphers.get()); 87 | Cppssh::getSupportedHmacs(hmacs.get()); 88 | cdLog(LogLevel::Info) << "Supported ciphers: " << ciphers; 89 | cdLog(LogLevel::Info) << "Supported hmacs: " << hmacs; 90 | Cppssh::setPreferredCipher("aes256-cbc"); 91 | Cppssh::setPreferredHmac("hmac-md5"); 92 | 93 | //Cppssh::generateRsaKeyPair("test", "privRsa", "pubRsa", 1024); 94 | //Cppssh::generateDsaKeyPair("test", "privDsa", "pubDsa", 1024); 95 | 96 | std::vector threads; 97 | for (int i = 0; i < NUM_THREADS; i++) 98 | { 99 | threads.push_back(std::thread(&runConnectionTest, argv[1], argv[2], argv[3])); 100 | } 101 | for (std::thread& t : threads) 102 | { 103 | t.join(); 104 | } 105 | } 106 | catch (const std::exception& ex) 107 | { 108 | cdLog(LogLevel::Error) << "Exception: " << ex.what() << std::endl; 109 | } 110 | Cppssh::destroy(); 111 | return 0; 112 | } 113 | -------------------------------------------------------------------------------- /include/cppssh.h: -------------------------------------------------------------------------------- 1 | /* 2 | cppssh - C++ ssh library 3 | Copyright (C) 2015 Chris Desjardins 4 | http://blog.chrisd.info cjd@chrisd.info 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | */ 19 | #ifndef _CPPSSH_Hxx 20 | #define _CPPSSH_Hxx 21 | 22 | #include "export.h" 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | class CppsshImpl; 29 | class CppsshMessage; 30 | class CppsshConstPacket; 31 | class CppsshChannel; 32 | 33 | #define CPPSSH_API_LEVEL_0 0 34 | 35 | #define CPPSSH_API_LEVEL_CURRENT CPPSSH_API_LEVEL_0 36 | 37 | enum CppsshConnectStatus_t 38 | { 39 | CPPSSH_CONNECT_OK, 40 | CPPSSH_CONNECT_UNKNOWN_HOST, 41 | CPPSSH_CONNECT_AUTH_FAIL, 42 | CPPSSH_CONNECT_INCOMPATIBLE_SERVER, 43 | CPPSSH_CONNECT_KEX_FAIL, 44 | 45 | CPPSSH_CONNECT_ERROR 46 | }; 47 | 48 | class Cppssh 49 | { 50 | public: 51 | Cppssh() = delete; 52 | Cppssh(const Cppssh&) = delete; 53 | Cppssh& operator=(const Cppssh&) = delete; 54 | 55 | CPPSSH_EXPORT static const char* getCppsshVersion(bool detailed); 56 | CPPSSH_EXPORT static int getApiLevel(); 57 | // Timeout is in milliseconds 58 | // term is the TERM environment variable value (nullptr for no shell) 59 | CPPSSH_EXPORT static CppsshConnectStatus_t connect(int* connectionId, const char* host, const short port, const char* username, const char* privKeyFile, const char* password, unsigned int timeout = 1000, const bool x11Forwarded = true, const bool keepAlives = false, 60 | const char* term = "xterm-color"); 61 | CPPSSH_EXPORT static bool isConnected(const int connectionId); 62 | CPPSSH_EXPORT static bool writeString(const int connectionId, const char* data); 63 | CPPSSH_EXPORT static bool write(const int connectionId, const uint8_t* data, size_t bytes); 64 | CPPSSH_EXPORT static bool read(const int connectionId, CppsshMessage* data); 65 | CPPSSH_EXPORT static bool windowChange(const int connectionId, const uint32_t cols, const uint32_t rows); 66 | CPPSSH_EXPORT static bool close(const int connectionId); 67 | 68 | // Set the preferred cipher/hmac, call multiple times to set the order 69 | // use getSupportedCipher/Hmac to get the list of possibilities 70 | CPPSSH_EXPORT static bool setPreferredCipher(const char* prefCipher); 71 | CPPSSH_EXPORT static bool setPreferredHmac(const char* prefHmac); 72 | // Call with ciphers or hmacs==NULL to get the length of the returned string 73 | // Then call again with a properly sized string as an argument, and it 74 | // will be filled with a coma separated list of ciphers. 75 | CPPSSH_EXPORT static size_t getSupportedCiphers(char* ciphers); 76 | CPPSSH_EXPORT static size_t getSupportedHmacs(char* hmacs); 77 | 78 | CPPSSH_EXPORT static bool generateRsaKeyPair(const char* fqdn, const char* privKeyFileName, const char* pubKeyFileName, short keySize); 79 | CPPSSH_EXPORT static bool generateDsaKeyPair(const char* fqdn, const char* privKeyFileName, const char* pubKeyFileName, short keySize); 80 | CPPSSH_EXPORT static void create() 81 | { 82 | create(CPPSSH_API_LEVEL_CURRENT); 83 | } 84 | 85 | CPPSSH_EXPORT static void destroy(); 86 | private: 87 | 88 | static void create(int apiLevel); 89 | static bool checkConnectionId(const int connectionId); 90 | static std::shared_ptr s_cppsshInst; 91 | static std::mutex s_cppsshInstMutex; 92 | }; 93 | 94 | class CppsshMessage 95 | { 96 | public: 97 | CppsshMessage& operator=(const CppsshMessage&); 98 | CPPSSH_EXPORT CppsshMessage(); 99 | CPPSSH_EXPORT virtual ~CppsshMessage(); 100 | CPPSSH_EXPORT const uint8_t* message() const; 101 | CPPSSH_EXPORT size_t length() const; 102 | friend class CppsshConstPacket; 103 | friend class CppsshChannel; 104 | private: 105 | virtual void setMessage(const uint8_t* message, size_t bytes); 106 | uint8_t* _message; 107 | size_t _len; 108 | }; 109 | 110 | #endif 111 | -------------------------------------------------------------------------------- /src/transportcrypto.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | cppssh - C++ ssh library 3 | Copyright (C) 2015 Chris Desjardins 4 | http://blog.chrisd.info cjd@chrisd.info 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | */ 19 | #include "transportcrypto.h" 20 | #include "crypto.h" 21 | #include "channel.h" 22 | #include "debug.h" 23 | 24 | CppsshTransportCrypto::CppsshTransportCrypto(const std::shared_ptr& session, SOCKET sock) 25 | : CppsshTransportThreaded(session), 26 | _txSeq(3), 27 | _rxSeq(3) 28 | { 29 | _sock = sock; 30 | } 31 | 32 | CppsshTransportCrypto::~CppsshTransportCrypto() 33 | { 34 | cdLog(LogLevel::Debug) << "~CppsshTransportCrypto"; 35 | stopThreads(); 36 | } 37 | 38 | bool CppsshTransportCrypto::sendMessage(const Botan::secure_vector& buffer) 39 | { 40 | bool ret = true; 41 | Botan::secure_vector crypted; 42 | Botan::secure_vector hmac; 43 | Botan::secure_vector buf; 44 | setupMessage(buffer, &buf); 45 | if (_session->_crypto->encryptPacket(&crypted, &hmac, buf.data(), buf.size(), _txSeq) == false) 46 | { 47 | cdLog(LogLevel::Error) << "Failure to encrypt the payload."; 48 | ret = false; 49 | } 50 | else 51 | { 52 | crypted += hmac; 53 | if (CppsshTransport::sendMessage(crypted) == false) 54 | { 55 | ret = false; 56 | } 57 | if (ret == true) 58 | { 59 | _txSeq++; 60 | } 61 | } 62 | return ret; 63 | } 64 | 65 | void CppsshTransportCrypto::rxThread() 66 | { 67 | cdLog(LogLevel::Debug) << "starting crypto rx thread"; 68 | try 69 | { 70 | Botan::secure_vector decrypted; 71 | const uint32_t decryptBlockSize = _session->_crypto->getDecryptBlockSize(); 72 | const uint32_t macSize = _session->_crypto->getMacInLen(); 73 | while (_running == true) 74 | { 75 | uint32_t cryptoLen = 0; 76 | 77 | if (_in.size() < decryptBlockSize) 78 | { 79 | if (receiveMessage(&_in, decryptBlockSize) == false) 80 | { 81 | break; 82 | } 83 | } 84 | _session->_crypto->decryptPacket(&decrypted, _in.data(), decryptBlockSize); 85 | CppsshConstPacket cpacket(&decrypted); 86 | cryptoLen = cpacket.getCryptoLength(); 87 | if (_in.size() < cryptoLen + macSize) 88 | { 89 | if (receiveMessage(&_in, cryptoLen + macSize) == false) 90 | { 91 | break; 92 | } 93 | } 94 | if ((cryptoLen > decryptBlockSize) && (_in.size() >= cryptoLen)) 95 | { 96 | _session->_crypto->decryptPacket(&decrypted, 97 | _in.data() + decryptBlockSize, cryptoLen - decryptBlockSize); 98 | } 99 | if (computeMac(decrypted, &cryptoLen) == false) 100 | { 101 | break; 102 | } 103 | if (processIncomingData(&_in, decrypted, cryptoLen) == true) 104 | { 105 | _rxSeq++; 106 | } 107 | decrypted.clear(); 108 | } 109 | } 110 | catch (const std::exception& ex) 111 | { 112 | cdLog(LogLevel::Error) << "rxThread exception: " << ex.what(); 113 | CppsshDebug::dumpStack(_session->getConnectionId()); 114 | } 115 | _running = false; 116 | cdLog(LogLevel::Debug) << "crypto rx thread done"; 117 | } 118 | 119 | bool CppsshTransportCrypto::computeMac(const Botan::secure_vector& decrypted, uint32_t* cryptoLen) 120 | { 121 | bool ret = true; 122 | const uint32_t macSize = _session->_crypto->getMacInLen(); 123 | if (macSize > 0) 124 | { 125 | if (_in.size() >= ((*cryptoLen) + macSize)) 126 | { 127 | Botan::secure_vector ourMac; 128 | _session->_crypto->computeMac(&ourMac, decrypted, _rxSeq); 129 | 130 | if (std::equal(_in.begin() + (*cryptoLen), _in.begin() + (*cryptoLen) + macSize, ourMac.begin()) == false) 131 | { 132 | cdLog(LogLevel::Error) << "Mismatched HMACs."; 133 | ret = false; 134 | } 135 | else 136 | { 137 | *cryptoLen += macSize; 138 | } 139 | } 140 | else 141 | { 142 | cdLog(LogLevel::Error) << "Unable to compute HMAC due to lack of data."; 143 | ret = false; 144 | } 145 | } 146 | return ret; 147 | } 148 | -------------------------------------------------------------------------------- /src/transportthreaded.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | cppssh - C++ ssh library 3 | Copyright (C) 2015 Chris Desjardins 4 | http://blog.chrisd.info cjd@chrisd.info 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | */ 19 | 20 | #include "transportthreaded.h" 21 | #include "crypto.h" 22 | #include "channel.h" 23 | #include "debug.h" 24 | 25 | CppsshTransportThreaded::CppsshTransportThreaded(const std::shared_ptr& session) 26 | : CppsshTransport(session) 27 | { 28 | } 29 | 30 | CppsshTransportThreaded::~CppsshTransportThreaded() 31 | { 32 | cdLog(LogLevel::Debug) << "~CppsshTransportThreaded"; 33 | stopThreads(); 34 | } 35 | 36 | void CppsshTransportThreaded::stopThreads() 37 | { 38 | _running = false; 39 | if (_rxThread.joinable() == true) 40 | { 41 | _rxThread.join(); 42 | } 43 | if (_txThread.joinable() == true) 44 | { 45 | _txThread.join(); 46 | } 47 | } 48 | 49 | bool CppsshTransportThreaded::startThreads() 50 | { 51 | _rxThread = std::thread(&CppsshTransportThreaded::rxThread, this); 52 | _txThread = std::thread(&CppsshTransportThreaded::txThread, this); 53 | return true; 54 | } 55 | 56 | bool CppsshTransportThreaded::setupMessage(const Botan::secure_vector& buffer, 57 | Botan::secure_vector* outBuf) 58 | { 59 | bool ret = true; 60 | size_t length = buffer.size(); 61 | CppsshPacket out(outBuf); 62 | Botan::byte padLen; 63 | uint32_t packetLen; 64 | 65 | uint32_t encryptBlockSize = _session->_crypto->getEncryptBlockSize(); 66 | if (encryptBlockSize == 0) 67 | { 68 | encryptBlockSize = 8; 69 | } 70 | 71 | padLen = (Botan::byte)(3 + encryptBlockSize - ((length + 8) % encryptBlockSize)); 72 | packetLen = 1 + length + padLen; 73 | 74 | out.addInt(packetLen); 75 | out.addByte(padLen); 76 | out.addVector(buffer); 77 | 78 | Botan::secure_vector padBytes; 79 | padBytes.resize(padLen, 0); 80 | out.addVector(padBytes); 81 | return ret; 82 | } 83 | 84 | bool CppsshTransportThreaded::sendMessage(const Botan::secure_vector& buffer) 85 | { 86 | bool ret; 87 | Botan::secure_vector buf; 88 | setupMessage(buffer, &buf); 89 | ret = CppsshTransport::sendMessage(buf); 90 | return ret; 91 | } 92 | 93 | void CppsshTransportThreaded::rxThread() 94 | { 95 | cdLog(LogLevel::Debug) << "starting rx thread"; 96 | try 97 | { 98 | Botan::secure_vector incoming; 99 | size_t size = 0; 100 | while (_running == true) 101 | { 102 | if (incoming.size() < sizeof(uint32_t)) 103 | { 104 | size = sizeof(uint32_t); 105 | } 106 | if (receiveMessage(&incoming, size) == true) 107 | { 108 | CppsshPacket packet(&incoming); 109 | size = packet.getCryptoLength(); 110 | if (incoming.size() >= size) 111 | { 112 | processIncomingData(&incoming, incoming, size); 113 | size = packet.getCryptoLength(); 114 | } 115 | } 116 | else 117 | { 118 | break; 119 | } 120 | } 121 | } 122 | catch (const std::exception& ex) 123 | { 124 | cdLog(LogLevel::Error) << "rxThread exception: " << ex.what(); 125 | CppsshDebug::dumpStack(_session->getConnectionId()); 126 | } 127 | _running = false; 128 | cdLog(LogLevel::Debug) << "rx thread done"; 129 | } 130 | 131 | void CppsshTransportThreaded::txThread() 132 | { 133 | cdLog(LogLevel::Debug) << "starting tx thread"; 134 | try 135 | { 136 | while (_running == true) 137 | { 138 | if (_session->_channel->flushOutgoingChannelData() == false) 139 | { 140 | break; 141 | } 142 | std::this_thread::sleep_for(std::chrono::milliseconds(1)); 143 | sendKeepAlive(); 144 | } 145 | } 146 | catch (const std::exception& ex) 147 | { 148 | cdLog(LogLevel::Error) << "txThread exception: " << ex.what(); 149 | CppsshDebug::dumpStack(_session->getConnectionId()); 150 | } 151 | cdLog(LogLevel::Debug) << "tx thread done"; 152 | } 153 | 154 | bool CppsshTransportThreaded::processIncomingData(Botan::secure_vector* inBuf, 155 | const Botan::secure_vector& incoming, 156 | uint32_t dataLen) const 157 | { 158 | bool dataProcessed = false; 159 | if ((_running == true) && (incoming.empty() == false)) 160 | { 161 | dataProcessed = true; 162 | _session->_channel->handleReceived(incoming); 163 | if (inBuf->size() == dataLen) 164 | { 165 | inBuf->clear(); 166 | } 167 | else 168 | { 169 | inBuf->erase(inBuf->begin(), inBuf->begin() + dataLen); 170 | } 171 | } 172 | return dataProcessed; 173 | } 174 | -------------------------------------------------------------------------------- /src/crypto.h: -------------------------------------------------------------------------------- 1 | /* 2 | cppssh - C++ ssh library 3 | Copyright (C) 2015 Chris Desjardins 4 | http://blog.chrisd.info cjd@chrisd.info 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | */ 19 | #ifndef _CRYPTO_Hxx 20 | #define _CRYPTO_Hxx 21 | 22 | #include "crypto.h" 23 | #include "session.h" 24 | #include "botan/hmac.h" 25 | #include "botan/dh.h" 26 | #include "botan/dsa.h" 27 | #include "botan/rsa.h" 28 | #include "botan/filters.h" 29 | #include "botan/pipe.h" 30 | #include "botan/cbc.h" 31 | #include "cryptoalgos.h" 32 | #include 33 | 34 | class CppsshCrypto 35 | { 36 | public: 37 | CppsshCrypto(const std::shared_ptr& session); 38 | 39 | uint32_t getEncryptBlockSize() const 40 | { 41 | return _encryptBlockSize; 42 | } 43 | 44 | uint32_t getDecryptBlockSize() const 45 | { 46 | return _decryptBlockSize; 47 | } 48 | 49 | bool encryptPacket(Botan::secure_vector* encrypted, Botan::secure_vector* hmac, const Botan::byte* decrypted, uint32_t len, uint32_t seq); 50 | bool decryptPacket(Botan::secure_vector* decrypted, const Botan::byte* encrypted, uint32_t len); 51 | 52 | void computeMac(Botan::secure_vector* hmac, const Botan::secure_vector& packet, uint32_t seq) const; 53 | bool computeH(Botan::secure_vector* result, const Botan::secure_vector& val); 54 | 55 | bool verifySig(const Botan::secure_vector& hostKey, const Botan::secure_vector& sig); 56 | 57 | bool setNegotiatedKex(const kexMethods kexAlgo); 58 | bool setNegotiatedHostkey(const hostkeyMethods hostkeyAlgo); 59 | bool setNegotiatedCryptoC2s(const cryptoMethods cryptoAlgo); 60 | bool setNegotiatedCryptoS2c(const cryptoMethods cryptoAlgo); 61 | bool setNegotiatedMacC2s(const macMethods macAlgo); 62 | bool setNegotiatedMacS2c(const macMethods macAlgo); 63 | bool setNegotiatedCmprsC2s(const compressionMethods cmprsAlgo); 64 | bool setNegotiatedCmprsS2c(const compressionMethods cmprsAlgo); 65 | 66 | bool getKexPublic(Botan::BigInt& publicKey); 67 | bool makeKexSecret(Botan::secure_vector* result, Botan::BigInt& f); 68 | bool makeNewKeys(); 69 | 70 | uint32_t getMacOutLen() const 71 | { 72 | return _c2sMacDigestLen; 73 | } 74 | 75 | uint32_t getMacInLen() const 76 | { 77 | return _s2cMacDigestLen; 78 | } 79 | 80 | private: 81 | std::unique_ptr getMacHashAlgo(macMethods macMethod, uint32_t* macDigestLen) const; 82 | std::unique_ptr getBlockCipher(cryptoMethods cryptoMethod) const; 83 | bool buildCipherPipe(Botan::Cipher_Dir direction, Botan::byte ivID, Botan::byte keyID, Botan::byte macID, cryptoMethods cryptoMethod, macMethods macMethod, uint32_t* macDigestLen, uint32_t* blockSize, Botan::Keyed_Filter** filter, std::unique_ptr& pipe, 84 | std::unique_ptr& hmac, Botan::secure_vector& nonce) const; 85 | 86 | std::shared_ptr getDSAKey(const Botan::secure_vector& hostKey); 87 | std::shared_ptr getRSAKey(const Botan::secure_vector& hostKey); 88 | bool computeKey(const std::string& keyType, Botan::secure_vector* key, Botan::byte ID, uint32_t nBytes) const; 89 | bool setNegotiatedCrypto(const cryptoMethods cryptoAlgo, cryptoMethods* cryptoMethod) const; 90 | bool setNegotiatedMac(const macMethods macAlgo, macMethods* macMethod); 91 | bool setNegotiatedCmprs(const compressionMethods cmprsAlgo, compressionMethods* cmprsMethod) const; 92 | //std::string getCryptAlgo(cryptoMethods crypto) const; 93 | const char* getHashAlgo() const; 94 | //const std::string& getHmacAlgo(macMethods method) const; 95 | size_t maxKeyLengthOf(const std::string& name, cryptoMethods method) const; 96 | void setNonce(Botan::Keyed_Filter* filter, Botan::secure_vector& nonce) const; 97 | 98 | std::shared_ptr _session; 99 | std::unique_ptr _encrypt; 100 | std::unique_ptr _decrypt; 101 | std::unique_ptr _hmacOut; 102 | std::unique_ptr _hmacIn; 103 | Botan::Keyed_Filter* _encryptFilter; 104 | Botan::Keyed_Filter* _decryptFilter; 105 | Botan::secure_vector _c2sNonce; 106 | Botan::secure_vector _s2cNonce; 107 | 108 | uint32_t _encryptBlockSize; 109 | uint32_t _decryptBlockSize; 110 | uint32_t _c2sMacDigestLen; 111 | uint32_t _s2cMacDigestLen; 112 | 113 | macMethods _c2sMacMethod; 114 | macMethods _s2cMacMethod; 115 | kexMethods _kexMethod; 116 | hostkeyMethods _hostkeyMethod; 117 | cryptoMethods _c2sCryptoMethod; 118 | cryptoMethods _s2cCryptoMethod; 119 | compressionMethods _c2sCmprsMethod; 120 | compressionMethods _s2cCmprsMethod; 121 | 122 | std::unique_ptr _privKexKey; 123 | Botan::secure_vector _K; 124 | Botan::secure_vector _H; 125 | }; 126 | 127 | #endif 128 | -------------------------------------------------------------------------------- /src/x11channel.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | cppssh - C++ ssh library 3 | Copyright (C) 2015 Chris Desjardins 4 | http://blog.chrisd.info cjd@chrisd.info 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | */ 19 | 20 | #include "x11channel.h" 21 | #include "cppssh.h" 22 | #include "unparam.h" 23 | #include 24 | 25 | CppsshX11Channel::CppsshX11Channel(const std::shared_ptr& session, const std::string& channelName) 26 | : CppsshSubChannel(session, channelName) 27 | { 28 | cdLog(LogLevel::Debug) << "CppsshX11Channel"; 29 | } 30 | 31 | CppsshX11Channel::~CppsshX11Channel() 32 | { 33 | cdLog(LogLevel::Debug) << "~CppsshX11Channel"; 34 | disconnect(); 35 | } 36 | 37 | void CppsshX11Channel::disconnect() 38 | { 39 | if (_x11transport != nullptr) 40 | { 41 | _x11transport->disconnect(); 42 | } 43 | if (_x11RxThread.joinable() == true) 44 | { 45 | _x11RxThread.join(); 46 | } 47 | if (_x11TxThread.joinable() == true) 48 | { 49 | _x11TxThread.join(); 50 | } 51 | _x11transport.reset(); 52 | } 53 | 54 | bool CppsshX11Channel::startChannel() 55 | { 56 | bool ret = false; 57 | cdLog(LogLevel::Debug) << "startChannel"; 58 | _x11transport.reset(new CppsshTransport(_session)); 59 | if (_x11transport->establishX11() == true) 60 | { 61 | ret = true; 62 | _x11RxThread = std::thread(&CppsshX11Channel::x11RxThread, this); 63 | _x11TxThread = std::thread(&CppsshX11Channel::x11TxThread, this); 64 | } 65 | return ret; 66 | } 67 | 68 | void CppsshX11Channel::x11RxThread() 69 | { 70 | bool first = true; 71 | cdLog(LogLevel::Debug) << "starting x11 rx thread"; 72 | while (_x11transport->isRunning() == true) 73 | { 74 | CppsshMessage message; 75 | if (readChannel(&message) == true) 76 | { 77 | Botan::secure_vector buf((Botan::byte*)message.message(), 78 | (Botan::byte*)message.message() + message.length()); 79 | if (first == true) 80 | { 81 | CppsshPacket magicPacket(&buf); 82 | magicPacket.replace( 83 | message.length() - _session->_channel->_realX11Cookie.size(), _session->_channel->_realX11Cookie); 84 | first = false; 85 | } 86 | _x11transport->sendMessage(buf); 87 | } 88 | } 89 | cdLog(LogLevel::Debug) << "x11 rx thread done"; 90 | } 91 | 92 | void CppsshX11Channel::x11TxThread() 93 | { 94 | cdLog(LogLevel::Debug) << "starting x11 tx thread " << _txChannel; 95 | while (_x11transport->isRunning() == true) 96 | { 97 | Botan::secure_vector buf; 98 | if ((_x11transport->receiveMessage(&buf) == true) && (buf.size() > 0)) 99 | { 100 | writeChannel(buf.data(), buf.size()); 101 | } 102 | } 103 | cdLog(LogLevel::Debug) << "x11 tx thread done " << _txChannel; 104 | } 105 | 106 | void CppsshX11Channel::getDisplay(std::string* display) 107 | { 108 | char* d = getenv("DISPLAY"); 109 | if (d != nullptr) 110 | { 111 | *display = d; 112 | } 113 | if (display->length() == 0) 114 | { 115 | *display = ":0"; 116 | } 117 | } 118 | 119 | bool CppsshX11Channel::runXauth(const std::string& display, std::string* method, 120 | Botan::secure_vector* cookie) 121 | { 122 | bool ret = false; 123 | #ifndef WIN32 124 | std::stringstream xauth; 125 | std::string tmpname; 126 | CppsshChannel::getRandomString(16, &tmpname); 127 | xauth << "/usr/bin/xauth list " << display << " 2> /dev/null" << " 1> " << tmpname; 128 | if (system(xauth.str().c_str()) == 0) 129 | { 130 | Botan::secure_vector buf; 131 | CppsshPacket packet(&buf); 132 | if (packet.addFile(tmpname) == true) 133 | { 134 | std::string magic(buf.begin(), buf.end()); 135 | std::istringstream iss(magic); 136 | std::vector cookies; 137 | std::copy(std::istream_iterator(iss), 138 | std::istream_iterator(), 139 | std::back_inserter(cookies)); 140 | // If there are multiple xauth entries for the display 141 | // then just take the first one. 142 | if (cookies.size() > 3) 143 | { 144 | cookies.erase(cookies.begin() + 3, cookies.end()); 145 | } 146 | if (cookies.size() == 3) 147 | { 148 | *method = cookies[1]; 149 | std::string c(cookies[2]); 150 | for (size_t i = 0; i < c.length(); i += 2) 151 | { 152 | int x; 153 | std::istringstream css(c.substr(i, 2)); 154 | css >> std::hex >> x; 155 | cookie->push_back((Botan::byte)x); 156 | } 157 | ret = true; 158 | } 159 | else 160 | { 161 | cdLog(LogLevel::Error) << "Invalid magic string from \"" << xauth.str() << "\": " << magic; 162 | } 163 | } 164 | else 165 | { 166 | cdLog(LogLevel::Error) << "Unable to read magic file: " << tmpname; 167 | } 168 | } 169 | else 170 | { 171 | cdLog(LogLevel::Error) << "Unable to run command: " << xauth.str(); 172 | } 173 | remove(tmpname.c_str()); 174 | #else 175 | UNREF_PARAM(display); 176 | UNREF_PARAM(method); 177 | UNREF_PARAM(cookie); 178 | #endif 179 | return ret; 180 | } 181 | -------------------------------------------------------------------------------- /src/cppssh.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | cppssh - C++ ssh library 3 | Copyright (C) 2015 Chris Desjardins 4 | http://blog.chrisd.info cjd@chrisd.info 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | */ 19 | 20 | #include "cppssh.h" 21 | #include "impl.h" 22 | 23 | std::shared_ptr Cppssh::s_cppsshInst; 24 | std::mutex Cppssh::s_cppsshInstMutex; 25 | 26 | void Cppssh::create(int apiLevel) 27 | { 28 | if (s_cppsshInst == nullptr) 29 | { 30 | std::unique_lock lock(s_cppsshInstMutex); 31 | if (s_cppsshInst == nullptr) 32 | { 33 | // A quick check to make sure that the header files in an end program are the 34 | // same API level as the library was built with. 35 | if (apiLevel != getApiLevel()) 36 | { 37 | cdLog(LogLevel::Error) << 38 | "API level defined in cppssh.h differs from API level in the cppssh library." << std::endl; 39 | cdLog(LogLevel::Error) << "Current API level: " << apiLevel << " API level in cppssh library: " << 40 | CPPSSH_API_LEVEL_CURRENT << std::endl; 41 | abort(); 42 | } 43 | s_cppsshInst.reset(new CppsshImpl()); 44 | } 45 | } 46 | } 47 | 48 | void Cppssh::destroy() 49 | { 50 | s_cppsshInst.reset(); 51 | } 52 | 53 | const char* Cppssh::getCppsshVersion(bool detailed) 54 | { 55 | const char* ret = CPPSSH_SHORT_VERSION; 56 | if (detailed == true) 57 | { 58 | ret = CPPSSH_FULL_VERSION; 59 | } 60 | return ret; 61 | } 62 | 63 | int Cppssh::getApiLevel() 64 | { 65 | return CPPSSH_API_LEVEL_CURRENT; 66 | } 67 | 68 | CppsshConnectStatus_t Cppssh::connect(int* connectionId, const char* host, const short port, const char* username, 69 | const char* privKeyFile, const char* password, unsigned int timeout, 70 | const bool x11Forwarded, const bool keepAlives, const char* term) 71 | { 72 | CppsshConnectStatus_t ret = CPPSSH_CONNECT_ERROR; 73 | std::shared_ptr cppsshInst = s_cppsshInst; 74 | if (cppsshInst != nullptr) 75 | { 76 | ret = cppsshInst->connect(connectionId, host, port, username, privKeyFile, password, timeout, x11Forwarded, 77 | keepAlives, term); 78 | } 79 | return ret; 80 | } 81 | 82 | bool Cppssh::isConnected(const int connectionId) 83 | { 84 | bool ret = false; 85 | std::shared_ptr cppsshInst = s_cppsshInst; 86 | if (cppsshInst != nullptr) 87 | { 88 | ret = cppsshInst->isConnected(connectionId); 89 | } 90 | return ret; 91 | } 92 | 93 | bool Cppssh::writeString(const int connectionId, const char* data) 94 | { 95 | return write(connectionId, (const uint8_t*)data, strlen(data)); 96 | } 97 | 98 | bool Cppssh::write(const int connectionId, const uint8_t* data, size_t bytes) 99 | { 100 | bool ret = false; 101 | std::shared_ptr cppsshInst = s_cppsshInst; 102 | if (cppsshInst != nullptr) 103 | { 104 | ret = cppsshInst->write(connectionId, data, bytes); 105 | } 106 | return ret; 107 | } 108 | 109 | bool Cppssh::read(const int connectionId, CppsshMessage* data) 110 | { 111 | bool ret = false; 112 | std::shared_ptr cppsshInst = s_cppsshInst; 113 | if (cppsshInst != nullptr) 114 | { 115 | ret = cppsshInst->read(connectionId, data); 116 | } 117 | return ret; 118 | } 119 | 120 | bool Cppssh::windowChange(const int connectionId, const uint32_t cols, const uint32_t rows) 121 | { 122 | bool ret = false; 123 | std::shared_ptr cppsshInst = s_cppsshInst; 124 | if (cppsshInst != nullptr) 125 | { 126 | ret = cppsshInst->windowChange(connectionId, cols, rows); 127 | } 128 | return ret; 129 | } 130 | 131 | bool Cppssh::close(const int connectionId) 132 | { 133 | bool ret = false; 134 | std::shared_ptr cppsshInst = s_cppsshInst; 135 | if (cppsshInst != nullptr) 136 | { 137 | ret = cppsshInst->close(connectionId); 138 | } 139 | return ret; 140 | } 141 | 142 | bool Cppssh::setPreferredCipher(const char* prefCipher) 143 | { 144 | return CppsshImpl::setPreferredCipher(prefCipher); 145 | } 146 | 147 | bool Cppssh::setPreferredHmac(const char* prefHmac) 148 | { 149 | return CppsshImpl::setPreferredHmac(prefHmac); 150 | } 151 | 152 | size_t Cppssh::getSupportedCiphers(char* ciphers) 153 | { 154 | return CppsshImpl::getSupportedCiphers(ciphers); 155 | } 156 | 157 | size_t Cppssh::getSupportedHmacs(char* hmacs) 158 | { 159 | return CppsshImpl::getSupportedHmacs(hmacs); 160 | } 161 | 162 | bool Cppssh::generateRsaKeyPair(const char* fqdn, const char* privKeyFileName, const char* pubKeyFileName, 163 | short keySize) 164 | { 165 | return CppsshImpl::generateRsaKeyPair(fqdn, privKeyFileName, pubKeyFileName, keySize); 166 | } 167 | 168 | bool Cppssh::generateDsaKeyPair(const char* fqdn, const char* privKeyFileName, const char* pubKeyFileName, 169 | short keySize) 170 | { 171 | return CppsshImpl::generateDsaKeyPair(fqdn, privKeyFileName, pubKeyFileName, keySize); 172 | } 173 | 174 | CppsshMessage::CppsshMessage() 175 | : _message(nullptr), 176 | _len(0) 177 | { 178 | } 179 | 180 | CppsshMessage::~CppsshMessage() 181 | { 182 | if (_message != nullptr) 183 | { 184 | delete[] _message; 185 | } 186 | } 187 | 188 | const uint8_t* CppsshMessage::message() const 189 | { 190 | return _message; 191 | } 192 | 193 | CppsshMessage& CppsshMessage::operator=(const CppsshMessage& other) 194 | { 195 | setMessage(other._message, other._len); 196 | return *this; 197 | } 198 | 199 | void CppsshMessage::setMessage(const uint8_t* message, size_t bytes) 200 | { 201 | _message = new uint8_t[bytes + 1]; 202 | _len = bytes; 203 | memcpy(_message, message, _len); 204 | _message[_len] = 0; 205 | } 206 | 207 | size_t CppsshMessage::length() const 208 | { 209 | return _len; 210 | } 211 | -------------------------------------------------------------------------------- /src/cryptoalgos.h: -------------------------------------------------------------------------------- 1 | /* 2 | cppssh - C++ ssh library 3 | Copyright (C) 2015 Chris Desjardins 4 | http://blog.chrisd.info cjd@chrisd.info 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | */ 19 | #ifndef _CRYPTO_ALGOS_Hxx 20 | #define _CRYPTO_ALGOS_Hxx 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include "CDLogger/Logger.h" 27 | #include "strtrim.h" 28 | 29 | template class CryptoStrings 30 | { 31 | public: 32 | CryptoStrings(const T method, const std::string& sshName, const std::string& botanName) 33 | : _method(method), 34 | _sshName(sshName), 35 | _botanName(botanName) 36 | { 37 | } 38 | 39 | static bool ssh2enum(const std::string& sshAlgo, const std::vector& algoList, T* method) 40 | { 41 | bool ret = false; 42 | for (size_t i = 0; ((i < algoList.size()) && (ret == false)); i++) 43 | { 44 | if (sshAlgo == algoList[i]._sshName) 45 | { 46 | ret = true; 47 | (*method) = algoList[i]._method; 48 | } 49 | } 50 | return ret; 51 | } 52 | 53 | static const std::string& enum2name(const T method, const std::vector& algoList, bool sshName) 54 | { 55 | const static std::string fail; 56 | for (size_t i = 0; i < algoList.size(); i++) 57 | { 58 | if (method == algoList[i]._method) 59 | { 60 | if (sshName == true) 61 | { 62 | return algoList[i]._sshName; 63 | } 64 | else 65 | { 66 | return algoList[i]._botanName; 67 | } 68 | } 69 | } 70 | return fail; 71 | } 72 | 73 | bool operator<(const CryptoStrings& a) const 74 | { 75 | return _method < a._method; 76 | } 77 | 78 | T _method; 79 | std::string _sshName; 80 | std::string _botanName; 81 | CryptoStrings() = delete; 82 | }; 83 | 84 | template class CppsshAlgos 85 | { 86 | public: 87 | CppsshAlgos(const std::vector >& algos) 88 | : _algos(algos) 89 | { 90 | std::sort(_algos.begin(), _algos.end()); 91 | } 92 | 93 | CppsshAlgos() = delete; 94 | CppsshAlgos(const CppsshAlgos&) = delete; 95 | virtual ~CppsshAlgos() 96 | { 97 | } 98 | 99 | bool ssh2enum(const std::string& sshAlgo, T* method) const 100 | { 101 | return CryptoStrings::ssh2enum(sshAlgo, _algos, method); 102 | } 103 | 104 | const std::string& enum2botan(const T method) const 105 | { 106 | return CryptoStrings::enum2name(method, _algos, false); 107 | } 108 | 109 | const std::string& enum2ssh(const T method) const 110 | { 111 | return CryptoStrings::enum2name(method, _algos, true); 112 | } 113 | 114 | bool setPref(const char* pref) 115 | { 116 | bool ret = false; 117 | typename std::vector >::iterator it = findSshName(pref); 118 | if (it != _algos.end()) 119 | { 120 | std::iter_swap(_algos.begin(), it); 121 | ret = true; 122 | } 123 | if (ret == false) 124 | { 125 | cdLog(LogLevel::Error) << "Unable to set preferred algorithm: " << pref; 126 | } 127 | return ret; 128 | } 129 | 130 | bool agree(std::string* result, const std::string& remote) const 131 | { 132 | bool ret = false; 133 | std::vector::iterator agreedAlgo; 134 | std::vector remoteVec; 135 | std::string remoteStr((char*)remote.data(), 0, remote.size()); 136 | 137 | StrTrim::split(remoteStr, ',', remoteVec); 138 | 139 | for (const CryptoStrings& algo : _algos) 140 | { 141 | agreedAlgo = std::find(remoteVec.begin(), remoteVec.end(), algo._sshName); 142 | if (agreedAlgo != remoteVec.end()) 143 | { 144 | result->assign(*agreedAlgo); 145 | cdLog(LogLevel::Debug) << "agreed on: " << *result; 146 | ret = true; 147 | break; 148 | } 149 | } 150 | return ret; 151 | } 152 | 153 | void toString(std::string* outstr) const 154 | { 155 | for (const CryptoStrings& algo : _algos) 156 | { 157 | if (outstr->length() > 0) 158 | { 159 | outstr->push_back(','); 160 | } 161 | std::copy(algo._sshName.begin(), algo._sshName.end(), std::back_inserter(*outstr)); 162 | } 163 | } 164 | 165 | protected: 166 | typename std::vector >::iterator findSshName(const std::string& sshName) 167 | { 168 | for (typename std::vector >::iterator it = _algos.begin(); it != _algos.end(); it++) 169 | { 170 | if ((*it)._sshName == sshName) 171 | { 172 | return it; 173 | } 174 | } 175 | return _algos.end(); 176 | } 177 | 178 | std::vector > _algos; 179 | private: 180 | }; 181 | 182 | enum class macMethods 183 | { 184 | HMAC_SHA512, 185 | HMAC_SHA256, 186 | HMAC_SHA1, 187 | HMAC_MD5, 188 | HMAC_RIPEMD160, 189 | HMAC_NONE, 190 | MAX_VALS 191 | }; 192 | 193 | typedef CppsshAlgos CppsshMacAlgos; 194 | 195 | enum class cryptoMethods 196 | { 197 | AES256_CTR, 198 | AES192_CTR, 199 | AES128_CTR, 200 | AES256_CBC, 201 | AES192_CBC, 202 | AES128_CBC, 203 | BLOWFISH_CBC, 204 | CAST128_CBC, 205 | _3DES_CBC, 206 | //TWOFISH_CBC, 207 | //TWOFISH256_CBC, 208 | MAX_VALS 209 | }; 210 | 211 | typedef CppsshAlgos CppsshCryptoAlgos; 212 | 213 | enum class kexMethods 214 | { 215 | DIFFIE_HELLMAN_GROUP16_SHA512, 216 | DIFFIE_HELLMAN_GROUP18_SHA512, 217 | MAX_VALS, 218 | }; 219 | 220 | typedef CppsshAlgos CppsshKexAlgos; 221 | 222 | enum class hostkeyMethods 223 | { 224 | SSH_DSS, 225 | SSH_RSA, 226 | SSH_RSA_SHA2_512, 227 | MAX_VALS 228 | }; 229 | 230 | typedef CppsshAlgos CppsshHostkeyAlgos; 231 | 232 | enum class compressionMethods 233 | { 234 | NONE, 235 | MAX_VALS 236 | }; 237 | 238 | typedef CppsshAlgos CppsshCompressionAlgos; 239 | 240 | #endif 241 | -------------------------------------------------------------------------------- /src/impl.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | cppssh - C++ ssh library 3 | Copyright (C) 2015 Chris Desjardins 4 | http://blog.chrisd.info cjd@chrisd.info 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | */ 19 | 20 | #include "impl.h" 21 | #include "keys.h" 22 | #include "botan/init.h" 23 | 24 | std::mutex CppsshImpl::_optionsMutex; 25 | 26 | CppsshMacAlgos CppsshImpl::MAC_ALGORITHMS(std::vector > 27 | { 28 | CryptoStrings(macMethods::HMAC_SHA1, "hmac-sha1", "SHA-1"), 29 | CryptoStrings(macMethods::HMAC_MD5, "hmac-md5", "MD5"), 30 | CryptoStrings(macMethods::HMAC_NONE, "none", ""), 31 | CryptoStrings(macMethods::HMAC_SHA256, "hmac-sha2-256", "SHA-256"), 32 | CryptoStrings(macMethods::HMAC_SHA256, "hmac-ripemd160", "RIPEMD-160"), 33 | // Removed hmac-sha2-512 support due to bugs in some older version of openssh 34 | // fatal: dh_gen_key: group too small: 1024 (2*need 1024) [preauth] 35 | //CryptoStrings(macMethods::HMAC_SHA512, "hmac-sha2-512", "SHA-512"), 36 | }); 37 | 38 | CppsshCryptoAlgos CppsshImpl::CIPHER_ALGORITHMS(std::vector > 39 | { 40 | CryptoStrings(cryptoMethods::AES256_CTR, "aes256-ctr", "AES-256"), 41 | CryptoStrings(cryptoMethods::AES192_CTR, "aes192-ctr", "AES-192"), 42 | CryptoStrings(cryptoMethods::AES128_CTR, "aes128-ctr", "AES-128"), 43 | CryptoStrings(cryptoMethods::AES256_CBC, "aes256-cbc", "AES-256"), 44 | CryptoStrings(cryptoMethods::AES192_CBC, "aes192-cbc", "AES-192"), 45 | CryptoStrings(cryptoMethods::AES128_CBC, "aes128-cbc", "AES-128"), 46 | CryptoStrings(cryptoMethods::BLOWFISH_CBC, "blowfish-cbc", "Blowfish"), 47 | CryptoStrings(cryptoMethods::_3DES_CBC, "3des-cbc", "TripleDES"), 48 | CryptoStrings(cryptoMethods::CAST128_CBC, "cast128-cbc", "CAST-128"), 49 | }); 50 | CppsshKexAlgos CppsshImpl::KEX_ALGORITHMS(std::vector > 51 | { 52 | CryptoStrings(kexMethods::DIFFIE_HELLMAN_GROUP16_SHA512, "diffie-hellman-group16-sha512", "modp/ietf/4096"), 53 | CryptoStrings(kexMethods::DIFFIE_HELLMAN_GROUP18_SHA512, "diffie-hellman-group18-sha512", "modp/ietf/8192"), 54 | }); 55 | CppsshHostkeyAlgos CppsshImpl::HOSTKEY_ALGORITHMS(std::vector > 56 | { 57 | CryptoStrings(hostkeyMethods::SSH_DSS, "ssh-dss", "EMSA1(SHA-1)"), 58 | CryptoStrings(hostkeyMethods::SSH_RSA, "ssh-rsa", "EMSA3(SHA-1)"), 59 | CryptoStrings(hostkeyMethods::SSH_RSA_SHA2_512, "rsa-sha2-512", "EMSA3(SHA-512)"), 60 | }); 61 | CppsshCompressionAlgos CppsshImpl::COMPRESSION_ALGORITHMS(std::vector > 62 | { 63 | CryptoStrings(compressionMethods::NONE, "none", ""), 64 | }); 65 | 66 | std::shared_ptr CppsshImpl::RNG; 67 | 68 | CppsshImpl::CppsshImpl() 69 | : _connectionId(0) 70 | { 71 | RNG.reset(new Botan::Serialized_RNG(new Botan::AutoSeeded_RNG())); 72 | } 73 | 74 | CppsshImpl::~CppsshImpl() 75 | { 76 | RNG.reset(); 77 | } 78 | 79 | CppsshConnectStatus_t CppsshImpl::connect(int* connectionId, const char* host, const short port, const char* username, 80 | const char* privKeyFile, const char* password, unsigned int timeout, 81 | const bool x11Forwarded, const bool keepAlives, const char* term) 82 | { 83 | CppsshConnectStatus_t ret = CPPSSH_CONNECT_ERROR; 84 | std::shared_ptr con; 85 | {// new scope for mutex 86 | std::unique_lock lock(_connectionsMutex); 87 | *connectionId = ++_connectionId; 88 | con.reset(new CppsshConnection(*connectionId, timeout)); 89 | _connections[*connectionId] = con; 90 | } 91 | if (con != nullptr) 92 | { 93 | ret = con->connect(host, port, username, privKeyFile, password, x11Forwarded, keepAlives, term); 94 | if (ret != CPPSSH_CONNECT_OK) 95 | { 96 | close(*connectionId); 97 | } 98 | } 99 | return ret; 100 | } 101 | 102 | bool CppsshImpl::isConnected(const int connectionId) 103 | { 104 | bool ret = false; 105 | std::shared_ptr con = getConnection(connectionId); 106 | if (con != nullptr) 107 | { 108 | ret = con->isConnected(); 109 | } 110 | return ret; 111 | } 112 | 113 | bool CppsshImpl::write(const int connectionId, const uint8_t* data, size_t bytes) 114 | { 115 | bool ret = false; 116 | std::shared_ptr con = getConnection(connectionId); 117 | if (con != nullptr) 118 | { 119 | ret = con->write(data, bytes); 120 | } 121 | return ret; 122 | } 123 | 124 | bool CppsshImpl::read(const int connectionId, CppsshMessage* data) 125 | { 126 | bool ret = false; 127 | std::shared_ptr con = getConnection(connectionId); 128 | if (con != nullptr) 129 | { 130 | ret = con->read(data); 131 | } 132 | return ret; 133 | } 134 | 135 | bool CppsshImpl::windowChange(const int connectionId, const uint32_t cols, const uint32_t rows) 136 | { 137 | bool ret = false; 138 | std::shared_ptr con = getConnection(connectionId); 139 | if (con != nullptr) 140 | { 141 | ret = con->windowChange(cols, rows); 142 | } 143 | return ret; 144 | } 145 | 146 | bool CppsshImpl::close(int connectionId) 147 | { 148 | std::unique_lock lock(_connectionsMutex); 149 | if (checkConnectionId(connectionId) == true) 150 | { 151 | _connections[connectionId]->closeConnection(); 152 | _connections[connectionId].reset(); 153 | _connections.erase(connectionId); 154 | } 155 | return true; 156 | } 157 | 158 | bool CppsshImpl::setPreferredCipher(const char* prefCipher) 159 | { 160 | std::unique_lock lock(_optionsMutex); 161 | return CppsshImpl::CIPHER_ALGORITHMS.setPref(prefCipher); 162 | } 163 | 164 | bool CppsshImpl::setPreferredHmac(const char* prefHmac) 165 | { 166 | std::unique_lock lock(_optionsMutex); 167 | return CppsshImpl::MAC_ALGORITHMS.setPref(prefHmac); 168 | } 169 | 170 | template size_t CppsshImpl::getSupportedAlogs(const T& algos, char* list) 171 | { 172 | size_t ret; 173 | std::string str; 174 | algos.toString(&str); 175 | ret = str.length(); 176 | if (list != nullptr) 177 | { 178 | for (auto it = str.cbegin(); it != str.cend(); it++) 179 | { 180 | *list = *it; 181 | list++; 182 | *list = 0; 183 | } 184 | } 185 | return ret; 186 | } 187 | 188 | size_t CppsshImpl::getSupportedCiphers(char* ciphers) 189 | { 190 | return CppsshImpl::getSupportedAlogs(CppsshImpl::CIPHER_ALGORITHMS, ciphers); 191 | } 192 | 193 | size_t CppsshImpl::getSupportedHmacs(char* hmacs) 194 | { 195 | return CppsshImpl::getSupportedAlogs(CppsshImpl::MAC_ALGORITHMS, hmacs); 196 | } 197 | 198 | bool CppsshImpl::generateRsaKeyPair(const char* fqdn, const char* privKeyFileName, const char* pubKeyFileName, 199 | short keySize) 200 | { 201 | return CppsshKeys::generateRsaKeyPair(fqdn, privKeyFileName, pubKeyFileName, keySize); 202 | } 203 | 204 | bool CppsshImpl::generateDsaKeyPair(const char* fqdn, const char* privKeyFileName, const char* pubKeyFileName, 205 | short keySize) 206 | { 207 | return CppsshKeys::generateDsaKeyPair(fqdn, privKeyFileName, pubKeyFileName, keySize); 208 | } 209 | 210 | std::shared_ptr CppsshImpl::getConnection(const int connectionId) 211 | { 212 | std::shared_ptr con; 213 | { 214 | std::unique_lock lock(_connectionsMutex); 215 | if (checkConnectionId(connectionId) == true) 216 | { 217 | con = _connections[connectionId]; 218 | } 219 | } 220 | return con; 221 | } 222 | 223 | bool CppsshImpl::checkConnectionId(const int connectionId) 224 | { 225 | bool ret = false; 226 | 227 | if (_connections.find(connectionId) != _connections.end()) 228 | { 229 | ret = true; 230 | } 231 | return ret; 232 | } 233 | -------------------------------------------------------------------------------- /src/subchannel.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | cppssh - C++ ssh library 3 | Copyright (C) 2015 Chris Desjardins 4 | http://blog.chrisd.info cjd@chrisd.info 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | */ 19 | #include "cppssh.h" 20 | #include "session.h" 21 | #include "subchannel.h" 22 | #include "messages.h" 23 | 24 | #define CPPSSH_RX_WINDOW_SIZE (CPPSSH_MAX_PACKET_LEN * 150) 25 | 26 | CppsshSubChannel::CppsshSubChannel(const std::shared_ptr& session, const std::string& channelName) 27 | : _session(session), 28 | _windowRecv(CPPSSH_RX_WINDOW_SIZE), 29 | _windowSend(0), 30 | _txChannel(0), 31 | _maxPacket(0), 32 | _channelName(channelName) 33 | { 34 | } 35 | 36 | void CppsshSubChannel::sendAdjustWindow() 37 | { 38 | uint32_t len = CPPSSH_RX_WINDOW_SIZE - _windowRecv; 39 | Botan::secure_vector buf; 40 | CppsshPacket packet(&buf); 41 | packet.addByte(SSH2_MSG_CHANNEL_WINDOW_ADJUST); 42 | packet.addInt(_txChannel); 43 | packet.addInt(len); 44 | _windowRecv += len; 45 | _session->_transport->sendMessage(buf); 46 | } 47 | 48 | void CppsshSubChannel::handleEof() 49 | { 50 | cdLog(LogLevel::Debug) << "handleeof " << _channelName << " txChannel: " << _txChannel; 51 | Botan::secure_vector buf; 52 | CppsshPacket packet(&buf); 53 | packet.addByte(SSH2_MSG_CHANNEL_EOF); 54 | packet.addInt(_txChannel); 55 | _session->_transport->sendMessage(buf); 56 | } 57 | 58 | void CppsshSubChannel::handleClose() 59 | { 60 | cdLog(LogLevel::Debug) << "handleclose " << _channelName << " txChannel: " << _txChannel; 61 | Botan::secure_vector buf; 62 | CppsshPacket packet(&buf); 63 | packet.addByte(SSH2_MSG_CHANNEL_CLOSE); 64 | packet.addInt(_txChannel); 65 | _session->_transport->sendMessage(buf); 66 | } 67 | 68 | void CppsshSubChannel::handleChannelRequest(const Botan::secure_vector& buf) 69 | { 70 | Botan::byte response = SSH2_MSG_CHANNEL_FAILURE; 71 | std::string request; 72 | 73 | CppsshConstPacket packet(&buf); 74 | packet.skipHeader(); 75 | packet.getInt(); 76 | packet.getString(&request); 77 | Botan::byte wantReply = packet.getByte(); 78 | if (request == "exit-status") 79 | { 80 | response = SSH2_MSG_CHANNEL_SUCCESS; 81 | } 82 | else if ((request == "pty-req") || (request == "x11-req") || (request == "env") || 83 | (request == "shell") || (request == "exec") || (request == "subsystem") || 84 | (request == "window-change") || (request == "xon-xoff") || (request == "signal") || 85 | (request == "exit-status") || (request == "exit-signal")) 86 | { 87 | cdLog(LogLevel::Error) << "Unhandled channel request: " << request; 88 | } 89 | else 90 | { 91 | cdLog(LogLevel::Error) << "Unknown channel request: " << request; 92 | } 93 | if (wantReply != 0) 94 | { 95 | Botan::secure_vector resp; 96 | CppsshPacket respPkt(&resp); 97 | respPkt.addByte(response); 98 | respPkt.addInt(_txChannel); 99 | _session->_transport->sendMessage(resp); 100 | } 101 | } 102 | 103 | void CppsshSubChannel::handleIncomingChannelData(const Botan::secure_vector& buf) 104 | { 105 | CppsshConstPacket packet(&buf); 106 | std::shared_ptr message(new CppsshMessage()); 107 | packet.skipHeader(); 108 | // rx channel 109 | /*uint32_t rxChannel = */ packet.getInt(); 110 | packet.getChannelData(message.get()); 111 | _windowRecv -= message->length(); 112 | if (_windowRecv < (CPPSSH_RX_WINDOW_SIZE / 2)) 113 | { 114 | sendAdjustWindow(); 115 | } 116 | _incomingChannelData.enqueue(message); 117 | } 118 | 119 | void CppsshSubChannel::handleIncomingControlData(const Botan::secure_vector& buf) 120 | { 121 | _incomingControlData.enqueue(buf); 122 | CppsshConstPacket packet(&buf); 123 | } 124 | 125 | void CppsshSubChannel::setParameters(uint32_t windowSend, uint32_t txChannel, uint32_t maxPacket) 126 | { 127 | _windowSend = windowSend; 128 | _txChannel = txChannel; 129 | _maxPacket = maxPacket; 130 | } 131 | 132 | bool CppsshSubChannel::flushOutgoingChannelData() 133 | { 134 | bool ret = true; 135 | while (_outgoingChannelData.size() > 0) 136 | { 137 | std::shared_ptr > message; 138 | if ((_outgoingChannelData.dequeue(message, 1) == true) && (message->size() > 0)) 139 | { 140 | _windowSend -= message->size(); 141 | Botan::secure_vector buf; 142 | CppsshPacket packet(&buf); 143 | packet.addByte(SSH2_MSG_CHANNEL_DATA); 144 | packet.addInt(_txChannel); 145 | packet.addInt(message->size()); 146 | packet.addVector(*message); 147 | ret = _session->_transport->sendMessage(buf); 148 | if (ret == false) 149 | { 150 | break; 151 | } 152 | } 153 | else 154 | { 155 | break; 156 | } 157 | } 158 | return ret; 159 | } 160 | 161 | bool CppsshSubChannel::writeChannel(const uint8_t* data, uint32_t bytes) 162 | { 163 | uint32_t totalBytesSent = 0; 164 | std::shared_ptr > message; 165 | uint32_t maxPacketSize = _maxPacket - 64; 166 | while (totalBytesSent < bytes) 167 | { 168 | uint32_t bytesSent = std::min(bytes, maxPacketSize); 169 | message.reset(new Botan::secure_vector()); 170 | CppsshPacket packet(message.get()); 171 | packet.addRawData(data, bytesSent); 172 | totalBytesSent += bytesSent; 173 | _outgoingChannelData.enqueue(message); 174 | } 175 | return (totalBytesSent == bytes); 176 | } 177 | 178 | bool CppsshSubChannel::readChannel(CppsshMessage* data) 179 | { 180 | std::shared_ptr m; 181 | bool ret = _incomingChannelData.dequeue(m, 1); 182 | if (ret == true) 183 | { 184 | *data = *m; 185 | } 186 | return ret; 187 | } 188 | 189 | bool CppsshSubChannel::windowChange(const uint32_t cols, const uint32_t rows) 190 | { 191 | bool ret; 192 | Botan::secure_vector buf; 193 | CppsshPacket packet(&buf); 194 | 195 | cdLog(LogLevel::Debug) << "windowChange[" << _session->getConnectionId() << "]: (" << cols << ", " << rows << ")"; 196 | 197 | packet.addInt(cols); 198 | packet.addInt(rows); 199 | packet.addInt(0); 200 | packet.addInt(0); 201 | ret = doChannelRequest("window-change", buf, false); 202 | return ret; 203 | } 204 | 205 | bool CppsshSubChannel::handleChannelConfirm() 206 | { 207 | bool ret = false; 208 | Botan::secure_vector buf; 209 | if (_incomingControlData.dequeue(buf, _session->getTimeout()) == false) 210 | { 211 | cdLog(LogLevel::Error) << "New channel: " << /* channelId << FIXME: rx channel id */ " could not be open. "; 212 | } 213 | else 214 | { 215 | const CppsshConstPacket packet(&buf); 216 | 217 | if (packet.getCommand() == SSH2_MSG_CHANNEL_OPEN_CONFIRMATION) 218 | { 219 | packet.skipHeader(); 220 | // Receive Channel 221 | //uint32_t rxChannel = packet.getInt(); 222 | packet.getInt(); 223 | _txChannel = packet.getInt(); 224 | _windowSend = packet.getInt(); 225 | _maxPacket = packet.getInt(); 226 | ret = true; 227 | } 228 | } 229 | return ret; 230 | } 231 | 232 | bool CppsshSubChannel::doChannelRequest(const std::string& req, const Botan::secure_vector& reqdata, 233 | bool wantReply) 234 | { 235 | bool ret = false; 236 | Botan::secure_vector buf; 237 | CppsshPacket packet(&buf); 238 | packet.addByte(SSH2_MSG_CHANNEL_REQUEST); 239 | packet.addInt(_txChannel); 240 | packet.addString(req); 241 | packet.addByte(wantReply);// want reply == true 242 | packet.addVector(reqdata); 243 | 244 | if (_session->_transport->sendMessage(buf) == true) 245 | { 246 | if (wantReply == true) 247 | { 248 | if ((_incomingControlData.dequeue(buf, _session->getTimeout()) == true) && 249 | (packet.getCommand() == SSH2_MSG_CHANNEL_SUCCESS)) 250 | { 251 | ret = true; 252 | } 253 | else 254 | { 255 | cdLog(LogLevel::Error) << "Unable to send channel request: " << req; 256 | } 257 | } 258 | else 259 | { 260 | ret = true; 261 | } 262 | } 263 | return ret; 264 | } 265 | 266 | void CppsshSubChannel::handleBanner(const std::shared_ptr& banner) 267 | { 268 | _incomingChannelData.enqueue(banner); 269 | } 270 | 271 | uint32_t CppsshSubChannel::getRxWindowSize() 272 | { 273 | return CPPSSH_RX_WINDOW_SIZE; 274 | } 275 | -------------------------------------------------------------------------------- /test/testalgos.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import unittest, os, shutil, difflib, stat, random, argparse, sys 3 | from subprocess import call 4 | 5 | class TestAlgos(unittest.TestCase): 6 | 7 | ciphers = [ 8 | "aes128-cbc", 9 | "aes192-cbc", 10 | "aes256-cbc", 11 | "aes128-ctr", 12 | "aes192-ctr", 13 | "aes256-ctr", 14 | #"twofish-cbc", 15 | #"twofish256-cbc", 16 | "blowfish-cbc", 17 | "3des-cbc", 18 | "cast128-cbc" 19 | ] 20 | macs = [ 21 | "hmac-md5", 22 | "hmac-sha1", 23 | #"hmac-sha2-512", 24 | "hmac-sha2-256", 25 | "none" 26 | ] 27 | keys = [ 28 | "rsa", 29 | "dsa", 30 | "" 31 | ] 32 | passwords = [ 33 | "", 34 | "testpw" 35 | ] 36 | testlogIgnores = [ 37 | "Kex algos", 38 | "Cipher algos", 39 | "MAC algos", 40 | "Compression algos", 41 | "Hostkey algos", 42 | " agreed on: ", 43 | "Authenticated with", 44 | "Remote version: " 45 | ] 46 | testoutputIgnores = [ 47 | "Last login:", 48 | "SSH_CLIENT=", 49 | "SSH_CONNECTION=", 50 | "SSH_TTY=", 51 | "DISPLAY=", 52 | "XDG_RUNTIME_DIR=", 53 | "XDG_SESSION_ID=" 54 | 55 | ] 56 | testCases = [] 57 | verificationErrors = [] 58 | diffs = {} 59 | actualResultsBaseDir = "actualResults" 60 | expectedResultsBaseDir = "expectedResults" 61 | keysBaseDir = "keys" 62 | 63 | @classmethod 64 | def setUpClass(cls): 65 | for key in cls.keys: 66 | for password in cls.passwords: 67 | for cipher in cls.ciphers: 68 | for mac in cls.macs: 69 | test = { 'key': key, 'password': password, 'cipher': cipher, 'mac': mac } 70 | cls.testCases.append(test) 71 | 72 | if (args.all == False): 73 | cls.setupSubset() 74 | 75 | if (os.path.exists(cls.actualResultsBaseDir) == True): 76 | shutil.rmtree(cls.actualResultsBaseDir) 77 | os.makedirs(cls.actualResultsBaseDir) 78 | 79 | if (os.path.exists(cls.keysBaseDir) == True): 80 | shutil.rmtree(cls.keysBaseDir) 81 | os.mkdir(cls.keysBaseDir) 82 | 83 | @classmethod 84 | def setupSubset(cls): 85 | print("Running a subset of tests\n\n\n") 86 | random.seed() 87 | # Run a fraction of the tests 88 | numToRemove = len(cls.testCases) - (len(cls.testCases) / 5) 89 | for cnt in range(0, numToRemove): 90 | index = random.randint(0, len(cls.testCases) - 1) 91 | cls.testCases.remove(cls.testCases[index]) 92 | 93 | def myAssertEqual(self, a, b, msg=None): 94 | try: 95 | self.assertEqual(a, b, msg) 96 | except AssertionError as e: 97 | print(str(e)) 98 | self.verificationErrors.append(str(e)) 99 | 100 | def myAssertTrue(self, a, msg=None): 101 | try: 102 | self.assertTrue(a, msg) 103 | except AssertionError as e: 104 | print(str(e)) 105 | self.verificationErrors.append(str(e)) 106 | 107 | def cutTimeStamp(self, l): 108 | return l[24:] 109 | 110 | def shouldIgnore(self, l, ignoreLines): 111 | ignore = False 112 | for ignoreLine in ignoreLines: 113 | if (ignoreLine in l): 114 | ignore = True 115 | break; 116 | return ignore 117 | 118 | def getFileContent(self, filename, cutTimeStamp, ignoreLines): 119 | ret = [] 120 | try: 121 | with open(filename) as f: 122 | for line in f: 123 | l = line.strip() 124 | if (cutTimeStamp == True): 125 | l = self.cutTimeStamp(l) 126 | if (self.shouldIgnore(l, ignoreLines) == False): 127 | ret.append(l) 128 | except IOError: 129 | pass 130 | return sorted(ret) 131 | 132 | def cmpOutputFiles(self, filename, actualResultsDir, expectedResultsDir, cutTimeStamp, ignoreLines, verbose): 133 | verified = True 134 | difflist = [] 135 | if (os.path.exists(actualResultsDir) == False): 136 | os.makedirs(actualResultsDir) 137 | actualResultsFileName = os.path.join(actualResultsDir, filename) 138 | if (os.path.exists(filename) == True): 139 | shutil.copy(filename, actualResultsDir) 140 | os.remove(filename) 141 | expectedResultsFileName = os.path.join(expectedResultsDir, filename) 142 | actualResults = self.getFileContent(actualResultsFileName, cutTimeStamp, ignoreLines) 143 | expectedResults = self.getFileContent(expectedResultsFileName, cutTimeStamp, ignoreLines) 144 | difflist = list(difflib.context_diff(actualResults, expectedResults)) 145 | if (verbose == True): 146 | self.myAssertTrue(len(actualResults) > 0, "No actual output in " + actualResultsFileName) 147 | self.myAssertEqual(len(difflist), 0, "Differences in: " + actualResultsFileName + " " + expectedResultsFileName) 148 | if (len(difflist) > 0): 149 | verified = False 150 | if (verbose == True): 151 | self.diffs[actualResultsFileName] = expectedResultsFileName 152 | for d in difflist: 153 | print(d) 154 | else: 155 | print("Unable to find: " + actualResultsFileName) 156 | verified = False 157 | return verified 158 | 159 | def verifyAlgos(self, cipher, mac, actualResultsFileName, verbose): 160 | actualResults = "\n".join(self.getFileContent(actualResultsFileName, False, [])) 161 | verified = False 162 | if ((" agreed on: " + cipher in actualResults) and (" agreed on: " + mac in actualResults)): 163 | verified = True 164 | elif (verbose == True): 165 | self.diffs[actualResultsFileName] = cipher + " / " + mac 166 | self.myAssertTrue(verified, "Cipher or mac not found in " + actualResultsFileName + " " + actualResults) 167 | return verified 168 | 169 | def runAlgoTest(self, password, cipher, mac, keyfile): 170 | for i in range(0, 2): 171 | cmd = "../../install/bin/cppsshtestalgos 192.168.1.19 algotester " + password + " " + cipher + " " + mac + " " + keyfile 172 | print("Testing[" + str(i) + "]: " + cmd) 173 | call(cmd.split(" ")) 174 | directory = os.path.join(cipher, mac) 175 | if (len(keyfile) > 0): 176 | directory = os.path.join(directory, os.path.basename(keyfile)) 177 | actualResultsDir = os.path.join(self.actualResultsBaseDir, directory) 178 | passCnt = self.cmpOutputFiles("testlog.txt", actualResultsDir, self.expectedResultsBaseDir, True, self.testlogIgnores, bool(i)) 179 | passCnt += self.cmpOutputFiles("testoutput.txt", actualResultsDir, self.expectedResultsBaseDir, False, self.testoutputIgnores, bool(i)) 180 | passCnt += self.verifyAlgos(cipher, mac, os.path.join(actualResultsDir, "testlog.txt"), bool(i)) 181 | if (passCnt == 3): 182 | break 183 | 184 | def getKeyFilename(self, keyType, password): 185 | filename = "" 186 | if (len(keyType) > 0): 187 | filename = os.path.join(self.keysBaseDir, "testkey_" + keyType) 188 | if (password != ""): 189 | filename += "_pw" 190 | return filename 191 | 192 | def generateKey(self, keyType, password): 193 | filename = self.getKeyFilename(keyType, password) 194 | cmd = "ssh-keygen -t " + keyType + " -b 1024 -C test@home.com -f " + filename + " -N " + password 195 | print(cmd) 196 | call(cmd.split(" ")) 197 | if (password != ""): 198 | cmd = "openssl pkcs8 -in " + filename + " -passin pass:" + password + " -topk8 -v2 des3 -out " + filename + "_new -passout pass:" + password 199 | print(cmd) 200 | call(cmd.split(" ")) 201 | shutil.move(filename + "_new", filename) 202 | os.chmod(filename, stat.S_IWUSR | stat.S_IRUSR) 203 | 204 | def testKeys(self): 205 | 206 | for key in self.keys: 207 | for password in self.passwords: 208 | if (len(key) > 0): 209 | self.generateKey(key, password) 210 | 211 | cmd = "../../install/bin/cppsshtestkeys 192.168.1.19 algotester password " + self.keysBaseDir 212 | call(cmd.split(" ")) 213 | for testCase in self.testCases: 214 | key = testCase['key'] 215 | password = testCase['password'] 216 | cipher = testCase['cipher'] 217 | mac = testCase['mac'] 218 | if (len(key) == 0): 219 | password = "password" 220 | self.runAlgoTest(password, cipher, mac, self.getKeyFilename(key, password)) 221 | 222 | def tearDown(self): 223 | if (len(self.verificationErrors) == 0): 224 | print("OK") 225 | else: 226 | print("FAILED (errors=" + str(len(self.verificationErrors)) + ")") 227 | for k, v in self.diffs.items(): 228 | print("diff " + k + " " + v) 229 | self.assertEqual(len(self.verificationErrors), 0) 230 | 231 | if __name__ == '__main__': 232 | parser = argparse.ArgumentParser() 233 | parser.add_argument('--all', action='store_true') 234 | parser.add_argument('unittest_args', nargs='*') 235 | args = parser.parse_args() 236 | sys.argv[1:] = args.unittest_args 237 | 238 | unittest.main() 239 | -------------------------------------------------------------------------------- /src/transportimpl.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | cppssh - C++ ssh library 3 | Copyright (C) 2015 Chris Desjardins 4 | http://blog.chrisd.info cjd@chrisd.info 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | */ 19 | 20 | #include "transport.h" 21 | #include "crypto.h" 22 | #include "channel.h" 23 | #include "packet.h" 24 | #include "messages.h" 25 | #include "x11channel.h" 26 | 27 | #ifdef WIN32 28 | #define close closesocket 29 | #define socklen_t int 30 | #define MSG_NOSIGNAL 0 31 | #else 32 | #include 33 | #include 34 | #endif 35 | 36 | bool CppsshTransportImpl::establish(const std::string& host, short port) 37 | { 38 | bool ret = false; 39 | sockaddr_in remoteAddr; 40 | hostent* remoteHost; 41 | 42 | remoteHost = gethostbyname(host.c_str()); 43 | if ((remoteHost == nullptr) || (remoteHost->h_length == 0)) 44 | { 45 | cdLog(LogLevel::Error) << "Host" << host << "not found."; 46 | } 47 | else 48 | { 49 | remoteAddr.sin_family = AF_INET; 50 | remoteAddr.sin_addr.s_addr = *(long*)remoteHost->h_addr_list[0]; 51 | remoteAddr.sin_port = htons(port); 52 | 53 | _sock = socket(AF_INET, SOCK_STREAM, 0); 54 | if (_sock < 0) 55 | { 56 | cdLog(LogLevel::Error) << "Failure to bind to socket."; 57 | } 58 | else 59 | { 60 | if (setNonBlocking(true) == true) 61 | { 62 | ret = makeConnection(&remoteAddr); 63 | if (ret == false) 64 | { 65 | cdLog(LogLevel::Error) << "Unable to connect to remote server: '" << host << "'."; 66 | } 67 | } 68 | } 69 | } 70 | return ret; 71 | } 72 | 73 | bool CppsshTransportImpl::makeConnection(void* remoteAddr) 74 | { 75 | bool ret = false; 76 | // Non blocking connect needs some help from select and getsockopt to work 77 | if (connect(_sock, (struct sockaddr*) remoteAddr, sizeof(sockaddr_in)) == -1) 78 | { 79 | if (isConnectInProgress() == true) 80 | { 81 | int cnt = 0; 82 | while ((_running == true) && (cnt++ < 200)) 83 | { 84 | int res; 85 | struct timeval tv; 86 | fd_set connectSet; 87 | tv.tv_sec = 0; 88 | tv.tv_usec = 100000; 89 | setupFd(&connectSet); 90 | res = select(_sock + 1, nullptr, &connectSet, nullptr, &tv); 91 | if ((res < 0) && (errno != EINTR)) 92 | { 93 | cdLog(LogLevel::Error) << "Connection failed due to select error"; 94 | break; 95 | } 96 | else if (res > 0) 97 | { 98 | int valopt; 99 | socklen_t lon = sizeof(int); 100 | res = getsockopt(_sock, SOL_SOCKET, SO_ERROR, (char*)(&valopt), &lon); 101 | if (res < 0) 102 | { 103 | cdLog(LogLevel::Error) << "Connection failed due to socket error"; 104 | break; 105 | } 106 | else if (valopt) 107 | { 108 | cdLog(LogLevel::Error) << "Connection failed"; 109 | break; 110 | } 111 | else 112 | { 113 | ret = true; 114 | break; 115 | } 116 | } 117 | } 118 | } 119 | } 120 | else 121 | { 122 | ret = true; 123 | } 124 | 125 | return ret; 126 | } 127 | 128 | bool CppsshTransportImpl::parseDisplay(const std::string& display, int* displayNum, int* screenNum) 129 | { 130 | bool ret = false; 131 | size_t start = display.find(':') + 1; 132 | size_t mid = display.find('.'); 133 | std::string sn; 134 | std::string dn; 135 | if (mid == std::string::npos) 136 | { 137 | mid = display.length(); 138 | sn = "0"; 139 | } 140 | else 141 | { 142 | sn = display.substr(mid + 1); 143 | } 144 | dn = display.substr(start, mid - start); 145 | if ((dn.length() > 0) && (sn.length() > 0)) 146 | { 147 | std::istringstream dss(dn); 148 | dss >> *displayNum; 149 | 150 | std::istringstream sss(sn); 151 | sss >> *screenNum; 152 | ret = true; 153 | } 154 | return ret; 155 | } 156 | 157 | bool CppsshTransportImpl::establishX11() 158 | { 159 | bool ret = false; 160 | std::string display; 161 | CppsshX11Channel::getDisplay(&display); 162 | 163 | if ((display.find("unix:") == 0) || (display.find(":") == 0) || (display.find("localhost:") == 0)) 164 | { 165 | ret = establishLocalX11(display); 166 | } 167 | else 168 | { 169 | // FIXME: Connect to remote x11 170 | } 171 | return ret; 172 | } 173 | 174 | void CppsshTransportImpl::disconnect() 175 | { 176 | cdLog(LogLevel::Info) << "CppsshTransport::disconnect"; 177 | _running = false; 178 | close(_sock); 179 | } 180 | 181 | void CppsshTransportImpl::setupFd(fd_set* fd) 182 | { 183 | #if defined(WIN32) 184 | #pragma warning(push) 185 | #pragma warning(disable : 4127) 186 | #endif 187 | FD_ZERO(fd); 188 | FD_SET(_sock, fd); 189 | #if defined(WIN32) 190 | #pragma warning(pop) 191 | #endif 192 | } 193 | 194 | bool CppsshTransportImpl::wait(bool isWrite) 195 | { 196 | bool ret = false; 197 | int status = 0; 198 | std::chrono::steady_clock::time_point t0 = std::chrono::steady_clock::now(); 199 | while ((_running == true) && (ret == false) && 200 | (std::chrono::steady_clock::now() < (t0 + std::chrono::milliseconds(_session->getTimeout())))) 201 | { 202 | fd_set fds; 203 | struct timeval waitTime; 204 | waitTime.tv_sec = 0; 205 | waitTime.tv_usec = 1000; 206 | 207 | setupFd(&fds); 208 | if (isWrite == false) 209 | { 210 | status = select(_sock + 1, &fds, nullptr, nullptr, &waitTime); 211 | } 212 | else 213 | { 214 | status = select(_sock + 1, nullptr, &fds, nullptr, &waitTime); 215 | } 216 | if ((status > 0) && (FD_ISSET(_sock, &fds))) 217 | { 218 | ret = true; 219 | break; 220 | } 221 | std::this_thread::sleep_for(std::chrono::milliseconds(1)); 222 | } 223 | 224 | return ret; 225 | } 226 | 227 | bool CppsshTransportImpl::receiveMessage(Botan::secure_vector* buffer, size_t numBytes) 228 | { 229 | bool ret = true; 230 | while ((buffer->size() < numBytes) && (_running == true)) 231 | { 232 | if (receiveMessage(buffer) == false) 233 | { 234 | ret = false; 235 | break; 236 | } 237 | } 238 | return ((ret == true) && (buffer->size() >= numBytes)); 239 | } 240 | 241 | // Append new receive data to the end of the buffer 242 | bool CppsshTransportImpl::receiveMessage(Botan::secure_vector* buffer) 243 | { 244 | bool ret = true; 245 | int len = 0; 246 | int bufferLen = buffer->size(); 247 | buffer->resize(CPPSSH_MAX_PACKET_LEN + bufferLen); 248 | 249 | if (wait(false) == true) 250 | { 251 | len = ::recv(_sock, (char*)buffer->data() + bufferLen, CPPSSH_MAX_PACKET_LEN, 0); 252 | if (len > 0) 253 | { 254 | bufferLen += len; 255 | } 256 | if (len == 0) 257 | { 258 | cdLog(LogLevel::Error) << "Connection dropped. Rx 0 bytes"; 259 | disconnect(); 260 | ret = false; 261 | } 262 | } 263 | buffer->resize(bufferLen); 264 | 265 | if ((_running == true) && (len < 0)) 266 | { 267 | cdLog(LogLevel::Error) << "Connection dropped, Rx failed"; 268 | disconnect(); 269 | ret = false; 270 | } 271 | 272 | return ret; 273 | } 274 | 275 | bool CppsshTransportImpl::sendMessage(const Botan::secure_vector& buffer) 276 | { 277 | int len; 278 | size_t sent = 0; 279 | std::chrono::steady_clock::time_point t0 = std::chrono::steady_clock::now(); 280 | 281 | while ((sent < buffer.size()) && (_running == true) && 282 | (std::chrono::steady_clock::now() < (t0 + std::chrono::milliseconds(_session->getTimeout())))) 283 | { 284 | if (wait(true) == true) 285 | { 286 | len = ::send(_sock, (char*)(buffer.data() + sent), buffer.size() - sent, MSG_NOSIGNAL); 287 | _lastMsgTime = std::chrono::steady_clock::now(); 288 | } 289 | else 290 | { 291 | break; 292 | } 293 | if ((_running == true) && (len < 0)) 294 | { 295 | cdLog(LogLevel::Error) << "Connection dropped, Tx failed"; 296 | disconnect(); 297 | break; 298 | } 299 | sent += len; 300 | } 301 | 302 | return sent == buffer.size(); 303 | } 304 | 305 | CppsshTransportImpl::CppsshTransportImpl(const std::shared_ptr& session) 306 | : _session(session), 307 | _sock((SOCKET)-1), 308 | _running(true), 309 | _sendKeepAlives(false) 310 | { 311 | } 312 | 313 | CppsshTransportImpl::~CppsshTransportImpl() 314 | { 315 | _running = false; 316 | } 317 | 318 | bool CppsshTransportImpl::doSendKeepAlive() 319 | { 320 | bool ret = true; 321 | if (std::chrono::steady_clock::now() > (_lastMsgTime + std::chrono::minutes(5))) 322 | { 323 | Botan::secure_vector buf; 324 | CppsshPacket packet(&buf); 325 | 326 | packet.addByte(SSH2_MSG_GLOBAL_REQUEST); 327 | packet.addString("keepalive@combomb.com"); 328 | packet.addByte(true); // want reply == true 329 | ret = sendMessage(buf); 330 | } 331 | return ret; 332 | } 333 | -------------------------------------------------------------------------------- /src/packet.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | cppssh - C++ ssh library 3 | Copyright (C) 2015 Chris Desjardins 4 | http://blog.chrisd.info cjd@chrisd.info 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | */ 19 | 20 | #include "packet.h" 21 | #include "cppssh.h" 22 | #include "CDLogger/Logger.h" 23 | #include 24 | #include 25 | #include 26 | #ifdef NDEBUG 27 | #include "unparam.h" 28 | #endif 29 | #if !defined(WIN32) && !defined(__MINGW32__) 30 | # include 31 | #else 32 | # include 33 | #endif 34 | 35 | #define CPPSSH_PACKET_LENGTH_OFFS 0 36 | #define CPPSSH_PACKET_LENGTH_SIZE 4 37 | 38 | #define CPPSSH_PACKET_PAD_OFFS 4 39 | #define CPPSSH_PACKET_PAD_SIZE 1 40 | 41 | #define CPPSSH_PACKET_PAYLOAD_OFFS 5 42 | #define CPPSSH_PACKET_CMD_SIZE 1 43 | 44 | #define CPPSSH_PACKET_HEADER_SIZE (CPPSSH_PACKET_PAYLOAD_OFFS + CPPSSH_PACKET_CMD_SIZE) 45 | 46 | CppsshConstPacket::CppsshConstPacket(const Botan::secure_vector* const data) 47 | : _cdata(data), 48 | _index(0) 49 | { 50 | } 51 | 52 | void CppsshConstPacket::bn2vector(Botan::secure_vector* result, const Botan::BigInt& bi) 53 | { 54 | bool high; 55 | 56 | std::vector strVector = Botan::BigInt::encode(bi); 57 | 58 | high = (*(strVector.begin()) & 0x80) ? true : false; 59 | 60 | if (high == true) 61 | { 62 | result->push_back(0); 63 | } 64 | else 65 | { 66 | result->clear(); 67 | } 68 | *result += strVector; 69 | } 70 | 71 | uint32_t CppsshConstPacket::getPacketLength() const 72 | { 73 | uint32_t ret = 0; 74 | if (_cdata->size() >= CPPSSH_PACKET_LENGTH_SIZE) 75 | { 76 | ret = ntohl(*((uint32_t*)(_cdata->data() + _index))); 77 | } 78 | return ret; 79 | } 80 | 81 | uint32_t CppsshConstPacket::getCryptoLength() const 82 | { 83 | uint32_t ret = getPacketLength(); 84 | if (ret > 0) 85 | { 86 | ret += sizeof(uint32_t); 87 | } 88 | return ret; 89 | } 90 | 91 | Botan::byte CppsshConstPacket::getPadLength() const 92 | { 93 | Botan::byte ret = 0; 94 | if (_cdata->size() >= (CPPSSH_PACKET_PAD_OFFS + CPPSSH_PACKET_PAD_SIZE)) 95 | { 96 | ret = _cdata->begin()[CPPSSH_PACKET_PAD_OFFS]; 97 | } 98 | return ret; 99 | } 100 | 101 | Botan::byte CppsshConstPacket::getCommand() const 102 | { 103 | Botan::byte ret = 0; 104 | if (_cdata->size() >= (CPPSSH_PACKET_PAYLOAD_OFFS + CPPSSH_PACKET_CMD_SIZE)) 105 | { 106 | ret = _cdata->begin()[CPPSSH_PACKET_PAYLOAD_OFFS]; 107 | } 108 | return ret; 109 | } 110 | 111 | Botan::secure_vector::const_iterator CppsshConstPacket::getPayloadBegin() const 112 | { 113 | Botan::secure_vector::const_iterator ret = _cdata->cend(); 114 | if (_cdata->size() > CPPSSH_PACKET_PAYLOAD_OFFS) 115 | { 116 | ret = _cdata->begin() + CPPSSH_PACKET_PAYLOAD_OFFS; 117 | } 118 | return ret; 119 | } 120 | 121 | Botan::secure_vector::const_iterator CppsshConstPacket::getPayloadEnd() const 122 | { 123 | return getPayloadBegin() + (getPacketLength() - 1); 124 | } 125 | 126 | bool CppsshConstPacket::getString(Botan::secure_vector* result) const 127 | { 128 | bool ret = true; 129 | uint32_t len = getPacketLength(); 130 | 131 | if (len > (_cdata->size() + _index)) 132 | { 133 | ret = false; 134 | } 135 | else 136 | { 137 | *result = 138 | Botan::secure_vector(_cdata->begin() + sizeof(uint32_t) + _index, 139 | _cdata->begin() + (sizeof(uint32_t) + len + _index)); 140 | _index += sizeof(uint32_t) + len; 141 | } 142 | return ret; 143 | } 144 | 145 | bool CppsshConstPacket::getString(std::string* result) const 146 | { 147 | bool ret; 148 | Botan::secure_vector buf; 149 | ret = getString(&buf); 150 | result->clear(); 151 | result->append((char*)buf.data(), buf.size()); 152 | return ret; 153 | } 154 | 155 | bool CppsshConstPacket::getBigInt(Botan::BigInt* result) const 156 | { 157 | bool ret = true; 158 | uint32_t len = getPacketLength(); 159 | 160 | if (len > _cdata->size()) 161 | { 162 | ret = false; 163 | } 164 | else 165 | { 166 | Botan::BigInt tmpBI(_cdata->data() + sizeof(uint32_t) + _index, len); 167 | result->swap(tmpBI); 168 | _index += sizeof(uint32_t) + len; 169 | } 170 | return ret; 171 | } 172 | 173 | void CppsshConstPacket::getChannelData(CppsshMessage* result) const 174 | { 175 | // hackery to avoid tons of memcpy 176 | const Botan::byte* p = _cdata->data() + _index; 177 | uint32_t len = ntohl(*((uint32_t*)p)); 178 | result->setMessage((uint8_t*)(p + sizeof(uint32_t)), len); 179 | } 180 | 181 | uint32_t CppsshConstPacket::getInt() const 182 | { 183 | uint32_t result = getPacketLength(); 184 | _index += sizeof(uint32_t); 185 | return result; 186 | } 187 | 188 | uint8_t CppsshConstPacket::getByte() const 189 | { 190 | uint8_t result = 0; 191 | if (_cdata->size() >= sizeof(uint8_t)) 192 | { 193 | result = (uint8_t)((*_cdata)[_index]); 194 | _index += sizeof(uint8_t); 195 | } 196 | return result; 197 | } 198 | 199 | size_t CppsshConstPacket::size() const 200 | { 201 | return _cdata->size(); 202 | } 203 | 204 | void CppsshConstPacket::skipHeader() const 205 | { 206 | _index += CPPSSH_PACKET_HEADER_SIZE; 207 | } 208 | 209 | CppsshPacket::CppsshPacket(Botan::secure_vector* data) 210 | : CppsshConstPacket(data), 211 | _data(data) 212 | { 213 | } 214 | 215 | void CppsshPacket::copy(const Botan::secure_vector& src) 216 | { 217 | *_data = src; 218 | } 219 | 220 | void CppsshPacket::replace(size_t startingPos, const Botan::secure_vector& src) 221 | { 222 | if (startingPos < _data->size()) 223 | { 224 | size_t len = std::min(_data->size() - startingPos, src.size()); 225 | _data->erase(_data->begin() + startingPos, _data->begin() + startingPos + len); 226 | for (size_t i = 0; i < len; i++) 227 | { 228 | _data->push_back(src[i]); 229 | } 230 | } 231 | } 232 | 233 | void CppsshPacket::clear() 234 | { 235 | _data->clear(); 236 | } 237 | 238 | void CppsshPacket::addVectorField(const Botan::secure_vector& vec) 239 | { 240 | addInt(vec.size()); 241 | addVector(vec); 242 | } 243 | 244 | void CppsshPacket::addVector(const Botan::secure_vector& vec) 245 | { 246 | for (size_t i = 0; i < vec.size(); i++) 247 | { 248 | _data->push_back(vec[i]); 249 | } 250 | } 251 | 252 | void CppsshPacket::addRawData(const uint8_t* data, uint32_t bytes) 253 | { 254 | for (uint32_t i = 0; i < bytes; i++) 255 | { 256 | _data->push_back(data[i]); 257 | } 258 | } 259 | 260 | void CppsshPacket::addString(const std::string& str) 261 | { 262 | addInt(str.length()); 263 | for (size_t i = 0; i < str.length(); i++) 264 | { 265 | _data->push_back(str[i]); 266 | } 267 | } 268 | 269 | void CppsshPacket::addInt(const uint32_t var) 270 | { 271 | uint32_t data = htonl(var); 272 | Botan::byte* p; 273 | p = (Botan::byte*)&data; 274 | _data->push_back(p[0]); 275 | _data->push_back(p[1]); 276 | _data->push_back(p[2]); 277 | _data->push_back(p[3]); 278 | } 279 | 280 | void CppsshPacket::addByte(const uint8_t ch) 281 | { 282 | _data->push_back(ch); 283 | } 284 | 285 | void CppsshPacket::addBigInt(const Botan::BigInt& bn) 286 | { 287 | Botan::secure_vector converted; 288 | CppsshConstPacket::bn2vector(&converted, bn); 289 | addVectorField(converted); 290 | } 291 | 292 | bool CppsshPacket::addFile(const std::string& fileName) 293 | { 294 | bool ret = false; 295 | // open the file: 296 | std::ifstream file(fileName, std::ios::in); 297 | 298 | if (file.is_open() == true) 299 | { 300 | // Stop eating new lines in binary mode!!! 301 | file.unsetf(std::ios::skipws); 302 | 303 | // get its size: 304 | std::streampos fileSize; 305 | 306 | file.seekg(0, std::ios::end); 307 | fileSize = file.tellg(); 308 | file.seekg(0, std::ios::beg); 309 | 310 | // reserve capacity 311 | _data->reserve((size_t)fileSize); 312 | 313 | // read the data: 314 | _data->insert(_data->begin(), 315 | std::istream_iterator(file), 316 | std::istream_iterator()); 317 | 318 | ret = true; 319 | } 320 | return ret; 321 | } 322 | 323 | void CppsshPacket::removeWhitespace() 324 | { 325 | _data->erase(remove_if(_data->begin(), _data->end(), isspace), _data->end()); 326 | } 327 | 328 | void CppsshConstPacket::dumpAscii(Botan::secure_vector::const_iterator it, size_t len, 329 | std::stringstream* ss) const 330 | { 331 | #ifdef NDEBUG 332 | UNREF_PARAM(it); 333 | UNREF_PARAM(len); 334 | UNREF_PARAM(ss); 335 | #else 336 | if (len > 0) 337 | { 338 | size_t i; 339 | for (i = 0; i < 16 - len; i++) 340 | { 341 | *ss << " "; 342 | } 343 | for (i = 0; ((i < len) && (it != _cdata->end())); i++) 344 | { 345 | *ss << (char)(isprint(it[i]) ? it[i] : '.'); 346 | } 347 | *ss << std::endl; 348 | } 349 | #endif 350 | } 351 | 352 | void CppsshConstPacket::dumpPacket(const std::string& tag) const 353 | { 354 | #ifdef NDEBUG 355 | UNREF_PARAM(tag); 356 | #else 357 | size_t cnt = 0; 358 | size_t offs = 0; 359 | std::stringstream ss; 360 | Botan::secure_vector::const_iterator it; 361 | for (it = _cdata->begin() + _index; it != _cdata->end(); it++) 362 | { 363 | if ((cnt % 16) == 0) 364 | { 365 | dumpAscii(it - cnt, cnt, &ss); 366 | ss << tag << " " << std::hex << std::setw(6) << std::setfill('0') << offs << ": "; 367 | cnt = 0; 368 | } 369 | ss << std::hex << std::setw(2) << std::setfill('0') << (int)*it << std::dec << std::setw(0) << 370 | std::setfill(' ') << " "; 371 | cnt++; 372 | offs++; 373 | } 374 | dumpAscii(it - cnt, cnt, &ss); 375 | ss << std::endl; 376 | cdLog(LogLevel::Debug) << ss.str(); 377 | #endif 378 | } 379 | -------------------------------------------------------------------------------- /src/kex.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | cppssh - C++ ssh library 3 | Copyright (C) 2015 Chris Desjardins 4 | http://blog.chrisd.info cjd@chrisd.info 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | */ 19 | 20 | #include "kex.h" 21 | #include "messages.h" 22 | #include "impl.h" 23 | #include "packet.h" 24 | #include "crypto.h" 25 | 26 | CppsshKex::CppsshKex(const std::shared_ptr& session) 27 | : _session(session) 28 | { 29 | } 30 | 31 | void CppsshKex::constructLocalKex() 32 | { 33 | std::vector random; 34 | std::string kexStr; 35 | std::string hostkeyStr; 36 | std::string compressors; 37 | std::string ciphersStr; 38 | std::string hmacsStr; 39 | 40 | _localKex.clear(); 41 | _localKex.push_back(SSH2_MSG_KEXINIT); 42 | 43 | random.resize(16); 44 | CppsshImpl::RNG->randomize(random.data(), random.size()); 45 | 46 | std::copy(random.begin(), random.end(), std::back_inserter(_localKex)); 47 | 48 | CppsshImpl::KEX_ALGORITHMS.toString(&kexStr); 49 | CppsshImpl::HOSTKEY_ALGORITHMS.toString(&hostkeyStr); 50 | CppsshImpl::CIPHER_ALGORITHMS.toString(&ciphersStr); 51 | CppsshImpl::MAC_ALGORITHMS.toString(&hmacsStr); 52 | CppsshImpl::COMPRESSION_ALGORITHMS.toString(&compressors); 53 | 54 | CppsshPacket localKex(&_localKex); 55 | 56 | Botan::secure_vector kex(kexStr.begin(), kexStr.end()); 57 | Botan::secure_vector hostKey(hostkeyStr.begin(), hostkeyStr.end()); 58 | Botan::secure_vector ciphers(ciphersStr.begin(), ciphersStr.end()); 59 | Botan::secure_vector hmacs(hmacsStr.begin(), hmacsStr.end()); 60 | localKex.addVectorField(kex); 61 | localKex.addVectorField(hostKey); 62 | localKex.addVectorField(ciphers); 63 | localKex.addVectorField(ciphers); 64 | localKex.addVectorField(hmacs); 65 | localKex.addVectorField(hmacs); 66 | localKex.addString(compressors); 67 | localKex.addString(compressors); 68 | localKex.addInt(0); 69 | localKex.addInt(0); 70 | localKex.addByte('\0'); 71 | localKex.addInt(0); 72 | } 73 | 74 | bool CppsshKex::sendInit(Botan::secure_vector& buf) 75 | { 76 | bool ret = false; 77 | CppsshPacket packet(&buf); 78 | 79 | constructLocalKex(); 80 | 81 | if (_session->_transport->sendMessage(_localKex) == true) 82 | { 83 | if ((_session->_channel->waitForGlobalMessage(buf) == true) && (packet.getCommand() == SSH2_MSG_KEXINIT)) 84 | { 85 | ret = true; 86 | } 87 | else 88 | { 89 | cdLog(LogLevel::Error) << "Timeout while waiting for key exchange init reply."; 90 | } 91 | } 92 | 93 | return ret; 94 | } 95 | 96 | template T CppsshKex::runAgreement(const CppsshConstPacket& remoteKexAlgosPacket, 97 | const CppsshAlgos& algorithms, const std::string& tag) const 98 | { 99 | T ret = T::MAX_VALS; 100 | std::string algos; 101 | std::string agreed; 102 | 103 | if (remoteKexAlgosPacket.getString(&algos) == true) 104 | { 105 | cdLog(LogLevel::Debug) << tag << " algos: " << algos; 106 | if (algorithms.agree(&agreed, algos) == true) 107 | { 108 | algorithms.ssh2enum(agreed, &ret); 109 | } 110 | else 111 | { 112 | cdLog(LogLevel::Error) << "No compatible " << tag << " exchange algorithms."; 113 | } 114 | } 115 | return ret; 116 | } 117 | 118 | bool CppsshKex::handleInit() 119 | { 120 | bool ret = false; 121 | Botan::secure_vector buf; 122 | CppsshPacket packet(&buf); 123 | if (sendInit(buf) == true) 124 | { 125 | _remoteKex.clear(); 126 | CppsshPacket remoteKexPacket(&_remoteKex); 127 | remoteKexPacket.addVector(Botan::secure_vector(packet.getPayloadBegin(), 128 | (packet.getPayloadEnd() - packet.getPadLength()))); 129 | 130 | Botan::secure_vector remoteKexAlgos(packet.getPayloadBegin() + 17, packet.getPayloadEnd()); 131 | const CppsshConstPacket remoteKexAlgosPacket(&remoteKexAlgos); 132 | 133 | if ((_session->_crypto->setNegotiatedKex(runAgreement(remoteKexAlgosPacket, 134 | CppsshImpl::KEX_ALGORITHMS, 135 | "Kex")) == true) && 136 | (_session->_crypto->setNegotiatedHostkey(runAgreement(remoteKexAlgosPacket, 137 | CppsshImpl::HOSTKEY_ALGORITHMS, 138 | "Hostkey")) == true) && 139 | (_session->_crypto->setNegotiatedCryptoC2s(runAgreement(remoteKexAlgosPacket, 140 | CppsshImpl::CIPHER_ALGORITHMS, 141 | "C2S Cipher")) == true) && 142 | (_session->_crypto->setNegotiatedCryptoS2c(runAgreement(remoteKexAlgosPacket, 143 | CppsshImpl::CIPHER_ALGORITHMS, 144 | "S2C Cipher")) == true) && 145 | (_session->_crypto->setNegotiatedMacC2s(runAgreement(remoteKexAlgosPacket, 146 | CppsshImpl::MAC_ALGORITHMS, 147 | "C2S MAC")) == true) && 148 | (_session->_crypto->setNegotiatedMacS2c(runAgreement(remoteKexAlgosPacket, 149 | CppsshImpl::MAC_ALGORITHMS, 150 | "S2C MAC")) == true) && 151 | (_session->_crypto->setNegotiatedCmprsC2s(runAgreement(remoteKexAlgosPacket, 152 | CppsshImpl:: 153 | COMPRESSION_ALGORITHMS, 154 | "C2S Compression")) == true) && 155 | (_session->_crypto->setNegotiatedCmprsS2c(runAgreement(remoteKexAlgosPacket, 156 | CppsshImpl:: 157 | COMPRESSION_ALGORITHMS, 158 | "S2C Compression")) == true)) 159 | { 160 | ret = true; 161 | } 162 | } 163 | return ret; 164 | } 165 | 166 | bool CppsshKex::sendKexDHInit(Botan::secure_vector& buf) 167 | { 168 | bool ret = false; 169 | Botan::BigInt publicKey; 170 | 171 | if (_session->_crypto->getKexPublic(publicKey) == true) 172 | { 173 | CppsshPacket dhInit(&buf); 174 | dhInit.addByte(SSH2_MSG_KEXDH_INIT); 175 | dhInit.addBigInt(publicKey); 176 | 177 | _e.clear(); 178 | CppsshConstPacket::bn2vector(&_e, publicKey); 179 | 180 | if (_session->_transport->sendMessage(buf) == true) 181 | { 182 | if ((_session->_channel->waitForGlobalMessage(buf) == true) && 183 | (dhInit.getCommand() == SSH2_MSG_KEXDH_REPLY)) 184 | { 185 | ret = true; 186 | } 187 | else 188 | { 189 | cdLog(LogLevel::Error) << "Timeout while waiting for key exchange DH reply."; 190 | } 191 | } 192 | } 193 | return ret; 194 | } 195 | 196 | bool CppsshKex::handleKexDHReply() 197 | { 198 | bool ret = false; 199 | Botan::secure_vector buffer; 200 | Botan::secure_vector hSig, kVector, hVector; 201 | CppsshPacket packet(&buffer); 202 | 203 | if ((sendKexDHInit(buffer) == true) && (buffer.empty() == false)) 204 | { 205 | packet.skipHeader(); 206 | Botan::BigInt publicKey; 207 | 208 | _hostKey.clear(); 209 | if ((packet.getString(&_hostKey) == true) && (packet.getBigInt(&publicKey) == true)) 210 | { 211 | _f.clear(); 212 | _k.clear(); 213 | CppsshConstPacket::bn2vector(&_f, publicKey); 214 | 215 | if ((packet.getString(&hSig) == true) && (_session->_crypto->makeKexSecret(&_k, publicKey) == true)) 216 | { 217 | makeH(&hVector); 218 | if (hVector.empty() == false) 219 | { 220 | _session->setSessionID(hVector); 221 | ret = _session->_crypto->verifySig(_hostKey, hSig); 222 | } 223 | } 224 | } 225 | } 226 | return ret; 227 | } 228 | 229 | void CppsshKex::makeH(Botan::secure_vector* hVector) 230 | { 231 | Botan::secure_vector buf; 232 | CppsshPacket hashBytes(&buf); 233 | 234 | hashBytes.addString(_session->getLocalVersion()); 235 | hashBytes.addString(_session->getRemoteVersion()); 236 | hashBytes.addVectorField(_localKex); 237 | hashBytes.addVectorField(_remoteKex); 238 | hashBytes.addVectorField(_hostKey); 239 | hashBytes.addVectorField(_e); 240 | hashBytes.addVectorField(_f); 241 | hashBytes.addVectorField(_k); 242 | 243 | _session->_crypto->computeH(hVector, buf); 244 | } 245 | 246 | bool CppsshKex::sendKexNewKeys() 247 | { 248 | bool ret = false; 249 | Botan::secure_vector buf; 250 | CppsshPacket packet(&buf); 251 | 252 | if ((_session->_channel->waitForGlobalMessage(buf) == true) && (packet.getCommand() == SSH2_MSG_NEWKEYS)) 253 | { 254 | Botan::secure_vector newKeys; 255 | CppsshPacket newKeysPacket(&newKeys); 256 | newKeysPacket.addByte(SSH2_MSG_NEWKEYS); 257 | if (_session->_transport->sendMessage(newKeys) == true) 258 | { 259 | if (_session->_crypto->makeNewKeys() == false) 260 | { 261 | cdLog(LogLevel::Error) << "Could not make keys."; 262 | } 263 | else 264 | { 265 | ret = true; 266 | } 267 | } 268 | } 269 | else 270 | { 271 | cdLog(LogLevel::Error) << "Timeout while waiting for key exchange newkeys reply."; 272 | } 273 | 274 | return ret; 275 | } 276 | -------------------------------------------------------------------------------- /src/connection.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | cppssh - C++ ssh library 3 | Copyright (C) 2015 Chris Desjardins 4 | http://blog.chrisd.info cjd@chrisd.info 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | */ 19 | 20 | #include "connection.h" 21 | #include "kex.h" 22 | #include "keys.h" 23 | #include "packet.h" 24 | #include "messages.h" 25 | #include "transportcrypto.h" 26 | #include "cppssh.h" 27 | #include "impl.h" 28 | #include "strtrim.h" 29 | 30 | CppsshConnection::CppsshConnection(int connectionId, unsigned int timeout) 31 | : _session(new CppsshSession(connectionId, timeout)), 32 | _connected(false) 33 | { 34 | cdLog(LogLevel::Debug) << "CppsshConnection"; 35 | _session->_transport.reset(new CppsshTransportThreaded(_session)); 36 | _session->_crypto.reset(new CppsshCrypto(_session)); 37 | _session->_channel.reset(new CppsshChannel(_session)); 38 | } 39 | 40 | CppsshConnection::~CppsshConnection() 41 | { 42 | cdLog(LogLevel::Debug) << "~CppsshConnection"; 43 | _connected = false; 44 | _session->_channel->disconnect(); 45 | _session->_transport.reset(); 46 | _session->_channel.reset(); 47 | _session->_crypto.reset(); 48 | _session.reset(); 49 | } 50 | 51 | CppsshConnectStatus_t CppsshConnection::connect(const char* host, const short port, const char* username, 52 | const char* privKeyFile, const char* password, const bool x11Forwarded, 53 | const bool keepAlives, const char* term) 54 | { 55 | CppsshConnectStatus_t ret = CPPSSH_CONNECT_OK; 56 | CppsshKex kex(_session); 57 | 58 | if (_session->_channel->establish(host, port) == false) 59 | { 60 | ret = CPPSSH_CONNECT_UNKNOWN_HOST; 61 | } 62 | else if (checkRemoteVersion() == false) 63 | { 64 | ret = CPPSSH_CONNECT_INCOMPATIBLE_SERVER; 65 | } 66 | else if (sendLocalVersion() == false) 67 | { 68 | ret = CPPSSH_CONNECT_INCOMPATIBLE_SERVER; 69 | } 70 | else if (_session->_transport->startThreads() == false) 71 | { 72 | ret = CPPSSH_CONNECT_ERROR; 73 | } 74 | else if (kex.handleInit() == false) 75 | { 76 | ret = CPPSSH_CONNECT_KEX_FAIL; 77 | } 78 | else if (kex.handleKexDHReply() == false) 79 | { 80 | ret = CPPSSH_CONNECT_KEX_FAIL; 81 | } 82 | else if (kex.sendKexNewKeys() == 0) 83 | { 84 | ret = CPPSSH_CONNECT_KEX_FAIL; 85 | } 86 | else 87 | { 88 | std::string pkf; 89 | _session->_transport.reset(new CppsshTransportCrypto(_session, _session->_transport->getSocket())); 90 | if (_session->_transport->startThreads() == false) 91 | { 92 | ret = CPPSSH_CONNECT_ERROR; 93 | } 94 | else if (requestService("ssh-userauth") == false) 95 | { 96 | ret = CPPSSH_CONNECT_ERROR; 97 | } 98 | else 99 | { 100 | if (privKeyFile != nullptr) 101 | { 102 | pkf.assign(privKeyFile); 103 | } 104 | if ((authWithKey(username, pkf, password) == false) && (authWithPassword(username, password) == false)) 105 | { 106 | ret = CPPSSH_CONNECT_AUTH_FAIL; 107 | } 108 | else if (_session->_channel->openChannel() == false) 109 | { 110 | ret = CPPSSH_CONNECT_ERROR; 111 | } 112 | else 113 | { 114 | if (term != nullptr) 115 | { 116 | if (x11Forwarded == true) 117 | { 118 | _session->_channel->getX11(); 119 | } 120 | if (_session->_channel->getShell(term) == false) 121 | { 122 | ret = CPPSSH_CONNECT_ERROR; 123 | } 124 | else if (keepAlives == true) 125 | { 126 | _session->_transport->enableKeepAlives(); 127 | } 128 | } 129 | _connected = true; 130 | } 131 | } 132 | } 133 | return ret; 134 | } 135 | 136 | bool CppsshConnection::write(const uint8_t* data, uint32_t bytes) 137 | { 138 | return _session->_channel->writeMainChannel(data, bytes); 139 | } 140 | 141 | bool CppsshConnection::read(CppsshMessage* data) 142 | { 143 | return _session->_channel->readMainChannel(data); 144 | } 145 | 146 | bool CppsshConnection::windowChange(const uint32_t cols, const uint32_t rows) 147 | { 148 | return _session->_channel->windowChange(cols, rows); 149 | } 150 | 151 | bool CppsshConnection::isConnected() 152 | { 153 | return _connected && _session->_channel->isConnected(); 154 | } 155 | 156 | bool CppsshConnection::checkRemoteVersion() 157 | { 158 | bool ret = false; 159 | Botan::secure_vector remoteVer; 160 | if (_session->_transport->receiveMessage(&remoteVer) == true) 161 | { 162 | std::string sshVer("SSH-2.0"); 163 | std::string rv(remoteVer.begin(), remoteVer.end()); 164 | StrTrim::trim(rv); 165 | cdLog(LogLevel::Info) << "Remote version: " << rv; 166 | if ((remoteVer.size() >= sshVer.length()) && 167 | std::equal(remoteVer.begin(), remoteVer.begin() + sshVer.length(), sshVer.begin())) 168 | { 169 | ret = true; 170 | _session->setRemoteVersion(rv); 171 | } 172 | } 173 | return ret; 174 | } 175 | 176 | bool CppsshConnection::sendLocalVersion() 177 | { 178 | const std::string localVer("SSH-2.0-cppssh_" CPPSSH_SHORT_VERSION); 179 | _session->setLocalVersion(localVer); 180 | Botan::secure_vector lv; 181 | lv.assign(localVer.begin(), localVer.end()); 182 | lv.push_back('\r'); 183 | lv.push_back('\n'); 184 | return _session->_transport->CppsshTransport::sendMessage(lv); 185 | } 186 | 187 | bool CppsshConnection::requestService(const std::string& service) 188 | { 189 | bool ret = false; 190 | Botan::secure_vector buf; 191 | CppsshPacket packet(&buf); 192 | 193 | packet.addByte(SSH2_MSG_SERVICE_REQUEST); 194 | packet.addString(service); 195 | if (_session->_transport->sendMessage(buf) == true) 196 | { 197 | if ((_session->_channel->waitForGlobalMessage(buf) == true) && (packet.getCommand() == SSH2_MSG_SERVICE_ACCEPT)) 198 | { 199 | ret = true; 200 | } 201 | else 202 | { 203 | cdLog(LogLevel::Error) << "Service request failed."; 204 | } 205 | } 206 | return ret; 207 | } 208 | 209 | bool CppsshConnection::authenticate(const Botan::secure_vector& userAuthRequest) 210 | { 211 | bool ret = false; 212 | Botan::secure_vector buf; 213 | CppsshPacket packet(&buf); 214 | 215 | if ((_session->_transport->sendMessage(userAuthRequest) == true) && 216 | (_session->_channel->waitForGlobalMessage(buf) == true)) 217 | { 218 | if ((packet.getCommand() == SSH2_MSG_USERAUTH_SUCCESS) || (packet.getCommand() == SSH2_MSG_USERAUTH_PK_OK)) 219 | { 220 | ret = true; 221 | } 222 | else if (packet.getCommand() == SSH2_MSG_USERAUTH_FAILURE) 223 | { 224 | std::string methods; 225 | packet.skipHeader(); 226 | packet.getString(&methods); 227 | cdLog(LogLevel::Error) << "Authentication failed. Supported authentication methods: " << methods.data(); 228 | } 229 | else 230 | { 231 | cdLog(LogLevel::Error) << "Unknown user auth response: " << packet.getCommand(); 232 | } 233 | } 234 | return ret; 235 | } 236 | 237 | bool CppsshConnection::authWithPassword(const std::string& username, const std::string& password) 238 | { 239 | bool ret; 240 | Botan::secure_vector buf; 241 | CppsshPacket packet(&buf); 242 | 243 | packet.addByte(SSH2_MSG_USERAUTH_REQUEST); 244 | packet.addString(username); 245 | packet.addString("ssh-connection"); 246 | packet.addString("password"); 247 | packet.addByte('\0'); 248 | packet.addString(password); 249 | ret = authenticate(buf); 250 | if (ret == true) 251 | { 252 | cdLog(LogLevel::Debug) << "Authenticated with password"; 253 | } 254 | return ret; 255 | } 256 | 257 | bool CppsshConnection::authWithKey(const std::string& username, const std::string& privKeyFileName, 258 | const char* keyPassword) 259 | { 260 | bool ret = false; 261 | CppsshKeys keyPair; 262 | 263 | if ((privKeyFileName.length() > 0) && (keyPair.getKeyPairFromFile(privKeyFileName, keyPassword) == true)) 264 | { 265 | Botan::secure_vector buf; 266 | Botan::secure_vector beginBuf; 267 | Botan::secure_vector endBuf; 268 | CppsshPacket packet(&buf); 269 | CppsshPacket packetBegin(&beginBuf); 270 | CppsshPacket packetEnd(&endBuf); 271 | 272 | packetBegin.addByte(SSH2_MSG_USERAUTH_REQUEST); 273 | packetBegin.addString(username); 274 | packetBegin.addString("ssh-connection"); 275 | packetBegin.addString("publickey"); 276 | 277 | std::string algo = CppsshImpl::HOSTKEY_ALGORITHMS.enum2ssh(keyPair.getKeyAlgo()); 278 | std::transform(algo.begin(), algo.end(), algo.begin(), ::tolower); 279 | packetEnd.addString(algo); 280 | size_t packetSize = endBuf.size(); 281 | packetEnd.addVectorField(keyPair.getPublicKeyBlob()); 282 | if (packetSize == endBuf.size()) 283 | { 284 | cdLog(LogLevel::Error) << "Invallid public key."; 285 | } 286 | else 287 | { 288 | packet.addVector(beginBuf); 289 | packet.addByte(0); 290 | packet.addVector(endBuf); 291 | if (authenticate(buf) == true) 292 | { 293 | buf.clear(); 294 | packet.addVector(beginBuf); 295 | packet.addByte(1); 296 | packet.addVector(endBuf); 297 | Botan::secure_vector sigBlob = keyPair.generateSignature(_session->getSessionID(), buf); 298 | if (sigBlob.size() == 0) 299 | { 300 | cdLog(LogLevel::Error) << "Failure while generating the signature."; 301 | } 302 | else 303 | { 304 | packet.addVectorField(sigBlob); 305 | ret = authenticate(buf); 306 | cdLog(LogLevel::Debug) << "Authenticated with key: " << privKeyFileName; 307 | } 308 | } 309 | } 310 | } 311 | return ret; 312 | } 313 | 314 | bool CppsshConnection::closeConnection() 315 | { 316 | _session->_transport->disconnect(); 317 | return true; 318 | } 319 | -------------------------------------------------------------------------------- /src/channel.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | cppssh - C++ ssh library 3 | Copyright (C) 2015 Chris Desjardins 4 | http://blog.chrisd.info cjd@chrisd.info 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | */ 19 | #include "impl.h" 20 | #include "channel.h" 21 | #include "messages.h" 22 | #include "transport.h" 23 | #include "packet.h" 24 | #include "CDLogger/Logger.h" 25 | #include "x11channel.h" 26 | #include "visualencode.h" 27 | #include 28 | #include 29 | #include "debug.h" 30 | 31 | CppsshChannel::CppsshChannel(const std::shared_ptr& session) 32 | : _session(session), 33 | _mainChannel(0), 34 | _x11ReqSuccess(false) 35 | { 36 | } 37 | 38 | CppsshChannel::~CppsshChannel() 39 | { 40 | disconnect(); 41 | } 42 | 43 | bool CppsshChannel::establish(const std::string& host, short port) 44 | { 45 | bool ret = false; 46 | std::string channelName("session"); 47 | if (createNewSubChannel(channelName, &_mainChannel) == true) 48 | { 49 | ret = _session->_transport->establish(host, port); 50 | } 51 | return ret; 52 | } 53 | 54 | bool CppsshChannel::openChannel() 55 | { 56 | bool ret = false; 57 | Botan::secure_vector buf; 58 | CppsshPacket packet(&buf); 59 | packet.addByte(SSH2_MSG_CHANNEL_OPEN); 60 | try 61 | { 62 | packet.addString(_channels.at(_mainChannel)->getChannelName()); 63 | packet.addInt(_mainChannel); 64 | 65 | packet.addInt(CppsshSubChannel::getRxWindowSize()); 66 | packet.addInt(CPPSSH_MAX_PACKET_LEN); 67 | 68 | if (_session->_transport->sendMessage(buf) == true) 69 | { 70 | ret = _channels.at(_mainChannel)->handleChannelConfirm(); 71 | } 72 | } 73 | catch (const std::exception& ex) 74 | { 75 | cdLog(LogLevel::Error) << "openChannel " << ex.what(); 76 | } 77 | return ret; 78 | } 79 | 80 | bool CppsshChannel::isConnected() 81 | { 82 | return ((_channels.find(_mainChannel) != _channels.cend()) && 83 | (_session->_transport->isRunning() == true)); 84 | } 85 | 86 | bool CppsshChannel::writeMainChannel(const uint8_t* data, uint32_t bytes) 87 | { 88 | bool ret = false; 89 | try 90 | { 91 | std::shared_ptr > lock = _channels.getLock(); 92 | ret = _channels.at(_mainChannel)->writeChannel(data, bytes); 93 | } 94 | catch (const std::exception& ex) 95 | { 96 | cdLog(LogLevel::Error) << "writeMainChannel " << ex.what(); 97 | } 98 | return ret; 99 | } 100 | 101 | bool CppsshChannel::readMainChannel(CppsshMessage* data) 102 | { 103 | bool ret = false; 104 | try 105 | { 106 | std::shared_ptr > lock = _channels.getLock(); 107 | ret = _channels.at(_mainChannel)->readChannel(data); 108 | } 109 | catch (const std::exception& ex) 110 | { 111 | cdLog(LogLevel::Error) << "readMainChannel " << ex.what(); 112 | } 113 | return ret; 114 | } 115 | 116 | bool CppsshChannel::windowChange(const uint32_t rows, const uint32_t cols) 117 | { 118 | bool ret = false; 119 | try 120 | { 121 | std::shared_ptr > lock = _channels.getLock(); 122 | ret = _channels.at(_mainChannel)->windowChange(rows, cols); 123 | } 124 | catch (const std::exception& ex) 125 | { 126 | cdLog(LogLevel::Error) << "windowChange " << ex.what(); 127 | } 128 | return ret; 129 | } 130 | 131 | void CppsshChannel::handleDebug(const CppsshConstPacket& packet) 132 | { 133 | std::string dbg; 134 | packet.skipHeader(); 135 | // getByte for always display 136 | packet.getByte(); 137 | if ((packet.getString(&dbg) == true) && (dbg.size() > 0)) 138 | { 139 | cdLog(LogLevel::Debug) << dbg; 140 | } 141 | } 142 | 143 | void CppsshChannel::handleDisconnect(const CppsshConstPacket& packet) 144 | { 145 | std::string err; 146 | packet.skipHeader(); 147 | packet.getInt(); 148 | packet.getString(&err); 149 | cdLog(LogLevel::Error) << "Remote error: " << err; 150 | disconnect(); 151 | } 152 | 153 | void CppsshChannel::disconnect() 154 | { 155 | cdLog(LogLevel::Debug) << "disconnect[" << _session->getConnectionId() << "]"; 156 | _channels.clear(); 157 | } 158 | 159 | void CppsshChannel::handleEof(const Botan::secure_vector& buf) 160 | { 161 | CppsshConstPacket packet(&buf); 162 | packet.skipHeader(); 163 | uint32_t rxChannel = packet.getInt(); 164 | _channels.at(rxChannel)->handleEof(); 165 | } 166 | 167 | void CppsshChannel::handleClose(const Botan::secure_vector& buf) 168 | { 169 | CppsshConstPacket packet(&buf); 170 | packet.skipHeader(); 171 | uint32_t rxChannel = packet.getInt(); 172 | _channels.at(rxChannel)->handleClose(); 173 | _channels.erase(rxChannel); 174 | } 175 | 176 | bool CppsshChannel::createNewSubChannel(const std::string& channelName, uint32_t windowSend, uint32_t maxPacket, 177 | uint32_t txChannel, uint32_t* rxChannel) 178 | { 179 | bool ret = createNewSubChannel(channelName, rxChannel); 180 | if (ret == true) 181 | { 182 | _channels.at(*rxChannel)->setParameters(windowSend, txChannel, maxPacket); 183 | } 184 | return ret; 185 | } 186 | 187 | bool CppsshChannel::createNewSubChannel(const std::string& channelName, uint32_t* rxChannel) 188 | { 189 | int chan; 190 | bool ret = false; 191 | std::shared_ptr channel; 192 | if (channelName == "x11") 193 | { 194 | channel.reset(new CppsshX11Channel(_session, channelName)); 195 | } 196 | else 197 | { 198 | channel.reset(new CppsshSubChannel(_session, channelName)); 199 | } 200 | 201 | for (chan = 100; chan < 2048; chan++) 202 | { 203 | if (_channels.find(chan) == _channels.cend()) 204 | { 205 | _channels.insert(std::pair >(chan, channel)); 206 | *rxChannel = chan; 207 | cdLog(LogLevel::Debug) << "createNewSubChannel " << channelName << " rxChannel: " << chan; 208 | ret = channel->startChannel(); 209 | break; 210 | } 211 | } 212 | return ret; 213 | } 214 | 215 | void CppsshChannel::sendOpenConfirmation(uint32_t rxChannel) 216 | { 217 | std::shared_ptr channel = _channels.at(rxChannel); 218 | Botan::secure_vector buf; 219 | CppsshPacket openConfirmation(&buf); 220 | openConfirmation.addByte(SSH2_MSG_CHANNEL_OPEN_CONFIRMATION); 221 | openConfirmation.addInt(channel->getTxChannel()); 222 | openConfirmation.addInt(rxChannel); 223 | openConfirmation.addInt(channel->getWindowRecv()); 224 | openConfirmation.addInt(CPPSSH_MAX_PACKET_LEN); 225 | _session->_transport->sendMessage(buf); 226 | } 227 | 228 | void CppsshChannel::sendOpenFailure(uint32_t rxChannel, CppsshOpenFailureReason reason) 229 | { 230 | Botan::secure_vector buf; 231 | CppsshPacket openFaulure(&buf); 232 | openFaulure.addByte(SSH2_MSG_CHANNEL_OPEN_FAILURE); 233 | openFaulure.addInt(rxChannel); 234 | openFaulure.addInt(reason); 235 | openFaulure.addString("Bad request"); 236 | openFaulure.addString("EN"); 237 | _session->_transport->sendMessage(buf); 238 | } 239 | 240 | void CppsshChannel::handleOpen(const Botan::secure_vector& buf) 241 | { 242 | std::string channelName; 243 | std::string originatorAddr; 244 | CppsshConstPacket openPacket(&buf); 245 | openPacket.skipHeader(); 246 | openPacket.getString(&channelName); 247 | uint32_t txChannel = openPacket.getInt(); 248 | uint32_t windowSend = openPacket.getInt(); 249 | uint32_t maxPacket = openPacket.getInt(); 250 | openPacket.getString(&originatorAddr); 251 | //uint32_t originatorPort = openPacket.getInt(); 252 | if (channelName == "x11") 253 | { 254 | if (_x11ReqSuccess == false) 255 | { 256 | sendOpenFailure(txChannel, SSH2_OPEN_CONNECT_FAILED); 257 | } 258 | else 259 | { 260 | uint32_t rxChannel; 261 | if (createNewSubChannel(channelName, windowSend, maxPacket, txChannel, &rxChannel) == true) 262 | { 263 | sendOpenConfirmation(rxChannel); 264 | } 265 | else 266 | { 267 | sendOpenFailure(txChannel, SSH2_OPEN_RESOURCE_SHORTAGE); 268 | } 269 | } 270 | } 271 | else 272 | { 273 | sendOpenFailure(txChannel, SSH2_OPEN_UNKNOWN_CHANNEL_TYPE); 274 | } 275 | } 276 | 277 | bool CppsshChannel::flushOutgoingChannelData() 278 | { 279 | bool ret = true; 280 | std::shared_ptr > lock = _channels.getLock(); 281 | std::map >::const_iterator it; 282 | for (it = _channels.cbegin(); (it != _channels.cend() && (ret == true)); it++) 283 | { 284 | ret = it->second->flushOutgoingChannelData(); 285 | } 286 | return ret; 287 | } 288 | 289 | bool CppsshChannel::getShell(const char* term) 290 | { 291 | bool ret = false; 292 | Botan::secure_vector buf; 293 | CppsshPacket packet(&buf); 294 | 295 | packet.addString(term); 296 | packet.addInt(80); 297 | packet.addInt(24); 298 | packet.addInt(0); 299 | packet.addInt(0); 300 | packet.addString(""); 301 | 302 | try 303 | { 304 | if (_channels.at(_mainChannel)->doChannelRequest("pty-req", buf) == true) 305 | { 306 | buf.clear(); 307 | if (_channels.at(_mainChannel)->doChannelRequest("shell", buf) == true) 308 | { 309 | ret = true; 310 | } 311 | } 312 | } 313 | catch (const std::exception& ex) 314 | { 315 | cdLog(LogLevel::Error) << "getShell " << ex.what(); 316 | } 317 | return ret; 318 | } 319 | 320 | bool CppsshChannel::getRandomString(const int size, std::string* randomString) 321 | { 322 | std::vector random; 323 | random.resize(size / 2); 324 | CppsshImpl::RNG->randomize(random.data(), random.size()); 325 | std::stringstream fake; 326 | for (Botan::byte it : random) 327 | { 328 | fake << std::hex << std::setw(2) << std::setfill('0') << (int)it; 329 | } 330 | *randomString = fake.str(); 331 | return true; 332 | } 333 | 334 | bool CppsshChannel::getX11() 335 | { 336 | bool ret = false; 337 | std::string display; 338 | CppsshX11Channel::getDisplay(&display); 339 | if (display.length() > 0) 340 | { 341 | if (CppsshX11Channel::runXauth(display, &_X11Method, &_realX11Cookie) == false) 342 | { 343 | getRandomString(16, &_fakeX11Cookie); 344 | _X11Method = "MIT-MAGIC-COOKIE-1"; 345 | } 346 | else 347 | { 348 | getRandomString(_realX11Cookie.size(), &_fakeX11Cookie); 349 | } 350 | int displayNum; 351 | int screenNum; 352 | 353 | CppsshTransport::parseDisplay(display, &displayNum, &screenNum); 354 | Botan::secure_vector x11req; 355 | CppsshPacket x11packet(&x11req); 356 | x11packet.addByte(0);// single connection 357 | x11packet.addString(_X11Method); 358 | x11packet.addString(_fakeX11Cookie); 359 | x11packet.addInt(screenNum); 360 | try 361 | { 362 | ret = _channels.at(_mainChannel)->doChannelRequest("x11-req", x11req); 363 | if (ret == true) 364 | { 365 | _x11ReqSuccess = true; 366 | } 367 | } 368 | catch (const std::exception& ex) 369 | { 370 | cdLog(LogLevel::Error) << "getX11 " << ex.what(); 371 | } 372 | } 373 | return ret; 374 | } 375 | 376 | void CppsshChannel::handleIncomingChannelData(const Botan::secure_vector& buf) 377 | { 378 | CppsshConstPacket packet(&buf); 379 | packet.skipHeader(); 380 | uint32_t rxChannel = packet.getInt(); 381 | _channels.at(rxChannel)->handleIncomingChannelData(buf); 382 | } 383 | 384 | void CppsshChannel::handleIncomingControlData(const Botan::secure_vector& buf) 385 | { 386 | CppsshConstPacket packet(&buf); 387 | packet.skipHeader(); 388 | uint32_t rxChannel = packet.getInt(); 389 | _channels.at(rxChannel)->handleIncomingControlData(buf); 390 | } 391 | 392 | void CppsshChannel::handleWindowAdjust(const Botan::secure_vector& buf) 393 | { 394 | CppsshConstPacket packet(&buf); 395 | packet.skipHeader(); 396 | uint32_t rxChannel = packet.getInt(); 397 | uint32_t size = packet.getInt(); 398 | cdLog(LogLevel::Debug) << "handleWindowAdjust " << rxChannel << " " << size; 399 | _channels.at(rxChannel)->increaseWindowSend(size); 400 | } 401 | 402 | void CppsshChannel::handleIncomingGlobalData(const Botan::secure_vector& buf) 403 | { 404 | _incomingGlobalData.enqueue(buf); 405 | } 406 | 407 | bool CppsshChannel::waitForGlobalMessage(Botan::secure_vector& buf) 408 | { 409 | return _incomingGlobalData.dequeue(buf, _session->getTimeout()); 410 | } 411 | 412 | void CppsshChannel::handleBanner(const Botan::secure_vector& buf) 413 | { 414 | std::shared_ptr > lock = _channels.getLock(); 415 | std::string banner; 416 | CppsshConstPacket packet(&buf); 417 | packet.skipHeader(); 418 | packet.getString(&banner); 419 | std::shared_ptr message(new CppsshMessage()); 420 | VisualEncodeSafeOctalNoSlash vis(banner.c_str()); 421 | message->setMessage((const uint8_t*)vis.getEncoded().c_str(), vis.getEncoded().length()); 422 | _channels.at(_mainChannel)->handleBanner(message); 423 | } 424 | 425 | void CppsshChannel::handleChannelRequest(const Botan::secure_vector& buf) 426 | { 427 | CppsshConstPacket packet(&buf); 428 | packet.skipHeader(); 429 | uint32_t rxChannel = packet.getInt(); 430 | _channels.at(rxChannel)->handleChannelRequest(buf); 431 | } 432 | 433 | void CppsshChannel::handleReceived(const Botan::secure_vector& buf) 434 | { 435 | const CppsshConstPacket packet(&buf); 436 | Botan::byte cmd = packet.getCommand(); 437 | try 438 | { 439 | switch (cmd) 440 | { 441 | case SSH2_MSG_CHANNEL_WINDOW_ADJUST: 442 | handleWindowAdjust(buf); 443 | break; 444 | 445 | case SSH2_MSG_CHANNEL_SUCCESS: 446 | case SSH2_MSG_CHANNEL_FAILURE: 447 | case SSH2_MSG_CHANNEL_OPEN_CONFIRMATION: 448 | case SSH2_MSG_CHANNEL_OPEN_FAILURE: 449 | handleIncomingControlData(buf); 450 | break; 451 | 452 | case SSH2_MSG_CHANNEL_DATA: 453 | handleIncomingChannelData(buf); 454 | break; 455 | 456 | case SSH2_MSG_USERAUTH_FAILURE: 457 | case SSH2_MSG_USERAUTH_SUCCESS: 458 | case SSH2_MSG_USERAUTH_PK_OK: 459 | case SSH2_MSG_SERVICE_ACCEPT: 460 | case SSH2_MSG_KEXDH_REPLY: 461 | case SSH2_MSG_NEWKEYS: 462 | case SSH2_MSG_KEXINIT: 463 | handleIncomingGlobalData(buf); 464 | break; 465 | 466 | case SSH2_MSG_USERAUTH_BANNER: 467 | handleBanner(buf); 468 | break; 469 | 470 | case SSH2_MSG_CHANNEL_EXTENDED_DATA: 471 | //handleExtendedData(newPacket.value()); 472 | cdLog(LogLevel::Error) << "Unhandled SSH2_MSG_CHANNEL_EXTENDED_DATA: " << cmd; 473 | break; 474 | 475 | case SSH2_MSG_CHANNEL_EOF: 476 | handleEof(buf); 477 | break; 478 | 479 | case SSH2_MSG_CHANNEL_OPEN: 480 | handleOpen(buf); 481 | break; 482 | 483 | case SSH2_MSG_CHANNEL_CLOSE: 484 | handleClose(buf); 485 | break; 486 | 487 | case SSH2_MSG_CHANNEL_REQUEST: 488 | handleChannelRequest(buf); 489 | break; 490 | 491 | case SSH2_MSG_DEBUG: 492 | handleDebug(packet); 493 | break; 494 | 495 | case SSH2_MSG_DISCONNECT: 496 | handleDisconnect(packet); 497 | break; 498 | 499 | case SSH2_MSG_IGNORE: 500 | case SSH2_MSG_GLOBAL_REQUEST: 501 | case SSH2_MSG_REQUEST_SUCCESS: 502 | case SSH2_MSG_REQUEST_FAILURE: 503 | break; 504 | 505 | default: 506 | cdLog(LogLevel::Error) << "Unhandled command encountered: " << (int)cmd; 507 | break; 508 | } 509 | } 510 | catch (const std::exception& ex) 511 | { 512 | cdLog(LogLevel::Error) << CPPSSH_EXCEPTION; 513 | } 514 | } 515 | -------------------------------------------------------------------------------- /src/keys.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | cppssh - C++ ssh library 3 | Copyright (C) 2015 Chris Desjardins 4 | http://blog.chrisd.info cjd@chrisd.info 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | */ 19 | 20 | #include "impl.h" 21 | #include "keys.h" 22 | #include "packet.h" 23 | #include "botan/rsa.h" 24 | #include "botan/pem.h" 25 | #include "botan/ber_dec.h" 26 | #include "botan/der_enc.h" 27 | #include "botan/pubkey.h" 28 | #include "botan/numthry.h" 29 | #include "botan/pkcs8.h" 30 | #include "botan/x509_key.h" 31 | #include 32 | #ifndef WIN32 33 | #include 34 | #endif 35 | #include "debug.h" 36 | 37 | const std::string CppsshKeys::HEADER_DSA = "-----BEGINDSAPRIVATEKEY-----"; 38 | const std::string CppsshKeys::FOOTER_DSA = "-----ENDDSAPRIVATEKEY-----"; 39 | const std::string CppsshKeys::HEADER_RSA = "-----BEGINRSAPRIVATEKEY-----"; 40 | const std::string CppsshKeys::FOOTER_RSA = "-----ENDRSAPRIVATEKEY-----"; 41 | const std::string CppsshKeys::PROC_TYPE = "Proc-Type:"; 42 | const std::string CppsshKeys::DEK_INFO = "DEK-Info:"; 43 | 44 | bool CppsshKeys::isKey(const Botan::secure_vector& buf, std::string header, std::string footer) 45 | { 46 | bool ret = false; 47 | if ((std::search(buf.begin(), buf.end(), header.begin(), header.end()) != buf.end()) && 48 | (std::search(buf.begin(), buf.end(), footer.begin(), footer.end()) != buf.end())) 49 | { 50 | ret = true; 51 | } 52 | return ret; 53 | } 54 | 55 | bool CppsshKeys::checkPrivKeyFile(const std::string& privKeyFileName) 56 | { 57 | bool ret = true; 58 | #ifndef WIN32 59 | struct stat privKeyStatus; 60 | 61 | if (lstat(privKeyFileName.c_str(), &privKeyStatus) < 0) 62 | { 63 | ret = false; 64 | } 65 | else if ((privKeyStatus.st_mode & (S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)) != 0) 66 | { 67 | cdLog(LogLevel::Error) << "Private key file permissions are read/write by others: " << privKeyFileName; 68 | ret = false; 69 | } 70 | #endif 71 | return ret; 72 | } 73 | 74 | bool CppsshKeys::getKeyPairFromFile(const std::string& privKeyFileName, const char* keyPassword) 75 | { 76 | bool ret = false; 77 | Botan::secure_vector buf; 78 | CppsshPacket privKeyPacket(&buf); 79 | 80 | if ((checkPrivKeyFile(privKeyFileName) == true) && (privKeyPacket.addFile(privKeyFileName) == true)) 81 | { 82 | privKeyPacket.removeWhitespace(); 83 | _keyAlgo = hostkeyMethods::MAX_VALS; 84 | 85 | try 86 | { 87 | if (isKey(buf, PROC_TYPE, DEK_INFO) == true) 88 | { 89 | cdLog(LogLevel::Error) << 90 | "SSH traditional format private key, use \"openssl pkcs8 -topk8\" to modernize"; 91 | } 92 | else 93 | { 94 | if (isKey(buf, HEADER_DSA, FOOTER_DSA)) 95 | { 96 | _keyAlgo = hostkeyMethods::SSH_DSS; 97 | ret = getUnencryptedDSAKeys(buf); 98 | } 99 | else if (isKey(buf, HEADER_RSA, FOOTER_RSA)) 100 | { 101 | _keyAlgo = hostkeyMethods::SSH_RSA; 102 | ret = getUnencryptedRSAKeys(buf); 103 | } 104 | else 105 | { 106 | std::shared_ptr privKey(Botan::PKCS8::load_key(privKeyFileName, 107 | *CppsshImpl::RNG, std::string( 108 | keyPassword))); 109 | if (privKey != nullptr) 110 | { 111 | ret = getRSAKeys(privKey); 112 | if (ret == true) 113 | { 114 | _keyAlgo = hostkeyMethods::SSH_RSA; 115 | } 116 | else 117 | { 118 | ret = getDSAKeys(privKey); 119 | if (ret == true) 120 | { 121 | _keyAlgo = hostkeyMethods::SSH_DSS; 122 | } 123 | } 124 | } 125 | } 126 | } 127 | } 128 | catch (const std::exception& ex) 129 | { 130 | cdLog(LogLevel::Error) << "Unable to read keys: " << ex.what(); 131 | } 132 | } 133 | return ret; 134 | } 135 | 136 | Botan::secure_vector::const_iterator CppsshKeys::findKeyBegin( 137 | const Botan::secure_vector& privateKey, const std::string& header) 138 | { 139 | return privateKey.cbegin() + header.length(); 140 | } 141 | 142 | Botan::secure_vector::const_iterator CppsshKeys::findKeyEnd( 143 | const Botan::secure_vector& privateKey, const std::string& footer) 144 | { 145 | return privateKey.cend() - footer.length(); 146 | } 147 | 148 | bool CppsshKeys::getUnencryptedRSAKeys(Botan::secure_vector privateKey) 149 | { 150 | bool ret = false; 151 | Botan::secure_vector keyDataRaw; 152 | Botan::BigInt p, q, e, d, n; 153 | Botan::secure_vector key(findKeyBegin(privateKey, HEADER_RSA), findKeyEnd(privateKey, FOOTER_RSA)); 154 | Botan::Pipe base64dec(new Botan::Base64_Decoder); 155 | base64dec.process_msg(key); 156 | keyDataRaw = base64dec.read_all(); 157 | try 158 | { 159 | size_t version = 0; 160 | Botan::BER_Decoder decoder(keyDataRaw); 161 | Botan::BER_Decoder sequence = decoder.start_cons(Botan::SEQUENCE); 162 | 163 | sequence.decode(version); 164 | 165 | if (version != 0) 166 | { 167 | cdLog(LogLevel::Error) << "Encountered unknown RSA key version."; 168 | } 169 | else 170 | { 171 | sequence.decode(n); 172 | sequence.decode(e); 173 | sequence.decode(d); 174 | sequence.decode(p); 175 | sequence.decode(q); 176 | 177 | sequence.discard_remaining(); 178 | sequence.verify_end(); 179 | 180 | if (n.is_zero() || e.is_zero() || d.is_zero() || p.is_zero() || q.is_zero()) 181 | { 182 | cdLog(LogLevel::Error) << "Could not decode the supplied RSA key."; 183 | } 184 | else 185 | { 186 | _rsaPrivateKey.reset(new Botan::RSA_PrivateKey(p, q, e, d, n)); 187 | _publicKeyBlob.clear(); 188 | CppsshPacket publicKeyPacket(&_publicKeyBlob); 189 | publicKeyPacket.addString("ssh-rsa"); 190 | publicKeyPacket.addBigInt(e); 191 | publicKeyPacket.addBigInt(n); 192 | ret = true; 193 | } 194 | } 195 | } 196 | catch (const Botan::BER_Decoding_Error& ex) 197 | { 198 | cdLog(LogLevel::Error) << "Error decoding private key: " << ex.what(); 199 | CppsshDebug::dumpStack(-1); 200 | } 201 | return ret; 202 | } 203 | 204 | bool CppsshKeys::getUnencryptedDSAKeys(Botan::secure_vector privateKey) 205 | { 206 | bool ret = false; 207 | Botan::secure_vector keyDataRaw; 208 | Botan::BigInt p, q, g, y, x; 209 | Botan::secure_vector key(findKeyBegin(privateKey, HEADER_DSA), findKeyEnd(privateKey, FOOTER_DSA)); 210 | 211 | Botan::Pipe base64dec(new Botan::Base64_Decoder); 212 | base64dec.process_msg(key); 213 | keyDataRaw = base64dec.read_all(); 214 | 215 | try 216 | { 217 | size_t version; 218 | Botan::BER_Decoder decoder(keyDataRaw); 219 | Botan::BER_Decoder sequence = decoder.start_cons(Botan::SEQUENCE); 220 | sequence.decode(version); 221 | 222 | if (version) 223 | { 224 | cdLog(LogLevel::Error) << "Encountered unknown DSA key version."; 225 | } 226 | else 227 | { 228 | sequence.decode(p); 229 | sequence.decode(q); 230 | sequence.decode(g); 231 | sequence.decode(y); 232 | sequence.decode(x); 233 | 234 | sequence.discard_remaining(); 235 | sequence.verify_end(); 236 | 237 | if (p.is_zero() || q.is_zero() || g.is_zero() || y.is_zero() || x.is_zero()) 238 | { 239 | cdLog(LogLevel::Error) << "Could not decode the supplied DSA key."; 240 | } 241 | else 242 | { 243 | Botan::DL_Group dsaGroup(p, q, g); 244 | 245 | _dsaPrivateKey.reset(new Botan::DSA_PrivateKey(*CppsshImpl::RNG, dsaGroup, x)); 246 | _publicKeyBlob.clear(); 247 | CppsshPacket publicKeyPacket(&_publicKeyBlob); 248 | publicKeyPacket.addString("ssh-dss"); 249 | publicKeyPacket.addBigInt(p); 250 | publicKeyPacket.addBigInt(q); 251 | publicKeyPacket.addBigInt(g); 252 | publicKeyPacket.addBigInt(y); 253 | ret = true; 254 | } 255 | } 256 | } 257 | catch (const Botan::BER_Decoding_Error& ex) 258 | { 259 | cdLog(LogLevel::Error) << "Error decoding private key: " << ex.what(); 260 | CppsshDebug::dumpStack(-1); 261 | } 262 | return ret; 263 | } 264 | 265 | bool CppsshKeys::getRSAKeys(const std::shared_ptr& privKey) 266 | { 267 | bool ret = false; 268 | _rsaPrivateKey = std::dynamic_pointer_cast(privKey); 269 | if (_rsaPrivateKey != nullptr) 270 | { 271 | std::shared_ptr pubKey(Botan::X509::load_key(Botan::X509::BER_encode(*_rsaPrivateKey))); 272 | if (pubKey != nullptr) 273 | { 274 | std::shared_ptr rsaPubKey = std::dynamic_pointer_cast(pubKey); 275 | if (rsaPubKey != nullptr) 276 | { 277 | _publicKeyBlob.clear(); 278 | CppsshPacket publicKeyPacket(&_publicKeyBlob); 279 | publicKeyPacket.addString("ssh-rsa"); 280 | publicKeyPacket.addBigInt(rsaPubKey->get_e()); 281 | publicKeyPacket.addBigInt(rsaPubKey->get_n()); 282 | ret = true; 283 | } 284 | } 285 | } 286 | return ret; 287 | } 288 | 289 | bool CppsshKeys::getDSAKeys(const std::shared_ptr& privKey) 290 | { 291 | bool ret = false; 292 | _dsaPrivateKey = std::dynamic_pointer_cast(privKey); 293 | if (_dsaPrivateKey != nullptr) 294 | { 295 | std::shared_ptr pubKey(Botan::X509::load_key(Botan::X509::BER_encode(*privKey))); 296 | if (pubKey != nullptr) 297 | { 298 | std::shared_ptr dsaPubKey = std::dynamic_pointer_cast(pubKey); 299 | if (dsaPubKey != nullptr) 300 | { 301 | _publicKeyBlob.clear(); 302 | CppsshPacket publicKeyPacket(&_publicKeyBlob); 303 | publicKeyPacket.addString("ssh-dss"); 304 | publicKeyPacket.addBigInt(dsaPubKey->group_p()); 305 | publicKeyPacket.addBigInt(dsaPubKey->group_q()); 306 | publicKeyPacket.addBigInt(dsaPubKey->group_g()); 307 | publicKeyPacket.addBigInt(dsaPubKey->get_y()); 308 | ret = true; 309 | } 310 | } 311 | } 312 | return ret; 313 | } 314 | 315 | const Botan::secure_vector& CppsshKeys::generateSignature( 316 | const Botan::secure_vector& sessionID, const Botan::secure_vector& signingData) 317 | { 318 | _signature.clear(); 319 | switch (_keyAlgo) 320 | { 321 | case hostkeyMethods::SSH_RSA: 322 | _signature = generateRSASignature(sessionID, signingData); 323 | break; 324 | 325 | case hostkeyMethods::SSH_DSS: 326 | _signature = generateDSASignature(sessionID, signingData); 327 | break; 328 | 329 | default: 330 | cdLog(LogLevel::Error) << "Invalid key type (RSA, or DSA required)."; 331 | break; 332 | } 333 | 334 | return _signature; 335 | } 336 | 337 | Botan::secure_vector CppsshKeys::generateRSASignature(const Botan::secure_vector& sessionID, 338 | const Botan::secure_vector& signingData) 339 | { 340 | Botan::secure_vector ret; 341 | Botan::secure_vector sigRaw; 342 | CppsshPacket sigData(&sigRaw); 343 | 344 | sigData.addVectorField(sessionID); 345 | sigData.addVector(signingData); 346 | 347 | if (_rsaPrivateKey == nullptr) 348 | { 349 | cdLog(LogLevel::Error) << "Private RSA key not initialized."; 350 | } 351 | else 352 | { 353 | std::vector signedRaw; 354 | 355 | std::unique_ptr RSASigner(new Botan::PK_Signer(*_rsaPrivateKey, *CppsshImpl::RNG, 356 | "EMSA3(SHA-1)")); 357 | signedRaw = RSASigner->sign_message(sigRaw, *CppsshImpl::RNG); 358 | if (signedRaw.size() == 0) 359 | { 360 | cdLog(LogLevel::Error) << "Failure while generating RSA signature."; 361 | } 362 | else 363 | { 364 | CppsshPacket retPacket(&ret); 365 | retPacket.addString("ssh-rsa"); 366 | retPacket.addVectorField(Botan::secure_vector(signedRaw.begin(), signedRaw.end())); 367 | } 368 | } 369 | return ret; 370 | } 371 | 372 | Botan::secure_vector CppsshKeys::generateDSASignature(const Botan::secure_vector& sessionID, 373 | const Botan::secure_vector& signingData) 374 | { 375 | Botan::secure_vector ret; 376 | Botan::secure_vector sigRaw; 377 | CppsshPacket sigData(&sigRaw); 378 | 379 | sigData.addVectorField(sessionID); 380 | sigData.addVector(signingData); 381 | 382 | if (_dsaPrivateKey == nullptr) 383 | { 384 | cdLog(LogLevel::Error) << "Private DSA key not initialized."; 385 | } 386 | else 387 | { 388 | std::vector signedRaw; 389 | 390 | std::unique_ptr DSASigner(new Botan::PK_Signer(*_dsaPrivateKey, *CppsshImpl::RNG, 391 | "EMSA1(SHA-1)")); 392 | signedRaw = DSASigner->sign_message(sigRaw, *CppsshImpl::RNG); 393 | if (signedRaw.size() == 0) 394 | { 395 | cdLog(LogLevel::Error) << "Failure to generate DSA signature."; 396 | } 397 | else 398 | { 399 | if (signedRaw.size() != 40) 400 | { 401 | cdLog(LogLevel::Error) << 402 | "DSS signature block <> 320 bits. Make sure you are using 1024 bit keys for authentication!"; 403 | } 404 | else 405 | { 406 | CppsshPacket retPacket(&ret); 407 | retPacket.addString("ssh-dss"); 408 | retPacket.addVectorField(Botan::secure_vector(signedRaw.begin(), signedRaw.end())); 409 | } 410 | } 411 | } 412 | return ret; 413 | } 414 | 415 | bool CppsshKeys::generateRsaKeyPair(const char* fqdn, const char* privKeyFileName, const char* pubKeyFileName, 416 | short keySize) 417 | { 418 | bool ret = false; 419 | std::unique_ptr rsaPrivKey; 420 | Botan::BigInt e, n, d, p, q; 421 | Botan::BigInt dmp1, dmq1, iqmp; 422 | std::ofstream pubKeyFile; 423 | Botan::secure_vector buf; 424 | CppsshPacket pubKeyBlob(&buf); 425 | 426 | rsaPrivKey.reset(new Botan::RSA_PrivateKey(*CppsshImpl::RNG, keySize)); 427 | 428 | e = rsaPrivKey->get_e(); 429 | n = rsaPrivKey->get_n(); 430 | 431 | d = rsaPrivKey->get_d(); 432 | p = rsaPrivKey->get_p(); 433 | q = rsaPrivKey->get_q(); 434 | 435 | dmp1 = d % (p - 1); 436 | dmq1 = d % (q - 1); 437 | iqmp = Botan::inverse_mod(q, p); 438 | 439 | pubKeyBlob.addString("ssh-rsa"); 440 | pubKeyBlob.addBigInt(e); 441 | pubKeyBlob.addBigInt(n); 442 | 443 | Botan::Pipe base64it(new Botan::Base64_Encoder); 444 | base64it.process_msg(buf); 445 | 446 | Botan::secure_vector pubKeyBase64 = base64it.read_all(); 447 | 448 | pubKeyFile.open(pubKeyFileName); 449 | 450 | if (pubKeyFile.is_open() == false) 451 | { 452 | cdLog(LogLevel::Error) << "Cannot open file where public key is stored. Filename: " << pubKeyFileName; 453 | } 454 | else 455 | { 456 | pubKeyFile.exceptions(std::ofstream::failbit | std::ofstream::badbit); 457 | try 458 | { 459 | pubKeyFile.write("ssh-rsa ", 8); 460 | pubKeyFile.write((char*)pubKeyBase64.data(), (size_t)pubKeyBase64.size()); 461 | pubKeyFile.write(" ", 1); 462 | pubKeyFile.write(fqdn, strlen(fqdn)); 463 | pubKeyFile.write("\n", 1); 464 | } 465 | catch (const std::ofstream::failure&) 466 | { 467 | cdLog(LogLevel::Error) << "I/O error while writting to file: " << pubKeyFileName; 468 | CppsshDebug::dumpStack(-1); 469 | } 470 | if (pubKeyFile.fail() == false) 471 | { 472 | std::ofstream privKeyFile; 473 | std::string privKeyEncoded; 474 | privKeyEncoded = Botan::PEM_Code::encode( 475 | Botan::DER_Encoder().start_cons(Botan::SEQUENCE) 476 | .encode((size_t)0U) 477 | .encode(n) 478 | .encode(e) 479 | .encode(d) 480 | .encode(p) 481 | .encode(q) 482 | .encode(dmp1) 483 | .encode(dmq1) 484 | .encode(iqmp) 485 | .end_cons() 486 | .get_contents(), "RSA PRIVATE KEY"); 487 | 488 | privKeyFile.open(privKeyFileName); 489 | if (privKeyFile.is_open() == false) 490 | { 491 | cdLog(LogLevel::Error) << "Cannot open file where the private key is stored.Filename: " << 492 | privKeyFileName; 493 | } 494 | else 495 | { 496 | privKeyFile.write(privKeyEncoded.c_str(), privKeyEncoded.length()); 497 | if (privKeyFile.fail() == true) 498 | { 499 | cdLog(LogLevel::Error) << "IO error while writting to file: " << privKeyFileName; 500 | } 501 | else 502 | { 503 | ret = true; 504 | } 505 | } 506 | } 507 | } 508 | return ret; 509 | } 510 | 511 | bool CppsshKeys::generateDsaKeyPair(const char* fqdn, const char* privKeyFileName, const char* pubKeyFileName, 512 | short keySize) 513 | { 514 | bool ret = false; 515 | Botan::BigInt p, q, g, y, x; 516 | std::ofstream pubKeyFile; 517 | Botan::secure_vector buf; 518 | CppsshPacket pubKeyBlob(&buf); 519 | 520 | Botan::DL_Group dsaGroup(*CppsshImpl::RNG, Botan::DL_Group::DSA_Kosherizer, keySize); 521 | Botan::DSA_PrivateKey privDsaKey(*CppsshImpl::RNG, dsaGroup); 522 | Botan::DSA_PublicKey pubDsaKey = privDsaKey; 523 | 524 | p = dsaGroup.get_p(); 525 | q = dsaGroup.get_q(); 526 | g = dsaGroup.get_g(); 527 | y = pubDsaKey.get_y(); 528 | x = privDsaKey.get_x(); 529 | 530 | pubKeyBlob.addString("ssh-dss"); 531 | pubKeyBlob.addBigInt(p); 532 | pubKeyBlob.addBigInt(q); 533 | pubKeyBlob.addBigInt(g); 534 | pubKeyBlob.addBigInt(y); 535 | 536 | Botan::Pipe base64it(new Botan::Base64_Encoder); 537 | base64it.process_msg(buf); 538 | 539 | Botan::secure_vector pubKeyBase64 = base64it.read_all(); 540 | 541 | pubKeyFile.open(pubKeyFileName); 542 | 543 | if (pubKeyFile.is_open() == false) 544 | { 545 | cdLog(LogLevel::Error) << "Cannot open file where public key is stored. Filename: " << pubKeyFileName; 546 | } 547 | else 548 | { 549 | pubKeyFile.exceptions(std::ofstream::failbit | std::ofstream::badbit); 550 | try 551 | { 552 | pubKeyFile.write("ssh-dss ", 8); 553 | pubKeyFile.write((char*)pubKeyBase64.data(), pubKeyBase64.size()); 554 | pubKeyFile.write(" ", 1); 555 | pubKeyFile.write(fqdn, strlen(fqdn)); 556 | pubKeyFile.write("\n", 1); 557 | } 558 | catch (const std::ofstream::failure&) 559 | { 560 | cdLog(LogLevel::Error) << "I/O error while writting to file: " << pubKeyFileName; 561 | CppsshDebug::dumpStack(-1); 562 | } 563 | if (pubKeyFile.fail() == false) 564 | { 565 | Botan::DER_Encoder encoder; 566 | std::ofstream privKeyFile; 567 | std::string privKeyEncoded; 568 | 569 | encoder.start_cons(Botan::SEQUENCE) 570 | .encode((size_t)0U) 571 | .encode(p) 572 | .encode(q) 573 | .encode(g) 574 | .encode(y) 575 | .encode(x) 576 | .end_cons(); 577 | privKeyEncoded = Botan::PEM_Code::encode(encoder.get_contents(), "DSA PRIVATE KEY"); 578 | 579 | privKeyFile.open(privKeyFileName); 580 | 581 | if (privKeyFile.is_open() == false) 582 | { 583 | cdLog(LogLevel::Error) << "Cannot open file where private key is stored. Filename: " << privKeyFileName; 584 | } 585 | else 586 | { 587 | privKeyFile.write(privKeyEncoded.c_str(), privKeyEncoded.length()); 588 | if (privKeyFile.fail() == true) 589 | { 590 | cdLog(LogLevel::Error) << "I/O error while writting to file: " << privKeyFileName; 591 | } 592 | else 593 | { 594 | ret = true; 595 | } 596 | } 597 | } 598 | } 599 | return ret; 600 | } 601 | --------------------------------------------------------------------------------