diff --git a/e2e/sign_test.go b/e2e/sign_test.go new file mode 100644 index 00000000..c1eec41f --- /dev/null +++ b/e2e/sign_test.go @@ -0,0 +1,115 @@ +// Copyright 2024 The Sigstore Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build e2e +// +build e2e + +package e2e + +import ( + "context" + "encoding/json" + "os" + "testing" + "time" + + "github.com/go-git/go-billy/v5/memfs" + "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/plumbing/object" + "github.com/go-git/go-git/v5/storage/memory" + "github.com/sigstore/cosign/v2/pkg/providers" + "github.com/sigstore/gitsign/internal/git/gittest" + "github.com/sigstore/gitsign/pkg/fulcio" + gsgit "github.com/sigstore/gitsign/pkg/git" + "github.com/sigstore/gitsign/pkg/gitsign" + "github.com/sigstore/gitsign/pkg/rekor" + "github.com/sigstore/sigstore/pkg/oauth" + "github.com/sigstore/sigstore/pkg/oauthflow" +) + +func TestSign(t *testing.T) { + ctx := context.Background() + + var flow oauthflow.TokenGetter = &oauthflow.InteractiveIDTokenGetter{ + HTMLPage: oauth.InteractiveSuccessHTML, + } + if providers.Enabled(ctx) { + // If automatic token provisioning is enabled, use it. + token, err := providers.Provide(ctx, "sigstore") + if err != nil { + t.Fatal(err) + } + flow = &oauthflow.StaticTokenGetter{ + RawToken: token, + } + } + fulcio, err := fulcio.NewClient("https://fulcio.sigstore.dev", fulcio.OIDCOptions{ + ClientID: "sigstore", + Issuer: "https://oauth2.sigstore.dev/auth", + TokenGetter: flow, + }) + if err != nil { + t.Fatal(err) + } + rekor, err := rekor.NewWithOptions(ctx, "https://rekor.sigstore.dev") + if err != nil { + t.Fatal(err) + } + signer, err := gitsign.NewSigner(ctx, fulcio, rekor) + if err != nil { + t.Fatal(err) + } + + // Make a commit + sign it + storage := memory.NewStorage() + repo, err := git.Init(storage, memfs.New()) + if err != nil { + panic(err) + } + w, err := repo.Worktree() + if err != nil { + panic(err) + } + sha, err := w.Commit("example commit", &git.CommitOptions{ + Author: &object.Signature{ + Name: "John Doe", + Email: "john@example.com", + When: time.UnixMicro(1234567890).UTC(), + }, + Signer: signer, + AllowEmptyCommits: true, + }) + if err != nil { + t.Fatal(err) + } + commit, err := repo.CommitObject(sha) + if err != nil { + t.Fatal(err) + } + body := gittest.MarshalCommitBody(t, commit) + sig := []byte(commit.PGPSignature) + + // Verify the commit + verifier, err := gsgit.NewDefaultVerifier(ctx) + if err != nil { + t.Fatal(err) + } + summary, err := gsgit.Verify(ctx, verifier, rekor, body, sig, true) + if err != nil { + t.Fatal(err) + } + enc := json.NewEncoder(os.Stdout) + enc.SetIndent("", " ") + enc.Encode(summary.LogEntry) +} diff --git a/go.mod b/go.mod index 146a2a52..ac4cb505 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/coreos/go-systemd/v22 v22.5.0 github.com/github/smimesign v0.2.0 github.com/go-git/go-billy/v5 v5.5.0 - github.com/go-git/go-git/v5 v5.11.0 + github.com/go-git/go-git/v5 v5.11.1-0.20240221104814-686a0f7a4928 github.com/go-openapi/runtime v0.27.1 github.com/go-openapi/strfmt v0.22.0 github.com/go-openapi/swag v0.22.9 @@ -18,6 +18,7 @@ require ( github.com/jonboulle/clockwork v0.4.0 github.com/mattn/go-tty v0.0.5 github.com/patrickmn/go-cache v2.1.0+incompatible + github.com/psanford/memfs v0.0.0-20230130182539-4dbf7e3e865e github.com/secure-systems-lab/go-securesystemslib v0.8.0 github.com/sigstore/cosign/v2 v2.2.3 github.com/sigstore/fulcio v1.4.3 @@ -48,7 +49,7 @@ require ( github.com/Azure/go-autorest/logger v0.2.1 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c // indirect + github.com/ProtonMail/go-crypto v1.0.0 // indirect github.com/ThalesIgnite/crypto11 v1.2.5 // indirect github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4 // indirect github.com/alibabacloud-go/cr-20160607 v1.0.1 // indirect @@ -168,7 +169,7 @@ require ( github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sassoftware/relic v7.2.1+incompatible // indirect github.com/segmentio/ksuid v1.0.4 // indirect - github.com/sergi/go-diff v1.3.1 // indirect + github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect github.com/shibumi/go-pathspec v1.3.0 // indirect github.com/sigstore/timestamp-authority v1.2.1 // indirect github.com/sirupsen/logrus v1.9.3 // indirect @@ -200,14 +201,14 @@ require ( go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.26.0 // indirect golang.org/x/exp v0.0.0-20231108232855-2478ac86f678 // indirect - golang.org/x/mod v0.14.0 // indirect + golang.org/x/mod v0.15.0 // indirect golang.org/x/net v0.21.0 // indirect golang.org/x/sync v0.6.0 // indirect golang.org/x/sys v0.17.0 // indirect golang.org/x/term v0.17.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.5.0 // indirect - golang.org/x/tools v0.16.1 // indirect + golang.org/x/tools v0.18.0 // indirect google.golang.org/api v0.159.0 // indirect google.golang.org/appengine v1.6.8 // indirect google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac // indirect diff --git a/go.sum b/go.sum index 5d748c09..e5b42a37 100644 --- a/go.sum +++ b/go.sum @@ -57,8 +57,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c h1:kMFnB0vCcX7IL/m9Y5LO+KQYv+t1CQOiFe6+SV2J7bE= -github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= +github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78= +github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= github.com/ThalesIgnite/crypto11 v1.2.5 h1:1IiIIEqYmBvUYFeMnHqRft4bwf/O36jryEUpY+9ef8E= github.com/ThalesIgnite/crypto11 v1.2.5/go.mod h1:ILDKtnCKiQ7zRoNxcp36Y1ZR8LBPmR2E23+wTQe/MlE= github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0= @@ -253,8 +253,8 @@ github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+ github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= -github.com/go-git/go-git/v5 v5.11.0 h1:XIZc1p+8YzypNr34itUfSvYJcv+eYdTnTvOZ2vD3cA4= -github.com/go-git/go-git/v5 v5.11.0/go.mod h1:6GFcX2P3NM7FPBfpePbpLd21XxsgdAt+lKqXmCUiUCY= +github.com/go-git/go-git/v5 v5.11.1-0.20240221104814-686a0f7a4928 h1:KhFQZmW7PuR0d4GySsCkCPlLXbAUCBlcKxcnd6e0ATA= +github.com/go-git/go-git/v5 v5.11.1-0.20240221104814-686a0f7a4928/go.mod h1:bjjasRHBEKHquuJUiVxAokJncx14xMhH6v/iguEEPTc= github.com/go-jose/go-jose/v3 v3.0.1 h1:pWmKFVtt+Jl0vBZTIpz/eAKwsm6LkIxDVVbFHKkchhA= github.com/go-jose/go-jose/v3 v3.0.1/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -338,6 +338,7 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= @@ -534,6 +535,8 @@ github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lne github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +github.com/psanford/memfs v0.0.0-20230130182539-4dbf7e3e865e h1:51xcRlSMBU5rhM9KahnJGfEsBPVPz3182TgFRowA8yY= +github.com/psanford/memfs v0.0.0-20230130182539-4dbf7e3e865e/go.mod h1:tcaRap0jS3eifrEEllL6ZMd9dg8IlDpi2S1oARrQ+NI= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= @@ -552,8 +555,8 @@ github.com/secure-systems-lab/go-securesystemslib v0.8.0 h1:mr5An6X45Kb2nddcFlbm github.com/secure-systems-lab/go-securesystemslib v0.8.0/go.mod h1:UH2VZVuJfCYR8WgMlCU1uFsOUU+KeyrTWcSS73NBOzU= github.com/segmentio/ksuid v1.0.4 h1:sBo2BdShXjmcugAMwjugoGUdUV0pcxY5mW4xKRn3v4c= github.com/segmentio/ksuid v1.0.4/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE= -github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= -github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/shibumi/go-pathspec v1.3.0 h1:QUyMZhFo0Md5B8zV8x2tesohbb5kfbpTi9rBnKh5dkI= github.com/shibumi/go-pathspec v1.3.0/go.mod h1:Xutfslp817l2I1cZvgcfeMQJG5QnU2lh5tVaaMCl3jE= github.com/sigstore/cosign/v2 v2.2.3 h1:WX7yawI+EXu9h7S5bZsfYCbB9XW6Jc43ctKy/NoOSiA= @@ -716,6 +719,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -827,6 +832,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA= golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= +golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ= +golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/internal/fulcio/identity.go b/internal/fulcio/identity.go index c64e9240..294b45b9 100644 --- a/internal/fulcio/identity.go +++ b/internal/fulcio/identity.go @@ -34,6 +34,7 @@ import ( "github.com/sigstore/gitsign/internal/config" "github.com/sigstore/gitsign/internal/fulcio/fulcioroots" "github.com/sigstore/gitsign/internal/signerverifier" + "github.com/sigstore/gitsign/pkg/fulcio" "github.com/sigstore/sigstore/pkg/oauth" "github.com/sigstore/sigstore/pkg/oauthflow" "github.com/sigstore/sigstore/pkg/signature" @@ -233,8 +234,8 @@ func (f *IdentityFactory) NewIdentity(ctx context.Context, cfg *config.Config) ( return nil, fmt.Errorf("generating private key: %w", err) } - client, err := NewClient(cfg.Fulcio, - OIDCOptions{ + client, err := fulcio.NewClient(cfg.Fulcio, + fulcio.OIDCOptions{ Issuer: cfg.Issuer, ClientID: clientID, RedirectURL: cfg.RedirectURL, diff --git a/internal/fulcio/fulcio.go b/pkg/fulcio/fulcio.go similarity index 89% rename from internal/fulcio/fulcio.go rename to pkg/fulcio/fulcio.go index 856d87e3..0e3c2f21 100644 --- a/internal/fulcio/fulcio.go +++ b/pkg/fulcio/fulcio.go @@ -27,9 +27,13 @@ import ( "github.com/sigstore/sigstore/pkg/oauthflow" ) +type Client interface { + GetCert(crypto.Signer) (*api.CertificateResponse, error) +} + // Client provides a fulcio client with helpful options for configuring OIDC // flows. -type Client struct { +type ClientImpl struct { api.LegacyClient oidc OIDCOptions } @@ -43,20 +47,20 @@ type OIDCOptions struct { TokenGetter oauthflow.TokenGetter } -func NewClient(fulcioURL string, opts OIDCOptions) (*Client, error) { +func NewClient(fulcioURL string, opts OIDCOptions) (*ClientImpl, error) { u, err := url.Parse(fulcioURL) if err != nil { return nil, err } client := api.NewClient(u, api.WithUserAgent("gitsign")) - return &Client{ + return &ClientImpl{ LegacyClient: client, oidc: opts, }, nil } // GetCert exchanges the given private key for a Fulcio certificate. -func (c *Client) GetCert(priv crypto.Signer) (*api.CertificateResponse, error) { +func (c *ClientImpl) GetCert(priv crypto.Signer) (*api.CertificateResponse, error) { pubBytes, err := x509.MarshalPKIXPublicKey(priv.Public()) if err != nil { return nil, err diff --git a/internal/fulcio/fulcio_test.go b/pkg/fulcio/fulcio_test.go similarity index 99% rename from internal/fulcio/fulcio_test.go rename to pkg/fulcio/fulcio_test.go index 23819ea3..57ad2726 100644 --- a/internal/fulcio/fulcio_test.go +++ b/pkg/fulcio/fulcio_test.go @@ -120,7 +120,7 @@ func TestGetCert(t *testing.T) { key, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) email := "foo@example.com" - client := &Client{ + client := &ClientImpl{ // fakeFulcio is what will be doing the validation. LegacyClient: &fakeFulcio{ signer: key, diff --git a/pkg/git/verifier.go b/pkg/git/verifier.go index 1385496c..1eab8885 100644 --- a/pkg/git/verifier.go +++ b/pkg/git/verifier.go @@ -23,6 +23,8 @@ import ( "time" cms "github.com/sigstore/gitsign/internal/fork/ietf-cms" + "github.com/sigstore/gitsign/internal/fulcio/fulcioroots" + "github.com/sigstore/sigstore/pkg/tuf" ) // Verifier verifies git commit signature data. @@ -139,3 +141,16 @@ func (v *CertVerifier) Verify(_ context.Context, data, sig []byte, detached bool return cert, nil } + +// NewDefaultVerifier returns a new CertVerifier with the default Fulcio roots loaded from the local TUF client. +// See https://docs.sigstore.dev/system_config/custom_components/ for how to customize this behavior. +func NewDefaultVerifier(ctx context.Context) (*CertVerifier, error) { + if err := tuf.Initialize(ctx, tuf.DefaultRemoteRoot, nil); err != nil { + return nil, err + } + root, intermediate, err := fulcioroots.New(x509.NewCertPool(), fulcioroots.FromTUF(ctx)) + if err != nil { + return nil, err + } + return NewCertVerifier(WithRootPool(root), WithIntermediatePool(intermediate)) +} diff --git a/pkg/gitsign/signer.go b/pkg/gitsign/signer.go new file mode 100644 index 00000000..45fe9863 --- /dev/null +++ b/pkg/gitsign/signer.go @@ -0,0 +1,95 @@ +// Copyright 2024 The Sigstore Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package gitsign provides a signer for signing git commits and tags via Gitsign keyless flow. +package gitsign + +import ( + "context" + "crypto" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "fmt" + "io" + + "github.com/go-git/go-git/v5" + fulciointernal "github.com/sigstore/gitsign/internal/fulcio" + "github.com/sigstore/gitsign/internal/signature" + "github.com/sigstore/gitsign/pkg/fulcio" + "github.com/sigstore/gitsign/pkg/rekor" + + // Enable OIDC providers + _ "github.com/sigstore/cosign/v2/pkg/providers/all" +) + +type PrivateKeySigner interface { + crypto.PrivateKey + crypto.Signer +} + +var ( + _ git.Signer = &Signer{} +) + +type Signer struct { + ctx context.Context + key PrivateKeySigner + fulcio fulcio.Client + rekor rekor.Writer +} + +func NewSigner(ctx context.Context, fulcio fulcio.Client, rekor rekor.Writer) (*Signer, error) { + priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return nil, fmt.Errorf("generating private key: %w", err) + } + return &Signer{ + ctx: ctx, + key: priv, + fulcio: fulcio, + rekor: rekor, + }, nil +} + +func (f *Signer) Sign(message io.Reader) ([]byte, error) { + cert, err := f.fulcio.GetCert(f.key) + if err != nil { + return nil, fmt.Errorf("error getting fulcio cert: %w", err) + } + + id := &fulciointernal.Identity{ + PrivateKey: f.key, + CertPEM: cert.CertPEM, + ChainPEM: cert.ChainPEM, + } + + body, err := io.ReadAll(message) + if err != nil { + return nil, fmt.Errorf("error reading message: %w", err) + } + + resp, err := signature.Sign(f.ctx, id, body, signature.SignOptions{ + Rekor: f.rekor, + + // TODO: make SignOptions configurable? + Armor: true, + Detached: true, + IncludeCerts: -2, + }) + if err != nil { + return nil, err + } + return resp.Signature, nil +}