github.com/Racer159/jackal@v0.32.7-0.20240401174413-0bd2339e4f2e/src/pkg/utils/cosign.go (about)

     1  // Forked from https://github.com/sigstore/cosign/blob/v1.7.1/pkg/sget/sget.go
     2  // SPDX-License-Identifier: Apache-2.0
     3  // SPDX-FileCopyrightText: 2021-Present The Jackal Authors
     4  
     5  // Package utils provides generic utility functions.
     6  package utils
     7  
     8  import (
     9  	"context"
    10  	"fmt"
    11  	"io"
    12  	"os"
    13  	"strings"
    14  
    15  	"github.com/Racer159/jackal/src/config"
    16  	"github.com/Racer159/jackal/src/config/lang"
    17  	"github.com/Racer159/jackal/src/pkg/message"
    18  	"github.com/defenseunicorns/pkg/helpers"
    19  	"github.com/google/go-containerregistry/pkg/authn"
    20  	"github.com/google/go-containerregistry/pkg/name"
    21  	"github.com/google/go-containerregistry/pkg/v1/remote"
    22  	"github.com/pkg/errors"
    23  
    24  	"github.com/sigstore/cosign/v2/cmd/cosign/cli/fulcio"
    25  	"github.com/sigstore/cosign/v2/cmd/cosign/cli/options"
    26  	"github.com/sigstore/cosign/v2/cmd/cosign/cli/sign"
    27  	"github.com/sigstore/cosign/v2/cmd/cosign/cli/verify"
    28  	"github.com/sigstore/cosign/v2/pkg/cosign"
    29  	ociremote "github.com/sigstore/cosign/v2/pkg/oci/remote"
    30  	sigs "github.com/sigstore/cosign/v2/pkg/signature"
    31  
    32  	// Register the provider-specific plugins
    33  	_ "github.com/sigstore/sigstore/pkg/signature/kms/aws"
    34  	_ "github.com/sigstore/sigstore/pkg/signature/kms/azure"
    35  	_ "github.com/sigstore/sigstore/pkg/signature/kms/gcp"
    36  	_ "github.com/sigstore/sigstore/pkg/signature/kms/hashivault"
    37  )
    38  
    39  // Sget performs a cosign signature verification on a given image using the specified public key.
    40  func Sget(ctx context.Context, image, key string, out io.Writer) error {
    41  	message.Warnf(lang.WarnSGetDeprecation)
    42  
    43  	// If this is a DefenseUnicorns package, use an internal sget public key
    44  	if strings.HasPrefix(image, fmt.Sprintf("%s://defenseunicorns", helpers.SGETURLScheme)) {
    45  		os.Setenv("DU_SGET_KEY", config.CosignPublicKey)
    46  		key = "env://DU_SGET_KEY"
    47  	}
    48  
    49  	// Remove the custom protocol header from the url
    50  	image = strings.TrimPrefix(image, helpers.SGETURLPrefix)
    51  
    52  	message.Debugf("utils.Sget: image=%s, key=%s", image, key)
    53  
    54  	spinner := message.NewProgressSpinner("Loading signed file %s", image)
    55  	defer spinner.Stop()
    56  
    57  	ref, err := name.ParseReference(image)
    58  	if err != nil {
    59  		return err
    60  	}
    61  
    62  	opts := []remote.Option{
    63  		remote.WithAuthFromKeychain(authn.DefaultKeychain),
    64  		remote.WithContext(ctx),
    65  	}
    66  
    67  	co := &cosign.CheckOpts{
    68  		ClaimVerifier:      cosign.SimpleClaimVerifier,
    69  		RegistryClientOpts: []ociremote.Option{ociremote.WithRemoteOptions(opts...)},
    70  	}
    71  	if _, ok := ref.(name.Tag); ok {
    72  		if key == "" && !options.EnableExperimental() {
    73  			return errors.New("public key must be specified when fetching by tag, you must fetch by digest or supply a public key")
    74  		}
    75  	}
    76  	// Overwrite "ref" with a digest to avoid a race where we verify the tag,
    77  	// and then access the file through the tag.  This has a race where we
    78  	// might download content that isn't what we verified.
    79  	ref, err = ociremote.ResolveDigest(ref, co.RegistryClientOpts...)
    80  	if err != nil {
    81  		return err
    82  	}
    83  
    84  	if key != "" {
    85  		pub, err := sigs.LoadPublicKey(ctx, key)
    86  		if err != nil {
    87  			return err
    88  		}
    89  		co.SigVerifier = pub
    90  	}
    91  
    92  	// NB: There are only 2 kinds of verification right now:
    93  	// 1. You gave us the public key explicitly to verify against so co.SigVerifier is non-nil or,
    94  	// 2. We're going to find an x509 certificate on the signature and verify against Fulcio root trust
    95  	// TODO(nsmith5): Refactor this verification logic to pass back _how_ verification
    96  	// was performed so we don't need to use this fragile logic here.
    97  	fulcioVerified := co.SigVerifier == nil
    98  
    99  	co.RootCerts, err = fulcio.GetRoots()
   100  	if err != nil {
   101  		return fmt.Errorf("getting Fulcio roots: %w", err)
   102  	}
   103  
   104  	co.IntermediateCerts, err = fulcio.GetIntermediates()
   105  	if err != nil {
   106  		return fmt.Errorf("getting Fulcio intermediates: %w", err)
   107  	}
   108  
   109  	co.IgnoreTlog = true
   110  	co.IgnoreSCT = true
   111  	co.Offline = true
   112  
   113  	verifyMsg := fmt.Sprintf("%s cosign verified: ", image)
   114  
   115  	sp, bundleVerified, err := cosign.VerifyImageSignatures(ctx, ref, co)
   116  	if err != nil {
   117  		return err
   118  	}
   119  
   120  	if co.ClaimVerifier != nil {
   121  		if co.Annotations != nil {
   122  			verifyMsg += "ANNOTATIONS. "
   123  		}
   124  		verifyMsg += "CLAIMS. "
   125  	}
   126  
   127  	if bundleVerified {
   128  		verifyMsg += "TRANSPARENCY LOG (BUNDLED). "
   129  	} else if co.RekorClient != nil {
   130  		verifyMsg += "TRANSPARENCY LOG. "
   131  	}
   132  
   133  	if co.SigVerifier != nil {
   134  		verifyMsg += "PUBLIC KEY. "
   135  	}
   136  
   137  	if fulcioVerified {
   138  		spinner.Updatef("KEYLESS (OIDC). ")
   139  	}
   140  
   141  	for _, sig := range sp {
   142  		if cert, err := sig.Cert(); err == nil && cert != nil {
   143  			message.Debugf("Certificate subject: %s", cert.Subject)
   144  
   145  			ce := cosign.CertExtensions{Cert: cert}
   146  			if issuerURL := ce.GetIssuer(); issuerURL != "" {
   147  				message.Debugf("Certificate issuer URL: %s", issuerURL)
   148  			}
   149  		}
   150  
   151  		p, err := sig.Payload()
   152  		if err != nil {
   153  			spinner.Errorf(err, "Error getting payload")
   154  			return err
   155  		}
   156  		message.Debug(string(p))
   157  	}
   158  
   159  	// TODO(mattmoor): Depending on what this is, use the higher-level stuff.
   160  	img, err := remote.Image(ref, opts...)
   161  	if err != nil {
   162  		return err
   163  	}
   164  	layers, err := img.Layers()
   165  	if err != nil {
   166  		return err
   167  	}
   168  	if len(layers) != 1 {
   169  		return errors.New("invalid artifact")
   170  	}
   171  	rc, err := layers[0].Compressed()
   172  	if err != nil {
   173  		return err
   174  	}
   175  
   176  	_, err = io.Copy(out, rc)
   177  	spinner.Successf(verifyMsg)
   178  
   179  	return err
   180  }
   181  
   182  // CosignVerifyBlob verifies the jackal.yaml.sig was signed with the key provided by the flag
   183  func CosignVerifyBlob(blobRef string, sigRef string, keyPath string) error {
   184  	keyOptions := options.KeyOpts{KeyRef: keyPath}
   185  	cmd := &verify.VerifyBlobCmd{
   186  		KeyOpts:    keyOptions,
   187  		SigRef:     sigRef,
   188  		IgnoreSCT:  true,
   189  		Offline:    true,
   190  		IgnoreTlog: true,
   191  	}
   192  	err := cmd.Exec(context.TODO(), blobRef)
   193  	if err == nil {
   194  		message.Successf("Package signature validated!")
   195  	}
   196  
   197  	return err
   198  }
   199  
   200  // CosignSignBlob signs the provide binary and returns the signature
   201  func CosignSignBlob(blobPath string, outputSigPath string, keyPath string, passwordFunc func(bool) ([]byte, error)) ([]byte, error) {
   202  	rootOptions := &options.RootOptions{Verbose: false, Timeout: options.DefaultTimeout}
   203  
   204  	keyOptions := options.KeyOpts{KeyRef: keyPath,
   205  		PassFunc: passwordFunc}
   206  	b64 := true
   207  	outputCertificate := ""
   208  	tlogUpload := false
   209  
   210  	sig, err := sign.SignBlobCmd(rootOptions,
   211  		keyOptions,
   212  		blobPath,
   213  		b64,
   214  		outputSigPath,
   215  		outputCertificate,
   216  		tlogUpload)
   217  
   218  	return sig, err
   219  }
   220  
   221  // GetCosignArtifacts returns signatures and attestations for the given image
   222  func GetCosignArtifacts(image string) (cosignList []string, err error) {
   223  	var cosignArtifactList []string
   224  	var nameOpts []name.Option
   225  	ref, err := name.ParseReference(image, nameOpts...)
   226  
   227  	if err != nil {
   228  		return cosignArtifactList, err
   229  	}
   230  
   231  	var remoteOpts []ociremote.Option
   232  	simg, _ := ociremote.SignedEntity(ref, remoteOpts...)
   233  	if simg == nil {
   234  		return cosignArtifactList, nil
   235  	}
   236  	// Errors are dogsled because these functions always return a name.Tag which we can check for layers
   237  	sigRef, _ := ociremote.SignatureTag(ref, remoteOpts...)
   238  	attRef, _ := ociremote.AttestationTag(ref, remoteOpts...)
   239  
   240  	sigs, err := simg.Signatures()
   241  	if err != nil {
   242  		return cosignArtifactList, err
   243  	}
   244  	layers, err := sigs.Layers()
   245  	if err != nil {
   246  		return cosignArtifactList, err
   247  	}
   248  	if len(layers) > 0 {
   249  		cosignArtifactList = append(cosignArtifactList, sigRef.String())
   250  	}
   251  
   252  	atts, err := simg.Attestations()
   253  	if err != nil {
   254  		return cosignArtifactList, err
   255  	}
   256  	layers, err = atts.Layers()
   257  	if err != nil {
   258  		return cosignArtifactList, err
   259  	}
   260  	if len(layers) > 0 {
   261  		cosignArtifactList = append(cosignArtifactList, attRef.String())
   262  	}
   263  	return cosignArtifactList, nil
   264  }