DEV Community

Cover image for The Post-Quantum Transition, Protocol by Protocol: Your Pipelines and Your Agents
Andrew Stevens for Sakura Sky

Posted on Originally published at sakurasky.com

The Post-Quantum Transition, Protocol by Protocol: Your Pipelines and Your Agents

Almost every connection and credential in your data platform and your agents rests on RSA or elliptic-curve cryptography. On 27 October 2026 NIST published the final version of its plan for retiring both. All quantum-vulnerable signature and key-establishment algorithms are deprecated after 2030 and disallowed under NIST standards and guidelines after 2035 (NIST, 2026).

In NIST's terms, deprecated means you may still use an algorithm but you carry some security risk in doing so; disallowed means you may no longer use it for that purpose (NIST, 2026). The 2030 step is the part that changed. According to a comparison of the two versions, the November 2024 draft deprecated only 112-bit parameter sets after 2030, such as RSA-2048 and P-224. Signatures and key establishment at 128 bits or more (P-256, P-384, and RSA-3072, plus Ed25519 for signatures) had no deprecation step before 2035 (Ivezic, 2026). The final report deprecates them at every strength. If your plans were built on the draft, most of what you run will count as deprecated from 2030 instead of going straight to disallowed in 2035. The 2035 date stays where it was.

The dates bind US federal systems directly. Federal high-value assets have tighter targets still: post-quantum key establishment by 31 December 2030 and post-quantum signatures by 31 December 2031 (NIST, 2026). For everyone else, NIST's dates tend to arrive indirectly, through FIPS 140 validation, procurement, and auditors. US bank supervisors have issued guidance on the transition without naming a year (Meghji, 2026). In August 2026 the Treasury set up a Quantum-Readiness Task Force for the financial sector, and one of its workstreams is third-party and vendor readiness (Orrick, 2026).

The EU's coordinated roadmap sets comparable dates. It says quantum-vulnerable public-key mechanisms shall not be used on their own for high-risk use cases after the end of 2030, with medium-risk use cases following by 2035. It also names the banking system among the services that cryptography secures (NIS Cooperation Group, 2025). Financial entities in scope of DORA, banks included, already have a related obligation: the ICT risk management standard requires a cryptographic controls policy that provides for changing cryptographic technology as cryptanalysis develops, and a record of the mitigations adopted when that isn't possible (European Commission, 2024).

The rest of this post takes a data platform and an AI agent on Google Cloud, the same kind of setup as The Pipeline as Attack Surface and Agent Security in Practice, and asks two questions of each place public-key cryptography appears: what is exposed, and who has to move first. On the identity side I've kept to protocols (TLS, OAuth, OpenID Connect, JWT, SAML, X.509, SPIFFE, and WebAuthn). Whichever identity providers you run, these are what carry their signatures.

What public-key cryptography does in your platform

Public-key cryptography does three jobs in a platform like this, and a quantum computer threatens them on different timelines.

Key establishment agrees the session keys that encrypt traffic: the TLS handshake, an SSH session to a partner's SFTP server, or an IPsec tunnel. Anyone who records that traffic today can keep it and decrypt it once a large enough quantum computer exists. NIST names this "harvest now, decrypt later" threat as one of the main reasons the transition is urgent, and it calls TLS an early priority (NIST, 2026).

Encryption to a public key protects data for someone to open later. PGP-encrypted partner files, JWE payloads, and keys wrapped with RSA all fall here. The exposure is the same as for key establishment and often worse, since the ciphertext is already sitting in storage.

Signatures prove who issued a certificate, a token, an assertion, or a software artefact. Here the risk sits with the signing key, whatever becomes of the tokens it signed. Its public half is published in an identity provider's JWKS, a SAML metadata file, or a certificate. Once a capable quantum computer exists, an attacker can derive the private half from that public key and issue new tokens or certificates, and any verifier that still trusts the key will accept them. Your exposure ends on the day your last verifier stops accepting RSA and elliptic-curve keys, and that day has to come before a capable quantum computer does. Some keys carry a second problem as well. Certificate authority roots, update and firmware signing keys, and the keys behind evidence you keep are slow to replace, and what they sign has to stay verifiable for years.

The first two jobs are on the shorter clock, because the recording may already be happening. Figure 1 puts the three in order.

Figure 1. A decision flow for each use of RSA, ECDH, ECDSA, or EdDSA, noting that a TLS connection has two uses, the key exchange and the certificate. If the use protects confidentiality, as with TLS, SSH, IPsec, PGP files, JWE, or key wrapping, ask whether the traffic or stored ciphertext would still matter in ten years. If yes, move first, with hybrid ML-KEM tested through every middlebox. If no, move next with library upgrades, without waiting for 2030. If the use is a signature, ask whether it is slow to replace or whether what it signed must stay verifiable for years. If yes, as with roots, update and firmware signing, and kept evidence, start now with parallel ML-DSA or SLH-DSA roots and signing keys, and renew timestamps on kept evidence. If no, as with token-signing keys and leaf certificates, verifiers accept ML-DSA by explicit policy, then drop RSA and elliptic-curve keys per issuer, or per client for OpenID Connect. Every path ends by recording the item in the inventory with an owner and a date.

The ten-year question comes from the EU roadmap, which treats a use case as high-risk when compromising its confidentiality after ten years or more would still cause significant damage (NIS Cooperation Group, 2025). Treat it as a way to rank the work. It says nothing about when a capable quantum computer will arrive, and data that matters for less than ten years is still exposed if one arrives sooner. That's why the "No" branch says move next, close behind the first group. For customer data with long retention periods, I'd expect much of the pipeline to land in the first group.

Symmetric cryptography is mostly out of scope. NIST considers its symmetric algorithms at 128 bits of security or more, such as AES-256 and SHA-256, far less exposed to known quantum attacks, and doesn't expect to replace them in this transition (NIST, 2026). Data at rest under AES-256, including customer-managed encryption keys that are themselves symmetric, isn't where the work is.

