Skip to content

Commit

Permalink
Add support for certificate chain to verify certificate
Browse files Browse the repository at this point in the history
This adds a flag to provide a chain of CA certificates to verify a
certificate provided by flag. Callers should include a chain from the
parent of the certificate to the root.

While it'd be ideal to force the root to be specified out of band, by
TUF, that code is currently intertwined with expectations around Fulcio
and Rekor usage.

Signed-off-by: Hayden Blauzvern <[email protected]>
  • Loading branch information
haydentherapper committed Mar 25, 2022
1 parent 7402eb4 commit faa1cbe
Show file tree
Hide file tree
Showing 13 changed files with 98 additions and 6 deletions.
1 change: 1 addition & 0 deletions cmd/cosign/cli/dockerfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ Shell-like variables in the Dockerfile's FROM lines will be substituted with val
CertRef: o.CertVerify.Cert,
CertEmail: o.CertVerify.CertEmail,
CertOidcIssuer: o.CertVerify.CertOidcIssuer,
CertChain: o.CertVerify.CertChain,
Sk: o.SecurityKey.Use,
Slot: o.SecurityKey.Slot,
Output: o.Output,
Expand Down
1 change: 1 addition & 0 deletions cmd/cosign/cli/manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ against the transparency log.`,
CertRef: o.CertVerify.Cert,
CertEmail: o.CertVerify.CertEmail,
CertOidcIssuer: o.CertVerify.CertOidcIssuer,
CertChain: o.CertVerify.CertChain,
Sk: o.SecurityKey.Use,
Slot: o.SecurityKey.Slot,
Output: o.Output,
Expand Down
7 changes: 7 additions & 0 deletions cmd/cosign/cli/options/certificate.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type CertVerifyOptions struct {
Cert string
CertEmail string
CertOidcIssuer string
CertChain string
}

var _ Interface = (*RekorOptions)(nil)
Expand All @@ -37,4 +38,10 @@ func (o *CertVerifyOptions) AddFlags(cmd *cobra.Command) {

cmd.Flags().StringVar(&o.CertOidcIssuer, "cert-oidc-issuer", "",
"the OIDC issuer expected in a valid Fulcio certificate, e.g. https://token.actions.githubusercontent.com or https://oauth2.sigstore.dev/auth")

cmd.Flags().StringVar(&o.CertChain, "cert-chain", "",
"path to a list of CA certificates in PEM format which will be needed "+
"when building the certificate chain for the signing certificate. "+
"Must start with the parent intermediate CA certificate of the "+
"signing certificate and end with the root certificate")
}
7 changes: 6 additions & 1 deletion cmd/cosign/cli/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ against the transparency log.`,
# verify image with an on-disk signed image from 'cosign save'
cosign verify --key cosign.pub --local-image <PATH>
# verify image with local certificate and certificate chain
cosign verify --cert cosign.crt --cert-chain chain.crt <IMAGE>
# verify image with public key provided by URL
cosign verify --key https://host.for/[FILE] <IMAGE>
Expand Down Expand Up @@ -93,6 +96,7 @@ against the transparency log.`,
CertRef: o.CertVerify.Cert,
CertEmail: o.CertVerify.CertEmail,
CertOidcIssuer: o.CertVerify.CertOidcIssuer,
CertChain: o.CertVerify.CertChain,
Sk: o.SecurityKey.Use,
Slot: o.SecurityKey.Slot,
Output: o.Output,
Expand Down Expand Up @@ -169,6 +173,7 @@ against the transparency log.`,
CertRef: o.CertVerify.Cert,
CertEmail: o.CertVerify.CertEmail,
CertOidcIssuer: o.CertVerify.CertOidcIssuer,
CertChain: o.CertVerify.CertChain,
KeyRef: o.Key,
Sk: o.SecurityKey.Use,
Slot: o.SecurityKey.Slot,
Expand Down Expand Up @@ -247,7 +252,7 @@ The blob may be specified as a path to a file or - for stdin.`,
BundlePath: o.BundlePath,
}
if err := verify.VerifyBlobCmd(cmd.Context(), ko, o.CertVerify.Cert,
o.CertVerify.CertEmail, o.CertVerify.CertOidcIssuer, o.Signature, args[0]); err != nil {
o.CertVerify.CertEmail, o.CertVerify.CertOidcIssuer, o.CertVerify.CertChain, o.Signature, args[0]); err != nil {
return errors.Wrapf(err, "verifying blob %s", args)
}
return nil
Expand Down
32 changes: 32 additions & 0 deletions cmd/cosign/cli/verify/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package verify

import (
"bytes"
"context"
"crypto"
"crypto/ecdsa"
Expand Down Expand Up @@ -53,6 +54,7 @@ type VerifyCommand struct {
CertRef string
CertEmail string
CertOidcIssuer string
CertChain string
Sk bool
Slot string
Output string
Expand Down Expand Up @@ -139,6 +141,24 @@ func (c *VerifyCommand) Exec(ctx context.Context, images []string) (err error) {
if err != nil {
return err
}
// Verify certificate with chain
// First intermediate at chain[0], root at chain[n-1]
if c.CertChain != "" {
certs, err := loadCertChainFromFileOrUrl(c.CertChain)
if err != nil {
return err
}
rootPool := x509.NewCertPool()
rootPool.AddCert(certs[len(certs)-1])
subPool := x509.NewCertPool()
for _, c := range certs[:len(certs)-1] {
subPool.AddCert(c)
}
err = cosign.TrustedCert(cert, rootPool, subPool)
if err != nil {
return err
}
}
pubKey, err = signature.LoadECDSAVerifier(cert.PublicKey.(*ecdsa.PublicKey), crypto.SHA256)
if err != nil {
return err
Expand Down Expand Up @@ -296,3 +316,15 @@ func loadCertFromPEM(pems []byte) (*x509.Certificate, error) {
}
return certs[0], nil
}

func loadCertChainFromFileOrUrl(path string) ([]*x509.Certificate, error) {
pems, err := blob.LoadFileOrURL(path)
if err != nil {
return nil, err
}
certs, err := cryptoutils.LoadCertificatesFromPEM(bytes.NewReader(pems))
if err != nil {
return nil, err
}
return certs, nil
}
20 changes: 20 additions & 0 deletions cmd/cosign/cli/verify/verify_attestation.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"context"
"crypto"
"crypto/ecdsa"
"crypto/x509"
"encoding/base64"
"encoding/json"
"flag"
Expand Down Expand Up @@ -51,6 +52,7 @@ type VerifyAttestationCommand struct {
CertRef string
CertEmail string
CertOidcIssuer string
CertChain string
KeyRef string
Sk bool
Slot string
Expand Down Expand Up @@ -121,6 +123,24 @@ func (c *VerifyAttestationCommand) Exec(ctx context.Context, images []string) (e
if err != nil {
return errors.Wrap(err, "loading certificate from reference")
}
// Verify certificate with chain
// First intermediate at chain[0], root at chain[n-1]
if c.CertChain != "" {
certs, err := loadCertChainFromFileOrUrl(c.CertChain)
if err != nil {
return err
}
rootPool := x509.NewCertPool()
rootPool.AddCert(certs[len(certs)-1])
subPool := x509.NewCertPool()
for _, c := range certs[:len(certs)-1] {
subPool.AddCert(c)
}
err = cosign.TrustedCert(cert, rootPool, subPool)
if err != nil {
return err
}
}
co.SigVerifier, err = signature.LoadECDSAVerifier(cert.PublicKey.(*ecdsa.PublicKey), crypto.SHA256)
if err != nil {
return errors.Wrap(err, "creating certificate verifier")
Expand Down
20 changes: 19 additions & 1 deletion cmd/cosign/cli/verify/verify_blob.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func isb64(data []byte) bool {
}

// nolint
func VerifyBlobCmd(ctx context.Context, ko sign.KeyOpts, certRef, certEmail, certOidcIssuer, sigRef, blobRef string) error {
func VerifyBlobCmd(ctx context.Context, ko sign.KeyOpts, certRef, certEmail, certOidcIssuer, certChain, sigRef, blobRef string) error {
var verifier signature.Verifier
var cert *x509.Certificate

Expand Down Expand Up @@ -104,6 +104,24 @@ func VerifyBlobCmd(ctx context.Context, ko sign.KeyOpts, certRef, certEmail, cer
if err != nil {
return err
}
// Verify certificate with chain
// First intermediate at chain[0], root at chain[n-1]
if certChain != "" {
certs, err := loadCertChainFromFileOrUrl(certChain)
if err != nil {
return err
}
rootPool := x509.NewCertPool()
rootPool.AddCert(certs[len(certs)-1])
subPool := x509.NewCertPool()
for _, c := range certs[:len(certs)-1] {
subPool.AddCert(c)
}
err = cosign.TrustedCert(cert, rootPool, subPool)
if err != nil {
return err
}
}
verifier, err = signature.LoadECDSAVerifier(cert.PublicKey.(*ecdsa.PublicKey), crypto.SHA256)
if err != nil {
return err
Expand Down
1 change: 1 addition & 0 deletions doc/cosign_dockerfile_verify.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions doc/cosign_manifest_verify.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions doc/cosign_verify-attestation.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions doc/cosign_verify-blob.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions doc/cosign_verify.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions test/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -501,8 +501,8 @@ func TestSignBlob(t *testing.T) {
KeyRef: pubKeyPath2,
}
// Verify should fail on a bad input
mustErr(cliverify.VerifyBlobCmd(ctx, ko1, "" /*certRef*/, "" /*certEmail*/, "" /*certOidcIssuer*/, "badsig", blob), t)
mustErr(cliverify.VerifyBlobCmd(ctx, ko2, "" /*certRef*/, "" /*certEmail*/, "" /*certOidcIssuer*/, "badsig", blob), t)
mustErr(cliverify.VerifyBlobCmd(ctx, ko1, "" /*certRef*/, "" /*certEmail*/, "" /*certOidcIssuer*/, "" /*certChain*/, "badsig", blob), t)
mustErr(cliverify.VerifyBlobCmd(ctx, ko2, "" /*certRef*/, "" /*certEmail*/, "" /*certOidcIssuer*/, "" /*certChain*/, "badsig", blob), t)

// Now sign the blob with one key
ko := sign.KeyOpts{
Expand All @@ -514,8 +514,8 @@ func TestSignBlob(t *testing.T) {
t.Fatal(err)
}
// Now verify should work with that one, but not the other
must(cliverify.VerifyBlobCmd(ctx, ko1, "" /*certRef*/, "" /*certEmail*/, "" /*certOidcIssuer*/, string(sig), bp), t)
mustErr(cliverify.VerifyBlobCmd(ctx, ko2, "" /*certRef*/, "" /*certEmail*/, "" /*certOidcIssuer*/, string(sig), bp), t)
must(cliverify.VerifyBlobCmd(ctx, ko1, "" /*certRef*/, "" /*certEmail*/, "" /*certOidcIssuer*/, "" /*certChain*/, string(sig), bp), t)
mustErr(cliverify.VerifyBlobCmd(ctx, ko2, "" /*certRef*/, "" /*certEmail*/, "" /*certOidcIssuer*/, "" /*certChain*/, string(sig), bp), t)
}

func TestSignBlobBundle(t *testing.T) {
Expand Down

0 comments on commit faa1cbe

Please sign in to comment.