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
35 changes: 27 additions & 8 deletions pkg/cosign/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -574,6 +574,30 @@ func validateCertExtensions(ce CertExtensions, co *CheckOpts) error {
return nil
}

// intermediatePoolFromChain builds the intermediate pool from a certificate chain that
// was attached alongside a signature. The chain ends with a root only when the signer
// attached the whole chain; when the root is supplied separately (co.RootCerts), every
// certificate in the chain is an intermediate and must be kept, including the last one.
// Returns nil when the chain contributes no intermediates.
func intermediatePoolFromChain(chain []*x509.Certificate) *x509.CertPool {
intermediates := chain
if len(chain) > 0 {
last := chain[len(chain)-1]
if bytes.Equal(last.RawIssuer, last.RawSubject) {
// Self-signed: this is a root, not an intermediate.
intermediates = chain[:len(chain)-1]
}
}
if len(intermediates) == 0 {
return nil
}
pool := x509.NewCertPool()
for _, cert := range intermediates {
pool.AddCert(cert)
}
return pool
}

// ValidateAndUnpackCertWithChain creates a Verifier from a certificate. Verifies that the certificate
// chains up to the provided root. Chain should start with the parent of the certificate and end with the root.
// Optionally verifies the subject and issuer of the certificate.
Expand Down Expand Up @@ -902,14 +926,9 @@ func verifyInternal(ctx context.Context, sig oci.Signature, h v1.Hash,
}
// If there is no chain annotation present, we preserve the pools set in the CheckOpts.
var pool *x509.CertPool
if len(chain) > 1 {
if co.IntermediateCerts == nil {
// If the intermediate certs have not been loaded in by TUF
pool = x509.NewCertPool()
for _, cert := range chain[:len(chain)-1] {
pool.AddCert(cert)
}
}
if co.IntermediateCerts == nil {
// If the intermediate certs have not been loaded in by TUF
pool = intermediatePoolFromChain(chain)
}
// In case pool is not set than set it from co.IntermediateCerts
if pool == nil {
Expand Down
54 changes: 54 additions & 0 deletions pkg/cosign/verify_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1364,6 +1364,60 @@ func TestValidateAndUnpackCertInvalidGithubWorkflowRef(t *testing.T) {
require.Contains(t, err.Error(), "expected GitHub Workflow Ref not found in certificate")
}

func TestIntermediatePoolFromChain(t *testing.T) {
// A chain attached next to a signature ends with a root only when the signer attached
// the whole chain. When the root is supplied separately, the chain is intermediates
// only and the last entry must not be dropped.
// https://github.com/sigstore/cosign/issues/3976
rootCert, rootKey, _ := test.GenerateRootCa()
subCert, subKey, _ := test.GenerateSubordinateCa(rootCert, rootKey)
sub2Cert, _, _ := test.GenerateSubordinateCa(subCert, subKey)

tests := []struct {
name string
chain []*x509.Certificate
want []*x509.Certificate
}{{
name: "empty chain contributes nothing",
chain: nil,
want: nil,
}, {
name: "intermediate only, root supplied elsewhere",
chain: []*x509.Certificate{subCert},
want: []*x509.Certificate{subCert},
}, {
name: "intermediate and root attached",
chain: []*x509.Certificate{subCert, rootCert},
want: []*x509.Certificate{subCert},
}, {
name: "two intermediates, root supplied elsewhere",
chain: []*x509.Certificate{sub2Cert, subCert},
want: []*x509.Certificate{sub2Cert, subCert},
}, {
name: "root only",
chain: []*x509.Certificate{rootCert},
want: nil,
}}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
pool := intermediatePoolFromChain(tc.chain)
if len(tc.want) == 0 {
if pool != nil {
t.Fatalf("expected no pool, got one")
}
return
}
if pool == nil {
t.Fatalf("expected a pool with %d cert(s), got nil", len(tc.want))
}
if got := len(pool.Subjects()); got != len(tc.want) { //nolint:staticcheck // pool built here, not from the system
t.Errorf("expected %d cert(s) in pool, got %d", len(tc.want), got)
}
})
}
}

func TestValidateAndUnpackCertWithChainSuccess(t *testing.T) {
subject := "email@email"
oidcIssuer := "https://accounts.google.com"
Expand Down