-
Notifications
You must be signed in to change notification settings - Fork 49
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
Add functionality to sign DSSE envelopes with arbitrary payloads #1054
Conversation
Signed-off-by: Martin Sablotny <[email protected]>
Signed-off-by: Martin Sablotny <[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.
Thanks @susperius!
Did an initial pass -- structure LGTM overall, but I want to confirm that this is actually supported by the Sigstore PGI before moving forwards 🙂
sigstore/sign.py
Outdated
@@ -229,6 +230,45 @@ def sign_dsse( | |||
|
|||
return self._finalize_sign(cert, content, proposed_entry) | |||
|
|||
def sign_dsse_envelope( |
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'm a little wary of a new top-level sign API, especially one that's so close to sign_dsse
-- instead of making this a new function, what do you think about making sign_dsse
take Statement | Envelope
?
From there, we'd need to improve the docs. But I think that might be a little cleaner/easier to maintain long-term 🙂
sigstore/sign.py
Outdated
), | ||
), | ||
) | ||
return self._finalize_sign(cert, envelope, proposed_entry) |
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.
Making sure I'm not missing anything: does this actually work yet? My understanding was that Rekor doesn't yet support arbitrary DSSE envelopes, since it expects a valid in-toto statement within the envelope.
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.
That is correct, https://github.com/sigstore/rekor/blob/main/pkg/types/dsse/v0.0.1/entry.go#L112-L146, also pointed out in #982 (comment).
The reason is so that Rekor can extract index keys from the statement. We can add other payload types.
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.
Thanks @haydentherapper! I was feeling some deja vu at this 😅
Given the above, I'm not sure we can merge this/it makes sense to merge until Rekor supports other payload types (since users who attempt to use it will hit a hard error and think that sigstore-python is broken).
(Another option here would be to use a DSSE envelope with an arbitrary payload, but to emit a hashedrekord
entry instead. I have no strong opinion on whether this is a good idea or not, but it'll likely cause a lot of incompatibilities with the other clients in the ecosystem.)
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.
Oh ok, I missed that. Sorry!
We could log a warning when an arbitrary DSSE envelope is used and skip the Rekor post in finalize sign.
Would that make sense or would it mess up the verification itself?
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.
Would that make sense or would it mess up the verification itself?
That would mess up the verification itself, unfortunately -- that would result in a bundle with no transparency log entries, which would both be invalid and also incompatible with other clients.
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.
Even if we made index keys optional for unparseable DSSEs, this would be a breaking change for log monitors that expect an intoto payload in order to extract subject values.
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.
How could we proceed with this then?
- Would it be possible to use the SHA256 of the payload as index key for arbitrary payloads?
- Should we define our DSSE envelope payload type before and then add it to rekor? <- this wouldn't be a solution for arbitrary payloads but helps the model signing effort.
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.
@haydentherapper I meant to bring your focus back to this by re-triggering the review ;)
sigstore/dsse.py
Outdated
@@ -210,6 +211,23 @@ def _from_json(cls, contents: bytes | str) -> Envelope: | |||
inner = _Envelope().from_json(contents) | |||
return cls(inner) | |||
|
|||
@classmethod | |||
def from_payload(cls, payload_type: str, payload: bytes) -> Envelope: |
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.
Like elsewhere: we should start with this being a private API, before we make it public.
def from_payload(cls, payload_type: str, payload: bytes) -> Envelope: | |
def _from_payload(cls, payload_type: str, payload: bytes) -> Envelope: |
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.
It is the only "valid" way to create an envelope for users (except of just calling the private method).
Surely, I can change this but just like to get your thoughts on how otherwise a user can sign an envelope.
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.
Hmm, yeah. The problem here is that from_payload
is a little misleading as an API: it produces an Envelope
, but that Envelope
is actually in an invalid state (since it's missing signatures).
Instead of passing an incomplete Envelope
in for signing, maybe we could adapt the top-level sign_dsse
signature:
def sign_dsse(
self,
input_: dsse.Statement | tuple[str, bytes],
) -> Bundle:
...where tuple[str, bytes]
is the envelope type and arbitrary contents. That can then be passed into dsse._sign
with some similar small tweaks.
(I still don't love that signature, but I think it's a better starting point and avoids the need for a partial constructor here 🙂)
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.
How about using a dataclass?
@dataclass
class DSSEPayload:
payload_type: str
payload: bytes
I think at some point the sign_dsse
might need to be renamed :) but for now a dataclass would make it clearer for users.
WDYT?
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.
That works for me, although maybe dsse.RawPayload
to make it clear that this is the "raw" variant of possible payload types (dsse.Statement
being another valid variant).
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.
PTAL
Thank you for looking into this! |
…gned and wrapped into a DSSE envelope Signed-off-by: Martin Sablotny <[email protected]>
) | ||
elif type(input_) is dsse.RawPayload: | ||
content = dsse._sign_payload(self._private_key, input_) | ||
# TODO: figure out an entry that works. |
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.
NB: this is still a hard blocker: the client specification requires bundles to have a transparency log entry, so we can't just skip it when the signed-over input isn't a DSSE.
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 there are two possible resolutions here, both of which require some kind of upstream change:
- Option 1: Rekor learns about non-JSON DSSE payloads. I don't know what the timeline for this would be 🙂
- Option 2: The client spec clarifies/expresses a judgement about using
dsse
bundles with non-dsse
Rekor entry types (such ashashedrekord
, which would work fine here if the DSSE envelope is canonicalized).
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.
In case it helps: the model signing use case would contain JSON as payload. However, as things stand now rekor solely supports in-toto statements.
@@ -256,6 +289,19 @@ def _sign(key: ec.EllipticCurvePrivateKey, stmt: Statement) -> Envelope: | |||
) | |||
|
|||
|
|||
def _sign_payload( |
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.
Rather than a new _sign_payload
function, can we make _sign
take Statement | RawPayload
? That'll require some internal checks but will be a bit shorter 🙂
@classmethod | ||
def _from_payload( | ||
cls, payload: RawPayload, sigs: list[Signature] | ||
) -> Envelope: | ||
"""Return an unsigned DSSE envelope. | ||
|
||
Args: | ||
payload_type (str): The envelope's payload type | ||
payload (bytes): The envelope's payload | ||
|
||
Returns: | ||
Envelope: An unsigned DSSE envelope | ||
""" | ||
inner = _Envelope( | ||
payload=payload.data, | ||
payload_type=payload.type, | ||
signatures=sigs, | ||
) | ||
return cls(inner) |
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.
Merging _sign_payload
with _sign
should eliminate the need for this new helper 🙂
Summary
Added functionality to sign DSSE envelopes with arbitrary payloads. This is to ensure model signing can in the future use sigstore with its own manifest format inside of a DSSE envelope.
Closes #982
Release Note
sign_dsse_envelope
takes an envelope as input value and adds a signature for its contents to the envelopeEnvelope
provides a public classmethod to create an envelope based on an arbitrary payload type and payload