TIL - Docker signature verification

Docker hardened images are signed with cosign

Image publishers usually only sign the multi-platform tag. This should provide enough trust since the multi-platform tag does not point to a sinlge docker image but to a manifest list which provides a list of all different tags for each platform with its digest.

Inspecting a multi-platform image

Lets take the aquasec/trivy image as example. We can use docker to retrieve the manifest with:

docker buildx imagetools inspect --raw aquasec/trivy@sha256:be1190afcb28352bfddc4ddeb71470835d16462af68d310f9f4bca710961a41e

Here we see the list of different platform images with their digests:

{
   "schemaVersion": 2,
   "mediaType": "application/vnd.docker.distribution.manifest.list.v2+json",
   "manifests": [
      {
         "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
         "size": 1159,
         "digest": "sha256:85e87be1a96459c38a4eea47dc64eb2d342bb14cd4b4cef96adcf6ff03378b7c",
         "platform": {
            "architecture": "amd64",
            "os": "linux"
         }
      },
      {
         "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
         "size": 1159,
         "digest": "sha256:06c0a1359e4d745c6d709f5b64421db79fe0b5602c87d6eb5d6ba7d3d79d91ab",
         "platform": {
            "architecture": "arm64",
            "os": "linux"
         }
      },
      {
         "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
         "size": 1159,
         "digest": "sha256:6d2abdf6a6fcaa5d48921f0b372bee9ea3f4c29d028eec3e3e5a2d90d87feadf",
         "platform": {
            "architecture": "ppc64le",
            "os": "linux"
         }
      },
      {
         "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
         "size": 1159,
         "digest": "sha256:1e480b4dc4d70168b6eeeebb64d101954a1e310cd8d4ba8c88c77c990659d63e",
         "platform": {
            "architecture": "s390x",
            "os": "linux"
         }
      }
   ]
}

Checking image signature with cosign

This can be done with cosign verify, which verifies a signature and annotations on an image by checking the claims against the transparency log. Fulcio, which has such a good documentation, is commonly used as certificate authority.

To verify that the image was signed using the oidc announced by the publisher use the args –certificate-oidc-issuer and -certificate-identity-regexp. Cosign will check that the Fulcio certificate was signed using those values.

Lets see the output of a verified multi-platform tag:

cosign verify aquasec/trivy@sha256:be1190afcb28352bfddc4ddeb71470835d16462af68d310f9f4bca710961a41e \
  --certificate-identity-regexp 'https://github.com/aquasecurity/trivy/.*' \
  --certificate-oidc-issuer 'https://token.actions.githubusercontent.com'

This will return:

[
  {
    "critical": {
      "identity": {
        "docker-reference": "index.docker.io/aquasec/trivy:0.70.0"
      },
      "image": {
        "docker-manifest-digest": "sha256:be1190afcb28352bfddc4ddeb71470835d16462af68d310f9f4bca710961a41e"
      },
      "type": "https://sigstore.dev/cosign/sign/v1"
    },
    "optional": {}
  },
  {
    "critical": {
      "identity": {
        "docker-reference": "index.docker.io/aquasec/trivy:0.70.0"
      },
      "image": {
        "docker-manifest-digest": "sha256:be1190afcb28352bfddc4ddeb71470835d16462af68d310f9f4bca710961a41e"
      },
      "type": "https://sigstore.dev/cosign/sign/v1"
    },
    "optional": {}
  }
]

And lets compare that to a tag which was not signed, lets take a platform tag from above:

cosign verify aquasec/trivy@sha256:85e87be1a96459c38a4eea47dc64eb2d342bb14cd4b4cef96adcf6ff03378b7c \
  --certificate-identity-regexp 'https://github.com/aquasecurity/trivy/.*' \
  --certificate-oidc-issuer 'https://token.actions.githubusercontent.com'

Error: no signatures found
error during command execution: no signatures found

Or if we try to verify the multi-platform tag with an different issuer:

cosign verify aquasec/trivy@sha256:be1190afcb28352bfddc4ddeb71470835d16462af68d310f9f4bca710961a41e \
  --certificate-identity-regexp 'https://github.com/aquasecurity/trivy/.*' \
  --certificate-oidc-issuer 'https://google.com'

Error: no matching attestations: failed to verify certificate identity: no matching CertificateIdentity found, last error: expected issuer value "https://google.com", got "https://token.actions.githubusercontent.com"
failed to verify certificate identity: no matching CertificateIdentity found, last error: expected issuer value "https://google.com", got "https://token.actions.githubusercontent.com"
error during command execution: no matching attestations: failed to verify certificate identity: no matching CertificateIdentity found, last error: expected issuer value "https://google.com", got "https://token.actions.githubusercontent.com"
failed to verify certificate identity: no matching CertificateIdentity found, last error: expected issuer value "https://google.com", got "https://token.actions.githubusercontent.com"

A useful error which can help identify the correct issuer.

Similarly if use the wrong the identity (note the missing ‘y’ in trivy), a useful error message wil be given:

cosign verify aquasec/trivy@sha256:be1190afcb28352bfddc4ddeb71470835d16462af68d310f9f4bca710961a41e \
  --certificate-identity-regexp 'https://github.com/aquasecurity/triv/.*' \
  --certificate-oidc-issuer 'https://token.actions.githubusercontent.com'

Error: no matching attestations: failed to verify certificate identity: no matching CertificateIdentity found, last error: expected SAN value to match regex "https://github.com/aquasecurity/triv/.*", got "https://github.com/aquasecurity/trivy/.github/workflows/reusable-release.yaml@refs/tags/v0.70.0"
failed to verify certificate identity: no matching CertificateIdentity found, last error: expected SAN value to match regex "https://github.com/aquasecurity/triv/.*", got "https://github.com/aquasecurity/trivy/.github/workflows/reusable-release.yaml@refs/tags/v0.70.0"
error during command execution: no matching attestations: failed to verify certificate identity: no matching CertificateIdentity found, last error: expected SAN value to match regex "https://github.com/aquasecurity/triv/.*", got "https://github.com/aquasecurity/trivy/.github/workflows/reusable-release.yaml@refs/tags/v0.70.0"
failed to verify certificate identity: no matching CertificateIdentity found, last error: expected SAN value to match regex "https://github.com/aquasecurity/triv/.*", got "https://github.com/aquasecurity/trivy/.github/workflows/reusable-release.yaml@refs/tags/v0.70.0"

Links

As mentioned Fulcio has a very interesting and detailed documentation about the security of the certificates: