Skip to content

std.cryptography: Validate basicConstraints cA and keyCertSign in chain_verify_path - #3340

Merged
nicolas-maman merged 2 commits into
aether-lang-dev:mainfrom
adilalperenciftci:fix/tls13-cert-chain-ca-validation
Oct 28, 2026
Merged

nicolas-maman merged 2 commits into
aether-lang-dev:mainfrom
adilalperenciftci:fix/tls13-cert-chain-ca-validation

Conversation

@adilalperenciftci

@adilalperenciftci adilalperenciftci commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Part of #3303 (the CA-flag hole; the rest of RFC 5280 path validation listed there stays open).

Root cause

std/cryptography/tls13_cert's chain_verify_path walked intermediate certificates checking only subject/issuer DN matching and the signature, never basicConstraints.cA or keyUsage.keyCertSign. A leaf certificate a trusted CA issued could therefore act as an intermediate and sign a certificate for any host.

Solution

  1. LeafCert records is_ca, has_key_usage and key_cert_sign.
  2. Extension parsing decodes basicConstraints (2.5.29.19) and keyUsage (2.5.29.15); one read_extn_value helper skips the optional critical flag for these and for subjectAltName.
  3. find_issuer_unused accepts an issuer only when is_ca == 1 and, if keyUsage is present, keyCertSign == 1.
  4. tests/integration/crypto_tls13_cert has openssl-made chains whose names and signatures are all valid and whose signer is a cA=FALSE certificate (rejected), a CA without keyCertSign (rejected), or a CA with no keyUsage extension (accepted). Removing the check fails the first two.
  5. A security changelog fragment in new_changelogs/.

…in_verify_path

When traversing intermediate certificates in chain_verify_path, verify that
candidate issuers assert basicConstraints cA=TRUE and keyUsage keyCertSign
before accepting them as intermediate authorities.

Signed-off-by: Adil Alperen Çiftci <adilalperenciftci@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings October 28, 2026 08:11

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.

The leaf-as-intermediate test passed without the fix: the leaf's subject
never matches its own issuer, so the path was rejected for want of an
issuer before the cA check ran. Replace it with openssl-made chains whose
names and signatures are all valid and whose signer is a cA=FALSE
certificate, a CA without keyCertSign, or a CA with no keyUsage (accepted).
Removing the check fails the first two; dropping the no-keyUsage allowance
fails the third and the P-384 chain.

The SAN, basicConstraints and keyUsage branches each repeated the skip of
the optional critical BOOLEAN; read_extn_value does it once.
decode_basic_constraints now requires the SEQUENCE tag.

Security changelog fragment; it says aether-lang-dev#3303 is only partly fixed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@paul-hammant

Copy link
Copy Markdown
Collaborator

Thanks - looking

@paul-hammant

Copy link
Copy Markdown
Collaborator

@nicolas-maman your unpushed work for #3310 and #3311 almost certainly edits the same chain_verify_path, so you could well have a merge conflict.

@paul-hammant

Copy link
Copy Markdown
Collaborator

Waiting on Nic's input, Adil - thanks for making this contrib.

You look to be quite the security researcher :)

@nicolas-maman

Copy link
Copy Markdown
Collaborator

@adilalperenciftci let me check your work, sorry I missed this. Thank you for your contribution!

@nicolas-maman
nicolas-maman merged commit e33816a into aether-lang-dev:main Oct 28, 2026
44 checks passed
@nicolas-maman

Copy link
Copy Markdown
Collaborator

Looks good, thank you once again.

@paul-hammant

Copy link
Copy Markdown
Collaborator

Hi @adilalperenciftci, what interests you about Aether may we ask?

@adilalperenciftci

Copy link
Copy Markdown
Contributor Author

Hi @adilalperenciftci, what interests you about Aether may we ask?

hi, i came across aether while looking into actor-based languages. the actor model without a vm or gc got me intrested, so i started going through the repo and open issues. i saw #3303 and picked up the missing ca/key usage checks. i enjoy working on security code, especially tls/x509 validation, so it seemed like a good place to contribute.

@paul-hammant

paul-hammant commented Oct 28, 2026 •

Copy link
Copy Markdown
Collaborator

