Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
connect: publish openid-tadr-1_0-00 initial draft of OpenID Trust Anc…
…hor Distribution & Rotation (TADR) spec

- Introduces Draft 00 of openid-tadr-1_0
- Includes required sections (Abstract, Introduction, Security Considerations, References, Notices, etc.)
- Adds JSON Schema definition for trust anchor bundle
- Adds Appendix C with non-normative reference implementation (server.js, fetch-client.py, README.md)
  • Loading branch information
photon6 committed Oct 17, 2025
commit 8ef96f263a9c791a7b3c4130d022132a1f099dfa
32 changes: 32 additions & 0 deletions connect/examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Trust Anchor Discovery Extension for OIDC Clients

## Overview

This project proposes an OpenID Connect Discovery extension that enables clients to proactively retrieve and validate TLS server certificates—referred to as *trust anchors*—prior to initiating secure sessions. This is especially useful in environments with short-lived certificates, zero-trust architectures, and machine-to-machine communication.

## Motivation

As TLS certificate lifetimes shrink (e.g., 90-day validity becoming standard), clients must adapt to more frequent trust anchor rotations. This extension allows OpenID Connect clients to discover and prefetch server certificates during the discovery phase using the Client Credentials Grant, enabling proactive trust establishment for mTLS or HTTPS sessions.

## Key Features

- Adds a `trust_anchor_uri` field to the OIDC Discovery document.
- Enables clients to fetch and validate server certificates before TLS handshakes.
- Supports caching and rotation of trust anchors.
- Compatible with existing OIDC Discovery and JWKS mechanisms.

## Repository Structure


```
trust-anchor-discovery-oidc/
├── spec.md # The full Markdown spec (from our previous draft)
├── README.md # Project overview and motivation
├── LICENSE # Choose an open license (e.g., Apache 2.0 or MIT)
├── .well-known/
│ └── openid-configuration.json # Sample discovery doc with trust_anchor_uri
├── trust/
│ └── certs.json # Example trust anchor document
└── examples/
└── client-fetch.py # Optional: sample client script to fetch and parse anchors
```
53 changes: 53 additions & 0 deletions connect/examples/client-fetch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import requests
from cryptography import x509
from cryptography.hazmat.backends import default_backend
from cryptography.x509.verification import PolicyBuilder, Store
from cryptography.x509 import DNSName, load_pem_x509_certificates
from datetime import datetime
import certifi

# Config
DISCOVERY_URL = "https://server.example.com/.well-known/openid-configuration"
CLIENT_ID = "your-client-id"
CLIENT_SECRET = "your-client-secret"
TOKEN_URL = "https://server.example.com/token"

# Step 1: Fetch discovery metadata
discovery = requests.get(DISCOVERY_URL).json()
trust_anchor_uri = discovery.get("trust_anchor_uri")
if not trust_anchor_uri:
raise ValueError("trust_anchor_uri not found")

# Step 2: Get access token
token_response = requests.post(
TOKEN_URL,
data={"grant_type": "client_credentials"},
auth=(CLIENT_ID, CLIENT_SECRET),
)
access_token = token_response.json()["access_token"]

# Step 3: Fetch trust anchors
headers = {"Authorization": f"Bearer {access_token}"}
trust_response = requests.get(trust_anchor_uri, headers=headers)
trust_anchors = trust_response.json()

# Step 4: Load trusted root CAs
with open(certifi.where(), "rb") as f:
trusted_certs = load_pem_x509_certificates(f.read(), default_backend())
store = Store(trusted_certs)

# Step 5: Validate each certificate
for cert_entry in trust_anchors.get("certificates", []):
print(f"Validating: {cert_entry['subject']}")
try:
pem_data = cert_entry["pem"].encode()
cert = x509.load_pem_x509_certificate(pem_data, default_backend())

builder = PolicyBuilder().store(store).time(datetime.utcnow())
verifier = builder.build_server_verifier(DNSName("server.example.com"))
chain = verifier.verify(cert, [])

print("✅ Certificate is valid and trusted.")
except Exception as e:
print(f"❌ Validation failed: {e}")
print("-" * 40)
130 changes: 130 additions & 0 deletions connect/examples/server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
const express = require("express");
const https = require("https");
const fs = require("fs");
const path = require("path");
const { X509Certificate, createHash } = require("crypto");
const { execSync } = require("child_process");

const app = express();
const PORT = 3443;

// --- Paths for security assets ---
const securityPath = path.join(__dirname, "lib", "security");

// --- Load server TLS cert and key ---
const serverKey = fs.readFileSync(path.join(securityPath, "server.key"));
const serverCertPem = fs.readFileSync(path.join(securityPath, "server.crt"));

const options = {
key: serverKey,
cert: serverCertPem
};

