Skip to main content
Version: 1.x

How to sign WebAssembly components with Cosign (OIDC)

Software signing is an essential piece of maintaining a secure software supply chain and verifying the provenance and integrity of software artifacts.

This document demonstrates how to use Cosign to sign WebAssembly component artifacts, push those artifacts to an OCI-compliant registry (GHCR), and verify them prior to execution. The example will use OIDC-based signing so signatures are linked to an identity and stored as invisible infrastructure alongside your artifact.

Why sign WebAssembly components?

Like traditional packages, WebAssembly components should be signed, stored, and verified before deploying in production in order to ensure the component wasn’t modified post-build, bind the artifact to a known publisher identity, and align with modern supply-chain expectations.

The following example demonstrates one among many possible patterns for signing a component, pushing to a registry, and verifying an artifact before deployment.

Prerequisites

In addition to Wasm Shell (wash) and standard Rust tooling for developing components (cargo 1.82+ and the wasm32-wasip2 target installed with rustup target add wasm32-wasip2), you will need the following open source CLI tools:

  • rekor-cli for signing certificate retrieval
  • openssl cryptography toolkit (likely already on your system, install with your package manager of choice if not)
  • cosign for securely signing software artifacts

Create a component

Generate a Rust-based WebAssembly component with wash:

bash
wash new

At the prompts, select a Rust component and the http-hello-world template.

Build the component

From your new project directory http-hello-world, use cargo to compile your component in release mode for WASI P2:

bash
cargo build --release --target wasm32-wasip2

Confirm that the WebAssembly component has been built:

bash
ls -la target/wasm32-wasip2/release/http_hello_world.wasm

If you would like to test or iterate on this component, you can use wash dev and compile the component again.

Authenticate to GitHub Container Registry

Before using any cosign commands to upload or sign against ghcr.io, authenticate Docker and confirm that your Personal Access Token (PAT) used for docker login ghcr.io has the following permissions:

  • read:packages (pull)
  • write:packages (push)

Note: When authenticating in a GitHub Action, you can use a GITHUB_TOKEN rather than a PAT—see the GitHub Actions documentation for more information.

Authenticate:

bash
echo $GITHUB_TOKEN | docker login ghcr.io -u <your-username> --password-stdin

There are multiple ways to sign and verify artifacts with Cosign (key-pair, keyless/OIDC, hardware-backed, GitHub Actions, etc.). This tutorial demonstrates OIDC (keyless) signing for WebAssembly artifacts.

There are a variety of strategies for artifact signing—this example demonstrates one possible method. See the Sigstore documentation for a broader overview.

Upload the WebAssembly component to GHCR

Upload the built WebAssembly component artifact (note the path):

bash
cosign upload wasm -f target/wasm32-wasip2/release/http_hello_world.wasm ghcr.io/<your-org>/http-hello-world

Sign the WebAssembly component with Cosign (OIDC, No Key)

The Cosign philosophy is that signatures should be invisible infrastructure, living beside artifacts in a registry without adding operational friction for consumers.

Cosign's OIDC mode does not require you to specify a key when you sign the component. Instead, when you run the cosign sign command, Cosign will:

  • Launch a browser flow to authenticate you with an OIDC provider (e.g., GitHub, Google).
  • Generate ephemeral keys.
  • Obtain a short-lived signing certificate** binding your identity to the signature.
  • Store the signature with the artifact in the registry and record it in the transparency log (Rekor).

We recommend that you always sign by digest (@sha256:...) rather than a tag (e.g., :latest). Tags move—digests uniquely identify the content you intend to sign.

In this case, you can copy the digest from your package's GHCR page and then run cosign sign:

bash
cosign sign ghcr.io/<your-org>/http-hello-world@sha256:<your-digest>

You should see output like the following:

text
setting TUF refresh period to 24h0m0s
Generating ephemeral keys...
Retrieving signed certificate...

    The sigstore service, hosted by sigstore a Series of LF Projects, LLC, is provided pursuant to the Hosted Project Tools Terms of Use, available at https://lfprojects.org/policies/hosted-project-tools-terms-of-use/.
    Note that if your submission includes personal data associated with this signed artifact, it will be part of an immutable record.
    This may include the email address associated with the account with which you authenticate your contractual Agreement.
    This information will be used for signing this artifact and will be stored in public transparency logs and cannot be removed later, and is subject to the Immutable Record notice at https://lfprojects.org/policies/hosted-project-tools-immutable-records/.

