Learn how to use SignServer together with Cosign to create signed container images.

A signed container image allows users to verify where an image came from, ensuring it was not tampered with, and ensuring that only trusted images are pulled into their systems. A container signature identifies and authenticates who signed the image and carries a signed payload in a JSON file that identifies the signed image.

The following sections demonstrate how you can use SignServer to sign a payload generated by Cosign, and use Cosign to verify the signed container image.

Cosign is developed as part of the Sigstore project of the Linux foundation and allows storing signatures alongside the image or artifact in the Open Container Initiative (OCI) registry. For more information, refer to the Sigstore documentation on Cosign or Cosign on GitHub.

In this tutorial, you will learn how to:

  • Create signing key and CSR in SignServer

  • Issue signing certificate

  • Activate signing worker in SignServer
  • Create Docker container image 

  • Create container signature payload with Cosign

  • Sign payload with SignServer

  • Attach signed payload to container with Cosign

  • Verify signed container image with Cosign

Prerequisites

This tutorial uses an Arch Linux installation and SignServer Community and EJBCA Community Docker containers.

Before you begin, you need SignServer and EJBCA running.

Follow the quick start guides to start the containers as ephemeral instances which means that when you stop the container, it will automatically be removed and all data will be destroyed. This setup is suitable for testing or for evaluating added functionality in an updated version. For information on running the container with more production-like settings with persistent data, refer to SignServer on Docker Hub.

You also need to have Docker installed. To download and install, refer to the Docker documentation. This tutorial runs the docker commands with sudo.

Step 1 - Create signing key and CSR in SignServer

The following steps describe how to create a signing key that is stored in the SignServer database and create a Certificate Signing Request (CSR) for the signing key.

Create Crypto Worker

In SignServer, start by adding a crypto worker for storing the private keys. 

  1. Go to the SignServer Administration Web Workers page and click Add to add a new worker.
  2. On the Add Worker / Load Configuration page, choose the method From Template.
  3. In the Load From Template list menu, select the keystore-crypto.properties worker template and click Next.
  4. Update the following in the configuration:
    1. Change the keystore type "WORKERGENID1.KEYSTORETYPE=PKCS12" to "WORKERGENID1.KEYSTORETYPE=INTERNAL to store the signing keys in the SignServer database.
    2. Remove the line starting with "WORKERGENID1.KEYSTOREPATH" in order to not provide a path to any existing keystore.
  5. Click Apply to add the crypto worker.
  6. To activate the crypto worker, select the newly created "CryptoTokenP12" in the Workers list and click Activate.
  7. Specify a new password to create this keystore in the database, and then click Activate.
    Make sure to remember this password as it will be used again to re-activate the worker.

Generate Signing Key

To generate a signing key for the crypto token:

  1. On the SignServer Workers page, select the CryptoTokenP12 and then click Crypto Token.

  2. On the Crypto Token page, click Generate key.

    • For New Key Alias, specify a name for the key such as "PlainSigner".

    • For Key Algorithm, select ECDSA.
    • For Key Specification, use the prime256v1 curve.
  3. Click Generate and verify that the worker is now in state ACTIVE.

Now that you have created your signing key, you can create your signing worker.

Create Signing Worker

To create the signing worker:

  1. On the SignServer Workers page, click Add.

  2. On the Add Worker / Load Configuration page, choose the method From Template.
  3. In the Load From Template list menu, select the plainsigner.properties worker template and click Next.
  4. Update the following in the configuration:
    • Specify that the worker should use the "PlainSigner" key by changing "WORKERGENID1.DEFAULTKEY=00003" to "WORKERGENID1.DEFAULTKEY=PlainSigner"
  5. Click Apply to load the configuration.

The signing worker is OFFLINE as it needs a certificate. You now have a crypto worker and your key, you will next create a Certificate Signing Request (CSR).

Generate CSR

To create a Certificate Signing Request (CSR) for the signing key, do the following:

  1. Select the PlainSigner in the Workers list, and click Generate CSR.

  2. Update the following:
    • For Signature Algorithm, select SHA256withECDSA.
    • For DN (distinguished name) for the certificate, specify a common name such as "CN=PlainSigner".

  3. Click Generate and then click Download to store the CSR as a PKCS#10 file (PlainSigner-PlainSigner.p10).

Next, use the downloaded signing request to issue a certificate in EJBCA that then can be imported into SignServer.

Step 2 - Issue signing certificate

Once you have downloaded the Certificate Signing Request (CSR), you need to bring the CSR to your Certificate Authority (CA) to issue a signer certificate.

