Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/cryptography/hazmat/asn1/asn1.py: 26%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

202 statements  

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 builtins 

8import dataclasses 

9import enum 

10import sys 

11import types 

12import typing 

13 

14if sys.version_info < (3, 11): 

15 import typing_extensions 

16 

17 LiteralString = typing_extensions.LiteralString 

18else: 

19 LiteralString = typing.LiteralString 

20 

21from cryptography.hazmat.bindings._rust import declarative_asn1 

22from cryptography.hazmat.bindings._rust import x509 as rust_x509 

23 

24if sys.version_info < (3, 10): 

25 NoneType = type(None) 

26else: 

27 NoneType = types.NoneType # type: ignore[nonetype-type] 

28 

29T = typing.TypeVar("T", covariant=True) 

30U = typing.TypeVar("U") 

31Tag = typing.TypeVar("Tag", bound=LiteralString) 

32 

33 

34@dataclasses.dataclass(frozen=True) 

35class Variant(typing.Generic[U, Tag]): 

36 """ 

37 A tagged variant for CHOICE fields with the same underlying type. 

38 

39 Use this when you have multiple CHOICE alternatives with the same type 

40 and need to distinguish between them: 

41 

42 foo: ( 

43 Annotated[Variant[int, typing.Literal["IntA"]], Implicit(0)] 

44 | Annotated[Variant[int, typing.Literal["IntB"]], Implicit(1)] 

45 ) 

46 

47 Usage: 

48 example = Example(foo=Variant(5, "IntA")) 

49 decoded.foo.value # The int value 

50 decoded.foo.tag # "IntA" or "IntB" 

51 """ 

52 

53 value: U 

54 tag: str 

55 

56 

57decode_der = declarative_asn1.decode_der 

58encode_der = declarative_asn1.encode_der 

59 

60 

61_X509_TYPES = ( 

62 rust_x509.Certificate, 

63 rust_x509.CertificateSigningRequest, 

64 rust_x509.CertificateRevocationList, 

65) 

66 

67 

68def _check_x509_field_annotations( 

69 field_type: typing.Any, 

70 annotation: declarative_asn1.Annotation, 

71 field_name: str, 

72) -> None: 

73 if field_type in _X509_TYPES and isinstance(annotation.encoding, Implicit): 

74 raise TypeError( 

75 f"field '{field_name}' has an IMPLICIT annotation, but " 

76 "IMPLICIT annotations are not supported for X.509 types." 

77 ) 

78 

79 

80def _is_union(field_type: type) -> bool: 

81 # NOTE: types.UnionType for `T | U`, typing.Union for `Union[T, U]`. 

82 # TODO: Drop the `hasattr()` once the minimum supported Python version 

83 # is >= 3.10. 

84 union_types = ( 

85 (types.UnionType, typing.Union) 

86 if hasattr(types, "UnionType") 

87 else (typing.Union,) 

88 ) 

89 return typing.get_origin(field_type) in union_types 

90 

91 

92def _resolve_type_aliases(field_type: typing.Any) -> typing.Any: 

93 # Recursively resolve PEP 695 (`type X = ...`) type aliases (Python 

94 # 3.12+) to their underlying value, so that the rest of the 

95 # normalization logic never encounters an alias. Aliases can refer 

96 # to other aliases and can appear at any level of nesting (e.g. 

97 # inside `Annotated[...]`, unions, or `list[...]`). 

98 if sys.version_info < (3, 12): 

99 return field_type 

100 

101 while isinstance(field_type, typing.TypeAliasType): 

102 field_type = field_type.__value__ 

103 

104 args = typing.get_args(field_type) 

105 resolved_args = tuple(_resolve_type_aliases(arg) for arg in args) 

106 if resolved_args == args: 

107 # No aliases anywhere inside: return the type unchanged. 

108 return field_type 

109 

110 if _is_union(field_type): 

111 # `X | Y` unions can't be rebuilt through their origin like 

112 # other generics below: `typing.get_origin` returns 

113 # `types.UnionType` for them, which is only subscriptable on 

114 # Python 3.14+. Rebuilding through `typing.Union` also 

