Mercurial > hg
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 |