This tutorial uses the EJBCA CA. To learn how to get started with EJBCA, you can follow the Quick Start Guide - Start EJBCA Container with Client Certificate Authenticated Access.

The following sections describe how to create basic profiles in EJBCA and issue a digital signing certificate based on the downloaded CSR.

Create Certificate Profile

Certificate profiles define the constraints of the certificate, for example, what keys it can use, and what the extensions will be.

To create a certificate profile that the certificate will be issued under, follow these steps:

  1. In EJBCA, under CA Functions, click Certificate Profiles.
  2. Click the bottom row of the List of Certificate Profiles, specify the name PlainSigner, and then click Add.
    The newly created  template is displayed in the list of certificate profiles.
  3. Click Edit for the PlainSigner profile and specify the following:
    • For Available Key Algorithms, select ECDSA.
    • For Validity or end date of the certificate, specify 1y for one-year validity.
    • For Key Usage, clear Non-repudiation and Key encipherment to only use Digital Signature.
    • For Extended Key Usage, clear Use to not use any extended key usage.
  4. Leave the rest of the fields as default and click Save to store the profile.

Create End Entity Profile

To create an end entity profile with information about the holder of the certificate, follow these steps:

  1. In EJBCA, under RA Functions, click End Entity Profiles.
  2. In the Add End Entity Profile field, add a name for the new profile, in this example PlainSigner, and click Add profile.
    The newly created profile is displayed in the list of end entity profiles.
  3. Select the PlainSigner profile, and click Edit End Entity Profile.
  4. Update the following to map the end entity profile to the PlainSigner certificate profile:
    • For Available Certificate Profile, select PlainSigner.
    • For Default Certificate Profile, select PlainSigner.
  5. Click Save to store the updated end entity profile.

Now that you have created the profiles, you can continue and issue the certificate in the next step.

Issue certificate

To issue the certificate using the EJBCA RA UI:

  1. In EJBCA, click RA Web to access the EJBCA RA UI.
  2. Under Request new certificate, select Make New Request.
  3. For Select Request Template Certificate Type, select PlainSigner.
  4. For Key-pair generation, select Provided by user since the key pair was generated in SignServer.
  5. Under Upload CSR, click Browse and select to upload the CSR created earlier (PlainSigner-PlainSigner.p10).
  6. Under Provide request info, verify that the Required Subject DN Attributes is the Common Name specified, in this example PlainSigner.
  7. For Provide User Credentials, specify Username "PlainSigner" to save this in the EJBCA database under the user name PlainSigner.
  8. Click Download PEM full chain to download the certificate chain as a PEM file.

Your certificate is downloaded as a PlainSigner.pem file that you can now upload to SignServer.

Step 3 - Activate signing worker in SignServer

To install the signer certificate issued by the CA in SignServer, which will activate the PlainSigner worker, do the following:

  1. Go to the SignServer Administration Web Workers page, select the PlainSigner worker, and click Install Certificates.
  2. Click Browse to select the PEM certificate file (PlainSigner.pem), and then click Add to upload the certificate chain.

  3. Click Install to install the certificate in the worker.

  4. Confirm that the PlainSigner worker is listed as ACTIVE and ready to be used.

Your active PlainSigner worker can now be used to sign a container image that you will create in the next step.

Step 4 - Create Docker container image 

To build a docker container image, you use a Dockerfile. The Dockerfile is a text-based file that contains a script of instructions that Docker uses to create the container image. You can use the text editor of your choice to create the Dockerfile. Just ensure that the file is called exactly Dockerfile with a title case and no extension.

To create a simple Docker container image and put it in your local registry, follow these steps:

  1. In your terminal, create a Dockerfile:

    $ vi Dockerfile
    CODE
  2. In the editor, add the following contents to the Dockerfile and then save and close the file. This is instructing the container to use the Alpine Linux distribution and to print a “Hello world” message onto the command line interface.

    FROM alpine
    CMD ["echo", "Hello world"]
    CODE
  3. Build the container image and give it a name indicating your local host registry and the name "demo":

    $ sudo docker build . -t localhost:5000/demo
    CODE
  4. Once built, push the image to your local registry:

    $ sudo docker push localhost:5000/demo
    CODE

You now have a container image in your local registry.

Step 5 - Create container signature payload with Cosign

A Cosign signature payload contains the digest of the container image it is attached to and Cosign implements the Red Hat Simple Signing specification. By generating a payload, signing it with SignServer, and then attaching the signed payload to the container image in the registry, Cosign can later be used to verify that the digest matches the container and thus prove that the signature payload was “attached” by reference to the original container image that was signed.

