@@ -454,6 +454,226 @@ static int _git_ssh_session_create(
454454 return 0 ;
455455}
456456
457+ /*
458+ * Returns the typemask argument to pass to libssh2_knownhost_check{,p} based on
459+ * the type of key that libssh2_session_hostkey returns.
460+ */
461+ static int fingerprint_type_mask (int keytype )
462+ {
463+ int mask = LIBSSH2_KNOWNHOST_TYPE_PLAIN | LIBSSH2_KNOWNHOST_KEYENC_RAW ;
464+ return mask ;
465+
466+ switch (keytype ) {
467+ case LIBSSH2_HOSTKEY_TYPE_RSA :
468+ mask |= LIBSSH2_KNOWNHOST_KEY_SSHRSA ;
469+ break ;
470+ case LIBSSH2_HOSTKEY_TYPE_DSS :
471+ mask |= LIBSSH2_KNOWNHOST_KEY_SSHDSS ;
472+ break ;
473+ #ifdef LIBSSH2_HOSTKEY_TYPE_ECDSA_256
474+ case LIBSSH2_HOSTKEY_TYPE_ECDSA_256 :
475+ mask |= LIBSSH2_KNOWNHOST_KEY_ECDSA_256 ;
476+ break ;
477+ case LIBSSH2_HOSTKEY_TYPE_ECDSA_384 :
478+ mask |= LIBSSH2_KNOWNHOST_KEY_ECDSA_384 ;
479+ break ;
480+ case LIBSSH2_HOSTKEY_TYPE_ECDSA_521 :
481+ mask |= LIBSSH2_KNOWNHOST_KEY_ECDSA_521 ;
482+ break ;
483+ #endif
484+ #ifdef LIBSSH2_HOSTKEY_TYPE_ED25519
485+ case LIBSSH2_HOSTKEY_TYPE_ED25519 :
486+ mask |= LIBSSH2_KNOWNHOST_KEY_ED25519 ;
487+ break ;
488+ #endif
489+ }
490+
491+ return mask ;
492+ }
493+
494+ #define KNOWN_HOSTS_FILE ".ssh/known_hosts"
495+
496+ /*
497+ * Check the host against the user's known_hosts file.
498+ *
499+ * Returns 1/0 for valid/''not-valid or <0 for an error
500+ */
501+ static int check_against_known_hosts (
502+ LIBSSH2_SESSION * session ,
503+ const char * hostname ,
504+ int port ,
505+ const char * key ,
506+ size_t key_len ,
507+ int key_type )
508+ {
509+ int error , check , typemask , ret = 0 ;
510+ git_str path = GIT_STR_INIT , home = GIT_STR_INIT ;
511+ LIBSSH2_KNOWNHOSTS * known_hosts = NULL ;
512+ struct libssh2_knownhost * host = NULL ;
513+
514+ if ((error = git__getenv (& home , "HOME" )) < 0 ) {
515+ return error ;
516+ }
517+
518+ if ((error = git_str_joinpath (& path , git_str_cstr (& home ), KNOWN_HOSTS_FILE )) < 0 ) {
519+ ret = error ;
520+ goto out ;
521+ }
522+
523+ if ((known_hosts = libssh2_knownhost_init (session )) == NULL ) {
524+ ssh_error (session , "error initializing known hosts" );
525+ ret = -1 ;
526+ goto out ;
527+ }
528+
529+ /*
530+ * Try to read the file and consider not finding it as not trusting the
531+ * host rather than an error.
532+ */
533+ error = libssh2_knownhost_readfile (known_hosts , git_str_cstr (& path ), LIBSSH2_KNOWNHOST_FILE_OPENSSH );
534+ if (error == LIBSSH2_ERROR_FILE ) {
535+ ret = 0 ;
536+ goto out ;
537+ }
538+ if (error < 0 ) {
539+ ssh_error (session , "error reading known_hosts" );
540+ ret = -1 ;
541+ goto out ;
542+ }
543+
544+ typemask = fingerprint_type_mask (key_type );
545+ check = libssh2_knownhost_checkp (known_hosts , hostname , port , key , key_len , typemask , & host );
546+ if (check == LIBSSH2_KNOWNHOST_CHECK_FAILURE ) {
547+ ssh_error (session , "error checking for known host" );
548+ ret = -1 ;
549+ goto out ;
550+ }
551+
552+ ret = check == LIBSSH2_KNOWNHOST_CHECK_MATCH ? 1 : 0 ;
553+
554+ out :
555+ libssh2_knownhost_free (known_hosts );
556+ git_str_clear (& path );
557+ git_str_clear (& home );
558+
559+ return ret ;
560+ }
561+
562+ /*
563+ * Perform the check for the session's certificate against known hosts if
564+ * possible and then ask the user if they have a callback.
565+ *
566+ * Returns 1/0 for valid/not-valid or <0 for an error
567+ */
568+ static int check_certificate (
569+ LIBSSH2_SESSION * session ,
570+ git_transport_certificate_check_cb check_cb ,
571+ void * check_cb_payload ,
572+ const char * host ,
573+ const char * portstr )
574+ {
575+ git_cert_hostkey cert = {{ 0 }};
576+ const char * key ;
577+ size_t cert_len ;
578+ int cert_type , port , cert_valid = 0 , error = 0 ;
579+
580+ if ((key = libssh2_session_hostkey (session , & cert_len , & cert_type )) == NULL ) {
581+ ssh_error (session , "failed to retrieve hostkey" );
582+ return -1 ;
583+ }
584+
585+ /* Try to parse the port as a number, if we can't then fall back to default */
586+ if (git__strntol32 (& port , portstr , strlen (portstr ), NULL , 10 ) < 0 )
587+ port = -1 ;
588+
589+ if ((cert_valid = check_against_known_hosts (session , host , port , key , cert_len , cert_type )) < 0 )
590+ return -1 ;
591+
592+ cert .parent .cert_type = GIT_CERT_HOSTKEY_LIBSSH2 ;
593+ if (key != NULL ) {
594+ cert .type |= GIT_CERT_SSH_RAW ;
595+ cert .hostkey = key ;
596+ cert .hostkey_len = cert_len ;
597+ switch (cert_type ) {
598+ case LIBSSH2_HOSTKEY_TYPE_RSA :
599+ cert .raw_type = GIT_CERT_SSH_RAW_TYPE_RSA ;
600+ break ;
601+ case LIBSSH2_HOSTKEY_TYPE_DSS :
602+ cert .raw_type = GIT_CERT_SSH_RAW_TYPE_DSS ;
603+ break ;
604+
605+ #ifdef LIBSSH2_HOSTKEY_TYPE_ECDSA_256
606+ case LIBSSH2_HOSTKEY_TYPE_ECDSA_256 :
607+ cert .raw_type = GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_256 ;
608+ break ;
609+ case LIBSSH2_HOSTKEY_TYPE_ECDSA_384 :
610+ cert .raw_type = GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_384 ;
611+ break ;
612+ case LIBSSH2_KNOWNHOST_KEY_ECDSA_521 :
613+ cert .raw_type = GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_521 ;
614+ break ;
615+ #endif
616+
617+ #ifdef LIBSSH2_HOSTKEY_TYPE_ED25519
618+ case LIBSSH2_HOSTKEY_TYPE_ED25519 :
619+ cert .raw_type = GIT_CERT_SSH_RAW_TYPE_KEY_ED25519 ;
620+ break ;
621+ #endif
622+ default :
623+ cert .raw_type = GIT_CERT_SSH_RAW_TYPE_UNKNOWN ;
624+ }
625+ }
626+
627+ #ifdef LIBSSH2_HOSTKEY_HASH_SHA256
628+ key = libssh2_hostkey_hash (session , LIBSSH2_HOSTKEY_HASH_SHA256 );
629+ if (key != NULL ) {
630+ cert .type |= GIT_CERT_SSH_SHA256 ;
631+ memcpy (& cert .hash_sha256 , key , 32 );
632+ }
633+ #endif
634+
635+ key = libssh2_hostkey_hash (session , LIBSSH2_HOSTKEY_HASH_SHA1 );
636+ if (key != NULL ) {
637+ cert .type |= GIT_CERT_SSH_SHA1 ;
638+ memcpy (& cert .hash_sha1 , key , 20 );
639+ }
640+
641+ key = libssh2_hostkey_hash (session , LIBSSH2_HOSTKEY_HASH_MD5 );
642+ if (key != NULL ) {
643+ cert .type |= GIT_CERT_SSH_MD5 ;
644+ memcpy (& cert .hash_md5 , key , 16 );
645+ }
646+
647+ if (cert .type == 0 ) {
648+ git_error_set (GIT_ERROR_SSH , "unable to get the host key" );
649+ return -1 ;
650+ }
651+
652+ git_error_clear ();
653+ error = 0 ;
654+ if (!cert_valid ) {
655+ git_error_set (GIT_ERROR_SSH , "invalid or unknown remote ssh hostkey" );
656+ error = GIT_ECERTIFICATE ;
657+ }
658+
659+ if (check_cb != NULL ) {
660+ git_cert_hostkey * cert_ptr = & cert ;
661+ git_error_state previous_error = {0 };
662+
663+ git_error_state_capture (& previous_error , error );
664+ error = check_cb ((git_cert * ) cert_ptr , cert_valid , host , check_cb_payload );
665+ if (error == GIT_PASSTHROUGH ) {
666+ error = git_error_state_restore (& previous_error );
667+ } else if (error < 0 && !git_error_last ()) {
668+ git_error_set (GIT_ERROR_NET , "user canceled hostkey check" );
669+ }
670+
671+ git_error_state_free (& previous_error );
672+ }
673+
674+ return error ;
675+ }
676+
457677#define SSH_DEFAULT_PORT "22"
458678
459679static int _git_ssh_setup_conn (
@@ -493,93 +713,8 @@ static int _git_ssh_setup_conn(
493713 if ((error = _git_ssh_session_create (& session , s -> io )) < 0 )
494714 goto done ;
495715
496- if (t -> owner -> connect_opts .callbacks .certificate_check != NULL ) {
497- git_cert_hostkey cert = {{ 0 }}, * cert_ptr ;
498- const char * key ;
499- size_t cert_len ;
500- int cert_type ;
501-
502- cert .parent .cert_type = GIT_CERT_HOSTKEY_LIBSSH2 ;
503-
504- key = libssh2_session_hostkey (session , & cert_len , & cert_type );
505- if (key != NULL ) {
506- cert .type |= GIT_CERT_SSH_RAW ;
507- cert .hostkey = key ;
508- cert .hostkey_len = cert_len ;
509- switch (cert_type ) {
510- case LIBSSH2_HOSTKEY_TYPE_RSA :
511- cert .raw_type = GIT_CERT_SSH_RAW_TYPE_RSA ;
512- break ;
513- case LIBSSH2_HOSTKEY_TYPE_DSS :
514- cert .raw_type = GIT_CERT_SSH_RAW_TYPE_DSS ;
515- break ;
516-
517- #ifdef LIBSSH2_HOSTKEY_TYPE_ECDSA_256
518- case LIBSSH2_HOSTKEY_TYPE_ECDSA_256 :
519- cert .raw_type = GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_256 ;
520- break ;
521- case LIBSSH2_HOSTKEY_TYPE_ECDSA_384 :
522- cert .raw_type = GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_384 ;
523- break ;
524- case LIBSSH2_KNOWNHOST_KEY_ECDSA_521 :
525- cert .raw_type = GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_521 ;
526- break ;
527- #endif
528-
529- #ifdef LIBSSH2_HOSTKEY_TYPE_ED25519
530- case LIBSSH2_HOSTKEY_TYPE_ED25519 :
531- cert .raw_type = GIT_CERT_SSH_RAW_TYPE_KEY_ED25519 ;
532- break ;
533- #endif
534- default :
535- cert .raw_type = GIT_CERT_SSH_RAW_TYPE_UNKNOWN ;
536- }
537- }
538-
539- #ifdef LIBSSH2_HOSTKEY_HASH_SHA256
540- key = libssh2_hostkey_hash (session , LIBSSH2_HOSTKEY_HASH_SHA256 );
541- if (key != NULL ) {
542- cert .type |= GIT_CERT_SSH_SHA256 ;
543- memcpy (& cert .hash_sha256 , key , 32 );
544- }
545- #endif
546-
547- key = libssh2_hostkey_hash (session , LIBSSH2_HOSTKEY_HASH_SHA1 );
548- if (key != NULL ) {
549- cert .type |= GIT_CERT_SSH_SHA1 ;
550- memcpy (& cert .hash_sha1 , key , 20 );
551- }
552-
553- key = libssh2_hostkey_hash (session , LIBSSH2_HOSTKEY_HASH_MD5 );
554- if (key != NULL ) {
555- cert .type |= GIT_CERT_SSH_MD5 ;
556- memcpy (& cert .hash_md5 , key , 16 );
557- }
558-
559- if (cert .type == 0 ) {
560- git_error_set (GIT_ERROR_SSH , "unable to get the host key" );
561- error = -1 ;
562- goto done ;
563- }
564-
565- /* We don't currently trust any hostkeys */
566- git_error_clear ();
567-
568- cert_ptr = & cert ;
569-
570- error = t -> owner -> connect_opts .callbacks .certificate_check (
571- (git_cert * )cert_ptr ,
572- 0 ,
573- s -> url .host ,
574- t -> owner -> connect_opts .callbacks .payload );
575-
576- if (error < 0 && error != GIT_PASSTHROUGH ) {
577- if (!git_error_last ())
578- git_error_set (GIT_ERROR_NET , "user cancelled hostkey check" );
579-
580- goto done ;
581- }
582- }
716+ if ((error = check_certificate (session , t -> owner -> connect_opts .callbacks .certificate_check , t -> owner -> connect_opts .callbacks .payload , s -> url .host , s -> url .port )) < 0 )
717+ goto done ;
583718
584719 /* we need the username to ask for auth methods */
585720 if (!s -> url .username ) {
0 commit comments