您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 
 
 

260 行
7.9 KiB

  1. # 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. from __future__ import absolute_import, division, print_function
  5. from enum import Enum
  6. import six
  7. from cryptography import utils
  8. from cryptography.x509.oid import NameOID, ObjectIdentifier
  9. class _ASN1Type(Enum):
  10. UTF8String = 12
  11. NumericString = 18
  12. PrintableString = 19
  13. T61String = 20
  14. IA5String = 22
  15. UTCTime = 23
  16. GeneralizedTime = 24
  17. VisibleString = 26
  18. UniversalString = 28
  19. BMPString = 30
  20. _ASN1_TYPE_TO_ENUM = dict((i.value, i) for i in _ASN1Type)
  21. _SENTINEL = object()
  22. _NAMEOID_DEFAULT_TYPE = {
  23. NameOID.COUNTRY_NAME: _ASN1Type.PrintableString,
  24. NameOID.JURISDICTION_COUNTRY_NAME: _ASN1Type.PrintableString,
  25. NameOID.SERIAL_NUMBER: _ASN1Type.PrintableString,
  26. NameOID.DN_QUALIFIER: _ASN1Type.PrintableString,
  27. NameOID.EMAIL_ADDRESS: _ASN1Type.IA5String,
  28. NameOID.DOMAIN_COMPONENT: _ASN1Type.IA5String,
  29. }
  30. #: Short attribute names from RFC 4514:
  31. #: https://tools.ietf.org/html/rfc4514#page-7
  32. _NAMEOID_TO_NAME = {
  33. NameOID.COMMON_NAME: 'CN',
  34. NameOID.LOCALITY_NAME: 'L',
  35. NameOID.STATE_OR_PROVINCE_NAME: 'ST',
  36. NameOID.ORGANIZATION_NAME: 'O',
  37. NameOID.ORGANIZATIONAL_UNIT_NAME: 'OU',
  38. NameOID.COUNTRY_NAME: 'C',
  39. NameOID.STREET_ADDRESS: 'STREET',
  40. NameOID.DOMAIN_COMPONENT: 'DC',
  41. NameOID.USER_ID: 'UID',
  42. }
  43. def _escape_dn_value(val):
  44. """Escape special characters in RFC4514 Distinguished Name value."""
  45. # See https://tools.ietf.org/html/rfc4514#section-2.4
  46. val = val.replace('\\', '\\\\')
  47. val = val.replace('"', '\\"')
  48. val = val.replace('+', '\\+')
  49. val = val.replace(',', '\\,')
  50. val = val.replace(';', '\\;')
  51. val = val.replace('<', '\\<')
  52. val = val.replace('>', '\\>')
  53. val = val.replace('\0', '\\00')
  54. if val[0] in ('#', ' '):
  55. val = '\\' + val
  56. if val[-1] == ' ':
  57. val = val[:-1] + '\\ '
  58. return val
  59. class NameAttribute(object):
  60. def __init__(self, oid, value, _type=_SENTINEL):
  61. if not isinstance(oid, ObjectIdentifier):
  62. raise TypeError(
  63. "oid argument must be an ObjectIdentifier instance."
  64. )
  65. if not isinstance(value, six.text_type):
  66. raise TypeError(
  67. "value argument must be a text type."
  68. )
  69. if (
  70. oid == NameOID.COUNTRY_NAME or
  71. oid == NameOID.JURISDICTION_COUNTRY_NAME
  72. ):
  73. if len(value.encode("utf8")) != 2:
  74. raise ValueError(
  75. "Country name must be a 2 character country code"
  76. )
  77. if len(value) == 0:
  78. raise ValueError("Value cannot be an empty string")
  79. # The appropriate ASN1 string type varies by OID and is defined across
  80. # multiple RFCs including 2459, 3280, and 5280. In general UTF8String
  81. # is preferred (2459), but 3280 and 5280 specify several OIDs with
  82. # alternate types. This means when we see the sentinel value we need
  83. # to look up whether the OID has a non-UTF8 type. If it does, set it
  84. # to that. Otherwise, UTF8!
  85. if _type == _SENTINEL:
  86. _type = _NAMEOID_DEFAULT_TYPE.get(oid, _ASN1Type.UTF8String)
  87. if not isinstance(_type, _ASN1Type):
  88. raise TypeError("_type must be from the _ASN1Type enum")
  89. self._oid = oid
  90. self._value = value
  91. self._type = _type
  92. oid = utils.read_only_property("_oid")
  93. value = utils.read_only_property("_value")
  94. def rfc4514_string(self):
  95. """
  96. Format as RFC4514 Distinguished Name string.
  97. Use short attribute name if available, otherwise fall back to OID
  98. dotted string.
  99. """
  100. key = _NAMEOID_TO_NAME.get(self.oid, self.oid.dotted_string)
  101. return '%s=%s' % (key, _escape_dn_value(self.value))
  102. def __eq__(self, other):
  103. if not isinstance(other, NameAttribute):
  104. return NotImplemented
  105. return (
  106. self.oid == other.oid and
  107. self.value == other.value
  108. )
  109. def __ne__(self, other):
  110. return not self == other
  111. def __hash__(self):
  112. return hash((self.oid, self.value))
  113. def __repr__(self):
  114. return "<NameAttribute(oid={0.oid}, value={0.value!r})>".format(self)
  115. class RelativeDistinguishedName(object):
  116. def __init__(self, attributes):
  117. attributes = list(attributes)
  118. if not attributes:
  119. raise ValueError("a relative distinguished name cannot be empty")
  120. if not all(isinstance(x, NameAttribute) for x in attributes):
  121. raise TypeError("attributes must be an iterable of NameAttribute")
  122. # Keep list and frozenset to preserve attribute order where it matters
  123. self._attributes = attributes
  124. self._attribute_set = frozenset(attributes)
  125. if len(self._attribute_set) != len(attributes):
  126. raise ValueError("duplicate attributes are not allowed")
  127. def get_attributes_for_oid(self, oid):
  128. return [i for i in self if i.oid == oid]
  129. def rfc4514_string(self):
  130. """
  131. Format as RFC4514 Distinguished Name string.
  132. Within each RDN, attributes are joined by '+', although that is rarely
  133. used in certificates.
  134. """
  135. return '+'.join(attr.rfc4514_string() for attr in self._attributes)
  136. def __eq__(self, other):
  137. if not isinstance(other, RelativeDistinguishedName):
  138. return NotImplemented
  139. return self._attribute_set == other._attribute_set
  140. def __ne__(self, other):
  141. return not self == other
  142. def __hash__(self):
  143. return hash(self._attribute_set)
  144. def __iter__(self):
  145. return iter(self._attributes)
  146. def __len__(self):
  147. return len(self._attributes)
  148. def __repr__(self):
  149. return "<RelativeDistinguishedName({})>".format(self.rfc4514_string())
  150. class Name(object):
  151. def __init__(self, attributes):
  152. attributes = list(attributes)
  153. if all(isinstance(x, NameAttribute) for x in attributes):
  154. self._attributes = [
  155. RelativeDistinguishedName([x]) for x in attributes
  156. ]
  157. elif all(isinstance(x, RelativeDistinguishedName) for x in attributes):
  158. self._attributes = attributes
  159. else:
  160. raise TypeError(
  161. "attributes must be a list of NameAttribute"
  162. " or a list RelativeDistinguishedName"
  163. )
  164. def rfc4514_string(self):
  165. """
  166. Format as RFC4514 Distinguished Name string.
  167. For example 'CN=foobar.com,O=Foo Corp,C=US'
  168. An X.509 name is a two-level structure: a list of sets of attributes.
  169. Each list element is separated by ',' and within each list element, set
  170. elements are separated by '+'. The latter is almost never used in
  171. real world certificates.
  172. """
  173. return ','.join(attr.rfc4514_string() for attr in self._attributes)
  174. def get_attributes_for_oid(self, oid):
  175. return [i for i in self if i.oid == oid]
  176. @property
  177. def rdns(self):
  178. return self._attributes
  179. def public_bytes(self, backend):
  180. return backend.x509_name_bytes(self)
  181. def __eq__(self, other):
  182. if not isinstance(other, Name):
  183. return NotImplemented
  184. return self._attributes == other._attributes
  185. def __ne__(self, other):
  186. return not self == other
  187. def __hash__(self):
  188. # TODO: this is relatively expensive, if this looks like a bottleneck
  189. # for you, consider optimizing!
  190. return hash(tuple(self._attributes))
  191. def __iter__(self):
  192. for rdn in self._attributes:
  193. for ava in rdn:
  194. yield ava
  195. def __len__(self):
  196. return sum(len(rdn) for rdn in self._attributes)
  197. def __repr__(self):
  198. if six.PY2:
  199. return "<Name({})>".format(self.rfc4514_string().encode('utf8'))
  200. else:
  201. return "<Name({})>".format(self.rfc4514_string())