@@ -122,6 +122,25 @@ def credentials(self, url, username_from_url, allowed_types):
122
122
"""
123
123
raise Passthrough
124
124
125
+ def certificate_check (self , certificate , valid , host ):
126
+ """Certificate callback
127
+
128
+ Override with your own function to determine whether the accept
129
+ the server's certificate.
130
+
131
+ :param None certificate: The certificate. It is currently always None
132
+ while we figure out how to represent it cross-platform
133
+
134
+ :param bool valid: Whether the TLS/SSH library thinks the certificate
135
+ is valid
136
+
137
+ :param str host: The hostname we want to connect to
138
+
139
+ Return value: True to connect, False to abort
140
+ """
141
+
142
+ raise Passthrough
143
+
125
144
def transfer_progress (self , stats ):
126
145
"""Transfer progress callback
127
146
@@ -156,6 +175,7 @@ def _fill_fetch_options(self, fetch_opts):
156
175
fetch_opts .callbacks .transfer_progress = self ._transfer_progress_cb
157
176
fetch_opts .callbacks .update_tips = self ._update_tips_cb
158
177
fetch_opts .callbacks .credentials = self ._credentials_cb
178
+ fetch_opts .callbacks .certificate_check = self ._certificate_cb
159
179
# We need to make sure that this handle stays alive
160
180
self ._self_handle = ffi .new_handle (self )
161
181
fetch_opts .callbacks .payload = self ._self_handle
@@ -167,6 +187,7 @@ def _fill_push_options(self, push_opts):
167
187
push_opts .callbacks .transfer_progress = self ._transfer_progress_cb
168
188
push_opts .callbacks .update_tips = self ._update_tips_cb
169
189
push_opts .callbacks .credentials = self ._credentials_cb
190
+ push_opts .callbacks .certificate_check = self ._certificate_cb
170
191
push_opts .callbacks .push_update_reference = self ._push_update_reference_cb
171
192
# We need to make sure that this handle stays alive
172
193
self ._self_handle = ffi .new_handle (self )
@@ -266,6 +287,39 @@ def _credentials_cb(cred_out, url, username, allowed, data):
266
287
267
288
return 0
268
289
290
+ @ffi .callback ('int (*git_transport_certificate_check_cb)'
291
+ '(git_cert *cert, int valid, const char *host, void *payload)' )
292
+ def _certificate_cb (cert_i , valid , host , data ):
293
+ self = ffi .from_handle (data )
294
+
295
+ # We want to simulate what should happen if libgit2 supported pass-through for
296
+ # this callback. For SSH, 'valid' is always False, because it doesn't look
297
+ # at known_hosts, but we do want to let it through in order to do what libgit2 would
298
+ # if the callback were not set.
299
+ try :
300
+ is_ssh = cert_i .cert_type == C .GIT_CERT_HOSTKEY_LIBSSH2
301
+
302
+ if not hasattr (self , 'certificate_check' ) or not self .certificate_check :
303
+ raise Passthrough
304
+
305
+ # python's parsing is deep in the libraries and assumes an OpenSSL-owned cert
306
+ val = self .certificate_check (None , bool (valid ), ffi .string (host ))
307
+ if not val :
308
+ return C .GIT_ECERTIFICATE
309
+ except Exception as e :
310
+ if e is Passthrough :
311
+ if is_ssh :
312
+ return 0
313
+ elif valid :
314
+ return 0
315
+ else :
316
+ return C .GIT_ECERTIFICATE
317
+
318
+ self ._stored_exception = e
319
+ return C .GIT_EUSER
320
+
321
+ return 0
322
+
269
323
class Remote (object ):
270
324
def __init__ (self , repo , ptr ):
271
325
"""The constructor is for internal use only"""
0 commit comments