Signing Docker Images with GPG

Prerequisites

The following prerequisites are required to use the Unbound signing with CORE.

Prepare the Client Environment

The following procedure is needed to prepare the client.

  1. Install skopeo. See skopeo for more information.
  2. sudo yum install skopeo

  3. Add the Docker CE repository.
  4. sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo

  5. Install the Docker CLIClosedCommand Line Interface.
  6. sudo yum install -y docker-ce

  7. Start Docker
  8. sudo service docker start

Signing

This procedure details signing a docker image using GPGClosedGNU Privacy Guard - PGP cryptography implementation and CORE.

  1. List keys in CORE. You will use the key name in the next step.
  2. ucl list

  3. Load key to GPGClosedGNU Privacy Guard - PGP cryptography implementation keyring.
  4. ucl pgp-key -p <PARTITION> -n <KEYNAME>

  5. Export the public key.
  6. gpg2 --armor --output public.gpg --export <KEYNAME>

  7. (Optional) The certificate can be used to verify the origin of the public key. Export the certificate:
  8. ucl export -p <PARTITION NAME> -w <PASSWORD> -u <UID OF THE CERTIFICATE> -o certificate.pem

  9. Edit /etc/containers/registries.d/default.yaml.
  10. # This is a default registries.d configuration file. You may
    # add to this file or create additional files in registries.d/.
    #
    # sigstore: indicates a location that is read and write
    # sigstore-staging: indicates a location that is only for write
    #
    # sigstore and sigstore-staging take a value of the following:
    # sigstore: {schema}://location
    #
    # For reading signatures, schema may be http, https, or file.
    # For writing signatures, schema may only be file.
    # This is the default signature write location for docker registries.
    default-docker:
    # sigstore: file:///var/lib/atomic/sigstore
    sigstore-staging: file:///var/lib/atomic/sigstore

    # The 'docker' indicator here is the start of the configuration
    # for docker registries.
    #
    # docker:
    #
    # privateregistry.com:
    # sigstore: http://privateregistry.com/sigstore/
    # sigstore-staging: /mnt/nfs/privateregistry/sigstore

  11. Copy an image.
  12. skopeo copy docker://busybox:latest dir:busybox

  13. Sign and push the image.
  14. skopeo copy dir:busybox docker://<USER_NAME>/busybox:tag --dest-creds <USER_NAME>:<USER_PASS> --sign-by <KEYNAME>

  15. Compress the folder with the signature created in the previous step.
    For example:
  16. sudo tar -zcvf signature.tar /var/lib/atomic/sigstore/<USER_NAME>/busybox@sha256\=afe605d272837ce1732f390966166c2afff5391208ddd57de10942748694049d/

  17. Send public.gpg and certificate to the verifying machine. You only need to do this step once.
  18. Send the compressed folder with the signature to the verifying machine. You need to send a new signature for every release, and you may want to use a signature repository.

To verify the signature:

  1. Copy the public key and sigstore to the same place.
  2. Edit /etc/containers/registries.d/default.yaml.
  3. # This is a default registries.d configuration file. You may
    # add to this file or create additional files in registries.d/.
    #
    # sigstore: indicates a location that is read and write
    # sigstore-staging: indicates a location that is only for write
    #
    # sigstore and sigstore-staging take a value of the following:
    # sigstore: {schema}://location
    #
    # For reading signatures, schema may be http, https, or file.
    # For writing signatures, schema may only be file.
    # This is the default signature write location for docker registries.
    default-docker:
    sigstore: file:///var/lib/atomic/sigstore

    # sigstore-staging: file:///var/lib/atomic/sigstore
    # The 'docker' indicator here is the start of the configuration
    # for docker registries.
    #
    # docker:
    #
    # privateregistry.com:
    # sigstore: http://privateregistry.com/sigstore/
    # sigstore-staging: /mnt/nfs/privateregistry/sigstore

  4. Uncompress the signature folder that was copied to the verifying machine.
    For example, using the path specified in the previous step:

    sudo tar -zvxf signature.tar -C /var/lib/atomic/sigstore/<USER_NAME>/

  5. Edit /etc/containers/policy.json.
  6. {
      "default":[
        {
          "type":"reject"
        }
      ],
      "transports":{
        "docker":{
          "docker.io/<USER_NAME>":[
            {
              "type":"signedBy",
              "keyType":"GPGKeys",
              "keyPath":"/<path-to-public-key>/public.gpg"
            }
          ]
        }
      }
    }

  7. Pull the image.
  8. sudo skopeo copy docker://<USER_NAME>/busybox:tag dir:busybox

    Example success message:

    Getting image source signatures
    Checking if image destination supports signatures
    Copying blob 0669b0daf1fb done
    Copying config 83aa35aa1c done
    Writing manifest to image destination
    Storing signatures

    Example failure message:

    FATA[0001] Source image rejected: A signature was required, but no signature exists

  9. (Optional) Certificate verification, if you exported the certificate above.
    1. You can validate with the certificate authority directly using this command:
    2. openssl x509 -inform pem -in certificate.pem -noout -text

      This proves to the verifier that it was signed by the signer.

    3. Check if the certificate is active using this command. Note that DigiCert is used as an example.
    4. openssl ocsp -no_nonce -issuer <OSCPCHAINPUBLICKEYNAME> -cert certificate.pem -VAfile <OSCPCHAINPUBLICKEYNAME> -text -url http://ocsp.digicert.com -respout ocsptest

      This shows that the certificate is active.

      If the certificate is valid, the output is:

      Response verify OK

    5. OpenSSL shows the content of X509 certificate using the command:
    6. openssl x509 -text -in <X509-certificate-file>

    7. GPGClosedGNU Privacy Guard - PGP cryptography implementation shows the content of the public key file using the command:
    8. gpg2 -v --list-packets public.gpg

    9. Check the details of the public key and see that the same modulus and public exponent exists in both files, i.e. this is the same key/certificate.
    10. Note
      It is recommended to verify that the certificate is valid before sharing it with anyone who will be verifying images.