@@ -502,11 +502,17 @@ void SshSession::processState()
502502 }
503503 case State::VerifyHostKey: {
504504 if (!verifyHostKey ())
505+ {
506+ if (_state == State::VerifyHostKeyWaitForInput)
507+ return ;
508+
505509 setState (State::Failure);
510+ }
506511 else
507512 setState (State::AuthenticateAgent);
508513 break ;
509514 }
515+ case State::VerifyHostKeyWaitForInput: return ;
510516 case State::AuthenticateAgent: {
511517 authenticateWithAgent ();
512518 break ;
@@ -814,6 +820,11 @@ int SshSession::write(std::string_view buf)
814820 handlePreAuthenticationPasswordInput (buf, State::AuthenticatePrivateKey);
815821 return static_cast <int >(buf.size ()); // Make the caller believe that we have written all bytes.
816822 }
823+ else if (_state == State::VerifyHostKeyWaitForInput)
824+ {
825+ handlePreAuthenticationPasswordInput (buf, State::VerifyHostKey);
826+ return static_cast <int >(buf.size ()); // Make the caller believe that we have written all bytes.
827+ }
817828 else if (_state != State::Operational)
818829 {
819830 sshLog ()(" Ignoring write() call in state: {}" , _state);
@@ -839,13 +850,22 @@ int SshSession::write(std::string_view buf)
839850
840851 if (ptyOutLog)
841852 {
842- if (rv >= 0 )
843- ptyOutLog ()(" Sending bytes: \" {}\" " , crispy::escape (buf.data (), buf.data () + rv)); // NOLINT
853+ bool const sensitive =
854+ _state >= State::AuthenticatePrivateKeyStart && _state <= State::AuthenticatePassword;
855+ if (sensitive)
856+ {
857+ ptyOutLog ()(" Sending {} bytes: (hidden in auth state)" , rv);
858+ }
859+ else
860+ {
861+ if (rv >= 0 )
862+ ptyOutLog ()(" Sending bytes: \" {}\" " , crispy::escape (buf.data (), buf.data () + rv)); // NOLINT
844863
845- if (0 <= rv && static_cast <size_t >(rv) < buf.size ())
846- ptyOutLog ()(" Partial write. {} bytes written and {} bytes left." ,
847- rv,
848- buf.size () - static_cast <size_t >(rv));
864+ if (0 <= rv && static_cast <size_t >(rv) < buf.size ())
865+ ptyOutLog ()(" Partial write. {} bytes written and {} bytes left." ,
866+ rv,
867+ buf.size () - static_cast <size_t >(rv));
868+ }
849869 }
850870
851871 return static_cast <int >(rv);
@@ -1088,27 +1108,78 @@ bool SshSession::verifyHostKey()
10881108 logError (" Host key verification failed. Host key mismatch." );
10891109 return false ;
10901110 case LIBSSH2_KNOWNHOST_CHECK_NOTFOUND: {
1091- // TODO: Ask user whether to add host key to known_hosts file
1092- auto const comment =
1093- std::format (" {}@{}:{} (added by Contour)" , _config.username , _config.hostname , _config.port );
1094- auto const typeMask = LIBSSH2_KNOWNHOST_TYPE_PLAIN | LIBSSH2_KNOWNHOST_KEYENC_RAW | knownhostType;
1095- libssh2_knownhost_addc (knownHosts,
1096- _config.hostname .c_str (),
1097- nullptr /* salt */ ,
1098- hostkeyRaw,
1099- hostkeyLength,
1100- comment.data (),
1101- comment.size (),
1102- typeMask,
1103- nullptr /* store */ );
1104- rc = libssh2_knownhost_writefile (
1105- knownHosts, _config.knownHostsFile .string ().c_str (), LIBSSH2_KNOWNHOST_FILE_OPENSSH);
1106- if (rc != LIBSSH2_ERROR_NONE)
1111+ if (!_injectedWrite.empty ())
11071112 {
1108- logErrorWithDetails (rc, " Failed to write known_hosts file" );
1109- return false ;
1113+ // We've got a user response.
1114+ auto const trim = [](std::string_view s) {
1115+ auto const start = s.find_first_not_of (" \t\r\n " );
1116+ if (start == std::string_view::npos)
1117+ return std::string_view {};
1118+ auto const end = s.find_last_not_of (" \t\r\n " );
1119+ return s.substr (start, end - start + 1 );
1120+ };
1121+ auto const response = trim (_injectedWrite);
1122+ _injectedWrite.clear ();
1123+ if (response == " yes" )
1124+ {
1125+ auto const comment = std::format (
1126+ " {}@{}:{} (added by Contour)" , _config.username , _config.hostname , _config.port );
1127+ auto const typeMask =
1128+ LIBSSH2_KNOWNHOST_TYPE_PLAIN | LIBSSH2_KNOWNHOST_KEYENC_RAW | knownhostType;
1129+ libssh2_knownhost_addc (knownHosts,
1130+ _config.hostname .c_str (),
1131+ nullptr /* salt */ ,
1132+ hostkeyRaw,
1133+ hostkeyLength,
1134+ comment.data (),
1135+ comment.size (),
1136+ typeMask,
1137+ nullptr /* store */ );
1138+ rc = libssh2_knownhost_writefile (
1139+ knownHosts, _config.knownHostsFile .string ().c_str (), LIBSSH2_KNOWNHOST_FILE_OPENSSH);
1140+ if (rc != LIBSSH2_ERROR_NONE)
1141+ {
1142+ logErrorWithDetails (rc, " Failed to write known_hosts file" );
1143+ return false ;
1144+ }
1145+ injectRead (" \r\n Host key verified and added.\r\n " );
1146+ return true ;
1147+ }
1148+ else
1149+ {
1150+ injectRead (" \r\n Host key verification failed. Connection aborted.\r\n " );
1151+ return false ;
1152+ }
11101153 }
1111- return true ;
1154+ // Ask user whether to add host key to known_hosts file
1155+ libssh2_knownhost_get (knownHosts, &knownHost, knownHost); // wait, knownHost is null?
1156+ // libssh2_knownhost_checkp returns the known host if found, but here it is NOTFOUND.
1157+ // We need to generate fingerprint from hostkeyRaw.
1158+ libssh2_session_hostkey (
1159+ _p->sshSession , &hostkeyLength, &knownhostType); // Use session function to get fingerprint?
1160+ // Actually libssh2_hostkey_hash is what we want.
1161+ auto const fingerprint = libssh2_hostkey_hash (_p->sshSession , LIBSSH2_HOSTKEY_HASH_SHA256);
1162+
1163+ auto fingerprintHex = std::string {};
1164+ if (fingerprint)
1165+ {
1166+ // SHA256 is 32 bytes
1167+ for (int i = 0 ; i < 32 ; ++i)
1168+ fingerprintHex += std::format (" {:02X}:" , (unsigned char ) fingerprint[i]);
1169+ if (!fingerprintHex.empty ())
1170+ fingerprintHex.pop_back ();
1171+ }
1172+ else
1173+ fingerprintHex = " UNKNOWN" ;
1174+
1175+ auto const message = std::format (" \r\n The authenticity of host '{}' cannot be established.\r\n "
1176+ " Fingerprint: {}\r\n "
1177+ " Are you sure you want to continue connecting (yes/no)? " ,
1178+ _config.hostname ,
1179+ fingerprintHex);
1180+ injectRead (message);
1181+ setState (State::VerifyHostKeyWaitForInput);
1182+ return false ;
11121183 }
11131184 case LIBSSH2_KNOWNHOST_CHECK_FAILURE: {
11141185 logErrorWithDetails (rc, " Host key verification failed" );
0 commit comments