feat(admissions): add profession info type for the admissions extension (#11881)

* feat(admissions): add profession info python type for the admissions extension

Signed-off-by: oleg.hoefling <oleg.hoefling@gmail.com>

* feat(admissions): add profession info rust type for the admissions extension

Signed-off-by: oleg.hoefling <oleg.hoefling@gmail.com>

* feat(admissions): add test for profession info hash implementation

Signed-off-by: oleg.hoefling <oleg.hoefling@gmail.com>

* fix(admissions): minor fixes

Signed-off-by: oleg.hoefling <oleg.hoefling@gmail.com>

* remove the asn1 traits from the profession info rust type

Signed-off-by: oleg.hoefling <oleg.hoefling@gmail.com>

* remove the explicit mark from the naming authority field

Signed-off-by: oleg.hoefling <oleg.hoefling@gmail.com>

* chore: add commented out annotation for the naming authority field

Signed-off-by: Oleg Hoefling <oleg.hoefling@gmail.com>

* fix: use correct type for add_profeccion_info field

Signed-off-by: Oleg Hoefling <oleg.hoefling@gmail.com>

* refactor: explicitly convert profession items and oids to tuples for hash calculation

Signed-off-by: Oleg Hoefling <oleg.hoefling@gmail.com>

* refactor: add asn1 trait derives to naming authority and profession info types, commented out

Signed-off-by: Oleg Hoefling <oleg.hoefling@gmail.com>

---------

Signed-off-by: oleg.hoefling <oleg.hoefling@gmail.com>
Signed-off-by: Oleg Hoefling <oleg.hoefling@gmail.com>
This commit is contained in:
Oleg Höfling 2024-11-03 21:16:23 +01:00 committed by GitHub
parent 9e46c93034
commit f65ab4d7f5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 352 additions and 0 deletions

View file

@ -64,6 +64,7 @@ from cryptography.x509.extensions import (
PolicyInformation,
PrecertificateSignedCertificateTimestamps,
PrecertPoison,
ProfessionInfo,
ReasonFlags,
SignedCertificateTimestamps,
SubjectAlternativeName,
@ -228,6 +229,7 @@ __all__ = [
"PolicyInformation",
"PrecertPoison",
"PrecertificateSignedCertificateTimestamps",
"ProfessionInfo",
"PublicKeyAlgorithmOID",
"RFC822Name",
"ReasonFlags",

View file

@ -2222,6 +2222,104 @@ class NamingAuthority:
)
class ProfessionInfo:
def __init__(
self,
naming_authority: NamingAuthority | None,
profession_items: typing.Iterable[str],
profession_oids: typing.Iterable[ObjectIdentifier],
registration_number: str | None,
add_profession_info: bytes | None,
) -> None:
if naming_authority is not None and not isinstance(
naming_authority, NamingAuthority
):
raise TypeError("naming_authority must be a NamingAuthority")
profession_items = list(profession_items)
if not all(isinstance(item, str) for item in profession_items):
raise TypeError(
"Every item in the profession_items list must be a str"
)
profession_oids = list(profession_oids)
if not all(
isinstance(oid, ObjectIdentifier) for oid in profession_oids
):
raise TypeError(
"Every item in the profession_oids list must be an "
"ObjectIdentifier"
)
if registration_number is not None and not isinstance(
registration_number, str
):
raise TypeError("registration_number must be a str")
if add_profession_info is not None and not isinstance(
add_profession_info, bytes
):
raise TypeError("add_profession_info must be bytes")
self._naming_authority = naming_authority
self._profession_items = profession_items
self._profession_oids = profession_oids
self._registration_number = registration_number
self._add_profession_info = add_profession_info
@property
def naming_authority(self) -> NamingAuthority | None:
return self._naming_authority
@property
def profession_items(self) -> list[str]:
return self._profession_items
@property
def profession_oids(self) -> list[ObjectIdentifier]:
return self._profession_oids
@property
def registration_number(self) -> str | None:
return self._registration_number
@property
def add_profession_info(self) -> bytes | None:
return self._add_profession_info
def __repr__(self) -> str:
return (
f"<ProfessionInfo(naming_authority={self.naming_authority}, "
f"profession_items={self.profession_items}, "
f"profession_oids={self.profession_oids}, "
f"registration_number={self.registration_number}, "
f"add_profession_info={self.add_profession_info!r})>"
)
def __eq__(self, other: object) -> bool:
if not isinstance(other, ProfessionInfo):
return NotImplemented
return (
self.naming_authority == other.naming_authority
and self.profession_items == other.profession_items
and self.profession_oids == other.profession_oids
and self.registration_number == other.registration_number
and self.add_profession_info == other.add_profession_info
)
def __hash__(self) -> int:
return hash(
(
self.naming_authority,
*tuple(self.profession_items),
*tuple(self.profession_oids),
self.registration_number,
self.add_profession_info,
)
)
class UnrecognizedExtension(ExtensionType):
def __init__(self, oid: ObjectIdentifier, value: bytes) -> None:
if not isinstance(oid, ObjectIdentifier):

View file

@ -285,12 +285,33 @@ impl KeyUsage<'_> {
}
}
// #[derive(asn1::Asn1Read, asn1::Asn1Write)]
pub struct NamingAuthority<'a> {
pub id: Option<asn1::ObjectIdentifier>,
pub url: Option<asn1::IA5String<'a>>,
pub text: Option<DisplayText<'a>>,
}
type SequenceOfDisplayTexts<'a> = common::Asn1ReadableOrWritable<
asn1::SequenceOf<'a, DisplayText<'a>>,
asn1::SequenceOfWriter<'a, DisplayText<'a>, Vec<DisplayText<'a>>>,
>;
type SequenceOfObjectIdentifiers<'a> = common::Asn1ReadableOrWritable<
asn1::SequenceOf<'a, asn1::ObjectIdentifier>,
asn1::SequenceOfWriter<'a, asn1::ObjectIdentifier, Vec<asn1::ObjectIdentifier>>,
>;
// #[derive(asn1::Asn1Read, asn1::Asn1Write)]
pub struct ProfessionInfo<'a> {
// #[explicit(0)]
pub naming_authority: Option<NamingAuthority<'a>>,
pub profession_items: SequenceOfDisplayTexts<'a>,
pub profession_oids: Option<SequenceOfObjectIdentifiers<'a>>,
pub registration_number: Option<asn1::PrintableString<'a>>,
pub add_profession_info: Option<&'a [u8]>,
}
#[cfg(test)]
mod tests {
use super::{BasicConstraints, Extension, Extensions, KeyUsage};

View file

@ -6437,6 +6437,237 @@ class TestNamingAuthority:
assert hash(authority1) != hash(authority9)
class TestProfessionInfo:
def test_invalid_init(self):
with pytest.raises(TypeError):
x509.ProfessionInfo(
None,
None, # type:ignore[arg-type]
None, # type:ignore[arg-type]
None,
None,
)
with pytest.raises(TypeError):
x509.ProfessionInfo(
"spam", # type:ignore[arg-type]
[],
[],
None,
None,
)
with pytest.raises(TypeError):
x509.ProfessionInfo(
None,
[42], # type:ignore[list-item]
[],
None,
None,
)
with pytest.raises(TypeError):
x509.ProfessionInfo(
None,
[],
"spam", # type:ignore[arg-type]
None,
None,
)
with pytest.raises(TypeError):
x509.ProfessionInfo(
None,
[],
[],
42, # type:ignore[arg-type]
None,
)
with pytest.raises(TypeError):
x509.ProfessionInfo(
None,
[],
[],
None,
42, # type:ignore[arg-type]
)
def test_eq(self):
info1 = x509.ProfessionInfo(None, [], [], None, None)
info2 = x509.ProfessionInfo(None, [], [], None, None)
assert info1 == info2
info1 = x509.ProfessionInfo(
x509.NamingAuthority(
x509.ObjectIdentifier("1.2.3"), "https://example.com", "spam"
),
["spam"],
[x509.ObjectIdentifier("1.2.3.4")],
"eggs",
b"\x01\x02\x03",
)
info2 = x509.ProfessionInfo(
x509.NamingAuthority(
x509.ObjectIdentifier("1.2.3"), "https://example.com", "spam"
),
["spam"],
[x509.ObjectIdentifier("1.2.3.4")],
"eggs",
b"\x01\x02\x03",
)
assert info1 == info2
def test_ne(self):
info1 = x509.ProfessionInfo(
x509.NamingAuthority(
x509.ObjectIdentifier("1.2.3"), "https://example.com", "spam"
),
["spam"],
[x509.ObjectIdentifier("1.2.3.4")],
"eggs",
b"\x01\x02\x03",
)
info2 = x509.ProfessionInfo(
x509.NamingAuthority(
x509.ObjectIdentifier("1.2.3"), "https://example.com", "spam"
),
["spam"],
[x509.ObjectIdentifier("1.2.3.4")],
"eggs",
None,
)
info3 = x509.ProfessionInfo(
x509.NamingAuthority(
x509.ObjectIdentifier("1.2.3"), "https://example.com", "spam"
),
["spam"],
[x509.ObjectIdentifier("1.2.3.4")],
None,
None,
)
info4 = x509.ProfessionInfo(
x509.NamingAuthority(
x509.ObjectIdentifier("1.2.3"), "https://example.com", "spam"
),
["spam"],
[],
None,
None,
)
info5 = x509.ProfessionInfo(
x509.NamingAuthority(
x509.ObjectIdentifier("1.2.3"), "https://example.com", "spam"
),
[],
[],
None,
None,
)
info6 = x509.ProfessionInfo(None, ["spam"], [], None, None)
info7 = x509.ProfessionInfo(
None, [], [x509.ObjectIdentifier("1.2.3")], None, None
)
info8 = x509.ProfessionInfo(None, [], [], "spam", None)
info9 = x509.ProfessionInfo(None, [], [], None, b"\x01\x02\x03")
info10 = x509.ProfessionInfo(None, [], [], None, None)
assert info1 != info2
assert info1 != info2
assert info1 != info3
assert info1 != info4
assert info1 != info5
assert info1 != info6
assert info1 != info7
assert info1 != info8
assert info1 != info9
assert info1 != info10
assert info1 != object()
def test_repr(self):
info = x509.ProfessionInfo(None, [], [], None, None)
assert repr(info) == (
"<ProfessionInfo("
"naming_authority=None, "
"profession_items=[], "
"profession_oids=[], "
"registration_number=None, "
"add_profession_info=None)>"
)
info = x509.ProfessionInfo(
x509.NamingAuthority(
x509.ObjectIdentifier("1.2.3"), "https://example.com", "spam"
),
["spam"],
[x509.ObjectIdentifier("1.2.3.4")],
"eggs",
b"\x01\x02\x03",
)
assert repr(info) == (
"<ProfessionInfo("
"naming_authority=<NamingAuthority("
"id=<ObjectIdentifier(oid=1.2.3, name=Unknown OID)>, "
"url=https://example.com, text=spam)>, "
"profession_items=['spam'], "
"profession_oids="
"[<ObjectIdentifier(oid=1.2.3.4, name=Unknown OID)>], "
"registration_number=eggs, "
"add_profession_info=b'\\x01\\x02\\x03')>"
)
def test_hash(self):
info1 = x509.ProfessionInfo(
x509.NamingAuthority(None, None, None),
["spam"],
[x509.ObjectIdentifier("1.2.3.4")],
"eggs",
b"\x01\x02\x03",
)
info2 = x509.ProfessionInfo(
x509.NamingAuthority(None, None, None),
["spam"],
[x509.ObjectIdentifier("1.2.3.4")],
"eggs",
b"\x01\x02\x03",
)
info3 = x509.ProfessionInfo(
x509.NamingAuthority(
x509.ObjectIdentifier("1.2.3"), "https://example.com", "spam"
),
["spam"],
[x509.ObjectIdentifier("1.2.3.4")],
"eggs",
b"\x01\x02\x03",
)
info4 = x509.ProfessionInfo(
x509.NamingAuthority(None, None, None),
[],
[x509.ObjectIdentifier("1.2.3.4")],
"eggs",
b"\x01\x02\x03",
)
info5 = x509.ProfessionInfo(
x509.NamingAuthority(None, None, None),
[],
[],
"eggs",
b"\x01\x02\x03",
)
info6 = x509.ProfessionInfo(
x509.NamingAuthority(None, None, None),
[],
[],
None,
b"\x01\x02\x03",
)
info7 = x509.ProfessionInfo(
x509.NamingAuthority(None, None, None), [], [], None, None
)
assert hash(info1) == hash(info2)
assert hash(info1) != hash(info3)
assert hash(info1) != hash(info4)
assert hash(info1) != hash(info5)
assert hash(info1) != hash(info6)
assert hash(info1) != hash(info7)
def test_all_extension_oid_members_have_names_defined():
for oid in dir(ExtensionOID):
if oid.startswith("__"):