// --- Helper: build bundle from a PEM cert ---
function buildBundleFromPem(pem) {
const cert = new X509Certificate(pem);
const fingerprint = createHash("sha256")
.update(cert.raw)
.digest("hex")
.toUpperCase()
.match(/.{1,2}/g)
.join(":");

return {
certificates: [
{
subject: cert.subject,
issuer: cert.issuer,
valid_from: cert.validFrom,
valid_to: cert.validTo,
fingerprint: `SHA256:${fingerprint}`,
usage: "tls_server",
pem: pem.toString()
}
]
};
}

// --- Bundle V1: same as server.crt ---
const certBundleV1 = buildBundleFromPem(serverCertPem);

// --- Auto-generate a rotated cert under lib/security ---
function generateSelfSignedCert(filename) {
const pemPath = path.join(securityPath, filename + ".pem");
const keyPath = path.join(securityPath, filename + ".key");

/*
execSync(
`openssl req -x509 -newkey rsa:2048 -nodes -keyout ${keyPath} -out ${pemPath} -days 30 -subj "/CN=localhost"`
);
*/
execSync(
`openssl req -x509 -newkey rsa:2048 -nodes \
-keyout ${keyPath} -out ${pemPath} -days 30 \
-subj "/CN=localhost" \
-addext "subjectAltName=DNS:localhost,IP:127.0.0.1"`
);


return fs.readFileSync(pemPath);
}

const certV2Pem = generateSelfSignedCert("cert-v2");
const certBundleV2 = buildBundleFromPem(certV2Pem);

// --- Function to update trust-bundle.pem ---
function updateTrustBundle() {
const bundlePath = path.join(securityPath, "trust-bundle.pem");
fs.writeFileSync(bundlePath, serverCertPem + "\n" + certV2Pem);
console.log(`Updated trust bundle at ${bundlePath}`);
}

// --- Rotation logic ---
let currentBundle = certBundleV1;
setInterval(() => {
currentBundle = currentBundle === certBundleV1 ? certBundleV2 : certBundleV1;
console.log("Rotated trust anchor bundle");
updateTrustBundle();
}, 60000);

// --- OIDC discovery endpoint ---
app.get("/.well-known/openid-configuration", (req, res) => {
res.json({
issuer: `https://localhost:${PORT}`,
token_endpoint: `https://localhost:${PORT}/token`,
jwks_uri: `https://localhost:${PORT}/.well-known/jwks.json`,
trust_anchor_uri: `https://localhost:${PORT}/trust/certs`,
grant_types_supported: ["client_credentials"],
response_types_supported: ["code"],
subject_types_supported: ["public"],
id_token_signing_alg_values_supported: ["RS256"]
});
});




// --- Mock token endpoint ---
app.post("/token", (req, res) => {
res.json({
access_token: "mock-access-token",
token_type: "Bearer",
expires_in: 3600
});
});

// --- Trust anchor endpoint (rotating) ---
app.get("/trust/certs", (req, res) => {
res.json(currentBundle);
});

// --- JWKS endpoint (stub) ---
app.get("/.well-known/jwks.json", (req, res) => {
res.json({ keys: [] });
});

// --- Start HTTPS server ---
https.createServer(options, app).listen(PORT, () => {
console.log(`HTTPS Trust Anchor Service running at https://localhost:${PORT}`);
updateTrustBundle(); // initialize bundle on startup
});
168 changes: 168 additions & 0 deletions connect/tadr-1_0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
# OpenID Trust Anchor Distribution & Rotation (TADR)
**Draft 00**

**Specification Date:** 2025‑10‑17
**Authors:**
- Rahul Khanna, Independent

---

## 1. Abstract
This specification defines a mechanism for distributing and rotating trust anchors in OpenID Connect deployments. Current OpenID Connect Discovery does not provide a standardized way for clients to obtain and refresh trust anchors, leading to interoperability gaps and outages during certificate rotation. The Trust Anchor Distribution & Rotation (TADR) specification introduces a new discovery parameter and a standardized trust bundle format to enable automated, interoperable, and secure trust anchor lifecycle management.

---