Pass 1: key establishment and encryption

Most of the platform's traffic is TLS, and a TLS connection has two ends. RFC 10024 defines three hybrid groups for TLS 1.3 that pair ML-KEM with elliptic-curve Diffie-Hellman (Kwiatkowski et al., 2026). X25519MLKEM768 is the one Google's load balancers support (Google Cloud, 2026a), and it's the default in the client libraries below. If you run under FIPS 140, check whether your validated module offers the hybrid group in FIPS mode. Also check whether your policy needs both shares to come from approved mechanisms; if it does, RFC 10024 provides SecP256r1MLKEM768 for that case (Kwiatkowski et al., 2026). A connection only gets a hybrid group when both ends offer one, so for each leg it helps to know which end is yours.

Connection Who sets the server side What decides the client side Where it stands
Browser or API client to your load balancer Your load balancer's SSL policy The browser or client, or a TLS-inspecting proxy in the path Google's documented schedule enables post-quantum key exchange by default from October 2026, with DEFERRED available until October 2027 (Google Cloud, 2026a)
Load balancer to your backend Your backend The load balancer Post-quantum key exchange on Google's load balancers covers the frontend connection only
Pipeline jobs and the agent to Google APIs (BigQuery, Cloud Storage, and Vertex AI) Google The TLS library in your client library, dbt adapter, or runtime, or your egress proxy *.googleapis.com offers hybrid ML-KEM; your client has to offer it too
Composer tasks and the agent to partner APIs and MCP servers The partner or vendor Your HTTP client's TLS library, or your egress proxy Varies by partner
Partner file drops over SFTP The partner's SSH server Your SSH client OpenSSH has defaulted to a hybrid post-quantum exchange since 9.0, and to an ML-KEM hybrid since 10.0; other transfer products vary
Encrypted partner files (PGP) and wrapped keys Whoever holds the decryption key Whoever encrypts Classical until both sides support a post-quantum or hybrid scheme
VPN and Interconnect to on-premises Google and your edge Your edge devices On Google's roadmap for 2026 to 2027 (Haridas and Bachman, 2026)

Table 1. Key establishment and encryption in a Google Cloud data platform and agent, and which end you control.

Google's load balancer documentation describes the rollout in three steps. Post-quantum key exchange started off by default. Google's schedule turns it on by default from October 2026 unless a policy sets DEFERRED, and always on from October 2027, though the page hasn't yet been updated to say the first switch has happened. It only covers the connection between the client and the load balancer (Google Cloud, 2026a). Google's roadmap says its API endpoints, *.googleapis.com included, already implement ML-KEM in hybrid mode, and it puts client software that can negotiate those handshakes on the customer's side of the shared responsibility line (Haridas and Bachman, 2026). OpenSSH says post-quantum key agreement has been its default since 9.0 in 2022 (OpenSSH Project, n.d.), and 10.0 switched that default to mlkem768x25519-sha256 (OpenSSH Project, 2025).

On the client side, the library usually decides. OpenSSL 3.5 changed its default key shares to offer X25519MLKEM768 and X25519 (OpenSSL Project, 2025). Go has enabled X25519MLKEM768 by default since Go 1.24, as long as the application doesn't set its own curve preferences (Go Authors, 2025), and JDK 27 enables it by default alongside the classical groups (Inside Java, 2026). A Python job linked against an older OpenSSL will still negotiate classical X25519 against a server that would do better, and so will a JVM service on an earlier JDK or a client that pins its own groups without the hybrid one. A TLS-inspecting proxy in the path becomes the client towards Google or the partner, so its TLS stack decides that leg, whatever your job runs.

To see whether a server supports the hybrid group, run openssl s_client -groups X25519MLKEM768 -connect <host>:443 with OpenSSL 3.5 or later. To see what a job negotiates by default, turn on its own TLS library's handshake logging or capture its traffic; the group chosen in the ServerHello travels in the clear.

Test before your own clients start offering hybrid groups. A hybrid key share makes the ClientHello larger, often more than one packet. In Cloudflare's scans of origin servers, about 0.34% failed when sent a post-quantum key share. The same talk reports that 0.02% of scanned servers reset fragmented ClientHellos, that servers unable to reassemble out-of-order packets, including behind at least one firewall product, fail the handshake when packets are lost, and, separately, that 0.32% mishandled HelloRetryRequest (Ahmad, 2025). Start with the firewalls, SNI routers, and inspection proxies between your jobs, your agents, and their partners, and use a canary before any platform-wide change. Servers that browsers reach directly are likely seeing these larger ClientHellos already, so it's worth checking their logs for handshake resets now.

What identity traffic gives a recorder

Identity protocols ride inside TLS, so a recording of a classical TLS session holds whatever credentials crossed it. Short-lived access tokens will have expired long before anyone can decrypt them. The concern is anything still valid when decryption becomes possible:

  • Client secrets, API keys, and refresh tokens with no expiry, or with expiries measured in years.
  • Private keys in transit, such as a service account key or a private_key_jwt key while it's being downloaded, copied into CI, or read back from a secret manager. In normal use these keys only sign short-lived assertions, so the exposure is limited to the moments they travel. Part 1 of The Pipeline as Attack Surface looked at what one such key can reach.
  • Passwords typed at sign-in, which people often reuse, and the recovery codes and authenticator seeds sent at enrolment.
  • The personal data inside SAML assertions and ID tokens, such as names, email addresses, and group memberships. Their signatures stop mattering once they expire, but the data stays sensitive.

Some of this you can reduce before any post-quantum algorithm is involved. Federated, short-lived credentials leave less in a recording that's still useful later: workload identity federation for pipelines, attached service accounts for agents. The catch is that the external issuer you federate with joins your signature inventory. For every secret that has no expiry, set one.

