From 563cb9018e5371a590c6c70cf3f8d4d6a19e9c36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sat, 26 Sep 2015 23:40:01 +0200 Subject: [PATCH] Bring back the certificate check callback This was implemented for clone, but not for fetch or push, so it was deleted during the conversion, which shows why we need to unify these callback structures. --- pygit2/remote.py | 54 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/pygit2/remote.py b/pygit2/remote.py index a30cf27..8cc9e35 100644 --- a/pygit2/remote.py +++ b/pygit2/remote.py @@ -122,6 +122,25 @@ class RemoteCallbacks(object): """ raise Passthrough + def certificate_check(self, certificate, valid, host): + """Certificate callback + + Override with your own function to determine whether the accept + the server's certificate. + + :param None certificate: The certificate. It is currently always None + while we figure out how to represent it cross-platform + + :param bool valid: Whether the TLS/SSH library thinks the certificate + is valid + + :param str host: The hostname we want to connect to + + Return value: True to connect, False to abort + """ + + raise Passthrough + def transfer_progress(self, stats): """Transfer progress callback @@ -156,6 +175,7 @@ class RemoteCallbacks(object): fetch_opts.callbacks.transfer_progress = self._transfer_progress_cb fetch_opts.callbacks.update_tips = self._update_tips_cb fetch_opts.callbacks.credentials = self._credentials_cb + fetch_opts.callbacks.certificate_check = self._certificate_cb # We need to make sure that this handle stays alive self._self_handle = ffi.new_handle(self) fetch_opts.callbacks.payload = self._self_handle @@ -167,6 +187,7 @@ class RemoteCallbacks(object): push_opts.callbacks.transfer_progress = self._transfer_progress_cb push_opts.callbacks.update_tips = self._update_tips_cb push_opts.callbacks.credentials = self._credentials_cb + push_opts.callbacks.certificate_check = self._certificate_cb push_opts.callbacks.push_update_reference = self._push_update_reference_cb # We need to make sure that this handle stays alive self._self_handle = ffi.new_handle(self) @@ -266,6 +287,39 @@ class RemoteCallbacks(object): return 0 + @ffi.callback('int (*git_transport_certificate_check_cb)' + '(git_cert *cert, int valid, const char *host, void *payload)') + def _certificate_cb(cert_i, valid, host, data): + self = ffi.from_handle(data) + + # We want to simulate what should happen if libgit2 supported pass-through for + # this callback. For SSH, 'valid' is always False, because it doesn't look + # at known_hosts, but we do want to let it through in order to do what libgit2 would + # if the callback were not set. + try: + is_ssh = cert_i.cert_type == C.GIT_CERT_HOSTKEY_LIBSSH2 + + if not hasattr(self, 'certificate_check') or not self.certificate_check: + raise Passthrough + + # python's parsing is deep in the libraries and assumes an OpenSSL-owned cert + val = self.certificate_check(None, bool(valid), ffi.string(host)) + if not val: + return C.GIT_ECERTIFICATE + except Exception as e: + if e is Passthrough: + if is_ssh: + return 0 + elif valid: + return 0 + else: + return C.GIT_ECERTIFICATE + + self._stored_exception = e + return C.GIT_EUSER + + return 0 + class Remote(object): def __init__(self, repo, ptr): """The constructor is for internal use only"""