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

Don't fail open in VerifyBundle #1648

Merged
merged 1 commit into from Apr 26, 2022
Merged

Conversation

mtrmac
Copy link
Contributor

@mtrmac mtrmac commented Mar 22, 2022

Summary

Remove a “fail-open” code path in VerifyBundle

Ticket Link

Release Note

NONE


This code path succeeding and bypassing all future checks worries me greatly, and I can't find any documentation nor explanation for why that is necessary, so let's close this avenue and see what breaks.

@codecov-commenter
Copy link

codecov-commenter commented Mar 22, 2022

Codecov Report

Merging #1648 (37b2c58) into main (8cac645) will increase coverage by 0.57%.
The diff coverage is 50.00%.

@@            Coverage Diff             @@
##             main    #1648      +/-   ##
==========================================
+ Coverage   32.16%   32.73%   +0.57%     
==========================================
  Files         146      147       +1     
  Lines        9208     9301      +93     
==========================================
+ Hits         2962     3045      +83     
- Misses       5898     5902       +4     
- Partials      348      354       +6     
Impacted Files Coverage Δ
pkg/cosign/verify.go 29.89% <50.00%> (+0.41%) ⬆️
cmd/cosign/cli/sign/sign.go 14.36% <0.00%> (-0.34%) ⬇️
cmd/cosign/cli/sign.go 0.00% <0.00%> (ø)
cmd/cosign/cli/attest.go 0.00% <0.00%> (ø)
cmd/cosign/cli/verify.go 0.00% <0.00%> (ø)
cmd/cosign/cli/sign/sign_blob.go 0.00% <0.00%> (ø)
cmd/cosign/cli/verify/verify_blob.go 10.42% <0.00%> (ø)
pkg/blob/load.go 74.28% <0.00%> (ø)
pkg/signature/keys.go 20.98% <0.00%> (+1.49%) ⬆️
... and 2 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 8cac645...37b2c58. Read the comment docs.

@dlorenc
Copy link
Member

dlorenc commented Mar 22, 2022

cc @priyawadhwa

@@ -614,7 +614,7 @@ func VerifyBundle(ctx context.Context, sig oci.Signature) (bool, error) {
if err != nil {
return false, err
} else if cert == nil {
return true, nil
return false, errors.New("signature does not include a certificate")
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this codepath was for the case when a user signs with a key (instead of the "keyless" signing flow) and uploads to the tlog, in which case there would be no cert.

e.g. something like

COSIGN_EXPERIMENTAL=1 cosign sign --key cosign.key MYIMAGE

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks. Why would it be acceptable to skip the bundlehash != payloadHash check in that case? (Or a different way to ask this: if it is not necessary to check the payload hash for signatures with --key, why is it checked in the other cases?)

Copy link
Contributor

Choose a reason for hiding this comment

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

good catch, we should do the bundleHash != payloadHash check before returning.

Copy link
Contributor

Choose a reason for hiding this comment

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

Drive-by opinion: I think there's a lot of harm that comes from maintaining one codepath with a lot of branches based on keyless vs. keyfull signing flow and that splitting this into two functions with some "duplicate" code will make it much easier to reason about.

Copy link
Member

Choose a reason for hiding this comment

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

+1 in general but I think it's actually slightly more complicated than that: there are actually 3 modes, because the transparency log inclusion is separate:

  • keyless, (always tlog)
  • keyed (tlog)
  • keyed (no tlog)

the case where a keyed signature is in the tlog is the one that's always easiest to forget.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think this codepath was for the case when a user signs with a key (instead of the "keyless" signing flow) and uploads to the tlog

Updated to handle that, now.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Drive-by opinion: I think there's a lot of harm that comes from maintaining one codepath with a lot of branches based on keyless vs. keyfull signing flow and that splitting this into two functions with some "duplicate" code will make it much easier to reason about.

(Another drive-by opinion: I’m not so worried the code having branches than about having a clear and constrained state of knowledge, something that could be expressed as simple assertions.

E.g here, the certificate’s acceptability only validated at this point, way after VerifyImageSignature has already extracted the public key and used it to verify a signature. When the certificate’s acceptability is not yet established, the signature’s verification status is indeterminate until that happens.

I’d love to see something like

func verifySignature(configuredRootOfTrust …, untrustedRekorBundle, untrustedCertificate, untrustedSignature, untrustedPayload []byte) acceptedPayloadData {
    var acceptablePublicKey
    switch configuredRootOfTrust.signerMode {
    case .fulcioCertificate:
        correctlySignedCertificate, err = validateCertificateSignature(configuredRootOfTrust.fulcioCACertificates, untrustedCertificate)
        if err { fail }
        acceptableCertificate, err = verifyCertificateDataConstraints(configuredRootOfTrust.fulcioAcceptedIdentities, correctlySignedCertificate)
        if err { fail }
        acceptablePublicKey = acceptableCertificate.PublicKey
    case .rawPublicKey:
        acceptablePublicKey = configuredRootOfTrust.trustedPublicKey
    }

    correctlySignedPayloadBlob, err = verifySignature(acceptablePublicKey, untrustedSignature, untrustedPayload)
    if err { fail }
    if configuredRootOfTrust.requireRekorPresence {
        acceptableRekorBundle, err = verifyRekorBundleIsAcceptable(configuredRootOfTrust.rekorTrustedCertificates, untrustedRekorBundle)
        if err { fail }
        err = verifyPayloadIsConfirmedByRekorBundle(acceptableRekorBundle, correctlySignedPayload)
        if err { fail }
        decodablePayloadBlob = correctlySignedPayloadBlob
    } else {
        decodablePayloadBlob = correctlySignedPayloadBlob
    }
    acceptablePayload, err = parseAndValidatePayload(configuredRootOfTrust.payloadClaimVerifier, decodablePayloadBlob)
    if err { fail }
    return acceptablePayload.ParsedData
}

where every piece of data either comes from configuredRootOfTrust, or it starts as untrusted…, and is only ever used for any purpose after it becomes acceptable….

I feel that this would greatly simplify reasoning about the code, and possible states of the world — every piece of data is only “interesting” on the paths where it turns from untrusted… into acceptable…, and those should be linear and fairly easy to follow, perhaps even with various branches.

I hope to contribute work towards that direction.)

@mtrmac mtrmac force-pushed the cert-fail-open branch 2 times, most recently from a27ad0f to 1378dbb Compare April 22, 2022 18:34
We do need to accept a missing certificate here (to accept
raw signatures which are uploaded in a transparency log),
but that's not a reason to bypass all other checks in this function.

Signed-off-by: Miloslav Trmač <mitr@redhat.com>
@dlorenc dlorenc merged commit d104fc4 into sigstore:main Apr 26, 2022
@github-actions github-actions bot added this to the v1.8.0 milestone Apr 26, 2022
@mtrmac mtrmac deleted the cert-fail-open branch April 26, 2022 18:34
mlieberman85 pushed a commit to mlieberman85/cosign that referenced this pull request May 6, 2022
We do need to accept a missing certificate here (to accept
raw signatures which are uploaded in a transparency log),
but that's not a reason to bypass all other checks in this function.

Signed-off-by: Miloslav Trmač <mitr@redhat.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants