x509

March 22, 2026 · View on GitHub

The pyeudiw.x509 module provides trust evaluation using X.509 PKI according to RFC 5280.

Installation

pip install pyeudiw

Verifying an x5c Certificate Chain

Verify a chain of x509 certificates (e.g. from a JWT x5c header):

from pyeudiw.x509.verify import verify_x509_attestation_chain
from pyeudiw.x509.crl_helper import CRLHelper

# x5c: list of base64url-encoded DER certificates
x5c = [b"...", b"...", b"..."]  # bytes, or PEM strings

# Without CRL check
is_valid = verify_x509_attestation_chain(x5c)

# With CRL revocation check
crls = [
    CRLHelper.from_url("https://crl.example.com/ca.crl"),
]
is_valid = verify_x509_attestation_chain(x5c, crls=crls)

Certificate Format Conversions

from pyeudiw.x509.verify import (
    B64DER_cert_to_DER_cert,
    B64DER_cert_to_PEM_cert,
    DER_cert_to_B64DER_cert,
    PEM_cert_to_B64DER_cert,
    PEM_cert_to_DER_cert,
    to_DER_cert,
    to_PEM_cert,
    pem_to_pems_list,
    to_pem_list,
    to_der_list,
)

# Base64 DER <-> DER
der = B64DER_cert_to_DER_cert(base64_der_string)
b64 = DER_cert_to_B64DER_cert(der_bytes)

# PEM <-> DER
pem_str = to_PEM_cert(cert)   # cert can be str or bytes
der_bytes = to_DER_cert(cert)

# Split concatenated PEM into list
pem_list = pem_to_pems_list("-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\n...")

# Convert lists
pem_list = to_pem_list(der_list)
der_list = to_der_list(pem_list)

Extracting Information from x5c

from pyeudiw.x509.verify import (
    get_issuer_from_x5c,
    get_trust_anchor_from_x5c,
    get_expiry_date_from_x5c,
    get_public_key_from_x5c,
    get_x509_info,
)

x5c = [b"...", b"..."]  # Chain: leaf first, root last

issuer = get_issuer_from_x5c(x5c)
trust_anchor = get_trust_anchor_from_x5c(x5c)
expiry = get_expiry_date_from_x5c(x5c)

# Public key from leaf certificate (for JWT verification)
pubkey = get_public_key_from_x5c(x5c)

# Human-readable cert info
info = get_x509_info(x5c[0], san_dns=True)

CRL Helper

from pyeudiw.x509.crl_helper import CRLHelper

# From URL
crl = CRLHelper.from_url("https://crl.example.com/ca.crl")

# From PEM/DER string or bytes
crl = CRLHelper.from_crl(crl_pem_or_der, uri="https://crl.example.com/ca.crl")

# Check if a certificate is revoked (serial_number: int or hex string)
is_revoked = crl.is_revoked(serial_number=12345)

Building x5c-Like Chains

ChainBuilder creates a certificate chain (root CA → intermediate(s) → leaf). Call gen_certificate in that order: root first, leaf last. The resulting chain from get_chain() is returned as leaf first (suitable for x5c).

from cryptography import x509
from pyeudiw.x509.chain_builder import ChainBuilder

chain = ChainBuilder()

# 1. Root CA (add first)
chain.gen_certificate(
    cn="ca.example.com",
    organization_name="Example CA",
    country_name="IT",
    email_address="info@ca.example.com",
    dns="ca.example.com",
    uri="https://ca.example.com",
    ca=True,
    path_length=1,
    crl_distr_point="https://ca.example.com/crl.pem",
)

# 2. Intermediate CA
chain.gen_certificate(
    cn="intermediate.example.com",
    organization_name="Example Intermediate",
    country_name="IT",
    email_address="info@intermediate.example.com",
    dns="intermediate.example.com",
    uri="https://intermediate.example.com",
    ca=True,
    path_length=0,
)

# 3. Leaf (end-entity) certificate
chain.gen_certificate(
    cn="leaf.example.com",
    organization_name="Example Leaf",
    country_name="IT",
    email_address="info@leaf.example.com",
    dns="leaf.example.com",
    uri="https://leaf.example.com/openid4vp",
    ca=False,
    path_length=None,
    private_key=my_private_key,  # optional; generates EC P-256 if omitted
)

# Get chain as DER (for x5c) or PEM
der_chain = chain.get_chain("DER")  # list[bytes], leaf first
pem_chain = chain.get_chain("PEM")  # list[str]
ca_cert = chain.get_ca("DER")       # root CA only

gen_certificate parameters

ParameterRequiredDescription
cnCommon Name
organization_nameOrganization
country_nameCountry code
email_addressEmail
dnsDNS name (SubjectAlternativeName)
uriURI (SubjectAlternativeName)
caTrue for CA, False for leaf
path_lengthint for CA (max depth), None for leaf
private_keyEC or RSA key; auto-generated if omitted
crl_distr_pointCRL distribution URL
not_valid_before / not_valid_afterValidity period (default: ±1 day / +1 year)

Using with JWK

To use x5c keys for JWT verification:

from pyeudiw.jwk.parse import parse_x5c_keys
from pyeudiw.x509.verify import verify_x509_attestation_chain
from pyeudiw.jwt.jws_helper import JWSHelper

# 1. Verify the chain
x5c_bytes = [cert.encode() if isinstance(cert, str) else cert for cert in x5c]
if not verify_x509_attestation_chain(x5c_bytes):
    raise ValueError("Invalid certificate chain")

# 2. Extract keys
keys = parse_x5c_keys(x5c)
verifier = JWSHelper(jwks=[keys[0].as_dict()])
payload = verifier.verify(jws)