Skip to content

Keep an attached certificate chain that carries no root - #5046

Closed
Eljees wants to merge 1 commit into
sigstore:mainfrom
Eljees:fix/3976-attached-intermediate-chain
Closed

Keep an attached certificate chain that carries no root#5046
Eljees wants to merge 1 commit into
sigstore:mainfrom
Eljees:fix/3976-attached-intermediate-chain

Conversation

@Eljees

@Eljees Eljees commented Aug 7, 2026

Copy link
Copy Markdown

Fixes #3976.

When a signature carries an attached certificate chain, the intermediate pool is built like this:

if len(chain) > 1 {
    if co.IntermediateCerts == nil {
        pool = x509.NewCertPool()
        for _, cert := range chain[:len(chain)-1] {
            pool.AddCert(cert)
        }
    }
}

Both parts assume the chain ends with a root. That holds when the signer attaches the whole chain, but not when the root CA is supplied separately — then every certificate in the chain is an intermediate. With a single intermediate attached, len(chain) > 1 is false, the pool is never built, and the attached intermediate is dropped entirely.

That is the asymmetry in the report. Running the current logic verbatim against generated certificates:

chain=[intermediate]        -> pool=nil        the attached intermediate is discarded
chain=[intermediate, root]  -> pool with 1 cert

Which matches both observations: attaching intermediate + root works (with the root duplicated), attaching the intermediate alone fails unless it is also passed on the command line.

The pool building moves into intermediatePoolFromChain, which drops the last element only when it is actually self-signed, and keeps it otherwise. Existing behaviour for chains that do end with a root is unchanged.

Covered by a table test over the five shapes: empty, intermediate only, intermediate + root, two intermediates, root only. pkg/cosign is green with the change and on a clean tree.

go build ./... could not finish here — the module proxy timed out fetching open-policy-agent/opa, unrelated to this change; go vet ./pkg/cosign/ and gofmt are clean.

AI-assisted (LLM used for drafting); the reproduction and runs above are mine.

The intermediate pool built from a signature's attached certificate chain assumed
the chain always ends with a root, so a chain holding a single intermediate was
dropped and verification could only succeed if the intermediate was supplied
again alongside the root CA.

Fixes sigstore#3976

Signed-off-by: Eljees <3.14hell@gmail.com>
@Eljees
Eljees requested a review from a team as a code owner August 7, 2026 13:46
@Hayden-IO

Copy link
Copy Markdown
Contributor

Thank you for looking into this, but this code path will be removed as of #4959, so no need to fix these edge cases at this point.

@Hayden-IO Hayden-IO closed this Aug 7, 2026
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.

Intermediate cert and root CA must come via same source for verify to succeed

2 participants