Securing Your Supply Chain: A Guide to Signing and Verifying Blobs

Securing Your Supply Chain: A Guide to Signing and Verifying Blobs

Signing artifacts should be a crucial part of our workflow. As a part of Supply Chain Security, we must ensure that the artifact built on our CI platform is the artifact we are deploying on our production environments.

If by any means, we cannot verify that the artifact built is not the artifact we are going to deploy then it’s a RED flag and we might be likely compromised. In this walkthrough, we are going to look at how we can use components of Project Sigstore: Cosign, Flucio, and Rekor - to sign and verify our artifacts.

This guide is mostly focused on Binary Large Objects (Blob) and does not cover workflows related to Containers.

Primer 🐳

We produce different types of artifacts on our build system, which can range from container images, zip files, log records, SBOMs and many more depending on the use case. These artifacts can be business essential and when tampered with will disrupt our businesses.

The process of verifying the artifacts that happen in 3 main high-level steps:

  • Signing the artifact ✍️

  • Storing the artifact and signature 📦

  • Verifying the artifact ✅

Signing Artifacts ✍️

Sigstore offers us two ways of signing any artifact:

  1. using a public-private key (asymmetric encryption)

  2. using keyless mode (recommended) ✨

Using Public-Private Keys

This mechanism is something we have been using for ages. We sign our data using Private Key and verify the signature using the Public Key.

With cosign we can generate public-private key pair secured with a password.

Download cosign

cosign generate-key-pair

This will generate a public key: cosign.pub and a private key cosign.key.

Now using this private key, let’s sign our artifact. For this demo, I have created a zip file called artifact.zip where I have kept an arbitrary README file. You can do the same with a zip file or any file of your preference.

🧠
When we say signing the artifact, we are actually signing the hash (SHA-256) of the artifact. This is much more efficient when working with large artifacts.
cosign sign-blob --key cosign.key

# OR
# we can also store our public key and signature as a bundle using: 
cosign sign-blob --key cosign.key --bundle cosign.bundle

What just happened here?

We just finished signing our artifact but a few things have happened in the background.

  • Cosign signed generated a SHA256 hash of our artifact and created a signature by signing it

  • then it uploaded the public key and the signature to the transparency log Rekor and returned its index

  • Rekor is a public infrastructure that logs the public key and signature of all the data signed using cosign.


Understanding the log record from Rekor 🧐

We get the index of the log entry on the Rekor after signing the artifact which is provided as: tlog entry created with index. We can view the Rekor log for our artifact using the rekor-cli.

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

Let's look at this result in depth (numbers co-relate to the image above ):

  1. The SHA256 sum of our artifact.zip file, we can verify this using:

     shasum -a 256 artifact.zip
    

  2. Inside signature > content we have the same signature generated after signing the SHA of our artifact.

     rekor-cli get --log-index <log-index> --format json | jq -r .Body.HashedRekordObj.data.hash.value
    
  3. Our public key that was generated when we ran cosign generate-key-pair. It is base64 encoded right now, if we decode it, we’ll get the contents of our public key aka cosign.pub

     rekor-cli get --log-index <rekor-log-id> --format json | jq -r .Body.HashedRekordObj.signature.publicKey.content | base64 -D
    

Verifying the artifact ✅

We’ll need the signature to verify our artifact. We can get it from the Rekor log and store it on a file called cosign.sig.

rekor-cli get --log-index <rekor-log-id> --format json | jq -r .Body.HashedRekordObj.signature.content > cosign.sig

Now let’s verify our artifact! ⚡

We will be using our public key and signature for this.

cosign verify-blob --key cosign.pub artifact.zip --signature cosign.sig  artifact.zip

# If you generated cosign.bundle when signing the artifact, you can use that bundle to verify the artifact

cosign-verify-blob --bundle cosign.bundle artifact.zip

We’ll get the message of “Verified OK”. If our public key didn’t match or the signature didn’t match, then we’ll get an error.

Congratulations, we have successfully signed an artifact and verified it 🎉.

In real world scenario, we’ll be signing the artifact and storing it somewhere, and at the time of dispatching/deployment/usage, we’ll make sure it is verified and only then make the intended use of it. This ensures that our artifact has not been tampered and it’s safe for our workflow/workload.

Keyless Mode ✨