Rotating refresh tokens on each use only partly helps. Older tokens in a recording stop working, but the newest one is in the same recording. Pair rotation with an idle timeout and an absolute lifetime, so the last token issued also expires long before anyone can decrypt it. Sender-constrained tokens, whether DPoP or mTLS-bound, don't help against this attacker while the binding key is RSA or elliptic-curve, because the binding key's public half travels in the same recording and a quantum attacker can derive the private half from it. Keeping less personal data in tokens shrinks what a recording exposes in the first place.

Pass 2: signatures, protocol by protocol

Most of the identity work is in signatures, and most of it waits on someone else. For several protocols the standard now exists and adoption is what's missing. For SAML and JWT-SVIDs the standard itself still has to change.

Protocol or artefact Standard status Deployed today Order of moves
X.509 certificates and mTLS ML-DSA in certificates is RFC 9881; ML-DSA in the TLS 1.3 handshake is approved and awaiting publication as an RFC Private CAs first; Certificate Authority Service ML-DSA is on Google's roadmap for 2027 (Haridas and Bachman, 2026) New root distributed to every relying party, then leaves issued from it
OpenID Connect and OAuth JWTs ML-DSA for JOSE and COSE is RFC 9964 Identity providers and verifier libraries are only starting to adopt it Issuer publishes keys, verifiers accept them, issuer switches, classical keys withdrawn; for OpenID Connect ID tokens, per client, by registering for ML-DSA
SAML 2.0 (XML Signature) No standard yet; an individual draft proposes ML-DSA and SLH-DSA URIs No mainstream support Wait for the standard; track assertion-signing certificates meanwhile
SPIFFE SVIDs X.509-SVIDs inherit the X.509 row; the JWT-SVID specification allows only RSA and ECDSA algorithms Depends on your SPIFFE implementation and TLS libraries X.509-SVIDs as above; JWT-SVIDs after the specification changes
WebAuthn and passkeys COSE identifiers for ML-DSA are in RFC 9964 ML-DSA passkeys are starting to appear Relying parties accept, then new passkeys use it; existing ones stay classical
Code, containers, and artefacts ML-DSA and SLH-DSA (FIPS 204 and 205) Cloud KMS signing is generally available; HSM-backed keys are on Google's roadmap for 2028; signing tools vary Verifiers first, then dual signatures, then classical dropped

Table 2. Signatures across the identity protocols your platform and agents use, and the order in which the parties move.

For X.509, the certificate format is settled. RFC 9881 defines ML-DSA keys and signatures in certificates and CRLs (Massimo et al., 2025). Using an ML-DSA certificate in a TLS 1.3 handshake also needs signature scheme codepoints, and those are approved but not yet published as an RFC (Hollebeek, Schmieg and Westerbaan, 2026). If you run mTLS between services, plan for the CA root first, since it has to be trusted longest. Build a parallel ML-DSA hierarchy rather than swapping the old one out: get the new root into every relying party's trust store before you issue any leaf from it, and check that the HSM holding your root key can generate ML-DSA keys at all.

OpenID Connect and OAuth sign tokens with JOSE. RFC 9964, published in May 2026, defines ML-DSA-44, ML-DSA-65, and ML-DSA-87 for JOSE and COSE, along with a new key type, AKP (Prorock and Steele, 2026). The standard is there, but the issuers I checked haven't moved yet. Google's OpenID Connect discovery document lists RS256 as its only ID token signing algorithm (Google, 2026), and Google's workload identity federation documents external tokens signed with RS256 or ES256 (Google Cloud, 2026b). Until your issuers change, every ID token and JWT access token in your platform will carry an RSA or elliptic-curve signature. Workload identity federation is in that group too: Google's token service verifies the external issuer's token, so the issuer and Google both have to move. So is private_key_jwt client authentication, where the roles are reversed and your client signs while the identity provider verifies.

The part you control now is the verifier. If I were picking one piece of identity work to start this quarter, it would be this one, because you can do it before any issuer moves. Four questions decide whether a verifier is ready.

  • How does it handle unknown key types? RFC 7517 says implementations should ignore JWKs with a key type they don't understand (Jones, 2015), but some libraries reject the whole set. If yours does, the day an issuer pre-publishes an AKP key becomes an outage, RS256 tokens included.
  • Does it allowlist algorithms per issuer? Accepting ML-DSA protects nothing while RS256 is still accepted from the same issuer, because the attacker chooses the alg in the token header. Take accepted algorithms from configuration, per issuer, and bind them to each key rather than to the token. For AKP keys, which must carry an alg, accept a token only when its alg matches the key's. For OAuth access tokens, you're done when the issuer withdraws its RSA and elliptic-curve keys. OpenID Connect providers generally must support RS256 for ID tokens (OpenID Foundation, n.d.b), so their RSA keys may stay published. There, each relying party finishes on its own, once its client is registered for ML-DSA ID tokens and its verifier stops accepting RS256.
  • Can it handle the size? An ML-DSA-65 signature is 3,309 bytes and its public key 1,952 bytes (Prorock and Steele, 2026), against 256 bytes for an RS256 signature with a 2048-bit key. Base64url-encoded, the signature alone is about 4.4 KB. Chromium and Firefox reject cookies over about 4,096 bytes (Taylor, 2020), so an application that keeps an ID token or a JWT session in one cookie is likely to break before any gateway header limit does. Frameworks that split sessions across several cookies cope better.
  • How does it fetch the JWKS? The fetch travels over TLS authenticated by a classical certificate, which makes the certificate on the issuer's jwks_uri host part of the same chain of trust until X.509 moves. In the meantime, cache issuer keys and alert on unexpected key changes instead of refetching blindly.

You can test the first three today. Cloud KMS can sign with ML-DSA-65 (Etemad, 2026), so you can mint a test token in the RFC 9964 format, which uses pure ML-DSA rather than HashML-DSA, and push it through each verifier.

