-
Notifications
You must be signed in to change notification settings - Fork 82
fix: use sigstore-go TUF client for verify to match initialize #763
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| // 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 ( | ||
| "testing" | ||
|
|
||
| "github.com/sigstore/gitsign/internal/sigstoreroot" | ||
| ) | ||
|
|
||
| func TestFetchTrustedRoot(t *testing.T) { | ||
| trustedRoot, err := sigstoreroot.FetchTrustedRoot() | ||
| if err != nil { | ||
| t.Fatalf("FetchTrustedRoot() error = %v", err) | ||
| } | ||
| if trustedRoot == nil { | ||
| t.Fatal("FetchTrustedRoot() returned nil") | ||
| } | ||
|
|
||
| ctPubs, err := sigstoreroot.GetCTLogPubs(trustedRoot) | ||
| if err != nil { | ||
| t.Fatalf("GetCTLogPubs() error = %v", err) | ||
| } | ||
| if len(ctPubs.Keys) == 0 { | ||
| t.Fatal("GetCTLogPubs() returned no keys") | ||
| } | ||
|
|
||
| rekorPubs, err := sigstoreroot.GetRekorPubs(trustedRoot) | ||
| if err != nil { | ||
| t.Fatalf("GetRekorPubs() error = %v", err) | ||
| } | ||
| if len(rekorPubs.Keys) == 0 { | ||
| t.Fatal("GetRekorPubs() returned no keys") | ||
| } | ||
|
|
||
| certs, err := sigstoreroot.FulcioCertificates(trustedRoot) | ||
| if err != nil { | ||
| t.Fatalf("FulcioCertificates() error = %v", err) | ||
| } | ||
| if len(certs) == 0 { | ||
| t.Fatal("FulcioCertificates() returned no certificates") | ||
| } | ||
|
|
||
| hasCA := false | ||
| for _, cert := range certs { | ||
| if cert.IsCA { | ||
| hasCA = true | ||
| break | ||
| } | ||
| } | ||
| if !hasCA { | ||
| t.Fatal("FulcioCertificates() did not return any CA certificates") | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -19,13 +19,13 @@ import ( | |||||
| "bytes" | ||||||
| "context" | ||||||
| "crypto/x509" | ||||||
| "errors" | ||||||
| "fmt" | ||||||
| "os" | ||||||
|
|
||||||
| "github.com/sigstore/gitsign/internal/config" | ||||||
| "github.com/sigstore/gitsign/internal/sigstoreroot" | ||||||
| "github.com/sigstore/sigstore-go/pkg/root" | ||||||
| "github.com/sigstore/sigstore/pkg/cryptoutils" | ||||||
| "github.com/sigstore/sigstore/pkg/tuf" | ||||||
| ) | ||||||
|
|
||||||
| type CertificateSource func() ([]*x509.Certificate, error) | ||||||
|
|
@@ -67,41 +67,27 @@ func NewFromConfig(ctx context.Context, cfg *config.Config) (*x509.CertPool, *x5 | |||||
| return New(x509.NewCertPool(), src...) | ||||||
| } | ||||||
|
|
||||||
| const ( | ||||||
| // This is the root in the fulcio project. | ||||||
| fulcioTargetStr = "fulcio.crt.pem" | ||||||
|
|
||||||
| // This is the v1 migrated root. | ||||||
| fulcioV1TargetStr = "fulcio_v1.crt.pem" | ||||||
|
|
||||||
| // This is the untrusted v1 intermediate CA certificate, used or chain building. | ||||||
| fulcioV1IntermediateTargetStr = "fulcio_intermediate_v1.crt.pem" | ||||||
| ) | ||||||
|
|
||||||
| // FromTUF loads certs from the TUF client. | ||||||
| func FromTUF(ctx context.Context) CertificateSource { | ||||||
| // FromTUF loads Fulcio certificates from the sigstore-go TUF cache. | ||||||
| func FromTUF(_ context.Context) CertificateSource { | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Plumb through the context to the tuf.Options |
||||||
| return func() ([]*x509.Certificate, error) { | ||||||
| tufClient, err := tuf.NewFromEnv(ctx) | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think there's a regression here where we'd look for |
||||||
| trustedRoot, err := sigstoreroot.FetchTrustedRoot() | ||||||
| if err != nil { | ||||||
| return nil, fmt.Errorf("initializing tuf: %w", err) | ||||||
| } | ||||||
| // Retrieve from the embedded or cached TUF root. If expired, a network | ||||||
| // call is made to update the root. | ||||||
| targets, err := tufClient.GetTargetsByMeta(tuf.Fulcio, []string{fulcioTargetStr, fulcioV1TargetStr, fulcioV1IntermediateTargetStr}) | ||||||
| certs, err := sigstoreroot.FulcioCertificates(trustedRoot) | ||||||
| if err != nil { | ||||||
| return nil, fmt.Errorf("error getting targets: %w", err) | ||||||
| } | ||||||
| if len(targets) == 0 { | ||||||
| return nil, errors.New("none of the Fulcio roots have been found") | ||||||
| return nil, fmt.Errorf("initializing tuf: %w", err) | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| } | ||||||
| return certs, nil | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| certs := []*x509.Certificate{} | ||||||
| for _, t := range targets { | ||||||
| c, err := cryptoutils.UnmarshalCertificatesFromPEM(t.Target) | ||||||
| if err != nil { | ||||||
| return nil, fmt.Errorf("error unmarshalling certificates: %w", err) | ||||||
| } | ||||||
| certs = append(certs, c...) | ||||||
| // FromTrustedRoot loads Fulcio certificates from a pre-fetched TrustedRoot. | ||||||
| func FromTrustedRoot(trustedRoot *root.TrustedRoot) CertificateSource { | ||||||
| return func() ([]*x509.Certificate, error) { | ||||||
| certs, err := sigstoreroot.FulcioCertificates(trustedRoot) | ||||||
| if err != nil { | ||||||
| return nil, fmt.Errorf("getting fulcio certificates from trusted root: %w", err) | ||||||
| } | ||||||
| return certs, nil | ||||||
| } | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,124 @@ | ||
| // 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 sigstoreroot loads the Sigstore trusted root via the sigstore-go TUF client. | ||
| package sigstoreroot | ||
|
|
||
| import ( | ||
| "crypto/x509" | ||
| "encoding/json" | ||
| "fmt" | ||
| "os" | ||
| "path/filepath" | ||
| "time" | ||
|
|
||
| "github.com/sigstore/cosign/v3/pkg/cosign" | ||
| "github.com/sigstore/sigstore-go/pkg/root" | ||
| "github.com/sigstore/sigstore-go/pkg/tuf" | ||
| sigstoretuf "github.com/sigstore/sigstore/pkg/tuf" | ||
| ) | ||
|
|
||
| // TUFOptions returns sigstore-go TUF options, reading the mirror URL and | ||
| // cached root.json from the local TUF cache when a custom mirror is configured. | ||
| func TUFOptions() *tuf.Options { | ||
| opts := tuf.DefaultOptions() | ||
| if mirror, err := readRemoteHint(opts.CachePath); err == nil && mirror != "" { | ||
| opts.RepositoryBaseURL = mirror | ||
| } | ||
| if opts.RepositoryBaseURL != tuf.DefaultMirror { | ||
| cachedRoot := filepath.Join(opts.CachePath, tuf.URLToPath(opts.RepositoryBaseURL), "root.json") | ||
| if rootBytes, err := os.ReadFile(cachedRoot); err == nil { | ||
| opts.Root = rootBytes | ||
| } | ||
| } | ||
| return opts | ||
|
Comment on lines
+35
to
+45
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I assume this is to replicate cosign behavior? I wonder if there's something we can do to standardize that 🤔 (for now link to other similar behavior to explain why we're doing this / why sigstore-go doesn't handle it). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is more of a regression after 0.13, but it's possible that it makes sense, i haven't done a diff between 0.13 and 0.14... gitsign only started referencing that expired embedded root cert after gitsign version 0.13. So I rolled back and was testing with 0.13 and tufmirror defined and it would work fine until i referenced timestampserverurl (tsa) in the git config. All commits i did in mye testing repo was fine and tsa got correctly set inside the commits, however when i tried to verify them with gitsign verify, they always claimed to be invalid, even though their not before and not after parts where correct. This is where my proposal came in, i think that these fixups are very important for gitsign, since we are experiencing functionality breaking in 0.14 along with this, which was already an issue before 0.13 as well, at least it would seem: |
||
| } | ||
|
|
||
| // FetchTrustedRoot loads the Sigstore trusted root from the TUF cache. | ||
| func FetchTrustedRoot() (*root.TrustedRoot, error) { | ||
| return root.FetchTrustedRootWithOptions(TUFOptions()) | ||
| } | ||
|
|
||
| // GetCTLogPubs returns CT log public keys from the trusted root. | ||
| func GetCTLogPubs(trustedRoot *root.TrustedRoot) (*cosign.TrustedTransparencyLogPubKeys, error) { | ||
| return transparencyLogPubKeys(trustedRoot.CTLogs()) | ||
| } | ||
|
|
||
| // GetRekorPubs returns Rekor transparency log public keys from the trusted root. | ||
| func GetRekorPubs(trustedRoot *root.TrustedRoot) (*cosign.TrustedTransparencyLogPubKeys, error) { | ||
| return transparencyLogPubKeys(trustedRoot.RekorLogs()) | ||
| } | ||
|
|
||
| // FulcioCertificates extracts Fulcio root and intermediate certificates from the trusted root. | ||
| func FulcioCertificates(trustedRoot *root.TrustedRoot) ([]*x509.Certificate, error) { | ||
| cas := trustedRoot.FulcioCertificateAuthorities() | ||
| if len(cas) == 0 { | ||
| return nil, fmt.Errorf("no Fulcio certificate authorities found in trusted root") | ||
| } | ||
|
|
||
| var certs []*x509.Certificate | ||
| for _, ca := range cas { | ||
| fca, ok := ca.(*root.FulcioCertificateAuthority) | ||
| if !ok { | ||
| continue | ||
| } | ||
| if fca.Root != nil { | ||
| certs = append(certs, fca.Root) | ||
| } | ||
| certs = append(certs, fca.Intermediates...) | ||
| } | ||
| if len(certs) == 0 { | ||
| return nil, fmt.Errorf("no Fulcio certificates found in trusted root") | ||
| } | ||
| return certs, nil | ||
| } | ||
|
|
||
| func transparencyLogPubKeys(logs map[string]*root.TransparencyLog) (*cosign.TrustedTransparencyLogPubKeys, error) { | ||
| pubKeys := cosign.NewTrustedTransparencyLogPubKeys() | ||
|
|
||
| for logID, log := range logs { | ||
| if log.PublicKey == nil { | ||
| continue | ||
| } | ||
|
|
||
| status := sigstoretuf.Active | ||
| if !log.ValidityPeriodEnd.IsZero() && time.Now().After(log.ValidityPeriodEnd) { | ||
| status = sigstoretuf.Expired | ||
| } | ||
|
|
||
| pubKeys.Keys[logID] = cosign.TransparencyLogPubKey{ | ||
| PubKey: log.PublicKey, | ||
| Status: status, | ||
| } | ||
| } | ||
|
|
||
| if len(pubKeys.Keys) == 0 { | ||
| return nil, fmt.Errorf("no transparency log public keys found") | ||
| } | ||
| return &pubKeys, nil | ||
| } | ||
|
|
||
| func readRemoteHint(cachePath string) (string, error) { | ||
| data, err := os.ReadFile(filepath.Join(cachePath, "remote.json")) | ||
| if err != nil { | ||
| return "", err | ||
| } | ||
| var remote struct { | ||
| Mirror string `json:"mirror"` | ||
| } | ||
| if err := json.Unmarshal(data, &remote); err != nil { | ||
| return "", err | ||
| } | ||
| return remote.Mirror, 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.
Don't blanket disable linting rules like this - prefer disabling per-line with a description (happy to resolve any of these in another PR).