blob: 09d765014b8c6748ff0ab686cd250d4a59a684c2 [file] [log] [blame]
Paul Kehrer890cb7f2015-08-10 21:05:34 -05001# This file is dual licensed under the terms of the Apache License, Version
2# 2.0, and the BSD License. See the LICENSE file in the root of this repository
3# for complete details.
4
5from __future__ import absolute_import, division, print_function
6
7import hashlib
Paul Kehrerfbeaf2a2015-08-10 23:52:10 -05008import ipaddress
Paul Kehrer9f8069a2015-08-10 21:10:34 -05009from enum import Enum
Paul Kehrer890cb7f2015-08-10 21:05:34 -050010
11from pyasn1.codec.der import decoder
12from pyasn1.type import namedtype, univ
13
14import six
15
16from cryptography import utils
17from cryptography.hazmat.primitives import serialization
18from cryptography.x509.base import ExtensionType
Paul Kehrerfbeaf2a2015-08-10 23:52:10 -050019from cryptography.x509.general_name import GeneralName, IPAddress
Paul Kehrer9f8069a2015-08-10 21:10:34 -050020from cryptography.x509.name import Name
Paul Kehrer890cb7f2015-08-10 21:05:34 -050021from cryptography.x509.oid import (
Paul Kehrer012262c2015-08-10 23:42:57 -050022 AuthorityInformationAccessOID, ExtensionOID, ObjectIdentifier
Paul Kehrer890cb7f2015-08-10 21:05:34 -050023)
24
25
26class _SubjectPublicKeyInfo(univ.Sequence):
27 componentType = namedtype.NamedTypes(
28 namedtype.NamedType('algorithm', univ.Sequence()),
29 namedtype.NamedType('subjectPublicKey', univ.BitString())
30 )
31
32
33def _key_identifier_from_public_key(public_key):
34 # This is a very slow way to do this.
35 serialized = public_key.public_bytes(
36 serialization.Encoding.DER,
37 serialization.PublicFormat.SubjectPublicKeyInfo
38 )
39 spki, remaining = decoder.decode(
40 serialized, asn1Spec=_SubjectPublicKeyInfo()
41 )
42 assert not remaining
43 # the univ.BitString object is a tuple of bits. We need bytes and
44 # pyasn1 really doesn't want to give them to us. To get it we'll
45 # build an integer and convert that to bytes.
46 bits = 0
47 for bit in spki.getComponentByName("subjectPublicKey"):
48 bits = bits << 1 | bit
49
50 data = utils.int_to_bytes(bits)
51 return hashlib.sha1(data).digest()
52
53
Paul Kehrerfbeaf2a2015-08-10 23:52:10 -050054class DuplicateExtension(Exception):
55 def __init__(self, msg, oid):
56 super(DuplicateExtension, self).__init__(msg)
57 self.oid = oid
58
59
60class UnsupportedExtension(Exception):
61 def __init__(self, msg, oid):
62 super(UnsupportedExtension, self).__init__(msg)
63 self.oid = oid
64
65
66class ExtensionNotFound(Exception):
67 def __init__(self, msg, oid):
68 super(ExtensionNotFound, self).__init__(msg)
69 self.oid = oid
70
71
72class Extensions(object):
73 def __init__(self, extensions):
74 self._extensions = extensions
75
76 def get_extension_for_oid(self, oid):
77 for ext in self:
78 if ext.oid == oid:
79 return ext
80
81 raise ExtensionNotFound("No {0} extension was found".format(oid), oid)
82
Phoebe Queen64cf4cd2015-08-12 02:28:43 +010083 def get_extension_for_class(self, extclass):
84 for ext in self:
Phoebe Queen754be602015-08-12 03:11:35 +010085 if isinstance(ext.value, extclass):
Phoebe Queen64cf4cd2015-08-12 02:28:43 +010086 return ext
87
Phoebe Queen2cc111a2015-08-12 04:14:22 +010088 raise ExtensionNotFound(
89 "No {0} extension was found".format(extclass), extclass
90 )
Phoebe Queen64cf4cd2015-08-12 02:28:43 +010091
Paul Kehrerfbeaf2a2015-08-10 23:52:10 -050092 def __iter__(self):
93 return iter(self._extensions)
94
95 def __len__(self):
96 return len(self._extensions)
97
98
Paul Kehrer890cb7f2015-08-10 21:05:34 -050099@utils.register_interface(ExtensionType)
100class AuthorityKeyIdentifier(object):
101 oid = ExtensionOID.AUTHORITY_KEY_IDENTIFIER
102
103 def __init__(self, key_identifier, authority_cert_issuer,
104 authority_cert_serial_number):
105 if authority_cert_issuer or authority_cert_serial_number:
106 if not authority_cert_issuer or not authority_cert_serial_number:
107 raise ValueError(
108 "authority_cert_issuer and authority_cert_serial_number "
109 "must both be present or both None"
110 )
111
112 if not all(
113 isinstance(x, GeneralName) for x in authority_cert_issuer
114 ):
115 raise TypeError(
116 "authority_cert_issuer must be a list of GeneralName "
117 "objects"
118 )
119
120 if not isinstance(authority_cert_serial_number, six.integer_types):
121 raise TypeError(
122 "authority_cert_serial_number must be an integer"
123 )
124
125 self._key_identifier = key_identifier
126 self._authority_cert_issuer = authority_cert_issuer
127 self._authority_cert_serial_number = authority_cert_serial_number
128
129 @classmethod
130 def from_issuer_public_key(cls, public_key):
131 digest = _key_identifier_from_public_key(public_key)
132 return cls(
133 key_identifier=digest,
134 authority_cert_issuer=None,
135 authority_cert_serial_number=None
136 )
137
138 def __repr__(self):
139 return (
140 "<AuthorityKeyIdentifier(key_identifier={0.key_identifier!r}, "
141 "authority_cert_issuer={0.authority_cert_issuer}, "
142 "authority_cert_serial_number={0.authority_cert_serial_number}"
143 ")>".format(self)
144 )
145
146 def __eq__(self, other):
147 if not isinstance(other, AuthorityKeyIdentifier):
148 return NotImplemented
149
150 return (
151 self.key_identifier == other.key_identifier and
152 self.authority_cert_issuer == other.authority_cert_issuer and
153 self.authority_cert_serial_number ==
154 other.authority_cert_serial_number
155 )
156
157 def __ne__(self, other):
158 return not self == other
159
160 key_identifier = utils.read_only_property("_key_identifier")
161 authority_cert_issuer = utils.read_only_property("_authority_cert_issuer")
162 authority_cert_serial_number = utils.read_only_property(
163 "_authority_cert_serial_number"
164 )
165
166
167@utils.register_interface(ExtensionType)
168class SubjectKeyIdentifier(object):
169 oid = ExtensionOID.SUBJECT_KEY_IDENTIFIER
170
171 def __init__(self, digest):
172 self._digest = digest
173
174 @classmethod
175 def from_public_key(cls, public_key):
176 return cls(_key_identifier_from_public_key(public_key))
177
178 digest = utils.read_only_property("_digest")
179
180 def __repr__(self):
181 return "<SubjectKeyIdentifier(digest={0!r})>".format(self.digest)
182
183 def __eq__(self, other):
184 if not isinstance(other, SubjectKeyIdentifier):
185 return NotImplemented
186
187 return (
188 self.digest == other.digest
189 )
190
191 def __ne__(self, other):
192 return not self == other
Paul Kehrer9f8069a2015-08-10 21:10:34 -0500193
194
195@utils.register_interface(ExtensionType)
196class AuthorityInformationAccess(object):
197 oid = ExtensionOID.AUTHORITY_INFORMATION_ACCESS
198
199 def __init__(self, descriptions):
200 if not all(isinstance(x, AccessDescription) for x in descriptions):
201 raise TypeError(
202 "Every item in the descriptions list must be an "
203 "AccessDescription"
204 )
205
206 self._descriptions = descriptions
207
208 def __iter__(self):
209 return iter(self._descriptions)
210
211 def __len__(self):
212 return len(self._descriptions)
213
214 def __repr__(self):
215 return "<AuthorityInformationAccess({0})>".format(self._descriptions)
216
217 def __eq__(self, other):
218 if not isinstance(other, AuthorityInformationAccess):
219 return NotImplemented
220
221 return self._descriptions == other._descriptions
222
223 def __ne__(self, other):
224 return not self == other
225
226
227class AccessDescription(object):
228 def __init__(self, access_method, access_location):
229 if not (access_method == AuthorityInformationAccessOID.OCSP or
230 access_method == AuthorityInformationAccessOID.CA_ISSUERS):
231 raise ValueError(
232 "access_method must be OID_OCSP or OID_CA_ISSUERS"
233 )
234
235 if not isinstance(access_location, GeneralName):
236 raise TypeError("access_location must be a GeneralName")
237
238 self._access_method = access_method
239 self._access_location = access_location
240
241 def __repr__(self):
242 return (
243 "<AccessDescription(access_method={0.access_method}, access_locati"
244 "on={0.access_location})>".format(self)
245 )
246
247 def __eq__(self, other):
248 if not isinstance(other, AccessDescription):
249 return NotImplemented
250
251 return (
252 self.access_method == other.access_method and
253 self.access_location == other.access_location
254 )
255
256 def __ne__(self, other):
257 return not self == other
258
259 access_method = utils.read_only_property("_access_method")
260 access_location = utils.read_only_property("_access_location")
261
262
263@utils.register_interface(ExtensionType)
264class BasicConstraints(object):
265 oid = ExtensionOID.BASIC_CONSTRAINTS
266
267 def __init__(self, ca, path_length):
268 if not isinstance(ca, bool):
269 raise TypeError("ca must be a boolean value")
270
271 if path_length is not None and not ca:
272 raise ValueError("path_length must be None when ca is False")
273
274 if (
275 path_length is not None and
276 (not isinstance(path_length, six.integer_types) or path_length < 0)
277 ):
278 raise TypeError(
279 "path_length must be a non-negative integer or None"
280 )
281
282 self._ca = ca
283 self._path_length = path_length
284
285 ca = utils.read_only_property("_ca")
286 path_length = utils.read_only_property("_path_length")
287
288 def __repr__(self):
289 return ("<BasicConstraints(ca={0.ca}, "
290 "path_length={0.path_length})>").format(self)
291
292 def __eq__(self, other):
293 if not isinstance(other, BasicConstraints):
294 return NotImplemented
295
296 return self.ca == other.ca and self.path_length == other.path_length
297
298 def __ne__(self, other):
299 return not self == other
300
301
302@utils.register_interface(ExtensionType)
303class CRLDistributionPoints(object):
304 oid = ExtensionOID.CRL_DISTRIBUTION_POINTS
305
306 def __init__(self, distribution_points):
307 if not all(
308 isinstance(x, DistributionPoint) for x in distribution_points
309 ):
310 raise TypeError(
311 "distribution_points must be a list of DistributionPoint "
312 "objects"
313 )
314
315 self._distribution_points = distribution_points
316
317 def __iter__(self):
318 return iter(self._distribution_points)
319
320 def __len__(self):
321 return len(self._distribution_points)
322
323 def __repr__(self):
324 return "<CRLDistributionPoints({0})>".format(self._distribution_points)
325
326 def __eq__(self, other):
327 if not isinstance(other, CRLDistributionPoints):
328 return NotImplemented
329
330 return self._distribution_points == other._distribution_points
331
332 def __ne__(self, other):
333 return not self == other
334
335
336class DistributionPoint(object):
337 def __init__(self, full_name, relative_name, reasons, crl_issuer):
338 if full_name and relative_name:
339 raise ValueError(
340 "You cannot provide both full_name and relative_name, at "
341 "least one must be None."
342 )
343
344 if full_name and not all(
345 isinstance(x, GeneralName) for x in full_name
346 ):
347 raise TypeError(
348 "full_name must be a list of GeneralName objects"
349 )
350
351 if relative_name and not isinstance(relative_name, Name):
352 raise TypeError("relative_name must be a Name")
353
354 if crl_issuer and not all(
355 isinstance(x, GeneralName) for x in crl_issuer
356 ):
357 raise TypeError(
358 "crl_issuer must be None or a list of general names"
359 )
360
361 if reasons and (not isinstance(reasons, frozenset) or not all(
362 isinstance(x, ReasonFlags) for x in reasons
363 )):
364 raise TypeError("reasons must be None or frozenset of ReasonFlags")
365
366 if reasons and (
367 ReasonFlags.unspecified in reasons or
368 ReasonFlags.remove_from_crl in reasons
369 ):
370 raise ValueError(
371 "unspecified and remove_from_crl are not valid reasons in a "
372 "DistributionPoint"
373 )
374
375 if reasons and not crl_issuer and not (full_name or relative_name):
376 raise ValueError(
377 "You must supply crl_issuer, full_name, or relative_name when "
378 "reasons is not None"
379 )
380
381 self._full_name = full_name
382 self._relative_name = relative_name
383 self._reasons = reasons
384 self._crl_issuer = crl_issuer
385
386 def __repr__(self):
387 return (
388 "<DistributionPoint(full_name={0.full_name}, relative_name={0.rela"
389 "tive_name}, reasons={0.reasons}, crl_issuer={0.crl_is"
390 "suer})>".format(self)
391 )
392
393 def __eq__(self, other):
394 if not isinstance(other, DistributionPoint):
395 return NotImplemented
396
397 return (
398 self.full_name == other.full_name and
399 self.relative_name == other.relative_name and
400 self.reasons == other.reasons and
401 self.crl_issuer == other.crl_issuer
402 )
403
404 def __ne__(self, other):
405 return not self == other
406
407 full_name = utils.read_only_property("_full_name")
408 relative_name = utils.read_only_property("_relative_name")
409 reasons = utils.read_only_property("_reasons")
410 crl_issuer = utils.read_only_property("_crl_issuer")
411
412
413class ReasonFlags(Enum):
414 unspecified = "unspecified"
415 key_compromise = "keyCompromise"
416 ca_compromise = "cACompromise"
417 affiliation_changed = "affiliationChanged"
418 superseded = "superseded"
419 cessation_of_operation = "cessationOfOperation"
420 certificate_hold = "certificateHold"
421 privilege_withdrawn = "privilegeWithdrawn"
422 aa_compromise = "aACompromise"
423 remove_from_crl = "removeFromCRL"
Paul Kehrer012262c2015-08-10 23:42:57 -0500424
425
426@utils.register_interface(ExtensionType)
427class CertificatePolicies(object):
428 oid = ExtensionOID.CERTIFICATE_POLICIES
429
430 def __init__(self, policies):
431 if not all(isinstance(x, PolicyInformation) for x in policies):
432 raise TypeError(
433 "Every item in the policies list must be a "
434 "PolicyInformation"
435 )
436
437 self._policies = policies
438
439 def __iter__(self):
440 return iter(self._policies)
441
442 def __len__(self):
443 return len(self._policies)
444
445 def __repr__(self):
446 return "<CertificatePolicies({0})>".format(self._policies)
447
448 def __eq__(self, other):
449 if not isinstance(other, CertificatePolicies):
450 return NotImplemented
451
452 return self._policies == other._policies
453
454 def __ne__(self, other):
455 return not self == other
456
457
458class PolicyInformation(object):
459 def __init__(self, policy_identifier, policy_qualifiers):
460 if not isinstance(policy_identifier, ObjectIdentifier):
461 raise TypeError("policy_identifier must be an ObjectIdentifier")
462
463 self._policy_identifier = policy_identifier
464 if policy_qualifiers and not all(
465 isinstance(
466 x, (six.text_type, UserNotice)
467 ) for x in policy_qualifiers
468 ):
469 raise TypeError(
470 "policy_qualifiers must be a list of strings and/or UserNotice"
471 " objects or None"
472 )
473
474 self._policy_qualifiers = policy_qualifiers
475
476 def __repr__(self):
477 return (
478 "<PolicyInformation(policy_identifier={0.policy_identifier}, polic"
479 "y_qualifiers={0.policy_qualifiers})>".format(self)
480 )
481
482 def __eq__(self, other):
483 if not isinstance(other, PolicyInformation):
484 return NotImplemented
485
486 return (
487 self.policy_identifier == other.policy_identifier and
488 self.policy_qualifiers == other.policy_qualifiers
489 )
490
491 def __ne__(self, other):
492 return not self == other
493
494 policy_identifier = utils.read_only_property("_policy_identifier")
495 policy_qualifiers = utils.read_only_property("_policy_qualifiers")
496
497
498class UserNotice(object):
499 def __init__(self, notice_reference, explicit_text):
500 if notice_reference and not isinstance(
501 notice_reference, NoticeReference
502 ):
503 raise TypeError(
504 "notice_reference must be None or a NoticeReference"
505 )
506
507 self._notice_reference = notice_reference
508 self._explicit_text = explicit_text
509
510 def __repr__(self):
511 return (
512 "<UserNotice(notice_reference={0.notice_reference}, explicit_text="
513 "{0.explicit_text!r})>".format(self)
514 )
515
516 def __eq__(self, other):
517 if not isinstance(other, UserNotice):
518 return NotImplemented
519
520 return (
521 self.notice_reference == other.notice_reference and
522 self.explicit_text == other.explicit_text
523 )
524
525 def __ne__(self, other):
526 return not self == other
527
528 notice_reference = utils.read_only_property("_notice_reference")
529 explicit_text = utils.read_only_property("_explicit_text")
530
531
532class NoticeReference(object):
533 def __init__(self, organization, notice_numbers):
534 self._organization = organization
535 if not isinstance(notice_numbers, list) or not all(
536 isinstance(x, int) for x in notice_numbers
537 ):
538 raise TypeError(
539 "notice_numbers must be a list of integers"
540 )
541
542 self._notice_numbers = notice_numbers
543
544 def __repr__(self):
545 return (
546 "<NoticeReference(organization={0.organization!r}, notice_numbers="
547 "{0.notice_numbers})>".format(self)
548 )
549
550 def __eq__(self, other):
551 if not isinstance(other, NoticeReference):
552 return NotImplemented
553
554 return (
555 self.organization == other.organization and
556 self.notice_numbers == other.notice_numbers
557 )
558
559 def __ne__(self, other):
560 return not self == other
561
562 organization = utils.read_only_property("_organization")
563 notice_numbers = utils.read_only_property("_notice_numbers")
564
565
566@utils.register_interface(ExtensionType)
567class ExtendedKeyUsage(object):
568 oid = ExtensionOID.EXTENDED_KEY_USAGE
569
570 def __init__(self, usages):
571 if not all(isinstance(x, ObjectIdentifier) for x in usages):
572 raise TypeError(
573 "Every item in the usages list must be an ObjectIdentifier"
574 )
575
576 self._usages = usages
577
578 def __iter__(self):
579 return iter(self._usages)
580
581 def __len__(self):
582 return len(self._usages)
583
584 def __repr__(self):
585 return "<ExtendedKeyUsage({0})>".format(self._usages)
586
587 def __eq__(self, other):
588 if not isinstance(other, ExtendedKeyUsage):
589 return NotImplemented
590
591 return self._usages == other._usages
592
593 def __ne__(self, other):
594 return not self == other
595
596
597@utils.register_interface(ExtensionType)
598class OCSPNoCheck(object):
599 oid = ExtensionOID.OCSP_NO_CHECK
600
601
602@utils.register_interface(ExtensionType)
603class InhibitAnyPolicy(object):
604 oid = ExtensionOID.INHIBIT_ANY_POLICY
605
606 def __init__(self, skip_certs):
607 if not isinstance(skip_certs, six.integer_types):
608 raise TypeError("skip_certs must be an integer")
609
610 if skip_certs < 0:
611 raise ValueError("skip_certs must be a non-negative integer")
612
613 self._skip_certs = skip_certs
614
615 def __repr__(self):
616 return "<InhibitAnyPolicy(skip_certs={0.skip_certs})>".format(self)
617
618 def __eq__(self, other):
619 if not isinstance(other, InhibitAnyPolicy):
620 return NotImplemented
621
622 return self.skip_certs == other.skip_certs
623
624 def __ne__(self, other):
625 return not self == other
626
627 skip_certs = utils.read_only_property("_skip_certs")
Paul Kehrerfbeaf2a2015-08-10 23:52:10 -0500628
629
630@utils.register_interface(ExtensionType)
631class KeyUsage(object):
632 oid = ExtensionOID.KEY_USAGE
633
634 def __init__(self, digital_signature, content_commitment, key_encipherment,
635 data_encipherment, key_agreement, key_cert_sign, crl_sign,
636 encipher_only, decipher_only):
637 if not key_agreement and (encipher_only or decipher_only):
638 raise ValueError(
639 "encipher_only and decipher_only can only be true when "
640 "key_agreement is true"
641 )
642
643 self._digital_signature = digital_signature
644 self._content_commitment = content_commitment
645 self._key_encipherment = key_encipherment
646 self._data_encipherment = data_encipherment
647 self._key_agreement = key_agreement
648 self._key_cert_sign = key_cert_sign
649 self._crl_sign = crl_sign
650 self._encipher_only = encipher_only
651 self._decipher_only = decipher_only
652
653 digital_signature = utils.read_only_property("_digital_signature")
654 content_commitment = utils.read_only_property("_content_commitment")
655 key_encipherment = utils.read_only_property("_key_encipherment")
656 data_encipherment = utils.read_only_property("_data_encipherment")
657 key_agreement = utils.read_only_property("_key_agreement")
658 key_cert_sign = utils.read_only_property("_key_cert_sign")
659 crl_sign = utils.read_only_property("_crl_sign")
660
661 @property
662 def encipher_only(self):
663 if not self.key_agreement:
664 raise ValueError(
665 "encipher_only is undefined unless key_agreement is true"
666 )
667 else:
668 return self._encipher_only
669
670 @property
671 def decipher_only(self):
672 if not self.key_agreement:
673 raise ValueError(
674 "decipher_only is undefined unless key_agreement is true"
675 )
676 else:
677 return self._decipher_only
678
679 def __repr__(self):
680 try:
681 encipher_only = self.encipher_only
682 decipher_only = self.decipher_only
683 except ValueError:
684 encipher_only = None
685 decipher_only = None
686
687 return ("<KeyUsage(digital_signature={0.digital_signature}, "
688 "content_commitment={0.content_commitment}, "
689 "key_encipherment={0.key_encipherment}, "
690 "data_encipherment={0.data_encipherment}, "
691 "key_agreement={0.key_agreement}, "
692 "key_cert_sign={0.key_cert_sign}, crl_sign={0.crl_sign}, "
693 "encipher_only={1}, decipher_only={2})>").format(
694 self, encipher_only, decipher_only)
695
696 def __eq__(self, other):
697 if not isinstance(other, KeyUsage):
698 return NotImplemented
699
700 return (
701 self.digital_signature == other.digital_signature and
702 self.content_commitment == other.content_commitment and
703 self.key_encipherment == other.key_encipherment and
704 self.data_encipherment == other.data_encipherment and
705 self.key_agreement == other.key_agreement and
706 self.key_cert_sign == other.key_cert_sign and
707 self.crl_sign == other.crl_sign and
708 self._encipher_only == other._encipher_only and
709 self._decipher_only == other._decipher_only
710 )
711
712 def __ne__(self, other):
713 return not self == other
714
715
716@utils.register_interface(ExtensionType)
717class NameConstraints(object):
718 oid = ExtensionOID.NAME_CONSTRAINTS
719
720 def __init__(self, permitted_subtrees, excluded_subtrees):
721 if permitted_subtrees is not None:
722 if not all(
723 isinstance(x, GeneralName) for x in permitted_subtrees
724 ):
725 raise TypeError(
726 "permitted_subtrees must be a list of GeneralName objects "
727 "or None"
728 )
729
730 self._validate_ip_name(permitted_subtrees)
731
732 if excluded_subtrees is not None:
733 if not all(
734 isinstance(x, GeneralName) for x in excluded_subtrees
735 ):
736 raise TypeError(
737 "excluded_subtrees must be a list of GeneralName objects "
738 "or None"
739 )
740
741 self._validate_ip_name(excluded_subtrees)
742
743 if permitted_subtrees is None and excluded_subtrees is None:
744 raise ValueError(
745 "At least one of permitted_subtrees and excluded_subtrees "
746 "must not be None"
747 )
748
749 self._permitted_subtrees = permitted_subtrees
750 self._excluded_subtrees = excluded_subtrees
751
752 def __eq__(self, other):
753 if not isinstance(other, NameConstraints):
754 return NotImplemented
755
756 return (
757 self.excluded_subtrees == other.excluded_subtrees and
758 self.permitted_subtrees == other.permitted_subtrees
759 )
760
761 def __ne__(self, other):
762 return not self == other
763
764 def _validate_ip_name(self, tree):
765 if any(isinstance(name, IPAddress) and not isinstance(
766 name.value, (ipaddress.IPv4Network, ipaddress.IPv6Network)
767 ) for name in tree):
768 raise TypeError(
769 "IPAddress name constraints must be an IPv4Network or"
770 " IPv6Network object"
771 )
772
773 def __repr__(self):
774 return (
775 u"<NameConstraints(permitted_subtrees={0.permitted_subtrees}, "
776 u"excluded_subtrees={0.excluded_subtrees})>".format(self)
777 )
778
779 permitted_subtrees = utils.read_only_property("_permitted_subtrees")
780 excluded_subtrees = utils.read_only_property("_excluded_subtrees")