SAML is further behind. XML Signature has had registered URIs for the stateful hash-based XMSS since RFC 9231 (Eastlake, 2022), but URIs for ML-DSA and SLH-DSA exist only in an individual Internet-Draft (Eastlake, 2026). Post-quantum XML signing and SAML single sign-on have so far been shown in research implementations in OpenSAML, Apache Santuario, and BouncyCastle (Müller and Oupický, 2024). In the meantime, keep track of each identity provider's assertion-signing certificates (the use="signing" keys in its metadata), and of metadata-signing certificates where a federation signs its metadata. Both are published, and both are often valid for years. If you were already planning to move an application from SAML to OpenID Connect, a clearer post-quantum route is one more reason to. I wouldn't start a migration for this reason alone.

SPIFFE identities come as either an X.509 certificate or a JWT, and SPIFFE advises using X.509-SVIDs wherever possible (SPIFFE, n.d.a). X.509-SVIDs need ML-DSA in both the certificate and the TLS handshake. JWT-SVIDs need the standard to change first, because the JWT-SVID specification limits alg to RSA and ECDSA algorithms and tells validators to reject anything else (SPIFFE, n.d.b).

Passkeys sign with COSE algorithms, and RFC 9964 registers ML-DSA for COSE as well. 1Password added ML-DSA passkeys in October 2026, created only for sites that support ML-DSA (Léveillé, 2026). For your own relying party, that means accepting ML-DSA in the credential parameters once your WebAuthn library supports it. Existing passkeys stay on their classical algorithms until users register new ones or a migration mechanism is standardised. Their public keys aren't published, but they do cross the wire at registration, so a recording of that exchange is enough for a future forger.

Artefact signing is mostly in your hands, though the tooling is a dependency. Cloud KMS has offered ML-DSA and SLH-DSA signing keys as generally available since July 2026 (Etemad, 2026). HSM-backed post-quantum keys are on Google's roadmap for 2028 (Haridas and Bachman, 2026), which matters if your policy requires HSM protection. The signing tools are behind: Sigstore's supported suites are still ECDSA, Ed25519, and RSA, and its post-quantum work has so far been demonstrated as a foundation for later (Schirone, 2026). For software you ship to others, start with the verifiers, then add an ML-DSA signature alongside the existing one. Put software and firmware that others run and verify for years at the front of the queue; the EU roadmap gives securing software updates as an example of high quantum risk (NIS Cooperation Group, 2025). Internal deploy paths can usually wait, because you control both the signer and the verifier and can switch them together.

What changes for agents

The MCP specification's authorization is optional. Where an MCP server uses it over HTTP, the server acts as an OAuth 2.1 resource server and each request carries a bearer access token (Model Context Protocol, 2026a). If those tokens are JWTs, the MCP server verifies a JOSE signature and the JOSE verifier checks in Pass 2 apply. If they're opaque, the MCP server usually validates them by introspection over TLS, and the dependency moves to that connection.

In a typical agent flow there are several verifiers: your backend when the employee signs in, the token exchange endpoint when the backend swaps the user's token for one scoped to a tool (the pattern in Part 3 of Agent Security in Practice), and each MCP server, including a vendor's when the agent calls it with its own identity. An agent that calls four MCP servers adds four verifiers, and they're often owned by different teams and vendors. The agent itself verifies none of these access tokens, though it still checks classical TLS certificates on every call. It also signs things, such as client assertions with private_key_jwt and DPoP proofs if you use them, so its own keys belong in the inventory.

I'd count agent traffic among the most valuable traffic to record. Prompts, retrieved documents, and tool results pull much of the sensitive data a task touches into a handful of requests, often to model endpoints and vendor MCP servers outside your network. That makes the agent's outbound connections a strong candidate for the "move first" branch of Figure 1, especially where prompts carry data that will still matter in ten years. Routing them through one egress path gives you a single place to see what each connection negotiates. If that proxy terminates TLS, it's also where you upgrade, but it then holds plaintext prompts and tool results, and it adds one more leg to secure.

Agents keep records too. If you sign approvals or request records as evidence, like the request record in Part 4, those signatures belong in the "verifiable for years" branch. Long-term archiving already has an answer for this: timestamp the record, then renew the evidence with a newer timestamp and stronger algorithms before the original ones weaken (Gondrom, Brandner and Pordesch, 2007). A recent individual IETF draft makes the same case for agent delegation records, which it says must stay verifiable long after the key that signed them is retired (Uppalapati, 2026). Signing new evidence with ML-DSA where your tooling supports it, or renewing its timestamps on a schedule, matters for any record you expect to rely on in the late 2030s.

Listing your issuers' algorithms

The quickest part of the signature inventory is the list of algorithms and keys your issuers publish. Listing 1 tries an issuer's RFC 8414 metadata and its OpenID Connect discovery document in the order the MCP specification requires: the well-known path inserted before any path in the issuer URL, then, for OpenID Connect, appended to it (Model Context Protocol, 2026b). It checks that the issuer it gets back matches the one you asked for, labels each advertised algorithm and published key, refuses plain HTTP (redirects included), and moves on to the next issuer when one fails. Run it against every issuer your verifiers trust: your workforce identity provider, Google, your customer identity platform, any SPIFFE implementation that publishes a JWKS, and the authorization servers behind vendor MCP servers.

Python

import json
import sys
import urllib.request
from urllib.parse import urlsplit

# JOSE names (RFC 7518, RFC 8037, RFC 9864, RFC 9964). The RSA and
# elliptic-curve ones are deprecated after 2030 by NIST IR 8547.
SIGNATURE = ("RS", "PS", "ES", "EdDSA", "Ed25519", "Ed448")
ENCRYPTION = ("RSA-OAEP", "RSA1_5", "ECDH-ES")
SYMMETRIC = ("HS", "A128", "A192", "A256", "dir")
ENC_OPS = {"encrypt", "wrapKey", "deriveKey", "deriveBits"}
METADATA_FIELDS = (
    "id_token_signing_alg_values_supported",
    "id_token_encryption_alg_values_supported",
    "userinfo_encryption_alg_values_supported",
    "token_endpoint_auth_signing_alg_values_supported",  # what your clients sign
    "dpop_signing_alg_values_supported",                 # DPoP (RFC 9449)
)

