sigs.k8s.io/release-sdk@v0.11.1-0.20240417074027-8061fb5e4952/sign/sign.go (about) 1 /* 2 Copyright 2022 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package sign 18 19 import ( 20 "context" 21 "encoding/base64" 22 "errors" 23 "fmt" 24 "net/http" 25 "os" 26 "strings" 27 "sync" 28 "time" 29 30 "github.com/google/go-containerregistry/pkg/authn" 31 "github.com/google/go-containerregistry/pkg/crane" 32 "github.com/google/go-containerregistry/pkg/logs" 33 "github.com/google/go-containerregistry/pkg/name" 34 "github.com/google/go-containerregistry/pkg/v1/remote" 35 "github.com/google/go-containerregistry/pkg/v1/remote/transport" 36 "github.com/jellydator/ttlcache/v3" 37 "github.com/nozzle/throttler" 38 cliOpts "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" 39 sigs "github.com/sigstore/cosign/v2/pkg/signature" 40 signatureoptions "github.com/sigstore/sigstore/pkg/signature/options" 41 "github.com/sirupsen/logrus" 42 "k8s.io/apimachinery/pkg/util/wait" 43 "sigs.k8s.io/release-utils/hash" 44 ) 45 46 // Signer is the main structure to be used by API consumers. 47 type Signer struct { 48 impl impl 49 options *Options 50 signedRefs *ttlcache.Cache[string, bool] // key: imageRef, value: isSigned 51 parsedRefs *ttlcache.Cache[string, name.Reference] // key: imageRef, value: parsedRef 52 transports *ttlcache.Cache[name.Repository, http.RoundTripper] // key: repo of parsedRef, value: transport 53 signedObjs *ttlcache.Cache[string, *SignedObject] // key: imageRef, value: signed object 54 } 55 56 // New returns a new Signer instance. 57 func New(options *Options) *Signer { 58 if options == nil { 59 options = Default() 60 } 61 62 if options.Logger == nil { 63 options.Logger = logrus.New() 64 } 65 66 if options.Verbose { 67 logs.Debug.SetOutput(os.Stderr) 68 options.Logger.SetLevel(logrus.DebugLevel) 69 } 70 71 signer := &Signer{ 72 impl: &defaultImpl{}, 73 options: options, 74 signedRefs: ttlcache.New( 75 ttlcache.WithTTL[string, bool](options.CacheTimeout), 76 ttlcache.WithCapacity[string, bool](options.MaxCacheItems), 77 ), 78 parsedRefs: ttlcache.New( 79 ttlcache.WithTTL[string, name.Reference](options.CacheTimeout), 80 ttlcache.WithCapacity[string, name.Reference](options.MaxCacheItems), 81 ), 82 transports: ttlcache.New( 83 ttlcache.WithTTL[name.Repository, http.RoundTripper](options.CacheTimeout), 84 ttlcache.WithCapacity[name.Repository, http.RoundTripper](options.MaxCacheItems), 85 ), 86 signedObjs: ttlcache.New( 87 ttlcache.WithTTL[string, *SignedObject](options.CacheTimeout), 88 ttlcache.WithCapacity[string, *SignedObject](options.MaxCacheItems), 89 ), 90 } 91 92 go signer.signedRefs.Start() 93 go signer.parsedRefs.Start() 94 go signer.transports.Start() 95 go signer.signedObjs.Start() 96 97 return signer 98 } 99 100 // SetImpl can be used to set the internal implementation, which is mainly used 101 // for testing. 102 func (s *Signer) SetImpl(impl impl) { 103 s.impl = impl 104 } 105 106 // log returns the internally set logger. 107 func (s *Signer) log() *logrus.Logger { 108 return s.options.Logger 109 } 110 111 func (s *Signer) UploadBlob(path string) error { 112 s.log().Infof("Uploading blob: %s", path) 113 114 // TODO: unimplemented 115 116 return nil 117 } 118 119 // SignImage can be used to sign any provided container image reference by 120 // using keyless signing. 121 func (s *Signer) SignImage(reference string) (object *SignedObject, err error) { 122 return s.SignImageWithOptions(s.options, reference) 123 } 124 125 // SignImageWithOptions can be used to sign any provided container image 126 // reference by using the provided custom options. 127 func (s *Signer) SignImageWithOptions(options *Options, reference string) (object *SignedObject, err error) { 128 s.log().Infof("Signing reference: %s", reference) 129 130 // Ensure options to sign are correct 131 if err := options.verifySignOptions(); err != nil { 132 return nil, fmt.Errorf("checking signing options: %w", err) 133 } 134 s.log().Debugf("Using options: %+v", options) 135 136 ctx, cancel := options.context() 137 defer cancel() 138 139 // If we don't have a key path, we must ensure we can get an OIDC 140 // token or there is no way to sign. Depending on the options set, 141 // we may get the ID token from the cosign providers 142 identityToken := "" 143 if options.PrivateKeyPath == "" { 144 tok, err := s.identityToken(ctx) 145 if err != nil { 146 return nil, fmt.Errorf("getting identity token for keyless signing: %w", err) 147 } 148 identityToken = tok 149 if identityToken == "" { 150 return nil, errors.New( 151 "no private key or identity token are available, unable to sign", 152 ) 153 } 154 } 155 156 ko := cliOpts.KeyOpts{ 157 KeyRef: options.PrivateKeyPath, 158 IDToken: identityToken, 159 PassFunc: options.PassFunc, 160 FulcioURL: cliOpts.DefaultFulcioURL, 161 RekorURL: cliOpts.DefaultRekorURL, 162 OIDCIssuer: cliOpts.DefaultOIDCIssuerURL, 163 SkipConfirmation: true, 164 165 InsecureSkipFulcioVerify: false, 166 } 167 168 signOpts := cliOpts.SignOptions{ 169 OutputSignature: options.OutputSignaturePath, 170 OutputCertificate: options.OutputCertificatePath, 171 Upload: true, 172 Recursive: options.Recursive, 173 TlogUpload: true, 174 SkipConfirmation: true, 175 AnnotationOptions: cliOpts.AnnotationOptions{ 176 Annotations: options.Annotations, 177 }, 178 Registry: cliOpts.RegistryOptions{ 179 AllowInsecure: options.AllowInsecure, 180 }, 181 SignContainerIdentity: options.SignContainerIdentity, 182 } 183 184 images := []string{reference} 185 186 if err := s.impl.SignImageInternal(options.ToCosignRootOptions(), ko, signOpts, images); err != nil { 187 return nil, fmt.Errorf("sign reference: %s: %w", reference, err) 188 } 189 190 // remove the reference from the cache in case we called IsImageSigned before signing. 191 s.signedRefs.Delete(reference) 192 193 // After signing, registry consistency may not be there right 194 // away. Retry the image verification if it fails 195 // ref: https://github.com/kubernetes-sigs/promo-tools/issues/536 196 waitErr := wait.ExponentialBackoff(wait.Backoff{ 197 Duration: 500 * time.Millisecond, 198 Factor: 1.5, 199 Steps: int(options.MaxRetries), 200 }, func() (bool, error) { 201 object, err = s.VerifyImage(images[0]) 202 if err != nil { 203 err = fmt.Errorf("verifying reference %s: %w", images[0], err) 204 return false, nil 205 } 206 return true, nil 207 }) 208 209 if waitErr != nil { 210 return nil, fmt.Errorf("retrying image verification: %w: %w", waitErr, err) 211 } 212 213 return object, err 214 } 215 216 // SignFile can be used to sign any provided file path by using keyless 217 // signing. 218 func (s *Signer) SignFile(path string) (*SignedObject, error) { 219 s.log().Infof("Signing file path: %s", path) 220 221 ctx, cancel := s.options.context() 222 defer cancel() 223 224 // If we don't have a key path, we must ensure we can get an OIDC 225 // token or there is no way to sign. Depending on the options set, 226 // we may get the ID token from the cosign providers 227 identityToken := "" 228 if s.options.PrivateKeyPath == "" { 229 tok, err := s.identityToken(ctx) 230 if err != nil { 231 return nil, fmt.Errorf("getting identity token for keyless signing: %w", err) 232 } 233 identityToken = tok 234 if identityToken == "" { 235 return nil, errors.New( 236 "no private key or identity token are available, unable to sign", 237 ) 238 } 239 } 240 241 ko := cliOpts.KeyOpts{ 242 KeyRef: s.options.PrivateKeyPath, 243 IDToken: identityToken, 244 PassFunc: s.options.PassFunc, 245 FulcioURL: cliOpts.DefaultFulcioURL, 246 RekorURL: cliOpts.DefaultRekorURL, 247 OIDCIssuer: cliOpts.DefaultOIDCIssuerURL, 248 SkipConfirmation: true, 249 250 InsecureSkipFulcioVerify: false, 251 } 252 253 if s.options.OutputCertificatePath == "" { 254 s.options.OutputCertificatePath = fmt.Sprintf("%s.cert", path) 255 } 256 if s.options.OutputSignaturePath == "" { 257 s.options.OutputSignaturePath = fmt.Sprintf("%s.sig", path) 258 } 259 260 fileSHA, err := hash.SHA256ForFile(path) 261 if err != nil { 262 return nil, fmt.Errorf("file retrieve sha256: %s: %w", path, err) 263 } 264 265 if err := s.impl.SignFileInternal( 266 s.options.ToCosignRootOptions(), ko, path, true, 267 s.options.OutputSignaturePath, s.options.OutputCertificatePath, true, 268 ); err != nil { 269 return nil, fmt.Errorf("sign file: %s: %w", path, err) 270 } 271 272 verifyKo := ko 273 verifyKo.KeyRef = s.options.PublicKeyPath 274 275 certOpts := cliOpts.CertVerifyOptions{ 276 CertIdentity: s.options.CertIdentity, 277 CertIdentityRegexp: s.options.CertIdentityRegexp, 278 CertOidcIssuer: s.options.CertOidcIssuer, 279 CertOidcIssuerRegexp: s.options.CertOidcIssuerRegexp, 280 IgnoreSCT: s.options.IgnoreSCT, 281 } 282 283 if s.options.PublicKeyPath == "" { 284 certOpts.Cert = s.options.OutputCertificatePath 285 } 286 287 err = s.impl.VerifyFileInternal(ctx, verifyKo, certOpts, s.options.OutputSignaturePath, path) 288 if err != nil { 289 return nil, fmt.Errorf("verifying signed file: %s: %w", path, err) 290 } 291 292 return &SignedObject{ 293 file: &SignedFile{ 294 path: path, 295 sha256: fileSHA, 296 signaturePath: s.options.OutputSignaturePath, 297 certificatePath: s.options.OutputCertificatePath, 298 }, 299 }, nil 300 } 301 302 // VerifyImage can be used to validate any provided container image reference by 303 // using keyless signing. It ignores unsigned images. 304 func (s *Signer) VerifyImage(reference string) (*SignedObject, error) { 305 s.log().Infof("Verifying reference: %s", reference) 306 307 item := s.signedObjs.Get(reference) 308 if item != nil { 309 return item.Value(), nil 310 } 311 312 res, err := s.VerifyImages(reference) 313 if err != nil { 314 return nil, fmt.Errorf("verify image: %w", err) 315 } 316 317 o, ok := res.Load(reference) 318 if !ok { 319 // Probably not signed 320 return nil, nil 321 } 322 323 obj, ok := o.(*SignedObject) 324 if !ok { 325 return nil, fmt.Errorf("interface conversion error, result is not a *SignedObject: %v", o) 326 } 327 328 s.signedObjs.Set(reference, obj, ttlcache.DefaultTTL) 329 330 return obj, nil 331 } 332 333 // VerifyImages can be used to validate any provided container image reference 334 // list by using keyless signing. It ignores unsigned images. Returns a sync map 335 // where the key is the ref (string) and the value is the *SignedObject 336 func (s *Signer) VerifyImages(refs ...string) (*sync.Map, error) { 337 s.log().Debug("Checking cache") 338 res := &sync.Map{} 339 unknownRefs := []string{} 340 for _, ref := range refs { 341 item := s.signedObjs.Get(ref) 342 if item != nil { 343 res.Store(ref, item.Value()) 344 continue 345 } 346 347 unknownRefs = append(unknownRefs, ref) 348 } 349 350 if len(unknownRefs) == 0 { 351 s.log().Debug("All references already available in cache") 352 return res, nil 353 } 354 355 s.log().Infof("Verifying %d references", len(unknownRefs)) 356 357 // checking whether the image being verified has a signature 358 // if there is no signature, we should skip 359 // ref: https://kubernetes.slack.com/archives/CJH2GBF7Y/p1647459428848859?thread_ts=1647428695.280269&cid=CJH2GBF7Y 360 ctx, cancel := s.options.context() 361 defer cancel() 362 imagesSigned, err := s.impl.ImagesSigned(ctx, s, unknownRefs...) 363 if err != nil { 364 return nil, fmt.Errorf("verify if images are signed: %w", err) 365 } 366 unknownRefs = []string{} 367 imagesSigned.Range(func(key, value any) bool { 368 ref, ok := key.(string) 369 if !ok { 370 logrus.Errorf("Interface conversion failed: key is not a string: %v", key) 371 return false 372 } 373 isSigned, ok := value.(bool) 374 if !ok { 375 logrus.Errorf("Interface conversion failed: value is not a bool: %v", value) 376 return false 377 } 378 379 if isSigned { 380 unknownRefs = append(unknownRefs, ref) 381 } 382 383 return true 384 }) 385 386 t := throttler.New(int(s.options.MaxWorkers), len(unknownRefs)) 387 for _, ref := range unknownRefs { 388 go func(ref string) { 389 ctx, cancel := s.options.context() 390 defer cancel() 391 392 certOpts := cliOpts.CertVerifyOptions{ 393 CertIdentity: s.options.CertIdentity, 394 CertIdentityRegexp: s.options.CertIdentityRegexp, 395 CertOidcIssuer: s.options.CertOidcIssuer, 396 CertOidcIssuerRegexp: s.options.CertOidcIssuerRegexp, 397 IgnoreSCT: s.options.IgnoreSCT, 398 } 399 400 _, err = s.impl.VerifyImageInternal(ctx, certOpts, s.options.PublicKeyPath, []string{ref}, s.options.IgnoreTlog) 401 if err != nil { 402 t.Done(fmt.Errorf("verify image reference: %s: %w", ref, err)) 403 return 404 } 405 406 var parsedRef name.Reference 407 item := s.parsedRefs.Get(ref) 408 if item != nil { 409 parsedRef = item.Value() 410 } else { 411 parsedRef, err = s.impl.ParseReference(ref) 412 if err != nil { 413 t.Done(fmt.Errorf("parsing reference: %s: %w", ref, err)) 414 return 415 } 416 } 417 418 digest, err := s.impl.Digest(parsedRef.String()) 419 if err != nil { 420 t.Done(fmt.Errorf("getting the reference digest for %s: %w", ref, err)) 421 return 422 } 423 424 obj := &SignedObject{ 425 image: &SignedImage{ 426 digest: digest, 427 reference: parsedRef.String(), 428 signature: repoDigestToSig(parsedRef.Context(), digest), 429 }, 430 file: nil, 431 } 432 433 res.Store(ref, obj) 434 s.signedObjs.Set(ref, obj, ttlcache.DefaultTTL) 435 t.Done(nil) 436 }(ref) 437 438 if t.Throttle() > 0 { 439 break 440 } 441 } 442 443 s.log().Debug("Done verifying references") 444 445 if err := t.Err(); err != nil { 446 return res, fmt.Errorf("verifying references: %w", err) 447 } 448 449 return res, nil 450 } 451 452 // VerifyFile can be used to validate any provided file path. 453 // If no signed entry is found we skip the file without errors. 454 func (s *Signer) VerifyFile(path string, ignoreTLog bool) (*SignedObject, error) { 455 s.log().Infof("Verifying file path: %s", path) 456 457 ko := cliOpts.KeyOpts{ 458 KeyRef: s.options.PublicKeyPath, 459 RekorURL: cliOpts.DefaultRekorURL, 460 } 461 462 if s.options.OutputCertificatePath == "" { 463 s.options.OutputCertificatePath = fmt.Sprintf("%s.cert", path) 464 } 465 if s.options.OutputSignaturePath == "" { 466 s.options.OutputSignaturePath = fmt.Sprintf("%s.sig", path) 467 } 468 469 certOpts := cliOpts.CertVerifyOptions{ 470 CertIdentity: s.options.CertIdentity, 471 CertIdentityRegexp: s.options.CertIdentityRegexp, 472 CertOidcIssuer: s.options.CertOidcIssuer, 473 CertOidcIssuerRegexp: s.options.CertOidcIssuerRegexp, 474 IgnoreSCT: s.options.IgnoreSCT, 475 } 476 477 if s.options.PublicKeyPath == "" { 478 certOpts.Cert = s.options.OutputCertificatePath 479 } 480 481 ctx, cancel := s.options.context() 482 defer cancel() 483 484 isSigned := false 485 if !ignoreTLog { 486 var err error 487 isSigned, err = s.IsFileSigned(ctx, path) 488 if err != nil { 489 return nil, fmt.Errorf("checking if file is signed. file: %s, error: %w", path, err) 490 } 491 } 492 493 fileSHA, err := hash.SHA256ForFile(path) 494 if err != nil { 495 return nil, fmt.Errorf("file retrieve sha256 error: %s: %w", path, err) 496 } 497 498 if !isSigned && !ignoreTLog { 499 s.log().Infof("Skipping unsigned file: %s", path) 500 return nil, nil 501 } 502 503 err = s.impl.VerifyFileInternal(ctx, ko, certOpts, s.options.OutputSignaturePath, path) 504 if err != nil { 505 return nil, fmt.Errorf("verify file reference: %s: %w", path, err) 506 } 507 508 return &SignedObject{ 509 file: &SignedFile{ 510 path: path, 511 sha256: fileSHA, 512 signaturePath: s.options.OutputSignaturePath, 513 certificatePath: s.options.OutputCertificatePath, 514 }, 515 }, nil 516 } 517 518 // IsImageSigned takes an image reference and returns true if there are 519 // signatures available for it. It makes no signature verification, only 520 // checks to see if more than one signature is available. 521 func (s *Signer) IsImageSigned(imageRef string) (bool, error) { 522 item := s.signedRefs.Get(imageRef) 523 if item != nil { 524 return item.Value(), nil 525 } 526 527 res, err := s.impl.ImagesSigned(context.Background(), s, imageRef) 528 if err != nil { 529 return false, fmt.Errorf("check if image is signed: %w", err) 530 } 531 signed, ok := res.Load(imageRef) 532 if !ok { 533 return false, errors.New("ref is not part of result") 534 } 535 signedBool, ok := signed.(bool) 536 if !ok { 537 return false, fmt.Errorf("interface conversion error, result is not a bool: %v", signed) 538 } 539 540 s.signedRefs.Set(imageRef, signedBool, ttlcache.DefaultTTL) 541 542 return signedBool, nil 543 } 544 545 // ImagesSigned verifies if the provided image references are signed. It 546 // returns a sync map where the key is the ref and the value is a boolean which 547 // indicates if the image is signed or not. The method runs highly parallel. 548 func (s *Signer) ImagesSigned(ctx context.Context, refs ...string) (*sync.Map, error) { 549 s.log().Debug("Checking cache") 550 res := &sync.Map{} 551 unknownRefs := []string{} 552 for _, ref := range refs { 553 item := s.signedRefs.Get(ref) 554 if item != nil { 555 res.Store(ref, item.Value()) 556 continue 557 } 558 559 unknownRefs = append(unknownRefs, ref) 560 } 561 562 if len(unknownRefs) == 0 { 563 s.log().Debug("All references already available in cache") 564 return res, nil 565 } 566 567 s.log().Debug("Parsing references") 568 repos := []name.Repository{} 569 for _, ref := range unknownRefs { 570 item := s.parsedRefs.Get(ref) 571 if item != nil { 572 repos = append(repos, item.Value().Context()) 573 continue 574 } 575 576 parsedRef, err := s.impl.ParseReference(ref) 577 if err != nil { 578 return nil, fmt.Errorf("parsing image reference: %w", err) 579 } 580 581 repos = append(repos, parsedRef.Context()) 582 s.parsedRefs.Set(ref, parsedRef, ttlcache.DefaultTTL) 583 } 584 585 s.log().Debug("Building transports") 586 transports, count, err := s.transportsForRefs(ctx, repos...) 587 if err != nil { 588 return nil, fmt.Errorf("build transports: %w", err) 589 } 590 s.log().Debugf("Built %d transports for %d refs", count, len(unknownRefs)) 591 592 s.log().Debug("Checking if refs are signed") 593 t := throttler.New(int(s.options.MaxWorkers), len(unknownRefs)) 594 for i, repo := range repos { 595 go func(repo name.Repository, i int) { 596 ref := unknownRefs[i] 597 598 trans, ok := transports.Load(repo) 599 if !ok { 600 t.Done(fmt.Errorf("no transport found for repo: %s", repo.String())) 601 return 602 } 603 tr, ok := trans.(http.RoundTripper) 604 if !ok { 605 t.Done(fmt.Errorf("transport has wrong type: %v", tr)) 606 return 607 } 608 609 digest, err := s.impl.Digest(ref, crane.WithTransport(tr)) 610 if err != nil { 611 t.Done(fmt.Errorf("get digest for image reference: %w", err)) 612 return 613 } 614 if _, err := s.impl.Digest(repoDigestToSig(repo, digest), crane.WithTransport(tr)); err != nil { 615 if transportErr, ok := err.(*transport.Error); ok && len(transportErr.Errors) > 0 { 616 if transportErr.Errors[0].Code == transport.ManifestUnknownErrorCode { 617 res.Store(ref, false) 618 s.signedRefs.Set(ref, false, ttlcache.DefaultTTL) 619 t.Done(nil) 620 return 621 } 622 } 623 t.Done(fmt.Errorf("get digest for signature: %w", err)) 624 return 625 } 626 627 res.Store(ref, true) 628 s.signedRefs.Set(ref, true, ttlcache.DefaultTTL) 629 t.Done(nil) 630 }(repo, i) 631 632 if t.Throttle() > 0 { 633 break 634 } 635 } 636 637 s.log().Debug("Done checking if refs are signed") 638 639 if err := t.Err(); err != nil { 640 return res, fmt.Errorf("check if images are signed: %w", err) 641 } 642 643 return res, nil 644 } 645 646 func (s *Signer) transportsForRefs(ctx context.Context, repos ...name.Repository) (*sync.Map, int, error) { 647 count := 0 648 t := throttler.New(int(s.options.MaxWorkers), len(repos)) 649 transports := &sync.Map{} 650 651 for _, repo := range repos { 652 go func(repo name.Repository) { 653 if _, loaded := transports.LoadOrStore(repo, nil); loaded { 654 t.Done(nil) 655 return 656 } 657 658 item := s.transports.Get(repo) 659 if item != nil { 660 transports.Store(repo, item.Value()) 661 count++ 662 t.Done(nil) 663 return 664 } 665 666 tr, err := s.transportForRepo(ctx, repo) 667 if err != nil { 668 t.Done(fmt.Errorf("create transport for repo %s: %w", repo.String(), err)) 669 return 670 } 671 672 s.transports.Set(repo, tr, ttlcache.DefaultTTL) 673 transports.Store(repo, tr) 674 count++ 675 t.Done(nil) 676 }(repo) 677 678 if t.Throttle() > 0 { 679 break 680 } 681 } 682 683 if err := t.Err(); err != nil { 684 return nil, 0, fmt.Errorf("building transports: %w", err) 685 } 686 687 return transports, count, nil 688 } 689 690 func (s *Signer) transportForRepo(ctx context.Context, repo name.Repository) (http.RoundTripper, error) { 691 scopes := []string{repo.Scope(transport.PullScope)} 692 693 t := remote.DefaultTransport 694 t = transport.NewLogger(t) 695 t = transport.NewRetry(t) 696 t = transport.NewUserAgent(t, "k8s-release-sdk") 697 698 t, err := s.impl.NewWithContext(ctx, repo.Registry, authn.Anonymous, t, scopes) 699 if err != nil { 700 return nil, fmt.Errorf("create new transport: %w", err) 701 } 702 703 return t, nil 704 } 705 706 func repoDigestToSig(repo name.Repository, digest string) string { 707 return repo.Name() + ":" + strings.Replace(digest, ":", "-", 1) + ".sig" 708 } 709 710 // IsFileSigned takes an path reference and return true if there is a signature 711 // available for it. It makes no signature verification, only checks to see if 712 // there is a TLog to be found on Rekor. 713 func (s *Signer) IsFileSigned(ctx context.Context, path string) (bool, error) { 714 ko := cliOpts.KeyOpts{ 715 KeyRef: s.options.PublicKeyPath, 716 RekorURL: cliOpts.DefaultRekorURL, 717 } 718 719 rClient, err := s.impl.NewRekorClient(ko.RekorURL) 720 if err != nil { 721 return false, fmt.Errorf("creating rekor client: %w", err) 722 } 723 724 blobBytes, err := s.impl.PayloadBytes(path) 725 if err != nil { 726 return false, err 727 } 728 729 var b64sig string 730 if isb64(blobBytes) { 731 b64sig = string(blobBytes) 732 } else { 733 b64sig = base64.StdEncoding.EncodeToString(blobBytes) 734 } 735 736 pubKey, err := sigs.PublicKeyFromKeyRef(ctx, ko.KeyRef) 737 if err != nil { 738 return false, fmt.Errorf("getting the public key: %w", err) 739 } 740 741 pubBytes, err := sigs.PublicKeyPem(pubKey, signatureoptions.WithContext(ctx)) 742 if err != nil { 743 return false, fmt.Errorf("generating the public key pem: %w", err) 744 } 745 746 uuids, err := s.impl.FindTlogEntry(ctx, rClient, b64sig, blobBytes, pubBytes) 747 if err != nil { 748 return false, fmt.Errorf("find rekor tlog entries: %w", err) 749 } 750 751 return len(uuids) > 0, nil 752 } 753 754 // identityToken returns an identity token to perform keyless signing. 755 // If there is one set in the options we will use that one. If not, 756 // signer will try to get one from the cosign OIDC identity providers 757 // if options.EnableTokenProviders is set 758 func (s *Signer) identityToken(ctx context.Context) (string, error) { 759 tok := s.options.IdentityToken 760 if s.options.PrivateKeyPath == "" && s.options.IdentityToken == "" { 761 // We only attempt to pull from the providers if the option is set 762 if !s.options.EnableTokenProviders { 763 s.log().Warn("No token set in options and OIDC providers are disabled") 764 return "", nil 765 } 766 767 s.log().Info("No identity token was provided. Attempting to get one from supported providers.") 768 token, err := s.impl.TokenFromProviders(ctx, s.log()) 769 if err != nil { 770 return "", fmt.Errorf("getting identity token from providers: %w", err) 771 } 772 tok = token 773 } 774 return tok, nil 775 } 776 777 func isb64(data []byte) bool { 778 _, err := base64.StdEncoding.DecodeString(string(data)) 779 return err == nil 780 }