diff --git a/cmd/cosign/cli/dockerfile.go b/cmd/cosign/cli/dockerfile.go index 70fe83223a9..7c27fc01d47 100644 --- a/cmd/cosign/cli/dockerfile.go +++ b/cmd/cosign/cli/dockerfile.go @@ -90,6 +90,7 @@ Shell-like variables in the Dockerfile's FROM lines will be substituted with val KeyRef: o.Key, CertRef: o.CertVerify.Cert, CertEmail: o.CertVerify.CertEmail, + CertOidcIssuer: o.CertVerify.CertOidcIssuer, Sk: o.SecurityKey.Use, Slot: o.SecurityKey.Slot, Output: o.Output, diff --git a/cmd/cosign/cli/manifest.go b/cmd/cosign/cli/manifest.go index 65cbcc4378d..14fcfb7f173 100644 --- a/cmd/cosign/cli/manifest.go +++ b/cmd/cosign/cli/manifest.go @@ -85,6 +85,7 @@ against the transparency log.`, KeyRef: o.Key, CertRef: o.CertVerify.Cert, CertEmail: o.CertVerify.CertEmail, + CertOidcIssuer: o.CertVerify.CertOidcIssuer, Sk: o.SecurityKey.Use, Slot: o.SecurityKey.Slot, Output: o.Output, diff --git a/cmd/cosign/cli/options/certificate.go b/cmd/cosign/cli/options/certificate.go index 2335e660edd..782d6a8bb59 100644 --- a/cmd/cosign/cli/options/certificate.go +++ b/cmd/cosign/cli/options/certificate.go @@ -20,8 +20,9 @@ import ( // CertVerifyOptions is the wrapper for certificate verification. type CertVerifyOptions struct { - Cert string - CertEmail string + Cert string + CertEmail string + CertOidcIssuer string } var _ Interface = (*RekorOptions)(nil) @@ -33,4 +34,7 @@ func (o *CertVerifyOptions) AddFlags(cmd *cobra.Command) { cmd.Flags().StringVar(&o.CertEmail, "cert-email", "", "the email expected in a valid Fulcio certificate") + + cmd.Flags().StringVar(&o.CertOidcIssuer, "cert-oidc-issuer", "", + "the OIDC issuer expected in a valid Fulcio certificate, e.g. https://token.actions.githubusercontent.com or https://oauth2.sigstore.dev/auth") } diff --git a/cmd/cosign/cli/verify.go b/cmd/cosign/cli/verify.go index 90f66f19961..04a50b9e882 100644 --- a/cmd/cosign/cli/verify.go +++ b/cmd/cosign/cli/verify.go @@ -92,6 +92,7 @@ against the transparency log.`, KeyRef: o.Key, CertRef: o.CertVerify.Cert, CertEmail: o.CertVerify.CertEmail, + CertOidcIssuer: o.CertVerify.CertOidcIssuer, Sk: o.SecurityKey.Use, Slot: o.SecurityKey.Slot, Output: o.Output, @@ -161,6 +162,7 @@ against the transparency log.`, CheckClaims: o.CheckClaims, CertRef: o.CertVerify.Cert, CertEmail: o.CertVerify.CertEmail, + CertOidcIssuer: o.CertVerify.CertOidcIssuer, KeyRef: o.Key, Sk: o.SecurityKey.Use, Slot: o.SecurityKey.Slot, @@ -238,7 +240,7 @@ The blob may be specified as a path to a file or - for stdin.`, RekorURL: o.Rekor.URL, } if err := verify.VerifyBlobCmd(cmd.Context(), ko, o.CertVerify.Cert, - o.CertVerify.CertEmail, o.Signature, args[0]); err != nil { + o.CertVerify.CertEmail, o.CertVerify.CertOidcIssuer, o.Signature, args[0]); err != nil { return errors.Wrapf(err, "verifying blob %s", args) } return nil diff --git a/cmd/cosign/cli/verify/verify.go b/cmd/cosign/cli/verify/verify.go index 0e71a60bdcf..cbf1c0b705e 100644 --- a/cmd/cosign/cli/verify/verify.go +++ b/cmd/cosign/cli/verify/verify.go @@ -48,19 +48,20 @@ import ( // nolint type VerifyCommand struct { options.RegistryOptions - CheckClaims bool - KeyRef string - CertRef string - CertEmail string - Sk bool - Slot string - Output string - RekorURL string - Attachment string - Annotations sigs.AnnotationsMap - SignatureRef string - HashAlgorithm crypto.Hash - LocalImage bool + CheckClaims bool + KeyRef string + CertRef string + CertEmail string + CertOidcIssuer string + Sk bool + Slot string + Output string + RekorURL string + Attachment string + Annotations sigs.AnnotationsMap + SignatureRef string + HashAlgorithm crypto.Hash + LocalImage bool } // Exec runs the verification command @@ -92,6 +93,7 @@ func (c *VerifyCommand) Exec(ctx context.Context, images []string) (err error) { Annotations: c.Annotations.Annotations, RegistryClientOpts: ociremoteOpts, CertEmail: c.CertEmail, + CertOidcIssuer: c.CertOidcIssuer, SignatureRef: c.SignatureRef, } if c.CheckClaims { diff --git a/cmd/cosign/cli/verify/verify_attestation.go b/cmd/cosign/cli/verify/verify_attestation.go index b836999bf12..a734218dd16 100644 --- a/cmd/cosign/cli/verify/verify_attestation.go +++ b/cmd/cosign/cli/verify/verify_attestation.go @@ -47,17 +47,18 @@ import ( // nolint type VerifyAttestationCommand struct { options.RegistryOptions - CheckClaims bool - CertRef string - CertEmail string - KeyRef string - Sk bool - Slot string - Output string - RekorURL string - PredicateType string - Policies []string - LocalImage bool + CheckClaims bool + CertRef string + CertEmail string + CertOidcIssuer string + KeyRef string + Sk bool + Slot string + Output string + RekorURL string + PredicateType string + Policies []string + LocalImage bool } // Exec runs the verification command @@ -77,6 +78,7 @@ func (c *VerifyAttestationCommand) Exec(ctx context.Context, images []string) (e co := &cosign.CheckOpts{ RegistryClientOpts: ociremoteOpts, CertEmail: c.CertEmail, + CertOidcIssuer: c.CertOidcIssuer, } if c.CheckClaims { co.ClaimVerifier = cosign.IntotoSubjectClaimVerifier diff --git a/cmd/cosign/cli/verify/verify_blob.go b/cmd/cosign/cli/verify/verify_blob.go index b38bca25724..26a5762d221 100644 --- a/cmd/cosign/cli/verify/verify_blob.go +++ b/cmd/cosign/cli/verify/verify_blob.go @@ -54,7 +54,7 @@ func isb64(data []byte) bool { } // nolint -func VerifyBlobCmd(ctx context.Context, ko sign.KeyOpts, certRef, certEmail, sigRef, blobRef string) error { +func VerifyBlobCmd(ctx context.Context, ko sign.KeyOpts, certRef, certEmail, certOidcIssuer, sigRef, blobRef string) error { var verifier signature.Verifier var cert *x509.Certificate @@ -128,8 +128,9 @@ func VerifyBlobCmd(ctx context.Context, ko sign.KeyOpts, certRef, certEmail, sig } co := &cosign.CheckOpts{ - RootCerts: fulcio.GetRoots(), - CertEmail: certEmail, + RootCerts: fulcio.GetRoots(), + CertEmail: certEmail, + CertOidcIssuer: certOidcIssuer, } cert = certs[0] verifier, err = cosign.ValidateAndUnpackCert(cert, co) diff --git a/doc/cosign_dockerfile_verify.md b/doc/cosign_dockerfile_verify.md index 12aa7bb2094..8626d7266e3 100644 --- a/doc/cosign_dockerfile_verify.md +++ b/doc/cosign_dockerfile_verify.md @@ -59,6 +59,7 @@ cosign dockerfile verify [flags] --base-image-only only verify the base image (the last FROM image in the Dockerfile) --cert string path to the public certificate --cert-email string the email expected in a valid Fulcio certificate + --cert-oidc-issuer string the OIDC issuer expected in a valid Fulcio certificate, e.g. https://token.actions.githubusercontent.com or https://oauth2.sigstore.dev/auth --check-claims whether to check the claims found (default true) -h, --help help for verify --k8s-keychain whether to use the kubernetes keychain instead of the default keychain (supports workload identity). diff --git a/doc/cosign_manifest_verify.md b/doc/cosign_manifest_verify.md index 8c954459852..4ebd713130c 100644 --- a/doc/cosign_manifest_verify.md +++ b/doc/cosign_manifest_verify.md @@ -53,6 +53,7 @@ cosign manifest verify [flags] --attachment-tag-prefix [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] optional custom prefix to use for attached image tags. Attachment images are tagged as: [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] --cert string path to the public certificate --cert-email string the email expected in a valid Fulcio certificate + --cert-oidc-issuer string the OIDC issuer expected in a valid Fulcio certificate, e.g. https://token.actions.githubusercontent.com or https://oauth2.sigstore.dev/auth --check-claims whether to check the claims found (default true) -h, --help help for verify --k8s-keychain whether to use the kubernetes keychain instead of the default keychain (supports workload identity). diff --git a/doc/cosign_verify-attestation.md b/doc/cosign_verify-attestation.md index 3ffea92aada..f64e3bdbbc6 100644 --- a/doc/cosign_verify-attestation.md +++ b/doc/cosign_verify-attestation.md @@ -57,6 +57,7 @@ cosign verify-attestation [flags] --attachment-tag-prefix [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] optional custom prefix to use for attached image tags. Attachment images are tagged as: [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] --cert string path to the public certificate --cert-email string the email expected in a valid Fulcio certificate + --cert-oidc-issuer string the OIDC issuer expected in a valid Fulcio certificate, e.g. https://token.actions.githubusercontent.com or https://oauth2.sigstore.dev/auth --check-claims whether to check the claims found (default true) -h, --help help for verify-attestation --k8s-keychain whether to use the kubernetes keychain instead of the default keychain (supports workload identity). diff --git a/doc/cosign_verify-blob.md b/doc/cosign_verify-blob.md index c4e0bf6ca5b..903b3b6b302 100644 --- a/doc/cosign_verify-blob.md +++ b/doc/cosign_verify-blob.md @@ -65,6 +65,7 @@ cosign verify-blob [flags] --attachment-tag-prefix [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] optional custom prefix to use for attached image tags. Attachment images are tagged as: [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] --cert string path to the public certificate --cert-email string the email expected in a valid Fulcio certificate + --cert-oidc-issuer string the OIDC issuer expected in a valid Fulcio certificate, e.g. https://token.actions.githubusercontent.com or https://oauth2.sigstore.dev/auth -h, --help help for verify-blob --k8s-keychain whether to use the kubernetes keychain instead of the default keychain (supports workload identity). --key string path to the public key file, KMS URI or Kubernetes Secret diff --git a/doc/cosign_verify.md b/doc/cosign_verify.md index 8deb69a2f8b..43aa86fee4b 100644 --- a/doc/cosign_verify.md +++ b/doc/cosign_verify.md @@ -66,6 +66,7 @@ cosign verify [flags] --attachment-tag-prefix [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] optional custom prefix to use for attached image tags. Attachment images are tagged as: [AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName] --cert string path to the public certificate --cert-email string the email expected in a valid Fulcio certificate + --cert-oidc-issuer string the OIDC issuer expected in a valid Fulcio certificate, e.g. https://token.actions.githubusercontent.com or https://oauth2.sigstore.dev/auth --check-claims whether to check the claims found (default true) -h, --help help for verify --k8s-keychain whether to use the kubernetes keychain instead of the default keychain (supports workload identity). diff --git a/pkg/cosign/verify.go b/pkg/cosign/verify.go index 1dcf110cb60..cb397579687 100644 --- a/pkg/cosign/verify.go +++ b/pkg/cosign/verify.go @@ -21,6 +21,7 @@ import ( "crypto/ecdsa" "crypto/sha256" "crypto/x509" + "encoding/asn1" "encoding/base64" "encoding/hex" "encoding/json" @@ -75,6 +76,8 @@ type CheckOpts struct { RootCerts *x509.CertPool // CertEmail is the email expected for a certificate to be valid. The empty string means any certificate can be valid. CertEmail string + // CertOidcIssuer is the OIDC issuer expected for a certificate to be valid. The empty string means any certificate can be valid. + CertOidcIssuer string // SignatureRef is the reference to the signature file SignatureRef string @@ -160,6 +163,18 @@ func ValidateAndUnpackCert(cert *x509.Certificate, co *CheckOpts) (signature.Ver return nil, errors.New("expected email not found in certificate") } } + if co.CertOidcIssuer != "" { + issuer := "" + for _, ext := range cert.Extensions { + if ext.Id.Equal(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 1}) { + issuer = string(ext.Value) + break + } + } + if issuer != co.CertOidcIssuer { + return nil, errors.New("expected oidc issuer not found in certificate") + } + } return verifier, nil } @@ -697,7 +712,6 @@ func TrustedCert(cert *x509.Certificate, roots *x509.CertPool) error { CurrentTime: cert.NotBefore, Roots: roots, KeyUsages: []x509.ExtKeyUsage{ - x509.ExtKeyUsage(x509.KeyUsageDigitalSignature), x509.ExtKeyUsageCodeSigning, }, }); err != nil { diff --git a/pkg/cosign/verify_test.go b/pkg/cosign/verify_test.go index 9f39a292ac6..adf4999fda2 100644 --- a/pkg/cosign/verify_test.go +++ b/pkg/cosign/verify_test.go @@ -17,6 +17,7 @@ package cosign import ( "context" "crypto" + "crypto/x509" "encoding/base64" "encoding/json" "io" @@ -26,7 +27,9 @@ import ( "github.com/pkg/errors" "github.com/secure-systems-lab/go-securesystemslib/dsse" "github.com/sigstore/cosign/pkg/types" + "github.com/sigstore/cosign/test" "github.com/sigstore/sigstore/pkg/signature" + "github.com/stretchr/testify/require" ) type mockVerifier struct { @@ -89,3 +92,107 @@ func Test_verifyOCIAttestation(t *testing.T) { t.Error("verifyOCIAttestation() expected invalid payload type error, got nil") } } + +func TestValidateAndUnpackCertSuccess(t *testing.T) { + subject := "email@email" + oidcIssuer := "https://accounts.google.com" + + rootCert, rootKey, _ := test.GenerateRootCa() + leafCert, _, _ := test.GenerateLeafCert(subject, oidcIssuer, rootCert, rootKey) + + rootPool := x509.NewCertPool() + rootPool.AddCert(rootCert) + + co := &CheckOpts{ + RootCerts: rootPool, + CertEmail: subject, + CertOidcIssuer: oidcIssuer, + } + + _, err := ValidateAndUnpackCert(leafCert, co) + if err != nil { + t.Errorf("ValidateAndUnpackCert expected no error, got err = %v", err) + } +} + +func TestValidateAndUnpackCertSuccessAllowAllValues(t *testing.T) { + subject := "email@email" + oidcIssuer := "https://accounts.google.com" + + rootCert, rootKey, _ := test.GenerateRootCa() + leafCert, _, _ := test.GenerateLeafCert(subject, oidcIssuer, rootCert, rootKey) + + rootPool := x509.NewCertPool() + rootPool.AddCert(rootCert) + + co := &CheckOpts{ + RootCerts: rootPool, + } + + _, err := ValidateAndUnpackCert(leafCert, co) + if err != nil { + t.Errorf("ValidateAndUnpackCert expected no error, got err = %v", err) + } +} + +func TestValidateAndUnpackCertInvalidRoot(t *testing.T) { + subject := "email@email" + oidcIssuer := "https://accounts.google.com" + + rootCert, rootKey, _ := test.GenerateRootCa() + leafCert, _, _ := test.GenerateLeafCert(subject, oidcIssuer, rootCert, rootKey) + + otherRoot, _, _ := test.GenerateRootCa() + + rootPool := x509.NewCertPool() + rootPool.AddCert(otherRoot) + + co := &CheckOpts{ + RootCerts: rootPool, + CertEmail: subject, + CertOidcIssuer: oidcIssuer, + } + + _, err := ValidateAndUnpackCert(leafCert, co) + require.Contains(t, err.Error(), "certificate signed by unknown authority") +} + +func TestValidateAndUnpackCertInvalidOidcIssuer(t *testing.T) { + subject := "email@email" + oidcIssuer := "https://accounts.google.com" + + rootCert, rootKey, _ := test.GenerateRootCa() + leafCert, _, _ := test.GenerateLeafCert(subject, oidcIssuer, rootCert, rootKey) + + rootPool := x509.NewCertPool() + rootPool.AddCert(rootCert) + + co := &CheckOpts{ + RootCerts: rootPool, + CertEmail: subject, + CertOidcIssuer: "other", + } + + _, err := ValidateAndUnpackCert(leafCert, co) + require.Contains(t, err.Error(), "expected oidc issuer not found in certificate") +} + +func TestValidateAndUnpackCertInvalidEmail(t *testing.T) { + subject := "email@email" + oidcIssuer := "https://accounts.google.com" + + rootCert, rootKey, _ := test.GenerateRootCa() + leafCert, _, _ := test.GenerateLeafCert(subject, oidcIssuer, rootCert, rootKey) + + rootPool := x509.NewCertPool() + rootPool.AddCert(rootCert) + + co := &CheckOpts{ + RootCerts: rootPool, + CertEmail: "other", + CertOidcIssuer: oidcIssuer, + } + + _, err := ValidateAndUnpackCert(leafCert, co) + require.Contains(t, err.Error(), "expected email not found in certificate") +} diff --git a/test/cert_utils.go b/test/cert_utils.go new file mode 100644 index 00000000000..25e444ba95a --- /dev/null +++ b/test/cert_utils.go @@ -0,0 +1,146 @@ +// Copyright 2022 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 test + +import ( + "crypto" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/x509" + "crypto/x509/pkix" + "encoding/asn1" + "math/big" + "time" +) + +/* +To use: + +rootCert, rootKey, _ := GenerateRootCa() +subCert, subKey, _ := GenerateSubordinateCa(rootCert, rootKey) +leafCert, _, _ := GenerateLeafCert("subject", "oidc-issuer", subCert, subKey) + +roots := x509.NewCertPool() +subs := x509.NewCertPool() +roots.AddCert(rootCert) +subs.AddCert(subCert) +opts := x509.VerifyOptions{ + Roots: roots, + Intermediates: subs, + KeyUsages: []x509.ExtKeyUsage{ + x509.ExtKeyUsageCodeSigning, + }, +} +_, err := leafCert.Verify(opts) +*/ + +func createCertificate(template *x509.Certificate, parent *x509.Certificate, pub interface{}, priv crypto.Signer) (*x509.Certificate, error) { + certBytes, err := x509.CreateCertificate(rand.Reader, template, parent, pub, priv) + if err != nil { + return nil, err + } + + cert, err := x509.ParseCertificate(certBytes) + if err != nil { + return nil, err + } + return cert, nil +} + +func GenerateRootCa() (*x509.Certificate, *ecdsa.PrivateKey, error) { + rootTemplate := &x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{ + CommonName: "sigstore", + Organization: []string{"sigstore.dev"}, + }, + NotBefore: time.Now().Add(-5 * time.Minute), + NotAfter: time.Now().Add(5 * time.Hour), + KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign, + BasicConstraintsValid: true, + IsCA: true, + } + + priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return nil, nil, err + } + + cert, err := createCertificate(rootTemplate, rootTemplate, &priv.PublicKey, priv) + if err != nil { + return nil, nil, err + } + + return cert, priv, nil +} + +func GenerateSubordinateCa(rootTemplate *x509.Certificate, rootPriv crypto.Signer) (*x509.Certificate, *ecdsa.PrivateKey, error) { + subTemplate := &x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{ + CommonName: "sigstore-sub", + Organization: []string{"sigstore.dev"}, + }, + NotBefore: time.Now().Add(-2 * time.Minute), + NotAfter: time.Now().Add(2 * time.Hour), + KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning}, + BasicConstraintsValid: true, + IsCA: true, + } + + priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return nil, nil, err + } + + cert, err := createCertificate(subTemplate, rootTemplate, &priv.PublicKey, rootPriv) + if err != nil { + return nil, nil, err + } + + return cert, priv, nil +} + +func GenerateLeafCert(subject string, oidcIssuer string, parentTemplate *x509.Certificate, parentPriv crypto.Signer) (*x509.Certificate, *ecdsa.PrivateKey, error) { + certTemplate := &x509.Certificate{ + SerialNumber: big.NewInt(1), + EmailAddresses: []string{subject}, + NotBefore: time.Now().Add(-1 * time.Minute), + NotAfter: time.Now().Add(time.Hour), + KeyUsage: x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageCodeSigning}, + IsCA: false, + ExtraExtensions: []pkix.Extension{{ + // OID for OIDC Issuer extension + Id: asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 1}, + Critical: false, + Value: []byte(oidcIssuer), + }}, + } + + priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return nil, nil, err + } + + cert, err := createCertificate(certTemplate, parentTemplate, &priv.PublicKey, parentPriv) + if err != nil { + return nil, nil, err + } + + return cert, priv, nil +} diff --git a/test/e2e_test.go b/test/e2e_test.go index 0d48719ed29..edc63c064e0 100644 --- a/test/e2e_test.go +++ b/test/e2e_test.go @@ -449,8 +449,8 @@ func TestSignBlob(t *testing.T) { KeyRef: pubKeyPath2, } // Verify should fail on a bad input - mustErr(cliverify.VerifyBlobCmd(ctx, ko1, "" /*certRef*/, "" /*certEmail*/, "badsig", blob), t) - mustErr(cliverify.VerifyBlobCmd(ctx, ko2, "" /*certRef*/, "" /*certEmail*/, "badsig", blob), t) + mustErr(cliverify.VerifyBlobCmd(ctx, ko1, "" /*certRef*/, "" /*certEmail*/, "" /*certOidcIssuer*/, "badsig", blob), t) + mustErr(cliverify.VerifyBlobCmd(ctx, ko2, "" /*certRef*/, "" /*certEmail*/, "" /*certOidcIssuer*/, "badsig", blob), t) // Now sign the blob with one key ko := sign.KeyOpts{ @@ -462,8 +462,8 @@ func TestSignBlob(t *testing.T) { t.Fatal(err) } // Now verify should work with that one, but not the other - must(cliverify.VerifyBlobCmd(ctx, ko1, "" /*certRef*/, "" /*certEmail*/, string(sig), bp), t) - mustErr(cliverify.VerifyBlobCmd(ctx, ko2, "" /*certRef*/, "" /*certEmail*/, string(sig), bp), t) + must(cliverify.VerifyBlobCmd(ctx, ko1, "" /*certRef*/, "" /*certEmail*/, "" /*certOidcIssuer*/, string(sig), bp), t) + mustErr(cliverify.VerifyBlobCmd(ctx, ko2, "" /*certRef*/, "" /*certEmail*/, "" /*certOidcIssuer*/, string(sig), bp), t) } func TestGenerate(t *testing.T) {