Skip to content

Commit

Permalink
Fix certificate verification errors when checking for updates on some…
Browse files Browse the repository at this point in the history
… systems.

Windows fetches trusted root certificates on demand, but Python doesn't trigger this. Therefore, when verification fails, try to trigger a root cert update ourselves and then retry the update check.
Re #4803.
  • Loading branch information
jcsteh committed Jan 16, 2015
1 parent 041f739 commit ce5dd50
Showing 1 changed file with 56 additions and 1 deletion.
57 changes: 56 additions & 1 deletion source/updateCheck.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
import urllib
import tempfile
import hashlib
import ctypes.wintypes
import ssl
import wx
import languageHandler
import gui
Expand Down Expand Up @@ -67,7 +69,16 @@ def checkForUpdate(auto=False):
"language": languageHandler.getLanguage(),
"installed": config.isInstalledCopy(),
}
res = urllib.urlopen("%s?%s" % (CHECK_URL, urllib.urlencode(params)))
url = "%s?%s" % (CHECK_URL, urllib.urlencode(params))
try:
res = urllib.urlopen(url)
except IOError as e:
if isinstance(e.strerror, ssl.SSLError) and e.strerror.reason == "CERTIFICATE_VERIFY_FAILED":
# #4803: Windows fetches trusted root certificates on demand.
# Python doesn't trigger this fetch (PythonIssue:20916), so try it ourselves
_updateWindowsRootCertificates()
# and then retry the update check.
res = urllib.urlopen(url)
if res.code != 200:
raise RuntimeError("Checking for update failed with code %d" % res.code)
info = {}
Expand Down Expand Up @@ -471,3 +482,47 @@ def terminate():
if autoChecker:
autoChecker.terminate()
autoChecker = None

# These structs are only complete enough to achieve what we need.
class CERT_USAGE_MATCH(ctypes.Structure):
_fields_ = (
("dwType", ctypes.wintypes.DWORD),
# CERT_ENHKEY_USAGE struct
("cUsageIdentifier", ctypes.wintypes.DWORD),
("rgpszUsageIdentifier", ctypes.c_void_p), # LPSTR *
)

class CERT_CHAIN_PARA(ctypes.Structure):
_fields_ = (
("cbSize", ctypes.wintypes.DWORD),
("RequestedUsage", CERT_USAGE_MATCH),
("RequestedIssuancePolicy", CERT_USAGE_MATCH),
("dwUrlRetrievalTimeout", ctypes.wintypes.DWORD),
("fCheckRevocationFreshnessTime", ctypes.wintypes.BOOL),
("dwRevocationFreshnessTime", ctypes.wintypes.DWORD),
("pftCacheResync", ctypes.c_void_p), # LPFILETIME
("pStrongSignPara", ctypes.c_void_p), # PCCERT_STRONG_SIGN_PARA
("dwStrongSignFlags", ctypes.wintypes.DWORD),
)

def _updateWindowsRootCertificates():
crypt = ctypes.windll.crypt32
# Get the server certificate.
sslCont = ssl._create_unverified_context()
u = urllib.urlopen("https://www.nvaccess.org/nvdaUpdateCheck", context=sslCont)
cert = u.fp._sock.getpeercert(True)
u.close()
# Convert to a form usable by Windows.
certCont = crypt.CertCreateCertificateContext(
0x00000001, # X509_ASN_ENCODING
cert,
len(cert))
# Ask Windows to build a certificate chain, thus triggering a root certificate update.
chainCont = ctypes.c_void_p()
crypt.CertGetCertificateChain(None, certCont, None, None,
ctypes.byref(CERT_CHAIN_PARA(cbSize=ctypes.sizeof(CERT_CHAIN_PARA),
RequestedUsage=CERT_USAGE_MATCH())),
0, None,
ctypes.byref(chainCont))
crypt.CertFreeCertificateChain(chainCont)
crypt.CertFreeCertificateContext(certCont)

0 comments on commit ce5dd50

Please sign in to comment.