Repository: deislabs/ratify (go)

Issue:
Migrate Cosign verifier to be built in
### What would you like to be added?

Current cosign verifier is implemented as an external plugin. With new work being done to improve key + certificate config support with cosign, it will be better for Cosign to be a built in verifier. Plus, notation is already a built in and cosign is an equivalent signature standard.

### Anything else you would like to add?

_No response_

### Are you willing to submit PRs to contribute to this feature?

- [X] Yes, I am willing to implement it.

Pull request:
feat: move cosign to be a built in verifier
# Description

## What this PR does / why we need it:
- creates new `cosign` package in `verifier` package so cosign is a built in verifier
- implements verifier factory and verifier interfaces to be compliant
- deletes cosign plugin implementation (there will be NO changes from a user POV)
- updates go releaser to remove cosign binary publishing and introduces missing vulnerability report binary publishing
- updates all docs/guides to remove plugin cosign reference

## Which issue(s) this PR fixes *(optional, using `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close the issue(s) when the PR gets merged)*:
Fixes #1299

## Type of change

Please delete options that are not relevant.

- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] Helm Chart Change (any edit/addition/update that is necessary for changes merged to the `main` branch)
- [ ] This change requires a documentation update

# How Has This Been Tested?

Please describe the tests that you ran to verify your changes. Please also list any relevant details for your test configuration

- [ ] Test A
- [ ] Test B

# Checklist:

- [ ] Does the affected code have corresponding tests?
- [ ] Are the changes documented, not just with inline documentation, but also with conceptual documentation such as an overview of a new feature, or task-based documentation like a tutorial? Consider if this change should be announced on your project blog.
- [ ] Does this introduce breaking changes that would require an announcement or bumping the major version?
- [ ] Do all new files have appropriate license header?

# Post Merge Requirements
- [ ] MAINTAINERS: manually trigger the "Publish Package" workflow after merging any PR that indicates `Helm Chart Change`



Patch:
diff --git a/.github/workflows/publish-dev-assets.yml b/.github/workflows/publish-dev-assets.yml
index a5d353c..e24bbbd 100644
--- a/.github/workflows/publish-dev-assets.yml
+++ b/.github/workflows/publish-dev-assets.yml
@@ -61,7 +61,6 @@ jobs:
           docker buildx create --use
           docker buildx build -f ./httpserver/Dockerfile \
             --platform linux/amd64,linux/arm64,linux/arm/v7 \
-            --build-arg build_cosign=true \
             --build-arg build_sbom=true \
             --build-arg build_licensechecker=true \
             --build-arg build_schemavalidator=true \
diff --git a/.github/workflows/publish-package.yml b/.github/workflows/publish-package.yml
index fe917aa..77103e1 100644
--- a/.github/workflows/publish-package.yml
+++ b/.github/workflows/publish-package.yml
@@ -59,7 +59,6 @@ jobs:
           docker buildx create --use
           docker buildx build -f ./httpserver/Dockerfile \
             --platform linux/amd64,linux/arm64,linux/arm/v7 \
-            --build-arg build_cosign=true \
             --build-arg build_sbom=true \
             --build-arg build_licensechecker=true \
             --build-arg build_schemavalidator=true \
diff --git a/.goreleaser.yml b/.goreleaser.yml
index b0f9f2b..994d943 100644
--- a/.goreleaser.yml
+++ b/.goreleaser.yml
@@ -36,10 +36,10 @@ builds:
     ignore:
       - goos: windows
         goarch: arm64
