# 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](https://docs.sigstore.dev/cosign/installation/) ✨

```bash
cosign generate-key-pair
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1688790875059/21b761e9-e08b-4a17-bed1-842991381dd2.png align="center")

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.

<div data-node-type="callout">
<div data-node-type="callout-emoji">🧠</div>
<div data-node-type="callout-text">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.</div>
</div>

```bash
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
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1688791101013/813ba440-e850-4f6c-95e4-734740109088.png align="center")

### **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](https://docs.sigstore.dev/rekor/overview/#usage-and-installation).

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

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1688791456277/86e68c35-0392-492c-a91e-365c7f07bf0e.png align="center")

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:
    
    ```bash
    shasum -a 256 artifact.zip
    ```
    
    ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1688791490968/980ee99a-ddae-472a-a524-1b01d54ef3fb.png align="center")
    
2. Inside `signature > content` we have the same signature generated after signing the SHA of our artifact.
    
    ```bash
    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`
    
    ```bash
    rekor-cli get --log-index <rekor-log-id> --format json | jq -r .Body.HashedRekordObj.signature.publicKey.content | base64 -D
    ```
    
    ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1688791653137/f6a34176-ae16-461b-bc54-08819465fd2c.png align="center")
    

### **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`.

```bash
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.

```bash
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
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1688791711532/f15d58ad-4d33-468e-ab8f-fb3926bdfcac.png align="center")

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.

```bash
zip -r artifact-keyless.zip artifact.zip

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

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

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1688791857986/03d28b95-ff19-43c7-b6f2-775ed7f88c51.png align="center")

### **Sign the artifact**

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

```bash
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
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1688791920515/c04501f3-3cda-4b1c-ae2d-6558c76896c6.png align="center")

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.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1688791949333/2f79b392-d870-4f9a-bb2d-4eb55b154ba1.png align="center")

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

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1688791979114/a691917a-69aa-41bc-8455-3086daaed406.png align="center")

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

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1688792009844/1d7e1726-dcd1-48cc-b1b8-d53ca792ffe3.png align="center")

### **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.

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

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1688792104021/8afbd167-18f1-427b-94d1-95183f125c7c.png align="center")

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`:

```bash
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](https://smallstep.com/docs/step-cli/installation/).

```bash
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.

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

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1688792168718/e97c51bd-c366-4ab2-98ea-389aa4254fe1.png align="center")

### **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](https://fulcio.sigstore.dev/api/v2/trustBundle). 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 😉

* ```bash
      -----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:

```bash
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:

```bash
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`

```bash
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.

```bash
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.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1688792366281/5823ee35-61fa-4469-91be-93d6d1f45cd5.png align="center")

## 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!