## 2. Introduction
OpenID Connect Discovery ([OpenID.Discovery](https://openid.net/specs/openid-connect-discovery-1_0.html)) provides metadata about an issuer’s endpoints and keys, but does not define how clients should obtain or refresh trust anchors.
This omission forces implementers to rely on static CA bundles or manual distribution, which is brittle in the face of certificate rotation.

The TADR specification addresses this gap by:
- Defining a new `trust_anchor_uri` metadata field in the discovery document.
- Standardizing the format of trust anchor bundles.
- Requiring overlapping validity windows to support seamless rotation.
- Specifying client behavior for fetching, caching, and refreshing anchors.

---

## 3. Requirements and Notation Conventions
The key words **MUST**, **MUST NOT**, **REQUIRED**, **SHALL**, **SHALL NOT**, **SHOULD**, **SHOULD NOT**, **RECOMMENDED**, **NOT RECOMMENDED**, **MAY**, and **OPTIONAL** in this document are to be interpreted as described in [RFC 2119] and [RFC 8174].

---

## 4. Terminology
- **Trust Anchor**: A self‑signed or root certificate used to validate a certificate chain.
- **Trust Bundle**: A collection of one or more trust anchors published by an issuer.
- **Rotation Window**: The period during which both the current and next trust anchors are valid.

---

## 5. Specification

### 5.1 Discovery Metadata
Issuers supporting TADR MUST include the following field in their discovery document:

```json
{
"trust_anchor_uri": "https://issuer.example.org/trust/certs"
}
```

### 5.2 Trust Anchor Bundle Format
The trust anchor bundle is a JSON object containing one or more certificates:

```json
{
"certificates": [
{
"subject": "CN=issuer.example.org",
"issuer": "CN=issuer.example.org",
"valid_from": "2025-10-01T00:00:00Z",
"valid_to": "2026-10-01T00:00:00Z",
"fingerprint": "SHA256:AB:CD:...",
"usage": "tls_server",
"pem": "-----BEGIN CERTIFICATE-----..."
}
]
}
```

### 5.3 JSON Schema Definition
```json
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Trust Anchor Bundle",
"type": "object",
"properties": {
"certificates": {
"type": "array",
"items": {
"type": "object",
"properties": {
"subject": { "type": "string" },
"issuer": { "type": "string" },
"valid_from": { "type": "string", "format": "date-time" },
"valid_to": { "type": "string", "format": "date-time" },
"fingerprint": { "type": "string" },
"usage": { "type": "string", "enum": ["tls_server", "tls_client", "ca"] },
"pem": { "type": "string" }
},
"required": ["subject", "issuer", "valid_from", "valid_to", "fingerprint", "pem"]
}
}
},
"required": ["certificates"]
}
```

### 5.4 Client Behavior
- Clients **MUST** fetch the trust anchor bundle at bootstrap.
- Clients **SHOULD** refresh the bundle periodically (e.g., every 24 hours).
- Clients **MUST** retry with a refreshed bundle if a TLS validation error occurs.
- Clients **MUST** support overlapping anchors during rotation.

---

## 6. Security Considerations
- Trust anchor bundles **MUST** be served over HTTPS.
- Issuers **SHOULD** sign trust bundles to prevent tampering.
- Clients **MUST** validate certificate fingerprints and validity periods.
- Rotation windows **MUST** overlap to prevent outages.

---

## 7. Privacy Considerations
- Fetching trust anchors does not reveal user‑specific data.
- Issuers **SHOULD** avoid embedding identifying information in trust bundle metadata.

---

## 8. Implementation Considerations
- Issuers may generate bundles dynamically or pre‑publish them.
- Clients may cache bundles locally and refresh on schedule.
- Bundles may be distributed via CDNs for scalability.

---

## 9. IANA Considerations
This specification makes no requests of IANA.

---

## 10. References

### 10.1 Normative References
- [RFC 2119] Key words for use in RFCs to Indicate Requirement Levels.
- [RFC 8174] Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words.
- [RFC 5280] Internet X.509 Public Key Infrastructure Certificate and CRL Profile.
- [OpenID.Discovery] OpenID Connect Discovery 1.0, <https://openid.net/specs/openid-connect-discovery-1_0.html>

### 10.2 Informative References
- [RFC 5914] Trust Anchor Format.
- [CAB Forum Ballots] TLS Certificate Lifetime Reductions.

---

## Appendix A. Notices
Copyright (c) 2025 The OpenID Foundation.
The OpenID Intellectual Property Rights Policy governs this specification.

---

## Appendix B. Acknowledgements
The authors thank members of the OpenID Foundation AB Working Group and the PKI/IAM community for their input and feedback.

---

## Appendix C. Non‑Normative Reference Implementation
A reference implementation of TADR is available in the working group repository under `connect/examples/tadr/`.
This includes:
- **server.js**: A Node.js service that generates and rotates trust anchors, publishes a trust bundle, and exposes OIDC discovery metadata.
- **fetch-client.py**: A Python client that consumes the trust bundle, validates anchors, and demonstrates self‑healing behavior during rotation.
- **README.md**: Instructions for running the demo, including setup, execution, and expected behavior.

These materials are **non‑normative** and provided solely for illustration. They are not required for conformance with this specification.

---

## Appendix D. History
- **Draft 00 (2025‑10‑17)**: Initial draft submitted for discussion.