def classify(alg: str) -> str:
    if alg == "none":
        return "unsigned (reject)"
    if alg.startswith("ML-DSA"):
        return "post-quantum"
    if alg.startswith(ENCRYPTION):
        return "quantum-vulnerable (encryption)"
    if alg.startswith(SIGNATURE):
        return "quantum-vulnerable (signature)"
    if alg.startswith(SYMMETRIC):
        return "symmetric (check how the key is distributed)"
    return "unknown (check by hand)"

def classify_key(key: dict) -> tuple[str, str]:
    if isinstance(key.get("alg"), str):
        return key["alg"], classify(key["alg"])
    kty, crv = str(key.get("kty", "?")), str(key.get("crv", ""))
    label = f"{kty} {crv}".strip() + " (no alg)"
    if kty in ("RSA", "EC", "OKP"):
        enc = (key.get("use") == "enc" or crv in ("X25519", "X448")
               or bool(ENC_OPS & set(key.get("key_ops") or [])))
        return label, "quantum-vulnerable (" + ("encryption" if enc else "signature") + ")"
    if kty == "AKP":
        return label, "post-quantum (invalid: AKP keys must carry alg)"
    if kty == "oct":
        return label, "symmetric (check how the key is distributed)"
    return label, "unknown (check by hand)"

def rows(config: dict, jwks: dict) -> list[tuple[str, str, str]]:
    out = []
    for field in METADATA_FIELDS:
        for alg in config.get(field, []):
            out.append((field.split("_alg")[0], alg, classify(alg)))
    if "jwks_uri" not in config:
        out.append(("jwks_uri", "-", "no signing keys published (opaque tokens?)"))
    for key in jwks.get("keys", []):
        label, verdict = classify_key(key)
        out.append((f"key {str(key.get('kid', '?'))[:12]}", label, verdict))
    return out

class HttpsOnlyRedirects(urllib.request.HTTPRedirectHandler):
    def redirect_request(self, req, fp, code, msg, headers, newurl):
        if not newurl.startswith("https://"):
            raise ValueError(f"refusing redirect to non-HTTPS URL: {newurl}")
        return super().redirect_request(req, fp, code, msg, headers, newurl)

OPENER = urllib.request.build_opener(HttpsOnlyRedirects)

def get_json(url: str) -> dict:
    if not url.startswith("https://"):
        raise ValueError(f"refusing non-HTTPS URL: {url!r}")
    with OPENER.open(url, timeout=15) as resp:
        return json.load(resp)

def metadata_urls(issuer: str) -> list[str]:
    # The order MCP clients use: RFC 8414 and OpenID Connect with the
    # well-known segment inserted before any path, then appended to it.
    parts = urlsplit(issuer)
    root, path = f"https://{parts.netloc}", parts.path.rstrip("/")
    urls = [f"{root}/.well-known/oauth-authorization-server{path}"]
    if path:
        urls.append(f"{root}/.well-known/openid-configuration{path}")
    urls.append(f"{root}{path}/.well-known/openid-configuration")
    return urls

def discover(issuer: str) -> dict:
    if not issuer.startswith("https://"):
        raise ValueError("issuer must be an HTTPS URL")
    for url in metadata_urls(issuer):
        try:
            config = get_json(url)
        except Exception:
            continue
        if config.get("issuer") != issuer:
            raise ValueError(f"issuer mismatch: {config.get('issuer')}")
        return config
    raise ValueError("no RFC 8414 or OpenID Connect metadata found")

def main(issuers: list[str]) -> None:
    for issuer in issuers:
        print(issuer)
        try:
            config = discover(issuer)
            jwks = get_json(config["jwks_uri"]) if "jwks_uri" in config else {}
            found = rows(config, jwks)
        except Exception as err:  # keep going with the next issuer
            print(f"  error: {err}")
            continue
        for source, alg, verdict in found:
            print(f"  {source:<36} {alg:<18} {verdict}")

if __name__ == "__main__":
    main(sys.argv[1:])
Enter fullscreen mode Exit fullscreen mode

Rust

use serde_json::Value;

// JOSE names (RFC 7518, RFC 8037, RFC 9864, RFC 9964). The RSA and
// elliptic-curve ones are deprecated after 2030 by NIST IR 8547.
const SIGNATURE: [&str; 6] = ["RS", "PS", "ES", "EdDSA", "Ed25519", "Ed448"];
const ENCRYPTION: [&str; 3] = ["RSA-OAEP", "RSA1_5", "ECDH-ES"];
const SYMMETRIC: [&str; 5] = ["HS", "A128", "A192", "A256", "dir"];
const ENC_OPS: [&str; 4] = ["encrypt", "wrapKey", "deriveKey", "deriveBits"];
const METADATA_FIELDS: [&str; 5] = [
    "id_token_signing_alg_values_supported",
    "id_token_encryption_alg_values_supported",
    "userinfo_encryption_alg_values_supported",
    "token_endpoint_auth_signing_alg_values_supported", // what your clients sign
    "dpop_signing_alg_values_supported",                // DPoP (RFC 9449)
];

fn classify(alg: &str) -> &'static str {
    let starts = |list: &[&str]| list.iter().any(|p| alg.starts_with(p));
    if alg == "none" {
        "unsigned (reject)"
    } else if alg.starts_with("ML-DSA") {
        "post-quantum"
    } else if starts(&ENCRYPTION) {
        "quantum-vulnerable (encryption)"
    } else if starts(&SIGNATURE) {
        "quantum-vulnerable (signature)"
    } else if starts(&SYMMETRIC) {
        "symmetric (check how the key is distributed)"
    } else {
        "unknown (check by hand)"
    }
}