With the container in your local registry, install and use Cosign to create a payload describing the container image:

  1. Install Cosign. In this example, Arch Linux is used to install Cosign. For other installation options, refer to Sigstore documentation: Installation.

    $ sudo pacman -S cosign
    CODE
  2. Use Cosign to generate a payload describing the container image, and specify the image and a JSON file for the output:

    $ cosign generate localhost:5000/demo > demo-payload.json
    CODE
  3. Output the content of the JSON file to inspect the payload:

    $ cat demo-payload.json
    CODE
  4. The payload includes information about the container image and importantly the image manifest digest hash in the docker-manifest-digest field:

    [{"critical":{"identity":{"docker-reference":"localhost:5000/demo"},"image":{"docker-manifest-digest":"sha256:690ecfd885f008330a66d08be13dc6c115a439e1cc935c04d181d7116e198f9c"},"type":"cosign container image signature"},"optional":null}]
    CODE

    This image digest is used when verifying the signature and then allows proving the relation to the image.

Next, continue to sign the payload using SignServer.

Step 6 - Sign payload with SignServer

There are several options for integrating with SignServer, including the Web interface or CLI tool, see SignServer User Interfaces. The following example uses cURL to integrate with SignServer.

To integrate with SignServer and sign the payload with SignServer, follow these steps:

  • Run the cURL command and specify the following:

    • Name of the worker to do the signature with, such as PlainSigner.

    • File to upload that you want a signature for, such as demo-payload.json.

    • Name of the file to store the signature output in, such as demo-payload.sig.

    • URL to SignServer, such as http://localhost/signserver/process.

      $ curl -F workerName=PlainSigner -F file=@demo-payload.json --output demo-payload.sig http://localhost/signserver/process
      CODE

A signature from SignServer is saved.

Step 7 - Attach signed payload to container with Cosign

Convert the signature to base64 and then use Cosign to attach the signature to the container image in the registry:

  1. To convert the signature from binary to base64, use the base64 utility and specify a name for the file such as demo-payload.sig.b64:

    $ cat demo-payload.sig | base64 > demo-payload.sig.b64
    CODE
  2. To attach the signed payload to the image in the registry using Cosign, specify the initial payload, the signature, and the location in the registry under the container image:

    $ cosign attach signature --payload demo-payload.json --signature demo-payload.sig.b64 localhost:5000/demo
    CODE

You now have a signed container image and as a next step, you can use Cosign to verify that you trust the signature.

Step 8 - Verify signed container image with Cosign

You can now verify that you trust the signature of the signed container image. To verify the signature using Cosign, you need to provide the signer certificate (PlainSigner.pem) and a certificate chain that you trust (in this example ManagementCA.pem).

During verification, Cosign validates that the digest of the signature payload matches the digest of the container image that the signature is attached to.

To verify the signed container image, follow these steps:

  1. Copy the signer certificate (PlainSigner.pem) to the Linux Arch installation and ensure that you also have a trusted CA certificate present.
  2. Verify the signature by passing the signer certificate (PlainSigner.pem) and a trusted certificate chain (ManagementCA.pem) files to the cosign verify command and specify the container image in the registry that you want to verify a signature for:

    cosign verify --cert PlainSigner.pem --cert-chain ManagementCA.pem localhost:5000/demo
    BASH
  3. You should receive an output like the following, indicating that the signature was validated and displaying the output of the payload that was created with Cosign which contains the information that describes the image:

    Verification for localhost:5000/demo/:latest --
    The following checks were performed on each of these signatures:
      - The cosign claims were validated
      - The signatures were verified against the specified public key
    
    [{"critical":{"identity":{"docker-reference":"localhost:5000/demo"},"image":{"docker-manifest-digest":"sha256:690ecfd885f008330a66d08be13dc6c115a439e1cc935c04d181d7116e198f9c"},"type":"cosign container image signature"},"optional":null}]
    BASH

    The output includes the digest of the container image, verifying that these signatures cover the correct image.

Cosign inspects the payload created earlier and ensures that the Docker-manifest-digest value matches the value of the digest of the image. That proves that the signature payload was attached by reference to the original container image that was signed.

Next steps

In this tutorial, you learned how to use SignServer together with Cosign to create signed container images.

For more information on PKI and signature services in DevOps environments, see PKI and Signature Services for Microservices and DevOps.

To find out more about administrating SignServer, see SignServer Administration Web (AdminWeb) or if you are new to SignServer, start with the SignServer Introduction.

To learn how to get started with code signing in SignServer, see the Code Signing Technical How-to.