115 # flattens any nested union introduced by an alias of a union 

116 # (e.g. `Time | int` where `type Time = UTCTime | 

117 # GeneralizedTime`), just like `typing.Union` would have done 

118 # if the alias had been written inline. 

119 return typing.Union[resolved_args] 

120 

121 # An alias appeared inside a generic (e.g. `Annotated[Time, ...]`, 

122 # `list[MyInt]`, or `SetOf[MyInt]`): re-parameterize the generic 

123 # with the resolved arguments. Subscripting with a tuple is 

124 # equivalent to subscripting with multiple arguments. 

125 return typing.get_origin(field_type)[resolved_args] 

126 

127 

128def _extract_annotation( 

129 metadata: tuple, field_name: str 

130) -> declarative_asn1.Annotation: 

131 default = None 

132 encoding = None 

133 size = None 

134 for raw_annotation in metadata: 

135 if isinstance(raw_annotation, Default): 

136 if default is not None: 

137 raise TypeError( 

138 f"multiple DEFAULT annotations found in field " 

139 f"'{field_name}'" 

140 ) 

141 default = raw_annotation.value 

142 elif isinstance(raw_annotation, declarative_asn1.Encoding): 

143 if encoding is not None: 

144 raise TypeError( 

145 f"multiple IMPLICIT/EXPLICIT annotations found in field " 

146 f"'{field_name}'" 

147 ) 

148 encoding = raw_annotation 

149 elif isinstance(raw_annotation, declarative_asn1.Size): 

150 if size is not None: 

151 raise TypeError( 

152 f"multiple SIZE annotations found in field '{field_name}'" 

153 ) 

154 size = raw_annotation 

155 else: 

156 raise TypeError(f"unsupported annotation: {raw_annotation}") 

157 

158 return declarative_asn1.Annotation( 

159 default=default, encoding=encoding, size=size 

160 ) 

161 

162 

163def _normalize_field_type( 

164 field_type: typing.Any, field_name: str 

165) -> declarative_asn1.AnnotatedType: 

166 field_type = _resolve_type_aliases(field_type) 

167 

168 # Strip the `Annotated[...]` off, and populate the annotation 

169 # from it if it exists. 

170 if typing.get_origin(field_type) is typing.Annotated: 

171 annotation = _extract_annotation(field_type.__metadata__, field_name) 

172 field_type, *_ = typing.get_args(field_type) 

173 else: 

174 annotation = declarative_asn1.Annotation() 

175 

176 if annotation.size is not None and ( 

177 typing.get_origin(field_type) not in (builtins.list, SetOf) 

178 and field_type 

179 not in ( 

180 builtins.bytes, 

181 builtins.str, 

182 BitString, 

183 IA5String, 

184 PrintableString, 

185 ) 

186 ): 

187 raise TypeError( 

188 f"field '{field_name}' has a SIZE annotation, but SIZE " 

189 "annotations are only supported for fields of types: " 

190 "[SEQUENCE OF, SET OF, BIT STRING, OCTET STRING, UTF8String, " 

191 "PrintableString, IA5String]" 

192 ) 

193 

194 if field_type is TLV: 

195 if isinstance(annotation.encoding, Implicit): 

196 raise TypeError( 

197 f"field '{field_name}' has an IMPLICIT annotation, but " 

198 "IMPLICIT annotations are not supported for TLV types." 

199 ) 

200 elif annotation.default is not None: 

201 raise TypeError( 

202 f"field '{field_name}' has a DEFAULT annotation, but " 

203 "DEFAULT annotations are not supported for TLV types." 

204 ) 

205 

206 _check_x509_field_annotations(field_type, annotation, field_name) 

207 

208 if hasattr(field_type, "__asn1_root__"): 

209 root_type = field_type.__asn1_root__ 

210 if not isinstance( 

211 root_type, 

212 ( 

213 declarative_asn1.Type.Sequence, 

214 declarative_asn1.Type.Set, 

215 declarative_asn1.Type.ValueSet, 

216 ), 

217 ): 

218 raise TypeError(f"unsupported root type: {root_type}") 