fn classify_key(key: &Value) -> (String, &'static str) {
    if let Some(alg) = key["alg"].as_str() {
        return (alg.to_string(), classify(alg));
    }
    let kty = key["kty"].as_str().unwrap_or("?");
    let crv = key["crv"].as_str().unwrap_or("");
    let label = format!("{} (no alg)", format!("{kty} {crv}").trim());
    let verdict = match kty {
        "RSA" | "EC" | "OKP" => {
            let mut ops = key["key_ops"].as_array().into_iter().flatten().filter_map(Value::as_str);
            let enc = key["use"] == "enc"
                || crv == "X25519"
                || crv == "X448"
                || ops.any(|op| ENC_OPS.contains(&op));
            if enc { "quantum-vulnerable (encryption)" } else { "quantum-vulnerable (signature)" }
        }
        "AKP" => "post-quantum (invalid: AKP keys must carry alg)",
        "oct" => "symmetric (check how the key is distributed)",
        _ => "unknown (check by hand)",
    };
    (label, verdict)
}

fn rows(config: &Value, jwks: &Value) -> Vec<(String, String, &'static str)> {
    let mut out = Vec::new();
    for field in METADATA_FIELDS {
        for alg in config[field].as_array().into_iter().flatten().filter_map(Value::as_str) {
            let source = field.split("_alg").next().unwrap_or(field).to_string();
            out.push((source, alg.to_string(), classify(alg)));
        }
    }
    if config["jwks_uri"].as_str().is_none() {
        out.push(("jwks_uri".into(), "-".into(), "no signing keys published (opaque tokens?)"));
    }
    for key in jwks["keys"].as_array().into_iter().flatten() {
        let kid = match &key["kid"] {
            Value::String(s) => s.chars().take(12).collect(),
            Value::Null => "?".to_string(),
            other => other.to_string(),
        };
        let (label, verdict) = classify_key(key);
        out.push((format!("key {kid}"), label, verdict));
    }
    out
}

fn get_json(url: &str) -> Result<Value, Box<dyn std::error::Error>> {
    if !url.starts_with("https://") {
        return Err(format!("refusing non-HTTPS URL: {url:?}").into());
    }
    // https_only also applies to redirects.
    let agent: ureq::Agent = ureq::Agent::config_builder()
        .https_only(true)
        .timeout_global(Some(std::time::Duration::from_secs(15)))
        .build()
        .into();
    Ok(agent.get(url).call()?.body_mut().read_json()?)
}

fn metadata_urls(issuer: &str) -> Vec<String> {
    // The order MCP clients use: RFC 8414 and OpenID Connect with the
    // well-known segment inserted before any path, then appended to it.
    let rest = issuer.strip_prefix("https://").unwrap_or(issuer);
    let (host, path) = rest.split_once('/').unwrap_or((rest, ""));
    let path = path.trim_end_matches('/');
    let path = if path.is_empty() { String::new() } else { format!("/{path}") };
    let root = format!("https://{host}");
    let mut urls = vec![format!("{root}/.well-known/oauth-authorization-server{path}")];
    if !path.is_empty() {
        urls.push(format!("{root}/.well-known/openid-configuration{path}"));
    }
    urls.push(format!("{root}{path}/.well-known/openid-configuration"));
    urls
}

fn discover(issuer: &str) -> Result<Value, Box<dyn std::error::Error>> {
    if !issuer.starts_with("https://") {
        return Err("issuer must be an HTTPS URL".into());
    }
    for url in metadata_urls(issuer) {
        if let Ok(config) = get_json(&url) {
            if config["issuer"].as_str() != Some(issuer) {
                return Err(format!("issuer mismatch: {}", config["issuer"]).into());
            }
            return Ok(config);
        }
    }
    Err("no RFC 8414 or OpenID Connect metadata found".into())
}

