##// END OF EJS Templates
configitems: register the 'hostsecurity.*:fingerprints' config
Boris Feld -
r34776:17919e90 default
parent child Browse files
Show More
@@ -1,1008 +1,1012 b''
1 1 # configitems.py - centralized declaration of configuration option
2 2 #
3 3 # Copyright 2017 Pierre-Yves David <pierre-yves.david@octobus.net>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import functools
11 11 import re
12 12
13 13 from . import (
14 14 encoding,
15 15 error,
16 16 )
17 17
18 18 def loadconfigtable(ui, extname, configtable):
19 19 """update config item known to the ui with the extension ones"""
20 20 for section, items in configtable.items():
21 21 knownitems = ui._knownconfig.setdefault(section, itemregister())
22 22 knownkeys = set(knownitems)
23 23 newkeys = set(items)
24 24 for key in sorted(knownkeys & newkeys):
25 25 msg = "extension '%s' overwrite config item '%s.%s'"
26 26 msg %= (extname, section, key)
27 27 ui.develwarn(msg, config='warn-config')
28 28
29 29 knownitems.update(items)
30 30
31 31 class configitem(object):
32 32 """represent a known config item
33 33
34 34 :section: the official config section where to find this item,
35 35 :name: the official name within the section,
36 36 :default: default value for this item,
37 37 :alias: optional list of tuples as alternatives,
38 38 :generic: this is a generic definition, match name using regular expression.
39 39 """
40 40
41 41 def __init__(self, section, name, default=None, alias=(),
42 42 generic=False, priority=0):
43 43 self.section = section
44 44 self.name = name
45 45 self.default = default
46 46 self.alias = list(alias)
47 47 self.generic = generic
48 48 self.priority = priority
49 49 self._re = None
50 50 if generic:
51 51 self._re = re.compile(self.name)
52 52
53 53 class itemregister(dict):
54 54 """A specialized dictionary that can handle wild-card selection"""
55 55
56 56 def __init__(self):
57 57 super(itemregister, self).__init__()
58 58 self._generics = set()
59 59
60 60 def update(self, other):
61 61 super(itemregister, self).update(other)
62 62 self._generics.update(other._generics)
63 63
64 64 def __setitem__(self, key, item):
65 65 super(itemregister, self).__setitem__(key, item)
66 66 if item.generic:
67 67 self._generics.add(item)
68 68
69 69 def get(self, key):
70 70 if key in self:
71 71 return self[key]
72 72
73 73 # search for a matching generic item
74 74 generics = sorted(self._generics, key=(lambda x: (x.priority, x.name)))
75 75 for item in generics:
76 76 if item._re.match(key):
77 77 return item
78 78
79 79 # fallback to dict get
80 80 return super(itemregister, self).get(key)
81 81
82 82 coreitems = {}
83 83
84 84 def _register(configtable, *args, **kwargs):
85 85 item = configitem(*args, **kwargs)
86 86 section = configtable.setdefault(item.section, itemregister())
87 87 if item.name in section:
88 88 msg = "duplicated config item registration for '%s.%s'"
89 89 raise error.ProgrammingError(msg % (item.section, item.name))
90 90 section[item.name] = item
91 91
92 92 # special value for case where the default is derived from other values
93 93 dynamicdefault = object()
94 94
95 95 # Registering actual config items
96 96
97 97 def getitemregister(configtable):
98 98 return functools.partial(_register, configtable)
99 99
100 100 coreconfigitem = getitemregister(coreitems)
101 101
102 102 coreconfigitem('alias', '.*',
103 103 default=None,
104 104 generic=True,
105 105 )
106 106 coreconfigitem('annotate', 'nodates',
107 107 default=False,
108 108 )
109 109 coreconfigitem('annotate', 'showfunc',
110 110 default=False,
111 111 )
112 112 coreconfigitem('annotate', 'unified',
113 113 default=None,
114 114 )
115 115 coreconfigitem('annotate', 'git',
116 116 default=False,
117 117 )
118 118 coreconfigitem('annotate', 'ignorews',
119 119 default=False,
120 120 )
121 121 coreconfigitem('annotate', 'ignorewsamount',
122 122 default=False,
123 123 )
124 124 coreconfigitem('annotate', 'ignoreblanklines',
125 125 default=False,
126 126 )
127 127 coreconfigitem('annotate', 'ignorewseol',
128 128 default=False,
129 129 )
130 130 coreconfigitem('annotate', 'nobinary',
131 131 default=False,
132 132 )
133 133 coreconfigitem('annotate', 'noprefix',
134 134 default=False,
135 135 )
136 136 coreconfigitem('auth', 'cookiefile',
137 137 default=None,
138 138 )
139 139 # bookmarks.pushing: internal hack for discovery
140 140 coreconfigitem('bookmarks', 'pushing',
141 141 default=list,
142 142 )
143 143 # bundle.mainreporoot: internal hack for bundlerepo
144 144 coreconfigitem('bundle', 'mainreporoot',
145 145 default='',
146 146 )
147 147 # bundle.reorder: experimental config
148 148 coreconfigitem('bundle', 'reorder',
149 149 default='auto',
150 150 )
151 151 coreconfigitem('censor', 'policy',
152 152 default='abort',
153 153 )
154 154 coreconfigitem('chgserver', 'idletimeout',
155 155 default=3600,
156 156 )
157 157 coreconfigitem('chgserver', 'skiphash',
158 158 default=False,
159 159 )
160 160 coreconfigitem('cmdserver', 'log',
161 161 default=None,
162 162 )
163 163 coreconfigitem('color', '.*',
164 164 default=None,
165 165 generic=True,
166 166 )
167 167 coreconfigitem('color', 'mode',
168 168 default='auto',
169 169 )
170 170 coreconfigitem('color', 'pagermode',
171 171 default=dynamicdefault,
172 172 )
173 173 coreconfigitem('commands', 'status.relative',
174 174 default=False,
175 175 )
176 176 coreconfigitem('commands', 'status.skipstates',
177 177 default=[],
178 178 )
179 179 coreconfigitem('commands', 'status.verbose',
180 180 default=False,
181 181 )
182 182 coreconfigitem('commands', 'update.check',
183 183 default=None,
184 184 )
185 185 coreconfigitem('commands', 'update.requiredest',
186 186 default=False,
187 187 )
188 188 coreconfigitem('committemplate', '.*',
189 189 default=None,
190 190 generic=True,
191 191 )
192 192 coreconfigitem('debug', 'dirstate.delaywrite',
193 193 default=0,
194 194 )
195 195 coreconfigitem('defaults', '.*',
196 196 default=None,
197 197 generic=True,
198 198 )
199 199 coreconfigitem('devel', 'all-warnings',
200 200 default=False,
201 201 )
202 202 coreconfigitem('devel', 'bundle2.debug',
203 203 default=False,
204 204 )
205 205 coreconfigitem('devel', 'cache-vfs',
206 206 default=None,
207 207 )
208 208 coreconfigitem('devel', 'check-locks',
209 209 default=False,
210 210 )
211 211 coreconfigitem('devel', 'check-relroot',
212 212 default=False,
213 213 )
214 214 coreconfigitem('devel', 'default-date',
215 215 default=None,
216 216 )
217 217 coreconfigitem('devel', 'deprec-warn',
218 218 default=False,
219 219 )
220 220 coreconfigitem('devel', 'disableloaddefaultcerts',
221 221 default=False,
222 222 )
223 223 coreconfigitem('devel', 'warn-empty-changegroup',
224 224 default=False,
225 225 )
226 226 coreconfigitem('devel', 'legacy.exchange',
227 227 default=list,
228 228 )
229 229 coreconfigitem('devel', 'servercafile',
230 230 default='',
231 231 )
232 232 coreconfigitem('devel', 'serverexactprotocol',
233 233 default='',
234 234 )
235 235 coreconfigitem('devel', 'serverrequirecert',
236 236 default=False,
237 237 )
238 238 coreconfigitem('devel', 'strip-obsmarkers',
239 239 default=True,
240 240 )
241 241 coreconfigitem('devel', 'warn-config',
242 242 default=None,
243 243 )
244 244 coreconfigitem('devel', 'warn-config-default',
245 245 default=None,
246 246 )
247 247 coreconfigitem('devel', 'user.obsmarker',
248 248 default=None,
249 249 )
250 250 coreconfigitem('diff', 'nodates',
251 251 default=False,
252 252 )
253 253 coreconfigitem('diff', 'showfunc',
254 254 default=False,
255 255 )
256 256 coreconfigitem('diff', 'unified',
257 257 default=None,
258 258 )
259 259 coreconfigitem('diff', 'git',
260 260 default=False,
261 261 )
262 262 coreconfigitem('diff', 'ignorews',
263 263 default=False,
264 264 )
265 265 coreconfigitem('diff', 'ignorewsamount',
266 266 default=False,
267 267 )
268 268 coreconfigitem('diff', 'ignoreblanklines',
269 269 default=False,
270 270 )
271 271 coreconfigitem('diff', 'ignorewseol',
272 272 default=False,
273 273 )
274 274 coreconfigitem('diff', 'nobinary',
275 275 default=False,
276 276 )
277 277 coreconfigitem('diff', 'noprefix',
278 278 default=False,
279 279 )
280 280 coreconfigitem('email', 'bcc',
281 281 default=None,
282 282 )
283 283 coreconfigitem('email', 'cc',
284 284 default=None,
285 285 )
286 286 coreconfigitem('email', 'charsets',
287 287 default=list,
288 288 )
289 289 coreconfigitem('email', 'from',
290 290 default=None,
291 291 )
292 292 coreconfigitem('email', 'method',
293 293 default='smtp',
294 294 )
295 295 coreconfigitem('email', 'reply-to',
296 296 default=None,
297 297 )
298 298 coreconfigitem('experimental', 'allowdivergence',
299 299 default=False,
300 300 )
301 301 coreconfigitem('experimental', 'archivemetatemplate',
302 302 default=dynamicdefault,
303 303 )
304 304 coreconfigitem('experimental', 'bundle-phases',
305 305 default=False,
306 306 )
307 307 coreconfigitem('experimental', 'bundle2-advertise',
308 308 default=True,
309 309 )
310 310 coreconfigitem('experimental', 'bundle2-output-capture',
311 311 default=False,
312 312 )
313 313 coreconfigitem('experimental', 'bundle2.pushback',
314 314 default=False,
315 315 )
316 316 coreconfigitem('experimental', 'bundle2lazylocking',
317 317 default=False,
318 318 )
319 319 coreconfigitem('experimental', 'bundlecomplevel',
320 320 default=None,
321 321 )
322 322 coreconfigitem('experimental', 'changegroup3',
323 323 default=False,
324 324 )
325 325 coreconfigitem('experimental', 'clientcompressionengines',
326 326 default=list,
327 327 )
328 328 coreconfigitem('experimental', 'copytrace',
329 329 default='on',
330 330 )
331 331 coreconfigitem('experimental', 'copytrace.sourcecommitlimit',
332 332 default=100,
333 333 )
334 334 coreconfigitem('experimental', 'crecordtest',
335 335 default=None,
336 336 )
337 337 coreconfigitem('experimental', 'editortmpinhg',
338 338 default=False,
339 339 )
340 340 coreconfigitem('experimental', 'maxdeltachainspan',
341 341 default=-1,
342 342 )
343 343 coreconfigitem('experimental', 'mmapindexthreshold',
344 344 default=None,
345 345 )
346 346 coreconfigitem('experimental', 'nonnormalparanoidcheck',
347 347 default=False,
348 348 )
349 349 coreconfigitem('experimental', 'stabilization',
350 350 default=list,
351 351 alias=[('experimental', 'evolution')],
352 352 )
353 353 coreconfigitem('experimental', 'stabilization.bundle-obsmarker',
354 354 default=False,
355 355 alias=[('experimental', 'evolution.bundle-obsmarker')],
356 356 )
357 357 coreconfigitem('experimental', 'stabilization.track-operation',
358 358 default=True,
359 359 alias=[('experimental', 'evolution.track-operation')]
360 360 )
361 361 coreconfigitem('experimental', 'exportableenviron',
362 362 default=list,
363 363 )
364 364 coreconfigitem('experimental', 'extendedheader.index',
365 365 default=None,
366 366 )
367 367 coreconfigitem('experimental', 'extendedheader.similarity',
368 368 default=False,
369 369 )
370 370 coreconfigitem('experimental', 'format.compression',
371 371 default='zlib',
372 372 )
373 373 coreconfigitem('experimental', 'graphshorten',
374 374 default=False,
375 375 )
376 376 coreconfigitem('experimental', 'graphstyle.parent',
377 377 default=dynamicdefault,
378 378 )
379 379 coreconfigitem('experimental', 'graphstyle.missing',
380 380 default=dynamicdefault,
381 381 )
382 382 coreconfigitem('experimental', 'graphstyle.grandparent',
383 383 default=dynamicdefault,
384 384 )
385 385 coreconfigitem('experimental', 'hook-track-tags',
386 386 default=False,
387 387 )
388 388 coreconfigitem('experimental', 'httppostargs',
389 389 default=False,
390 390 )
391 391 coreconfigitem('experimental', 'manifestv2',
392 392 default=False,
393 393 )
394 394 coreconfigitem('experimental', 'mergedriver',
395 395 default=None,
396 396 )
397 397 coreconfigitem('experimental', 'obsmarkers-exchange-debug',
398 398 default=False,
399 399 )
400 400 coreconfigitem('experimental', 'rebase.multidest',
401 401 default=False,
402 402 )
403 403 coreconfigitem('experimental', 'revertalternateinteractivemode',
404 404 default=True,
405 405 )
406 406 coreconfigitem('experimental', 'revlogv2',
407 407 default=None,
408 408 )
409 409 coreconfigitem('experimental', 'spacemovesdown',
410 410 default=False,
411 411 )
412 412 coreconfigitem('experimental', 'treemanifest',
413 413 default=False,
414 414 )
415 415 # Deprecated, remove after 4.4 release
416 416 coreconfigitem('experimental', 'updatecheck',
417 417 default=None,
418 418 )
419 419 coreconfigitem('extensions', '.*',
420 420 default=None,
421 421 generic=True,
422 422 )
423 423 coreconfigitem('extdata', '.*',
424 424 default=None,
425 425 generic=True,
426 426 )
427 427 coreconfigitem('format', 'aggressivemergedeltas',
428 428 default=False,
429 429 )
430 430 coreconfigitem('format', 'chunkcachesize',
431 431 default=None,
432 432 )
433 433 coreconfigitem('format', 'dotencode',
434 434 default=True,
435 435 )
436 436 coreconfigitem('format', 'generaldelta',
437 437 default=False,
438 438 )
439 439 coreconfigitem('format', 'manifestcachesize',
440 440 default=None,
441 441 )
442 442 coreconfigitem('format', 'maxchainlen',
443 443 default=None,
444 444 )
445 445 coreconfigitem('format', 'obsstore-version',
446 446 default=None,
447 447 )
448 448 coreconfigitem('format', 'usefncache',
449 449 default=True,
450 450 )
451 451 coreconfigitem('format', 'usegeneraldelta',
452 452 default=True,
453 453 )
454 454 coreconfigitem('format', 'usestore',
455 455 default=True,
456 456 )
457 457 coreconfigitem('hooks', '.*',
458 458 default=dynamicdefault,
459 459 generic=True,
460 460 )
461 461 coreconfigitem('hgweb-paths', '.*',
462 462 default=list,
463 463 generic=True,
464 464 )
465 465 coreconfigitem('hostfingerprints', '.*',
466 466 default=list,
467 467 generic=True,
468 468 )
469 469 coreconfigitem('hostsecurity', 'ciphers',
470 470 default=None,
471 471 )
472 472 coreconfigitem('hostsecurity', 'disabletls10warning',
473 473 default=False,
474 474 )
475 475 coreconfigitem('hostsecurity', 'minimumprotocol',
476 476 default=dynamicdefault,
477 477 )
478 478 coreconfigitem('hostsecurity', '.*:minimumprotocol$',
479 479 default=dynamicdefault,
480 480 generic=True,
481 481 )
482 482 coreconfigitem('hostsecurity', '.*:ciphers$',
483 483 default=dynamicdefault,
484 484 generic=True,
485 485 )
486 coreconfigitem('hostsecurity', '.*:fingerprints$',
487 default=list,
488 generic=True,
489 )
486 490 coreconfigitem('http_proxy', 'always',
487 491 default=False,
488 492 )
489 493 coreconfigitem('http_proxy', 'host',
490 494 default=None,
491 495 )
492 496 coreconfigitem('http_proxy', 'no',
493 497 default=list,
494 498 )
495 499 coreconfigitem('http_proxy', 'passwd',
496 500 default=None,
497 501 )
498 502 coreconfigitem('http_proxy', 'user',
499 503 default=None,
500 504 )
501 505 coreconfigitem('logtoprocess', 'commandexception',
502 506 default=None,
503 507 )
504 508 coreconfigitem('logtoprocess', 'commandfinish',
505 509 default=None,
506 510 )
507 511 coreconfigitem('logtoprocess', 'command',
508 512 default=None,
509 513 )
510 514 coreconfigitem('logtoprocess', 'develwarn',
511 515 default=None,
512 516 )
513 517 coreconfigitem('logtoprocess', 'uiblocked',
514 518 default=None,
515 519 )
516 520 coreconfigitem('merge', 'checkunknown',
517 521 default='abort',
518 522 )
519 523 coreconfigitem('merge', 'checkignored',
520 524 default='abort',
521 525 )
522 526 coreconfigitem('merge', 'followcopies',
523 527 default=True,
524 528 )
525 529 coreconfigitem('merge', 'preferancestor',
526 530 default=lambda: ['*'],
527 531 )
528 532 coreconfigitem('pager', 'attend-.*',
529 533 default=dynamicdefault,
530 534 generic=True,
531 535 )
532 536 coreconfigitem('pager', 'ignore',
533 537 default=list,
534 538 )
535 539 coreconfigitem('pager', 'pager',
536 540 default=dynamicdefault,
537 541 )
538 542 coreconfigitem('patch', 'eol',
539 543 default='strict',
540 544 )
541 545 coreconfigitem('patch', 'fuzz',
542 546 default=2,
543 547 )
544 548 coreconfigitem('paths', 'default',
545 549 default=None,
546 550 )
547 551 coreconfigitem('paths', 'default-push',
548 552 default=None,
549 553 )
550 554 coreconfigitem('paths', '.*',
551 555 default=None,
552 556 generic=True,
553 557 )
554 558 coreconfigitem('phases', 'checksubrepos',
555 559 default='follow',
556 560 )
557 561 coreconfigitem('phases', 'new-commit',
558 562 default='draft',
559 563 )
560 564 coreconfigitem('phases', 'publish',
561 565 default=True,
562 566 )
563 567 coreconfigitem('profiling', 'enabled',
564 568 default=False,
565 569 )
566 570 coreconfigitem('profiling', 'format',
567 571 default='text',
568 572 )
569 573 coreconfigitem('profiling', 'freq',
570 574 default=1000,
571 575 )
572 576 coreconfigitem('profiling', 'limit',
573 577 default=30,
574 578 )
575 579 coreconfigitem('profiling', 'nested',
576 580 default=0,
577 581 )
578 582 coreconfigitem('profiling', 'output',
579 583 default=None,
580 584 )
581 585 coreconfigitem('profiling', 'showmax',
582 586 default=0.999,
583 587 )
584 588 coreconfigitem('profiling', 'showmin',
585 589 default=dynamicdefault,
586 590 )
587 591 coreconfigitem('profiling', 'sort',
588 592 default='inlinetime',
589 593 )
590 594 coreconfigitem('profiling', 'statformat',
591 595 default='hotpath',
592 596 )
593 597 coreconfigitem('profiling', 'type',
594 598 default='stat',
595 599 )
596 600 coreconfigitem('progress', 'assume-tty',
597 601 default=False,
598 602 )
599 603 coreconfigitem('progress', 'changedelay',
600 604 default=1,
601 605 )
602 606 coreconfigitem('progress', 'clear-complete',
603 607 default=True,
604 608 )
605 609 coreconfigitem('progress', 'debug',
606 610 default=False,
607 611 )
608 612 coreconfigitem('progress', 'delay',
609 613 default=3,
610 614 )
611 615 coreconfigitem('progress', 'disable',
612 616 default=False,
613 617 )
614 618 coreconfigitem('progress', 'estimateinterval',
615 619 default=60.0,
616 620 )
617 621 coreconfigitem('progress', 'format',
618 622 default=lambda: ['topic', 'bar', 'number', 'estimate'],
619 623 )
620 624 coreconfigitem('progress', 'refresh',
621 625 default=0.1,
622 626 )
623 627 coreconfigitem('progress', 'width',
624 628 default=dynamicdefault,
625 629 )
626 630 coreconfigitem('push', 'pushvars.server',
627 631 default=False,
628 632 )
629 633 coreconfigitem('server', 'bundle1',
630 634 default=True,
631 635 )
632 636 coreconfigitem('server', 'bundle1gd',
633 637 default=None,
634 638 )
635 639 coreconfigitem('server', 'bundle1.pull',
636 640 default=None,
637 641 )
638 642 coreconfigitem('server', 'bundle1gd.pull',
639 643 default=None,
640 644 )
641 645 coreconfigitem('server', 'bundle1.push',
642 646 default=None,
643 647 )
644 648 coreconfigitem('server', 'bundle1gd.push',
645 649 default=None,
646 650 )
647 651 coreconfigitem('server', 'compressionengines',
648 652 default=list,
649 653 )
650 654 coreconfigitem('server', 'concurrent-push-mode',
651 655 default='strict',
652 656 )
653 657 coreconfigitem('server', 'disablefullbundle',
654 658 default=False,
655 659 )
656 660 coreconfigitem('server', 'maxhttpheaderlen',
657 661 default=1024,
658 662 )
659 663 coreconfigitem('server', 'preferuncompressed',
660 664 default=False,
661 665 )
662 666 coreconfigitem('server', 'uncompressed',
663 667 default=True,
664 668 )
665 669 coreconfigitem('server', 'uncompressedallowsecret',
666 670 default=False,
667 671 )
668 672 coreconfigitem('server', 'validate',
669 673 default=False,
670 674 )
671 675 coreconfigitem('server', 'zliblevel',
672 676 default=-1,
673 677 )
674 678 coreconfigitem('smtp', 'host',
675 679 default=None,
676 680 )
677 681 coreconfigitem('smtp', 'local_hostname',
678 682 default=None,
679 683 )
680 684 coreconfigitem('smtp', 'password',
681 685 default=None,
682 686 )
683 687 coreconfigitem('smtp', 'port',
684 688 default=dynamicdefault,
685 689 )
686 690 coreconfigitem('smtp', 'tls',
687 691 default='none',
688 692 )
689 693 coreconfigitem('smtp', 'username',
690 694 default=None,
691 695 )
692 696 coreconfigitem('sparse', 'missingwarning',
693 697 default=True,
694 698 )
695 699 coreconfigitem('templates', '.*',
696 700 default=None,
697 701 generic=True,
698 702 )
699 703 coreconfigitem('trusted', 'groups',
700 704 default=list,
701 705 )
702 706 coreconfigitem('trusted', 'users',
703 707 default=list,
704 708 )
705 709 coreconfigitem('ui', '_usedassubrepo',
706 710 default=False,
707 711 )
708 712 coreconfigitem('ui', 'allowemptycommit',
709 713 default=False,
710 714 )
711 715 coreconfigitem('ui', 'archivemeta',
712 716 default=True,
713 717 )
714 718 coreconfigitem('ui', 'askusername',
715 719 default=False,
716 720 )
717 721 coreconfigitem('ui', 'clonebundlefallback',
718 722 default=False,
719 723 )
720 724 coreconfigitem('ui', 'clonebundleprefers',
721 725 default=list,
722 726 )
723 727 coreconfigitem('ui', 'clonebundles',
724 728 default=True,
725 729 )
726 730 coreconfigitem('ui', 'color',
727 731 default='auto',
728 732 )
729 733 coreconfigitem('ui', 'commitsubrepos',
730 734 default=False,
731 735 )
732 736 coreconfigitem('ui', 'debug',
733 737 default=False,
734 738 )
735 739 coreconfigitem('ui', 'debugger',
736 740 default=None,
737 741 )
738 742 coreconfigitem('ui', 'fallbackencoding',
739 743 default=None,
740 744 )
741 745 coreconfigitem('ui', 'forcecwd',
742 746 default=None,
743 747 )
744 748 coreconfigitem('ui', 'forcemerge',
745 749 default=None,
746 750 )
747 751 coreconfigitem('ui', 'formatdebug',
748 752 default=False,
749 753 )
750 754 coreconfigitem('ui', 'formatjson',
751 755 default=False,
752 756 )
753 757 coreconfigitem('ui', 'formatted',
754 758 default=None,
755 759 )
756 760 coreconfigitem('ui', 'graphnodetemplate',
757 761 default=None,
758 762 )
759 763 coreconfigitem('ui', 'http2debuglevel',
760 764 default=None,
761 765 )
762 766 coreconfigitem('ui', 'interactive',
763 767 default=None,
764 768 )
765 769 coreconfigitem('ui', 'interface',
766 770 default=None,
767 771 )
768 772 coreconfigitem('ui', 'interface.chunkselector',
769 773 default=None,
770 774 )
771 775 coreconfigitem('ui', 'logblockedtimes',
772 776 default=False,
773 777 )
774 778 coreconfigitem('ui', 'logtemplate',
775 779 default=None,
776 780 )
777 781 coreconfigitem('ui', 'merge',
778 782 default=None,
779 783 )
780 784 coreconfigitem('ui', 'mergemarkers',
781 785 default='basic',
782 786 )
783 787 coreconfigitem('ui', 'mergemarkertemplate',
784 788 default=('{node|short} '
785 789 '{ifeq(tags, "tip", "", '
786 790 'ifeq(tags, "", "", "{tags} "))}'
787 791 '{if(bookmarks, "{bookmarks} ")}'
788 792 '{ifeq(branch, "default", "", "{branch} ")}'
789 793 '- {author|user}: {desc|firstline}')
790 794 )
791 795 coreconfigitem('ui', 'nontty',
792 796 default=False,
793 797 )
794 798 coreconfigitem('ui', 'origbackuppath',
795 799 default=None,
796 800 )
797 801 coreconfigitem('ui', 'paginate',
798 802 default=True,
799 803 )
800 804 coreconfigitem('ui', 'patch',
801 805 default=None,
802 806 )
803 807 coreconfigitem('ui', 'portablefilenames',
804 808 default='warn',
805 809 )
806 810 coreconfigitem('ui', 'promptecho',
807 811 default=False,
808 812 )
809 813 coreconfigitem('ui', 'quiet',
810 814 default=False,
811 815 )
812 816 coreconfigitem('ui', 'quietbookmarkmove',
813 817 default=False,
814 818 )
815 819 coreconfigitem('ui', 'remotecmd',
816 820 default='hg',
817 821 )
818 822 coreconfigitem('ui', 'report_untrusted',
819 823 default=True,
820 824 )
821 825 coreconfigitem('ui', 'rollback',
822 826 default=True,
823 827 )
824 828 coreconfigitem('ui', 'slash',
825 829 default=False,
826 830 )
827 831 coreconfigitem('ui', 'ssh',
828 832 default='ssh',
829 833 )
830 834 coreconfigitem('ui', 'statuscopies',
831 835 default=False,
832 836 )
833 837 coreconfigitem('ui', 'strict',
834 838 default=False,
835 839 )
836 840 coreconfigitem('ui', 'style',
837 841 default='',
838 842 )
839 843 coreconfigitem('ui', 'supportcontact',
840 844 default=None,
841 845 )
842 846 coreconfigitem('ui', 'textwidth',
843 847 default=78,
844 848 )
845 849 coreconfigitem('ui', 'timeout',
846 850 default='600',
847 851 )
848 852 coreconfigitem('ui', 'traceback',
849 853 default=False,
850 854 )
851 855 coreconfigitem('ui', 'tweakdefaults',
852 856 default=False,
853 857 )
854 858 coreconfigitem('ui', 'usehttp2',
855 859 default=False,
856 860 )
857 861 coreconfigitem('ui', 'username',
858 862 alias=[('ui', 'user')]
859 863 )
860 864 coreconfigitem('ui', 'verbose',
861 865 default=False,
862 866 )
863 867 coreconfigitem('verify', 'skipflags',
864 868 default=None,
865 869 )
866 870 coreconfigitem('web', 'allowbz2',
867 871 default=False,
868 872 )
869 873 coreconfigitem('web', 'allowgz',
870 874 default=False,
871 875 )
872 876 coreconfigitem('web', 'allowpull',
873 877 default=True,
874 878 )
875 879 coreconfigitem('web', 'allow_push',
876 880 default=list,
877 881 )
878 882 coreconfigitem('web', 'allowzip',
879 883 default=False,
880 884 )
881 885 coreconfigitem('web', 'cache',
882 886 default=True,
883 887 )
884 888 coreconfigitem('web', 'contact',
885 889 default=None,
886 890 )
887 891 coreconfigitem('web', 'deny_push',
888 892 default=list,
889 893 )
890 894 coreconfigitem('web', 'guessmime',
891 895 default=False,
892 896 )
893 897 coreconfigitem('web', 'hidden',
894 898 default=False,
895 899 )
896 900 coreconfigitem('web', 'labels',
897 901 default=list,
898 902 )
899 903 coreconfigitem('web', 'logoimg',
900 904 default='hglogo.png',
901 905 )
902 906 coreconfigitem('web', 'logourl',
903 907 default='https://mercurial-scm.org/',
904 908 )
905 909 coreconfigitem('web', 'accesslog',
906 910 default='-',
907 911 )
908 912 coreconfigitem('web', 'address',
909 913 default='',
910 914 )
911 915 coreconfigitem('web', 'allow_archive',
912 916 default=list,
913 917 )
914 918 coreconfigitem('web', 'allow_read',
915 919 default=list,
916 920 )
917 921 coreconfigitem('web', 'baseurl',
918 922 default=None,
919 923 )
920 924 coreconfigitem('web', 'cacerts',
921 925 default=None,
922 926 )
923 927 coreconfigitem('web', 'certificate',
924 928 default=None,
925 929 )
926 930 coreconfigitem('web', 'collapse',
927 931 default=False,
928 932 )
929 933 coreconfigitem('web', 'csp',
930 934 default=None,
931 935 )
932 936 coreconfigitem('web', 'deny_read',
933 937 default=list,
934 938 )
935 939 coreconfigitem('web', 'descend',
936 940 default=True,
937 941 )
938 942 coreconfigitem('web', 'description',
939 943 default="",
940 944 )
941 945 coreconfigitem('web', 'encoding',
942 946 default=lambda: encoding.encoding,
943 947 )
944 948 coreconfigitem('web', 'errorlog',
945 949 default='-',
946 950 )
947 951 coreconfigitem('web', 'ipv6',
948 952 default=False,
949 953 )
950 954 coreconfigitem('web', 'maxchanges',
951 955 default=10,
952 956 )
953 957 coreconfigitem('web', 'maxfiles',
954 958 default=10,
955 959 )
956 960 coreconfigitem('web', 'maxshortchanges',
957 961 default=60,
958 962 )
959 963 coreconfigitem('web', 'motd',
960 964 default='',
961 965 )
962 966 coreconfigitem('web', 'name',
963 967 default=dynamicdefault,
964 968 )
965 969 coreconfigitem('web', 'port',
966 970 default=8000,
967 971 )
968 972 coreconfigitem('web', 'prefix',
969 973 default='',
970 974 )
971 975 coreconfigitem('web', 'push_ssl',
972 976 default=True,
973 977 )
974 978 coreconfigitem('web', 'refreshinterval',
975 979 default=20,
976 980 )
977 981 coreconfigitem('web', 'staticurl',
978 982 default=None,
979 983 )
980 984 coreconfigitem('web', 'stripes',
981 985 default=1,
982 986 )
983 987 coreconfigitem('web', 'style',
984 988 default='paper',
985 989 )
986 990 coreconfigitem('web', 'templates',
987 991 default=None,
988 992 )
989 993 coreconfigitem('web', 'view',
990 994 default='served',
991 995 )
992 996 coreconfigitem('worker', 'backgroundclose',
993 997 default=dynamicdefault,
994 998 )
995 999 # Windows defaults to a limit of 512 open files. A buffer of 128
996 1000 # should give us enough headway.
997 1001 coreconfigitem('worker', 'backgroundclosemaxqueue',
998 1002 default=384,
999 1003 )
1000 1004 coreconfigitem('worker', 'backgroundcloseminfilecount',
1001 1005 default=2048,
1002 1006 )
1003 1007 coreconfigitem('worker', 'backgroundclosethreadcount',
1004 1008 default=4,
1005 1009 )
1006 1010 coreconfigitem('worker', 'numcpus',
1007 1011 default=None,
1008 1012 )
@@ -1,865 +1,864 b''
1 1 # sslutil.py - SSL handling for mercurial
2 2 #
3 3 # Copyright 2005, 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
4 4 # Copyright 2006, 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br>
5 5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 6 #
7 7 # This software may be used and distributed according to the terms of the
8 8 # GNU General Public License version 2 or any later version.
9 9
10 10 from __future__ import absolute_import
11 11
12 12 import hashlib
13 13 import os
14 14 import re
15 15 import ssl
16 16
17 17 from .i18n import _
18 18 from . import (
19 19 error,
20 20 pycompat,
21 21 util,
22 22 )
23 23
24 24 # Python 2.7.9+ overhauled the built-in SSL/TLS features of Python. It added
25 25 # support for TLS 1.1, TLS 1.2, SNI, system CA stores, etc. These features are
26 26 # all exposed via the "ssl" module.
27 27 #
28 28 # Depending on the version of Python being used, SSL/TLS support is either
29 29 # modern/secure or legacy/insecure. Many operations in this module have
30 30 # separate code paths depending on support in Python.
31 31
32 32 configprotocols = {
33 33 'tls1.0',
34 34 'tls1.1',
35 35 'tls1.2',
36 36 }
37 37
38 38 hassni = getattr(ssl, 'HAS_SNI', False)
39 39
40 40 # TLS 1.1 and 1.2 may not be supported if the OpenSSL Python is compiled
41 41 # against doesn't support them.
42 42 supportedprotocols = {'tls1.0'}
43 43 if util.safehasattr(ssl, 'PROTOCOL_TLSv1_1'):
44 44 supportedprotocols.add('tls1.1')
45 45 if util.safehasattr(ssl, 'PROTOCOL_TLSv1_2'):
46 46 supportedprotocols.add('tls1.2')
47 47
48 48 try:
49 49 # ssl.SSLContext was added in 2.7.9 and presence indicates modern
50 50 # SSL/TLS features are available.
51 51 SSLContext = ssl.SSLContext
52 52 modernssl = True
53 53 _canloaddefaultcerts = util.safehasattr(SSLContext, 'load_default_certs')
54 54 except AttributeError:
55 55 modernssl = False
56 56 _canloaddefaultcerts = False
57 57
58 58 # We implement SSLContext using the interface from the standard library.
59 59 class SSLContext(object):
60 60 def __init__(self, protocol):
61 61 # From the public interface of SSLContext
62 62 self.protocol = protocol
63 63 self.check_hostname = False
64 64 self.options = 0
65 65 self.verify_mode = ssl.CERT_NONE
66 66
67 67 # Used by our implementation.
68 68 self._certfile = None
69 69 self._keyfile = None
70 70 self._certpassword = None
71 71 self._cacerts = None
72 72 self._ciphers = None
73 73
74 74 def load_cert_chain(self, certfile, keyfile=None, password=None):
75 75 self._certfile = certfile
76 76 self._keyfile = keyfile
77 77 self._certpassword = password
78 78
79 79 def load_default_certs(self, purpose=None):
80 80 pass
81 81
82 82 def load_verify_locations(self, cafile=None, capath=None, cadata=None):
83 83 if capath:
84 84 raise error.Abort(_('capath not supported'))
85 85 if cadata:
86 86 raise error.Abort(_('cadata not supported'))
87 87
88 88 self._cacerts = cafile
89 89
90 90 def set_ciphers(self, ciphers):
91 91 self._ciphers = ciphers
92 92
93 93 def wrap_socket(self, socket, server_hostname=None, server_side=False):
94 94 # server_hostname is unique to SSLContext.wrap_socket and is used
95 95 # for SNI in that context. So there's nothing for us to do with it
96 96 # in this legacy code since we don't support SNI.
97 97
98 98 args = {
99 99 'keyfile': self._keyfile,
100 100 'certfile': self._certfile,
101 101 'server_side': server_side,
102 102 'cert_reqs': self.verify_mode,
103 103 'ssl_version': self.protocol,
104 104 'ca_certs': self._cacerts,
105 105 'ciphers': self._ciphers,
106 106 }
107 107
108 108 return ssl.wrap_socket(socket, **args)
109 109
110 110 def _hostsettings(ui, hostname):
111 111 """Obtain security settings for a hostname.
112 112
113 113 Returns a dict of settings relevant to that hostname.
114 114 """
115 115 s = {
116 116 # Whether we should attempt to load default/available CA certs
117 117 # if an explicit ``cafile`` is not defined.
118 118 'allowloaddefaultcerts': True,
119 119 # List of 2-tuple of (hash algorithm, hash).
120 120 'certfingerprints': [],
121 121 # Path to file containing concatenated CA certs. Used by
122 122 # SSLContext.load_verify_locations().
123 123 'cafile': None,
124 124 # Whether certificate verification should be disabled.
125 125 'disablecertverification': False,
126 126 # Whether the legacy [hostfingerprints] section has data for this host.
127 127 'legacyfingerprint': False,
128 128 # PROTOCOL_* constant to use for SSLContext.__init__.
129 129 'protocol': None,
130 130 # String representation of minimum protocol to be used for UI
131 131 # presentation.
132 132 'protocolui': None,
133 133 # ssl.CERT_* constant used by SSLContext.verify_mode.
134 134 'verifymode': None,
135 135 # Defines extra ssl.OP* bitwise options to set.
136 136 'ctxoptions': None,
137 137 # OpenSSL Cipher List to use (instead of default).
138 138 'ciphers': None,
139 139 }
140 140
141 141 # Allow minimum TLS protocol to be specified in the config.
142 142 def validateprotocol(protocol, key):
143 143 if protocol not in configprotocols:
144 144 raise error.Abort(
145 145 _('unsupported protocol from hostsecurity.%s: %s') %
146 146 (key, protocol),
147 147 hint=_('valid protocols: %s') %
148 148 ' '.join(sorted(configprotocols)))
149 149
150 150 # We default to TLS 1.1+ where we can because TLS 1.0 has known
151 151 # vulnerabilities (like BEAST and POODLE). We allow users to downgrade to
152 152 # TLS 1.0+ via config options in case a legacy server is encountered.
153 153 if 'tls1.1' in supportedprotocols:
154 154 defaultprotocol = 'tls1.1'
155 155 else:
156 156 # Let people know they are borderline secure.
157 157 # We don't document this config option because we want people to see
158 158 # the bold warnings on the web site.
159 159 # internal config: hostsecurity.disabletls10warning
160 160 if not ui.configbool('hostsecurity', 'disabletls10warning'):
161 161 ui.warn(_('warning: connecting to %s using legacy security '
162 162 'technology (TLS 1.0); see '
163 163 'https://mercurial-scm.org/wiki/SecureConnections for '
164 164 'more info\n') % hostname)
165 165 defaultprotocol = 'tls1.0'
166 166
167 167 key = 'minimumprotocol'
168 168 protocol = ui.config('hostsecurity', key, defaultprotocol)
169 169 validateprotocol(protocol, key)
170 170
171 171 key = '%s:minimumprotocol' % hostname
172 172 protocol = ui.config('hostsecurity', key, protocol)
173 173 validateprotocol(protocol, key)
174 174
175 175 # If --insecure is used, we allow the use of TLS 1.0 despite config options.
176 176 # We always print a "connection security to %s is disabled..." message when
177 177 # --insecure is used. So no need to print anything more here.
178 178 if ui.insecureconnections:
179 179 protocol = 'tls1.0'
180 180
181 181 s['protocol'], s['ctxoptions'], s['protocolui'] = protocolsettings(protocol)
182 182
183 183 ciphers = ui.config('hostsecurity', 'ciphers')
184 184 ciphers = ui.config('hostsecurity', '%s:ciphers' % hostname, ciphers)
185 185 s['ciphers'] = ciphers
186 186
187 187 # Look for fingerprints in [hostsecurity] section. Value is a list
188 188 # of <alg>:<fingerprint> strings.
189 fingerprints = ui.configlist('hostsecurity', '%s:fingerprints' % hostname,
190 [])
189 fingerprints = ui.configlist('hostsecurity', '%s:fingerprints' % hostname)
191 190 for fingerprint in fingerprints:
192 191 if not (fingerprint.startswith(('sha1:', 'sha256:', 'sha512:'))):
193 192 raise error.Abort(_('invalid fingerprint for %s: %s') % (
194 193 hostname, fingerprint),
195 194 hint=_('must begin with "sha1:", "sha256:", '
196 195 'or "sha512:"'))
197 196
198 197 alg, fingerprint = fingerprint.split(':', 1)
199 198 fingerprint = fingerprint.replace(':', '').lower()
200 199 s['certfingerprints'].append((alg, fingerprint))
201 200
202 201 # Fingerprints from [hostfingerprints] are always SHA-1.
203 202 for fingerprint in ui.configlist('hostfingerprints', hostname):
204 203 fingerprint = fingerprint.replace(':', '').lower()
205 204 s['certfingerprints'].append(('sha1', fingerprint))
206 205 s['legacyfingerprint'] = True
207 206
208 207 # If a host cert fingerprint is defined, it is the only thing that
209 208 # matters. No need to validate CA certs.
210 209 if s['certfingerprints']:
211 210 s['verifymode'] = ssl.CERT_NONE
212 211 s['allowloaddefaultcerts'] = False
213 212
214 213 # If --insecure is used, don't take CAs into consideration.
215 214 elif ui.insecureconnections:
216 215 s['disablecertverification'] = True
217 216 s['verifymode'] = ssl.CERT_NONE
218 217 s['allowloaddefaultcerts'] = False
219 218
220 219 if ui.configbool('devel', 'disableloaddefaultcerts'):
221 220 s['allowloaddefaultcerts'] = False
222 221
223 222 # If both fingerprints and a per-host ca file are specified, issue a warning
224 223 # because users should not be surprised about what security is or isn't
225 224 # being performed.
226 225 cafile = ui.config('hostsecurity', '%s:verifycertsfile' % hostname)
227 226 if s['certfingerprints'] and cafile:
228 227 ui.warn(_('(hostsecurity.%s:verifycertsfile ignored when host '
229 228 'fingerprints defined; using host fingerprints for '
230 229 'verification)\n') % hostname)
231 230
232 231 # Try to hook up CA certificate validation unless something above
233 232 # makes it not necessary.
234 233 if s['verifymode'] is None:
235 234 # Look at per-host ca file first.
236 235 if cafile:
237 236 cafile = util.expandpath(cafile)
238 237 if not os.path.exists(cafile):
239 238 raise error.Abort(_('path specified by %s does not exist: %s') %
240 239 ('hostsecurity.%s:verifycertsfile' % hostname,
241 240 cafile))
242 241 s['cafile'] = cafile
243 242 else:
244 243 # Find global certificates file in config.
245 244 cafile = ui.config('web', 'cacerts')
246 245
247 246 if cafile:
248 247 cafile = util.expandpath(cafile)
249 248 if not os.path.exists(cafile):
250 249 raise error.Abort(_('could not find web.cacerts: %s') %
251 250 cafile)
252 251 elif s['allowloaddefaultcerts']:
253 252 # CAs not defined in config. Try to find system bundles.
254 253 cafile = _defaultcacerts(ui)
255 254 if cafile:
256 255 ui.debug('using %s for CA file\n' % cafile)
257 256
258 257 s['cafile'] = cafile
259 258
260 259 # Require certificate validation if CA certs are being loaded and
261 260 # verification hasn't been disabled above.
262 261 if cafile or (_canloaddefaultcerts and s['allowloaddefaultcerts']):
263 262 s['verifymode'] = ssl.CERT_REQUIRED
264 263 else:
265 264 # At this point we don't have a fingerprint, aren't being
266 265 # explicitly insecure, and can't load CA certs. Connecting
267 266 # is insecure. We allow the connection and abort during
268 267 # validation (once we have the fingerprint to print to the
269 268 # user).
270 269 s['verifymode'] = ssl.CERT_NONE
271 270
272 271 assert s['protocol'] is not None
273 272 assert s['ctxoptions'] is not None
274 273 assert s['verifymode'] is not None
275 274
276 275 return s
277 276
278 277 def protocolsettings(protocol):
279 278 """Resolve the protocol for a config value.
280 279
281 280 Returns a 3-tuple of (protocol, options, ui value) where the first
282 281 2 items are values used by SSLContext and the last is a string value
283 282 of the ``minimumprotocol`` config option equivalent.
284 283 """
285 284 if protocol not in configprotocols:
286 285 raise ValueError('protocol value not supported: %s' % protocol)
287 286
288 287 # Despite its name, PROTOCOL_SSLv23 selects the highest protocol
289 288 # that both ends support, including TLS protocols. On legacy stacks,
290 289 # the highest it likely goes is TLS 1.0. On modern stacks, it can
291 290 # support TLS 1.2.
292 291 #
293 292 # The PROTOCOL_TLSv* constants select a specific TLS version
294 293 # only (as opposed to multiple versions). So the method for
295 294 # supporting multiple TLS versions is to use PROTOCOL_SSLv23 and
296 295 # disable protocols via SSLContext.options and OP_NO_* constants.
297 296 # However, SSLContext.options doesn't work unless we have the
298 297 # full/real SSLContext available to us.
299 298 if supportedprotocols == {'tls1.0'}:
300 299 if protocol != 'tls1.0':
301 300 raise error.Abort(_('current Python does not support protocol '
302 301 'setting %s') % protocol,
303 302 hint=_('upgrade Python or disable setting since '
304 303 'only TLS 1.0 is supported'))
305 304
306 305 return ssl.PROTOCOL_TLSv1, 0, 'tls1.0'
307 306
308 307 # WARNING: returned options don't work unless the modern ssl module
309 308 # is available. Be careful when adding options here.
310 309
311 310 # SSLv2 and SSLv3 are broken. We ban them outright.
312 311 options = ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3
313 312
314 313 if protocol == 'tls1.0':
315 314 # Defaults above are to use TLS 1.0+
316 315 pass
317 316 elif protocol == 'tls1.1':
318 317 options |= ssl.OP_NO_TLSv1
319 318 elif protocol == 'tls1.2':
320 319 options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1
321 320 else:
322 321 raise error.Abort(_('this should not happen'))
323 322
324 323 # Prevent CRIME.
325 324 # There is no guarantee this attribute is defined on the module.
326 325 options |= getattr(ssl, 'OP_NO_COMPRESSION', 0)
327 326
328 327 return ssl.PROTOCOL_SSLv23, options, protocol
329 328
330 329 def wrapsocket(sock, keyfile, certfile, ui, serverhostname=None):
331 330 """Add SSL/TLS to a socket.
332 331
333 332 This is a glorified wrapper for ``ssl.wrap_socket()``. It makes sane
334 333 choices based on what security options are available.
335 334
336 335 In addition to the arguments supported by ``ssl.wrap_socket``, we allow
337 336 the following additional arguments:
338 337
339 338 * serverhostname - The expected hostname of the remote server. If the
340 339 server (and client) support SNI, this tells the server which certificate
341 340 to use.
342 341 """
343 342 if not serverhostname:
344 343 raise error.Abort(_('serverhostname argument is required'))
345 344
346 345 for f in (keyfile, certfile):
347 346 if f and not os.path.exists(f):
348 347 raise error.Abort(_('certificate file (%s) does not exist; '
349 348 'cannot connect to %s') % (f, serverhostname),
350 349 hint=_('restore missing file or fix references '
351 350 'in Mercurial config'))
352 351
353 352 settings = _hostsettings(ui, serverhostname)
354 353
355 354 # We can't use ssl.create_default_context() because it calls
356 355 # load_default_certs() unless CA arguments are passed to it. We want to
357 356 # have explicit control over CA loading because implicitly loading
358 357 # CAs may undermine the user's intent. For example, a user may define a CA
359 358 # bundle with a specific CA cert removed. If the system/default CA bundle
360 359 # is loaded and contains that removed CA, you've just undone the user's
361 360 # choice.
362 361 sslcontext = SSLContext(settings['protocol'])
363 362
364 363 # This is a no-op unless using modern ssl.
365 364 sslcontext.options |= settings['ctxoptions']
366 365
367 366 # This still works on our fake SSLContext.
368 367 sslcontext.verify_mode = settings['verifymode']
369 368
370 369 if settings['ciphers']:
371 370 try:
372 371 sslcontext.set_ciphers(settings['ciphers'])
373 372 except ssl.SSLError as e:
374 373 raise error.Abort(_('could not set ciphers: %s') % e.args[0],
375 374 hint=_('change cipher string (%s) in config') %
376 375 settings['ciphers'])
377 376
378 377 if certfile is not None:
379 378 def password():
380 379 f = keyfile or certfile
381 380 return ui.getpass(_('passphrase for %s: ') % f, '')
382 381 sslcontext.load_cert_chain(certfile, keyfile, password)
383 382
384 383 if settings['cafile'] is not None:
385 384 try:
386 385 sslcontext.load_verify_locations(cafile=settings['cafile'])
387 386 except ssl.SSLError as e:
388 387 if len(e.args) == 1: # pypy has different SSLError args
389 388 msg = e.args[0]
390 389 else:
391 390 msg = e.args[1]
392 391 raise error.Abort(_('error loading CA file %s: %s') % (
393 392 settings['cafile'], msg),
394 393 hint=_('file is empty or malformed?'))
395 394 caloaded = True
396 395 elif settings['allowloaddefaultcerts']:
397 396 # This is a no-op on old Python.
398 397 sslcontext.load_default_certs()
399 398 caloaded = True
400 399 else:
401 400 caloaded = False
402 401
403 402 try:
404 403 sslsocket = sslcontext.wrap_socket(sock, server_hostname=serverhostname)
405 404 except ssl.SSLError as e:
406 405 # If we're doing certificate verification and no CA certs are loaded,
407 406 # that is almost certainly the reason why verification failed. Provide
408 407 # a hint to the user.
409 408 # Only modern ssl module exposes SSLContext.get_ca_certs() so we can
410 409 # only show this warning if modern ssl is available.
411 410 # The exception handler is here to handle bugs around cert attributes:
412 411 # https://bugs.python.org/issue20916#msg213479. (See issues5313.)
413 412 # When the main 20916 bug occurs, 'sslcontext.get_ca_certs()' is a
414 413 # non-empty list, but the following conditional is otherwise True.
415 414 try:
416 415 if (caloaded and settings['verifymode'] == ssl.CERT_REQUIRED and
417 416 modernssl and not sslcontext.get_ca_certs()):
418 417 ui.warn(_('(an attempt was made to load CA certificates but '
419 418 'none were loaded; see '
420 419 'https://mercurial-scm.org/wiki/SecureConnections '
421 420 'for how to configure Mercurial to avoid this '
422 421 'error)\n'))
423 422 except ssl.SSLError:
424 423 pass
425 424 # Try to print more helpful error messages for known failures.
426 425 if util.safehasattr(e, 'reason'):
427 426 # This error occurs when the client and server don't share a
428 427 # common/supported SSL/TLS protocol. We've disabled SSLv2 and SSLv3
429 428 # outright. Hopefully the reason for this error is that we require
430 429 # TLS 1.1+ and the server only supports TLS 1.0. Whatever the
431 430 # reason, try to emit an actionable warning.
432 431 if e.reason == 'UNSUPPORTED_PROTOCOL':
433 432 # We attempted TLS 1.0+.
434 433 if settings['protocolui'] == 'tls1.0':
435 434 # We support more than just TLS 1.0+. If this happens,
436 435 # the likely scenario is either the client or the server
437 436 # is really old. (e.g. server doesn't support TLS 1.0+ or
438 437 # client doesn't support modern TLS versions introduced
439 438 # several years from when this comment was written).
440 439 if supportedprotocols != {'tls1.0'}:
441 440 ui.warn(_(
442 441 '(could not communicate with %s using security '
443 442 'protocols %s; if you are using a modern Mercurial '
444 443 'version, consider contacting the operator of this '
445 444 'server; see '
446 445 'https://mercurial-scm.org/wiki/SecureConnections '
447 446 'for more info)\n') % (
448 447 serverhostname,
449 448 ', '.join(sorted(supportedprotocols))))
450 449 else:
451 450 ui.warn(_(
452 451 '(could not communicate with %s using TLS 1.0; the '
453 452 'likely cause of this is the server no longer '
454 453 'supports TLS 1.0 because it has known security '
455 454 'vulnerabilities; see '
456 455 'https://mercurial-scm.org/wiki/SecureConnections '
457 456 'for more info)\n') % serverhostname)
458 457 else:
459 458 # We attempted TLS 1.1+. We can only get here if the client
460 459 # supports the configured protocol. So the likely reason is
461 460 # the client wants better security than the server can
462 461 # offer.
463 462 ui.warn(_(
464 463 '(could not negotiate a common security protocol (%s+) '
465 464 'with %s; the likely cause is Mercurial is configured '
466 465 'to be more secure than the server can support)\n') % (
467 466 settings['protocolui'], serverhostname))
468 467 ui.warn(_('(consider contacting the operator of this '
469 468 'server and ask them to support modern TLS '
470 469 'protocol versions; or, set '
471 470 'hostsecurity.%s:minimumprotocol=tls1.0 to allow '
472 471 'use of legacy, less secure protocols when '
473 472 'communicating with this server)\n') %
474 473 serverhostname)
475 474 ui.warn(_(
476 475 '(see https://mercurial-scm.org/wiki/SecureConnections '
477 476 'for more info)\n'))
478 477
479 478 elif (e.reason == 'CERTIFICATE_VERIFY_FAILED' and
480 479 pycompat.iswindows):
481 480
482 481 ui.warn(_('(the full certificate chain may not be available '
483 482 'locally; see "hg help debugssl")\n'))
484 483 raise
485 484
486 485 # check if wrap_socket failed silently because socket had been
487 486 # closed
488 487 # - see http://bugs.python.org/issue13721
489 488 if not sslsocket.cipher():
490 489 raise error.Abort(_('ssl connection failed'))
491 490
492 491 sslsocket._hgstate = {
493 492 'caloaded': caloaded,
494 493 'hostname': serverhostname,
495 494 'settings': settings,
496 495 'ui': ui,
497 496 }
498 497
499 498 return sslsocket
500 499
501 500 def wrapserversocket(sock, ui, certfile=None, keyfile=None, cafile=None,
502 501 requireclientcert=False):
503 502 """Wrap a socket for use by servers.
504 503
505 504 ``certfile`` and ``keyfile`` specify the files containing the certificate's
506 505 public and private keys, respectively. Both keys can be defined in the same
507 506 file via ``certfile`` (the private key must come first in the file).
508 507
509 508 ``cafile`` defines the path to certificate authorities.
510 509
511 510 ``requireclientcert`` specifies whether to require client certificates.
512 511
513 512 Typically ``cafile`` is only defined if ``requireclientcert`` is true.
514 513 """
515 514 # This function is not used much by core Mercurial, so the error messaging
516 515 # doesn't have to be as detailed as for wrapsocket().
517 516 for f in (certfile, keyfile, cafile):
518 517 if f and not os.path.exists(f):
519 518 raise error.Abort(_('referenced certificate file (%s) does not '
520 519 'exist') % f)
521 520
522 521 protocol, options, _protocolui = protocolsettings('tls1.0')
523 522
524 523 # This config option is intended for use in tests only. It is a giant
525 524 # footgun to kill security. Don't define it.
526 525 exactprotocol = ui.config('devel', 'serverexactprotocol')
527 526 if exactprotocol == 'tls1.0':
528 527 protocol = ssl.PROTOCOL_TLSv1
529 528 elif exactprotocol == 'tls1.1':
530 529 if 'tls1.1' not in supportedprotocols:
531 530 raise error.Abort(_('TLS 1.1 not supported by this Python'))
532 531 protocol = ssl.PROTOCOL_TLSv1_1
533 532 elif exactprotocol == 'tls1.2':
534 533 if 'tls1.2' not in supportedprotocols:
535 534 raise error.Abort(_('TLS 1.2 not supported by this Python'))
536 535 protocol = ssl.PROTOCOL_TLSv1_2
537 536 elif exactprotocol:
538 537 raise error.Abort(_('invalid value for serverexactprotocol: %s') %
539 538 exactprotocol)
540 539
541 540 if modernssl:
542 541 # We /could/ use create_default_context() here since it doesn't load
543 542 # CAs when configured for client auth. However, it is hard-coded to
544 543 # use ssl.PROTOCOL_SSLv23 which may not be appropriate here.
545 544 sslcontext = SSLContext(protocol)
546 545 sslcontext.options |= options
547 546
548 547 # Improve forward secrecy.
549 548 sslcontext.options |= getattr(ssl, 'OP_SINGLE_DH_USE', 0)
550 549 sslcontext.options |= getattr(ssl, 'OP_SINGLE_ECDH_USE', 0)
551 550
552 551 # Use the list of more secure ciphers if found in the ssl module.
553 552 if util.safehasattr(ssl, '_RESTRICTED_SERVER_CIPHERS'):
554 553 sslcontext.options |= getattr(ssl, 'OP_CIPHER_SERVER_PREFERENCE', 0)
555 554 sslcontext.set_ciphers(ssl._RESTRICTED_SERVER_CIPHERS)
556 555 else:
557 556 sslcontext = SSLContext(ssl.PROTOCOL_TLSv1)
558 557
559 558 if requireclientcert:
560 559 sslcontext.verify_mode = ssl.CERT_REQUIRED
561 560 else:
562 561 sslcontext.verify_mode = ssl.CERT_NONE
563 562
564 563 if certfile or keyfile:
565 564 sslcontext.load_cert_chain(certfile=certfile, keyfile=keyfile)
566 565
567 566 if cafile:
568 567 sslcontext.load_verify_locations(cafile=cafile)
569 568
570 569 return sslcontext.wrap_socket(sock, server_side=True)
571 570
572 571 class wildcarderror(Exception):
573 572 """Represents an error parsing wildcards in DNS name."""
574 573
575 574 def _dnsnamematch(dn, hostname, maxwildcards=1):
576 575 """Match DNS names according RFC 6125 section 6.4.3.
577 576
578 577 This code is effectively copied from CPython's ssl._dnsname_match.
579 578
580 579 Returns a bool indicating whether the expected hostname matches
581 580 the value in ``dn``.
582 581 """
583 582 pats = []
584 583 if not dn:
585 584 return False
586 585
587 586 pieces = dn.split(r'.')
588 587 leftmost = pieces[0]
589 588 remainder = pieces[1:]
590 589 wildcards = leftmost.count('*')
591 590 if wildcards > maxwildcards:
592 591 raise wildcarderror(
593 592 _('too many wildcards in certificate DNS name: %s') % dn)
594 593
595 594 # speed up common case w/o wildcards
596 595 if not wildcards:
597 596 return dn.lower() == hostname.lower()
598 597
599 598 # RFC 6125, section 6.4.3, subitem 1.
600 599 # The client SHOULD NOT attempt to match a presented identifier in which
601 600 # the wildcard character comprises a label other than the left-most label.
602 601 if leftmost == '*':
603 602 # When '*' is a fragment by itself, it matches a non-empty dotless
604 603 # fragment.
605 604 pats.append('[^.]+')
606 605 elif leftmost.startswith('xn--') or hostname.startswith('xn--'):
607 606 # RFC 6125, section 6.4.3, subitem 3.
608 607 # The client SHOULD NOT attempt to match a presented identifier
609 608 # where the wildcard character is embedded within an A-label or
610 609 # U-label of an internationalized domain name.
611 610 pats.append(re.escape(leftmost))
612 611 else:
613 612 # Otherwise, '*' matches any dotless string, e.g. www*
614 613 pats.append(re.escape(leftmost).replace(r'\*', '[^.]*'))
615 614
616 615 # add the remaining fragments, ignore any wildcards
617 616 for frag in remainder:
618 617 pats.append(re.escape(frag))
619 618
620 619 pat = re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE)
621 620 return pat.match(hostname) is not None
622 621
623 622 def _verifycert(cert, hostname):
624 623 '''Verify that cert (in socket.getpeercert() format) matches hostname.
625 624 CRLs is not handled.
626 625
627 626 Returns error message if any problems are found and None on success.
628 627 '''
629 628 if not cert:
630 629 return _('no certificate received')
631 630
632 631 dnsnames = []
633 632 san = cert.get('subjectAltName', [])
634 633 for key, value in san:
635 634 if key == 'DNS':
636 635 try:
637 636 if _dnsnamematch(value, hostname):
638 637 return
639 638 except wildcarderror as e:
640 639 return e.args[0]
641 640
642 641 dnsnames.append(value)
643 642
644 643 if not dnsnames:
645 644 # The subject is only checked when there is no DNS in subjectAltName.
646 645 for sub in cert.get('subject', []):
647 646 for key, value in sub:
648 647 # According to RFC 2818 the most specific Common Name must
649 648 # be used.
650 649 if key == 'commonName':
651 650 # 'subject' entries are unicode.
652 651 try:
653 652 value = value.encode('ascii')
654 653 except UnicodeEncodeError:
655 654 return _('IDN in certificate not supported')
656 655
657 656 try:
658 657 if _dnsnamematch(value, hostname):
659 658 return
660 659 except wildcarderror as e:
661 660 return e.args[0]
662 661
663 662 dnsnames.append(value)
664 663
665 664 if len(dnsnames) > 1:
666 665 return _('certificate is for %s') % ', '.join(dnsnames)
667 666 elif len(dnsnames) == 1:
668 667 return _('certificate is for %s') % dnsnames[0]
669 668 else:
670 669 return _('no commonName or subjectAltName found in certificate')
671 670
672 671 def _plainapplepython():
673 672 """return true if this seems to be a pure Apple Python that
674 673 * is unfrozen and presumably has the whole mercurial module in the file
675 674 system
676 675 * presumably is an Apple Python that uses Apple OpenSSL which has patches
677 676 for using system certificate store CAs in addition to the provided
678 677 cacerts file
679 678 """
680 679 if (not pycompat.isdarwin or util.mainfrozen() or
681 680 not pycompat.sysexecutable):
682 681 return False
683 682 exe = os.path.realpath(pycompat.sysexecutable).lower()
684 683 return (exe.startswith('/usr/bin/python') or
685 684 exe.startswith('/system/library/frameworks/python.framework/'))
686 685
687 686 _systemcacertpaths = [
688 687 # RHEL, CentOS, and Fedora
689 688 '/etc/pki/tls/certs/ca-bundle.trust.crt',
690 689 # Debian, Ubuntu, Gentoo
691 690 '/etc/ssl/certs/ca-certificates.crt',
692 691 ]
693 692
694 693 def _defaultcacerts(ui):
695 694 """return path to default CA certificates or None.
696 695
697 696 It is assumed this function is called when the returned certificates
698 697 file will actually be used to validate connections. Therefore this
699 698 function may print warnings or debug messages assuming this usage.
700 699
701 700 We don't print a message when the Python is able to load default
702 701 CA certs because this scenario is detected at socket connect time.
703 702 """
704 703 # The "certifi" Python package provides certificates. If it is installed
705 704 # and usable, assume the user intends it to be used and use it.
706 705 try:
707 706 import certifi
708 707 certs = certifi.where()
709 708 if os.path.exists(certs):
710 709 ui.debug('using ca certificates from certifi\n')
711 710 return certs
712 711 except (ImportError, AttributeError):
713 712 pass
714 713
715 714 # On Windows, only the modern ssl module is capable of loading the system
716 715 # CA certificates. If we're not capable of doing that, emit a warning
717 716 # because we'll get a certificate verification error later and the lack
718 717 # of loaded CA certificates will be the reason why.
719 718 # Assertion: this code is only called if certificates are being verified.
720 719 if pycompat.iswindows:
721 720 if not _canloaddefaultcerts:
722 721 ui.warn(_('(unable to load Windows CA certificates; see '
723 722 'https://mercurial-scm.org/wiki/SecureConnections for '
724 723 'how to configure Mercurial to avoid this message)\n'))
725 724
726 725 return None
727 726
728 727 # Apple's OpenSSL has patches that allow a specially constructed certificate
729 728 # to load the system CA store. If we're running on Apple Python, use this
730 729 # trick.
731 730 if _plainapplepython():
732 731 dummycert = os.path.join(
733 732 os.path.dirname(pycompat.fsencode(__file__)), 'dummycert.pem')
734 733 if os.path.exists(dummycert):
735 734 return dummycert
736 735
737 736 # The Apple OpenSSL trick isn't available to us. If Python isn't able to
738 737 # load system certs, we're out of luck.
739 738 if pycompat.isdarwin:
740 739 # FUTURE Consider looking for Homebrew or MacPorts installed certs
741 740 # files. Also consider exporting the keychain certs to a file during
742 741 # Mercurial install.
743 742 if not _canloaddefaultcerts:
744 743 ui.warn(_('(unable to load CA certificates; see '
745 744 'https://mercurial-scm.org/wiki/SecureConnections for '
746 745 'how to configure Mercurial to avoid this message)\n'))
747 746 return None
748 747
749 748 # / is writable on Windows. Out of an abundance of caution make sure
750 749 # we're not on Windows because paths from _systemcacerts could be installed
751 750 # by non-admin users.
752 751 assert not pycompat.iswindows
753 752
754 753 # Try to find CA certificates in well-known locations. We print a warning
755 754 # when using a found file because we don't want too much silent magic
756 755 # for security settings. The expectation is that proper Mercurial
757 756 # installs will have the CA certs path defined at install time and the
758 757 # installer/packager will make an appropriate decision on the user's
759 758 # behalf. We only get here and perform this setting as a feature of
760 759 # last resort.
761 760 if not _canloaddefaultcerts:
762 761 for path in _systemcacertpaths:
763 762 if os.path.isfile(path):
764 763 ui.warn(_('(using CA certificates from %s; if you see this '
765 764 'message, your Mercurial install is not properly '
766 765 'configured; see '
767 766 'https://mercurial-scm.org/wiki/SecureConnections '
768 767 'for how to configure Mercurial to avoid this '
769 768 'message)\n') % path)
770 769 return path
771 770
772 771 ui.warn(_('(unable to load CA certificates; see '
773 772 'https://mercurial-scm.org/wiki/SecureConnections for '
774 773 'how to configure Mercurial to avoid this message)\n'))
775 774
776 775 return None
777 776
778 777 def validatesocket(sock):
779 778 """Validate a socket meets security requirements.
780 779
781 780 The passed socket must have been created with ``wrapsocket()``.
782 781 """
783 782 host = sock._hgstate['hostname']
784 783 ui = sock._hgstate['ui']
785 784 settings = sock._hgstate['settings']
786 785
787 786 try:
788 787 peercert = sock.getpeercert(True)
789 788 peercert2 = sock.getpeercert()
790 789 except AttributeError:
791 790 raise error.Abort(_('%s ssl connection error') % host)
792 791
793 792 if not peercert:
794 793 raise error.Abort(_('%s certificate error: '
795 794 'no certificate received') % host)
796 795
797 796 if settings['disablecertverification']:
798 797 # We don't print the certificate fingerprint because it shouldn't
799 798 # be necessary: if the user requested certificate verification be
800 799 # disabled, they presumably already saw a message about the inability
801 800 # to verify the certificate and this message would have printed the
802 801 # fingerprint. So printing the fingerprint here adds little to no
803 802 # value.
804 803 ui.warn(_('warning: connection security to %s is disabled per current '
805 804 'settings; communication is susceptible to eavesdropping '
806 805 'and tampering\n') % host)
807 806 return
808 807
809 808 # If a certificate fingerprint is pinned, use it and only it to
810 809 # validate the remote cert.
811 810 peerfingerprints = {
812 811 'sha1': hashlib.sha1(peercert).hexdigest(),
813 812 'sha256': hashlib.sha256(peercert).hexdigest(),
814 813 'sha512': hashlib.sha512(peercert).hexdigest(),
815 814 }
816 815
817 816 def fmtfingerprint(s):
818 817 return ':'.join([s[x:x + 2] for x in range(0, len(s), 2)])
819 818
820 819 nicefingerprint = 'sha256:%s' % fmtfingerprint(peerfingerprints['sha256'])
821 820
822 821 if settings['certfingerprints']:
823 822 for hash, fingerprint in settings['certfingerprints']:
824 823 if peerfingerprints[hash].lower() == fingerprint:
825 824 ui.debug('%s certificate matched fingerprint %s:%s\n' %
826 825 (host, hash, fmtfingerprint(fingerprint)))
827 826 if settings['legacyfingerprint']:
828 827 ui.warn(_('(SHA-1 fingerprint for %s found in legacy '
829 828 '[hostfingerprints] section; '
830 829 'if you trust this fingerprint, remove the old '
831 830 'SHA-1 fingerprint from [hostfingerprints] and '
832 831 'add the following entry to the new '
833 832 '[hostsecurity] section: %s:fingerprints=%s)\n') %
834 833 (host, host, nicefingerprint))
835 834 return
836 835
837 836 # Pinned fingerprint didn't match. This is a fatal error.
838 837 if settings['legacyfingerprint']:
839 838 section = 'hostfingerprint'
840 839 nice = fmtfingerprint(peerfingerprints['sha1'])
841 840 else:
842 841 section = 'hostsecurity'
843 842 nice = '%s:%s' % (hash, fmtfingerprint(peerfingerprints[hash]))
844 843 raise error.Abort(_('certificate for %s has unexpected '
845 844 'fingerprint %s') % (host, nice),
846 845 hint=_('check %s configuration') % section)
847 846
848 847 # Security is enabled but no CAs are loaded. We can't establish trust
849 848 # for the cert so abort.
850 849 if not sock._hgstate['caloaded']:
851 850 raise error.Abort(
852 851 _('unable to verify security of %s (no loaded CA certificates); '
853 852 'refusing to connect') % host,
854 853 hint=_('see https://mercurial-scm.org/wiki/SecureConnections for '
855 854 'how to configure Mercurial to avoid this error or set '
856 855 'hostsecurity.%s:fingerprints=%s to trust this server') %
857 856 (host, nicefingerprint))
858 857
859 858 msg = _verifycert(peercert2, host)
860 859 if msg:
861 860 raise error.Abort(_('%s certificate error: %s') % (host, msg),
862 861 hint=_('set hostsecurity.%s:certfingerprints=%s '
863 862 'config setting or use --insecure to connect '
864 863 'insecurely') %
865 864 (host, nicefingerprint))
General Comments 0
You need to be logged in to leave comments. Login now