-
Notifications
You must be signed in to change notification settings - Fork 26
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
Make unsafe uses of Verify
function explicit / make Verify
options safe by default
#3
Conversation
Introduced VerifyWithArtifact and VerifyWithArtifactDigest to make it explicit what the preferred usage path is; users should not use VerifyUnsafe unless they are very very careful and know what they are doing.
pkg/verify/signed_entity.go
Outdated
func (v *SignedEntityVerifier) VerifyWithArtifact(entity SignedEntity, artifact io.Reader, identities ...CertificateIdentity) (*VerificationResult, error) { | ||
policies := []PolicyOptionConfigurator{WithArtifact(artifact)} | ||
|
||
for _, certID := range identities { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't we need to fail if no identities are passed? Or will verify unsafe fail if the set of identities is empty/nil?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch! No harm in enforcing it in both spots, actually.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's confusing to have so many ways of specifying CertificateIdentity
. Should the user call verify.WithCertificateIdentity
, or pass it in to Verify*
(or both?)
Ideally, there would be one way for the user to specify this information.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, there should probably only be one way, for security products the API complexity should be minimal. Would it make sense to open an issue or GDoc for a discussion of the API for sigstore-go? Having an orthogonal API is crucial IMO.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, so another idea @phillmv and I discussed 1-1: instead of having Verify*
functions, go back to having one Verify
function, but have it take arguments like insecureWithoutArtifactIsOkay
or insecureWithoutCertificateIdentityIsOkay
. Then Verify
can look at the policy options, and if the the policy option is missing it can check if the corresponding insecure*
option was passed.
That way there's one way to specify the certificate identity, and you still support broad use cases, and you're still requiring the developer type insecure
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, and, what if they're just a positional ArtifactPolicyConfigurator
/ IdentityPolicyConfigurator
, so we don't have to change any of the existing logic / add new interfaces?
i.e.
type ArtifactPolicyConfigurator func(*PolicyOptions) error
and
type IdentityPolicyConfigurator func(*PolicyOptions) error
and the existing logic stays the same.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hrm maybe it'd be like,
Verify(entity, ArtifactPolicyConfigurator, IdentityPolicyConfigurator…)
so you can be like,
Verify(entity, WithArtifact(), WithCertIdentity(), WithCertIdentity()…)
The only thing it forecloses is the ability to add new policy enforcement options in the future.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we care for extensibility, for not have an options struct? Very simple to work with, and can easily extend the struct in the future without braking the API (i.e parameters added/removed). That is if course as long as the struct can be created with sensible defaults. I'm personally not a fan of the functional config pattern, even though I know it's popular in the Go world.
But @phillmv 's latest suggestion
Verify(entity, WithArtifact(), WithCertIdentity(), WithCertIdentity()…)
Looks good I think.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Had a little chat with @kommendorkapten, and maybe an options struct is the way. Something like:
id := NewShortCertificateIdentity("foo", "", "", "")
policy := NewPolicy(WithArtifact(foo), WithId(id), WithId(id2))
Verify(entity, trustMaterial, policy)
This would preserve the option of extending policy in the future, for example, maybe adding the ability to enforce a predicate type?
Barring any objections, I think I'll try whipping this up!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, I've gone ahead and implemented this.
When verifying with a bundle, the payload in the DSSE envelope is the artifact (even in our case it contains an in-toto attestation that references another artifact). There is no safe |
@kommendorkapten Here we've been using "artifact" to mean the subject that was signed or is referenced in the attestation. If you were to pass in the DSSE envelope as the artifact, the function should fail 🤔. Would it be less confusing if we named it |
I'm talking about an edge case(?). As the Sigstore bundle does not mention anything about the content in the DSSE envelope, it can be a text string As I understand we do have a verify path for a "genereic" DSSE envelope: https://github.com/github/sigstore-go/blob/cd2b59895a54eafae3fd9ab080d4580114c5fdd8/pkg/verify/signature.go#L88 |
pkg/verify/signed_entity.go
Outdated
func (v *SignedEntityVerifier) VerifyWithArtifact(entity SignedEntity, artifact io.Reader, identities ...CertificateIdentity) (*VerificationResult, error) { | ||
policies := []PolicyOptionConfigurator{WithArtifact(artifact)} | ||
|
||
for _, certID := range identities { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's confusing to have so many ways of specifying CertificateIdentity
. Should the user call verify.WithCertificateIdentity
, or pass it in to Verify*
(or both?)
Ideally, there would be one way for the user to specify this information.
As a side note, I REALLY THINK we should require in-toto payload when dealing with dsse envelopes 😝. Having two separate methods for handling bare signatures (MessageSignature, raw DSSE envelope) is going to lead us down a path of madness. |
If we require an in-toto attestation in the DSSE envelope, it should be clearly documented as this is a deviation from the bundle spec. I don't think it will be controversial as in the Sigstore community we should expect in-toto attestations. But it must be clear when reading the docs. |
VerifyWithArtifact
and VerifyWithArtifactDigest
Verify
function explicit / make Verify
options safe by default
Signed-off-by: Phill MV <[email protected]>
added validation checks. Signed-off-by: Phill MV <[email protected]>
Signed-off-by: Phill MV <[email protected]>
Signed-off-by: Phill MV <[email protected]>
Signed-off-by: Phill MV <[email protected]>
Re-requesting a review as I have implemented the changes suggested in the previous round of feedback! |
…ith PolicyOption.
Signed-off-by: Phill MV <[email protected]>
This way, we can't accidentally use a default PolicyConfig struct. Signed-Off-By: Phill MV <[email protected]>
Signed-Off-By: Phill MV <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
What?
This PR renames re-organizes the logic
SignedEntityVerifier.Verify
's functional options in order to make unsafe uses of theVerify
function explicit / makeVerify
's options safe by default.After some discussion, it accomplishes this by:
WithoutArtifactUnsafe
andWithoutIdentitiesUnsafe
policy optionsVerify
loop checks for the presence of artifacts and certificate identities; before, the default was to assume we would not check for artifacts or identities. Now that behaviour must be explicitly requested.Verify
for encapsulating the optional funcs while giving us the opportunity to expand policy options in the future.Before
After
Why?
This PR seeks to make it explicit to any downstream users of this library what the preferred verification path is. A casual user browsing the package's Godoc may not realize that the previous
Verify
function had to be used very carefully and very intentionally in order to produce adequate results; we accidentally ended up with aVerify
loop that by default would work with the absence of an artifact and certificate identities.Accidentally foisting complexity is a common pitfall of verification libraries, and has led to many vulnerabilities.
To quote from the linked blog post,
I find this to be very compelling. For this reason, there should be an obviously safe path for callers to use that requires little to no thinking or knowledge of intricate details of the underlying specifications.
Whereas before,
Verify
function requires furnishing an artifact and a certificate identityNow,
Unsafe
in the name.Hopefully, any developer blindly typing
WithoutIdentitiesUnsafe
will feel foolish, think twice about what they are doing and stand out during code review.