feat(ST2.UtilPackages): bump up all packages
- Refresh PackageCache with latest versions of everything
This commit is contained in:
@@ -0,0 +1,378 @@
|
||||
import hashlib
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
import sys
|
||||
|
||||
from .cmd import Cli
|
||||
from .console_write import console_write
|
||||
from .open_compat import open_compat, read_compat
|
||||
|
||||
|
||||
# Have somewhere to store the CA bundle, even when not running in Sublime Text
|
||||
try:
|
||||
import sublime
|
||||
ca_bundle_dir = None
|
||||
except (ImportError):
|
||||
ca_bundle_dir = os.path.join(os.path.expanduser('~'), '.package_control')
|
||||
if not os.path.exists(ca_bundle_dir):
|
||||
os.mkdir(ca_bundle_dir)
|
||||
|
||||
|
||||
def find_root_ca_cert(settings, domain):
|
||||
runner = OpensslCli(settings.get('openssl_binary'), settings.get('debug'))
|
||||
binary = runner.retrieve_binary()
|
||||
|
||||
args = [binary, 's_client', '-showcerts', '-connect', domain + ':443']
|
||||
result = runner.execute(args, os.path.dirname(binary))
|
||||
|
||||
certs = []
|
||||
temp = []
|
||||
|
||||
in_block = False
|
||||
for line in result.splitlines():
|
||||
if line.find('BEGIN CERTIFICATE') != -1:
|
||||
in_block = True
|
||||
if in_block:
|
||||
temp.append(line)
|
||||
if line.find('END CERTIFICATE') != -1:
|
||||
in_block = False
|
||||
certs.append(u"\n".join(temp))
|
||||
temp = []
|
||||
|
||||
# Remove the cert for the domain itself, just leaving the
|
||||
# chain cert and the CA cert
|
||||
certs.pop(0)
|
||||
|
||||
# Look for the "parent" root CA cert
|
||||
subject = openssl_get_cert_subject(settings, certs[-1])
|
||||
issuer = openssl_get_cert_issuer(settings, certs[-1])
|
||||
|
||||
cert = get_ca_cert_by_subject(settings, issuer)
|
||||
cert_hash = hashlib.md5(cert.encode('utf-8')).hexdigest()
|
||||
|
||||
return [cert, cert_hash]
|
||||
|
||||
|
||||
|
||||
def get_system_ca_bundle_path(settings):
|
||||
"""
|
||||
Get the filesystem path to the system CA bundle. On Linux it looks in a
|
||||
number of predefined places, however on OS X it has to be programatically
|
||||
exported from the SystemRootCertificates.keychain. Windows does not ship
|
||||
with a CA bundle, but also we use WinINet on Windows, so we don't need to
|
||||
worry about CA certs.
|
||||
|
||||
:param settings:
|
||||
A dict to look in for `debug` and `openssl_binary` keys
|
||||
|
||||
:return:
|
||||
The full filesystem path to the .ca-bundle file, or False on error
|
||||
"""
|
||||
|
||||
# If the sublime module is available, we bind this value at run time
|
||||
# since the sublime.packages_path() is not available at import time
|
||||
global ca_bundle_dir
|
||||
|
||||
platform = sys.platform
|
||||
debug = settings.get('debug')
|
||||
|
||||
ca_path = False
|
||||
|
||||
if platform == 'win32':
|
||||
console_write(u"Unable to get system CA cert path since Windows does not ship with them", True)
|
||||
return False
|
||||
|
||||
# OS X
|
||||
if platform == 'darwin':
|
||||
if not ca_bundle_dir:
|
||||
ca_bundle_dir = os.path.join(sublime.packages_path(), 'User')
|
||||
ca_path = os.path.join(ca_bundle_dir, 'Package Control.system-ca-bundle')
|
||||
|
||||
exists = os.path.exists(ca_path)
|
||||
# The bundle is old if it is a week or more out of date
|
||||
is_old = exists and os.stat(ca_path).st_mtime < time.time() - 604800
|
||||
|
||||
if not exists or is_old:
|
||||
if debug:
|
||||
console_write(u"Generating new CA bundle from system keychain", True)
|
||||
_osx_create_ca_bundle(settings, ca_path)
|
||||
if debug:
|
||||
console_write(u"Finished generating new CA bundle at %s" % ca_path, True)
|
||||
elif debug:
|
||||
console_write(u"Found previously exported CA bundle at %s" % ca_path, True)
|
||||
|
||||
# Linux
|
||||
else:
|
||||
# Common CA cert paths
|
||||
paths = [
|
||||
'/usr/lib/ssl/certs/ca-certificates.crt',
|
||||
'/etc/ssl/certs/ca-certificates.crt',
|
||||
'/etc/pki/tls/certs/ca-bundle.crt',
|
||||
'/etc/ssl/ca-bundle.pem'
|
||||
]
|
||||
for path in paths:
|
||||
if os.path.exists(path):
|
||||
ca_path = path
|
||||
break
|
||||
|
||||
if debug and ca_path:
|
||||
console_write(u"Found system CA bundle at %s" % ca_path, True)
|
||||
|
||||
return ca_path
|
||||
|
||||
|
||||
def get_ca_cert_by_subject(settings, subject):
|
||||
bundle_path = get_system_ca_bundle_path(settings)
|
||||
|
||||
with open_compat(bundle_path, 'r') as f:
|
||||
contents = read_compat(f)
|
||||
|
||||
temp = []
|
||||
|
||||
in_block = False
|
||||
for line in contents.splitlines():
|
||||
if line.find('BEGIN CERTIFICATE') != -1:
|
||||
in_block = True
|
||||
|
||||
if in_block:
|
||||
temp.append(line)
|
||||
|
||||
if line.find('END CERTIFICATE') != -1:
|
||||
in_block = False
|
||||
cert = u"\n".join(temp)
|
||||
temp = []
|
||||
|
||||
if openssl_get_cert_subject(settings, cert) == subject:
|
||||
return cert
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def openssl_get_cert_issuer(settings, cert):
|
||||
"""
|
||||
Uses the openssl command line client to extract the issuer of an x509
|
||||
certificate.
|
||||
|
||||
:param settings:
|
||||
A dict to look in for `debug` and `openssl_binary` keys
|
||||
|
||||
:param cert:
|
||||
A string containing the PEM-encoded x509 certificate to extract the
|
||||
issuer from
|
||||
|
||||
:return:
|
||||
The cert issuer
|
||||
"""
|
||||
|
||||
runner = OpensslCli(settings.get('openssl_binary'), settings.get('debug'))
|
||||
binary = runner.retrieve_binary()
|
||||
args = [binary, 'x509', '-noout', '-issuer']
|
||||
output = runner.execute(args, os.path.dirname(binary), cert)
|
||||
return re.sub('^issuer=\s*', '', output)
|
||||
|
||||
|
||||
def openssl_get_cert_name(settings, cert):
|
||||
"""
|
||||
Uses the openssl command line client to extract the name of an x509
|
||||
certificate. If the commonName is set, that is used, otherwise the first
|
||||
organizationalUnitName is used. This mirrors what OS X uses for storing
|
||||
trust preferences.
|
||||
|
||||
:param settings:
|
||||
A dict to look in for `debug` and `openssl_binary` keys
|
||||
|
||||
:param cert:
|
||||
A string containing the PEM-encoded x509 certificate to extract the
|
||||
name from
|
||||
|
||||
:return:
|
||||
The cert subject name, which is the commonName (if available), or the
|
||||
first organizationalUnitName
|
||||
"""
|
||||
|
||||
runner = OpensslCli(settings.get('openssl_binary'), settings.get('debug'))
|
||||
|
||||
binary = runner.retrieve_binary()
|
||||
|
||||
args = [binary, 'x509', '-noout', '-subject', '-nameopt',
|
||||
'sep_multiline,lname,utf8']
|
||||
result = runner.execute(args, os.path.dirname(binary), cert)
|
||||
|
||||
# First look for the common name
|
||||
cn = None
|
||||
# If there is no common name for the cert, the trust prefs use the first
|
||||
# orginizational unit name
|
||||
first_ou = None
|
||||
|
||||
for line in result.splitlines():
|
||||
match = re.match('^\s+commonName=(.*)$', line)
|
||||
if match:
|
||||
cn = match.group(1)
|
||||
break
|
||||
match = re.match('^\s+organizationalUnitName=(.*)$', line)
|
||||
if first_ou is None and match:
|
||||
first_ou = match.group(1)
|
||||
continue
|
||||
|
||||
# This is the name of the cert that would be used in the trust prefs
|
||||
return cn or first_ou
|
||||
|
||||
|
||||
def openssl_get_cert_subject(settings, cert):
|
||||
"""
|
||||
Uses the openssl command line client to extract the subject of an x509
|
||||
certificate.
|
||||
|
||||
:param settings:
|
||||
A dict to look in for `debug` and `openssl_binary` keys
|
||||
|
||||
:param cert:
|
||||
A string containing the PEM-encoded x509 certificate to extract the
|
||||
subject from
|
||||
|
||||
:return:
|
||||
The cert subject
|
||||
"""
|
||||
|
||||
runner = OpensslCli(settings.get('openssl_binary'), settings.get('debug'))
|
||||
binary = runner.retrieve_binary()
|
||||
args = [binary, 'x509', '-noout', '-subject']
|
||||
output = runner.execute(args, os.path.dirname(binary), cert)
|
||||
return re.sub('^subject=\s*', '', output)
|
||||
|
||||
|
||||
def _osx_create_ca_bundle(settings, destination):
|
||||
"""
|
||||
Uses the OS X `security` command line tool to export the system's list of
|
||||
CA certs from /System/Library/Keychains/SystemRootCertificates.keychain.
|
||||
Checks the cert names against the trust preferences, ensuring that
|
||||
distrusted certs are not exported.
|
||||
|
||||
:param settings:
|
||||
A dict to look in for `debug` and `openssl_binary` keys
|
||||
|
||||
:param destination:
|
||||
The full filesystem path to the destination .ca-bundle file
|
||||
"""
|
||||
|
||||
distrusted_certs = _osx_get_distrusted_certs(settings)
|
||||
|
||||
# Export the root certs
|
||||
args = ['/usr/bin/security', 'export', '-k',
|
||||
'/System/Library/Keychains/SystemRootCertificates.keychain', '-t',
|
||||
'certs', '-p']
|
||||
result = Cli(None, settings.get('debug')).execute(args, '/usr/bin')
|
||||
|
||||
certs = []
|
||||
temp = []
|
||||
|
||||
in_block = False
|
||||
for line in result.splitlines():
|
||||
if line.find('BEGIN CERTIFICATE') != -1:
|
||||
in_block = True
|
||||
|
||||
if in_block:
|
||||
temp.append(line)
|
||||
|
||||
if line.find('END CERTIFICATE') != -1:
|
||||
in_block = False
|
||||
cert = u"\n".join(temp)
|
||||
temp = []
|
||||
|
||||
if distrusted_certs:
|
||||
# If it is a distrusted cert, we move on to the next
|
||||
cert_name = openssl_get_cert_name(settings, cert)
|
||||
if cert_name in distrusted_certs:
|
||||
if settings.get('debug'):
|
||||
console_write(u'Skipping root certficate %s because it is distrusted' % cert_name, True)
|
||||
continue
|
||||
|
||||
certs.append(cert)
|
||||
|
||||
with open_compat(destination, 'w') as f:
|
||||
f.write(u"\n".join(certs))
|
||||
|
||||
|
||||
def _osx_get_distrusted_certs(settings):
|
||||
"""
|
||||
Uses the OS X `security` binary to get a list of admin trust settings,
|
||||
which is what is set when a user changes the trust setting on a root
|
||||
certificate. By looking at the SSL policy, we can properly exclude
|
||||
distrusted certs from out export.
|
||||
|
||||
Tested on OS X 10.6 and 10.8
|
||||
|
||||
:param settings:
|
||||
A dict to look in for `debug` key
|
||||
|
||||
:return:
|
||||
A list of CA cert names, where the name is the commonName (if
|
||||
available), or the first organizationalUnitName
|
||||
"""
|
||||
|
||||
args = ['/usr/bin/security', 'dump-trust-settings', '-d']
|
||||
result = Cli(None, settings.get('debug')).execute(args, '/usr/bin')
|
||||
|
||||
distrusted_certs = []
|
||||
cert_name = None
|
||||
ssl_policy = False
|
||||
for line in result.splitlines():
|
||||
if line == '':
|
||||
continue
|
||||
|
||||
# Reset for each cert
|
||||
match = re.match('Cert\s+\d+:\s+(.*)$', line)
|
||||
if match:
|
||||
cert_name = match.group(1)
|
||||
continue
|
||||
|
||||
# Reset for each trust setting
|
||||
if re.match('^\s+Trust\s+Setting\s+\d+:', line):
|
||||
ssl_policy = False
|
||||
continue
|
||||
|
||||
# We are only interested in SSL policies
|
||||
if re.match('^\s+Policy\s+OID\s+:\s+SSL', line):
|
||||
ssl_policy = True
|
||||
continue
|
||||
|
||||
distrusted = re.match('^\s+Result\s+Type\s+:\s+kSecTrustSettingsResultDeny', line)
|
||||
if ssl_policy and distrusted and cert_name not in distrusted_certs:
|
||||
if settings.get('debug'):
|
||||
console_write(u'Found SSL distrust setting for root certificate %s' % cert_name, True)
|
||||
distrusted_certs.append(cert_name)
|
||||
|
||||
return distrusted_certs
|
||||
|
||||
|
||||
class OpensslCli(Cli):
|
||||
|
||||
cli_name = 'openssl'
|
||||
|
||||
def retrieve_binary(self):
|
||||
"""
|
||||
Returns the path to the openssl executable
|
||||
|
||||
:return: The string path to the executable or False on error
|
||||
"""
|
||||
|
||||
name = 'openssl'
|
||||
if os.name == 'nt':
|
||||
name += '.exe'
|
||||
|
||||
binary = self.find_binary(name)
|
||||
if binary and os.path.isdir(binary):
|
||||
full_path = os.path.join(binary, name)
|
||||
if os.path.exists(full_path):
|
||||
binary = full_path
|
||||
|
||||
if not binary:
|
||||
show_error((u'Unable to find %s. Please set the openssl_binary ' +
|
||||
u'setting by accessing the Preferences > Package Settings > ' +
|
||||
u'Package Control > Settings \u2013 User menu entry. The ' +
|
||||
u'Settings \u2013 Default entry can be used for reference, ' +
|
||||
u'but changes to that will be overwritten upon next upgrade.') % name)
|
||||
return False
|
||||
|
||||
return binary
|
Reference in New Issue
Block a user