Skip to content

Commit

Permalink
Add policy flag to enforce SCT
Browse files Browse the repository at this point in the history
Signed-off-by: Hayden Blauzvern <hblauzvern@google.com>
  • Loading branch information
haydentherapper committed Apr 13, 2022
1 parent 557aef0 commit 71fb752
Show file tree
Hide file tree
Showing 15 changed files with 68 additions and 12 deletions.
1 change: 1 addition & 0 deletions cmd/cosign/cli/dockerfile.go
Expand Up @@ -92,6 +92,7 @@ Shell-like variables in the Dockerfile's FROM lines will be substituted with val
CertEmail: o.CertVerify.CertEmail,
CertOidcIssuer: o.CertVerify.CertOidcIssuer,
CertChain: o.CertVerify.CertChain,
EnforceSCT: o.CertVerify.EnforceSCT,
Sk: o.SecurityKey.Use,
Slot: o.SecurityKey.Slot,
Output: o.Output,
Expand Down
1 change: 1 addition & 0 deletions cmd/cosign/cli/manifest.go
Expand Up @@ -87,6 +87,7 @@ against the transparency log.`,
CertEmail: o.CertVerify.CertEmail,
CertOidcIssuer: o.CertVerify.CertOidcIssuer,
CertChain: o.CertVerify.CertChain,
EnforceSCT: o.CertVerify.EnforceSCT,
Sk: o.SecurityKey.Use,
Slot: o.SecurityKey.Slot,
Output: o.Output,
Expand Down
5 changes: 5 additions & 0 deletions cmd/cosign/cli/options/certificate.go
Expand Up @@ -24,6 +24,7 @@ type CertVerifyOptions struct {
CertEmail string
CertOidcIssuer string
CertChain string
EnforceSCT bool
}

var _ Interface = (*RekorOptions)(nil)
Expand All @@ -44,4 +45,8 @@ func (o *CertVerifyOptions) AddFlags(cmd *cobra.Command) {
"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")

cmd.Flags().BoolVar(&o.EnforceSCT, "enforce-sct", false,
"whether to enforce that a certificate contain an embedded SCT, a proof of "+
"inclusion in a certificate transparency log")
}
5 changes: 4 additions & 1 deletion cmd/cosign/cli/verify.go
Expand Up @@ -97,6 +97,7 @@ against the transparency log.`,
CertEmail: o.CertVerify.CertEmail,
CertOidcIssuer: o.CertVerify.CertOidcIssuer,
CertChain: o.CertVerify.CertChain,
EnforceSCT: o.CertVerify.EnforceSCT,
Sk: o.SecurityKey.Use,
Slot: o.SecurityKey.Slot,
Output: o.Output,
Expand Down Expand Up @@ -174,6 +175,7 @@ against the transparency log.`,
CertEmail: o.CertVerify.CertEmail,
CertOidcIssuer: o.CertVerify.CertOidcIssuer,
CertChain: o.CertVerify.CertChain,
EnforceSCT: o.CertVerify.EnforceSCT,
KeyRef: o.Key,
Sk: o.SecurityKey.Use,
Slot: o.SecurityKey.Slot,
Expand Down Expand Up @@ -252,7 +254,8 @@ 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.CertVerify.CertChain, o.Signature, args[0]); err != nil {
o.CertVerify.CertEmail, o.CertVerify.CertOidcIssuer, o.CertVerify.CertChain,
o.Signature, args[0], o.CertVerify.EnforceSCT); err != nil {
return errors.Wrapf(err, "verifying blob %s", args)
}
return nil
Expand Down
2 changes: 2 additions & 0 deletions cmd/cosign/cli/verify/verify.go
Expand Up @@ -54,6 +54,7 @@ type VerifyCommand struct {
CertEmail string
CertOidcIssuer string
CertChain string
EnforceSCT bool
Sk bool
Slot string
Output string
Expand Down Expand Up @@ -95,6 +96,7 @@ func (c *VerifyCommand) Exec(ctx context.Context, images []string) (err error) {
RegistryClientOpts: ociremoteOpts,
CertEmail: c.CertEmail,
CertOidcIssuer: c.CertOidcIssuer,
EnforceSCT: c.EnforceSCT,
SignatureRef: c.SignatureRef,
}
if c.CheckClaims {
Expand Down
2 changes: 2 additions & 0 deletions cmd/cosign/cli/verify/verify_attestation.go
Expand Up @@ -50,6 +50,7 @@ type VerifyAttestationCommand struct {
CertEmail string
CertOidcIssuer string
CertChain string
EnforceSCT bool
Sk bool
Slot string
Output string
Expand Down Expand Up @@ -77,6 +78,7 @@ func (c *VerifyAttestationCommand) Exec(ctx context.Context, images []string) (e
RegistryClientOpts: ociremoteOpts,
CertEmail: c.CertEmail,
CertOidcIssuer: c.CertOidcIssuer,
EnforceSCT: c.EnforceSCT,
}
if c.CheckClaims {
co.ClaimVerifier = cosign.IntotoSubjectClaimVerifier
Expand Down
10 changes: 7 additions & 3 deletions cmd/cosign/cli/verify/verify_blob.go
Expand Up @@ -61,7 +61,8 @@ func isb64(data []byte) bool {
}

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

Expand Down Expand Up @@ -119,6 +120,7 @@ func VerifyBlobCmd(ctx context.Context, ko sign.KeyOpts, certRef, certEmail, cer
co := &cosign.CheckOpts{
CertEmail: certEmail,
CertOidcIssuer: certOidcIssuer,
EnforceSCT: enforceSCT,
}
verifier, err = cosign.ValidateAndUnpackCertWithChain(cert, chain, co)
if err != nil {
Expand Down Expand Up @@ -162,7 +164,7 @@ func VerifyBlobCmd(ctx context.Context, ko sign.KeyOpts, certRef, certEmail, cer
if len(uuids) == 0 {
return errors.New("could not find a tlog entry for provided blob")
}
return verifySigByUUID(ctx, ko, rClient, certEmail, certOidcIssuer, sig, b64sig, uuids, blobBytes)
return verifySigByUUID(ctx, ko, rClient, certEmail, certOidcIssuer, sig, b64sig, uuids, blobBytes, enforceSCT)
}

// Use the DSSE verifier if the payload is a DSSE with the In-Toto format.
Expand All @@ -184,7 +186,8 @@ func VerifyBlobCmd(ctx context.Context, ko sign.KeyOpts, certRef, certEmail, cer
return nil
}

func verifySigByUUID(ctx context.Context, ko sign.KeyOpts, rClient *client.Rekor, certEmail, certOidcIssuer, sig, b64sig string, uuids []string, blobBytes []byte) error {
func verifySigByUUID(ctx context.Context, ko sign.KeyOpts, rClient *client.Rekor, certEmail, certOidcIssuer, sig, b64sig string,
uuids []string, blobBytes []byte, enforceSCT bool) error {
var validSigExists bool
for _, u := range uuids {
tlogEntry, err := cosign.GetTlogEntry(ctx, rClient, u)
Expand All @@ -202,6 +205,7 @@ func verifySigByUUID(ctx context.Context, ko sign.KeyOpts, rClient *client.Rekor
IntermediateCerts: fulcio.GetIntermediates(),
CertEmail: certEmail,
CertOidcIssuer: certOidcIssuer,
EnforceSCT: enforceSCT,
}
cert := certs[0]
verifier, err := cosign.ValidateAndUnpackCert(cert, co)
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.

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

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

7 changes: 6 additions & 1 deletion pkg/cosign/verify.go
Expand Up @@ -82,6 +82,9 @@ type CheckOpts struct {
CertEmail string
// CertOidcIssuer is the OIDC issuer expected for a certificate to be valid. The empty string means any certificate can be valid.
CertOidcIssuer string
// EnforceSCT requires that a certificate contain an embedded SCT during verification. An SCT is proof of inclusion in a
// certificate transparency log.
EnforceSCT bool

// SignatureRef is the reference to the signature file
SignatureRef string
Expand Down Expand Up @@ -180,11 +183,13 @@ func ValidateAndUnpackCert(cert *x509.Certificate, co *CheckOpts) (signature.Ver
return nil, errors.New("expected oidc issuer not found in certificate")
}
}
// TODO: Add flag in CheckOpts to enforce embedded SCT
contains, err := ctl.ContainsSCT(cert.Raw)
if err != nil {
return nil, err
}
if co.EnforceSCT && !contains {
return nil, errors.New("certificate does not include required embedded SCT")
}
if contains {
// handle if chains has more than one chain - grab first and print message
if len(chains) > 1 {
Expand Down
28 changes: 28 additions & 0 deletions pkg/cosign/verify_test.go
Expand Up @@ -315,6 +315,34 @@ func TestValidateAndUnpackCertWithSCT(t *testing.T) {
if err != nil {
t.Errorf("ValidateAndUnpackCert expected no error, got err = %v", err)
}

// validate again, explicitly setting enforce SCT
co.EnforceSCT = true
_, err = ValidateAndUnpackCert(chain[0], co)
if err != nil {
t.Errorf("ValidateAndUnpackCert expected no error, got err = %v", err)
}
}

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

rootCert, rootKey, _ := test.GenerateRootCa()
leafCert, _, _ := test.GenerateLeafCert(subject, oidcIssuer, rootCert, rootKey)

rootPool := x509.NewCertPool()
rootPool.AddCert(rootCert)

co := &CheckOpts{
RootCerts: rootPool,
CertEmail: subject,
CertOidcIssuer: oidcIssuer,
EnforceSCT: true,
}

_, err := ValidateAndUnpackCert(leafCert, co)
require.Contains(t, err.Error(), "certificate does not include required embedded SCT")
}

func TestValidateAndUnpackCertInvalidRoot(t *testing.T) {
Expand Down
14 changes: 7 additions & 7 deletions test/e2e_test.go
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*/, "" /*certChain*/, "badsig", blob), t)
mustErr(cliverify.VerifyBlobCmd(ctx, ko2, "" /*certRef*/, "" /*certEmail*/, "" /*certOidcIssuer*/, "" /*certChain*/, "badsig", blob), t)
mustErr(cliverify.VerifyBlobCmd(ctx, ko1, "" /*certRef*/, "" /*certEmail*/, "" /*certOidcIssuer*/, "" /*certChain*/, "badsig", blob, false), t)
mustErr(cliverify.VerifyBlobCmd(ctx, ko2, "" /*certRef*/, "" /*certEmail*/, "" /*certOidcIssuer*/, "" /*certChain*/, "badsig", blob, false), 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*/, "" /*certChain*/, string(sig), bp), t)
mustErr(cliverify.VerifyBlobCmd(ctx, ko2, "" /*certRef*/, "" /*certEmail*/, "" /*certOidcIssuer*/, "" /*certChain*/, string(sig), bp), t)
must(cliverify.VerifyBlobCmd(ctx, ko1, "" /*certRef*/, "" /*certEmail*/, "" /*certOidcIssuer*/, "" /*certChain*/, string(sig), bp, false), t)
mustErr(cliverify.VerifyBlobCmd(ctx, ko2, "" /*certRef*/, "" /*certEmail*/, "" /*certOidcIssuer*/, "" /*certChain*/, string(sig), bp, false), t)
}

func TestSignBlobBundle(t *testing.T) {
Expand All @@ -540,7 +540,7 @@ func TestSignBlobBundle(t *testing.T) {
BundlePath: bundlePath,
}
// Verify should fail on a bad input
mustErr(cliverify.VerifyBlobCmd(ctx, ko1, "", "", "", "", "", blob), t)
mustErr(cliverify.VerifyBlobCmd(ctx, ko1, "", "", "", "", "", blob, false), t)

// Now sign the blob with one key
ko := sign.KeyOpts{
Expand All @@ -553,7 +553,7 @@ func TestSignBlobBundle(t *testing.T) {
t.Fatal(err)
}
// Now verify should work
must(cliverify.VerifyBlobCmd(ctx, ko1, "", "", "", "", "", bp), t)
must(cliverify.VerifyBlobCmd(ctx, ko1, "", "", "", "", "", bp, false), t)

// Now we turn on the tlog and sign again
defer setenv(t, options.ExperimentalEnv, "1")()
Expand All @@ -563,7 +563,7 @@ func TestSignBlobBundle(t *testing.T) {

// Point to a fake rekor server to make sure offline verification of the tlog entry works
os.Setenv(serverEnv, "notreal")
must(cliverify.VerifyBlobCmd(ctx, ko1, "", "", "", "", "", bp), t)
must(cliverify.VerifyBlobCmd(ctx, ko1, "", "", "", "", "", bp, false), t)
}

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

0 comments on commit 71fb752

Please sign in to comment.