Mercurial > public > mercurial-scm > hg
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) |