Skip to content

Commit

Permalink
add functional tests for CARoots handling
Browse files Browse the repository at this point in the history
Signed-off-by: Dmitry S <dsavints@gmail.com>
  • Loading branch information
dmitris committed Apr 3, 2024
1 parent 7ba741d commit f7157af
Show file tree
Hide file tree
Showing 2 changed files with 233 additions and 1 deletion.
3 changes: 3 additions & 0 deletions cmd/cosign/cli/verify/verify.go
Expand Up @@ -309,6 +309,9 @@ func (c *VerifyCommand) Exec(ctx context.Context, images []string) (err error) {
}
co.SCT = sct
}
default:
// Do nothing. Neither keyRef, c.Sk, nor certRef were set - can happen for example when using Fulcio and TSA.
// For an example see the TestAttachWithRFC3161Timestamp test in test/e2e_test.go.
}
co.SigVerifier = pubKey

Expand Down
231 changes: 230 additions & 1 deletion test/e2e_test.go
Expand Up @@ -24,6 +24,7 @@ import (
"crypto"
"crypto/rand"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/json"
"encoding/pem"
Expand Down Expand Up @@ -145,6 +146,33 @@ var verifyKeylessTSA = func(imageRef string, tsaCertChain string, skipSCT bool,
return cmd.Exec(context.Background(), args)
}

var verifyKeylessTSAWithCARoots = func(imageRef string,
caroots string, // filename of a PEM file with CA Roots certificates
intermediates string, // empty or filename of a PEM file with Intermediate certificates
certFile string, // filename of a PEM file with the codesigning certificate
tsaCertChain string,
skipSCT bool,
skipTlogVerify bool) error {
cmd := cliverify.VerifyCommand{
CertVerifyOptions: options.CertVerifyOptions{
CertOidcIssuerRegexp: ".*",
CertIdentityRegexp: ".*",
},
CertRef: certFile,
CARoots: caroots,
CAIntermediates: intermediates,
RekorURL: rekorURL,
HashAlgorithm: crypto.SHA256,
TSACertChainPath: tsaCertChain,
IgnoreSCT: skipSCT,
IgnoreTlog: skipTlogVerify,
MaxWorkers: 10,
}
args := []string{imageRef}

return cmd.Exec(context.Background(), args)
}

// Used to verify local images stored on disk
var verifyLocal = func(keyRef, path string, checkClaims bool, annotations map[string]interface{}, attachment string) error {
cmd := cliverify.VerifyCommand{
Expand Down Expand Up @@ -906,6 +934,203 @@ func TestAttestationRFC3161Timestamp(t *testing.T) {
must(verifyAttestation.Exec(ctx, []string{imgName}), t)
}

func TestVerifyWithCARoots(t *testing.T) {
ctx := context.Background()
// TSA server needed to create timestamp
viper.Set("timestamp-signer", "memory")
viper.Set("timestamp-signer-hash", "sha256")
apiServer := server.NewRestAPIServer("localhost", 0, []string{"http"}, false, 10*time.Second, 10*time.Second)
server := httptest.NewServer(apiServer.GetHandler())
t.Cleanup(server.Close)

repo, stop := reg(t)
defer stop()
td := t.TempDir()

imgName := path.Join(repo, "cosign-verify-caroots-e2e")

_, _, cleanup := mkimage(t, imgName)
defer cleanup()

b := bytes.Buffer{}
must(generate.GenerateCmd(context.Background(), options.RegistryOptions{}, imgName, nil, &b), t)

rootCert, rootKey, _ := GenerateRootCa()
subCert, subKey, _ := GenerateSubordinateCa(rootCert, rootKey)
leafCert, privKey, _ := GenerateLeafCert("subject@mail.com", "oidc-issuer", subCert, subKey)

pemRoot := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: rootCert.Raw})
pemSub := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: subCert.Raw})
pemLeaf := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: leafCert.Raw})

rootCert02, rootKey02, _ := GenerateRootCa()
subCert02, subKey02, _ := GenerateSubordinateCa(rootCert02, rootKey02)
leafCert02, _, _ := GenerateLeafCert("subject02@mail.com", "oidc-issuer02", subCert02, subKey02)
pemRoot02 := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: rootCert02.Raw})
pemSub02 := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: subCert02.Raw})
pemLeaf02 := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: leafCert02.Raw})
pemsubRef02 := mkfile(string(pemSub02), td, t)
pemrootRef02 := mkfile(string(pemRoot02), td, t)
pemleafRef02 := mkfile(string(pemLeaf02), td, t)

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

payloadref := mkfile(b.String(), td, t)

h := sha256.Sum256(b.Bytes())
signature, _ := privKey.Sign(rand.Reader, h[:], crypto.SHA256)
b64signature := base64.StdEncoding.EncodeToString([]byte(signature))
sigRef := mkfile(b64signature, td, t)
pemsubRef := mkfile(string(pemSub), td, t)
pemrootRef := mkfile(string(pemRoot), td, t)
pemleafRef := mkfile(string(pemLeaf), td, t)
certchainRef := mkfile(string(append(pemSub[:], pemRoot[:]...)), td, t)