@adilalperenciftci thanks for this, and welcome. As you no doubt saw, I pushed a follow-up commit onto your branch: your leaf-as-intermediate test was rejecting the chain before the cA check ran, so I replaced it with openssl-made chains whose only defect is the signer's extensions, and added a changelog fragment. It merged and shipped in 0.856.0. The rest of #3303 (pathLenConstraint, EKU, critical extensions, name constraints, backtracking) is still open if you want more.

We're doing security research in the Aether repos too, and since your background is runtime security and PKI, some of it may interest you.

Containment in the language itself

Running other languages' code under the same grant list

contrib/host/ embeds Lua, Python, Perl, Ruby, Tcl and JS in-process, and runs Java, Go and Aether-in-Aether as separate processes. Each is called as <lang>.run_sandboxed(perms, code). The same grants and the same LD_PRELOAD layer gate the guest interpreter's libc calls (the Python one, for example). Escaping from a guest interpreter is exactly the kind of thing we'd like more eyes on.

Up a level: infrastructure as an Aether composition

aeo is an orchestrator written in Aether. One composition brings up and tears down a dependency-ordered tree of FreeBSD jails, bhyve VMs, Linux containers (podman/docker/LXC), KVM VMs and bubblewrap sandboxes. Its confinement is one grammar (limit{} for resource caps; constrain{} for fd grants and egress/ingress policy) rendered to rctl/Capsicum/pf on FreeBSD and to cgroups/seccomp/network policy on Linux. It also verifies image digests before boot (attest(...)) and keeps a tamper-evident audit trail. Its threat model says what it does and doesn't claim, and aeo status reports supported: no / applied: no where it can't enforce a property that was asked for, rather than pretending it did.

Issues or PRs against any of these are welcome. A bypass of a boundary we claim is the most useful kind. But also any ideas you have for an IaC tech that should scale from hobbyist to in-house cloud soltions for corporates

@adilalperenciftci

Copy link
Copy Markdown
Contributor Author

Thanks Paul. Since #3340 has merged and Nicolas is continuing #3334, I will avoid overlapping with that work and focus on independent Python host sandbox permission-lifecycle verification tests.

adilalperenciftci pushed a commit to adilalperenciftci/aether that referenced this pull request Oct 30, 2026
aether-lang-dev#3303, aether-lang-dev#1298)

This extends aether-lang-dev#3340 by Adil Alperen Ciftci, which made an issuing
certificate need basicConstraints cA and, with a keyUsage, keyCertSign
(LeafCert.is_ca, has_key_usage, key_cert_sign; read_extn_value,
decode_basic_constraints, decode_key_usage). Path validation reads those
fields; his decoders are extended for the rest.

The pure TLS client accepted any path to a trust anchor whose DNs matched
and whose signatures verified. New tls13_cert.validate_chain(leaf,
intermediates, anchors, at, purpose, max_depth): a bounded depth-first
search over every candidate path that accepts one where every signature
verifies, every certificate is inside its validity period, every issuer is
a v3 CA (is_ca) with keyCertSign when it has a keyUsage, within its
pathLenConstraint, extendedKeyUsage allows the purpose wherever it is
listed, DNS and IP name constraints hold, and no certificate has an
unknown critical, repeated or malformed extension. The certificate parser
now records version, pathLenConstraint, the keyUsage bits,
extendedKeyUsage and nameConstraints. The client calls it;
chain_verify_path now calls it for a server at the current time.

aether-lang-dev#3340's decoders are made strict where they were lenient: a keyUsage that
is not a BIT STRING, a repeated extension, and a BOOLEAN other than 0x00
or 0xFF (cA, or the critical flag) now make the certificate malformed
instead of reading as no keyUsage, the last copy, or TRUE.

Test crypto_tls13_chain: a CA's end entity (cA FALSE, and with no
basicConstraints) signing a certificate for another host is refused
through chain_verify_path (both accepted before the fix), plus keyUsage,
pathLen, EKU, name constraint, critical extension and validity cases; and
three issuers aether-lang-dev#3340's code accepted (seen: 3 FAILURES on e33816a's
tls13_cert): keyUsage as an OCTET STRING, keyUsage twice, cA as the
BOOLEAN 0x01. aether-lang-dev#3340's three chains in crypto_tls13_cert still pass.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants