Search code examples
cgitsshlibgit2libssh2

Using SSH authentification with libgit2


I am trying to authenticate against a git server with libgit2 using SSH keys. So far, this is working for URLs like ssh://[email protected]:1234/dirs/repo.git, where my application accepts the URL as an argument.

However, if I remove the username from the URL (i.e. ssh://host.domain:1234/dirs/repo.git) the connection fails, even if I set the user name programmatically (see below).

Consider the following MCVE for a program that checks whether a certain repository is reachable (no error checks except for the necessary):

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <pwd.h>
#include <git2.h>
#include <git2/sys/repository.h>

int my_cred_cb(git_cred **out, const char *url,
    const char *username_from_url, unsigned int allowed_types, void *payload)
{
  uid_t uid = geteuid();             // Get effective user ID
  struct passwd* pw = getpwuid(uid); // Get password file entry

  char privkeypath[strlen(pw->pw_dir) + 13];
  strcpy(privkeypath, pw->pw_dir);
  strcat(privkeypath, "/.ssh/id_rsa"); // "~user/.ssh/id_rsa"

  char publkeypath[strlen(privkeypath) + 5]; 
  strcpy(publkeypath, privkeypath);
  strcat(publkeypath, ".pub"); // "~user/.ssh/id_rsa.pub"

  const char* username = (username_from_url != NULL ? username_from_url : pw->pw_name);

  printf("Using username: %s, priv key %s and publ key %s\n", username, privkeypath, publkeypath);
  return git_cred_ssh_key_new(out, username, publkeypath, privkeypath, ""); // No passphrase for keys
}

int main(int argc, char** argv)
{
  git_remote* remote = NULL;
  git_repository* repo = NULL;
  git_remote_callbacks cbs;
  const git_error* err;

  if (argc != 2) return EXIT_FAILURE;

  git_libgit2_init();
  git_repository_new(&repo);
  git_remote_create_anonymous(&remote, repo, argv[1]);
  git_remote_init_callbacks(&cbs, GIT_REMOTE_CALLBACKS_VERSION);
  cbs.credentials = my_cred_cb;

  if (git_remote_connect(remote, GIT_DIRECTION_FETCH, &cbs, NULL, NULL)) {
    err = giterr_last();
    printf ("Error %d: %s\n", err->klass, err->message);
    return EXIT_FAILURE;
  }

  git_libgit2_shutdown();
  printf("git repo exists and is reachable.\n");
}

This can be compiled with gcc -Wall -pedantic -std=c99 -o myapp main.c -lssh2 -lgit2, assuming the include and library paths are set properly.

Now consider the output for different URLs:

$ ./myapp ssh://[email protected]:1234/dirs/repo.git
Using username: myuser, priv key /home/myuser/.ssh/id_rsa and publ key /home/myuser/.ssh/id_rsa.pub
git repo exists and is reachable.

And now the failing case:

$ ./myapp ssh://host.domain:1234/dirs/repo.git # Note the missing user name
Using username: myuser, priv key /home/myuser/.ssh/id_rsa and publ key /home/myuser/.ssh/id_rsa.pub
Error 23: callback returned unsupported credentials type

I do not understand why I receive this error, since I pass the exact same information to the library (why is it "unsupported credentials type" in the first place?).

Even worse, I usually use Host entries in my ~/.ssh/config, such that instead of putting host.domain:1234 I can simply use myhost (e.g. git clone ssh://myhost/dirs/repo.git works just fine). For my application, this is the output:

$ ./myapp ssh://myhost/dirs/repo.git
Error 12: failed to resolve address for myhost: Name or service not known

It looks like libgit2 does not configure libssh2 to read in the ssh-config. That's a related, yet probably a different problem. Still I feel these two problems belong together.

Summarizing my questions:

  1. How do I pass the username to the git credentials programmatically (vs. in the URL)?
  2. How do I tell libgit2 to retrieve information from my ssh-config before attempting to connect?

Solution

  • These really are two separate unrelated questions.

    1. You should be checking the allowed_types parameter in your callback and only return a git_cred_ssh_key_new credential if it contains GIT_CREDTYPE_SSH_KEY. The backend is probably requesting a credential of type GIT_CREDTYPE_USERNAME because the URL doesn't have one. In that case you should be returning a credential created by git_cred_username_new. Your callback will be called twice, once to get the username, and a second time to create the ssh credential.

    2. Reading config settings from your OpenSSH config file at ~/.ssh/config isn't supported by libgit2 because it isn't support by libssh2. If you want to read settings from there, you have to do it yourself.