# X.509 Extension Value Builders
`synta.ext` provides DER-encoding helpers for the most common X.509 v3 extension values.
Each function returns the raw `extnValue` bytes (the content inside the OCTET STRING wrapper
in the `Extension` SEQUENCE). Import with `import synta.ext as ext`.
`subject_key_identifier` and `authority_key_identifier` require the `openssl` Cargo feature.
`basic_constraints`, `key_usage`, and the eight builder classes do not.
## Key identifier method constants
Pass one of these to `subject_key_identifier` or `authority_key_identifier`:
| `KEYID_RFC5280` | 0 | SHA-1 | BIT STRING value of `subjectPublicKey` | 20 bytes | RFC 5280 §4.2.1.2 |
| `KEYID_RFC7093M1` | 1 | SHA-256 | BIT STRING value of `subjectPublicKey` | 20 bytes (truncated) | RFC 7093 §2 m1 |
| `KEYID_RFC7093M2` | 2 | SHA-384 | BIT STRING value of `subjectPublicKey` | 20 bytes (truncated) | RFC 7093 §2 m2 |
| `KEYID_RFC7093M3` | 3 | SHA-512 | BIT STRING value of `subjectPublicKey` | 20 bytes (truncated) | RFC 7093 §2 m3 |
| `KEYID_RFC7093M4` | 4 | SHA-256 | full `SubjectPublicKeyInfo` DER | 32 bytes | RFC 7093 §2 m4 |
## Key usage bitmask constants
OR these together and pass the result to `key_usage()`:
| `KU_DIGITAL_SIGNATURE` | `0x001` | `digitalSignature` |
| `KU_NON_REPUDIATION` | `0x002` | `contentCommitment` |
| `KU_KEY_ENCIPHERMENT` | `0x004` | `keyEncipherment` |
| `KU_DATA_ENCIPHERMENT` | `0x008` | `dataEncipherment` |
| `KU_KEY_AGREEMENT` | `0x010` | `keyAgreement` |
| `KU_KEY_CERT_SIGN` | `0x020` | `keyCertSign` |
| `KU_CRL_SIGN` | `0x040` | `cRLSign` |
| `KU_ENCIPHER_ONLY` | `0x080` | `encipherOnly` |
| `KU_DECIPHER_ONLY` | `0x100` | `decipherOnly` |
## Functions
```python
Encode a BasicConstraints extension value (OID 2.5.29.19). Returns the DER SEQUENCE bytes.
When `ca` is `False` the `cA` field is omitted (DER DEFAULT-FALSE rule). `path_length` is
ignored when `ca` is `False`.
```python
def key_usage(bits: int) -> bytes: ...
```
Encode a KeyUsage extension value (OID 2.5.29.15). `bits` is an integer formed by OR-ing
`KU_*` constants. Returns the DER BIT STRING bytes.
```python
def subject_key_identifier(spki_der: bytes, method: int = KEYID_RFC5280) -> bytes: ...
```
Encode a SubjectKeyIdentifier extension value (OID 2.5.29.14). `spki_der` must be a complete
DER-encoded SubjectPublicKeyInfo. `method` selects the key-identifier algorithm. Returns the
DER OCTET STRING value. Raises `ValueError` if `spki_der` cannot be decoded.
```python
def authority_key_identifier(issuer_spki_der: bytes, method: int = KEYID_RFC5280) -> bytes: ...
```
Encode an AuthorityKeyIdentifier extension value (OID 2.5.29.35). Sets only the
`keyIdentifier [0]` field. Returns the DER AuthorityKeyIdentifier SEQUENCE bytes.
## Fluent builder classes
Eight builder classes accumulate entries and produce the complete DER `extnValue` on
`.build()`. No `openssl` Cargo feature is required.
| `SubjectAlternativeNameBuilder` | `SAN` | Subject Alternative Name | 2.5.29.17 | RFC 5280 §4.2.1.6 |
| `AuthorityInformationAccessBuilder` | `AIA` | Authority Information Access | 1.3.6.1.5.5.7.1.1 | RFC 5280 §4.2.2.1 |
| `ExtendedKeyUsageBuilder` | `EKU` | Extended Key Usage | 2.5.29.37 | RFC 5280 §4.2.1.12 |
| `NameConstraintsBuilder` | `NC` | Name Constraints | 2.5.29.30 | RFC 5280 §4.2.1.10 |
| `CRLDistributionPointsBuilder` | `CDP` | CRL Distribution Points | 2.5.29.31 | RFC 5280 §4.2.1.13 |
| `IssuerAlternativeNameBuilder` | `IAN` | Issuer Alternative Name | 2.5.29.18 | RFC 5280 §4.2.1.8 |
| `IssuingDistributionPointBuilder` | `IDP` | Issuing Distribution Point | 2.5.29.28 | RFC 5280 §5.2.5 |
| `CertificatePoliciesBuilder` | `CP` | Certificate Policies | 2.5.29.32 | RFC 5280 §4.2.1.4 |
### SubjectAlternativeNameBuilder (alias: SAN)
```python
class SubjectAlternativeNameBuilder:
def __init__(self) -> None: ...
def dns_name(self, name: str) -> SubjectAlternativeNameBuilder: ...
def rfc822_name(self, email: str) -> SubjectAlternativeNameBuilder: ...
def uri(self, uri: str) -> SubjectAlternativeNameBuilder: ...
def ip_address(self, addr: bytes) -> SubjectAlternativeNameBuilder: ...
# Pass 4 bytes for IPv4 or 16 bytes for IPv6.
def directory_name(self, name_der: bytes) -> SubjectAlternativeNameBuilder: ...
# name_der is the DER TLV of a Name SEQUENCE.
def registered_id(self, oid_comps: list[int]) -> SubjectAlternativeNameBuilder: ...
def other_name(self, other_name_der: bytes) -> SubjectAlternativeNameBuilder: ...
# other_name_der is the complete DER TLV of an OtherName SEQUENCE.
def build(self) -> bytes: ...
# Return the DER GeneralNames SEQUENCE. Raises ValueError on encoding error.
SAN = SubjectAlternativeNameBuilder # short alias
```
### AuthorityInformationAccessBuilder (alias: AIA)
```python
class AuthorityInformationAccessBuilder:
def __init__(self) -> None: ...
def ocsp(self, uri: str) -> AuthorityInformationAccessBuilder: ...
def ca_issuers(self, uri: str) -> AuthorityInformationAccessBuilder: ...
def build(self) -> bytes: ...
AIA = AuthorityInformationAccessBuilder # short alias
```
### ExtendedKeyUsageBuilder (alias: EKU)
```python
class ExtendedKeyUsageBuilder:
def __init__(self) -> None: ...
def server_auth(self) -> ExtendedKeyUsageBuilder: ... # id-kp-serverAuth
def client_auth(self) -> ExtendedKeyUsageBuilder: ... # id-kp-clientAuth
def code_signing(self) -> ExtendedKeyUsageBuilder: ... # id-kp-codeSigning
def email_protection(self) -> ExtendedKeyUsageBuilder: ... # id-kp-emailProtection
def time_stamping(self) -> ExtendedKeyUsageBuilder: ... # id-kp-timeStamping
def ocsp_signing(self) -> ExtendedKeyUsageBuilder: ... # id-kp-OCSPSigning
def add_oid(self, oid: str | ObjectIdentifier) -> ExtendedKeyUsageBuilder: ...
def build(self) -> bytes: ...
EKU = ExtendedKeyUsageBuilder # short alias
```
### NameConstraintsBuilder (alias: NC)
Builds a `NameConstraints` extension value (OID 2.5.29.30, RFC 5280 §4.2.1.10).
When present in a CA certificate this extension **must** be marked critical.
**DNS name conventions:** a leading dot (e.g. `".example.com"`) constrains the entire
subtree rooted at `example.com`; without the dot, only that exact host name is constrained.
**IP address constraints:** pass address bytes concatenated with mask bytes — 8 bytes for
IPv4 (`address[4] + mask[4]`) or 32 bytes for IPv6 (`address[16] + mask[16]`).
```python
class NameConstraintsBuilder:
def __init__(self) -> None: ...
# Permitted subtrees — names that certificates issued by this CA MAY contain.
def permit_dns(self, dns: str) -> NameConstraintsBuilder: ...
def permit_rfc822(self, email: str) -> NameConstraintsBuilder: ...
def permit_ip(self, addr_and_mask: bytes) -> NameConstraintsBuilder: ...
# 8 bytes for IPv4 (address + mask), 32 bytes for IPv6.
def permit_uri(self, uri: str) -> NameConstraintsBuilder: ...
def permit_directory_name(self, name_der: bytes) -> NameConstraintsBuilder: ...
# Excluded subtrees — names that certificates issued by this CA MUST NOT contain.
def exclude_dns(self, dns: str) -> NameConstraintsBuilder: ...
def exclude_rfc822(self, email: str) -> NameConstraintsBuilder: ...
def exclude_ip(self, addr_and_mask: bytes) -> NameConstraintsBuilder: ...
def exclude_uri(self, uri: str) -> NameConstraintsBuilder: ...
def exclude_directory_name(self, name_der: bytes) -> NameConstraintsBuilder: ...
def build(self) -> bytes: ...
# Return the DER NameConstraints SEQUENCE. Raises ValueError on encoding error.
NC = NameConstraintsBuilder # short alias
```
### CRLDistributionPointsBuilder (alias: CDP)
Builds a `CRLDistributionPoints` extension value (OID 2.5.29.31, RFC 5280 §4.2.1.13).
Each call to a `full_name_*` method adds one distribution point.
```python
class CRLDistributionPointsBuilder:
def __init__(self) -> None: ...
def full_name_uri(self, uri: str) -> CRLDistributionPointsBuilder: ...
def full_name_dns(self, dns: str) -> CRLDistributionPointsBuilder: ...
def full_name_directory(self, name_der: bytes) -> CRLDistributionPointsBuilder: ...
# name_der is the DER TLV of a Name SEQUENCE.
def build(self) -> bytes: ...
# Returns DER CRLDistributionPoints SEQUENCE OF. Raises ValueError on error.
CDP = CRLDistributionPointsBuilder # short alias
```
### IssuerAlternativeNameBuilder (alias: IAN)
Builds an `IssuerAltName` extension value (OID 2.5.29.18, RFC 5280 §4.2.1.7).
Encoding is identical to `SubjectAlternativeNameBuilder` but with the IAN OID.
`otherName` is not supported by this builder.
```python
class IssuerAlternativeNameBuilder:
def __init__(self) -> None: ...
def dns_name(self, name: str) -> IssuerAlternativeNameBuilder: ...
def rfc822_name(self, email: str) -> IssuerAlternativeNameBuilder: ...
def uri(self, uri: str) -> IssuerAlternativeNameBuilder: ...
def ip_address(self, addr: bytes) -> IssuerAlternativeNameBuilder: ...
def directory_name(self, name_der: bytes) -> IssuerAlternativeNameBuilder: ...
def registered_id(self, oid_comps: list[int]) -> IssuerAlternativeNameBuilder: ...
def build(self) -> bytes: ...
IAN = IssuerAlternativeNameBuilder # short alias
```
### IssuingDistributionPointBuilder (alias: IDP)
Builds an `IssuingDistributionPoint` extension value (OID 2.5.29.28, RFC 5280 §5.2.5).
Used in CRL extensions to identify the scope and distribution point of a particular CRL.
```python
class IssuingDistributionPointBuilder:
def __init__(self) -> None: ...
def full_name_uri(self, uri: str) -> IssuingDistributionPointBuilder: ...
def full_name_dns(self, dns: str) -> IssuingDistributionPointBuilder: ...
def only_contains_user_certs(self, val: bool) -> IssuingDistributionPointBuilder: ...
def only_contains_cacerts(self, val: bool) -> IssuingDistributionPointBuilder: ...
def indirect_crl(self, val: bool) -> IssuingDistributionPointBuilder: ...
def only_contains_attribute_certs(self, val: bool) -> IssuingDistributionPointBuilder: ...
def build(self) -> bytes: ...
# Returns DER IssuingDistributionPoint SEQUENCE. Raises ValueError on error.
IDP = IssuingDistributionPointBuilder # short alias
```
### CertificatePoliciesBuilder (alias: CP)
Builds a `CertificatePolicies` extension value (OID 2.5.29.32, RFC 5280 §4.2.1.4).
Each policy entry may optionally carry a CPS URI qualifier.
```python
class CertificatePoliciesBuilder:
def __init__(self) -> None: ...
def add_policy(self, oid_comps: list[int]) -> CertificatePoliciesBuilder: ...
# Add a policy with no qualifiers. oid_comps: e.g. [2, 23, 140, 1, 2, 1].
def add_policy_cps(self, oid_comps: list[int], cps_uri: str) -> CertificatePoliciesBuilder: ...
# Add a policy with an id-qt-cps (1.3.6.1.5.5.7.2.1) URI qualifier.
def build(self) -> bytes: ...
# Returns DER CertificatePolicies SEQUENCE OF. Raises ValueError on error.
CP = CertificatePoliciesBuilder # short alias
```
Usage example:
```python,ignore
import synta.ext as ext
cdp = ext.CDP() \
.full_name_uri("http://crl.example.com/ca.crl") \
.build()
cp = ext.CP() \
.add_policy([2, 23, 140, 1, 2, 1]) \
.add_policy_cps([2, 23, 140, 1, 2, 2], "https://example.com/cps") \
.build()
```
Usage example:
```python,ignore
import synta
import synta.ext as ext
import synta.oids as oids
# Build the NameConstraints value.
nc_der = (
ext.NC()
.permit_dns(".example.com")
.permit_rfc822(".example.com")
.permit_ip(b"\xc0\xa8\x00\x00\xff\xff\x00\x00") # 192.168.0.0/16
.exclude_dns(".legacy.example.com")
.build()
)
# Embed in a CA certificate as a critical extension (OID 2.5.29.30).
ca_cert = (
synta.CertificateBuilder()
# ... issuer_name, subject_name, public_key, serial_number, validity ...
.add_extension("2.5.29.30", True, nc_der) # critical=True required by RFC 5280
.sign(ca_key, "sha256")
)
# Read it back by OID.
nc_back = ca_cert.get_extension_value_der(oids.NAME_CONSTRAINTS)
assert nc_back == nc_der
```
### CRLDistributionPointsBuilder (alias: CDP)
Builds a `CRLDistributionPoints` extension value (OID 2.5.29.31, RFC 5280 §4.2.1.13).
Each call to a `full_name_*` method appends one `DistributionPoint` entry to the
SEQUENCE OF. Multiple entries are valid and represent alternative CRL retrieval locations.
The `reasons` and `cRLIssuer` fields are not supported by this builder; omitting them
produces a standards-compliant value for the common case.
```python
class CRLDistributionPointsBuilder:
def __init__(self) -> None: ...
def full_name_uri(self, uri: str) -> CRLDistributionPointsBuilder: ...
# Add a DistributionPoint whose fullName is a uniformResourceIdentifier.
def full_name_dns(self, dns: str) -> CRLDistributionPointsBuilder: ...
# Add a DistributionPoint whose fullName is a dNSName.
def full_name_directory(self, name_der: bytes) -> CRLDistributionPointsBuilder: ...
# Add a DistributionPoint whose fullName is a directoryName (DER-encoded Name SEQUENCE).
def build(self) -> bytes: ...
# Return the DER CRLDistributionPoints SEQUENCE OF. Raises ValueError on encoding error.
CDP = CRLDistributionPointsBuilder # short alias
```
### IssuerAlternativeNameBuilder (alias: IAN)
Builds an `IssuerAltName` extension value (OID 2.5.29.18, RFC 5280 §4.2.1.8).
The DER structure is identical to `SubjectAltName` — a `GeneralNames` SEQUENCE — but
the OID identifies it as the issuer's alternative names rather than the subject's.
**Limitation:** `otherName` is not supported by this builder. If the issuer identity
requires an `otherName` entry (e.g. a Kerberos principal name), use
`SubjectAlternativeNameBuilder` to construct the raw `GeneralNames` bytes and embed them
manually under OID 2.5.29.18.
```python
class IssuerAlternativeNameBuilder:
def __init__(self) -> None: ...
def dns_name(self, name: str) -> IssuerAlternativeNameBuilder: ...
def rfc822_name(self, email: str) -> IssuerAlternativeNameBuilder: ...
def uri(self, uri: str) -> IssuerAlternativeNameBuilder: ...
def ip_address(self, addr: bytes) -> IssuerAlternativeNameBuilder: ...
# Pass 4 bytes for IPv4 or 16 bytes for IPv6.
def directory_name(self, name_der: bytes) -> IssuerAlternativeNameBuilder: ...
# name_der is the DER TLV of a Name SEQUENCE.
def registered_id(self, oid_comps: list[int]) -> IssuerAlternativeNameBuilder: ...
# otherName is NOT supported by this builder.
def build(self) -> bytes: ...
# Return the DER GeneralNames SEQUENCE. Raises ValueError on encoding error.
IAN = IssuerAlternativeNameBuilder # short alias
```
### IssuingDistributionPointBuilder (alias: IDP)
Builds an `IssuingDistributionPoint` extension value (OID 2.5.29.28, RFC 5280 §5.2.5).
This extension appears in CRL (Certificate Revocation List) structures, not in end-entity
or CA certificates. It identifies the CRL distribution point and limits the scope of the
CRL (e.g. user certs only, CA certs only, or indirect CRL).
**This extension must always be marked critical** when present in a CRL
(RFC 5280 §5.2.5). All boolean flags default to `False` (omitted in DER).
```python
class IssuingDistributionPointBuilder:
def __init__(self) -> None: ...
def full_name_uri(self, uri: str) -> IssuingDistributionPointBuilder: ...
# Set the distribution point to a URI uniformResourceIdentifier full name.
def full_name_dns(self, dns: str) -> IssuingDistributionPointBuilder: ...
# Set the distribution point to a dNSName full name.
def only_contains_user_certs(self, val: bool) -> IssuingDistributionPointBuilder: ...
# When True, the CRL only contains end-entity certificate revocations.
def only_contains_cacerts(self, val: bool) -> IssuingDistributionPointBuilder: ...
# When True, the CRL only contains CA certificate revocations.
def indirect_crl(self, val: bool) -> IssuingDistributionPointBuilder: ...
# When True, the CRL may contain revocations for certificates issued by other CAs.
def only_contains_attribute_certs(self, val: bool) -> IssuingDistributionPointBuilder: ...
# When True, the CRL only contains attribute certificate revocations.
def build(self) -> bytes: ...
# Return the DER IssuingDistributionPoint SEQUENCE. Raises ValueError on encoding error.
IDP = IssuingDistributionPointBuilder # short alias
```
### CertificatePoliciesBuilder (alias: CP)
Builds a `CertificatePolicies` extension value (OID 2.5.29.32, RFC 5280 §4.2.1.4).
Each `add_policy` or `add_policy_cps` call appends one `PolicyInformation` entry to
the SEQUENCE OF. Only `id-qt-cps` (CPS URI) qualifiers are supported; `id-qt-unotice`
qualifiers must be added by hand if required.
Policy OIDs are passed as integer arc component lists — e.g. `[2, 23, 140, 1, 2, 1]`
for the CA/Browser Forum `baseline-requirements-domain-validated` policy OID
`2.23.140.1.2.1`. The CPS URI must be ASCII-only.
```python
class CertificatePoliciesBuilder:
def __init__(self) -> None: ...
def add_policy(self, oid_comps: list[int]) -> CertificatePoliciesBuilder: ...
# Add a PolicyInformation with no qualifiers.
def add_policy_cps(self, oid_comps: list[int], cps_uri: str) -> CertificatePoliciesBuilder: ...
# Add a PolicyInformation with a CPS URI qualifier (id-qt-cps, 1.3.6.1.5.5.7.2.1).
# cps_uri must be ASCII.
def build(self) -> bytes: ...
# Return the DER CertificatePolicies SEQUENCE OF. Raises ValueError on encoding error.
CP = CertificatePoliciesBuilder # short alias
```
## Usage
```python,ignore
import synta
import synta.ext as ext
# Parse a CA certificate to extract its SubjectPublicKeyInfo DER
ca_der = open("ca.der", "rb").read()
ca_cert = synta.Certificate.from_der(ca_der)
spki_der = ca_cert.subject_public_key_info_der
# ── BasicConstraints (OID 2.5.29.19) ──────────────────────────────────────────
# End-entity certificate: empty SEQUENCE → 30 00
ee_bc = ext.basic_constraints()
# CA with no path-length limit: cA = TRUE → 30 03 01 01 ff
ca_bc = ext.basic_constraints(ca=True)
# CA with pathLen = 0 → 30 06 01 01 ff 02 01 00
ca_pl0 = ext.basic_constraints(ca=True, path_length=0)
# ── KeyUsage (OID 2.5.29.15) ──────────────────────────────────────────────────
# Typical CA key usage: keyCertSign | cRLSign | digitalSignature
# TLS server key usage: digitalSignature | keyEncipherment
# ── SubjectKeyIdentifier (OID 2.5.29.14) ──────────────────────────────────────
# RFC 5280 default: SHA-1 of the BIT STRING value of subjectPublicKey
ski = ext.subject_key_identifier(spki_der)
# RFC 7093 method 1: SHA-256 of BIT STRING value, truncated to 160 bits
ski_m1 = ext.subject_key_identifier(spki_der, method=ext.KEYID_RFC7093M1)
# RFC 7093 method 4: SHA-256 of the full SubjectPublicKeyInfo DER
ski_m4 = ext.subject_key_identifier(spki_der, method=ext.KEYID_RFC7093M4)
# ── AuthorityKeyIdentifier (OID 2.5.29.35) ────────────────────────────────────
aki = ext.authority_key_identifier(spki_der)
aki_m1 = ext.authority_key_identifier(spki_der, method=ext.KEYID_RFC7093M1)
# ── Fluent builders ───────────────────────────────────────────────────────────
san = ext.SAN().dns_name("www.example.com").dns_name("example.com").build()
aia = ext.AIA().ocsp("http://ocsp.example.com").ca_issuers("http://ca.example.com/ca.crt").build()
eku = ext.EKU().server_auth().client_auth().build()
cdp = ext.CDP().full_name_uri("http://crl.example.com/ca.crl").build()
ian = ext.IAN().rfc822_name("ca@example.com").build()
idp = ext.IDP().full_name_uri("http://crl.example.com/delta.crl").only_contains_user_certs(True).build()
cp = ext.CP().add_policy([2, 23, 140, 1, 2, 1]).build()
# ── NameConstraintsBuilder (OID 2.5.29.30) ───────────────────────────────────
nc = ext.NC() \
.permit_dns(".example.com") \
.permit_rfc822(".example.com") \
.permit_ip(b"\xc0\xa8\x00\x00\xff\xff\x00\x00") \
.exclude_dns(".legacy.example.com") \
.build()
# ── CRLDistributionPointsBuilder (OID 2.5.29.31) ─────────────────────────────
# Single HTTP CRL endpoint — the common case for publicly-trusted CAs.
cdp = ext.CDP() \
.full_name_uri("http://crl.example.com/ca.crl") \
.build()
# Two endpoints: HTTP primary, LDAP fallback.
cdp_dual = (
ext.CDP()
.full_name_uri("http://crl.example.com/ca.crl")
.full_name_uri("ldap://ldap.example.com/cn=CA,dc=example,dc=com?certificateRevocationList")
.build()
)
# ── IssuerAlternativeNameBuilder (OID 2.5.29.18) ─────────────────────────────
# Used in CRL or subordinate CA certificate to identify the issuing CA.
# Note: otherName is not supported; use add_extension() with raw bytes if needed.
ian = ext.IAN() \
.rfc822_name("ca@example.com") \
.uri("http://www.example.com") \
.build()
# ── IssuingDistributionPointBuilder (OID 2.5.29.28) ──────────────────────────
# CRL extension — must be critical=True when embedding in a CRL.
idp = ext.IDP() \
.full_name_uri("http://crl.example.com/ca.crl") \
.only_contains_user_certs(True) \
.build()
# ── CertificatePoliciesBuilder (OID 2.5.29.32) ───────────────────────────────
# CA/Browser Forum DV and OV policies with CPS pointers.
cp = (
ext.CP()
.add_policy([2, 23, 140, 1, 2, 1]) # 2.23.140.1.2.1 — BR DV
.add_policy_cps(
[2, 23, 140, 1, 2, 2], # 2.23.140.1.2.2 — BR OV
"https://www.example.com/cps",
)
.build()
)
```
See also [CRL and OCSP Builders](crl-ocsp-builders.md) for building complete CRL and OCSP
response structures.