By typing 'y', you attest that (1) you are not submitting the personal data of any other person; and (2) you understand and agree to the statement and the Agreement terms at the URLs listed above.
Are you sure you would like to continue? [y/N] y
Your browser will now be opened to:
https://oauth2.sigstore.dev/auth/auth?access_type=online&client_id=sigstore&code_challenge=NE7EzOgpwQLE2rvBN45okIWw9ajx8ZLivQNf-dpoo5k&code_challenge_method=S256&nonce=326kV0EDXNFRD362gXkRHYtyToE&redirect_uri=http%3A%2F%2Flocalhost%3A56069%2Fauth%2Fcallback&response_type=code&scope=openid+email&state=326kUvmKpRbDn6nuy6kpgeq1SE0
Successfully verified SCT...
WARNING: "ghcr.io/<your-org>/http-hello-world" appears to be a private repository, please confirm uploading to the transparency log at "https://rekor.sigstore.dev"
Are you sure you would like to continue? [y/N] y
tlog entry created with index: 457070846
Pushing signature to: ghcr.io/<your-org>/http-hello-world

In the process above:

  • Cosign refreshes trusted metadata (TUF), generates a short-lived key pair, and retrieves a signed certificate.
  • You confirm the immutability and public transparency of the record (Rekor).
  • Browser-based OIDC auth binds your identity to the certificate.
  • Cosign pushes the signature to GHCR and records it in the transparency log.

Retrieve the signer’s public certificate from the transparency log (Rekor)

Every OIDC (keyless) signature is recorded in the Rekor transparency log, an append-only, publicly auditable ledger. Each entry includes the ephemeral public key certificate used to sign the artifact. You can retrieve it for auditing, pinning, or offline validation.

Use the log index from the signing output above (tlog entry created with index: 457070846) to fetch the entry:

bash
rekor-cli get --log-index <your-log-index>

You should see output like the following:

bash
LogID: c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d
Index: 457070846
IntegratedTime: 2025-09-01T17:41:00Z
UUID: 108e9186e8c5677ae2e00d3b5962864ce70caf89477763e66e37b3570d54962f2ab0b136abfda517
Body: {
  "HashedRekordObj": {
    "data": {
      "hash": {
        "algorithm": "sha256",
        "value": "feb69f67b43a716974e85b983cdd642850c368e0bb1719f3396cc0d651874bd0"
      }
    },
    "signature": {
      "content": "MEUCIE10cxf4DSJ6GPUTPVu6PObhbamPhNCgCuqKkYGcRbhnAiEAqudA6Y4MYMb/jdl0ULyj2Xzd0CLyWTaAwSAzi9sVzaw=",
      "publicKey": {
        "content": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUN5VENDQWxDZ0F3SUJBZ0lVV01xZkdlM1hnejFQYVBSSlVuRXlCM2lMV25vd0NnWUlLb1pJemowRUF3TXcKTnpFVk1CTUdBMVVFQ2hNTWMybG5jM1J2Y21VdVpHVjJNUjR3SEFZRFZRUURFeFZ6YVdkemRHOXlaUzFwYm5SbApjbTFsWkdsaGRHVXdIaGNOTWpVd09UQXhNVGMwTURNMVdoY05NalV3T1RBeE1UYzFNRE0xV2pBQU1Ga3dFd1lICktvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUU2andaTksyOW8yamVYcWZHNCtBbkU3OEc3Y1FhdHNmL1ppUEoKTHBDS1g0UjI0eVZSZk94Yzc0bUFzcGJ2bUZiU0lwUWdGZU5GdVQ4Yy9mN2VjeEU3dktPQ0FXOHdnZ0ZyTUE0RwpBMVVkRHdFQi93UUVBd0lIZ0RBVEJnTlZIU1VFRERBS0JnZ3JCZ0VGQlFjREF6QWRCZ05WSFE0RUZnUVVEWW9oClNQNlJBYlU5d1hOc1JwYTJmQ3V2ZDRFd0h3WURWUjBqQkJnd0ZvQVUzOVBwejFZa0VaYjVxTmpwS0ZXaXhpNFkKWkQ4d0h3WURWUjBSQVFIL0JCVXdFNEVSYkdsaGJVQmpiM050YjI1cFl5NWpiMjB3S1FZS0t3WUJCQUdEdnpBQgpBUVFiYUhSMGNITTZMeTloWTJOdmRXNTBjeTVuYjI5bmJHVXVZMjl0TUNzR0Npc0dBUVFCZzc4d0FRZ0VIUXdiCmFIUjBjSE02THk5aFkyTnZkVzUwY3k1bmIyOW5iR1V1WTI5dE1JR0tCZ29yQmdFRUFkWjVBZ1FDQkh3RWVnQjQKQUhZQTNUMHdhc2JIRVRKakdSNGNtV2MzQXFKS1hyamVQSzMvaDRweWdDOHA3bzRBQUFHWkJsM09YQUFBQkFNQQpSekJGQWlFQXlOUEtIRytwcEwrVU5FN25LUWptaDQzSWVxdUxVaXVJQXpNY3NhOXJybFlDSUc3OVBNNGRZMVJpCjl2MDhsczZBMk5vd1pCQWlrdytmaTJ5SGRwKy8vWkNsTUFvR0NDcUdTTTQ5QkFNREEyY0FNR1FDTUdnYjVtTWEKbDNyN3h2RjhqK2Rwa0lKOEpsaFFDT20zeGRWTXByZjl6Y0JodndibVZvTktuY3puQUF1ajQzeVZLQUl3V0hjRApRQXVjczJlLzU2ZmtjYUdycTl4a29QVEFkZWEwRm9PdnNxUW01OTZqWG1Oa0lMTGdXZWYzbGZqbGpKZHMKLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo="
      }
    }
  }
}
What is this?
  • Rekor entry: A tamper-evident record storing a hash of the content and metadata about the signature.
  • publicKey.content: A base64-encoded PEM of the short-lived X.509 code-signing certificate issued during OIDC signing. It binds your OIDC identity to the signature.
  • Use cases: Extract the certificate for auditing, pinning identities, or performing offline verification flows.

