Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: fix cert chain validation for verify-blob in non-experimental mode #2256

Merged
merged 5 commits into from Sep 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
34 changes: 30 additions & 4 deletions cmd/cosign/cli/verify/verify_blob.go
Expand Up @@ -107,7 +107,7 @@ func VerifyBlobCmd(ctx context.Context, ko options.KeyOpts, certRef, certEmail,
co.RekorClient = rekorClient
}
}
if certRef == "" || options.EnableExperimental() {
if options.EnableExperimental() {
co.RootCerts, err = fulcio.GetRoots()
if err != nil {
return fmt.Errorf("getting Fulcio roots: %w", err)
Expand Down Expand Up @@ -145,9 +145,18 @@ func VerifyBlobCmd(ctx context.Context, ko options.KeyOpts, certRef, certEmail,
return err
}
if certChain == "" {
co.RootCerts, err = fulcio.GetRoots()
asraa marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return fmt.Errorf("getting Fulcio roots: %w", err)
}

co.IntermediateCerts, err = fulcio.GetIntermediates()
if err != nil {
return fmt.Errorf("getting Fulcio intermediates: %w", err)
}
co.SigVerifier, err = cosign.ValidateAndUnpackCert(cert, co)
if err != nil {
return err
return fmt.Errorf("validating certRef: %w", err)
}
} else {
// Verify certificate with chain
Expand Down Expand Up @@ -178,10 +187,27 @@ func VerifyBlobCmd(ctx context.Context, ko options.KeyOpts, certRef, certEmail,
// check if cert is actually a public key
co.SigVerifier, err = sigs.LoadPublicKeyRaw(certBytes, crypto.SHA256)
} else {
co.SigVerifier, err = cosign.ValidateAndUnpackCert(cert, co)
if certChain == "" {
co.RootCerts, err = fulcio.GetRoots()
if err != nil {
return fmt.Errorf("getting Fulcio roots: %w", err)
}
co.IntermediateCerts, err = fulcio.GetIntermediates()
if err != nil {
return fmt.Errorf("getting Fulcio intermediates: %w", err)
}
co.SigVerifier, err = cosign.ValidateAndUnpackCert(cert, co)
} else {
// Verify certificate with chain
chain, err := loadCertChainFromFileOrURL(certChain)
if err != nil {
return err
}
co.SigVerifier, err = cosign.ValidateAndUnpackCertWithChain(cert, chain, co)
}
}
if err != nil {
return err
return fmt.Errorf("loading verifier from bundle: %w", err)
}
bundle = b.Bundle
// No certificate is provided: search by artifact sha in the TLOG.
Expand Down
162 changes: 160 additions & 2 deletions cmd/cosign/cli/verify/verify_blob_test.go
Expand Up @@ -434,7 +434,6 @@ func TestVerifyBlob(t *testing.T) {
experimental: false,
shouldErr: true,
},

{
name: "valid signature with expired certificate - experimental good rekor lookup",
blob: blobBytes,
Expand All @@ -446,7 +445,6 @@ func TestVerifyBlob(t *testing.T) {
expiredLeafPem, true),
shouldErr: false,
},

{
name: "valid signature with expired certificate - experimental bad rekor integrated time",
blob: blobBytes,
Expand Down Expand Up @@ -957,6 +955,166 @@ func TestVerifyBlobCmdWithBundle(t *testing.T) {
t.Fatalf("expected error with mismatched issuer, got %v", err)
}
})
t.Run("Implicit Fulcio chain with bundle in non-experimental mode", func(t *testing.T) {
identity := "hello@foo.com"
issuer := "issuer"
leafCert, _, leafPemCert, signer := keyless.genLeafCert(t, identity, issuer)

// Create blob
blob := "someblob"

// Sign blob with private key
sig, err := signer.SignMessage(bytes.NewReader([]byte(blob)))
if err != nil {
t.Fatal(err)
}

// Create bundle
entry := genRekorEntry(t, hashedrekord.KIND, hashedrekord.New().DefaultVersion(), []byte(blob), leafPemCert, sig)
b := createBundle(t, sig, leafPemCert, keyless.rekorLogID, leafCert.NotBefore.Unix()+1, entry)
b.Bundle.SignedEntryTimestamp = keyless.rekorSignPayload(t, b.Bundle.Payload)
bundlePath := writeBundleFile(t, keyless.td, b, "bundle.json")
blobPath := writeBlobFile(t, keyless.td, blob, "blob.txt")
certPath := writeBlobFile(t, keyless.td, string(leafPemCert), "cert.pem")

// Verify command
err = VerifyBlobCmd(context.Background(),
options.KeyOpts{BundlePath: bundlePath},
certPath, /*certRef*/
identity, /*certEmail*/
issuer, /*certOidcIssuer*/
"", /*certChain*/ // Chain is fetched from TUF/SIGSTORE_ROOT_FILE
"", /*sigRef*/ // Sig is fetched from bundle
blobPath, /*blobRef*/
// GitHub identity flags start
"", "", "", "", "",
// GitHub identity flags end
false /*enforceSCT*/)
if err != nil {
t.Fatalf("expected success without specifying the intermediates, got %v", err)
}
})
t.Run("Explicit Fulcio chain with bundle in non-experimental mode", func(t *testing.T) {
identity := "hello@foo.com"
issuer := "issuer"
leafCert, _, leafPemCert, signer := keyless.genLeafCert(t, identity, issuer)

// Create blob
blob := "someblob"

// Sign blob with private key
sig, err := signer.SignMessage(bytes.NewReader([]byte(blob)))
if err != nil {
t.Fatal(err)
}

// Create bundle
entry := genRekorEntry(t, hashedrekord.KIND, hashedrekord.New().DefaultVersion(), []byte(blob), leafPemCert, sig)
b := createBundle(t, sig, leafPemCert, keyless.rekorLogID, leafCert.NotBefore.Unix()+1, entry)
b.Bundle.SignedEntryTimestamp = keyless.rekorSignPayload(t, b.Bundle.Payload)
bundlePath := writeBundleFile(t, keyless.td, b, "bundle.json")
blobPath := writeBlobFile(t, keyless.td, blob, "blob.txt")

// Verify command
err = VerifyBlobCmd(context.Background(),
options.KeyOpts{BundlePath: bundlePath},
"", /*certRef*/
identity, /*certEmail*/
issuer, /*certOidcIssuer*/
os.Getenv("SIGSTORE_ROOT_FILE"), /*certChain*/
"", /*sigRef*/ // Sig is fetched from bundle
blobPath, /*blobRef*/
// GitHub identity flags start
"", "", "", "", "",
// GitHub identity flags end
false /*enforceSCT*/)
if err != nil {
t.Fatalf("expected success specifying the intermediates, got %v", err)
}
})
}

func TestVerifyBlobCmdInvalidRootCA(t *testing.T) {
keyless := newKeylessStack(t)
// Change the keyless stack.
_ = newKeylessStack(t)
t.Run("Invalid certificate root when specifying cert via certRef", func(t *testing.T) {
identity := "hello@foo.com"
issuer := "issuer"
leafCert, _, leafPemCert, signer := keyless.genLeafCert(t, identity, issuer)

// Create blob
blob := "someblob"

// Sign blob with private key
sig, err := signer.SignMessage(bytes.NewReader([]byte(blob)))
if err != nil {
t.Fatal(err)
}

// Create bundle
entry := genRekorEntry(t, hashedrekord.KIND, hashedrekord.New().DefaultVersion(), []byte(blob), leafPemCert, sig)
b := createBundle(t, sig, leafPemCert, keyless.rekorLogID, leafCert.NotBefore.Unix()+1, entry)
b.Bundle.SignedEntryTimestamp = keyless.rekorSignPayload(t, b.Bundle.Payload)
bundlePath := writeBundleFile(t, keyless.td, b, "bundle.json")
blobPath := writeBlobFile(t, keyless.td, blob, "blob.txt")
certPath := writeBlobFile(t, keyless.td, string(leafPemCert), "cert.pem")

// Verify command
err = VerifyBlobCmd(context.Background(),
options.KeyOpts{BundlePath: bundlePath},
certPath, /*certRef*/
identity, /*certEmail*/
issuer, /*certOidcIssuer*/
"", /*certChain*/ // Chain is fetched from TUF/SIGSTORE_ROOT_FILE
"", /*sigRef*/ // Sig is fetched from bundle
blobPath, /*blobRef*/
// GitHub identity flags start
"", "", "", "", "",
// GitHub identity flags end
false /*enforceSCT*/)
if err == nil || !strings.Contains(err.Error(), "certificate signed by unknown authority") {
t.Fatalf("expected error with invalid root CA, got %v", err)
}
})
t.Run("Invalid certificate root when specifying cert in bundle", func(t *testing.T) {
identity := "hello@foo.com"
issuer := "issuer"
leafCert, _, leafPemCert, signer := keyless.genLeafCert(t, identity, issuer)

// Create blob
blob := "someblob"

// Sign blob with private key
sig, err := signer.SignMessage(bytes.NewReader([]byte(blob)))
if err != nil {
t.Fatal(err)
}

// Create bundle
entry := genRekorEntry(t, hashedrekord.KIND, hashedrekord.New().DefaultVersion(), []byte(blob), leafPemCert, sig)
b := createBundle(t, sig, leafPemCert, keyless.rekorLogID, leafCert.NotBefore.Unix()+1, entry)
b.Bundle.SignedEntryTimestamp = keyless.rekorSignPayload(t, b.Bundle.Payload)
bundlePath := writeBundleFile(t, keyless.td, b, "bundle.json")
blobPath := writeBlobFile(t, keyless.td, blob, "blob.txt")

// Verify command
err = VerifyBlobCmd(context.Background(),
options.KeyOpts{BundlePath: bundlePath},
"", /*certRef*/ // Fetched from bundle
identity, /*certEmail*/
issuer, /*certOidcIssuer*/
"", /*certChain*/ // Chain is fetched from TUF/SIGSTORE_ROOT_FILE
"", /*sigRef*/ // Sig is fetched from bundle
blobPath, /*blobRef*/
// GitHub identity flags start
"", "", "", "", "",
// GitHub identity flags end
false /*enforceSCT*/)
if err == nil || !strings.Contains(err.Error(), "certificate signed by unknown authority") {
t.Fatalf("expected error with invalid root CA, got %v", err)
}
})
}

type keylessStack struct {
Expand Down