Keyless mode is a convenient feature that hugely reduces the friction in artifact signing. When we use Public-Private keys, there’s this hassle of managing the keys, and keeping them safe. With keyless mode, we don’t have the overhead of managing any Private keys at all. It is all managed for us!

But the catch of using keyless mode is, it requires OIDC (Open ID Connect) to associate our identity to the X.509 certificate it generates; this is to attest the signer of the artifact which we can later verify. And the certificate is pushed to a public append-only log Rekor, where our identity will be available. Identity can be an email or pipeline ID which we and our team can verify is authentic and trust. For OIDC, it currently supports:

  • Google

  • Microsoft

  • GitHub

Preparing Artifact 🔨

Let’s create a new zip from the existing zip and call it artifact-keyless.zip (or choose any file to your liking). For this demo, SHA is different for both of the zips created.

zip -r artifact-keyless.zip artifact.zip

shasum -a 256 artifact.zip | awk '{print $1}'

shasum -a 256 artifact-keyless.zip | awk '{print $1}'

Sign the artifact

To sign in the keyless mode we just have to run the following command:

cosign sign-blob artifact-keyless.zip

# OR 
# sign-blob and generate a bundle containing X.509 ceritifcate and signature. 
cosign sign-blob artifact-keyless.zip --bundle cosign.bundle

Press y on the prompt. Then a browser tab will open for the OIDC verification. This is the identity verification step for the Certificate Authority (CA) before creating our digital certificate.

We need to select one of the OIDC providers, and once the authentication is successful, we’ll see a similar message as shown below.

You can notice that it has generated an ephemeral certificate which is only valid for 10 mins.

What just happened here?

  • Cosign generated a private key in memory and then a public key out of that. The private key never touches the physical storage.

  • It generated the SHA of our artifact and signed it generating a signature.

  • It then communicated with the Certificate Authority (CA) Flucio to bind its identity with the public key on an X.509 certificate.

  • For identity proofing, we were prompted for OIDC.

  • Once we verify our identity, it receives the OIDC token.

  • It took the public key, signature and OIDC token to the Fulcio Certificate Authority.

  • the CA created an X.509 certificate which binds our public key with our identity, along with the signature of the signed artifact.

  • CA signed that certificate with its Private Key and pushed the certificate details to the Rekor Transparency log.

See a lot of things happened in the background, but when we were using it; it was frictionless! It is one of the benefits of keyless signing with cosign. 🤩

Like before, on the output we have received the index of our Rekor log. Let’s take that log index and see the details of our artifact.

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

The output is of the same format as before. But we should notice that the size of publicKey > content is bigger than before. In the private-public key mode, we were using our public key and its size was small, but now it holds the X.509 certificate generated by Flucio.

Let’s see what our certificate looks like; the content is Base64 encoded, we should first decode it and save it to a file called artifact-keyless.crt:

rekor-cli get --log-index <rekor-log-index> --format json | jq -r .Body.HashedRekordObj.signature.publicKey.content | base64 -D > artifact-keyless.crt

Now, let’s use a tool called step to view our certificate. You can download it here.

step certificate inspect artifact-keyless.crt --format json

It will generate a huge JSON, if you search for your email address you can find it in the subject_alt_name section.

step certificate inspect artifact-keyless.crt --format json  | jq .extensions.subject_alt_name

Chain of Trust ⛓️

Now we have the X.509 certificate, how do we make sure that the Fulcio Certificate Authority has issued it?

Well, our X.509 certificate has been signed with the private key from Fulcio and we can use its public key to verify them. The Root Certificate and Intermediate Certificate for Fulcio is available here. You can download them and save them to a file called chain.crt or any name you prefer.

