comparison mercurial/win32.py @ 33492:14af04391fb9

win32: add a method to trigger the Crypto API to complete a certificate chain I started a thread[1] on the mailing list awhile ago, but the short version is that Windows doesn't ship with a full list of certificates[2]. Even if the server sends the whole chain, if Windows doesn't have the appropriate certificate pre-installed in its "Third-Party Root Certification Authorities" store, connections mysteriously fail with: abort: error: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:661) Windows expects the application to call the methods invoked here as part of the certificate verification, triggering a call out to Windows update if necessary, to complete the trust chain. The python bug to add this support[3] hasn't had any recent activity, and isn't targeting py27 anyway. The only work around that I could find (besides figuring out the certificate and walking through the import wizard) is to browse to the site in Internet Explorer. Opening the page with FireFox or Chrome didn't work. That's a pretty obscure way to fix a pretty obscure problem. We go to great lengths to demystify various SSL errors, but this case is clearly lacking. Let's try to make things easier to diagnose and fix. When I had trouble figuring out how to get ctypes to work with all of the API pointers, I found that there are other python projects[4] using this API to achieve the same thing. [1] https://www.mercurial-scm.org/pipermail/mercurial-devel/2017-April/096501.html [2] https://support.microsoft.com/en-us/help/931125/how-to-get-a-root-certificate-update-for-windows [3] https://bugs.python.org/issue20916 [4] https://github.com/nvaccess/nvda/blob/3b86bce2066b1934df14b96f2e83369900860ecf/source/updateCheck.py#L511
author Matt Harbison <matt_harbison@yahoo.com>
date Wed, 29 Mar 2017 23:45:23 -0400
parents 7c33adc823e0
children d5b2beca16c0
comparison
equal deleted inserted replaced
33491:1adcb594eb6b 33492:14af04391fb9
20 ) 20 )
21 21
22 _kernel32 = ctypes.windll.kernel32 22 _kernel32 = ctypes.windll.kernel32
23 _advapi32 = ctypes.windll.advapi32 23 _advapi32 = ctypes.windll.advapi32
24 _user32 = ctypes.windll.user32 24 _user32 = ctypes.windll.user32
25 _crypt32 = ctypes.windll.crypt32
25 26
26 _BOOL = ctypes.c_long 27 _BOOL = ctypes.c_long
27 _WORD = ctypes.c_ushort 28 _WORD = ctypes.c_ushort
28 _DWORD = ctypes.c_ulong 29 _DWORD = ctypes.c_ulong
29 _UINT = ctypes.c_uint 30 _UINT = ctypes.c_uint
30 _LONG = ctypes.c_long 31 _LONG = ctypes.c_long
31 _LPCSTR = _LPSTR = ctypes.c_char_p 32 _LPCSTR = _LPSTR = ctypes.c_char_p
32 _HANDLE = ctypes.c_void_p 33 _HANDLE = ctypes.c_void_p
33 _HWND = _HANDLE 34 _HWND = _HANDLE
35 _PCCERT_CONTEXT = ctypes.c_void_p
34 36
35 _INVALID_HANDLE_VALUE = _HANDLE(-1).value 37 _INVALID_HANDLE_VALUE = _HANDLE(-1).value
36 38
37 # GetLastError 39 # GetLastError
38 _ERROR_SUCCESS = 0 40 _ERROR_SUCCESS = 0
132 ('dwMaximumWindowSize', _COORD)] 134 ('dwMaximumWindowSize', _COORD)]
133 135
134 _STD_OUTPUT_HANDLE = _DWORD(-11).value 136 _STD_OUTPUT_HANDLE = _DWORD(-11).value
135 _STD_ERROR_HANDLE = _DWORD(-12).value 137 _STD_ERROR_HANDLE = _DWORD(-12).value
136 138
139 # CERT_TRUST_STATUS dwErrorStatus
140 CERT_TRUST_IS_PARTIAL_CHAIN = 0x10000
141
142 # CertCreateCertificateContext encodings
143 X509_ASN_ENCODING = 0x00000001
144 PKCS_7_ASN_ENCODING = 0x00010000
145
146 # These structs are only complete enough to achieve what we need.
147 class CERT_CHAIN_CONTEXT(ctypes.Structure):
148 _fields_ = (
149 ("cbSize", _DWORD),
150
151 # CERT_TRUST_STATUS struct
152 ("dwErrorStatus", _DWORD),
153 ("dwInfoStatus", _DWORD),
154
155 ("cChain", _DWORD),
156 ("rgpChain", ctypes.c_void_p),
157 ("cLowerQualityChainContext", _DWORD),
158 ("rgpLowerQualityChainContext", ctypes.c_void_p),
159 ("fHasRevocationFreshnessTime", _BOOL),
160 ("dwRevocationFreshnessTime", _DWORD),
161 )
162
163 class CERT_USAGE_MATCH(ctypes.Structure):
164 _fields_ = (
165 ("dwType", _DWORD),
166
167 # CERT_ENHKEY_USAGE struct
168 ("cUsageIdentifier", _DWORD),
169 ("rgpszUsageIdentifier", ctypes.c_void_p), # LPSTR *
170 )
171
172 class CERT_CHAIN_PARA(ctypes.Structure):
173 _fields_ = (
174 ("cbSize", _DWORD),
175 ("RequestedUsage", CERT_USAGE_MATCH),
176 ("RequestedIssuancePolicy", CERT_USAGE_MATCH),
177 ("dwUrlRetrievalTimeout", _DWORD),
178 ("fCheckRevocationFreshnessTime", _BOOL),
179 ("dwRevocationFreshnessTime", _DWORD),
180 ("pftCacheResync", ctypes.c_void_p), # LPFILETIME
181 ("pStrongSignPara", ctypes.c_void_p), # PCCERT_STRONG_SIGN_PARA
182 ("dwStrongSignFlags", _DWORD),
183 )
184
137 # types of parameters of C functions used (required by pypy) 185 # types of parameters of C functions used (required by pypy)
186
187 _crypt32.CertCreateCertificateContext.argtypes = [_DWORD, # cert encoding
188 ctypes.c_char_p, # cert
189 _DWORD] # cert size
190 _crypt32.CertCreateCertificateContext.restype = _PCCERT_CONTEXT
191
192 _crypt32.CertGetCertificateChain.argtypes = [
193 ctypes.c_void_p, # HCERTCHAINENGINE
194 _PCCERT_CONTEXT,
195 ctypes.c_void_p, # LPFILETIME
196 ctypes.c_void_p, # HCERTSTORE
197 ctypes.c_void_p, # PCERT_CHAIN_PARA
198 _DWORD,
199 ctypes.c_void_p, # LPVOID
200 ctypes.c_void_p # PCCERT_CHAIN_CONTEXT *
201 ]
202 _crypt32.CertGetCertificateChain.restype = _BOOL
203
204 _crypt32.CertFreeCertificateContext.argtypes = [_PCCERT_CONTEXT]
205 _crypt32.CertFreeCertificateContext.restype = _BOOL
138 206
139 _kernel32.CreateFileA.argtypes = [_LPCSTR, _DWORD, _DWORD, ctypes.c_void_p, 207 _kernel32.CreateFileA.argtypes = [_LPCSTR, _DWORD, _DWORD, ctypes.c_void_p,
140 _DWORD, _DWORD, _HANDLE] 208 _DWORD, _DWORD, _HANDLE]
141 _kernel32.CreateFileA.restype = _HANDLE 209 _kernel32.CreateFileA.restype = _HANDLE
142 210
231 if not _kernel32.GetFileInformationByHandle(fh, ctypes.byref(fi)): 299 if not _kernel32.GetFileInformationByHandle(fh, ctypes.byref(fi)):
232 _raiseoserror(name) 300 _raiseoserror(name)
233 return fi 301 return fi
234 finally: 302 finally:
235 _kernel32.CloseHandle(fh) 303 _kernel32.CloseHandle(fh)
304
305 def checkcertificatechain(cert, build=True):
306 '''Tests the given certificate to see if there is a complete chain to a
307 trusted root certificate. As a side effect, missing certificates are
308 downloaded and installed unless ``build=False``. True is returned if a
309 chain to a trusted root exists (even if built on the fly), otherwise
310 False. NB: A chain to a trusted root does NOT imply that the certificate
311 is valid.
312 '''
313
314 chainctxptr = ctypes.POINTER(CERT_CHAIN_CONTEXT)
315
316 pchainctx = chainctxptr()
317 chainpara = CERT_CHAIN_PARA(cbSize=ctypes.sizeof(CERT_CHAIN_PARA),
318 RequestedUsage=CERT_USAGE_MATCH())
319
320 certctx = _crypt32.CertCreateCertificateContext(X509_ASN_ENCODING, cert,
321 len(cert))
322 if certctx is None:
323 _raiseoserror('CertCreateCertificateContext')
324
325 flags = 0
326
327 if not build:
328 flags |= 0x100 # CERT_CHAIN_DISABLE_AUTH_ROOT_AUTO_UPDATE
329
330 try:
331 # Building the certificate chain will update root certs as necessary.
332 if not _crypt32.CertGetCertificateChain(None, # hChainEngine
333 certctx, # pCertContext
334 None, # pTime
335 None, # hAdditionalStore
336 ctypes.byref(chainpara),
337 flags,
338 None, # pvReserved
339 ctypes.byref(pchainctx)):
340 _raiseoserror('CertGetCertificateChain')
341
342 chainctx = pchainctx.contents
343
344 return chainctx.dwErrorStatus & CERT_TRUST_IS_PARTIAL_CHAIN == 0
345 finally:
346 if pchainctx:
347 _crypt32.CertFreeCertificateChain(pchainctx)
348 _crypt32.CertFreeCertificateContext(certctx)
236 349
237 def oslink(src, dst): 350 def oslink(src, dst):
238 try: 351 try:
239 if not _kernel32.CreateHardLinkA(dst, src, None): 352 if not _kernel32.CreateHardLinkA(dst, src, None):
240 _raiseoserror(src) 353 _raiseoserror(src)