comparison mercurial/sslutil.py @ 29260:70bc9912d83d

sslutil: move CA file processing into _hostsettings() The CA file processing code has been moved from _determinecertoptions into _hostsettings(). As part of the move, the logic has been changed slightly and the "cacerts" variable has been renamed to "cafile" to match the argument used by SSLContext.load_verify_locations(). Since _determinecertoptions() no longer contains any meaningful code, it has been removed.
author Gregory Szorc <gregory.szorc@gmail.com>
date Sat, 28 May 2016 12:53:33 -0700
parents ec247e8595f9
children dfc4f08aa160
comparison
equal deleted inserted replaced
29259:ec247e8595f9 29260:70bc9912d83d
112 Returns a dict of settings relevant to that hostname. 112 Returns a dict of settings relevant to that hostname.
113 """ 113 """
114 s = { 114 s = {
115 # List of 2-tuple of (hash algorithm, hash). 115 # List of 2-tuple of (hash algorithm, hash).
116 'certfingerprints': [], 116 'certfingerprints': [],
117 # Path to file containing concatenated CA certs. Used by
118 # SSLContext.load_verify_locations().
119 'cafile': None,
117 # ssl.CERT_* constant used by SSLContext.verify_mode. 120 # ssl.CERT_* constant used by SSLContext.verify_mode.
118 'verifymode': None, 121 'verifymode': None,
119 } 122 }
120 123
121 # Fingerprints from [hostfingerprints] are always SHA-1. 124 # Fingerprints from [hostfingerprints] are always SHA-1.
130 133
131 # If --insecure is used, don't take CAs into consideration. 134 # If --insecure is used, don't take CAs into consideration.
132 elif ui.insecureconnections: 135 elif ui.insecureconnections:
133 s['verifymode'] = ssl.CERT_NONE 136 s['verifymode'] = ssl.CERT_NONE
134 137
135 # TODO assert verifymode is not None once we integrate cacert 138 # Try to hook up CA certificate validation unless something above
136 # checking in this function. 139 # makes it not necessary.
140 if s['verifymode'] is None:
141 # Find global certificates file in config.
142 cafile = ui.config('web', 'cacerts')
143
144 if cafile:
145 cafile = util.expandpath(cafile)
146 if not os.path.exists(cafile):
147 raise error.Abort(_('could not find web.cacerts: %s') % cafile)
148 else:
149 # No global CA certs. See if we can load defaults.
150 cafile = _defaultcacerts()
151 if cafile:
152 ui.debug('using %s to enable OS X system CA\n' % cafile)
153
154 s['cafile'] = cafile
155
156 # Require certificate validation if CA certs are being loaded and
157 # verification hasn't been disabled above.
158 if cafile or _canloaddefaultcerts:
159 s['verifymode'] = ssl.CERT_REQUIRED
160 else:
161 # At this point we don't have a fingerprint, aren't being
162 # explicitly insecure, and can't load CA certs. Connecting
163 # at this point is insecure. But we do it for BC reasons.
164 # TODO abort here to make secure by default.
165 s['verifymode'] = ssl.CERT_NONE
166
167 assert s['verifymode'] is not None
137 168
138 return s 169 return s
139
140 def _determinecertoptions(ui, settings):
141 """Determine certificate options for a connections.
142
143 Returns a tuple of (cert_reqs, ca_certs).
144 """
145 if settings['verifymode'] == ssl.CERT_NONE:
146 return ssl.CERT_NONE, None
147
148 cacerts = ui.config('web', 'cacerts')
149
150 # If a value is set in the config, validate against a path and load
151 # and require those certs.
152 if cacerts:
153 cacerts = util.expandpath(cacerts)
154 if not os.path.exists(cacerts):
155 raise error.Abort(_('could not find web.cacerts: %s') % cacerts)
156
157 return ssl.CERT_REQUIRED, cacerts
158
159 # No CAs in config. See if we can load defaults.
160 cacerts = _defaultcacerts()
161
162 # We found an alternate CA bundle to use. Load it.
163 if cacerts:
164 ui.debug('using %s to enable OS X system CA\n' % cacerts)
165 ui.setconfig('web', 'cacerts', cacerts, 'defaultcacerts')
166 return ssl.CERT_REQUIRED, cacerts
167
168 # FUTURE this can disappear once wrapsocket() is secure by default.
169 if _canloaddefaultcerts:
170 return ssl.CERT_REQUIRED, None
171
172 return ssl.CERT_NONE, None
173 170
174 def wrapsocket(sock, keyfile, certfile, ui, serverhostname=None): 171 def wrapsocket(sock, keyfile, certfile, ui, serverhostname=None):
175 """Add SSL/TLS to a socket. 172 """Add SSL/TLS to a socket.
176 173
177 This is a glorified wrapper for ``ssl.wrap_socket()``. It makes sane 174 This is a glorified wrapper for ``ssl.wrap_socket()``. It makes sane
186 """ 183 """
187 if not serverhostname: 184 if not serverhostname:
188 raise error.Abort('serverhostname argument is required') 185 raise error.Abort('serverhostname argument is required')
189 186
190 settings = _hostsettings(ui, serverhostname) 187 settings = _hostsettings(ui, serverhostname)
191 cert_reqs, ca_certs = _determinecertoptions(ui, settings)
192 188
193 # Despite its name, PROTOCOL_SSLv23 selects the highest protocol 189 # Despite its name, PROTOCOL_SSLv23 selects the highest protocol
194 # that both ends support, including TLS protocols. On legacy stacks, 190 # that both ends support, including TLS protocols. On legacy stacks,
195 # the highest it likely goes in TLS 1.0. On modern stacks, it can 191 # the highest it likely goes in TLS 1.0. On modern stacks, it can
196 # support TLS 1.2. 192 # support TLS 1.2.
213 209
214 # This is a no-op on old Python. 210 # This is a no-op on old Python.
215 sslcontext.options |= OP_NO_SSLv2 | OP_NO_SSLv3 211 sslcontext.options |= OP_NO_SSLv2 | OP_NO_SSLv3
216 212
217 # This still works on our fake SSLContext. 213 # This still works on our fake SSLContext.
218 sslcontext.verify_mode = cert_reqs 214 sslcontext.verify_mode = settings['verifymode']
219 215
220 if certfile is not None: 216 if certfile is not None:
221 def password(): 217 def password():
222 f = keyfile or certfile 218 f = keyfile or certfile
223 return ui.getpass(_('passphrase for %s: ') % f, '') 219 return ui.getpass(_('passphrase for %s: ') % f, '')
224 sslcontext.load_cert_chain(certfile, keyfile, password) 220 sslcontext.load_cert_chain(certfile, keyfile, password)
225 221
226 if ca_certs is not None: 222 if settings['cafile'] is not None:
227 sslcontext.load_verify_locations(cafile=ca_certs) 223 sslcontext.load_verify_locations(cafile=settings['cafile'])
228 caloaded = True 224 caloaded = True
229 else: 225 else:
230 # This is a no-op on old Python. 226 # This is a no-op on old Python.
231 sslcontext.load_default_certs() 227 sslcontext.load_default_certs()
232 caloaded = _canloaddefaultcerts 228 caloaded = _canloaddefaultcerts