Extract the PEM-encoded certificate to a file:

bash
rekor-cli get --log-index <your-log-index> --format json   | jq -r '.Body.HashedRekordObj.signature.publicKey.content'   | base64 -d > signer-cert.pem

Inspect the certificate:

bash
openssl x509 -in signer-cert.pem -noout -subject -issuer -dates

You can also retrieve by UUID instead of index:

bash
rekor-cli get --uuid 108e9186e8c5677ae2e00d3b5962864ce70caf89477763e66e37b3570d54962f2ab0b136abfda517 --format json

Note: Manual Rekor retrieval isn’t required for cosign verify (the Rekor bundle is embedded), but it’s useful for audits, forensics, or air‑gapped validations where you want explicit control over verification inputs.

Verify the signed WebAssembly component (identity-scoped)

For OIDC-signed artifacts, you can verify and assert the expected identity and OIDC issuer. For example:

bash
cosign verify ghcr.io/<your-org>/http-hello-world@sha256:9e7a511fb3130ee4641baf1adc0400bed674d4afc3f1b81bb581c3c8f613f812  --certificate-identity=<your-identity> --certificate-oidc-issuer=<your-oidc-issuer>

Cosign will return the following information:

  • Claims validated: Cosign verified the signature payload structure and contents.
  • Transparency log proof: The signature exists in Rekor, and verification can occur offline using the embedded bundle.
  • Certificate chain: The short-lived code-signing certificate chains to a trusted CA, and the subject identity with issuer matches your verification flags.
  • The docker-manifest-digest equals the digest you signed, proving the exact artifact was verified.

Quick reference

Build a release artifact targeting WASI P2:

bash
cargo build --release --target wasm32-wasip2

Upload a Wasm artifact to GHCR:

bash
cosign upload wasm -f target/wasm32-wasip2/release/http_hello_world.wasm ghcr.io/<your-org>/http-hello-world

Sign by digest (OIDC, no --key):

bash
cosign sign ghcr.io/<your-org>/http-hello-world@sha256:<digest>

Verify with identity constraints:

bash
cosign verify ghcr.io/<your-org>/http-hello-world@sha256:<digest>   --certificate-identity=<your-email>   --certificate-oidc-issuer=https://accounts.google.com

Further reading