Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(signature): Checksum signature verification #1670

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

hibare
Copy link
Contributor

@hibare hibare commented Jan 21, 2024

Addresses issue #1627

This PR adds checksum.txt file signature verification before downloading and installing actual binary. This is an optional opt-in feature using command line flag -v to installation script.

Signature verification process depends on 3rd party cosign binary. If the binary is not found, the user is prompted to install the binary. Cosign binary installation is not part of this script.

The overall process with signature verification looks like this:

  1. User supplies -v command line flag to installation script.
  2. Script checks for cosign binary, terminates execution with a message if not found.
  3. Script downloads checksum.txt, checksum.txt.sig and checksum.txt.pem files.
  4. Script runs checksum.txt file signature verification.
  5. On successful verification, the script continues to download the actual binary, run checksum verification and install it.

Successful signature verification:

❯ ./install.sh -b /tmp -d -v
[info] checking github for the current release tag 
[debug] http_download(url=https://github.com/anchore/grype/releases/latest) 
[info] using release tag='v0.74.2' version='0.74.2' os='linux' arch='amd64' 
[debug] downloading files into /tmp/tmp.daUQPcfBjV 
[debug] http_download(url=https://github.com/anchore/grype/releases/download/v0.74.2/grype_0.74.2_checksums.txt) 
[debug] http_download(url=https://github.com/anchore/grype/releases/download/v0.74.2/grype_0.74.2_checksums.txt.sig) 
[info] downloaded checksums signature file: /tmp/tmp.daUQPcfBjV/grype_0.74.2_checksums.txt.sig 
[debug] http_download(url=https://github.com/anchore/grype/releases/download/v0.74.2/grype_0.74.2_checksums.txt.pem) 
[info] downloaded checksums certificate file: /tmp/tmp.daUQPcfBjV/grype_0.74.2_checksums.txt.pem 
Verified OK
[info] checksum signature verification succeeded 
[debug] http_download(url=https://github.com/anchore/grype/releases/download/v0.74.2/grype_0.74.2_linux_amd64.tar.gz) 
[info] installed /tmp/grype 

Signature verification failure:

❯ ./install.sh -b /tmp -d -v
[info] checking github for the current release tag 
[debug] http_download(url=https://github.com/anchore/grype/releases/latest) 
[info] using release tag='v0.74.2' version='0.74.2' os='linux' arch='amd64' 
[debug] downloading files into /tmp/tmp.DxdVQpUMaP 
[debug] http_download(url=https://github.com/anchore/grype/releases/download/v0.74.2/grype_0.74.2_checksums.txt) 
[debug] http_download(url=https://github.com/anchore/grype/releases/download/v0.74.2/grype_0.74.2_checksums.txt.sig) 
[info] downloaded checksums signature file: /tmp/tmp.DxdVQpUMaP/grype_0.74.2_checksums.txt.sig 
[debug] http_download(url=https://github.com/anchore/grype/releases/download/v0.74.2/grype_0.74.2_checksums.txt.pem) 
[info] downloaded checksums certificate file: /tmp/tmp.DxdVQpUMaP/grype_0.74.2_checksums.txt.pem 
Error: none of the expected identities matched what was in the certificate, got subjects [https://github.com/anchore/grype/.github/workflows/release.yaml@refs/heads/main] with issuer https://token.actions.githubusercontent.com
main.go:74: error during command execution: none of the expected identities matched what was in the certificate, got subjects [https://github.com/anchore/grype/.github/workflows/release.yaml@refs/heads/main] with issuer https://token.actions.githubusercontent.com
[error] checksum signature verification failed 
[error] could not find release asset for os='linux' arch='amd64' format='tar.gz'  
[error] failed to install grype

Cosign binary not found:

❯ ./install.sh -b /tmp -d -v
[info] checking github for the current release tag 
[debug] http_download(url=https://github.com/anchore/grype/releases/latest) 
[info] using release tag='v0.74.2' version='0.74.2' os='linux' arch='amd64' 
[debug] downloading files into /tmp/tmp.4VDtYTtI6R 
[debug] http_download(url=https://github.com/anchore/grype/releases/download/v0.74.2/grype_0.74.2_checksums.txt) 
[error] Signature verification is requested but cosign binary is not installed. Follow steps from https://docs.sigstore.dev/system_config/installation/ to install it. 
[error] could not find release asset for os='linux' arch='amd64' format='tar.gz'  
[error] failed to install grype

Signed-off-by: Shubham Hibare <shubham@hibare.in>
Signed-off-by: Shubham Hibare <shubham@hibare.in>
install.sh Outdated Show resolved Hide resolved
README.md Show resolved Hide resolved
@@ -616,7 +679,7 @@ main() (
install_dir=${install_dir:-./bin}

# note: never change the program flags or arguments (this must always be backwards compatible)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: never change the program flags or arguments (this must always be backwards compatible)

A note for reviewers/testers: we need to verify that this would function when installing a previous release. Ideally we'd know the specific release that implemented this and be able to short circuit this and error when validation would silently never occur.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Note: currently unknown options print usage but are ignored, so old releases would download without checking anything. If you want older releases to fail, we'll need to compare the version given here with the first release that supports this as you suggest)

Copy link
Contributor

@wagoodman wagoodman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an awesome add! I need to think through and run some testing to ensure installing previous releases is ok https://github.com/anchore/grype/pull/1670/files#r1467020629

Signed-off-by: Shubham Hibare <shubham@hibare.in>
@hibare
Copy link
Contributor Author

hibare commented Feb 11, 2024

@wagoodman Any update? As you mentioned, definitely need a check to test which version this feature was rolled out and show error when -v option is used, but script is before the rolled out version.

Copy link

@martinetd martinetd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this work!

return 1
fi

checksum_sig_file_path=$(download_github_release_checksums_sig "${download_url}" "${name}" "${version}" "${destination}")

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a problem per se, but cosign can be passed an url directly, is there any benefit to downloading the cert/signatures?

e.g. this worked (for syft but it's similar)

cosign verify-blob syft_1.4.1_checksums.txt \
    --certificate https://github.com/anchore/syft/releases/download/v1.4.1/syft_1.4.1_checksums.txt.pem \
    --signature https://github.com/anchore/syft/releases/download/v1.4.1/syft_1.4.1_checksums.txt.sig   \
    --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
    --certificate-identity 'https://github.com/anchore/syft/.github/workflows/release.yaml@refs/heads/main'

${COSIGN_BINARY} verify-blob "$1" \
--certificate "$2" \
--signature "$3" \
--certificate-identity-regexp "https://github\.com/${OWNER}/${REPO}/\.github/workflows/.+" \

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We probably should be more strict than the regex for this, otherwise I assume I could open a PR that adds a new workflow that'll sign something bad™ -- that'll leave traces but it'll still be usable for old releases.

Since the path currently doesn't change, we can just use it as is:

--certificate-identity "https://github.com/${OWNER}/${REPO}/.github/workflows/release.yaml@refs/heads/main"

Ideally the build script should be updated to build releases by tag (refs/tags/v1.2.3) instead of branch, at which point we'll be able to say @refs/tags/$version

checksum_sig_file_path=$(download_github_release_checksums_sig "${download_url}" "${name}" "${version}" "${destination}")
log_info "downloaded checksums signature file: ${checksum_sig_file_path}"

checksums_cert_file_path=$(download_github_release_checksums_cert "${download_url}" "${name}" "${version}" "${destination}")

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's more of a cosign question than a question about this script, but if we download the signing certificate what prevents an attacker to provide another checksums file/certificate/signature set?
I'd assume it's the github oidc feature, because the authority cosign has built in wouldn't issue that tokens.actions.githubusercontent.com issuer intermediate certificate to anyone else, and github wouldn't give this certificate identity to another workflow, but given the tool didn't make any network request I don't like the fact that there doesn't seem to be a CRL, so if a bug allows malicious users to abuse the github token they'll be able to provide their own set of files? Meaning I'd be much more comfortable pinning a fingerprint we could just update if it leaks...

But now I'm looking it looks like each release generates a different certificate every time, so that wouldn't work. I guess I'll have to learn to trust these tools...

@@ -616,7 +679,7 @@ main() (
install_dir=${install_dir:-./bin}

# note: never change the program flags or arguments (this must always be backwards compatible)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Note: currently unknown options print usage but are ignored, so old releases would download without checking anything. If you want older releases to fail, we'll need to compare the version given here with the first release that supports this as you suggest)

curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin -v
```
> ![NOTE]
> This requires `cosign` to be installed.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might want to link to https://github.com/sigstore/cosign as many distros don't have a package for it (yet?)

@wagoodman wagoodman self-assigned this May 24, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: Stalled
Development

Successfully merging this pull request may close these issues.

None yet

3 participants