219 return declarative_asn1.AnnotatedType( 

220 typing.cast(declarative_asn1.Type, root_type), annotation 

221 ) 

222 elif _is_union(field_type): 

223 union_args = typing.get_args(field_type) 

224 if len(union_args) == 2 and NoneType in union_args: 

225 # A Union between a type and None is an OPTIONAL 

226 optional_type = ( 

227 union_args[0] if union_args[1] is type(None) else union_args[1] 

228 ) 

229 if optional_type is TLV: 

230 raise TypeError( 

231 "optional TLV types (`TLV | None`) are not " 

232 "currently supported" 

233 ) 

234 # For optional types, the annotation is associated with the 

235 # union, so we check it against the inner type here. 

236 _check_x509_field_annotations( 

237 optional_type, annotation, field_name 

238 ) 

239 annotated_type = _normalize_field_type(optional_type, field_name) 

240 

241 if not annotated_type.annotation.is_empty(): 

242 raise TypeError( 

243 "optional (`X | None`) types cannot have `X` " 

244 "annotated: annotations must apply to the union " 

245 "(i.e: `Annotated[X | None, annotation]`)" 

246 ) 

247 

248 if annotation.default is not None: 

249 raise TypeError( 

250 "optional (`X | None`) types should not have a DEFAULT " 

251 "annotation" 

252 ) 

253 

254 rust_field_type = declarative_asn1.Type.Option(annotated_type) 

255 

256 else: 

257 # Otherwise, the Union is a CHOICE 

258 if isinstance(annotation.encoding, Implicit): 

259 # CHOICEs cannot be IMPLICIT. See X.680 section 31.2.9. 

260 raise TypeError( 

261 "CHOICE (`X | Y | ...`) types should not have an IMPLICIT " 

262 "annotation" 

263 ) 

264 variants = [ 

265 _type_to_variant(arg, field_name) 

266 for arg in union_args 

267 if arg is not type(None) 

268 ] 

269 

270 # Union types should either be all Variants 

271 # (`Variant[..] | Variant[..] | etc`) or all non Variants 

272 are_union_types_tagged = variants[0].tag_name is not None 

273 if any( 

274 (v.tag_name is not None) != are_union_types_tagged 

275 for v in variants 

276 ): 

277 raise TypeError( 

278 "When using `asn1.Variant` in a union, all the other " 

279 "types in the union must also be `asn1.Variant`" 

280 ) 

281 

282 if are_union_types_tagged: 

283 tags = {v.tag_name for v in variants} 

284 if len(variants) != len(tags): 

285 raise TypeError( 

286 "When using `asn1.Variant` in a union, the tags used " 

287 "must be unique" 

288 ) 

289 

290 rust_choice_type = declarative_asn1.Type.Choice(variants) 

291 # If None is part of the union types, this is an OPTIONAL CHOICE 

292 rust_field_type = ( 

293 declarative_asn1.Type.Option( 

294 declarative_asn1.AnnotatedType( 

295 rust_choice_type, declarative_asn1.Annotation() 

296 ) 

297 ) 

298 if NoneType in union_args 

299 else rust_choice_type 

300 ) 

301 

302 elif typing.get_origin(field_type) is builtins.list: 

303 inner_type = _normalize_field_type( 

304 typing.get_args(field_type)[0], field_name 

305 ) 

306 rust_field_type = declarative_asn1.Type.SequenceOf(inner_type) 

307 elif typing.get_origin(field_type) is SetOf: 

308 inner_type = _normalize_field_type( 

309 typing.get_args(field_type)[0], field_name 

310 ) 

311 rust_field_type = declarative_asn1.Type.SetOf(inner_type) 

312 else: 

313 rust_field_type = declarative_asn1.non_root_python_to_rust(field_type) 

314 

315 return declarative_asn1.AnnotatedType(rust_field_type, annotation) 

316 

317 

318# Convert a type to a Variant. Used with types inside Union 

319# annotations (T1, T2, etc in `Union[T1, T2, ...]`). 

320def _type_to_variant( 

321 t: typing.Any, field_name: str 

322) -> declarative_asn1.Variant: 