fn main() {
    for issuer in std::env::args().skip(1) {
        println!("{issuer}");
        let result = discover(&issuer).and_then(|config| {
            let jwks = match config["jwks_uri"].as_str() {
                Some(uri) => get_json(uri)?,
                None => Value::Null,
            };
            Ok(rows(&config, &jwks))
        });
        match result {
            Ok(found) => {
                for (source, alg, verdict) in found {
                    println!("  {source:<36} {alg:<18} {verdict}");
                }
            }
            Err(err) => println!("  error: {err}"), // keep going with the next issuer
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Listing 1. List the algorithms an OAuth authorization server or OpenID Connect issuer advertises and the keys it publishes, and label each as quantum-vulnerable (signature or encryption), post-quantum, symmetric, or something to check by hand.

The Rust version uses the serde_json crate and the ureq crate with its json feature. When you read the output, keep in mind that OpenID Connect providers must list RS256 among their ID token algorithms (OpenID Foundation, n.d.a). The advertised rows will still show it after an issuer adds ML-DSA, so the published keys are the better sign that something has changed. The fields Listing 1 reads cover ID tokens, JWE, client authentication, and DPoP. The algorithm for JWT access tokens isn't advertised in the metadata fields Listing 1 reads, so the keys are your guide there as well. An authorization server with no jwks_uri publishes no signing keys, which usually means it issues opaque tokens. The output shows what each issuer signs with; pair it with the verifier checks above to see what your verifiers accept. SAML identity providers need a separate look at their assertion-signing certificates.

A checklist for your platform

Most of this transition depends on other people: your cloud provider, your identity providers, the maintainers of the libraries in your jobs and agents, and whoever is on the other end of each connection. The parts that are yours are smaller, but they often have the longest lead times. NIST notes that moving from standardised algorithms to full integration into information systems has historically taken 10 to 20 years (NIST, 2026). Starting the inventory now gives you a better chance of fitting these changes into normal upgrade cycles instead of a rushed programme near the deadline. Where regulation applies, the inventory and any decision to defer are also the evidence you're likely to be asked for.

For platform engineering:

  1. Load balancer SSL policies. List load balancers that have no SSL policy, or a policy with no post-quantum setting. Google's documented schedule turns post-quantum key exchange on for those by default from October 2026. Watch handshake error rates as the rollout reaches them, and record a reason and an end date for any policy set to DEFERRED.
  2. Interop testing. Before your own clients, jobs, agents, and egress proxies offer hybrid groups, test them through every middlebox in the path: TLS-inspecting proxies, firewalls, SNI routers, and WAFs.
  3. Backend legs. For each load balancer, record what protects the connection to the backend, since the post-quantum setting doesn't cover it.
  4. TLS libraries in jobs, agents, and proxies. For Composer environments, dbt runners, Cloud Run services, agent containers, and egress proxies, record the TLS library and version, and check what connections to *.googleapis.com and to partners actually negotiate.
  5. Partner transfers. Record which SSH implementation and version each partner endpoint runs and what it negotiates, and check that FIPS-mode builds still offer the hybrid exchange. List PGP-encrypted exchanges and wrapped keys alongside how long their data matters.
  6. Long-lived secrets on the wire. List client secrets, API keys, refresh tokens, and private keys that cross TLS and don't expire. Set expiries, give refresh tokens an idle timeout and an absolute lifetime as well as rotation, and move to federated credentials where you can.

For security and architecture:

  1. Issuer algorithms and verifiers. Run Listing 1 against every issuer your verifiers trust. For each verifier, record whether it allowlists algorithms per issuer, whether it ignores unknown key types, whether it stores tokens in cookies or caps header size, and whether it checks ID token signatures at all or relies on the token endpoint's TLS certificate, as OpenID Connect allows (OpenID Foundation, n.d.b). Note when it will stop accepting RSA and elliptic-curve keys.
  2. SAML dependencies. List applications that still federate over SAML and the assertion-signing and metadata-signing certificates they trust, with their expiry dates, and ask each identity provider for its plan.
  3. Roots and signing keys. Write down every CA root, update or firmware signing key, client-authentication key, and evidence-signing key you own. For each one, note its expected lifetime, whether its HSM can generate ML-DSA keys, and a target for replacing it.
  4. Inventory format. Keep the inventory in a standard format, such as the Cryptographic Bill of Materials (CBOM) the EU roadmap recommends, so you can update it rather than rebuild it (NIS Cooperation Group, 2025).
  5. Vendor dates. Ask your identity providers, MCP server vendors, and data partners when they expect to offer post-quantum key exchange and signatures. Record each answer with the date you asked, and raise it again at contract renewal.

References

Ahmad, S. S., 2025. Lessons learned from testing millions of servers for PQ compatibility. PKI Consortium Post-Quantum Cryptography Conference, Austin, 16 January. Available at: https://pkic.org/events/2025/pqc-conference-austin-us/THU_BREAKOUT_1100_Syed-Suleman-Ahmad_Lessons-Learned-from-Testing-Millions-of-Servers-for-PQ-Compatibility.pdf [Accessed 13 November 2026].

Eastlake, D., 2022. Additional XML Security Uniform Resource Identifiers (URIs). RFC 9231. IETF. Available at: https://www.rfc-editor.org/rfc/rfc9231 [Accessed 13 November 2026].

Eastlake, D., 2026. Additional XML Security Uniform Resource Identifiers (URIs). Internet-Draft draft-eastlake-rfc9231bis-xmlsec-uris-09, work in progress. IETF. Available at: https://www.ietf.org/archive/id/draft-eastlake-rfc9231bis-xmlsec-uris-09.txt [Accessed 13 November 2026].

Etemad, M., 2026. Future-proofing data integrity: quantum-safe digital signatures in Cloud KMS. Google Cloud Blog, 28 July. Available at: https://cloud.google.com/blog/products/identity-security/future-proofing-data-integrity-quantum-safe-digital-signatures-in-cloud-kms [Accessed 13 November 2026].

European Commission, 2024. Commission Delegated Regulation (EU) 2024/1774 of 13 March 2024 supplementing Regulation (EU) 2022/2554 of the European Parliament and of the Council with regard to regulatory technical standards specifying ICT risk management tools, methods, processes, and policies and the simplified ICT risk management framework. Article 6. Official Journal of the European Union, L 2024/1774. Available at: https://eur-lex.europa.eu/eli/reg_del/2024/1774/oj [Accessed 12 November 2026].

Go Authors, 2025. Go 1.24 Release Notes. The Go Programming Language. Available at: https://go.dev/doc/go1.24 [Accessed 13 November 2026].

Gondrom, T., Brandner, R. and Pordesch, U., 2007. Evidence Record Syntax (ERS). RFC 4998. IETF. Available at: https://datatracker.ietf.org/doc/rfc4998/ [Accessed 13 November 2026].

Google, 2026. OpenID Connect discovery document for accounts.google.com. Available at: https://accounts.google.com/.well-known/openid-configuration [Accessed 13 November 2026].

Google Cloud, 2026a. Post-quantum TLS. Cloud Load Balancing documentation. Available at: https://docs.cloud.google.com/load-balancing/docs/post-quantum-tls [Accessed 13 November 2026].

Google Cloud, 2026b. Configure Workload Identity Federation with other identity providers. IAM documentation. Available at: https://docs.cloud.google.com/iam/docs/workload-identity-federation-with-other-providers [Accessed 13 November 2026].

Haridas, J. and Bachman, M., 2026. PQC in plaintext: Google Cloud's post-quantum cryptography roadmap. Google Cloud Blog, 11 August. Available at: https://cloud.google.com/blog/products/identity-security/pqc-in-plaintext-google-clouds-post-quantum-cryptography-roadmap [Accessed 13 November 2026].

Hollebeek, T., Schmieg, S. and Westerbaan, B., 2026. Use of ML-DSA in TLS 1.3. Internet-Draft draft-ietf-tls-mldsa, work in progress. IETF. Available at: https://datatracker.ietf.org/doc/draft-ietf-tls-mldsa/ [Accessed 13 November 2026].

Inside Java, 2026. Quality Outreach Heads-up - JDK 27: Post-Quantum Hybrid Key Exchange for TLS 1.3. 17 May. Available at: https://inside.java/2026/05/17/quality-heads-up/ [Accessed 13 November 2026].

Ivezic, M., 2026. NIST finalizes its post-quantum transition plan, deprecating RSA and ECC at all approved key sizes after 2030. PostQuantum.com, 28 October. Available at: https://postquantum.com/security-pqc/nist-ir-8547-final/ [Accessed 13 November 2026].

Jones, M., 2015. JSON Web Key (JWK). RFC 7517. IETF. Available at: https://www.rfc-editor.org/rfc/rfc7517 [Accessed 13 November 2026].

Kwiatkowski, K., Kampanakis, P., Westerbaan, B. E. and Stebila, D., 2026. Post-Quantum Traditional (PQ/T) Hybrid Key Agreement Mechanisms for TLS 1.3. RFC 10024. IETF. Available at: https://www.rfc-editor.org/rfc/rfc10024 [Accessed 13 November 2026].

Léveillé, R., 2026. Launching post-quantum safe passkeys. 1Password Blog, 20 October. Available at: https://1password.com/blog/post-quantum-passkeys [Accessed 13 November 2026].

Massimo, J., Kampanakis, P., Turner, S. and Westerbaan, B., 2025. Internet X.509 Public Key Infrastructure: Algorithm Identifiers for the Module-Lattice-Based Digital Signature Algorithm (ML-DSA). RFC 9881. IETF. Available at: https://www.rfc-editor.org/info/rfc9881 [Accessed 13 November 2026].

Meghji, S., 2026. Is the U.S. Banking System Ready for a Post-Quantum Future? Center for Strategic and International Studies, 17 September. Available at: https://www.csis.org/analysis/us-banking-system-ready-post-quantum-future [Accessed 13 November 2026].

Model Context Protocol, 2026a. Authorization. Specification, revision 2026-07-28. Available at: https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization [Accessed 13 November 2026].

Model Context Protocol, 2026b. Authorization Server Discovery. Specification, revision 2026-07-28. Available at: https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization/authorization-server-discovery [Accessed 13 November 2026].

Müller, J. and Oupický, J., 2024. Post-quantum XML and SAML Single Sign-On. Proceedings on Privacy Enhancing Technologies, 2024(4), pp. 525-543. Available at: https://www.petsymposium.org/popets/2024/popets-2024-0128.php [Accessed 13 November 2026].

NIS Cooperation Group, 2025. A Coordinated Implementation Roadmap for the Transition to Post-Quantum Cryptography. Part 1, version 1.1, EU PQC Workstream, 11 June. Available at: https://www.key4biz.it/wp-content/uploads/2025/06/Roadmap_on_postquantum_cryptography_PzBJxNUYyeuEdVUacWL696DofZQ_117507.pdf [Accessed 13 November 2026].

NIST, 2026. Transition to Post-Quantum Cryptography Standards. NIST IR 8547. National Institute of Standards and Technology. Available at: https://nvlpubs.nist.gov/nistpubs/ir/2026/NIST.IR.8547.pdf [Accessed 13 November 2026].

OpenID Foundation, n.d.a. OpenID Connect Discovery 1.0. Available at: https://openid.net/specs/openid-connect-discovery-1_0.html [Accessed 13 November 2026].

OpenID Foundation, n.d.b. OpenID Connect Core 1.0. Available at: https://openid.net/specs/openid-connect-core-1_0.html [Accessed 13 November 2026].

OpenSSH Project, 2025. Announce: OpenSSH 10.0 released. oss-security mailing list, 9 April. Available at: https://openwall.com/lists/oss-security/2025/04/09/1 [Accessed 13 November 2026].

OpenSSH Project, n.d. OpenSSH Post-Quantum Cryptography. Available at: https://www.openssh.org/pq.html [Accessed 13 November 2026].

OpenSSL Project, 2025. OpenSSL 3.5 Series Release Notes. Available at: https://openssl-library.org/news/openssl-3.5-notes [Accessed 13 November 2026].

Orrick, 2026. Treasury announces Quantum-Readiness Task Force. InfoBytes, 28 August. Available at: https://infobytes.orrick.com/2026-08-28/treasury-announces-quantum-readiness-task-force/ [Accessed 13 November 2026].

Prorock, M. and Steele, O., 2026. ML-DSA for JSON Object Signing and Encryption (JOSE) and CBOR Object Signing and Encryption (COSE). RFC 9964. IETF. Available at: https://www.rfc-editor.org/info/rfc9964 [Accessed 13 November 2026].

Schirone, R., 2026. Building cryptographic agility into Sigstore. Trail of Bits Blog, 29 January. Available at: https://blog.trailofbits.com/2026/01/29/building-cryptographic-agility-into-sigstore/ [Accessed 13 November 2026].

SPIFFE, n.d.a. SPIFFE Concepts. SPIFFE documentation. Available at: https://www.spiffe.io/docs/latest/spiffe-about/spiffe-concepts/ [Accessed 13 November 2026].

SPIFFE, n.d.b. The JWT SPIFFE Verifiable Identity Document. SPIFFE specifications. Available at: https://spiffe.io/docs/latest/spiffe-specs/jwt-svid/ [Accessed 13 November 2026].

Taylor, M., 2020. Differences in cookie length (size?) restrictions. 10 December. Available at: https://miketaylr.com/posts/2020/12/differences-in-cookie-length-restrictions.html [Accessed 13 November 2026].

Uppalapati, V. K., 2026. Post-Quantum Requirements for Software and AI Agent Identity. Internet-Draft draft-uppalapati-wimse-pq-agent-identity-00, work in progress. IETF. Available at: https://www.ietf.org/archive/id/draft-uppalapati-wimse-pq-agent-identity-00.html [Accessed 13 November 2026].

Top comments (0)