Repository: deislabs/ratify
Pull request #683 (2023-03-02 22:27:49)

feat: support OCI Image
# Description

## What this PR does / why we need it:
Most verifiers rely on the Blobs field to enumerate the content of each artifact. This field is populated directly from the manifest byte marshalling. In the case of an OCI Image referrer, this marshalling will not populate Blobs causing all other verifiers to prematurely finish before any content is verified. This PR:
- Updates ReferenceManifest to use `Blobs` and adds logic in ORAS store to marshal Layers/Blobs to the `Blobs` field depending on the referrer media type.
- Updates Notaryv2 verifier to check for 0 length blob array and fail if no signatures found
- Updates Cosign verifier to use `Blobs` field

TODO: Add e2e test with OCI Image and OCI Artifact as referrer manifest type

## 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 #

## Type of change

Please delete options that are not relevant.

- [x] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] 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?


codecov[bot]: # [Codecov](https://codecov.io/gh/deislabs/ratify/pull/683?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) Report
Patch coverage has no change and project coverage change: **`-0.31`** :warning:
> Comparison is base [(`0dca3a0`)](https://codecov.io/gh/deislabs/ratify/commit/0dca3a0a4824b738fe5c0dede26993500fafc09c?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) 44.57% compared to head [(`59edec5`)](https://codecov.io/gh/deislabs/ratify/pull/683?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) 44.27%.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main     #683      +/-   ##
==========================================
- Coverage   44.57%   44.27%   -0.31%     
==========================================
  Files          56       56              
  Lines        3244     3266      +22     
==========================================
  Hits         1446     1446              
- Misses       1651     1672      +21     
- Partials      147      148       +1     
```


| [Impacted Files](https://codecov.io/gh/deislabs/ratify/pull/683?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) | Coverage Δ | |
|---|---|---|
| [pkg/referrerstore/oras/oras.go](https://codecov.io/gh/deislabs/ratify/pull/683?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-cGtnL3JlZmVycmVyc3RvcmUvb3Jhcy9vcmFzLmdv) | `0.68% <0.00%> (-0.03%)` | :arrow_down: |
| [pkg/referrerstore/oras/utils.go](https://codecov.io/gh/deislabs/ratify/pull/683?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-cGtnL3JlZmVycmVyc3RvcmUvb3Jhcy91dGlscy5nbw==) | `0.00% <0.00%> (ø)` | |
| [pkg/verifier/notaryv2/notaryv2.go](https://codecov.io/gh/deislabs/ratify/pull/683?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-cGtnL3ZlcmlmaWVyL25vdGFyeXYyL25vdGFyeXYyLmdv) | `79.09% <0.00%> (-2.22%)` | :arrow_down: |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None)

</details>

[:umbrella: View full report at Codecov](https://codecov.io/gh/deislabs/ratify/pull/683?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None).   
:loudspeaker: Do you have feedback about the report comment? [Let us know in this issue](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None).

susanshi: we should also add doc to state we support both format incase customer wonders which format is supported.
akashsinghal: > we should also add doc to state we support both format incase customer have questions

Yes agreed. I'm struggling to figure out where to put though since we don't have an ORAS store specific doc. 

Patch:
diff --git a/Makefile b/Makefile
index aaf6494..e80ccc9 100644
--- a/Makefile
+++ b/Makefile
@@ -17,6 +17,7 @@ KUBERNETES_VERSION ?= 1.25.4
 GATEKEEPER_VERSION ?= 3.11.0
 COSIGN_VERSION ?= 1.13.1
 NOTATION_VERSION ?= 1.0.0-rc.2
+ORAS_VERSION ?= 1.0.0-rc.1
 
 HELM_VERSION ?= 3.9.2
 BATS_TESTS_FILE ?= test/bats/test.bats
@@ -141,11 +142,11 @@ e2e-dependencies:
 	# Download and install jq
 	curl -L https://github.com/stedolan/jq/releases/download/jq-1.6/jq-linux64 --output ${GITHUB_WORKSPACE}/bin/jq && chmod +x ${GITHUB_WORKSPACE}/bin/jq
 	# Install ORAS
-	curl -LO https://github.com/oras-project/oras/releases/download/v0.16.0/oras_0.16.0_linux_amd64.tar.gz
+	curl -LO https://github.com/oras-project/oras/releases/download/v${ORAS_VERSION}/oras_${ORAS_VERSION}_linux_amd64.tar.gz
 	mkdir -p oras-install/
-	tar -zxf oras_0.16.0_*.tar.gz -C oras-install/
+	tar -zxf oras*.tar.gz -C oras-install/
 	mv oras-install/oras ${GITHUB_WORKSPACE}/bin
-	rm -rf oras_0.16.0_*.tar.gz oras-install/
+	rm -rf oras*.tar.gz oras-install/
 
 KIND_NODE_VERSION := kindest/node:v$(KUBERNETES_VERSION)
 
@@ -167,7 +168,7 @@ e2e-create-local-registry:
 		${LOCAL_REGISTRY_IMAGE}
 
 	docker login -u ${LOCAL_TEST_REGISTRY_USERNAME} -p ${LOCAL_TEST_REGISTRY_PASSWORD} ${LOCAL_TEST_REGISTRY}
-	oras login \
+	${GITHUB_WORKSPACE}/bin/oras login \
 		-u ${LOCAL_TEST_REGISTRY_USERNAME} \
 		-p ${LOCAL_TEST_REGISTRY_PASSWORD} \
 		${LOCAL_TEST_REGISTRY}
@@ -204,6 +205,9 @@ e2e-notaryv2-setup:
 	echo 'FROM alpine\nCMD ["echo", "notaryv2 signed image"]' > .staging/notaryv2/Dockerfile
 	docker build -t ${LOCAL_TEST_REGISTRY}/notation:signed .staging/notaryv2
 	docker push ${LOCAL_TEST_REGISTRY}/notation:signed
+	echo 'FROM alpine\nCMD ["echo", "notaryv2 signed image OCI Image"]' > .staging/notaryv2/Dockerfile
+	docker build -t ${LOCAL_TEST_REGISTRY}/notation:signedImage .staging/notaryv2
+	docker push ${LOCAL_TEST_REGISTRY}/notation:signedImage
 
 	docker pull ${LOCAL_UNSIGNED_IMAGE}
 	docker image tag ${LOCAL_UNSIGNED_IMAGE} ${LOCAL_TEST_REGISTRY}/notation:unsigned
@@ -212,6 +216,7 @@ e2e-notaryv2-setup:
 	rm -rf ~/.config/notation
 	.staging/notaryv2/notation cert generate-test --default "ratify-bats-test"
 	.staging/notaryv2/notation sign -u ${LOCAL_TEST_REGISTRY_USERNAME} -p ${LOCAL_TEST_REGISTRY_PASSWORD} `docker image inspect ${LOCAL_TEST_REGISTRY}/notation:signed | jq -r .[0].RepoDigests[0]`
+	.staging/notaryv2/notation sign --signature-manifest image -u ${LOCAL_TEST_REGISTRY_USERNAME} -p ${LOCAL_TEST_REGISTRY_PASSWORD} `docker image inspect ${LOCAL_TEST_REGISTRY}/notation:signedImage | jq -r .[0].RepoDigests[0]`
 	.staging/notaryv2/notation sign -u ${LOCAL_TEST_REGISTRY_USERNAME} -p ${LOCAL_TEST_REGISTRY_PASSWORD} `docker image inspect ${LOCAL_TEST_REGISTRY}/all:v0 | jq -r .[0].RepoDigests[0]`
 
 e2e-cosign-setup:
@@ -249,13 +254,23 @@ e2e-licensechecker-setup:
 	docker build -t ${LOCAL_TEST_REGISTRY}/licensechecker:v0 .staging/licensechecker
 	docker push ${LOCAL_TEST_REGISTRY}/licensechecker:v0
 
+	# Build/Push OCI Image Signature 
+	echo 'FROM alpine@sha256:93d5a28ff72d288d69b5997b8ba47396d2cbb62a72b5d87cd3351094b5d578a0\nCMD ["echo", "licensechecker image oci image"]' > .staging/licensechecker/Dockerfile
+	docker build -t ${LOCAL_TEST_REGISTRY}/licensechecker:ociimage .staging/licensechecker
+	docker push ${LOCAL_TEST_REGISTRY}/licensechecker:ociimage
+
 	# Create/Attach SPDX
 	.staging/licensechecker/syft -o spdx --file .staging/licensechecker/sbom.spdx ${LOCAL_TEST_REGISTRY}/licensechecker:v0
-	oras attach ${LOCAL_TEST_REGISTRY}/licensechecker:v0 \
+	${GITHUB_WORKSPACE}/bin/oras attach ${LOCAL_TEST_REGISTRY}/licensechecker:v0 \
   		--artifact-type application/vnd.ratify.spdx.v0 \
   		--plain-http \
   		.staging/licensechecker/sbom.spdx:application/text
-	oras attach ${LOCAL_TEST_REGISTRY}/all:v0 \
+	${GITHUB_WORKSPACE}/bin/oras attach ${LOCAL_TEST_REGISTRY}/licensechecker:ociimage \
+  		--artifact-type application/vnd.ratify.spdx.v0 \
+  		--plain-http \
+		--image-spec v1.1-image \
+  		.staging/licensechecker/sbom.spdx:application/text
+	${GITHUB_WORKSPACE}/bin/oras attach ${LOCAL_TEST_REGISTRY}/all:v0 \
   		--artifact-type application/vnd.ratify.spdx.v0 \
   		--plain-http \
   		.staging/licensechecker/sbom.spdx:application/text
@@ -272,23 +287,32 @@ e2e-sbom-setup:
 	echo 'FROM alpine\nCMD ["echo", "sbom image"]' > .staging/sbom/Dockerfile
 	docker build -t ${LOCAL_TEST_REGISTRY}/sbom:v0 .staging/sbom
 	docker push ${LOCAL_TEST_REGISTRY}/sbom:v0
+	echo 'FROM alpine\nCMD ["echo", "sbom image oci image"]' > .staging/sbom/Dockerfile
+	docker build -t ${LOCAL_TEST_REGISTRY}/sbom:ociimage .staging/sbom
+	docker push ${LOCAL_TEST_REGISTRY}/sbom:ociimage
 	echo 'FROM alpine\nCMD ["echo", "sbom image unsigned"]' > .staging/sbom/Dockerfile
 	docker build -t ${LOCAL_TEST_REGISTRY}/sbom:unsigned .staging/sbom
 	docker push ${LOCAL_TEST_REGISTRY}/sbom:unsigned
 
 	# Generate/Attach sbom
 	.staging/sbom/sbom-tool generate -b .staging/sbom -bc .staging/sbom/ratify -pn ratify -m .staging/sbom -pv 1.0 -ps acme -nsu ratify -nsb http://registry:5000 -D true
-	oras attach \
+	${GITHUB_WORKSPACE}/bin/oras attach \
 		--artifact-type org.example.sbom.v0 \
 		--plain-http \
 		 ${LOCAL_TEST_REGISTRY}/sbom:v0 \
 		.staging/sbom/_manifest/spdx_2.2/manifest.spdx.json:application/spdx+json
-	oras attach \
+	${GITHUB_WORKSPACE}/bin/oras attach \
+		--artifact-type org.example.sbom.v0 \
+		--plain-http \
+		--image-spec v1.1-image \
+		 ${LOCAL_TEST_REGISTRY}/sbom:ociimage \
+		.staging/sbom/_manifest/spdx_2.2/manifest.spdx.json:application/spdx+json
+	${GITHUB_WORKSPACE}/bin/oras attach \
 		--artifact-type org.example.sbom.v0 \
 		--plain-http \
 		 ${LOCAL_TEST_REGISTRY}/sbom:unsigned \
 		.staging/sbom/_manifest/spdx_2.2/manifest.spdx.json:application/spdx+json
-	oras attach \
+	${GITHUB_WORKSPACE}/bin/oras attach \
 		--artifact-type org.example.sbom.v0 \
 		--plain-http \
 		 ${LOCAL_TEST_REGISTRY}/all:v0 \
@@ -296,6 +320,7 @@ e2e-sbom-setup:
 
 	# Push Signature to sbom
 	.staging/notaryv2/notation sign -u ${LOCAL_TEST_REGISTRY_USERNAME} -p ${LOCAL_TEST_REGISTRY_PASSWORD} ${LOCAL_TEST_REGISTRY}/sbom@`oras discover -o json --artifact-type org.example.sbom.v0 ${LOCAL_TEST_REGISTRY}/sbom:v0 | jq -r ".manifests[0].digest"`
+	.staging/notaryv2/notation sign --signature-manifest image -u ${LOCAL_TEST_REGISTRY_USERNAME} -p ${LOCAL_TEST_REGISTRY_PASSWORD} ${LOCAL_TEST_REGISTRY}/sbom@`oras discover -o json --artifact-type org.example.sbom.v0 ${LOCAL_TEST_REGISTRY}/sbom:ociimage | jq -r ".manifests[0].digest"`
 	.staging/notaryv2/notation sign -u ${LOCAL_TEST_REGISTRY_USERNAME} -p ${LOCAL_TEST_REGISTRY_PASSWORD} ${LOCAL_TEST_REGISTRY}/all@`oras discover -o json --artifact-type org.example.sbom.v0 ${LOCAL_TEST_REGISTRY}/all:v0 | jq -r ".manifests[0].digest"` 
 
 e2e-schemavalidator-setup:
@@ -310,14 +335,22 @@ e2e-schemavalidator-setup:
 	echo 'FROM alpine\nCMD ["echo", "schemavalidator image"]' > .staging/schemavalidator/Dockerfile
 	docker build -t ${LOCAL_TEST_REGISTRY}/schemavalidator:v0 .staging/schemavalidator
 	docker push ${LOCAL_TEST_REGISTRY}/schemavalidator:v0
+	echo 'FROM alpine\nCMD ["echo", "schemavalidator image oci image"]' > .staging/schemavalidator/Dockerfile
+	docker build -t ${LOCAL_TEST_REGISTRY}/schemavalidator:ociimage .staging/schemavalidator
+	docker push ${LOCAL_TEST_REGISTRY}/schemavalidator:ociimage
 
 	# Create/Attach Scan Results
 	.staging/schemavalidator/trivy image --format sarif --output .staging/schemavalidator/trivy-scan.sarif ${LOCAL_TEST_REGISTRY}/schemavalidator:v0
-	oras attach \
+	${GITHUB_WORKSPACE}/bin/oras attach \
 		--artifact-type vnd.aquasecurity.trivy.report.sarif.v1 \
 		${LOCAL_TEST_REGISTRY}/schemavalidator:v0 \
 		.staging/schemavalidator/trivy-scan.sarif:application/sarif+json
-	oras attach \
+	${GITHUB_WORKSPACE}/bin/oras attach \
+		--artifact-type vnd.aquasecurity.trivy.report.sarif.v1 \
+		${LOCAL_TEST_REGISTRY}/schemavalidator:ociimage \
+		--image-spec v1.1-image \
+		.staging/schemavalidator/trivy-scan.sarif:application/sarif+json
+	${GITHUB_WORKSPACE}/bin/oras attach \
 		--artifact-type vnd.aquasecurity.trivy.report.sarif.v1 \
 		${LOCAL_TEST_REGISTRY}/all:v0 \
 		.staging/schemavalidator/trivy-scan.sarif:application/sarif+json
diff --git a/pkg/ocispecs/descriptor.go b/pkg/ocispecs/descriptor.go
index 2a9e170..c37a197 100644
--- a/pkg/ocispecs/descriptor.go
+++ b/pkg/ocispecs/descriptor.go
@@ -32,7 +32,6 @@ type ReferenceManifest struct {
 	ArtifactType string            `json:"artifactType,omitempty"`
 	Blobs        []oci.Descriptor  `json:"blobs"`
 	Subject      *oci.Descriptor   `json:"subject,omitempty"`
-	Layers       []oci.Descriptor  `json:"layers"`
 	Annotations  map[string]string `json:"annotations,omitempty"`
 }
 
diff --git a/pkg/referrerstore/oras/oras.go b/pkg/referrerstore/oras/oras.go
index 19e6065..b8acffe 100644
--- a/pkg/referrerstore/oras/oras.go
+++ b/pkg/referrerstore/oras/oras.go
@@ -336,9 +336,20 @@ func (store *orasStore) GetReferenceManifest(ctx context.Context, subjectReferen
 	}
 
 	referenceManifest := ocispecs.ReferenceManifest{}
+
 	// marshal manifest bytes into reference manifest descriptor
-	if err := json.Unmarshal(manifestBytes, &referenceManifest); err != nil {
-		return ocispecs.ReferenceManifest{}, err
+	if referenceDesc.Descriptor.MediaType == oci.MediaTypeImageManifest {
+		var imageManifest oci.Manifest
+		if err := json.Unmarshal(manifestBytes, &imageManifest); err != nil {
+			return ocispecs.ReferenceManifest{}, err
+		}
+		referenceManifest = OciManifestToReferenceManifest(imageManifest)
+	} else if referenceDesc.Descriptor.MediaType == oci.MediaTypeArtifactManifest {
+		if err := json.Unmarshal(manifestBytes, &referenceManifest); err != nil {
+			return ocispecs.ReferenceManifest{}, err
+		}
+	} else {
+		return ocispecs.ReferenceManifest{}, fmt.Errorf("unsupported manifest media type: %s", referenceDesc.Descriptor.MediaType)
 	}
 
 	return referenceManifest, nil
diff --git a/pkg/referrerstore/oras/utils.go b/pkg/referrerstore/oras/utils.go
index 4d4b7dd..9984ae3 100644
--- a/pkg/referrerstore/oras/utils.go
+++ b/pkg/referrerstore/oras/utils.go
@@ -53,3 +53,13 @@ func OciDescriptorToReferenceDescriptor(ociDescriptor oci.Descriptor) ocispecs.R
 		ArtifactType: ociDescriptor.ArtifactType,
 	}
 }
+
+func OciManifestToReferenceManifest(ociManifest oci.Manifest) ocispecs.ReferenceManifest {
+	return ocispecs.ReferenceManifest{
+		MediaType:    ociManifest.MediaType,
+		ArtifactType: ociManifest.Config.MediaType,
+		Blobs:        ociManifest.Layers,
+		Subject:      ociManifest.Subject,
+		Annotations:  ociManifest.Annotations,
+	}
+}
diff --git a/pkg/verifier/notaryv2/notaryv2.go b/pkg/verifier/notaryv2/notaryv2.go
index 8482be3..41c1a7c 100644
--- a/pkg/verifier/notaryv2/notaryv2.go
+++ b/pkg/verifier/notaryv2/notaryv2.go
@@ -161,6 +161,10 @@ func (v *notaryV2Verifier) Verify(ctx context.Context,
 		return verifier.VerifierResult{IsSuccess: false}, fmt.Errorf("failed to get reference manifest for reference: %s, err: %w", subjectReference.Original, err)
 	}
 
+	if len(referenceManifest.Blobs) == 0 {
+		return verifier.VerifierResult{IsSuccess: false}, fmt.Errorf("no signature content found for referrer: %s@%s", subjectReference.Path, referenceDescriptor.Digest.String())
+	}
+
 	for _, blobDesc := range referenceManifest.Blobs {
 		refBlob, err := store.GetBlobContent(ctx, subjectReference, blobDesc.Digest)
 		if err != nil {
diff --git a/plugins/verifier/cosign/cosign.go b/plugins/verifier/cosign/cosign.go
index 7e2b14b..d5892bb 100644
--- a/plugins/verifier/cosign/cosign.go
+++ b/plugins/verifier/cosign/cosign.go
@@ -134,7 +134,7 @@ func VerifyReference(args *skel.CmdArgs, subjectReference common.Reference, refe
 	}
 
 	signatures := []oci.Signature{}
-	for _, blob := range referenceManifest.Layers {
+	for _, blob := range referenceManifest.Blobs {
 		blobBytes, err := referrerStore.GetBlobContent(ctx, subjectReference, blob.Digest)
 		if err != nil {
 			return errorToVerifyResult(input.Config.Name, fmt.Errorf("failed to get blob content: %w", err)), nil
diff --git a/plugins/verifier/licensechecker/licensechecker.go b/plugins/verifier/licensechecker/licensechecker.go
index 4fc4b02..3419d41 100644
--- a/plugins/verifier/licensechecker/licensechecker.go
+++ b/plugins/verifier/licensechecker/licensechecker.go
@@ -67,6 +67,14 @@ func VerifyReference(args *skel.CmdArgs, subjectReference common.Reference, desc
 		return nil, err
 	}
 
+	if len(referenceManifest.Blobs) == 0 {
+		return &verifier.VerifierResult{
+			Name:      input.Name,
+			IsSuccess: false,
+			Message:   fmt.Sprintf("License Check FAILED: no blobs found for referrer %s@%s", subjectReference.Path, descriptor.Digest.String()),
+		}, nil
+	}
+
 	for _, blobDesc := range referenceManifest.Blobs {
 		refBlob, err := store.GetBlobContent(ctx, subjectReference, blobDesc.Digest)
 		if err != nil {
diff --git a/plugins/verifier/schemavalidator/schema_validator.go b/plugins/verifier/schemavalidator/schema_validator.go
index 4021076..ba242df 100644
--- a/plugins/verifier/schemavalidator/schema_validator.go
+++ b/plugins/verifier/schemavalidator/schema_validator.go
@@ -67,6 +67,14 @@ func VerifyReference(args *skel.CmdArgs, subjectReference common.Reference, refe
 		return nil, fmt.Errorf("error fetching reference manifest for subject: %s reference descriptor: %v", subjectReference, referenceDescriptor.Descriptor)
 	}
 
+	if len(referenceManifest.Blobs) == 0 {
+		return &verifier.VerifierResult{
+			Name:      input.Name,
+			IsSuccess: false,
+			Message:   fmt.Sprintf("schema validation failed: no blobs found for referrer %s@%s", subjectReference.Path, referenceDescriptor.Digest.String()),
+		}, nil
+	}
+
 	for _, blobDesc := range referenceManifest.Blobs {
 		refBlob, err := referrerStore.GetBlobContent(ctx, subjectReference, blobDesc.Digest)
 		if err != nil {


Test patch:
diff --git a/pkg/referrerstore/oras/utils_test.go b/pkg/referrerstore/oras/utils_test.go
new file mode 100644
index 0000000..df89989
--- /dev/null
+++ b/pkg/referrerstore/oras/utils_test.go
@@ -0,0 +1,93 @@
+/*
+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 oras
+
+import (
+	"reflect"
+	"testing"
+
+	"github.com/deislabs/ratify/pkg/ocispecs"
+	oci "github.com/opencontainers/image-spec/specs-go/v1"
+)
+
+func TestOciManifestToReferenceManifest(t *testing.T) {
+	type args struct {
+		ociManifest oci.Manifest
+	}
+	tests := []struct {
+		name string
+		args args
+		want ocispecs.ReferenceManifest
+	}{
+		{
+			name: "empty",
+			args: args{
+				ociManifest: oci.Manifest{},
+			},
+			want: ocispecs.ReferenceManifest{
+				MediaType:    "",
+				ArtifactType: "",
+			},
+		},
+		{
+			name: "simple",
+			args: args{
+				ociManifest: oci.Manifest{
+					MediaType: "application/vnd.oci.image.manifest.v1+json",
+					Config: oci.Descriptor{
+						MediaType: "application/vnd.oci.image.config.v1+json",
+					},
+				},
+			},
+			want: ocispecs.ReferenceManifest{
+				MediaType:    "application/vnd.oci.image.manifest.v1+json",
+				ArtifactType: "application/vnd.oci.image.config.v1+json",
+			},
+		},
+		{
+			name: "layers",
+			args: args{
+				ociManifest: oci.Manifest{
+					MediaType: "application/vnd.oci.image.manifest.v1+json",
+					Config: oci.Descriptor{
+						MediaType: "application/vnd.oci.image.config.v1+json",
+					},
+					Layers: []oci.Descriptor{
+						{
+							MediaType: "application/vnd.oci.image.layer.v1.tar+gzip",
+						},
+					},
+				},
+			},
+			want: ocispecs.ReferenceManifest{
+				MediaType:    "application/vnd.oci.image.manifest.v1+json",
+				ArtifactType: "application/vnd.oci.image.config.v1+json",
+				Blobs: []oci.Descriptor{
+					{
+						MediaType: "application/vnd.oci.image.layer.v1.tar+gzip",
+					},
+				},
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := OciManifestToReferenceManifest(tt.args.ociManifest); !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("OciManifestToReferenceManifest() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
diff --git a/test/bats/cli-test.bats b/test/bats/cli-test.bats
index b70cb87..6918506 100644
--- a/test/bats/cli-test.bats
+++ b/test/bats/cli-test.bats
@@ -6,6 +6,10 @@ load helpers
     run bin/ratify verify -c $RATIFY_DIR/config.json -s $LOCAL_TEST_REGISTRY/notation:signed
     assert_cmd_verify_success
 
+    # Test with OCI Image Manifest Signature
+    run bin/ratify verify -c $RATIFY_DIR/config.json -s $LOCAL_TEST_REGISTRY/notation:signedImage
+    assert_cmd_verify_success
+
     run bin/ratify verify -c $RATIFY_DIR/config.json -s $LOCAL_TEST_REGISTRY/notation:unsigned
     assert_cmd_verify_failure
 }
@@ -25,6 +29,10 @@ load helpers
     run bin/ratify verify -c $RATIFY_DIR/complete_licensechecker_config.json -s $LOCAL_TEST_REGISTRY/licensechecker:v0
     assert_cmd_verify_success
 
+    # Test with OCI Image Manifest Signature
+    run bin/ratify verify -c $RATIFY_DIR/complete_licensechecker_config.json -s $LOCAL_TEST_REGISTRY/licensechecker:ociimage
+    assert_cmd_verify_success
+
     run bin/ratify verify -c $RATIFY_DIR/partial_licensechecker_config.json -s $LOCAL_TEST_REGISTRY/licensechecker:v0
     assert_cmd_verify_failure
 }
@@ -34,6 +42,10 @@ load helpers
     run bin/ratify verify -c $RATIFY_DIR/config.json -s $LOCAL_TEST_REGISTRY/sbom:v0
     assert_cmd_verify_success
 
+    # Test with OCI Image Manifest Signature
+    run bin/ratify verify -c $RATIFY_DIR/config.json -s $LOCAL_TEST_REGISTRY/sbom:ociimage
+    assert_cmd_verify_success
+
     run bin/ratify verify -c $RATIFY_DIR/config.json -s $LOCAL_TEST_REGISTRY/sbom:unsigned
     assert_cmd_verify_failure
 }
@@ -41,6 +53,10 @@ load helpers
 @test "schemavalidator verifier test" {
     run bin/ratify verify -c $RATIFY_DIR/schemavalidator_config.json -s $LOCAL_TEST_REGISTRY/schemavalidator:v0
     assert_cmd_verify_success
+
+    # Test with OCI Image Manifest Signature
+    run bin/ratify verify -c $RATIFY_DIR/schemavalidator_config.json -s $LOCAL_TEST_REGISTRY/schemavalidator:ociimage
+    assert_cmd_verify_success
 }
 
 @test "sbom/notary/cosign/licensechecker verifiers test" {
diff --git a/test/bats/test.bats b/test/bats/test.bats
index 4ee5abd..af65dee 100644
--- a/test/bats/test.bats
+++ b/test/bats/test.bats
@@ -10,6 +10,7 @@ SLEEP_TIME=1
     teardown() {
         echo "cleaning up"
         wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete pod demo --namespace default --force --ignore-not-found=true'
+        wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete pod demo-oci-image --namespace default --force --ignore-not-found=true'
         wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete pod demo1 --namespace default --force --ignore-not-found=true'
     }
     run kubectl apply -f ./library/default/template.yaml
@@ -20,6 +21,9 @@ SLEEP_TIME=1
     sleep 5
     run kubectl run demo --namespace default --image=registry:5000/notation:signed
     assert_success
+    # notation signature with OCI Image manifest format
+    run kubectl run demo-oci-image --namespace default --image=registry:5000/notation:signedImage
+    assert_success
     run kubectl run demo1 --namespace default --image=registry:5000/notation:unsigned
     assert_failure
 }
@@ -71,6 +75,7 @@ SLEEP_TIME=1
         echo "cleaning up"
         wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete pod license-checker --namespace default --force --ignore-not-found=true'
         wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete pod license-checker2 --namespace default --force --ignore-not-found=true'
+        wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete pod license-checker-oci-image --namespace default --force --ignore-not-found=true'
         wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete verifiers.config.ratify.deislabs.io/verifier-license-checker --namespace default --ignore-not-found=true'
     }
 
@@ -90,12 +95,16 @@ SLEEP_TIME=1
     sleep 15
     run kubectl run license-checker2 --namespace default --image=registry:5000/licensechecker:v0
     assert_success
+    # licensechecker artifact with OCI Image manifest format
+    run kubectl run license-checker-oci-image --namespace default --image=registry:5000/licensechecker:ociimage
+    assert_success
 }
 
 @test "sbom verifier test" {
     teardown() {
         echo "cleaning up"
         wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete pod sbom --namespace default --force --ignore-not-found=true'
+        wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete pod sbom-oci-image --namespace default --force --ignore-not-found=true'
         wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete pod sbom2 --namespace default --force --ignore-not-found=true'
     }
 
@@ -110,6 +119,9 @@ SLEEP_TIME=1
     sleep 5
     run kubectl run sbom --namespace default --image=registry:5000/sbom:v0
     assert_success
+    # sbom with OCI Image manifest format
+    run kubectl run sbom-oci-image --namespace default --image=registry:5000/sbom:ociimage
+    assert_success
 
     run kubectl delete verifiers.config.ratify.deislabs.io/verifier-sbom
     assert_success
@@ -127,6 +139,7 @@ SLEEP_TIME=1
         wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete verifiers.config.ratify.deislabs.io/verifier-sbom --namespace default --ignore-not-found=true'
         wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete verifiers.config.ratify.deislabs.io/verifier-schemavalidator --namespace default --ignore-not-found=true'
         wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete pod schemavalidator --namespace default --force --ignore-not-found=true'
+        wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete pod schemavalidator-oci-image --namespace default --force --ignore-not-found=true'
         wait_for_process ${WAIT_TIME} ${SLEEP_TIME} 'kubectl delete pod schemavalidator2 --namespace default --force --ignore-not-found=true'
     }
 
@@ -141,6 +154,9 @@ SLEEP_TIME=1
     sleep 5
     run kubectl run schemavalidator --namespace default --image=registry:5000/schemavalidator:v0
     assert_success
+    # schemavalidator with OCI Image manifest format
+    run kubectl run schemavalidator-oci-image --namespace default --image=registry:5000/schemavalidator:ociimage
+    assert_success
 
     run kubectl apply -f ./config/samples/config_v1alpha1_verifier_schemavalidator_bad.yaml
     assert_success