You can also find a copy of it here, but it's best if you download it from the official sources 😉

  •     -----BEGIN CERTIFICATE-----
        MIICGjCCAaGgAwIBAgIUALnViVfnU0brJasmRkHrn/UnfaQwCgYIKoZIzj0EAwMw
        KjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0y
        MjA0MTMyMDA2MTVaFw0zMTEwMDUxMzU2NThaMDcxFTATBgNVBAoTDHNpZ3N0b3Jl
        LmRldjEeMBwGA1UEAxMVc2lnc3RvcmUtaW50ZXJtZWRpYXRlMHYwEAYHKoZIzj0C
        AQYFK4EEACIDYgAE8RVS/ysH+NOvuDZyPIZtilgUF9NlarYpAd9HP1vBBH1U5CV7
        7LSS7s0ZiH4nE7Hv7ptS6LvvR/STk798LVgMzLlJ4HeIfF3tHSaexLcYpSASr1kS
        0N/RgBJz/9jWCiXno3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYB
        BQUHAwMwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU39Ppz1YkEZb5qNjp
        KFWixi4YZD8wHwYDVR0jBBgwFoAUWMAeX5FFpWapesyQoZMi0CrFxfowCgYIKoZI
        zj0EAwMDZwAwZAIwPCsQK4DYiZYDPIaDi5HFKnfxXx6ASSVmERfsynYBiX2X6SJR
        nZU84/9DZdnFvvxmAjBOt6QpBlc4J/0DxvkTCqpclvziL6BCCPnjdlIB3Pu3BxsP
        mygUY7Ii2zbdCdliiow=
        -----END CERTIFICATE-----
        -----BEGIN CERTIFICATE-----
        MIIB9zCCAXygAwIBAgIUALZNAPFdxHPwjeDloDwyYChAO/4wCgYIKoZIzj0EAwMw
        KjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0y
        MTEwMDcxMzU2NTlaFw0zMTEwMDUxMzU2NThaMCoxFTATBgNVBAoTDHNpZ3N0b3Jl
        LmRldjERMA8GA1UEAxMIc2lnc3RvcmUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAT7
        XeFT4rb3PQGwS4IajtLk3/OlnpgangaBclYpsYBr5i+4ynB07ceb3LP0OIOZdxex
        X69c5iVuyJRQ+Hz05yi+UF3uBWAlHpiS5sh0+H2GHE7SXrk1EC5m1Tr19L9gg92j
        YzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRY
        wB5fkUWlZql6zJChkyLQKsXF+jAfBgNVHSMEGDAWgBRYwB5fkUWlZql6zJChkyLQ
        KsXF+jAKBggqhkjOPQQDAwNpADBmAjEAj1nHeXZp+13NWBNa+EDsDP8G1WWg1tCM
        WP/WHPqpaVo0jhsweNFZgSs0eE7wYI4qAjEA2WB9ot98sIkoF3vZYdd3/VtWB5b9
        TNMea7Ix/stJ5TfcLLeABLE4BNJOsQ4vnBHJ
        -----END CERTIFICATE-----
    

Let’s verify our X.509 certificate.

If we are validating the certificate within 10 mins of signing then we can use the following command:

openssl verify -CAfile chain.crt artifact-keyless.crt

If we are trying to validate the certificate, beyond 10 mins then we should use the following command, to skip checking time in order to validate the overall certificate:

openssl verify  -no_check_time -CAfile chain.crt artifact-keyless.crt

Verify the artifact

Now that we have verified that the certificate is signed by the root CA. Let’s move into verifying our artifact. For this, we will also require the signature of our artifact. We can grab it from the signature > content section from the output of the rekor cli. And we can store it in a file called artifact-keyless.sig

rekor-cli get --log-index <index-number> --format json | jq -r .Body.HashedRekordObj.signature.content > artifact-keyless.sig

With that in place, let’s use the following command where we pass the certificate, signature, certificate identity and certificate OIDC issuer to verify our artifact.

cosign verify-blob artifact-keyless.zip \
 --certificate artifact-keyless.crt \
 --signature artifact-keyless.sig \
 --certificate-identity=<your-email-address> \
 --certificate-oidc-issuer=https://accounts.google.com

# OR 
# If you have generated the cosign.bundle when signing the artifact, you can you that bundle to replace certificate and signature, as the bundle contains both of these.

cosign verify-blob artifact-keyless.zip \
 --bundle cosign.bundle \
 --certificate-identity=<your-email-address> \
 --certificate-oidc-issuer=https://accounts.google.com

If we have got all the things right then we should get the “Verified OK” message, else we’ll get an error.

Conclusion

Artifact signing and verification have become one of crucial steps in securing our infrastructure against supply chain attacks. Project Sigstore has provided us with the tools and infrastructure to do this with minimal effort. If you are not signing your artifacts then it’s high time you start doing so!