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
5from __future__ import annotations
6
7import abc
8import ipaddress
9import typing
10from email.utils import parseaddr
11
12from cryptography.x509.name import Name
13from cryptography.x509.oid import ObjectIdentifier
14
15_IPAddressTypes = typing.Union[
16 ipaddress.IPv4Address,
17 ipaddress.IPv6Address,
18 ipaddress.IPv4Network,
19 ipaddress.IPv6Network,
20]
21
22
23class UnsupportedGeneralNameType(Exception):
24 pass
25
26
27class GeneralName(metaclass=abc.ABCMeta):
28 @property
29 @abc.abstractmethod
30 def value(self) -> typing.Any:
31 """
32 Return the value of the object
33 """
34
35
36class RFC822Name(GeneralName):
37 def __init__(self, value: str) -> None:
38 if isinstance(value, str):
39 try:
40 value.encode("ascii")
41 except UnicodeEncodeError:
42 raise ValueError(
43 "RFC822Name values should be passed as an A-label string. "
44 "This means unicode characters should be encoded via "
45 "a library like idna."
46 )
47 else:
48 raise TypeError("value must be string")
49
50 name, address = parseaddr(value)
51 if name or not address:
52 # parseaddr has found a name (e.g. Name <email>) or the entire
53 # value is an empty string.
54 raise ValueError("Invalid rfc822name value")
55
56 self._value = value
57
58 @property
59 def value(self) -> str:
60 return self._value
61
62 @classmethod
63 def _init_without_validation(cls, value: str) -> RFC822Name:
64 instance = cls.__new__(cls)
65 instance._value = value
66 return instance
67
68 def __repr__(self) -> str:
69 return f"<RFC822Name(value={self.value!r})>"
70
71 def __eq__(self, other: object) -> bool:
72 if not isinstance(other, RFC822Name):
73 return NotImplemented
74
75 return self.value == other.value
76
77 def __hash__(self) -> int:
78 return hash(self.value)
79
80
81class DNSName(GeneralName):
82 def __init__(self, value: str) -> None:
83 if isinstance(value, str):
84 try:
85 value.encode("ascii")
86 except UnicodeEncodeError:
87 raise ValueError(
88 "DNSName values should be passed as an A-label string. "
89 "This means unicode characters should be encoded via "
90 "a library like idna."
91 )
92 else:
93 raise TypeError("value must be string")
94
95 self._value = value
96
97 @property
98 def value(self) -> str:
99 return self._value
100
101 @classmethod
102 def _init_without_validation(cls, value: str) -> DNSName:
103 instance = cls.__new__(cls)
104 instance._value = value
105 return instance
106
107 def __repr__(self) -> str:
108 return f"<DNSName(value={self.value!r})>"
109
110 def __eq__(self, other: object) -> bool:
111 if not isinstance(other, DNSName):
112 return NotImplemented
113
114 return self.value == other.value
115
116 def __hash__(self) -> int:
117 return hash(self.value)
118
119
120class UniformResourceIdentifier(GeneralName):
121 def __init__(self, value: str) -> None:
122 if isinstance(value, str):
123 try:
124 value.encode("ascii")
125 except UnicodeEncodeError:
126 raise ValueError(
127 "URI values should be passed as an A-label string. "
128 "This means unicode characters should be encoded via "
129 "a library like idna."
130 )
131 else:
132 raise TypeError("value must be string")
133
134 self._value = value
135
136 @property
137 def value(self) -> str:
138 return self._value
139
140 @classmethod
141 def _init_without_validation(cls, value: str) -> UniformResourceIdentifier:
142 instance = cls.__new__(cls)
143 instance._value = value
144 return instance
145
146 def __repr__(self) -> str:
147 return f"<UniformResourceIdentifier(value={self.value!r})>"
148
149 def __eq__(self, other: object) -> bool:
150 if not isinstance(other, UniformResourceIdentifier):
151 return NotImplemented
152
153 return self.value == other.value
154
155 def __hash__(self) -> int:
156 return hash(self.value)
157
158
159class DirectoryName(GeneralName):
160 def __init__(self, value: Name) -> None:
161 if not isinstance(value, Name):
162 raise TypeError("value must be a Name")
163
164 self._value = value
165
166 @property
167 def value(self) -> Name:
168 return self._value
169
170 def __repr__(self) -> str:
171 return f"<DirectoryName(value={self.value})>"
172
173 def __eq__(self, other: object) -> bool:
174 if not isinstance(other, DirectoryName):
175 return NotImplemented
176
177 return self.value == other.value
178
179 def __hash__(self) -> int:
180 return hash(self.value)
181
182
183class RegisteredID(GeneralName):
184 def __init__(self, value: ObjectIdentifier) -> None:
185 if not isinstance(value, ObjectIdentifier):
186 raise TypeError("value must be an ObjectIdentifier")
187
188 self._value = value
189
190 @property
191 def value(self) -> ObjectIdentifier:
192 return self._value
193
194 def __repr__(self) -> str:
195 return f"<RegisteredID(value={self.value})>"
196
197 def __eq__(self, other: object) -> bool:
198 if not isinstance(other, RegisteredID):
199 return NotImplemented
200
201 return self.value == other.value
202
203 def __hash__(self) -> int:
204 return hash(self.value)
205
206
207class IPAddress(GeneralName):
208 def __init__(self, value: _IPAddressTypes) -> None:
209 if not isinstance(
210 value,
211 (
212 ipaddress.IPv4Address,
213 ipaddress.IPv6Address,
214 ipaddress.IPv4Network,
215 ipaddress.IPv6Network,
216 ),
217 ):
218 raise TypeError(
219 "value must be an instance of ipaddress.IPv4Address, "
220 "ipaddress.IPv6Address, ipaddress.IPv4Network, or "
221 "ipaddress.IPv6Network"
222 )
223
224 self._value = value
225
226 @property
227 def value(self) -> _IPAddressTypes:
228 return self._value
229
230 def _packed(self) -> bytes:
231 if isinstance(
232 self.value, (ipaddress.IPv4Address, ipaddress.IPv6Address)
233 ):
234 return self.value.packed
235 else:
236 return (
237 self.value.network_address.packed + self.value.netmask.packed
238 )
239
240 def __repr__(self) -> str:
241 return f"<IPAddress(value={self.value})>"
242
243 def __eq__(self, other: object) -> bool:
244 if not isinstance(other, IPAddress):
245 return NotImplemented
246
247 return self.value == other.value
248
249 def __hash__(self) -> int:
250 return hash(self.value)
251
252
253class OtherName(GeneralName):
254 def __init__(self, type_id: ObjectIdentifier, value: bytes) -> None:
255 if not isinstance(type_id, ObjectIdentifier):
256 raise TypeError("type_id must be an ObjectIdentifier")
257 if not isinstance(value, bytes):
258 raise TypeError("value must be a binary string")
259
260 self._type_id = type_id
261 self._value = value
262
263 @property
264 def type_id(self) -> ObjectIdentifier:
265 return self._type_id
266
267 @property
268 def value(self) -> bytes:
269 return self._value
270
271 def __repr__(self) -> str:
272 return f"<OtherName(type_id={self.type_id}, value={self.value!r})>"
273
274 def __eq__(self, other: object) -> bool:
275 if not isinstance(other, OtherName):
276 return NotImplemented
277
278 return self.type_id == other.type_id and self.value == other.value
279
280 def __hash__(self) -> int:
281 return hash((self.type_id, self.value))