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 |