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 }