pemrootBundleRef := mkfile(string(append(pemRoot[:], pemRoot02[:]...)), td, t)
pemsubBundleRef := mkfile(string(append(pemSub[:], pemSub02[:]...)), td, t)

tsclient, err := tsaclient.GetTimestampClient(server.URL)
if err != nil {
t.Error(err)
}

chain, err := tsclient.Timestamp.GetTimestampCertChain(nil)
if err != nil {
t.Fatalf("unexpected error getting timestamp chain: %v", err)
}

tsaChainRef, err := os.CreateTemp(os.TempDir(), "tempfile")
if err != nil {
t.Fatalf("error creating temp file: %v", err)
}
defer os.Remove(tsaChainRef.Name())
_, err = tsaChainRef.WriteString(chain.Payload)
if err != nil {
t.Fatalf("error writing chain payload to temp file: %v", err)
}

tsBytes, err := tsa.GetTimestampedSignature(signature, client.NewTSAClient(server.URL+"/api/v1/timestamp"))
if err != nil {
t.Fatalf("unexpected error creating timestamp: %v", err)
}
rfc3161TSRef := mkfile(string(tsBytes), td, t)

// Upload it!
err = attach.SignatureCmd(ctx, options.RegistryOptions{}, sigRef, payloadref, pemleafRef, certchainRef, rfc3161TSRef, "", imgName)
if err != nil {
t.Fatal(err)
}

// the following fields with non-changing values are logically "factored out" for brevity
// and passed to verifyKeylessTSAWithCARoots in the testing loop:
// imageName string
// tsaCertChainRef string
// skipSCT bool
// skipTlogVerify bool
tests := []struct {
name string
rootRef string
subRef string
leafRef string
wantError bool
}{
{
"verify with root, intermediate and leaf certificates",
pemrootRef,
pemsubRef,
pemleafRef,
false,
},
// NB - "confusely" switching the root and intermediate PEM files does _NOT_ (currently) produce an error
// - the Go crypto/x509 package doesn't strictly verify that the certificate chain is anchored
// in a self-signed root certificate. In this case, only the chain up to the intermediate
// certificate is verified, and the root certificate is ignored.
// See also https://gist.github.com/dmitris/15160f703b3038b1b00d03d3c7b66ce0 and in particular
// https://gist.github.com/dmitris/15160f703b3038b1b00d03d3c7b66ce0#file-main-go-L133-L135 as an example.
{
"switch root and intermediate no error",
pemsubRef,
pemrootRef,
pemleafRef,
false,
},
{
"leave out the root certificate",
"",
pemsubRef,
pemleafRef,
true,
},
{
"leave out the intermediate certificate",
pemrootRef,
"",
pemleafRef,
true,
},
{
"leave out the codesigning leaf certificate which is extracted from the image",
pemrootRef,
pemsubRef,
"",
false,
},
{
"wrong leaf certificate",
pemrootRef,
pemsubRef,
pemleafRef02,
true,
},
{
"root and intermediates bundles",
pemrootBundleRef,
pemsubBundleRef,
pemleafRef,
false,
},
{
"wrong root and intermediates bundles",
pemrootRef02,
pemsubRef02,
pemleafRef,
true,
},
{
"wrong root undle",
pemrootRef02,
pemsubBundleRef,
pemleafRef,
true,
},
{
"wrong intermediates bundle",
pemrootRef,
pemsubRef02,
pemleafRef,
true,
},
}
for _, tt := range tests {
err := verifyKeylessTSAWithCARoots(imgName,
tt.rootRef,
tt.subRef,
tt.leafRef,
tsaChainRef.Name(),
true,
true)
hasErr := (err != nil)
if hasErr != tt.wantError {
if tt.wantError {
t.Errorf("%s - no expected error", tt.name)
} else {
t.Errorf("%s - unexpected error: %v", tt.name, err)
}
}
}
}

func TestAttachWithRFC3161Timestamp(t *testing.T) {
ctx := context.Background()
// TSA server needed to create timestamp
Expand Down Expand Up @@ -946,6 +1171,10 @@ func TestAttachWithRFC3161Timestamp(t *testing.T) {
certchainRef := mkfile(string(append(pemSub[:], pemRoot[:]...)), td, t)

t.Setenv("SIGSTORE_ROOT_FILE", pemrootRef)
// reset the roots to use the root pointed by the environment variable SIGSTORE_ROOT_FILE
if err := fulcioroots.ReInit(); err != nil {
t.Fatal(err)
}

tsclient, err := tsaclient.GetTimestampClient(server.URL)
if err != nil {
Expand Down Expand Up @@ -2454,7 +2683,7 @@ func TestInvalidBundle(t *testing.T) {
t.Fatal(err)
}

// veriyfing image2 now should fail
// verifying image2 now should fail
cmd := cliverify.VerifyCommand{
KeyRef: pubKeyPath,
RekorURL: rekorURL,
Expand Down

0 comments on commit f7157af

Please sign in to comment.