-
-  - id: cosign
-    dir: plugins/verifier/cosign
-    binary: cosign
+  
+  - id: vulnerabilityreport
+    dir: plugins/verifier/vulnerabilityreport
+    binary: vulnerabilityreport
     env:
       - CGO_ENABLED=0
     goos:
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 04f87f5..3f39685 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -77,16 +77,16 @@
       ],
     },
     {
-      "name": "Debug Cosign Plugin",
+      "name": "Debug SBOM Plugin",
       "type": "go",
       "request": "launch",
       "mode": "auto",
-      "program": "${workspaceFolder}/plugins/verifier/cosign",
+      "program": "${workspaceFolder}/plugins/verifier/sbom",
       "env": {
         "RATIFY_EXPERIMENTAL_DYNAMIC_PLUGINS": "1",
         "RATIFY_LOG_LEVEL": "debug",
         "RATIFY_VERIFIER_COMMAND": "VERIFY",
-        "RATIFY_VERIFIER_SUBJECT": "wabbitnetworks.azurecr.io/test/cosign-image:signed",
+        "RATIFY_VERIFIER_SUBJECT": "wabbitnetworks.azurecr.io/test/image:sbom",
         "RATIFY_VERIFIER_VERSION": "1.0.0",
       },
       "console": "integratedTerminal"
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index a64269c..f6ee145 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -84,16 +84,16 @@ Sample launch json for debugging a plugin:
 {
   "version": "0.2.0",
   "configurations": [{
-    "name": "Debug Cosign Plugin",
+    "name": "Debug SBOM Plugin",
     "type": "go",
     "request": "launch",
     "mode": "auto",
-    "program": "${workspaceFolder}/plugins/verifier/cosign",
+    "program": "${workspaceFolder}/plugins/verifier/sbom",
     "env": {
       "RATIFY_EXPERIMENTAL_DYNAMIC_PLUGINS": "1",
       "RATIFY_LOG_LEVEL": "debug",
       "RATIFY_VERIFIER_COMMAND": "VERIFY",
-      "RATIFY_VERIFIER_SUBJECT": "wabbitnetworks.azurecr.io/test/cosign-image:signed",
+      "RATIFY_VERIFIER_SUBJECT": "wabbitnetworks.azurecr.io/test/image:sbom",
       "RATIFY_VERIFIER_VERSION": "1.0.0",
     },
     "console": "integratedTerminal"
@@ -107,15 +107,15 @@ Sample JSON stdin
 ```json
 {
   "config": {
-    "artifactTypes":"application/vnd.dev.cosign.artifact.sig.v1+json",
-    "key":"/home/akashsinghal/ratify/.staging/cosign/cosign.pub",
-    "name":"cosign"
+    "artifactTypes":"application/spdx+json",
+    "name":"sbom",
+    "disallowedLicenses":["AGPL"],
+    "disallowedPackages":[{"name":"log4j-core","versionInfo":"2.13.0"}]
   },
   "storeConfig": {
     "version":"1.0.0",
     "pluginBinDirs":null,
     "store": {
-      "cosignEnabled":true,
       "name":"oras",
       "useHttp":true
     }
@@ -124,7 +124,7 @@ Sample JSON stdin
     "mediaType":"application/vnd.oci.image.manifest.v1+json",
     "digest":"sha256:...",
     "size":558,
-    "artifactType":"application/vnd.dev.cosign.artifact.sig.v1+json"
+    "artifactType":"application/spdx+json"
   }
 }
 ```
@@ -144,7 +144,7 @@ Follow the steps below to build and deploy a Ratify image with your private chan
 export REGISTRY=yourregistry
 docker buildx create --use
 
-docker buildx build -f httpserver/Dockerfile --platform linux/amd64 --build-arg build_cosign=true --build-arg build_sbom=true --build-arg build_licensechecker=true --build-arg build_schemavalidator=true --build-arg build_vulnerabilityreport=true -t ${REGISTRY}/deislabs/ratify:yourtag .
+docker buildx build -f httpserver/Dockerfile --platform linux/amd64 --build-arg build_sbom=true --build-arg build_licensechecker=true --build-arg build_schemavalidator=true --build-arg build_vulnerabilityreport=true -t ${REGISTRY}/deislabs/ratify:yourtag .
 docker build --progress=plain --build-arg KUBE_VERSION="1.27.7" --build-arg TARGETOS="linux" --build-arg TARGETARCH="amd64" -f crd.Dockerfile -t ${REGISTRY}/localbuildcrd:yourtag ./charts/ratify/crds
 ```
 
diff --git a/Makefile b/Makefile
index 6a4f612..923edb6 100644
--- a/Makefile
+++ b/Makefile
@@ -75,7 +75,6 @@ build-cli: fmt vet
 
 .PHONY: build-plugins
 build-plugins:
-	go build -cover -coverpkg=github.com/deislabs/ratify/plugins/verifier/cosign/... -o ./bin/plugins/ ./plugins/verifier/cosign
 	go build -cover -coverpkg=github.com/deislabs/ratify/plugins/verifier/licensechecker/... -o ./bin/plugins/ ./plugins/verifier/licensechecker
 	go build -cover -coverpkg=github.com/deislabs/ratify/plugins/verifier/sample/... -o ./bin/plugins/ ./plugins/verifier/sample
 	go build -cover -coverpkg=github.com/deislabs/ratify/plugins/referrerstore/sample/... -o ./bin/plugins/referrerstore/ ./plugins/referrerstore/sample
@@ -530,7 +529,6 @@ e2e-deploy-ratify: e2e-notation-setup e2e-notation-leaf-cert-setup e2e-cosign-se
 
 e2e-build-local-ratify-image:
 	docker build --progress=plain --no-cache \
-	--build-arg build_cosign=true \
 	--build-arg build_sbom=true \
 	--build-arg build_licensechecker=true \
 	--build-arg build_schemavalidator=true \
diff --git a/cmd/ratify/main.go b/cmd/ratify/main.go
index 10f1373..cafaaaf 100644
--- a/cmd/ratify/main.go
+++ b/cmd/ratify/main.go
@@ -24,6 +24,7 @@ import (
 	_ "github.com/deislabs/ratify/pkg/policyprovider/configpolicy" // register configpolicy policy provider
 	_ "github.com/deislabs/ratify/pkg/policyprovider/regopolicy"   // register regopolicy policy provider
 	_ "github.com/deislabs/ratify/pkg/referrerstore/oras"          // register oras referrer store
+	_ "github.com/deislabs/ratify/pkg/verifier/cosign"             // register cosign verifier
 	_ "github.com/deislabs/ratify/pkg/verifier/notation"           // register notation verifier
 )
 
diff --git a/httpserver/Dockerfile b/httpserver/Dockerfile
index 2528463..678b523 100644
--- a/httpserver/Dockerfile
+++ b/httpserver/Dockerfile
@@ -23,7 +23,6 @@ ARG TARGETVARIANT=""
 ARG LDFLAGS
 ARG GOPROXY
 ARG build_sbom
-ARG build_cosign
 ARG build_licensechecker
 ARG build_schemavalidator
 ARG build_vulnerabilityreport
@@ -41,7 +40,6 @@ COPY . .
 RUN go build -ldflags "${LDFLAGS}" -o /app/out/ /app/cmd/ratify
 RUN mkdir /app/out/plugins
 RUN if [ "$build_sbom" = "true" ]; then go build -o /app/out/plugins/ /app/plugins/verifier/sbom; fi
-RUN if [ "$build_cosign" = "true" ]; then go build -o /app/out/plugins/ /app/plugins/verifier/cosign; fi
 RUN if [ "$build_licensechecker" = "true" ]; then go build -o /app/out/plugins/ /app/plugins/verifier/licensechecker; fi
 RUN if [ "$build_schemavalidator" = "true" ]; then go build -o /app/out/plugins/ /app/plugins/verifier/schemavalidator; fi
 RUN if [ "$build_vulnerabilityreport" = "true" ]; then go build -o /app/out/plugins/ /app/plugins/verifier/vulnerabilityreport; fi
diff --git a/pkg/verifier/cosign/cosign.go b/pkg/verifier/cosign/cosign.go
new file mode 100644
index 0000000..957c232
--- /dev/null
+++ b/pkg/verifier/cosign/cosign.go
@@ -0,0 +1,324 @@
+/*
+Copyright The Ratify Authors.
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package cosign
+
+import (
+	"context"
+	"crypto"
+	"crypto/x509"
+	"encoding/json"
+	"fmt"
+	"os"
+	"path/filepath"
+	"strings"
+
+	"github.com/deislabs/ratify/internal/logger"
+	"github.com/deislabs/ratify/pkg/common"
+	"github.com/deislabs/ratify/pkg/ocispecs"
+	"github.com/deislabs/ratify/pkg/referrerstore"
+	"github.com/deislabs/ratify/pkg/utils"
+	"github.com/deislabs/ratify/pkg/verifier"
+	"github.com/deislabs/ratify/pkg/verifier/config"
+	"github.com/deislabs/ratify/pkg/verifier/factory"
+	"github.com/deislabs/ratify/pkg/verifier/types"
+
+	re "github.com/deislabs/ratify/errors"
+	v1 "github.com/google/go-containerregistry/pkg/v1"
+	"github.com/opencontainers/go-digest"
+	imgspec "github.com/opencontainers/image-spec/specs-go/v1"
+	"github.com/pkg/errors"
+	"github.com/sigstore/cosign/v2/cmd/cosign/cli/fulcio"
+	"github.com/sigstore/cosign/v2/cmd/cosign/cli/rekor"
+	"github.com/sigstore/cosign/v2/pkg/cosign"
+	"github.com/sigstore/cosign/v2/pkg/cosign/bundle"
+	"github.com/sigstore/cosign/v2/pkg/oci"
+	"github.com/sigstore/cosign/v2/pkg/oci/static"
+	"github.com/sigstore/sigstore/pkg/signature"
+)
+
+type PluginConfig struct {
+	Name             string   `json:"name"`
+	Type             string   `json:"type,omitempty"`
+	ArtifactTypes    string   `json:"artifactTypes"`
+	KeyRef           string   `json:"key,omitempty"`
+	RekorURL         string   `json:"rekorURL,omitempty"`
+	NestedReferences []string `json:"nestedArtifactTypes,omitempty"`
+}
+
+type Extension struct {
+	SignatureExtension []cosignExtension `json:"signatures,omitempty"`
+}
+
+type cosignExtension struct {
+	SignatureDigest digest.Digest `json:"signatureDigest"`
+	IsSuccess       bool          `json:"isSuccess"`
+	BundleVerified  bool          `json:"bundleVerified"`
+	Err             string        `json:"error,omitempty"`
+}
+
+type cosignVerifier struct {
+	name             string
+	verifierType     string
+	artifactTypes    []string
+	nestedReferences []string
+	config           *PluginConfig
+}
+
+type cosignVerifierFactory struct{}
+
+var logOpt = logger.Option{
+	ComponentType: logger.Verifier,
+}
+
+const verifierType string = "cosign"
+
+// init() registers the cosign verifier with the factory
+func init() {
+	factory.Register(verifierType, &cosignVerifierFactory{})
+}
+
+// Create creates a new cosign verifier
+func (f *cosignVerifierFactory) Create(_ string, verifierConfig config.VerifierConfig, _ string, namespace string) (verifier.ReferenceVerifier, error) {
+	logger.GetLogger(context.Background(), logOpt).Debugf("creating cosign verifier with config %v, namespace '%v'", verifierConfig, namespace)
+	verifierName, hasName := verifierConfig[types.Name].(string)
+	if !hasName {
+		return nil, re.ErrorCodeConfigInvalid.WithComponentType(re.Verifier).WithPluginName(verifierName).WithDetail("missing name in verifier config")
+	}
+
+	config, err := parseVerifierConfig(verifierConfig)
+	if err != nil {
+		return nil, re.ErrorCodeConfigInvalid.WithComponentType(re.Verifier).WithPluginName(verifierName)
+	}
+
+	return &cosignVerifier{
+		name:             verifierName,
+		verifierType:     config.Type,
+		artifactTypes:    strings.Split(config.ArtifactTypes, ","),
+		nestedReferences: config.NestedReferences,
+		config:           config,
+	}, nil
+}
+
+// Name returns the name of the cosign verifier
+func (v *cosignVerifier) Name() string {
+	return v.name
+}
+
+// Type returns 'cosign' as the type of the verifier
+func (v *cosignVerifier) Type() string {
+	return verifierType
+}
+
+// CanVerify returns true if the referenceDescriptor's artifact type is in the list of artifact types supported by the verifier
+func (v *cosignVerifier) CanVerify(_ context.Context, referenceDescriptor ocispecs.ReferenceDescriptor) bool {
+	for _, at := range v.artifactTypes {
+		if at == "*" || at == referenceDescriptor.ArtifactType {
+			return true
+		}
+	}
+	return false
+}
+
+// Verify verifies the subject reference using the cosign verifier
+func (v *cosignVerifier) Verify(ctx context.Context, subjectReference common.Reference, referenceDescriptor ocispecs.ReferenceDescriptor, referrerStore referrerstore.ReferrerStore) (verifier.VerifierResult, error) {
+	cosignOpts := &cosign.CheckOpts{
+		ClaimVerifier: cosign.SimpleClaimVerifier,
+	}
+
+	var ecdsaVerifier signature.Verifier
+	var roots *x509.CertPool
+	var err error
+	if v.config.KeyRef != "" {
+		ecdsaVerifier, err = loadPublicKey(v.config.KeyRef)
+		if err != nil {
+			return errorToVerifyResult(v.name, v.verifierType, fmt.Errorf("failed to load public key: %w", err)), nil
+		}
+		cosignOpts.SigVerifier = ecdsaVerifier
+	} else {
+		roots, err = fulcio.GetRoots()
+		if err != nil {
+			return errorToVerifyResult(v.name, v.verifierType, fmt.Errorf("failed to get fulcio roots: %w", err)), nil
+		}
+		cosignOpts.RootCerts = roots
+		if cosignOpts.RootCerts == nil {
+			return errorToVerifyResult(v.name, v.verifierType, fmt.Errorf("failed to initialize root certificates")), nil
+		}
+	}
+
+	if v.config.RekorURL != "" {
+		cosignOpts.RekorClient, err = rekor.NewClient(v.config.RekorURL)
+		if err != nil {
+			return errorToVerifyResult(v.name, v.verifierType, fmt.Errorf("failed to create Rekor client from URL %s: %w", v.config.RekorURL, err)), nil
+		}
+		cosignOpts.CTLogPubKeys, err = cosign.GetCTLogPubs(ctx)
+		if err != nil {
+			return errorToVerifyResult(v.name, v.verifierType, fmt.Errorf("failed to set Certificate Transparency Log public keys: %w", err)), nil
+		}
+		// Fetches the Rekor public keys from the Rekor server
+		cosignOpts.RekorPubKeys, err = cosign.GetRekorPubs(ctx)
+		if err != nil {
+			return errorToVerifyResult(v.name, v.verifierType, fmt.Errorf("failed to set Rekor public keys: %w", err)), nil
+		}
+	} else {
+		// if no rekor url is provided, turn off transparency log verification and ignore SCTs
+		cosignOpts.IgnoreTlog = true
+		cosignOpts.IgnoreSCT = true
+	}
+
+	referenceManifest, err := referrerStore.GetReferenceManifest(ctx, subjectReference, referenceDescriptor)
+	if err != nil {
+		return errorToVerifyResult(v.name, v.verifierType, fmt.Errorf("failed to get reference manifest: %w", err)), nil
+	}
+
+	// manifest must be an OCI Image
+	if referenceManifest.MediaType != imgspec.MediaTypeImageManifest {
+		return errorToVerifyResult(v.name, v.verifierType, fmt.Errorf("reference manifest is not an image")), nil
+	}
+
+	subjectDesc, err := referrerStore.GetSubjectDescriptor(ctx, subjectReference)
+	if err != nil {
+		return errorToVerifyResult(v.name, v.verifierType, fmt.Errorf("failed to create subject hash: %w", err)), nil
+	}
+	subjectDescHash := v1.Hash{
+		Algorithm: subjectDesc.Digest.Algorithm().String(),
+		Hex:       subjectDesc.Digest.Hex(),
+	}
+
+	sigExtensions := make([]cosignExtension, 0)
+	signatures := []oci.Signature{}
+	for _, blob := range referenceManifest.Blobs {
+		blobBytes, err := referrerStore.GetBlobContent(ctx, subjectReference, blob.Digest)
+		if err != nil {
+			return errorToVerifyResult(v.name, v.verifierType, fmt.Errorf("failed to get blob content: %w", err)), nil
+		}
+		staticOpts, err := staticLayerOpts(blob)
+		if err != nil {
+			return errorToVerifyResult(v.name, v.verifierType, fmt.Errorf("failed to parse static signature opts: %w", err)), nil
+		}
+		sig, err := static.NewSignature(blobBytes, blob.Annotations[static.SignatureAnnotationKey], staticOpts...)
+		if err != nil {
+			return errorToVerifyResult(v.name, v.verifierType, fmt.Errorf("failed to generate static signature: %w", err)), nil
+		}
+		// The verification will return an error if the signature is not valid.
+		bundleVerified, err := cosign.VerifyImageSignature(ctx, sig, subjectDescHash, cosignOpts)
+		extension := cosignExtension{
+			SignatureDigest: blob.Digest,
+			IsSuccess:       true,
+			BundleVerified:  bundleVerified,
+		}
+		if err != nil {
+			extension.IsSuccess = false
+			extension.Err = err.Error()
+		} else {
+			signatures = append(signatures, sig)
+		}
+		sigExtensions = append(sigExtensions, extension)
+	}
+
+	if len(signatures) > 0 {
+		return verifier.VerifierResult{
+			Name:       v.name,
+			Type:       v.verifierType,
+			IsSuccess:  true,
+			Message:    "cosign verification success. valid signatures found",
+			Extensions: Extension{SignatureExtension: sigExtensions},
+		}, nil
+	}
+
+	errorResult := errorToVerifyResult(v.name, v.verifierType, fmt.Errorf("no valid signatures found"))
+	errorResult.Extensions = Extension{SignatureExtension: sigExtensions}
+	return errorResult, nil
+}
+
+// GetNestedReferences returns the nested reference artifact types configured
+func (v *cosignVerifier) GetNestedReferences() []string {
+	return v.nestedReferences
+}
+
+// ParseVerifierConfig parses the verifier config and returns a PluginConfig
+func parseVerifierConfig(verifierConfig config.VerifierConfig) (*PluginConfig, error) {
+	verifierName, hasName := verifierConfig[types.Name].(string)
+	if !hasName {
+		return nil, re.ErrorCodeConfigInvalid.NewError(re.Verifier, "", re.EmptyLink, nil, "missing name in verifier config", re.HideStackTrace)
+	}
+	conf := PluginConfig{}
+
+	verifierConfigBytes, err := json.Marshal(verifierConfig)
+	if err != nil {
+		return nil, re.ErrorCodeConfigInvalid.NewError(re.Verifier, verifierName, re.EmptyLink, err, nil, re.HideStackTrace)
+	}
+
+	if err := json.Unmarshal(verifierConfigBytes, &conf); err != nil {
+		return nil, re.ErrorCodeConfigInvalid.NewError(re.Verifier, verifierName, re.EmptyLink, err, fmt.Sprintf("failed to unmarshal to cosign verifier config from: %+v.", verifierConfig), re.HideStackTrace)
+	}
+
+	// if Type is not provided, use the Name as the Type (backwards compatibility)
+	if conf.Type == "" {
+		conf.Type = conf.Name
+	}
+
+	if conf.ArtifactTypes == "" {
+		return nil, re.ErrorCodeConfigInvalid.NewError(re.Verifier, verifierName, re.EmptyLink, nil, "missing artifactTypes in verifier config", re.HideStackTrace)
+	}
+
+	return &conf, nil
+}
+
+// LoadPublicKey loads the public key from the keyRef
+func loadPublicKey(keyRef string) (verifier signature.Verifier, err error) {
+	keyPath := filepath.Clean(utils.ReplaceHomeShortcut(keyRef))
+	raw, err := os.ReadFile(keyPath)
+	if err != nil {
+		return nil, err
+	}
+
+	// PEM encoded file.
+	ed, err := cosign.PemToECDSAKey(raw)
+	if err != nil {
+		return nil, errors.Wrap(err, "pem to ecdsa")
+	}
+	return signature.LoadECDSAVerifier(ed, crypto.SHA256)
+}
+
+// StaticLayerOpts builds the cosign options for static layer signatures
+func staticLayerOpts(desc imgspec.Descriptor) ([]static.Option, error) {
+	options := []static.Option{}
+	options = append(options, static.WithAnnotations(desc.Annotations))
+	cert := desc.Annotations[static.CertificateAnnotationKey]
+	chain := desc.Annotations[static.ChainAnnotationKey]
+	if cert != "" && chain != "" {
+		options = append(options, static.WithCertChain([]byte(cert), []byte(chain)))
+	}
+	var rekorBundle bundle.RekorBundle
+	if val, ok := desc.Annotations[static.BundleAnnotationKey]; ok {
+		if err := json.Unmarshal([]byte(val), &rekorBundle); err != nil {
+			return nil, errors.Wrap(err, "failed to unmarshal bundle from blob payload")
+		}
+		options = append(options, static.WithBundle(&rekorBundle))
+	}
+
+	return options, nil
+}
+
+// ErrorToVerifyResult returns a verifier result with the error message and isSuccess set to false
+func errorToVerifyResult(name string, verifierType string, err error) verifier.VerifierResult {
+	return verifier.VerifierResult{
+		IsSuccess: false,
+		Name:      name,
+		Type:      verifierType,
+		Message:   errors.Wrap(err, "cosign verification failed").Error(),
+	}
+}
diff --git a/plugins/verifier/cosign/README.md b/plugins/verifier/cosign/README.md
deleted file mode 100644
index 55f85f4..0000000
--- a/plugins/verifier/cosign/README.md
+++ /dev/null
@@ -1,1 +0,0 @@
-This document has been moved to https://ratify.dev/docs/external%20plugins/Verifier/cosign
\ No newline at end of file
diff --git a/plugins/verifier/cosign/cosign.go b/plugins/verifier/cosign/cosign.go
deleted file mode 100644
index af0ef65..0000000
--- a/plugins/verifier/cosign/cosign.go
+++ /dev/null
@@ -1,254 +0,0 @@
-/*
-Copyright The Ratify Authors.
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package main
-
-import (
-	"context"
-	"crypto"
-	"crypto/x509"
-	"encoding/json"
-	"fmt"
-	"os"
-	"path/filepath"
-
-	"github.com/deislabs/ratify/pkg/common"
-	"github.com/deislabs/ratify/pkg/ocispecs"
-	"github.com/deislabs/ratify/pkg/referrerstore"
-	_ "github.com/deislabs/ratify/pkg/referrerstore/oras"
-	"github.com/deislabs/ratify/pkg/utils"
-	"github.com/deislabs/ratify/pkg/verifier"
-	"github.com/deislabs/ratify/pkg/verifier/plugin/skel"
-
-	v1 "github.com/google/go-containerregistry/pkg/v1"
-	"github.com/opencontainers/go-digest"
-	imgspec "github.com/opencontainers/image-spec/specs-go/v1"
-	"github.com/pkg/errors"
-	"github.com/sigstore/cosign/v2/cmd/cosign/cli/fulcio"
-	"github.com/sigstore/cosign/v2/cmd/cosign/cli/rekor"
-	"github.com/sigstore/cosign/v2/pkg/cosign"
-	"github.com/sigstore/cosign/v2/pkg/cosign/bundle"
-	"github.com/sigstore/cosign/v2/pkg/oci"
-	"github.com/sigstore/cosign/v2/pkg/oci/static"
-	"github.com/sigstore/sigstore/pkg/signature"
-)
-
-type PluginConfig struct {
-	Name     string `json:"name"`
-	Type     string `json:"type"`
-	KeyRef   string `json:"key"`
-	RekorURL string `json:"rekorURL"`
-	// config specific to the plugin
-}
-
-type StoreConfig struct {
-	UseHTTP bool `json:"useHttp,omitempty"`
-}
-
-type StoreWrapperConfig struct {
-	StoreConfig StoreConfig `json:"store"`
-}
-
-type PluginInputConfig struct {
-	Config             PluginConfig       `json:"config"`
-	StoreWrapperConfig StoreWrapperConfig `json:"storeConfig"`
-}
-
-type Extension struct {
-	SignatureExtension []cosignExtension `json:"signatures,omitempty"`
-}
-
-type cosignExtension struct {
-	SignatureDigest digest.Digest `json:"signatureDigest"`
-	IsSuccess       bool          `json:"isSuccess"`
-	BundleVerified  bool          `json:"bundleVerified"`
-	Err             error         `json:"error,omitempty"`
-}
-
-func main() {
-	skel.PluginMain("cosign", "1.1.0", VerifyReference, []string{"1.0.0"})
-}
-
-func parseInput(stdin []byte) (*PluginInputConfig, error) {
-	conf := PluginInputConfig{}
-	if err := json.Unmarshal(stdin, &conf); err != nil {
-		return nil, fmt.Errorf("failed to parse stdin for the input: %w", err)
-	}
-
-	return &conf, nil
-}
-
-func VerifyReference(args *skel.CmdArgs, subjectReference common.Reference, referenceDescriptor ocispecs.ReferenceDescriptor, referrerStore referrerstore.ReferrerStore) (*verifier.VerifierResult, error) {
-	ctx := context.Background()
-	input, err := parseInput(args.StdinData)
-	if err != nil {
-		return nil, err
-	}
-	verifierType := input.Config.Name
-	if input.Config.Type != "" {
-		verifierType = input.Config.Type
-	}
-	keyRef := input.Config.KeyRef
-	rekorURL := input.Config.RekorURL
-	cosignOpts := &cosign.CheckOpts{
-		ClaimVerifier: cosign.SimpleClaimVerifier,
-	}
-
-	var ecdsaVerifier signature.Verifier
-	var roots *x509.CertPool
-	if keyRef != "" {
-		ecdsaVerifier, err = loadPublicKey(ctx, keyRef)
-		if err != nil {
-			return errorToVerifyResult(input.Config.Name, verifierType, fmt.Errorf("failed to load public key: %w", err)), nil
-		}
-		cosignOpts.SigVerifier = ecdsaVerifier
-	} else {
-		roots, err = fulcio.GetRoots()
-		if err != nil {
-			return errorToVerifyResult(input.Config.Name, verifierType, fmt.Errorf("failed to get fulcio roots: %w", err)), nil
-		}
-		cosignOpts.RootCerts = roots
-		if cosignOpts.RootCerts == nil {
-			return errorToVerifyResult(input.Config.Name, verifierType, fmt.Errorf("failed to initialize root certificates")), nil
-		}
-	}
-
-	if rekorURL != "" {
-		cosignOpts.RekorClient, err = rekor.NewClient(rekorURL)
-		if err != nil {
-			return errorToVerifyResult(input.Config.Name, verifierType, fmt.Errorf("failed to create Rekor client from URL %s: %w", rekorURL, err)), nil
-		}
-		cosignOpts.CTLogPubKeys, err = cosign.GetCTLogPubs(ctx)
-		if err != nil {
-			return errorToVerifyResult(input.Config.Name, verifierType, fmt.Errorf("failed to set Certificate Transparency Log public keys: %w", err)), nil
-		}
-		// Fetches the Rekor public keys from the Rekor server
-		cosignOpts.RekorPubKeys, err = cosign.GetRekorPubs(ctx)
-		if err != nil {
-			return errorToVerifyResult(input.Config.Name, verifierType, fmt.Errorf("failed to set Rekor public keys: %w", err)), nil
-		}
-	} else {
-		// if no rekor url is provided, turn off transparency log verification and ignore SCTs
-		cosignOpts.IgnoreTlog = true
-		cosignOpts.IgnoreSCT = true
-	}
-
-	referenceManifest, err := referrerStore.GetReferenceManifest(ctx, subjectReference, referenceDescriptor)
-	if err != nil {
-		return errorToVerifyResult(input.Config.Name, verifierType, fmt.Errorf("failed to get reference manifest: %w", err)), nil
-	}
-
-	// manifest must be an OCI Image
-	if referenceManifest.MediaType != imgspec.MediaTypeImageManifest {
-		return errorToVerifyResult(input.Config.Name, verifierType, fmt.Errorf("reference manifest is not an image")), nil
-	}
-
-	subjectDesc, err := referrerStore.GetSubjectDescriptor(ctx, subjectReference)
-	if err != nil {
-		return errorToVerifyResult(input.Config.Name, verifierType, fmt.Errorf("failed to create subject hash: %w", err)), nil
-	}
-	subjectDescHash := v1.Hash{
-		Algorithm: subjectDesc.Digest.Algorithm().String(),
-		Hex:       subjectDesc.Digest.Hex(),
-	}
-
-	sigExtensions := make([]cosignExtension, 0)
-	signatures := []oci.Signature{}
-	for _, blob := range referenceManifest.Blobs {
-		blobBytes, err := referrerStore.GetBlobContent(ctx, subjectReference, blob.Digest)
-		if err != nil {
-			return errorToVerifyResult(input.Config.Name, verifierType, fmt.Errorf("failed to get blob content: %w", err)), nil
-		}
-		staticOpts, err := staticLayerOpts(blob)
-		if err != nil {
-			return errorToVerifyResult(input.Config.Name, verifierType, fmt.Errorf("failed to parse static signature opts: %w", err)), nil
-		}
-		sig, err := static.NewSignature(blobBytes, blob.Annotations[static.SignatureAnnotationKey], staticOpts...)
-		if err != nil {
-			return errorToVerifyResult(input.Config.Name, verifierType, fmt.Errorf("failed to generate static signature: %w", err)), nil
-		}
-		// The verification will return an error if the signature is not valid.
-		bundleVerified, err := cosign.VerifyImageSignature(ctx, sig, subjectDescHash, cosignOpts)
-		extension := cosignExtension{
-			SignatureDigest: blob.Digest,
-			IsSuccess:       true,
-			BundleVerified:  bundleVerified,
-		}
-		if err != nil {
-			extension.IsSuccess = false
-			extension.Err = err
-		} else {
-			signatures = append(signatures, sig)
-		}
-		sigExtensions = append(sigExtensions, extension)
-	}
-
-	if len(signatures) > 0 {
-		return &verifier.VerifierResult{
-			Name:       input.Config.Name,
-			Type:       verifierType,
-			IsSuccess:  true,
-			Message:    "cosign verification success. valid signatures found",
-			Extensions: Extension{SignatureExtension: sigExtensions},
-		}, nil
-	}
-
-	errorResult := errorToVerifyResult(input.Config.Name, verifierType, fmt.Errorf("no valid signatures found"))
-	errorResult.Extensions = Extension{SignatureExtension: sigExtensions}
-	return errorResult, nil
-}
-
-func loadPublicKey(_ context.Context, keyRef string) (verifier signature.Verifier, err error) {
-	keyPath := filepath.Clean(utils.ReplaceHomeShortcut(keyRef))
-	raw, err := os.ReadFile(keyPath)
-	if err != nil {
-		return nil, err
-	}
-
-	// PEM encoded file.
-	ed, err := cosign.PemToECDSAKey(raw)
-	if err != nil {
-		return nil, errors.Wrap(err, "pem to ecdsa")
-	}
-	return signature.LoadECDSAVerifier(ed, crypto.SHA256)
-}
-
-func staticLayerOpts(desc imgspec.Descriptor) ([]static.Option, error) {
-	options := []static.Option{}
-	options = append(options, static.WithAnnotations(desc.Annotations))
-	cert := desc.Annotations[static.CertificateAnnotationKey]
-	chain := desc.Annotations[static.ChainAnnotationKey]
-	if cert != "" && chain != "" {
-		options = append(options, static.WithCertChain([]byte(cert), []byte(chain)))
-	}
-	var rekorBundle bundle.RekorBundle
-	if val, ok := desc.Annotations[static.BundleAnnotationKey]; ok {
-		if err := json.Unmarshal([]byte(val), &rekorBundle); err != nil {
-			return nil, errors.Wrap(err, "failed to unmarshal bundle from blob payload")
-		}
-		options = append(options, static.WithBundle(&rekorBundle))
-	}
-
-	return options, nil
-}
-
-func errorToVerifyResult(name string, verifierType string, err error) *verifier.VerifierResult {
-	return &verifier.VerifierResult{
-		IsSuccess: false,
-		Name:      name,
-		Type:      verifierType,
-		Message:   errors.Wrap(err, "cosign verification failed").Error(),
-	}
-}


Test patch:
diff --git a/pkg/verifier/cosign/cosign_test.go b/pkg/verifier/cosign/cosign_test.go
new file mode 100644
index 0000000..5cb6903
--- /dev/null
+++ b/pkg/verifier/cosign/cosign_test.go
@@ -0,0 +1,310 @@
+/*
+Copyright The Ratify Authors.
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package cosign
+
+import (
+	"context"
+	"fmt"
+	"testing"
+
+	"github.com/deislabs/ratify/pkg/ocispecs"
+	"github.com/deislabs/ratify/pkg/verifier/config"
+	imgspec "github.com/opencontainers/image-spec/specs-go/v1"
+	"github.com/sigstore/cosign/v2/pkg/oci/static"
+)
+
+// TestCreate tests the Create function of the cosign verifier
+func TestCreate(t *testing.T) {
+	tests := []struct {
+		name    string
+		config  config.VerifierConfig
+		wantErr bool
+	}{
+		{
+			name: "valid config",
+			config: config.VerifierConfig{
+				"name":          "test",
+				"artifactTypes": "testtype",
+			},
+			wantErr: false,
+		},
+		{
+			name: "missing name of verifier",
+			config: config.VerifierConfig{
+				"invalid": "test",
+			},
+			wantErr: true,
+		},
+		{
+			name: "invalid config",
+			config: config.VerifierConfig{
+				"name": "test",
+			},
+			wantErr: true,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			verifierFactory := cosignVerifierFactory{}
+			_, err := verifierFactory.Create("", tt.config, "", "test-namespace")
+			if (err != nil) != tt.wantErr {
+				t.Errorf("Create() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+		})
+	}
+}
+
+// TestName tests the Name function of the cosign verifier
+func TestName(t *testing.T) {
+	verifierFactory := cosignVerifierFactory{}
+	name := "test"
+	validConfig := config.VerifierConfig{
+		"name":          name,
+		"artifactTypes": "testtype",
+	}
+	cosignVerifier, err := verifierFactory.Create("", validConfig, "", "test-namespace")
+	if err != nil {
+		t.Fatalf("Create() error = %v", err)
+	}
+
+	if cosignVerifier.Name() != name {
+		t.Errorf("Name() = %v, want %v", cosignVerifier.Name(), name)
+	}
+}
+
+// TestType tests the Type function of the cosign verifier
+func TestType(t *testing.T) {
+	verifierFactory := cosignVerifierFactory{}
+	validConfig := config.VerifierConfig{
+		"name":          "test",
+		"artifactTypes": "testtype",
+	}
+	cosignVerifier, err := verifierFactory.Create("", validConfig, "", "test-namespace")
+	if err != nil {
+		t.Fatalf("Create() error = %v", err)
+	}
+
+	if cosignVerifier.Type() != "cosign" {
+		t.Errorf("Type() = %v, want %v", cosignVerifier.Type(), "cosign")
+	}
+}
+
+// TestCanVerify tests the CanVerify function of the cosign verifier
+func TestCanVerify(t *testing.T) {
+	tc := []struct {
+		name             string
+		artifactTypes    string
+		descArtifactType string
+		want             bool
+		shouldError      bool
+	}{
+		{
+			name:             "valid artifact type",
+			artifactTypes:    "testtype",
+			descArtifactType: "testtype",
+			want:             true,
+		},
+		{
+			name:             "all artifact types",
+			artifactTypes:    "*",
+			descArtifactType: "testtype",
+			want:             true,
+		},
+		{
+			name:             "non matching artifact type",
+			artifactTypes:    "testtype",
+			descArtifactType: "othertype",
+			want:             false,
+		},
+	}
+
+	for _, tt := range tc {
+		t.Run(tt.name, func(t *testing.T) {
+			verifierFactory := cosignVerifierFactory{}
+			validConfig := config.VerifierConfig{
+				"name":          "test",
+				"artifactTypes": tt.artifactTypes,
+			}
+			cosignVerifier, err := verifierFactory.Create("", validConfig, "", "test-namespace")
+			if err != nil {
+				t.Fatalf("Create() error = %v", err)
+			}
+			result := cosignVerifier.CanVerify(context.Background(), ocispecs.ReferenceDescriptor{ArtifactType: tt.descArtifactType})
+			if result != tt.want {
+				t.Errorf("CanVerify() = %v, want %v", result, tt.want)
+			}
+		})
+	}
+}
+
+// TestGetNestedReferences tests the GetNestedReferences function of the cosign verifier
+func TestGetNestedReferences(t *testing.T) {
+	verifierFactory := cosignVerifierFactory{}
+	validConfig := config.VerifierConfig{
+		"name":                "test",
+		"artifactTypes":       "testtype",
+		"nestedArtifactTypes": []string{"nested-artifact-type"},
+	}
+	cosignVerifier, err := verifierFactory.Create("", validConfig, "", "test-namespace")
+	if err != nil {
+		t.Fatalf("Create() error = %v", err)
+	}
+
+	refs := cosignVerifier.GetNestedReferences()
+	if len(refs) != 1 {
+		t.Fatalf("GetNestedReferences() = %v, want 1", refs)
+	}
+	if refs[0] != "nested-artifact-type" {
+		t.Errorf("GetNestedReferences() = %v, want nested-artifact-type", refs)
+	}
+}
+
+// TestParseVerifierConfig tests the parseVerifierConfig function
+func TestParseVerifierConfig(t *testing.T) {
+	tc := []struct {
+		name     string
+		config   config.VerifierConfig
+		wantType string
+		wantErr  bool
+	}{
+		{
+			name: "valid config",
+			config: config.VerifierConfig{
+				"name":                "test",
+				"artifactTypes":       "testtype",
+				"type":                "stuff",
+				"nestedArtifactTypes": []string{"nested-artifact-type"},
+				"key":                 "testkey_path",
+			},
+			wantType: "stuff",
+			wantErr:  false,
+		},
+		{
+			name: "missing type",
+			config: config.VerifierConfig{
+				"name":                "test",
+				"artifactTypes":       "testtype",
+				"nestedArtifactTypes": []string{"nested-artifact-type"},
+				"key":                 "testkey_path",
+			},
+			wantType: "test",
+			wantErr:  false,
+		},
+		{
+			name: "missing name",
+			config: config.VerifierConfig{
+				"artifactTypes":       "testtype",
+				"type":                "stuff",
+				"nestedArtifactTypes": []string{"nested-artifact-type"},
+				"key":                 "testkey_path",
+			},
+			wantType: "",
+			wantErr:  true,
+		},
+		{
+			name: "missing artifactTypes",
+			config: config.VerifierConfig{
+				"name":                "test",
+				"type":                "stuff",
+				"nestedArtifactTypes": []string{"nested-artifact-type"},
+				"key":                 "testkey_path",
+			},
+			wantType: "",
+			wantErr:  true,
+		},
+	}
+
+	for _, tt := range tc {
+		t.Run(tt.name, func(t *testing.T) {
+			pluginConfig, err := parseVerifierConfig(tt.config)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("parseVerifierConfig() error = %v, wantErr %v", err, tt.wantErr)
+			}
+
+			if err == nil && pluginConfig.Type != tt.wantType {
+				t.Errorf("Type() = %v, want %v", pluginConfig.Type, tt.wantType)
+			}
+		})
+	}
+}
+
+// TestStaticLayerOpts tests the staticLayerOpts function
+func TestStaticLayerOpts(test *testing.T) {
+	tc := []struct {
+		name    string
+		desc    imgspec.Descriptor
+		wantLen int
+		wantErr bool
+	}{
+		{
+			name: "valid config",
+			desc: imgspec.Descriptor{
+				MediaType: "application/vnd.oci.image.layer.v1.tar",
+				Annotations: map[string]string{
+					static.CertificateAnnotationKey: "testcert",
+					static.ChainAnnotationKey:       "testchain",
+					static.BundleAnnotationKey:      "{\"SignedEntryTimestamp\":\"MEUCIQDj21LM7GkNM3SlaodBrUgZBFF7gFCgI1u/bE82kCyzBgIgB7Oqk/vahZevqUTHqo7JFo02yc2zawTTw3gMwwf0En8=\",\"Payload\":{\"body\":\"eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiI4MzdhMjkyNGZlZjgwYTVhNmJiOGQ4NzExZTY2NTE4YzMwOTJkZTg4ODhlYTg4ODBhOTMyM2I5ODUwNjkzMzM5In19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FUUNJQXFueXVsUTVoZW1KaStQZ0EvdHgyVEFqM2xSdlJlYkFmSWtZYnM1NlZhckFpQnJoS3BaQ3Fzb2M2NGdzdllEZUxFWGRyb291U0RvQjRrZ2dJUEZ6MEFpSnc9PSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVTXhla05EUVd3eVowRjNTVUpCWjBsVldXOUlibWcyY25GQk0yZFVSSE5TTTFaUmRuSmFkazlQT0RWTmQwTm5XVWxMYjFwSmVtb3dSVUYzVFhjS1RucEZWazFDVFVkQk1WVkZRMmhOVFdNeWJHNWpNMUoyWTIxVmRWcEhWakpOVWpSM1NFRlpSRlpSVVVSRmVGWjZZVmRrZW1SSE9YbGFVekZ3WW01U2JBcGpiVEZzV2tkc2FHUkhWWGRJYUdOT1RXcE5kMDVFUlROTmFrRXhUVlJGTTFkb1kwNU5hazEzVGtSRk0wMXFSWGROVkVVelYycEJRVTFHYTNkRmQxbElDa3R2V2tsNmFqQkRRVkZaU1V0dldrbDZhakJFUVZGalJGRm5RVVV3VFRNd2FWTndaVUU1WVhGMk1VOUJVVEp6YlRKbVQwSkpSbUpMWVhOck1UVktOVTRLU25SSkswRTJUR2RFU1V0WVdYcEJTRXB0ZDBabFVGUkRZbHB6V2t3cldHeDZaVTlCTmxFelZYWXhPR1pFUWpoUFMyRlBRMEZZZDNkblowWTBUVUUwUndwQk1WVmtSSGRGUWk5M1VVVkJkMGxJWjBSQlZFSm5UbFpJVTFWRlJFUkJTMEpuWjNKQ1owVkdRbEZqUkVGNlFXUkNaMDVXU0ZFMFJVWm5VVlUwV1hsMUNqZzJMMjV2Um1Vclptb3ZkbFIwSzBKSlJ6Qk1WelEwZDBoM1dVUldVakJxUWtKbmQwWnZRVlV6T1ZCd2VqRlphMFZhWWpWeFRtcHdTMFpYYVhocE5Ga0tXa1E0ZDBwM1dVUldVakJTUVZGSUwwSkNNSGRITkVWYVdWZDBhR015YUhKak1teDFXakpvYUdKRWF6UlJSMlIwV1Zkc2MweHRUblppVkVGelFtZHZjZ3BDWjBWRlFWbFBMMDFCUlVKQ1FqVnZaRWhTZDJONmIzWk1NbVJ3WkVkb01WbHBOV3BpTWpCMllrYzVibUZYTkhaaU1rWXhaRWRuZDB4bldVdExkMWxDQ2tKQlIwUjJla0ZDUTBGUlowUkNOVzlrU0ZKM1kzcHZka3d5WkhCa1IyZ3hXV2sxYW1JeU1IWmlSemx1WVZjMGRtSXlSakZrUjJkM1oxbHJSME5wYzBjS1FWRlJRakZ1YTBOQ1FVbEZaWGRTTlVGSVkwRmtVVVJrVUZSQ2NYaHpZMUpOYlUxYVNHaDVXbHA2WTBOdmEzQmxkVTQwT0hKbUswaHBia3RCVEhsdWRRcHFaMEZCUVZsbFVTOUlhMkZCUVVGRlFYZENSMDFGVVVOSlIyTm5UVFl2YVZGWldIUlVkemhoWVUxSUwweDRhQzlYYjA4eGVISlFOVUZoY2xOV1JEaFlDa2t3YURSQmFVSk5jRU5NTkN0RlMxbHNPWGQ2TnpBd1JuUXdWSEIxVDNoVVJFVnhhR3hFVEZsVmNIWnFjR1prUW5aRVFVdENaMmR4YUd0cVQxQlJVVVFLUVhkT2IwRkVRbXhCYWtFeFpuZDVaVkowVnpGcU4yUllNbHA2UzA1aVluRjNjVU0zWVZjNWFXTlhlRE40U1dsRU1IQlZhVGRLWVRodWRWbG5XR3BJYUFwWVZVWnBRMng2UjNkS1kwTk5VVVEzV21GTVNERkxUekZwVTNwU1EwVkZaRGhhUm1KaFprMXhjSGc1VTBsVFVHaGxSVFJsYkdWV1NDdEhjbkZ5Ylc5WkNrMVBUbHB2TVhCa1ltOVZTMnh2TUQwS0xTMHRMUzFGVGtRZ1EwVlNWRWxHU1VOQlZFVXRMUzB0TFFvPSJ9fX19\",\"integratedTime\":1681764685,\"logIndex\":18215184,\"logID\":\"c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d\"}}",
+				},
+			},
+			wantLen: 3,
+			wantErr: false,
+		},
+		{
+			name: "incorrect rekor bundle",
+			desc: imgspec.Descriptor{
+				MediaType: "application/vnd.oci.image.layer.v1.tar",
+				Annotations: map[string]string{
+					static.CertificateAnnotationKey: "testcert",
+					static.ChainAnnotationKey:       "testchain",
+					static.BundleAnnotationKey:      "invalidbundle",
+				},
+			},
+			wantLen: 0,
+			wantErr: true,
+		},
+	}
+
+	for _, tt := range tc {
+		test.Run(tt.name, func(test *testing.T) {
+			layerOpts, err := staticLayerOpts(tt.desc)
+			if (err != nil) != tt.wantErr {
+				test.Errorf("staticLayerOpts() error = %v, wantErr %v", err, tt.wantErr)
+			}
+
+			if err == nil && len(layerOpts) != tt.wantLen {
+				test.Errorf("staticLayerOpts() = %v, want %v", len(layerOpts), tt.wantLen)
+			}
+		})
+	}
+}
+
+// TestErrorToVerifyResult tests the errorToVerifyResult function
+func TestErrorToVerifyResult(t *testing.T) {
+	verifierResult := errorToVerifyResult("test", "cosign", fmt.Errorf("test error"))
+	if verifierResult.IsSuccess {
+		t.Errorf("errorToVerifyResult() = %v, want %v", verifierResult.IsSuccess, false)
+	}
+	if verifierResult.Name != "test" {
+		t.Errorf("errorToVerifyResult() = %v, want %v", verifierResult.Name, "test")
+	}
+	if verifierResult.Type != "cosign" {
+		t.Errorf("errorToVerifyResult() = %v, want %v", verifierResult.Type, "cosign")
+	}
+	if verifierResult.Message != "cosign verification failed: test error" {
+		t.Errorf("errorToVerifyResult() = %v, want %v", verifierResult.Message, "cosign verification failed: test error")
+	}
+}
diff --git a/scripts/azure-ci-test.sh b/scripts/azure-ci-test.sh
index ddc3495..15d4513 100755
--- a/scripts/azure-ci-test.sh
+++ b/scripts/azure-ci-test.sh
@@ -40,7 +40,7 @@ REGISTRY="${ACR_NAME}.azurecr.io"
 
 build_push_to_acr() {
   echo "Building and pushing images to ACR"
-  docker build --progress=plain --no-cache --build-arg build_cosign=true --build-arg build_sbom=true --build-arg build_licensechecker=true --build-arg build_schemavalidator=true --build-arg build_vulnerabilityreport=true -f ./httpserver/Dockerfile -t "${ACR_NAME}.azurecr.io/test/localbuild:${TAG}" .
+  docker build --progress=plain --no-cache --build-arg build_sbom=true --build-arg build_licensechecker=true --build-arg build_schemavalidator=true --build-arg build_vulnerabilityreport=true -f ./httpserver/Dockerfile -t "${ACR_NAME}.azurecr.io/test/localbuild:${TAG}" .
   docker push "${REGISTRY}/test/localbuild:${TAG}"
 
   docker build --progress=plain --no-cache --build-arg KUBE_VERSION=${KUBERNETES_VERSION} --build-arg TARGETOS="linux" --build-arg TARGETARCH="amd64" -f crd.Dockerfile -t "${ACR_NAME}.azurecr.io/test/localbuildcrd:${TAG}" ./charts/ratify/crds