323 is_annotated = typing.get_origin(t) is typing.Annotated 

324 inner_type = typing.get_args(t)[0] if is_annotated else t 

325 

326 # Check if this is a Variant[T, Tag] type 

327 if typing.get_origin(inner_type) is Variant: 

328 value_type, tag_literal = typing.get_args(inner_type) 

329 if typing.get_origin(tag_literal) is not typing.Literal: 

330 raise TypeError( 

331 "When using `asn1.Variant` in a type annotation, the second " 

332 "type parameter must be a `typing.Literal` type. E.g: " 

333 '`Variant[int, typing.Literal["MyInt"]]`.' 

334 ) 

335 tag_name = typing.get_args(tag_literal)[0] 

336 

337 if hasattr(value_type, "__asn1_root__"): 

338 rust_type = value_type.__asn1_root__ 

339 else: 

340 rust_type = declarative_asn1.non_root_python_to_rust(value_type) 

341 

342 if is_annotated: 

343 ann_type = declarative_asn1.AnnotatedType( 

344 rust_type, 

345 _extract_annotation(t.__metadata__, field_name), 

346 ) 

347 else: 

348 ann_type = declarative_asn1.AnnotatedType( 

349 rust_type, 

350 declarative_asn1.Annotation(), 

351 ) 

352 

353 return declarative_asn1.Variant(Variant, ann_type, tag_name) 

354 else: 

355 # Plain type (not a tagged Variant) 

356 return declarative_asn1.Variant( 

357 inner_type, 

358 _normalize_field_type(t, field_name), 

359 None, 

360 ) 

361 

362 

363def _annotate_fields( 

364 raw_fields: dict[str, type], 

365) -> dict[str, declarative_asn1.AnnotatedType]: 

366 fields = {} 

367 for field_name, field_type in raw_fields.items(): 

368 # Recursively normalize the field type into something that the 

369 # Rust code can understand. 

370 annotated_field_type = _normalize_field_type(field_type, field_name) 

371 fields[field_name] = annotated_field_type 

372 

373 return fields 

374 

375 

376def _register_asn1_sequence(cls: type[U]) -> None: 

377 raw_fields = typing.get_type_hints(cls, include_extras=True) 

378 root = declarative_asn1.Type.Sequence(cls, _annotate_fields(raw_fields)) 

379 

380 setattr(cls, "__asn1_root__", root) 

381 

382 

383def _register_asn1_set(cls: type[U]) -> None: 

384 raw_fields = typing.get_type_hints(cls, include_extras=True) 

385 root = declarative_asn1.Type.Set(cls, _annotate_fields(raw_fields)) 

386 

387 setattr(cls, "__asn1_root__", root) 

388 

389 

390# Due to https://github.com/python/mypy/issues/19731, we can't define an alias 

391# for `dataclass_transform` that conditionally points to `typing` or 

392# `typing_extensions` depending on the Python version. We work around it by 

393# making the whole decorated class conditional on the Python version. 

394if sys.version_info < (3, 11): 

395 

396 @typing_extensions.dataclass_transform(kw_only_default=True) 

397 def sequence(cls: type[U]) -> type[U]: 

398 # We use `dataclasses.dataclass` to add an __init__ method 

399 # to the class with keyword-only parameters. 

400 if sys.version_info >= (3, 10): 

401 dataclass_cls = dataclasses.dataclass( 

402 repr=False, 

403 eq=False, 

404 # `match_args` was added in Python 3.10 and defaults 

405 # to True 

406 match_args=False, 

407 # `kw_only` was added in Python 3.10 and defaults to 

408 # False 

409 kw_only=True, 

410 )(cls) 

411 else: 

412 dataclass_cls = dataclasses.dataclass( 

413 repr=False, 

414 eq=False, 

415 )(cls) 

416 _register_asn1_sequence(dataclass_cls) 

417 return dataclass_cls 

418 

419 @typing_extensions.dataclass_transform(kw_only_default=True) 

420 def set(cls: type[U]) -> type[U]: 

421 # We use `dataclasses.dataclass` to add an __init__ method 

