Closed steiza closed 5 months ago
So here is a very rough example of what pkg/sign/
might look like:
For interface.go
:
package sign
import (
protobundle "github.com/sigstore/protobuf-specs/gen/pb-go/bundle/v1"
protocommon "github.com/sigstore/protobuf-specs/gen/pb-go/common/v1"
)
// this is just a convienient way to bundle the information from the signer to the witness(es)
type SignedEntity struct{
dataDigest []byte
signature []byte
ephemeralPublicKey []byte
envelope []byte
}
func Sign(data []byte, signer *Signer, witnesses []Witness, bundleVersion string) (*protobundle.Bundle, error) {
bundle := Bundle{}
signerResult, err := signer.SignData(data)
if err != nil {
return nil, err
}
// Put signerResult into SignedEntity, depending on type
// Put signerResult into bundle, depending on type
for _, witness := range witnesses {
witnessResult, err := witness.Witness(se)
if err != nil {
return nil, err
}
// Put witnessResult into bundle, depending on type
}
return bundle, nil
}
For signer.go
:
package sign
import (
"crypto"
"crypto/x509"
)
type Signer struct{}
type SignerResult struct{
// Populate these fields if you are signing with a key
signature []byte
publicKeyHint []byte
// Populate these fields if you are signing with Fulcio
ephemeralPublicKey []byte
signingCertificate *x509.Certificate
}
func (s *Signer) SignData(data) (*SignerResult, error) {
return nil, errors.New("not implemented")
}
type Fulcio struct {
baseUrl string
}
type Keypair struct {
crypto.PublicKey
crypto.PrivateKey
publicKeyHint []byte
}
func (f *Fulcio) SignData(data) (*SignerResult, error) {}
func (k *Keypair) SignData(data) (*SignerResult, error) {}
For witness.go
:
package sign
import (
"errors"
protocommon "github.com/sigstore/protobuf-specs/gen/pb-go/common/v1"
protorekor "github.com/sigstore/protobuf-specs/gen/pb-go/rekor/v1"
)
type Witness struct{}
type WitnessResult struct {
*protocommon.RFC3161SignedTimestamp
*protorekor.TransparencyLogEntry
}
func (w *Witness) Witness(se *SignedEntity) (*WitnessResult, error) {
return nil, errors.New("not implemented")
}
type TimestampAuthority struct{
baseUrl string
}
type Rekor struct {
baseUrl string
entryType string
}
func (ta *TimestampAuthority) Witness(se *SignedEntity) (*WitnessResult, error) {
// uses se.signature
}
func (r *Rekor) Witness(se *SignedEntity) (*WitnessResult, error) {
// hashedrekord uses se.signature and se.dataDigest
// dsse uses se.envelope and se.ephemeralPublicKey
}
I'll leave comments on the PR for the interface!
One quick comment is that "witness" needs a different name to avoid conflicting with the concept of witnessing from the tlog ecosystem. I also want to not conflate timestamping and auditability. It just so happens that the log can be used for both currently, but we would like to move towards timestamping being a requirement independent of the log so that you never have to trust the log.
+1 to "batteries included" Fulcio and private key support.
0.3.1 bundle support
As long as other clients support 0.3.x bundles, SGTM.
A few more goals I think we need to tackle:
Signing requires exactly 1 signer and at least 1 witness
I don't think we should enforce the latter because in the case of private key signing in a private environment, neither logging nor a timestamp is needed. As a CLI, not logging should be discouraged, but as an API, I don't think it should be opinionated.
@vishal-chdhry asked me today "what's left on signing?" so I thought I'd write it down here.
trusted_root.json
to the signer, so it can verify responses it gets from not just Fulcio, but also Rekor and Timestampstrusted_root.json
I'm new to sigstore-go and was curious about a few things here:
One quick comment is that "witness" needs a different name to avoid conflicting with the concept of witnessing from the tlog ecosystem
Can confirm, I was confused by this on first read. It looks like this term is already used in sigstore-js https://github.com/sigstore/sigstore-js/blob/main/packages/sign/README.md#witness so the train might have left the station on that one. For my education, could you clarify what a witness is supposed to be used for in this context? Does it have an equivalent concept in cosign?
Support ambient credentials, i.e. the "providers" interface from Cosign
With sigstore-go being mainly an API library, I wonder if it's really necessary to have built-in provider support the way cosign does - couldn't a Signer
be implemented for a provider in its own library, which the user could import and pass to Sign
when they call it?
Support interactive signing for developer-signed artifacts
Is it typical for other client libraries to have an interactive mode? What is the use case for using sigstore-go in interactive mode versus just using the cosign CLI?
Should we add end-to-end tests with local Fulcio / Rekor / Timestamp providers?
I think it's not a bad idea to at least add a basic smoke test for integration with the services, that way the skeleton is there and someone who wants to add a riskier feature has a starting point for more complex testing.
Hi @cmurphy, and welcome to sigstore-go!
could you clarify what a witness is supposed to be used for in this context?
I think the sigstore-js packages/sign/README.md#witness says it pretty well:
Each Witness receives the artifact signature and the public key and returns an VerificationMaterial which represents some sort of counter-signature for the artifact's signature. The returned VerificationMaterial may contain either Rekor transparency log entries or RFC3161 timestamps.
So basically it's a Rekor entry or a RFC3161 timestamp (or possibly both). Inside GitHub, we use the term "witness" as a shorthand for "a Rekor entry if you're working with the Sigstore Public Good Instance (like in the case of npm build provenance) or a RFC3161 Timestamp if you're using GitHub's internal Sigstore instance (like in the case of Artifact Attestations in a private repository)". This shorthand has definitely been useful inside GitHub, but I don't think it has caught on in the wider Sigstore community.
For sigstore-go we decided to use pkg/sign/transparency.go and pkg/sign/timestamping.go instead of combining them in the proposed pkg/sign/witness.go
.
Does it have an equivalent concept in cosign?
I knew cosign supported Rekor, but I had to double check on the RFC3161 Timestamp, which it looks like it does support with --timestamp-server-url
.
With sigstore-go being mainly an API library, I wonder if it's really necessary to have built-in provider support the way cosign does
Exactly; we ended up deciding that Signer
would take an IDToken
, but it was up to the caller of the sigstore-go API library to figure out how to obtain that IDToken
.
What is the use case for using sigstore-go in interactive mode versus just using the cosign CLI?
Yeah, once we decided Signer
would take an IDToken
the interactive mode was scoped out of sigstore-go as well. Users of the sigstore-go library (like cosign, possibly) are welcome to make an interactive mode, obtain the IDToken
, and then call into sigstore-go.
I think it's not a bad idea to at least add a basic smoke test for integration with the services, that way the skeleton is there and someone who wants to add a riskier feature has a starting point for more complex testing.
Agreed!
I knew cosign supported Rekor, but I had to double check on the RFC3161 Timestamp, which it looks like it does support with --timestamp-server-url.
Yes, Cosign supports RFC3161 timestamp authorities. Those timestamps can be persisted in OCI or as detached metadata.
With sigstore-go being mainly an API library, I wonder if it's really necessary to have built-in provider support the way cosign does Exactly; we ended up deciding that Signer would take an IDToken, but it was up to the caller of the sigstore-go API library to figure out how to obtain that IDToken.
To add some more context, what's missing is a "batteries included" configuration. I agree that interactive signing doesn't need to be a part of sigstore-go, though the question is, where should that logic live? How do we avoid integrators needing to reimplement the logic in Go to fetch identity tokens per platform? I would like this implemented somewhere in the Sigstore org, to minimize adoption burden and maximize the number of supported platforms.
We've got a few options:
My two cents, if not sigstore-go, let's move the "provider" logic from Cosign into sigstore/sigstore and demonstrate with an example application how to compose sigstore-go and sigstore/sigstore for signing blobs either interactively or on an automated platform. Does this seem reasonable?
"What's left for signing?"
One other feature would be support for the ClientTrustConfig
, which lets clients specify with a single configuration file the endpoints they will contact for the CA, logs and TSAs. This has been implemented by sigstore-python - https://github.com/sigstore/sigstore-python/pull/1010. I'll create an issue to track this. (Edit: https://github.com/sigstore/sigstore-go/issues/185)
I would like this implemented somewhere in the Sigstore org, to minimize adoption burden and maximize the number of supported platforms. We've got a few options:
What about either of:
What about either of:
a new repo sigstore/sigstore-providers
- avoids adding a bunch of new dependencies to sigstore/sigstore
- avoids making sigstore/sigstore even more of a "utils" dumping ground
individual repos for each provider, e.g. sigstore/sigstore-spiffe, sigstore/sigstore-google
- each repo only has the idp provider dependencies that it needs, so pulling in one provider to an application doesn't pull in the kitchen sink
- (con) the sigstore org becomes more cluttered
Sorry, missed responding! Given the complexity of adding repos into the org and finding active maintainers for each, having a single repo with a set of providers seems like a good approach.
We can revisit this as part of Cosign refactoring.
Exactly; we ended up deciding that
Signer
would take anIDToken
, but it was up to the caller of the sigstore-go API library to figure out how to obtain thatIDToken
.
Figuring out how to extract the IDToken is where I'm at now, as ossf/scorecard-action considers dropping sigstore/cosign in favor of sigstore/sigstore-go.
a new repo sigstore/sigstore-providers
If we switched today, I would likely copy Cosign's GitHub provider, but using a library would be nice.
Yea, you'll need Cosign's GitHub provider if you're fetching the token as part of a Go binary, or if you're running in an action, getting the token following https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-cloud-providers#adding-permissions-settings and passing that to the Scorecard binary.
cc @ramonpetgrave64 since we had chatted about this
Description
It would be great if sigstore-go could not just verify, but also sign bundles.
There aren't many libraries that support signing bundles today (just sigstore-js?) This would also allow sigstore-go to support the full range of tests in sigstore-conformance.
Goals
sigstore-go/pkg/sign/
supports signing Sigstore bundlescmd/conformance/main.go
is updated to supportsign-bundle
andsign
(similar toverify
,sign
will "wrap" the bundle flow).Anti-Goals
cmd/sigstore-go/main.go
, it should not compete with cosignsigstore-go
itselfReferences