# 24. `example_general_name.py` — Typed GeneralName API
[← Example index](index.md) · [example_general_name.py on Codeberg](https://codeberg.org/abbra/synta/src/branch/main/examples/example_general_name.py)
Bindings: `synta.general_name.DNSName`, `synta.general_name.IPAddress`,
`synta.general_name.RFC822Name`, `synta.general_name.OtherName`,
`synta.general_name.UniformResourceIdentifier`, `synta.parse_general_names`.
- Call `cert.subject_alt_names()` on a certificate with four SANs (two
`dNSName`, one `iPAddress`, one `rfc822Name`); dispatch by `isinstance`.
- Use `cert.general_names(oid)` to retrieve SANs by extension OID.
- Use `synta.parse_general_names(san_der)` for low-level tag/bytes access.
- Demonstrate integer tag constants (`gn.DNS_NAME`, `gn.IP_ADDRESS`, etc.).
## Source
```python
#!/usr/bin/env python3
"""
Example: Typed GeneralName API (synta.general_name).
Demonstrates the typed ``GeneralName`` objects returned by
``Certificate.subject_alt_names()`` and ``Certificate.general_names(oid)``,
plus the low-level ``synta.parse_general_names()`` function and the integer
tag constants in ``synta.general_name``.
The sample certificate has four Subject Alternative Names:
- dNSName "example.com"
- dNSName "www.example.com"
- iPAddress 192.168.1.1 (4 bytes)
- rfc822Name "admin@example.com"
"""
import base64
import ipaddress
import synta
import synta.general_name as gn
# ── Sample certificate DER (self-signed, ECDSA/P-256) ─────────────────────────
#
# Subject / Issuer: CN=example.com, O=Example Corp, C=US
# SAN: dNSName=example.com, dNSName=www.example.com,
# iPAddress=192.168.1.1, rfc822Name=admin@example.com
CERT_DER = base64.b64decode(
"MIIB6DCCAY2gAwIBAgIUEBUQAc/E3r/3vioki6IU6lyfL5IwCgYIKoZIzj0EAwIw"
"OjEUMBIGA1UEAwwLZXhhbXBsZS5jb20xFTATBgNVBAoMDEV4YW1wbGUgQ29ycDEL"
"MAkGA1UEBhMCVVMwHhcNMjUwMTAxMDAwMDAwWhcNMjcwMTAxMDAwMDAwWjA6MRQw"
"EgYDVQQDDAtleGFtcGxlLmNvbTEVMBMGA1UECgwMRXhhbXBsZSBDb3JwMQswCQYD"
"VQQGEwJVUzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABLIerh5Q5OVsw1o/hfHd"
"Hgi3mjz6WDirif1I+JAuF3oUPGa+iyMnngLjCgvIghAxvWNrcqp+/eewofR58P7X"
"g6ujcTBvMEAGA1UdEQQ5MDeCC2V4YW1wbGUuY29tgg93d3cuZXhhbXBsZS5jb22H"
"BMCoAQGBEWFkbWluQGV4YW1wbGUuY29tMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYE"
"FI8ICKdxcww2aHlOya4RvcbKlSVXMAoGCCqGSM49BAMCA0kAMEYCIQDrZBGKzs0V"
"cEQ3uYzh9xNlKOGqZOjxct32A+dWJMlEdAIhAM0zkny+EHOqpQXKYbsOuxJedws6"
"6d3nEZ7+v/kQ8hJP"
)
# Subject Alternative Name OID
SUBJECT_ALT_NAME = "2.5.29.17"
def section(title):
print(f"\n{'─' * 60}\n{title}\n{'─' * 60}")
# ── Section 1: Typed subject_alt_names() ─────────────────────────────────────
def demo_typed_subject_alt_names():
section("Typed subject_alt_names() — RFC 5280 §4.2.1.6")
cert = synta.Certificate.from_der(CERT_DER)
names = cert.subject_alt_names()
# Four SANs in this certificate
assert len(names) == 4, f"expected 4 SANs, got {len(names)}"
for name in names:
if isinstance(name, gn.DNSName):
print(f" dNSName: {name.value}")
assert name.value in ("example.com", "www.example.com")
elif isinstance(name, gn.IPAddress):
addr = ipaddress.ip_address(name.address)
print(f" iPAddress: {addr} ({len(name.address)} bytes)")
assert name.address == b"\xc0\xa8\x01\x01" # 192.168.1.1
elif isinstance(name, gn.RFC822Name):
print(f" rfc822Name: {name.value}")
assert name.value == "admin@example.com"
print(f" total SANs: {len(names)}")
# repr() gives a readable summary for each type
for name in names:
print(f" repr: {repr(name)}")
# ── Section 2: isinstance type-dispatch pattern ───────────────────────────────
def demo_type_dispatch():
section("Type-dispatch pattern — all GeneralName variants")
cert = synta.Certificate.from_der(CERT_DER)
names = cert.subject_alt_names()
print(" Full dispatch loop:")
for name in names:
if isinstance(name, gn.DNSName):
print(f" DNSName → {name.value!r}")
elif isinstance(name, gn.RFC822Name):
print(f" RFC822Name → {name.value!r}")
elif isinstance(name, gn.UniformResourceIdentifier):
print(f" URI → {name.value!r}")
elif isinstance(name, gn.IPAddress):
raw = name.address
if len(raw) == 4:
addr_str = str(ipaddress.IPv4Address(raw))
elif len(raw) == 16:
addr_str = str(ipaddress.IPv6Address(raw))
else:
addr_str = raw.hex()
print(f" IPAddress → {addr_str}")
elif isinstance(name, gn.RegisteredID):
print(f" RegisteredID → {name.oid}")
elif isinstance(name, gn.DirectoryName):
attrs = synta.parse_name_attrs(name.name_der)
print(f" DirectoryName → {attrs}")
elif isinstance(name, gn.OtherName):
print(f" OtherName type_id → {name.type_id}")
print(f" OtherName value → {name.value.hex()}")
elif isinstance(name, gn.X400Address):
print(f" X400Address raw_der → {name.raw_der.hex()}")
elif isinstance(name, gn.EDIPartyName):
print(f" EDIPartyName raw_der → {name.raw_der.hex()}")
else:
print(f" unknown type: {type(name)}")
# ── Section 3: general_names() with explicit OID ──────────────────────────────
def demo_general_names_oid():
section("general_names(oid) — decode any GeneralName extension by OID")
cert = synta.Certificate.from_der(CERT_DER)
# Pass OID as string — same result as subject_alt_names()
names_str_oid = cert.general_names(SUBJECT_ALT_NAME)
assert len(names_str_oid) == 4
# Pass OID as ObjectIdentifier object
oid_obj = synta.ObjectIdentifier(SUBJECT_ALT_NAME)
names_obj_oid = cert.general_names(oid_obj)
assert len(names_obj_oid) == 4
print(f" general_names({SUBJECT_ALT_NAME!r}) → {len(names_str_oid)} entries")
print(f" general_names(ObjectIdentifier(...)) → {len(names_obj_oid)} entries")
# Absent extension → empty list (no exception)
ISSUER_ALT_NAME = "2.5.29.18"
ian = cert.general_names(ISSUER_ALT_NAME)
assert ian == []
print(f" general_names({ISSUER_ALT_NAME!r}) → [] (absent extension)")
# Spot-check the first DNS name
dns_names = [n for n in names_str_oid if isinstance(n, gn.DNSName)]
assert dns_names[0].value == "example.com"
print(f" first dNSName: {dns_names[0].value!r}")
# ── Section 4: Low-level parse_general_names() ───────────────────────────────
def demo_low_level_parse():
section("Low-level synta.parse_general_names() — (tag_int, bytes) tuples")
cert = synta.Certificate.from_der(CERT_DER)
# get_extension_value_der() returns the raw OCTET STRING content of the
# extension — i.e. the DER of the SEQUENCE OF GeneralName
san_der = cert.get_extension_value_der(SUBJECT_ALT_NAME)
assert san_der is not None, "SAN extension absent"
print(f" raw SAN extension DER: {len(san_der)} bytes")
# parse_general_names() decodes that DER and returns (tag_int, value_bytes)
# tuples — the original low-level API
pairs = synta.parse_general_names(san_der)
assert len(pairs) == 4
for tag, value in pairs:
print(f" tag={tag} value={value!r}")
# Verify expected tag numbers
tags = [tag for tag, _ in pairs]
assert tags == [
gn.DNS_NAME, # 2
gn.DNS_NAME, # 2
gn.IP_ADDRESS, # 7
gn.RFC822_NAME, # 1
]
print(" tag order: DNS_NAME, DNS_NAME, IP_ADDRESS, RFC822_NAME ✓")
# Decode an IP address from the raw bytes
ip_pairs = [(t, v) for t, v in pairs if t == gn.IP_ADDRESS]
assert len(ip_pairs) == 1
_, ip_bytes = ip_pairs[0]
addr = ipaddress.ip_address(ip_bytes)
assert str(addr) == "192.168.1.1"
print(f" decoded IP: {addr}")
# ── Section 5: Integer tag constants ─────────────────────────────────────────
def demo_tag_constants():
section("synta.general_name integer tag constants")
constants = [
("OTHER_NAME", gn.OTHER_NAME, 0),
("RFC822_NAME", gn.RFC822_NAME, 1),
("DNS_NAME", gn.DNS_NAME, 2),
("X400_ADDRESS", gn.X400_ADDRESS, 3),
("DIRECTORY_NAME", gn.DIRECTORY_NAME, 4),
("EDI_PARTY_NAME", gn.EDI_PARTY_NAME, 5),
("URI", gn.URI, 6),
("IP_ADDRESS", gn.IP_ADDRESS, 7),
("REGISTERED_ID", gn.REGISTERED_ID, 8),
]
for name, value, expected in constants:
assert value == expected, f"{name}: expected {expected}, got {value}"
print(f" gn.{name} = {value}")
# Using constants in a dispatch loop (alternative to isinstance)
cert = synta.Certificate.from_der(CERT_DER)
san_der = cert.get_extension_value_der(SUBJECT_ALT_NAME)
assert san_der is not None
pairs = synta.parse_general_names(san_der)
print()
print(" Low-level dispatch using tag constants:")
for tag, value in pairs:
if tag == gn.DNS_NAME:
print(f" DNS_NAME → {value.decode()!r}")
elif tag == gn.IP_ADDRESS:
print(f" IP_ADDRESS → {ipaddress.ip_address(value)}")
elif tag == gn.RFC822_NAME:
print(f" RFC822_NAME → {value.decode()!r}")
# ── main ──────────────────────────────────────────────────────────────────────
def main():
print("=" * 60)
print("Example: Typed GeneralName API (synta.general_name)")
print("=" * 60)
demo_typed_subject_alt_names()
demo_type_dispatch()
demo_general_names_oid()
demo_low_level_parse()
demo_tag_constants()
print("\nAll GeneralName examples completed.")
if __name__ == "__main__":
main()
```