422 # to the class with keyword-only parameters. 

423 if sys.version_info >= (3, 10): 

424 dataclass_cls = dataclasses.dataclass( 

425 repr=False, 

426 eq=False, 

427 # `match_args` was added in Python 3.10 and defaults 

428 # to True 

429 match_args=False, 

430 # `kw_only` was added in Python 3.10 and defaults to 

431 # False 

432 kw_only=True, 

433 )(cls) 

434 else: 

435 dataclass_cls = dataclasses.dataclass( 

436 repr=False, 

437 eq=False, 

438 )(cls) 

439 _register_asn1_set(dataclass_cls) 

440 return dataclass_cls 

441 

442else: 

443 

444 @typing.dataclass_transform(kw_only_default=True) 

445 def sequence(cls: type[U]) -> type[U]: 

446 # Only add an __init__ method, with keyword-only 

447 # parameters. 

448 dataclass_cls = dataclasses.dataclass( 

449 repr=False, 

450 eq=False, 

451 match_args=False, 

452 kw_only=True, 

453 )(cls) 

454 _register_asn1_sequence(dataclass_cls) 

455 return dataclass_cls 

456 

457 @typing.dataclass_transform(kw_only_default=True) 

458 def set(cls: type[U]) -> type[U]: 

459 # Only add an __init__ method, with keyword-only 

460 # parameters. 

461 dataclass_cls = dataclasses.dataclass( 

462 repr=False, 

463 eq=False, 

464 match_args=False, 

465 kw_only=True, 

466 )(cls) 

467 _register_asn1_set(dataclass_cls) 

468 return dataclass_cls 

469 

470 

471def value_set( 

472 value_type: type, 

473) -> typing.Callable[[type[U]], type[U]]: 

474 """ 

475 A class decorator that registers an `enum.Enum` subclass as an 

476 ASN.1 value set of the given underlying type. All the member 

477 values must be instances of `value_type`. Members are encoded as 

478 their value; decoding fails if the decoded value does not match 

479 any member. 

480 """ 

481 rust_type = declarative_asn1.non_root_python_to_rust(value_type) 

482 

483 def decorator(cls: type[U]) -> type[U]: 

484 if not issubclass(cls, enum.Enum): 

485 raise TypeError( 

486 "value sets can only be defined from enum.Enum subclasses" 

487 ) 

488 members = list(cls) 

489 if not members: 

490 raise TypeError( 

491 f"value set '{cls.__name__}' must have at least one member" 

492 ) 

493 for member in members: 

494 if not isinstance(member.value, value_type): 

495 raise TypeError( 

496 f"member '{member.name}' of value set '{cls.__name__}' " 

497 f"must have a value of type " 

498 f"'{value_type.__name__}', got: " 

499 f"'{type(member.value).__name__}'" 

500 ) 

501 inner = declarative_asn1.AnnotatedType( 

502 rust_type, declarative_asn1.Annotation() 

503 ) 

504 # Map from member value to member, used for O(1) lookups when 

505 # decoding. This requires the member values to be hashable. 

506 value_map = {member.value: member for member in members} 

507 root = declarative_asn1.Type.ValueSet(cls, inner, value_map) 

508 

509 setattr(cls, "__asn1_root__", root) 

510 return cls 

511 

512 return decorator 

513 

514 

515# TODO: replace with `Default[U]` once the min Python version is >= 3.12 

516@dataclasses.dataclass(frozen=True) 

517class Default(typing.Generic[U]): 

518 value: U 

519 

520 

521SetOf = declarative_asn1.SetOf 

522 

523Explicit = declarative_asn1.Encoding.Explicit 

524Implicit = declarative_asn1.Encoding.Implicit 

525Size = declarative_asn1.Size 

526 

527PrintableString = declarative_asn1.PrintableString 

528IA5String = declarative_asn1.IA5String 

529UTCTime = declarative_asn1.UTCTime 

530GeneralizedTime = declarative_asn1.GeneralizedTime 

531BitString = declarative_asn1.BitString 

532TLV = declarative_asn1.Tlv 

533Null = declarative_asn1.Null