Repository: cli/cli
Pull request #9616 (2024-09-12 23:03:36)

Better messaging for `attestation verify` custom issuer mismatch error
GH Enterprise customers have the option to [customize](https://docs.github.com/en/enterprise-cloud@latest/actions/security-for-github-actions/security-hardening-your-deployments/about-security-hardening-with-openid-connect#customizing-the-issuer-value-for-an-enterprise) the issuer claim in their Actions-issued OIDC tokens to include their unique enterprise "slug".

For users who have customized the issuer claim, the `attestation verify` command is unable to determine to expected issuer value by default -- instead, the user must pass in their customized issuer value using the `--cert-oidc-issuer` flag. If the user does NOT supply the issuer value, they receive an error message similar to the following:

```
Error: verifying with issuer "sigstore.dev": failed to verify certificate identity: no matching CertificateIdentity found, last error: 
expected issuer value "https://token.actions.githubusercontent.com", got "https://token.actions.githubusercontent.com/foo-bar"
```

This change detects the scenario where the expected issuer differs only by the enterprise slug and nudges the user in the right direction by suggesting they use the `--cert-oidc-issuer` flag with their expected issuer value.

```
Error: expected issuer to be https://token.actions.githubusercontent.com, got https://token.actions.githubusercontent.com/foo-bar -- if you have customized the issuer for your enterprise try using the --cert-oidc-issuer flag with your expected issuer
```

See: https://github.com/cli/cli/issues/9615

cliAutomation: Hi! Thanks for the pull request. Please ensure that this change is linked to an issue by mentioning an issue number in the description of the pull request. If this pull request would close the issue, please put the word 'Fixes' before the issue number somewhere in the pull request body. If this is a tiny change like fixing a typo, feel free to ignore this message.

Patch:
diff --git a/go.mod b/go.mod
index 54d02ec8e..78ceb7f70 100644
--- a/go.mod
+++ b/go.mod
@@ -11,7 +11,7 @@ require (
 	github.com/cenkalti/backoff/v4 v4.3.0
 	github.com/charmbracelet/glamour v0.7.0
 	github.com/charmbracelet/lipgloss v0.10.1-0.20240413172830-d0be07ea6b9c
-	github.com/cli/go-gh/v2 v2.10.0
+	github.com/cli/go-gh/v2 v2.9.0
 	github.com/cli/oauth v1.0.1
 	github.com/cli/safeexec v1.0.1
 	github.com/cpuguy83/go-md2man/v2 v2.0.4
diff --git a/go.sum b/go.sum
index 416f2ee59..37c40407d 100644
--- a/go.sum
+++ b/go.sum
@@ -95,8 +95,8 @@ github.com/charmbracelet/x/exp/term v0.0.0-20240425164147-ba2a9512b05f/go.mod h1
 github.com/cli/browser v1.0.0/go.mod h1:IEWkHYbLjkhtjwwWlwTHW2lGxeS5gezEQBMLTwDHf5Q=
 github.com/cli/browser v1.3.0 h1:LejqCrpWr+1pRqmEPDGnTZOjsMe7sehifLynZJuqJpo=
 github.com/cli/browser v1.3.0/go.mod h1:HH8s+fOAxjhQoBUAsKuPCbqUuxZDhQ2/aD+SzsEfBTk=
-github.com/cli/go-gh/v2 v2.10.0 h1:GMflBKoErBXlLvN2euxzL+p7JaM8erlSmw0cT7uZr7M=
-github.com/cli/go-gh/v2 v2.10.0/go.mod h1:MeRoKzXff3ygHu7zP+NVTT+imcHW6p3tpuxHAzRM2xE=
+github.com/cli/go-gh/v2 v2.9.0 h1:D3lTjEneMYl54M+WjZ+kRPrR5CEJ5BHS05isBPOV3LI=
+github.com/cli/go-gh/v2 v2.9.0/go.mod h1:MeRoKzXff3ygHu7zP+NVTT+imcHW6p3tpuxHAzRM2xE=
 github.com/cli/oauth v1.0.1 h1:pXnTFl/qUegXHK531Dv0LNjW4mLx626eS42gnzfXJPA=
 github.com/cli/oauth v1.0.1/go.mod h1:qd/FX8ZBD6n1sVNQO3aIdRxeu5LGw9WhKnYhIIoC2A4=
 github.com/cli/safeexec v1.0.0/go.mod h1:Z/D4tTN8Vs5gXYHDCbaM1S/anmEDnJb1iW0+EJ5zx3Q=
diff --git a/internal/ghinstance/host.go b/internal/ghinstance/host.go
index f80bfef57..729d47f31 100644
--- a/internal/ghinstance/host.go
+++ b/internal/ghinstance/host.go
@@ -73,13 +73,6 @@ func GraphQLEndpoint(hostname string) string {
 	if isGarage(hostname) {
 		return fmt.Sprintf("https://%s/api/graphql", hostname)
 	}
-	// Once we change Tenancy to no longer be treated as Enterprise, this
-	// conditional can be removed as the flow will fall through to the bottom.
-	// However, we can't do that until we've investigated all places in which
-	// Tenancy is currently treated as Enterprise.
-	if IsTenancy(hostname) {
-		return fmt.Sprintf("https://api.%s/graphql", hostname)
-	}
 	if IsEnterprise(hostname) {
 		return fmt.Sprintf("https://%s/api/graphql", hostname)
 	}
@@ -93,13 +86,6 @@ func RESTPrefix(hostname string) string {
 	if isGarage(hostname) {
 		return fmt.Sprintf("https://%s/api/v3/", hostname)
 	}
-	// Once we change Tenancy to no longer be treated as Enterprise, this
-	// conditional can be removed as the flow will fall through to the bottom.
-	// However, we can't do that until we've investigated all places in which
-	// Tenancy is currently treated as Enterprise.
-	if IsTenancy(hostname) {
-		return fmt.Sprintf("https://api.%s/", hostname)
-	}
 	if IsEnterprise(hostname) {
 		return fmt.Sprintf("https://%s/api/v3/", hostname)
 	}
diff --git a/pkg/cmd/extension/manager.go b/pkg/cmd/extension/manager.go
index 45186d689..0ab429c2e 100644
--- a/pkg/cmd/extension/manager.go
+++ b/pkg/cmd/extension/manager.go
@@ -263,16 +263,10 @@ func (m *Manager) installBin(repo ghrepo.Interface, target string) error {
 		}
 	}
 
-	// if using an ARM-based Mac and an arm64 binary is unavailable, fall back to amd64 if a relevant binary is available and Rosetta 2 is installed
-	if asset == nil && isMacARM {
+	// if an arm64 binary is unavailable, fall back to amd64 if it can be executed through Rosetta 2
+	if asset == nil && isMacARM && hasRosetta() {
 		for _, a := range r.Assets {
 			if strings.HasSuffix(a.Name, "darwin-amd64") {
-				if !hasRosetta() {
-					return fmt.Errorf(
-						"%[1]s unsupported for %[2]s. Install Rosetta with `softwareupdate --install-rosetta` to use the available darwin-amd64 binary, or open an issue: `gh issue create -R %[3]s/%[1]s -t'Support %[2]s'`",
-						repo.RepoName(), platform, repo.RepoOwner())
-				}
-
 				asset = &a
 				break
 			}
@@ -771,7 +765,7 @@ func possibleDists() []string {
 	}
 }
 
-var hasRosetta = func() bool {
+func hasRosetta() bool {
 	_, err := os.Stat("/Library/Apple/usr/libexec/oah/libRosettaRuntime")
 	return err == nil
 }


Test patch:
diff --git a/internal/ghinstance/host_test.go b/internal/ghinstance/host_test.go
index e9fb97008..673fad24c 100644
--- a/internal/ghinstance/host_test.go
+++ b/internal/ghinstance/host_test.go
@@ -254,10 +254,6 @@ func TestGraphQLEndpoint(t *testing.T) {
 			host: "ghe.io",
 			want: "https://ghe.io/api/graphql",
 		},
-		{
-			host: "tenant.ghe.com",
-			want: "https://api.tenant.ghe.com/graphql",
-		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.host, func(t *testing.T) {
@@ -289,10 +285,6 @@ func TestRESTPrefix(t *testing.T) {
 			host: "ghe.io",
 			want: "https://ghe.io/api/v3/",
 		},
-		{
-			host: "tenant.ghe.com",
-			want: "https://api.tenant.ghe.com/",
-		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.host, func(t *testing.T) {
diff --git a/pkg/cmd/attestation/io/handler.go b/pkg/cmd/attestation/io/handler.go
index 9664c7f65..07be3d1df 100644
--- a/pkg/cmd/attestation/io/handler.go
+++ b/pkg/cmd/attestation/io/handler.go
@@ -62,10 +62,6 @@ func (h *Handler) VerbosePrintf(f string, v ...interface{}) (int, error) {
 }
 
 func (h *Handler) PrintTable(headers []string, rows [][]string) error {
-	if !h.IO.IsStdoutTTY() {
-		return nil
-	}
-
 	t := tableprinter.New(h.IO, tableprinter.WithHeader(headers...))
 
 	for _, row := range rows {
diff --git a/pkg/cmd/attestation/verification/attestation.go b/pkg/cmd/attestation/verification/attestation.go
index 50542a6b3..4b2545f62 100644
--- a/pkg/cmd/attestation/verification/attestation.go
+++ b/pkg/cmd/attestation/verification/attestation.go
@@ -53,25 +53,13 @@ func GetLocalAttestations(path string) ([]*api.Attestation, error) {
 	case ".json":
 		attestations, err := loadBundleFromJSONFile(path)
 		if err != nil {
-			var pathErr *os.PathError
-			if errors.As(err, &pathErr) {
-				return nil, fmt.Errorf("bundle could not be loaded from JSON file at %s", path)
-			} else if errors.Is(err, bundle.ErrValidation) {
-				return nil, err
-			}
-			return nil, fmt.Errorf("bundle content could not be parsed")
+			return nil, fmt.Errorf("bundle could not be loaded from JSON file: %v", err)
 		}
 		return attestations, nil
 	case ".jsonl":
 		attestations, err := loadBundlesFromJSONLinesFile(path)
 		if err != nil {
-			var pathErr *os.PathError
-			if errors.As(err, &pathErr) {
-				return nil, fmt.Errorf("bundles could not be loaded from JSON lines file at %s", path)
-			} else if errors.Is(err, bundle.ErrValidation) {
-				return nil, err
-			}
-			return nil, fmt.Errorf("bundle content could not be parsed")
+			return nil, fmt.Errorf("bundles could not be loaded from JSON lines file: %v", err)
 		}
 		return attestations, nil
 	}
@@ -90,7 +78,7 @@ func loadBundleFromJSONFile(path string) ([]*api.Attestation, error) {
 func loadBundlesFromJSONLinesFile(path string) ([]*api.Attestation, error) {
 	fileContent, err := os.ReadFile(path)
 	if err != nil {
-		return nil, err
+		return nil, fmt.Errorf("could not read file: %v", err)
 	}
 
 	attestations := []*api.Attestation{}
@@ -98,12 +86,12 @@ func loadBundlesFromJSONLinesFile(path string) ([]*api.Attestation, error) {
 	decoder := json.NewDecoder(bytes.NewReader(fileContent))
 
 	for decoder.More() {
-		var b bundle.Bundle
-		b.Bundle = new(protobundle.Bundle)
-		if err := decoder.Decode(&b); err != nil {
-			return nil, err
+		var bundle bundle.Bundle
+		bundle.Bundle = new(protobundle.Bundle)
+		if err := decoder.Decode(&bundle); err != nil {
+			return nil, fmt.Errorf("failed to unmarshal bundle from JSON: %v", err)
 		}
-		a := api.Attestation{Bundle: &b}
+		a := api.Attestation{Bundle: &bundle}
 		attestations = append(attestations, &a)
 	}
 
diff --git a/pkg/cmd/attestation/verification/attestation_test.go b/pkg/cmd/attestation/verification/attestation_test.go
index 08f0ccfef..0795f053c 100644
--- a/pkg/cmd/attestation/verification/attestation_test.go
+++ b/pkg/cmd/attestation/verification/attestation_test.go
@@ -88,32 +88,16 @@ func TestGetLocalAttestations(t *testing.T) {
 		require.Nil(t, attestations)
 	})
 
-	t.Run("with non-existent bundle file and JSON file", func(t *testing.T) {
-		path := "../test/data/not-found-bundle.json"
-		attestations, err := GetLocalAttestations(path)
-
-		require.ErrorContains(t, err, "bundle could not be loaded from JSON file")
-		require.Nil(t, attestations)
-	})
-
-	t.Run("with non-existent bundle file and JSON lines file", func(t *testing.T) {
-		path := "../test/data/not-found-bundle.jsonl"
-		attestations, err := GetLocalAttestations(path)
-
-		require.ErrorContains(t, err, "bundles could not be loaded from JSON lines file")
-		require.Nil(t, attestations)
-	})
-
 	t.Run("with missing verification material", func(t *testing.T) {
 		path := "../test/data/github_provenance_demo-0.0.12-py3-none-any-bundle-missing-verification-material.jsonl"
 		_, err := GetLocalAttestations(path)
-		require.ErrorIs(t, err, bundle.ErrMissingVerificationMaterial)
+		require.ErrorContains(t, err, "missing verification material")
 	})
 
 	t.Run("with missing verification certificate", func(t *testing.T) {
 		path := "../test/data/github_provenance_demo-0.0.12-py3-none-any-bundle-missing-cert.jsonl"
 		_, err := GetLocalAttestations(path)
-		require.ErrorIs(t, err, bundle.ErrMissingBundleContent)
+		require.ErrorContains(t, err, "missing bundle content")
 	})
 }
 
diff --git a/pkg/cmd/attestation/verification/extensions.go b/pkg/cmd/attestation/verification/extensions.go
index 727feb72c..2ffb11a9d 100644
--- a/pkg/cmd/attestation/verification/extensions.go
+++ b/pkg/cmd/attestation/verification/extensions.go
@@ -6,20 +6,25 @@ import (
 	"strings"
 )
 
-func VerifyCertExtensions(results []*AttestationProcessingResult, tenant, owner, repo string) error {
+var (
+	GitHubOIDCIssuer       = "https://token.actions.githubusercontent.com"
+	GitHubTenantOIDCIssuer = "https://token.actions.%s.ghe.com"
+)
+
+func VerifyCertExtensions(results []*AttestationProcessingResult, tenant, owner, repo, issuer string) error {
 	if len(results) == 0 {
 		return errors.New("no attestations proccessing results")
 	}
 
 	for _, attestation := range results {
-		if err := verifyCertExtensions(attestation, tenant, owner, repo); err != nil {
+		if err := verifyCertExtensions(attestation, tenant, owner, repo, issuer); err != nil {
 			return err
 		}
 	}
 	return nil
 }
 
-func verifyCertExtensions(attestation *AttestationProcessingResult, tenant, owner, repo string) error {
+func verifyCertExtensions(attestation *AttestationProcessingResult, tenant, owner, repo, issuer string) error {
 	var want string
 
 	if tenant == "" {
@@ -46,5 +51,26 @@ func verifyCertExtensions(attestation *AttestationProcessingResult, tenant, owne
 		}
 	}
 
+	// if issuer is anything other than the default, use the user-provided value;
+	// otherwise, select the appropriate default based on the tenant
+	if issuer != GitHubOIDCIssuer {
+		want = issuer
+	} else {
+		if tenant != "" {
+			want = fmt.Sprintf(GitHubTenantOIDCIssuer, tenant)
+		} else {
+			want = GitHubOIDCIssuer
+		}
+	}
+
+	certIssuer := attestation.VerificationResult.Signature.Certificate.Extensions.Issuer
+	if !strings.EqualFold(want, certIssuer) {
+		if strings.Index(certIssuer, want+"/") == 0 {
+			return fmt.Errorf("expected Issuer to be %s, got %s -- if you have a custom OIDC issuer policy for your enterprise, use the --cert-oidc-issuer flag with your expected issuer", want, certIssuer)
+		} else {
+			return fmt.Errorf("expected Issuer to be %s, got %s", want, certIssuer)
+		}
+	}
+
 	return nil
 }
diff --git a/pkg/cmd/attestation/verification/extensions_test.go b/pkg/cmd/attestation/verification/extensions_test.go
index c86f2030a..5eb28829d 100644
--- a/pkg/cmd/attestation/verification/extensions_test.go
+++ b/pkg/cmd/attestation/verification/extensions_test.go
@@ -17,6 +17,7 @@ func TestVerifyCertExtensions(t *testing.T) {
 						Extensions: certificate.Extensions{
 							SourceRepositoryOwnerURI: "https://github.com/owner",
 							SourceRepositoryURI:      "https://github.com/owner/repo",
+							Issuer:                   "https://token.actions.githubusercontent.com",
 						},
 					},
 				},
@@ -25,32 +26,72 @@ func TestVerifyCertExtensions(t *testing.T) {
 	}
 
 	t.Run("VerifyCertExtensions with owner and repo", func(t *testing.T) {
-		err := VerifyCertExtensions(results, "", "owner", "owner/repo")
+		err := VerifyCertExtensions(results, "", "owner", "owner/repo", GitHubOIDCIssuer)
 		require.NoError(t, err)
 	})
 
 	t.Run("VerifyCertExtensions with owner and repo, but wrong tenant", func(t *testing.T) {
-		err := VerifyCertExtensions(results, "foo", "owner", "owner/repo")
+		err := VerifyCertExtensions(results, "foo", "owner", "owner/repo", GitHubOIDCIssuer)
 		require.ErrorContains(t, err, "expected SourceRepositoryOwnerURI to be https://foo.ghe.com/owner, got https://github.com/owner")
 	})
 
 	t.Run("VerifyCertExtensions with owner", func(t *testing.T) {
-		err := VerifyCertExtensions(results, "", "owner", "")
+		err := VerifyCertExtensions(results, "", "owner", "", GitHubOIDCIssuer)
 		require.NoError(t, err)
 	})
 
 	t.Run("VerifyCertExtensions with wrong owner", func(t *testing.T) {
-		err := VerifyCertExtensions(results, "", "wrong", "")
+		err := VerifyCertExtensions(results, "", "wrong", "", GitHubOIDCIssuer)
 		require.ErrorContains(t, err, "expected SourceRepositoryOwnerURI to be https://github.com/wrong, got https://github.com/owner")
 	})
 
 	t.Run("VerifyCertExtensions with wrong repo", func(t *testing.T) {
-		err := VerifyCertExtensions(results, "", "owner", "wrong")
+		err := VerifyCertExtensions(results, "", "owner", "wrong", GitHubOIDCIssuer)
 		require.ErrorContains(t, err, "expected SourceRepositoryURI to be https://github.com/wrong, got https://github.com/owner/repo")
 	})
+
+	t.Run("VerifyCertExtensions with wrong issuer", func(t *testing.T) {
+		err := VerifyCertExtensions(results, "", "owner", "", "wrong")
+		require.ErrorContains(t, err, "expected Issuer to be wrong, got https://token.actions.githubusercontent.com")
+	})
+}
+
+func TestVerifyCertExtensionsCustomizedIssuer(t *testing.T) {
+	results := []*AttestationProcessingResult{
+		{
+			VerificationResult: &verify.VerificationResult{
+				Signature: &verify.SignatureVerificationResult{
+					Certificate: &certificate.Summary{
+						Extensions: certificate.Extensions{
+							SourceRepositoryOwnerURI: "https://github.com/owner",
+							SourceRepositoryURI:      "https://github.com/owner/repo",
+							Issuer:                   "https://token.actions.githubusercontent.com/foo-bar",
+						},
+					},
+				},
+			},
+		},
+	}
+
+	t.Run("VerifyCertExtensions with exact issuer match", func(t *testing.T) {
+		err := VerifyCertExtensions(results, "", "owner", "owner/repo", "https://token.actions.githubusercontent.com/foo-bar")
+		require.NoError(t, err)
+	})
+
+	t.Run("VerifyCertExtensions with partial issuer match", func(t *testing.T) {
+		err := VerifyCertExtensions(results, "", "owner", "owner/repo", "https://token.actions.githubusercontent.com")
+		require.ErrorContains(t, err, "expected Issuer to be https://token.actions.githubusercontent.com, got https://token.actions.githubusercontent.com/foo-bar -- if you have a custom OIDC issuer")
+	})
+
+	t.Run("VerifyCertExtensions with wrong issuer", func(t *testing.T) {
+		err := VerifyCertExtensions(results, "", "owner", "", "wrong")
+		require.ErrorContains(t, err, "expected Issuer to be wrong, got https://token.actions.githubusercontent.com/foo-bar")
+	})
 }
 
 func TestVerifyTenancyCertExtensions(t *testing.T) {
+	defaultIssuer := GitHubOIDCIssuer
+
 	results := []*AttestationProcessingResult{
 		{
 			VerificationResult: &verify.VerificationResult{
@@ -59,6 +100,7 @@ func TestVerifyTenancyCertExtensions(t *testing.T) {
 						Extensions: certificate.Extensions{
 							SourceRepositoryOwnerURI: "https://foo.ghe.com/owner",
 							SourceRepositoryURI:      "https://foo.ghe.com/owner/repo",
+							Issuer:                   "https://token.actions.foo.ghe.com",
 						},
 					},
 				},
@@ -67,32 +109,42 @@ func TestVerifyTenancyCertExtensions(t *testing.T) {
 	}
 
 	t.Run("VerifyCertExtensions with owner and repo", func(t *testing.T) {
-		err := VerifyCertExtensions(results, "foo", "owner", "owner/repo")
+		err := VerifyCertExtensions(results, "foo", "owner", "owner/repo", defaultIssuer)
 		require.NoError(t, err)
 	})
 
 	t.Run("VerifyCertExtensions with owner and repo, no tenant", func(t *testing.T) {
-		err := VerifyCertExtensions(results, "", "owner", "owner/repo")
+		err := VerifyCertExtensions(results, "", "owner", "owner/repo", defaultIssuer)
 		require.ErrorContains(t, err, "expected SourceRepositoryOwnerURI to be https://github.com/owner, got https://foo.ghe.com/owner")
 	})
 
 	t.Run("VerifyCertExtensions with owner and repo, wrong tenant", func(t *testing.T) {
-		err := VerifyCertExtensions(results, "bar", "owner", "owner/repo")
+		err := VerifyCertExtensions(results, "bar", "owner", "owner/repo", defaultIssuer)
 		require.ErrorContains(t, err, "expected SourceRepositoryOwnerURI to be https://bar.ghe.com/owner, got https://foo.ghe.com/owner")
 	})
 
 	t.Run("VerifyCertExtensions with owner", func(t *testing.T) {
-		err := VerifyCertExtensions(results, "foo", "owner", "")
+		err := VerifyCertExtensions(results, "foo", "owner", "", defaultIssuer)
 		require.NoError(t, err)
 	})
 
 	t.Run("VerifyCertExtensions with wrong owner", func(t *testing.T) {
-		err := VerifyCertExtensions(results, "foo", "wrong", "")
+		err := VerifyCertExtensions(results, "foo", "wrong", "", defaultIssuer)
 		require.ErrorContains(t, err, "expected SourceRepositoryOwnerURI to be https://foo.ghe.com/wrong, got https://foo.ghe.com/owner")
 	})
 
 	t.Run("VerifyCertExtensions with wrong repo", func(t *testing.T) {
-		err := VerifyCertExtensions(results, "foo", "owner", "wrong")
+		err := VerifyCertExtensions(results, "foo", "owner", "wrong", defaultIssuer)
 		require.ErrorContains(t, err, "expected SourceRepositoryURI to be https://foo.ghe.com/wrong, got https://foo.ghe.com/owner/repo")
 	})
+
+	t.Run("VerifyCertExtensions with correct, non-default issuer", func(t *testing.T) {
+		err := VerifyCertExtensions(results, "foo", "owner", "owner/repo", "https://token.actions.foo.ghe.com")
+		require.NoError(t, err)
+	})
+
+	t.Run("VerifyCertExtensions with wrong issuer", func(t *testing.T) {
+		err := VerifyCertExtensions(results, "foo", "owner", "owner/repo", "wrong")
+		require.ErrorContains(t, err, "expected Issuer to be wrong, got https://token.actions.foo.ghe.com")
+	})
 }
diff --git a/pkg/cmd/attestation/verification/mock_verifier.go b/pkg/cmd/attestation/verification/mock_verifier.go
index 9943e6a97..cb3a4c061 100644
--- a/pkg/cmd/attestation/verification/mock_verifier.go
+++ b/pkg/cmd/attestation/verification/mock_verifier.go
@@ -34,6 +34,7 @@ func (v *MockSigstoreVerifier) Verify(attestations []*api.Attestation, policy ve
 						BuildSignerURI:           "https://github.com/github/example/.github/workflows/release.yml@refs/heads/main",
 						SourceRepositoryOwnerURI: "https://github.com/sigstore",
 						SourceRepositoryURI:      "https://github.com/sigstore/sigstore-js",
+						Issuer:                   "https://token.actions.githubusercontent.com",
 					},
 				},
 			},
diff --git a/pkg/cmd/attestation/verification/sigstore.go b/pkg/cmd/attestation/verification/sigstore.go
index ca4015db2..927eacf49 100644
--- a/pkg/cmd/attestation/verification/sigstore.go
+++ b/pkg/cmd/attestation/verification/sigstore.go
@@ -202,7 +202,7 @@ func (v *LiveSigstoreVerifier) Verify(attestations []*api.Attestation, policy ve
 			))
 
 			return &SigstoreResults{
-				Error: fmt.Errorf("verifying with issuer \"%s\"", issuer),
+				Error: fmt.Errorf("verifying with issuer \"%s\": %v", issuer, err),
 			}
 		}
 
diff --git a/pkg/cmd/attestation/verify/policy.go b/pkg/cmd/attestation/verify/policy.go
index 0c7a686c2..f41b2f66b 100644
--- a/pkg/cmd/attestation/verify/policy.go
+++ b/pkg/cmd/attestation/verify/policy.go
@@ -13,8 +13,6 @@ import (
 )
 
 const (
-	GitHubOIDCIssuer       = "https://token.actions.githubusercontent.com"
-	GitHubTenantOIDCIssuer = "https://token.actions.%s.ghe.com"
 	// represents the GitHub hosted runner in the certificate RunnerEnvironment extension
 	GitHubRunner = "github-hosted"
 	hostRegex    = `^[a-zA-Z0-9-]+\.[a-zA-Z0-9-]+.*$`
@@ -51,7 +49,8 @@ func buildCertificateIdentityOption(opts *Options, runnerEnv string) (verify.Pol
 		return nil, err
 	}
 
-	issuerMatcher, err := verify.NewIssuerMatcher(opts.OIDCIssuer, "")
+	// Accept any issuer, we will verify the issuer as part of the extension verification
+	issuerMatcher, err := verify.NewIssuerMatcher("", ".*")
 	if err != nil {
 		return nil, err
 	}
diff --git a/pkg/cmd/attestation/verify/policy_test.go b/pkg/cmd/attestation/verify/policy_test.go
index 89f989b45..ae1e52955 100644
--- a/pkg/cmd/attestation/verify/policy_test.go
+++ b/pkg/cmd/attestation/verify/policy_test.go
@@ -22,7 +22,6 @@ func TestBuildPolicy(t *testing.T) {
 
 	opts := &Options{
 		ArtifactPath: artifactPath,
-		OIDCIssuer:   GitHubOIDCIssuer,
 		Owner:        "sigstore",
 		SANRegex:     "^https://github.com/sigstore/",
 	}
diff --git a/pkg/cmd/attestation/verify/verify.go b/pkg/cmd/attestation/verify/verify.go
index 9e508e994..17b8a84d1 100644
--- a/pkg/cmd/attestation/verify/verify.go
+++ b/pkg/cmd/attestation/verify/verify.go
@@ -151,7 +151,6 @@ func NewVerifyCmd(f *cmdutil.Factory, runF func(*Options) error) *cobra.Command
 				}
 				config.TrustDomain = td
 				opts.Tenant = tenant
-				opts.OIDCIssuer = fmt.Sprintf(GitHubTenantOIDCIssuer, tenant)
 			}
 
 			// set policy flags based on what has been provided
@@ -192,7 +191,7 @@ func NewVerifyCmd(f *cmdutil.Factory, runF func(*Options) error) *cobra.Command
 	verifyCmd.Flags().StringVarP(&opts.SignerRepo, "signer-repo", "", "", "Repository of reusable workflow that signed attestation in the format <owner>/<repo>")
 	verifyCmd.Flags().StringVarP(&opts.SignerWorkflow, "signer-workflow", "", "", "Workflow that signed attestation in the format [host/]<owner>/<repo>/<path>/<to>/<workflow>")
 	verifyCmd.MarkFlagsMutuallyExclusive("cert-identity", "cert-identity-regex", "signer-repo", "signer-workflow")
-	verifyCmd.Flags().StringVarP(&opts.OIDCIssuer, "cert-oidc-issuer", "", GitHubOIDCIssuer, "Issuer of the OIDC token")
+	verifyCmd.Flags().StringVarP(&opts.OIDCIssuer, "cert-oidc-issuer", "", verification.GitHubOIDCIssuer, "Issuer of the OIDC token")
 	verifyCmd.Flags().StringVarP(&opts.Hostname, "hostname", "", "", "Configure host to use")
 
 	return verifyCmd
@@ -269,7 +268,7 @@ func runVerify(opts *Options) error {
 	}
 
 	// Verify extensions
-	if err := verification.VerifyCertExtensions(sigstoreRes.VerifyResults, opts.Tenant, opts.Owner, opts.Repo); err != nil {
+	if err := verification.VerifyCertExtensions(sigstoreRes.VerifyResults, opts.Tenant, opts.Owner, opts.Repo, opts.OIDCIssuer); err != nil {
 		opts.Logger.Println(opts.Logger.ColorScheme.Red("✗ Verification failed"))
 		return err
 	}
diff --git a/pkg/cmd/attestation/verify/verify_integration_test.go b/pkg/cmd/attestation/verify/verify_integration_test.go
index df24b31c4..4b0f0adfb 100644
--- a/pkg/cmd/attestation/verify/verify_integration_test.go
+++ b/pkg/cmd/attestation/verify/verify_integration_test.go
@@ -38,7 +38,7 @@ func TestVerifyIntegration(t *testing.T) {
 		DigestAlgorithm:  "sha512",
 		Logger:           logger,
 		OCIClient:        oci.NewLiveClient(),
-		OIDCIssuer:       GitHubOIDCIssuer,
+		OIDCIssuer:       verification.GitHubOIDCIssuer,
 		Owner:            "sigstore",
 		SANRegex:         "^https://github.com/sigstore/",
 		SigstoreVerifier: verification.NewLiveSigstoreVerifier(sigstoreConfig),
@@ -180,7 +180,7 @@ func TestVerifyIntegrationReusableWorkflow(t *testing.T) {
 		DigestAlgorithm:  "sha256",
 		Logger:           logger,
 		OCIClient:        oci.NewLiveClient(),
-		OIDCIssuer:       GitHubOIDCIssuer,
+		OIDCIssuer:       verification.GitHubOIDCIssuer,
 		SigstoreVerifier: verification.NewLiveSigstoreVerifier(sigstoreConfig),
 	}
 
@@ -269,7 +269,7 @@ func TestVerifyIntegrationReusableWorkflowSignerWorkflow(t *testing.T) {
 		DigestAlgorithm:  "sha256",
 		Logger:           logger,
 		OCIClient:        oci.NewLiveClient(),
-		OIDCIssuer:       GitHubOIDCIssuer,
+		OIDCIssuer:       verification.GitHubOIDCIssuer,
 		Owner:            "malancas",
 		Repo:             "malancas/attest-demo",
 		SigstoreVerifier: verification.NewLiveSigstoreVerifier(sigstoreConfig),
diff --git a/pkg/cmd/attestation/verify/verify_test.go b/pkg/cmd/attestation/verify/verify_test.go
index 3ff309922..8b211fe4c 100644
--- a/pkg/cmd/attestation/verify/verify_test.go
+++ b/pkg/cmd/attestation/verify/verify_test.go
@@ -43,7 +43,7 @@ func TestNewVerifyCmd(t *testing.T) {
 			},
 		},
 	}
-	testReg.Register(httpmock.REST(http.MethodGet, "meta"),
+	testReg.Register(httpmock.REST(http.MethodGet, "api/v3/meta"),
 		httpmock.StatusJSONResponse(200, &metaResp))
 
 	f := &cmdutil.Factory{
@@ -71,7 +71,7 @@ func TestNewVerifyCmd(t *testing.T) {
 				BundlePath:       test.NormalizeRelativePath("../test/data/sigstore-js-2.1.0-bundle.json"),
 				DigestAlgorithm:  "sha384",
 				Limit:            30,
-				OIDCIssuer:       GitHubOIDCIssuer,
+				OIDCIssuer:       verification.GitHubOIDCIssuer,
 				Owner:            "sigstore",
 				SigstoreVerifier: verification.NewMockSigstoreVerifier(t),
 				Hostname:         "github.com",
@@ -86,7 +86,7 @@ func TestNewVerifyCmd(t *testing.T) {
 				BundlePath:       test.NormalizeRelativePath("../test/data/sigstore-js-2.1.0-bundle.json"),
 				DigestAlgorithm:  "sha256",
 				Limit:            30,
-				OIDCIssuer:       GitHubOIDCIssuer,
+				OIDCIssuer:       verification.GitHubOIDCIssuer,
 				Owner:            "sigstore",
 				SANRegex:         "(?i)^https://github.com/sigstore/",
 				SigstoreVerifier: verification.NewMockSigstoreVerifier(t),
@@ -102,7 +102,7 @@ func TestNewVerifyCmd(t *testing.T) {
 				BundlePath:       test.NormalizeRelativePath("../test/data/sigstore-js-2.1.0-bundle.json"),
 				DigestAlgorithm:  "sha256",
 				Limit:            30,
-				OIDCIssuer:       "https://token.actions.foo.ghe.com",
+				OIDCIssuer:       verification.GitHubOIDCIssuer,
 				Owner:            "sigstore",
 				SANRegex:         "(?i)^https://foo.ghe.com/sigstore/",
 				SigstoreVerifier: verification.NewMockSigstoreVerifier(t),
@@ -118,7 +118,7 @@ func TestNewVerifyCmd(t *testing.T) {
 				BundlePath:       test.NormalizeRelativePath("../test/data/sigstore-js-2.1.0-bundle.json"),
 				DigestAlgorithm:  "sha256",
 				Limit:            30,
-				OIDCIssuer:       GitHubOIDCIssuer,
+				OIDCIssuer:       verification.GitHubOIDCIssuer,
 				Owner:            "sigstore",
 				SANRegex:         "(?i)^https://github.com/sigstore/",
 				SigstoreVerifier: verification.NewMockSigstoreVerifier(t),
@@ -134,7 +134,7 @@ func TestNewVerifyCmd(t *testing.T) {
 				BundlePath:       test.NormalizeRelativePath("../test/data/sigstore-js-2.1.0-bundle.json"),
 				DigestAlgorithm:  "sha512",
 				Limit:            30,
-				OIDCIssuer:       GitHubOIDCIssuer,
+				OIDCIssuer:       verification.GitHubOIDCIssuer,
 				Owner:            "sigstore",
 				SANRegex:         "(?i)^https://github.com/sigstore/",
 				SigstoreVerifier: verification.NewMockSigstoreVerifier(t),
@@ -148,7 +148,7 @@ func TestNewVerifyCmd(t *testing.T) {
 			wants: Options{
 				ArtifactPath:     test.NormalizeRelativePath("../test/data/sigstore-js-2.1.0.tgz"),
 				DigestAlgorithm:  "sha256",
-				OIDCIssuer:       GitHubOIDCIssuer,
+				OIDCIssuer:       verification.GitHubOIDCIssuer,
 				Owner:            "sigstore",
 				Limit:            30,
 				SANRegex:         "(?i)^https://github.com/sigstore/",
@@ -163,7 +163,7 @@ func TestNewVerifyCmd(t *testing.T) {
 			wants: Options{
 				ArtifactPath:     artifactPath,
 				DigestAlgorithm:  "sha256",
-				OIDCIssuer:       GitHubOIDCIssuer,
+				OIDCIssuer:       verification.GitHubOIDCIssuer,
 				Owner:            "sigstore",
 				Repo:             "sigstore/sigstore-js",
 				Limit:            30,
@@ -179,7 +179,7 @@ func TestNewVerifyCmd(t *testing.T) {
 				ArtifactPath:     artifactPath,
 				DigestAlgorithm:  "sha256",
 				Limit:            30,
-				OIDCIssuer:       GitHubOIDCIssuer,
+				OIDCIssuer:       verification.GitHubOIDCIssuer,
 				Owner:            "sigstore",
 				SANRegex:         "(?i)^https://github.com/sigstore/",
 				SigstoreVerifier: verification.NewMockSigstoreVerifier(t),
@@ -193,7 +193,7 @@ func TestNewVerifyCmd(t *testing.T) {
 			wants: Options{
 				ArtifactPath:     artifactPath,
 				DigestAlgorithm:  "sha256",
-				OIDCIssuer:       GitHubOIDCIssuer,
+				OIDCIssuer:       verification.GitHubOIDCIssuer,
 				Owner:            "sigstore",
 				Limit:            101,
 				SANRegex:         "(?i)^https://github.com/sigstore/",
@@ -208,7 +208,7 @@ func TestNewVerifyCmd(t *testing.T) {
 			wants: Options{
 				ArtifactPath:     artifactPath,
 				DigestAlgorithm:  "sha256",
-				OIDCIssuer:       GitHubOIDCIssuer,
+				OIDCIssuer:       verification.GitHubOIDCIssuer,
 				Owner:            "sigstore",
 				Limit:            0,
 				SANRegex:         "(?i)^https://github.com/sigstore/",
@@ -224,7 +224,7 @@ func TestNewVerifyCmd(t *testing.T) {
 				ArtifactPath:     artifactPath,
 				DigestAlgorithm:  "sha256",
 				Limit:            30,
-				OIDCIssuer:       GitHubOIDCIssuer,
+				OIDCIssuer:       verification.GitHubOIDCIssuer,
 				Owner:            "sigstore",
 				SAN:              "https://github.com/sigstore/",
 				SANRegex:         "(?i)^https://github.com/sigstore/",
@@ -241,7 +241,7 @@ func TestNewVerifyCmd(t *testing.T) {
 				BundlePath:       bundlePath,
 				DigestAlgorithm:  "sha256",
 				Limit:            30,
-				OIDCIssuer:       GitHubOIDCIssuer,
+				OIDCIssuer:       verification.GitHubOIDCIssuer,
 				Owner:            "sigstore",
 				SANRegex:         "(?i)^https://github.com/sigstore/",
 				SigstoreVerifier: verification.NewMockSigstoreVerifier(t),
@@ -331,7 +331,7 @@ func TestJSONOutput(t *testing.T) {
 		APIClient:        api.NewTestClient(),
 		Logger:           io.NewHandler(testIO),
 		OCIClient:        oci.MockClient{},
-		OIDCIssuer:       GitHubOIDCIssuer,
+		OIDCIssuer:       verification.GitHubOIDCIssuer,
 		Owner:            "sigstore",
 		SANRegex:         "^https://github.com/sigstore/",
 		SigstoreVerifier: verification.NewMockSigstoreVerifier(t),
@@ -354,7 +354,7 @@ func TestRunVerify(t *testing.T) {
 		APIClient:        api.NewTestClient(),
 		Logger:           logger,
 		OCIClient:        oci.MockClient{},
-		OIDCIssuer:       GitHubOIDCIssuer,
+		OIDCIssuer:       verification.GitHubOIDCIssuer,
 		Owner:            "sigstore",
 		SANRegex:         "^https://github.com/sigstore/",
 		SigstoreVerifier: verification.NewMockSigstoreVerifier(t),
@@ -474,7 +474,7 @@ func TestRunVerify(t *testing.T) {
 			APIClient:        api.NewTestClient(),
 			DigestAlgorithm:  "sha512",
 			Logger:           logger,
-			OIDCIssuer:       GitHubOIDCIssuer,
+			OIDCIssuer:       verification.GitHubOIDCIssuer,
 			Owner:            "sigstore",
 			SAN:              SigstoreSanValue,
 			SigstoreVerifier: verification.NewMockSigstoreVerifier(t),
diff --git a/pkg/cmd/extension/manager_test.go b/pkg/cmd/extension/manager_test.go
index 5de4e15cb..6d9b63124 100644
--- a/pkg/cmd/extension/manager_test.go
+++ b/pkg/cmd/extension/manager_test.go
@@ -912,58 +912,6 @@ func TestManager_Install_binary_unsupported(t *testing.T) {
 	assert.Equal(t, "", stderr.String())
 }
 
-func TestManager_Install_rosetta_fallback_not_found(t *testing.T) {
-	repo := ghrepo.NewWithHost("owner", "gh-bin-ext", "example.com")
-
-	reg := httpmock.Registry{}
-	defer reg.Verify(t)
-	client := http.Client{Transport: &reg}
-
-	reg.Register(
-		httpmock.REST("GET", "api/v3/repos/owner/gh-bin-ext/releases/latest"),
-		httpmock.JSONResponse(
-			release{
-				Assets: []releaseAsset{
-					{
-						Name:   "gh-bin-ext-darwin-amd64",
-						APIURL: "https://example.com/release/cool",
-					},
-				},
-			}))
-	reg.Register(
-		httpmock.REST("GET", "api/v3/repos/owner/gh-bin-ext/releases/latest"),
-		httpmock.JSONResponse(
-			release{
-				Tag: "v1.0.1",
-				Assets: []releaseAsset{
-					{
-						Name:   "gh-bin-ext-darwin-amd64",
-						APIURL: "https://example.com/release/cool",
-					},
-				},
-			}))
-
-	ios, _, stdout, stderr := iostreams.Test()
-	tempDir := t.TempDir()
-
-	m := newTestManager(tempDir, &client, nil, ios)
-	m.platform = func() (string, string) {
-		return "darwin-arm64", ""
-	}
-
-	originalHasRosetta := hasRosetta
-	t.Cleanup(func() { hasRosetta = originalHasRosetta })
-	hasRosetta = func() bool {
-		return false
-	}
-
-	err := m.Install(repo, "")
-	assert.EqualError(t, err, "gh-bin-ext unsupported for darwin-arm64. Install Rosetta with `softwareupdate --install-rosetta` to use the available darwin-amd64 binary, or open an issue: `gh issue create -R owner/gh-bin-ext -t'Support darwin-arm64'`")
-
-	assert.Equal(t, "", stdout.String())
-	assert.Equal(t, "", stderr.String())
-}
-
 func TestManager_Install_binary(t *testing.T) {
 	repo := ghrepo.NewWithHost("owner", "gh-bin-ext", "example.com")
 
@@ -1028,80 +976,6 @@ func TestManager_Install_binary(t *testing.T) {
 	assert.Equal(t, "", stderr.String())
 }
 
-func TestManager_Install_amd64_when_supported(t *testing.T) {
-	repo := ghrepo.NewWithHost("owner", "gh-bin-ext", "example.com")
-
-	reg := httpmock.Registry{}
-	defer reg.Verify(t)
-	client := http.Client{Transport: &reg}
-
-	reg.Register(
-		httpmock.REST("GET", "api/v3/repos/owner/gh-bin-ext/releases/latest"),
-		httpmock.JSONResponse(
-			release{
-				Assets: []releaseAsset{
-					{
-						Name:   "gh-bin-ext-darwin-amd64",
-						APIURL: "https://example.com/release/cool",
-					},
-				},
-			}))
-	reg.Register(
-		httpmock.REST("GET", "api/v3/repos/owner/gh-bin-ext/releases/latest"),
-		httpmock.JSONResponse(
-			release{
-				Tag: "v1.0.1",
-				Assets: []releaseAsset{
-					{
-						Name:   "gh-bin-ext-darwin-amd64",
-						APIURL: "https://example.com/release/cool",
-					},
-				},
-			}))
-	reg.Register(
-		httpmock.REST("GET", "release/cool"),
-		httpmock.StringResponse("FAKE BINARY"))
-
-	ios, _, stdout, stderr := iostreams.Test()
-	tempDir := t.TempDir()
-
-	m := newTestManager(tempDir, &client, nil, ios)
-	m.platform = func() (string, string) {
-		return "darwin-arm64", ""
-	}
-
-	originalHasRosetta := hasRosetta
-	t.Cleanup(func() { hasRosetta = originalHasRosetta })
-	hasRosetta = func() bool {
-		return true
-	}
-
-	err := m.Install(repo, "")
-	assert.NoError(t, err)
-
-	manifest, err := os.ReadFile(filepath.Join(tempDir, "extensions/gh-bin-ext", manifestName))
-	assert.NoError(t, err)
-
-	var bm binManifest
-	err = yaml.Unmarshal(manifest, &bm)
-	assert.NoError(t, err)
-
-	assert.Equal(t, binManifest{
-		Name:  "gh-bin-ext",
-		Owner: "owner",
-		Host:  "example.com",
-		Tag:   "v1.0.1",
-		Path:  filepath.Join(tempDir, "extensions/gh-bin-ext/gh-bin-ext"),
-	}, bm)
-
-	fakeBin, err := os.ReadFile(filepath.Join(tempDir, "extensions/gh-bin-ext/gh-bin-ext"))
-	assert.NoError(t, err)
-	assert.Equal(t, "FAKE BINARY", string(fakeBin))
-
-	assert.Equal(t, "", stdout.String())
-	assert.Equal(t, "", stderr.String())
-}
-
 func TestManager_repo_not_found(t *testing.T) {
 	repo := ghrepo.NewWithHost("owner", "gh-bin-ext", "example.com")