//
// Copyright 2021 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 options
import (
"fmt"
"strings"
sigs "github.com/sigstore/cosign/v3/pkg/signature"
"github.com/spf13/cobra"
)
// AnnotationOptions is the top level wrapper for the annotations.
type AnnotationOptions struct {
Annotations []string
}
var _ Interface = (*AnnotationOptions)(nil)
func (o *AnnotationOptions) AnnotationsMap() (sigs.AnnotationsMap, error) {
ann := sigs.AnnotationsMap{}
for _, a := range o.Annotations {
kv := strings.Split(a, "=")
if len(kv) != 2 {
return ann, fmt.Errorf("unable to parse annotation: %s", a)
}
if ann.Annotations == nil {
ann.Annotations = map[string]interface{}{}
}
ann.Annotations[kv[0]] = kv[1]
}
return ann, nil
}
// AddFlags implements Interface
func (o *AnnotationOptions) AddFlags(cmd *cobra.Command) {
cmd.Flags().StringSliceVarP(&o.Annotations, "annotations", "a", nil,
"extra key=value pairs to sign")
_ = cmd.RegisterFlagCompletionFunc("annotations", cobra.NoFileCompletions)
}
//
// Copyright 2021 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 options
import (
"fmt"
"strings"
"github.com/google/go-containerregistry/pkg/v1/types"
ctypes "github.com/sigstore/cosign/v3/pkg/types"
"github.com/spf13/cobra"
)
// AttachSignatureOptions is the top level wrapper for the attach signature command.
type AttachSignatureOptions struct {
Signature string
Payload string
Cert string
CertChain string
TimeStampedSig string
RekorBundle string
Registry RegistryOptions
}
var _ Interface = (*AttachSignatureOptions)(nil)
// AddFlags implements Interface
func (o *AttachSignatureOptions) AddFlags(cmd *cobra.Command) {
o.Registry.AddFlags(cmd)
cmd.Flags().StringVar(&o.Signature, "signature", "",
"path to the signature, or {-} for stdin")
cmd.Flags().StringVar(&o.Payload, "payload", "",
"path to the payload covered by the signature")
cmd.Flags().StringVar(&o.Payload, "bundle", "",
"path to bundle containing signature (alias for payload)")
cmd.Flags().StringVar(&o.Cert, "certificate", "",
"path to the X.509 certificate in PEM format to include in the OCI Signature")
cmd.Flags().StringVar(&o.CertChain, "certificate-chain", "",
"path to a list of CA X.509 certificates in PEM format which will be needed "+
"when building the certificate chain for the signing certificate. "+
"Must start with the parent intermediate CA certificate of the "+
"signing certificate and end with the root certificate. Included in the OCI Signature")
cmd.Flags().StringVar(&o.TimeStampedSig, "tsr", "",
"path to the Time Stamped Signature Response from RFC3161 compliant TSA")
cmd.Flags().StringVar(&o.RekorBundle, "rekor-response", "",
"path to the rekor bundle")
}
// AttachSBOMOptions is the top level wrapper for the attach sbom command.
type AttachSBOMOptions struct {
SBOM string
SBOMType string
SBOMInputFormat string
Registry RegistryOptions
RegistryExperimental RegistryExperimentalOptions
}
var _ Interface = (*AttachSBOMOptions)(nil)
// AddFlags implements Interface
func (o *AttachSBOMOptions) AddFlags(cmd *cobra.Command) {
o.Registry.AddFlags(cmd)
o.RegistryExperimental.AddFlags(cmd)
cmd.Flags().StringVar(&o.SBOM, "sbom", "",
"path to the sbom, or {-} for stdin")
_ = cmd.MarkFlagFilename("sbom", sbomExts...)
sbomTypes := []string{"spdx", "cyclonedx", "syft"}
cmd.Flags().StringVar(&o.SBOMType, "type", sbomTypes[0],
"type of sbom ("+strings.Join(sbomTypes, "|")+")")
_ = cmd.RegisterFlagCompletionFunc("type", cobra.FixedCompletions(sbomTypes, cobra.ShellCompDirectiveNoFileComp))
inputFormats := []string{ctypes.JSONInputFormat, ctypes.XMLInputFormat, ctypes.TextInputFormat}
cmd.Flags().StringVar(&o.SBOMInputFormat, "input-format", "",
"type of sbom input format ("+strings.Join(inputFormats, "|")+")")
_ = cmd.RegisterFlagCompletionFunc("input-format", cobra.FixedCompletions(inputFormats, cobra.ShellCompDirectiveNoFileComp))
}
func (o *AttachSBOMOptions) MediaType() (types.MediaType, error) {
var looksLikeJSON bool
if strings.HasSuffix(o.SBOM, ".json") {
looksLikeJSON = true
}
switch o.SBOMType {
case "cyclonedx":
if o.SBOMInputFormat != "" && o.SBOMInputFormat != ctypes.XMLInputFormat && o.SBOMInputFormat != ctypes.JSONInputFormat {
return "invalid", fmt.Errorf("invalid SBOM input format: %q, expected (json|xml)", o.SBOMInputFormat)
}
if o.SBOMInputFormat == ctypes.JSONInputFormat || looksLikeJSON {
return ctypes.CycloneDXJSONMediaType, nil
}
return ctypes.CycloneDXXMLMediaType, nil
case "spdx":
if o.SBOMInputFormat != "" && o.SBOMInputFormat != ctypes.TextInputFormat && o.SBOMInputFormat != ctypes.JSONInputFormat {
return "invalid", fmt.Errorf("invalid SBOM input format: %q, expected (json|text)", o.SBOMInputFormat)
}
if o.SBOMInputFormat == ctypes.JSONInputFormat || looksLikeJSON {
return ctypes.SPDXJSONMediaType, nil
}
return ctypes.SPDXMediaType, nil
case "syft":
if o.SBOMInputFormat != "" && o.SBOMInputFormat != ctypes.JSONInputFormat {
return "invalid", fmt.Errorf("invalid SBOM input format: %q, expected (json)", o.SBOMInputFormat)
}
return ctypes.SyftMediaType, nil
default:
return "unknown", fmt.Errorf("unknown SBOM type: %q, expected (spdx|cyclonedx|syft)", o.SBOMType)
}
}
// AttachAttestationOptions is the top level wrapper for the attach attestation command.
type AttachAttestationOptions struct {
Attestations []string
Registry RegistryOptions
}
// AddFlags implements Interface
func (o *AttachAttestationOptions) AddFlags(cmd *cobra.Command) {
o.Registry.AddFlags(cmd)
cmd.Flags().StringArrayVarP(&o.Attestations, "attestation", "", nil,
"path to the attestation envelope")
}
//
// Copyright 2021 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 options
import (
"strings"
"github.com/spf13/cobra"
)
// AttestOptions is the top level wrapper for the attest command.
type AttestOptions struct {
Key string
Cert string
CertChain string
IssueCertificate bool
NoUpload bool
Replace bool
SkipConfirmation bool
TlogUpload bool
TSAClientCACert string
TSAClientCert string
TSAClientKey string
TSAServerName string
TSAServerURL string
RekorEntryType string
RecordCreationTimestamp bool
BundlePath string
NewBundleFormat bool
UseSigningConfig bool
SigningConfigPath string
TrustedRootPath string
Rekor RekorOptions
Fulcio FulcioOptions
OIDC OIDCOptions
SecurityKey SecurityKeyOptions
Predicate PredicateLocalOptions
Registry RegistryOptions
}
var _ Interface = (*AttestOptions)(nil)
// AddFlags implements Interface
func (o *AttestOptions) AddFlags(cmd *cobra.Command) {
o.SecurityKey.AddFlags(cmd)
o.Predicate.AddFlags(cmd)
o.Fulcio.AddFlags(cmd)
o.OIDC.AddFlags(cmd)
o.Rekor.AddFlags(cmd)
o.Registry.AddFlags(cmd)
cmd.Flags().StringVar(&o.Key, "key", "",
"path to the private key file, KMS URI or Kubernetes Secret")
_ = cmd.MarkFlagFilename("key", privateKeyExts...)
cmd.Flags().StringVar(&o.Cert, "certificate", "",
"path to the X.509 certificate in PEM format to include in the OCI Signature")
_ = cmd.MarkFlagFilename("certificate", certificateExts...)
cmd.Flags().StringVar(&o.CertChain, "certificate-chain", "",
"path to a list of CA X.509 certificates in PEM format which will be needed "+
"when building the certificate chain for the signing certificate. "+
"Must start with the parent intermediate CA certificate of the "+
"signing certificate and end with the root certificate. Included in the OCI Signature")
_ = cmd.MarkFlagFilename("certificate-chain", certificateExts...)
cmd.Flags().BoolVar(&o.NoUpload, "no-upload", false,
"do not upload the generated attestation, but send the attestation output to STDOUT")
cmd.Flags().BoolVarP(&o.Replace, "replace", "", false,
"")
cmd.Flags().BoolVarP(&o.SkipConfirmation, "yes", "y", false,
"skip confirmation prompts for non-destructive operations")
cmd.Flags().BoolVar(&o.TlogUpload, "tlog-upload", true,
"whether or not to upload to the tlog")
_ = cmd.Flags().MarkDeprecated("tlog-upload", "prefer using a --signing-config file with no transparency log services")
cmd.Flags().StringVar(&o.RekorEntryType, "rekor-entry-type", rekorEntryTypes[0],
"specifies the type to be used for a rekor entry upload ("+strings.Join(rekorEntryTypes, "|")+")")
_ = cmd.RegisterFlagCompletionFunc("rekor-entry-type", cobra.FixedCompletions(rekorEntryTypes, cobra.ShellCompDirectiveNoFileComp))
cmd.Flags().StringVar(&o.TSAClientCACert, "timestamp-client-cacert", "",
"path to the X.509 CA certificate file in PEM format to be used for the connection to the TSA Server")
cmd.Flags().StringVar(&o.TSAClientCert, "timestamp-client-cert", "",
"path to the X.509 certificate file in PEM format to be used for the connection to the TSA Server")
cmd.Flags().StringVar(&o.TSAClientKey, "timestamp-client-key", "",
"path to the X.509 private key file in PEM format to be used, together with the 'timestamp-client-cert' value, for the connection to the TSA Server")
cmd.Flags().StringVar(&o.TSAServerName, "timestamp-server-name", "",
"SAN name to use as the 'ServerName' tls.Config field to verify the mTLS connection to the TSA Server")
cmd.Flags().StringVar(&o.TSAServerURL, "timestamp-server-url", "",
"url to the Timestamp RFC3161 server, default none. Must be the path to the API to request timestamp responses, e.g. https://freetsa.org/tsr")
_ = cmd.RegisterFlagCompletionFunc("timestamp-server-url", cobra.NoFileCompletions)
cmd.Flags().BoolVar(&o.RecordCreationTimestamp, "record-creation-timestamp", false,
"set the createdAt timestamp in the attestation artifact to the time it was created; by default, cosign sets this to the zero value")
cmd.Flags().BoolVar(&o.IssueCertificate, "issue-certificate", false,
"issue a code signing certificate from Fulcio, even if a key is provided")
cmd.Flags().StringVar(&o.BundlePath, "bundle", "",
"write everything required to verify the blob to a FILE")
_ = cmd.MarkFlagFilename("bundle", bundleExts...)
cmd.Flags().BoolVar(&o.NewBundleFormat, "new-bundle-format", true, "attach a Sigstore bundle using OCI referrers API")
cmd.Flags().BoolVar(&o.UseSigningConfig, "use-signing-config", true,
"whether to use a TUF-provided signing config for the service URLs. Must set --new-bundle-format, which will store verification material in the new format")
cmd.Flags().StringVar(&o.SigningConfigPath, "signing-config", "",
"path to a signing config file. Must provide --new-bundle-format, which will store verification material in the new format")
cmd.MarkFlagsMutuallyExclusive("use-signing-config", "signing-config")
cmd.Flags().StringVar(&o.TrustedRootPath, "trusted-root", "",
"optional path to a TrustedRoot JSON file to verify a signature after signing")
}
// 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 options
import (
"strings"
"github.com/spf13/cobra"
)
// AttestOptions is the top level wrapper for the attest command.
type AttestBlobOptions struct {
Key string
Cert string
CertChain string
IssueCertificate bool
SkipConfirmation bool
TlogUpload bool
TSAClientCACert string
TSAClientCert string
TSAClientKey string
TSAServerName string
TSAServerURL string
RFC3161TimestampPath string
Hash string
Predicate PredicateLocalOptions
OutputSignature string
OutputAttestation string
OutputCertificate string
BundlePath string
NewBundleFormat bool
RekorEntryType string
Rekor RekorOptions
Fulcio FulcioOptions
OIDC OIDCOptions
SecurityKey SecurityKeyOptions
UseSigningConfig bool
SigningConfigPath string
TrustedRootPath string
}
var _ Interface = (*AttestOptions)(nil)
// AddFlags implements Interface
func (o *AttestBlobOptions) AddFlags(cmd *cobra.Command) {
o.Predicate.AddFlags(cmd)
o.Rekor.AddFlags(cmd)
o.Fulcio.AddFlags(cmd)
o.OIDC.AddFlags(cmd)
o.SecurityKey.AddFlags(cmd)
cmd.Flags().StringVar(&o.Key, "key", "",
"path to the private key file, KMS URI or Kubernetes Secret")
_ = cmd.MarkFlagFilename("key", privateKeyExts...)
cmd.Flags().StringVar(&o.Cert, "certificate", "",
"path to the X.509 certificate in PEM format to include in the OCI Signature")
_ = cmd.MarkFlagFilename("certificate", certificateExts...)
cmd.Flags().StringVar(&o.CertChain, "certificate-chain", "",
"path to a list of CA X.509 certificates in PEM format which will be needed "+
"when building the certificate chain for the signing certificate. "+
"Must start with the parent intermediate CA certificate of the "+
"signing certificate and end with the root certificate. Included in the OCI Signature")
_ = cmd.MarkFlagFilename("certificate-chain", certificateExts...)
cmd.Flags().StringVar(&o.OutputSignature, "output-signature", "",
"write the signature to FILE")
_ = cmd.MarkFlagFilename("output-signature", signatureExts...)
cmd.Flags().StringVar(&o.OutputAttestation, "output-attestation", "",
"write the attestation to FILE")
// _ = cmd.MarkFlagFilename("output-attestation") // no typical extensions
cmd.Flags().StringVar(&o.OutputCertificate, "output-certificate", "",
"write the certificate to FILE")
_ = cmd.MarkFlagFilename("key", certificateExts...)
cmd.Flags().StringVar(&o.BundlePath, "bundle", "",
"write everything required to verify the blob to a FILE")
_ = cmd.MarkFlagFilename("bundle", bundleExts...)
cmd.Flags().BoolVar(&o.NewBundleFormat, "new-bundle-format", true,
"output bundle in new format that contains all verification material")
cmd.Flags().BoolVar(&o.UseSigningConfig, "use-signing-config", true,
"whether to use a TUF-provided signing config for the service URLs. Must provide --bundle, which will output verification material in the new format")
cmd.Flags().StringVar(&o.SigningConfigPath, "signing-config", "",
"path to a signing config file. Must provide --bundle, which will output verification material in the new format")
cmd.MarkFlagsMutuallyExclusive("use-signing-config", "signing-config")
cmd.Flags().StringVar(&o.TrustedRootPath, "trusted-root", "",
"optional path to a TrustedRoot JSON file to verify a signature after signing")
cmd.Flags().StringVar(&o.Hash, "hash", "",
"hash of blob in hexadecimal (base16). Used if you want to sign an artifact stored elsewhere and have the hash")
_ = cmd.RegisterFlagCompletionFunc("hash", cobra.NoFileCompletions)
cmd.Flags().BoolVarP(&o.SkipConfirmation, "yes", "y", false,
"skip confirmation prompts for non-destructive operations")
cmd.Flags().BoolVar(&o.TlogUpload, "tlog-upload", true,
"whether or not to upload to the tlog")
_ = cmd.Flags().MarkDeprecated("tlog-upload", "prefer using a --signing-config file with no transparency log services")
cmd.Flags().StringVar(&o.RekorEntryType, "rekor-entry-type", rekorEntryTypes[0],
"specifies the type to be used for a rekor entry upload ("+strings.Join(rekorEntryTypes, "|")+")")
_ = cmd.RegisterFlagCompletionFunc("rekor-entry-type", cobra.FixedCompletions(rekorEntryTypes, cobra.ShellCompDirectiveNoFileComp))
cmd.Flags().StringVar(&o.TSAClientCACert, "timestamp-client-cacert", "",
"path to the X.509 CA certificate file in PEM format to be used for the connection to the TSA Server")
cmd.Flags().StringVar(&o.TSAClientCert, "timestamp-client-cert", "",
"path to the X.509 certificate file in PEM format to be used for the connection to the TSA Server")
cmd.Flags().StringVar(&o.TSAClientKey, "timestamp-client-key", "",
"path to the X.509 private key file in PEM format to be used, together with the 'timestamp-client-cert' value, for the connection to the TSA Server")
cmd.Flags().StringVar(&o.TSAServerName, "timestamp-server-name", "",
"SAN name to use as the 'ServerName' tls.Config field to verify the mTLS connection to the TSA Server")
cmd.Flags().StringVar(&o.TSAServerURL, "timestamp-server-url", "",
"url to the Timestamp RFC3161 server, default none. Must be the path to the API to request timestamp responses, e.g. https://freetsa.org/tsr")
_ = cmd.RegisterFlagCompletionFunc("timestamp-server-url", cobra.NoFileCompletions)
cmd.Flags().StringVar(&o.RFC3161TimestampPath, "rfc3161-timestamp-bundle", "",
"path to an RFC 3161 timestamp bundle FILE")
// _ = cmd.MarkFlagFilename("rfc3161-timestamp-bundle") // no typical extensions
cmd.Flags().BoolVar(&o.IssueCertificate, "issue-certificate", false,
"issue a code signing certificate from Fulcio, even if a key is provided")
}
//
// Copyright 2024 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 options
import (
"strings"
"github.com/spf13/cobra"
)
type BundleCreateOptions struct {
Artifact string
AttestationPath string
BundlePath string
CertificatePath string
IgnoreTlog bool
KeyRef string
Out string
RekorURL string
RFC3161TimestampPath string
SignaturePath string
Sk bool
Slot string
}
var _ Interface = (*BundleCreateOptions)(nil)
func (o *BundleCreateOptions) AddFlags(cmd *cobra.Command) {
cmd.Flags().StringVar(&o.Artifact, "artifact", "",
"path to artifact FILE")
// _ = cmd.MarkFlagFilename("artifact") // no typical extensions
cmd.Flags().StringVar(&o.AttestationPath, "attestation", "",
"path to attestation FILE")
// _ = cmd.MarkFlagFilename("attestation") // no typical extensions
cmd.Flags().StringVar(&o.BundlePath, "bundle", "",
"path to old format bundle FILE")
_ = cmd.MarkFlagFilename("bundle", bundleExts...)
cmd.Flags().StringVar(&o.CertificatePath, "certificate", "",
"path to the signing certificate, likely from Fulco.")
_ = cmd.MarkFlagFilename("certificate", certificateExts...)
cmd.Flags().BoolVar(&o.IgnoreTlog, "ignore-tlog", false,
"ignore transparency log verification, to be used when an artifact "+
"signature has not been uploaded to the transparency log.")
cmd.Flags().StringVar(&o.KeyRef, "key", "",
"path to the public key file, KMS URI or Kubernetes Secret")
_ = cmd.MarkFlagFilename("key", publicKeyExts...)
cmd.Flags().StringVar(&o.Out, "out", "", "path to output bundle")
_ = cmd.MarkFlagFilename("out", bundleExts...)
cmd.Flags().StringVar(&o.RekorURL, "rekor-url", "https://rekor.sigstore.dev",
"address of rekor STL server")
_ = cmd.RegisterFlagCompletionFunc("rekor-url", cobra.NoFileCompletions)
cmd.Flags().StringVar(&o.RFC3161TimestampPath, "rfc3161-timestamp", "",
"path to RFC3161 timestamp FILE")
// _ = cmd.MarkFlagFilename("rfc3161-timestamp") // no typical extensions
cmd.Flags().StringVar(&o.SignaturePath, "signature", "",
"path to base64-encoded signature over attestation in DSSE format")
_ = cmd.MarkFlagFilename("signature", signatureExts...)
cmd.Flags().BoolVar(&o.Sk, "sk", false,
"whether to use a hardware security key")
slots := []string{"authentication", "signature", "card-authentication", "key-management"}
cmd.Flags().StringVar(&o.Slot, "slot", "signature",
"security key slot to use for generated key ("+
strings.Join(slots, "|")+")")
_ = cmd.RegisterFlagCompletionFunc("slot", cobra.FixedCompletions(slots, cobra.ShellCompDirectiveNoFileComp))
cmd.MarkFlagsMutuallyExclusive("bundle", "certificate")
cmd.MarkFlagsMutuallyExclusive("bundle", "signature")
}
// 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 options
import (
"errors"
"github.com/sigstore/cosign/v3/pkg/cosign"
"github.com/spf13/cobra"
)
// CertVerifyOptions is the wrapper for certificate verification.
type CertVerifyOptions struct {
Cert string
CertIdentity string
CertIdentityRegexp string
CertOidcIssuer string
CertOidcIssuerRegexp string
CertGithubWorkflowTrigger string
CertGithubWorkflowSha string
CertGithubWorkflowName string
CertGithubWorkflowRepository string
CertGithubWorkflowRef string
CAIntermediates string
CARoots string
CertChain string
SCT string
IgnoreSCT bool
}
var _ Interface = (*RekorOptions)(nil)
// AddFlags implements Interface
func (o *CertVerifyOptions) AddFlags(cmd *cobra.Command) {
cmd.Flags().StringVar(&o.Cert, "certificate", "",
"path to the public certificate. The certificate will be verified against the Fulcio roots if the --certificate-chain option is not passed.")
_ = cmd.MarkFlagFilename("certificate", certificateExts...)
cmd.Flags().StringVar(&o.CertIdentity, "certificate-identity", "",
"The identity expected in a valid Fulcio certificate. Valid values include email address, DNS names, IP addresses, and URIs. Either --certificate-identity or --certificate-identity-regexp must be set for keyless flows.")
cmd.Flags().StringVar(&o.CertIdentityRegexp, "certificate-identity-regexp", "",
"A regular expression alternative to --certificate-identity. Accepts the Go regular expression syntax described at https://golang.org/s/re2syntax. Either --certificate-identity or --certificate-identity-regexp must be set for keyless flows.")
cmd.Flags().StringVar(&o.CertOidcIssuer, "certificate-oidc-issuer", "",
"The OIDC issuer expected in a valid Fulcio certificate, e.g. https://token.actions.githubusercontent.com or https://oauth2.sigstore.dev/auth. Either --certificate-oidc-issuer or --certificate-oidc-issuer-regexp must be set for keyless flows.")
cmd.Flags().StringVar(&o.CertOidcIssuerRegexp, "certificate-oidc-issuer-regexp", "",
"A regular expression alternative to --certificate-oidc-issuer. Accepts the Go regular expression syntax described at https://golang.org/s/re2syntax. Either --certificate-oidc-issuer or --certificate-oidc-issuer-regexp must be set for keyless flows.")
// -- Cert extensions begin --
// Source: https://github.com/sigstore/fulcio/blob/main/docs/oid-info.md
cmd.Flags().StringVar(&o.CertGithubWorkflowTrigger, "certificate-github-workflow-trigger", "",
"contains the event_name claim from the GitHub OIDC Identity token that contains the name of the event that triggered the workflow run")
cmd.Flags().StringVar(&o.CertGithubWorkflowSha, "certificate-github-workflow-sha", "",
"contains the sha claim from the GitHub OIDC Identity token that contains the commit SHA that the workflow run was based upon.")
cmd.Flags().StringVar(&o.CertGithubWorkflowName, "certificate-github-workflow-name", "",
"contains the workflow claim from the GitHub OIDC Identity token that contains the name of the executed workflow.")
cmd.Flags().StringVar(&o.CertGithubWorkflowRepository, "certificate-github-workflow-repository", "",
"contains the repository claim from the GitHub OIDC Identity token that contains the repository that the workflow run was based upon")
cmd.Flags().StringVar(&o.CertGithubWorkflowRef, "certificate-github-workflow-ref", "",
"contains the ref claim from the GitHub OIDC Identity token that contains the git ref that the workflow run was based upon.")
// -- Cert extensions end --
cmd.Flags().StringVar(&o.CAIntermediates, "ca-intermediates", "",
"path to a file of intermediate CA certificates in PEM format which will be needed "+
"when building the certificate chains for the signing certificate. "+
"The flag is optional and must be used together with --ca-roots, conflicts with "+
"--certificate-chain.")
_ = cmd.MarkFlagFilename("ca-intermediates", certificateExts...)
cmd.Flags().StringVar(&o.CARoots, "ca-roots", "",
"path to a bundle file of CA certificates in PEM format which will be needed "+
"when building the certificate chains for the signing certificate. Conflicts with --certificate-chain.")
_ = cmd.MarkFlagFilename("ca-roots", certificateExts...)
cmd.Flags().StringVar(&o.CertChain, "certificate-chain", "",
"path to a list of CA certificates in PEM format which will be needed "+
"when building the certificate chain for the signing certificate. "+
"Must start with the parent intermediate CA certificate of the "+
"signing certificate and end with the root certificate. Conflicts with --ca-roots and --ca-intermediates.")
_ = cmd.MarkFlagFilename("certificate-chain", certificateExts...)
cmd.MarkFlagsMutuallyExclusive("ca-roots", "certificate-chain")
cmd.MarkFlagsMutuallyExclusive("ca-intermediates", "certificate-chain")
cmd.Flags().StringVar(&o.SCT, "sct", "",
"path to a detached Signed Certificate Timestamp, formatted as a RFC6962 AddChainResponse struct. "+
"If a certificate contains an SCT, verification will check both the detached and embedded SCTs.")
// _ = cmd.MarkFlagFilename("sct") // no typical extensions
cmd.Flags().BoolVar(&o.IgnoreSCT, "insecure-ignore-sct", false,
"when set, verification will not check that a certificate contains an embedded SCT, a proof of "+
"inclusion in a certificate transparency log")
}
func (o *CertVerifyOptions) Identities() ([]cosign.Identity, error) {
if o.CertIdentity == "" && o.CertIdentityRegexp == "" {
return nil, errors.New("--certificate-identity or --certificate-identity-regexp is required for verification in keyless mode")
}
if o.CertOidcIssuer == "" && o.CertOidcIssuerRegexp == "" {
return nil, errors.New("--certificate-oidc-issuer or --certificate-oidc-issuer-regexp is required for verification in keyless mode")
}
return []cosign.Identity{{IssuerRegExp: o.CertOidcIssuerRegexp, Issuer: o.CertOidcIssuer, SubjectRegExp: o.CertIdentityRegexp, Subject: o.CertIdentity}}, nil
}
// 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 options
import (
"errors"
"github.com/spf13/cobra"
)
type CleanType string
const (
CleanTypeSignature CleanType = "signature"
CleanTypeAttestation CleanType = "attestation"
CleanTypeSbom CleanType = "sbom"
CleanTypeAll CleanType = "all"
)
func defaultCleanType() CleanType {
return CleanTypeAll
}
// cleanType implements github.com/spf13/pflag.Value.
func (c *CleanType) String() string {
return string(*c)
}
// cleanType implements github.com/spf13/pflag.Value.
func (c *CleanType) Set(v string) error {
switch v {
case "signature", "attestation", "sbom", "all":
*c = CleanType(v)
return nil
default:
return errors.New(`must be one of "signature", "attestation", "sbom", or "all"`)
}
}
// cleanType implements github.com/spf13/pflag.Value.
func (c *CleanType) Type() string {
return "CLEAN_TYPE"
}
type CleanOptions struct {
Registry RegistryOptions
CleanType CleanType
Force bool
}
var _ Interface = (*CleanOptions)(nil)
func (c *CleanOptions) AddFlags(cmd *cobra.Command) {
c.Registry.AddFlags(cmd)
c.CleanType = defaultCleanType()
cmd.Flags().Var(&c.CleanType, "type", "a type of clean: <signature|attestation|sbom|all> (sbom is deprecated)")
// TODO(#2044): Rename to --skip-confirmation for consistency?
cmd.Flags().BoolVarP(&c.Force, "force", "f", false, "do not prompt for confirmation")
}
//
// Copyright 2021 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 options
import (
"github.com/spf13/cobra"
)
// CopyOptions is the top level wrapper for the copy command.
type CopyOptions struct {
CopyOnly []string
SignatureOnly bool
Force bool
Platform string
Registry RegistryOptions
}
var _ Interface = (*CopyOptions)(nil)
// AddFlags implements Interface
func (o *CopyOptions) AddFlags(cmd *cobra.Command) {
o.Registry.AddFlags(cmd)
cmd.Flags().StringSliceVar(&o.CopyOnly, "only", []string{},
"custom string array to only copy specific items, this flag is comma delimited. ex: --only=sig,att,sbom")
cmd.Flags().BoolVar(&o.SignatureOnly, "sig-only", false,
"[DEPRECATED] only copy the image signature")
cmd.Flags().BoolVarP(&o.Force, "force", "f", false,
"overwrite destination image(s), if necessary")
cmd.Flags().StringVar(&o.Platform, "platform", "",
"only copy container image and its signatures for a specific platform image")
}
//
// 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 options
import "github.com/spf13/cobra"
// DownloadOptions is the struct for control
type SBOMDownloadOptions struct {
Platform string // Platform to download sboms
}
type AttestationDownloadOptions struct {
PredicateType string // Predicate type of attestation to retrieve
Platform string // Platform to download attestations
}
var _ Interface = (*SBOMDownloadOptions)(nil)
var _ Interface = (*AttestationDownloadOptions)(nil)
// AddFlags implements Interface
func (o *SBOMDownloadOptions) AddFlags(cmd *cobra.Command) {
cmd.Flags().StringVar(&o.Platform, "platform", "",
"download SBOM for a specific platform image")
}
// AddFlags implements Interface
func (o *AttestationDownloadOptions) AddFlags(cmd *cobra.Command) {
cmd.Flags().StringVar(&o.PredicateType, "predicate-type", "",
"download attestation with matching predicateType")
cmd.Flags().StringVar(&o.Platform, "platform", "",
"download attestation for a specific platform image")
}
//
// 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 options
import (
"github.com/spf13/cobra"
)
// EnvOptions is the top level wrapper for the env command.
type EnvOptions struct {
ShowDescriptions bool
ShowSensitiveValues bool
}
var _ Interface = (*EnvOptions)(nil)
// AddFlags implements Interface
func (o *EnvOptions) AddFlags(cmd *cobra.Command) {
cmd.Flags().BoolVar(&o.ShowDescriptions, "show-descriptions", true,
"show descriptions for environment variables")
cmd.Flags().BoolVar(&o.ShowSensitiveValues, "show-sensitive-values", false,
"show values of sensitive environment variables")
}
//
// Copyright 2021 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 options
// KeyParseError is an error returned when an incorrect set of key flags
// are parsed by the CLI
type KeyParseError struct{}
// PubKeyParseError is an error returned when an incorrect set of public key
// flags are parsed by the CLI
type PubKeyParseError struct{}
func (e *KeyParseError) Error() string {
return "exactly one of: key reference (--key), or hardware token (--sk) must be provided"
}
func (e *PubKeyParseError) Error() string {
return "exactly one of: key reference (--key), certificate (--cert) or hardware token (--sk) must be provided"
}
// Copyright 2021 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 options
import (
"strconv"
"github.com/sigstore/cosign/v3/pkg/cosign/env"
)
func EnableExperimental() bool {
if b, err := strconv.ParseBool(env.Getenv(env.VariableExperimental)); err == nil {
return b
}
return false
}
//
// Copyright 2021 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 options
import (
"fmt"
"strings"
cremote "github.com/sigstore/cosign/v3/pkg/cosign/remote"
"github.com/spf13/cobra"
)
// FilesOptions is the wrapper for the files.
type FilesOptions struct {
Files []string
}
var _ Interface = (*FilesOptions)(nil)
func (o *FilesOptions) Parse() ([]cremote.File, error) {
fs := cremote.FilesFromFlagList(o.Files)
// If we have multiple files, each file must have a platform.
if len(fs) > 1 {
for _, f := range fs {
if f.Platform() == nil {
return nil, fmt.Errorf("each file must include a unique platform, %s had no platform", f.Path())
}
}
}
return fs, nil
}
func (o *FilesOptions) String() string {
return strings.Join(o.Files, ",")
}
// AddFlags implements Interface
func (o *FilesOptions) AddFlags(cmd *cobra.Command) {
cmd.Flags().StringSliceVarP(&o.Files, "files", "f", nil,
"<filepath>:[platform/arch]")
// _ = cmd.MarkFlagFilename("files") // no typical extensions
}
//
// Copyright 2021 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 options
import (
"reflect"
)
// OneOf ensures that only one of the supplied interfaces is set to a non-zero value.
func OneOf(args ...interface{}) bool {
return NOf(args...) == 1
}
// NOf returns how many of the fields are non-zero
func NOf(args ...interface{}) int {
n := 0
for _, arg := range args {
if !reflect.ValueOf(arg).IsZero() {
n++
}
}
return n
}
//
// Copyright 2021 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 options
import (
"github.com/spf13/cobra"
)
const DefaultFulcioURL = "https://fulcio.sigstore.dev"
// FulcioOptions is the wrapper for Fulcio related options.
type FulcioOptions struct {
URL string
AuthFlow string
IdentityToken string
InsecureSkipFulcioVerify bool
}
var _ Interface = (*FulcioOptions)(nil)
// AddFlags implements Interface
func (o *FulcioOptions) AddFlags(cmd *cobra.Command) {
// TODO: change this back to api.SigstorePublicServerURL after the v1 migration is complete.
cmd.Flags().StringVar(&o.URL, "fulcio-url", DefaultFulcioURL,
"address of sigstore PKI server")
cmd.Flags().StringVar(&o.IdentityToken, "identity-token", "",
"identity token to use for certificate from fulcio. the token or a path to a file containing the token is accepted.")
// _ = cmd.MarkFlagFilename("identity-token") // no typical extensions
cmd.Flags().StringVar(&o.AuthFlow, "fulcio-auth-flow", "",
"fulcio interactive oauth2 flow to use for certificate from fulcio. Defaults to determining the flow based on the runtime environment. (options) normal|device|token|client_credentials")
cmd.Flags().BoolVar(&o.InsecureSkipFulcioVerify, "insecure-skip-verify", false,
"skip verifying fulcio published to the SCT (this should only be used for testing).")
}
//
// Copyright 2021 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 options
import (
"github.com/spf13/cobra"
)
// GenerateOptions is the top level wrapper for the generate command.
type GenerateOptions struct {
AnnotationOptions
Registry RegistryOptions
}
var _ Interface = (*GenerateOptions)(nil)
// AddFlags implements Interface
func (o *GenerateOptions) AddFlags(cmd *cobra.Command) {
o.AnnotationOptions.AddFlags(cmd)
o.Registry.AddFlags(cmd)
}
//
// Copyright 2021 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 options
import (
"github.com/spf13/cobra"
)
// GenerateKeyPairOptions is the top level wrapper for the generate-key-pair command.
type GenerateKeyPairOptions struct {
// KMS Key Management Service
KMS string
OutputKeyPrefix string
}
var _ Interface = (*GenerateKeyPairOptions)(nil)
// AddFlags implements Interface
func (o *GenerateKeyPairOptions) AddFlags(cmd *cobra.Command) {
cmd.Flags().StringVar(&o.KMS, "kms", "",
"create key pair in KMS service to use for signing")
cmd.Flags().StringVar(&o.OutputKeyPrefix, "output-key-prefix", "cosign",
"name used for generated .pub and .key files (defaults to `cosign`)")
}
//
// Copyright 2021 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 options
import (
"github.com/spf13/cobra"
)
// ImportKeyPairOptions is the top level wrapper for the import-key-pair command.
type ImportKeyPairOptions struct {
// Local key file generated by external program such as OpenSSL
Key string
// Filename used for outputted keys
OutputKeyPrefix string
SkipConfirmation bool
}
var _ Interface = (*ImportKeyPairOptions)(nil)
// AddFlags implements Interface
func (o *ImportKeyPairOptions) AddFlags(cmd *cobra.Command) {
cmd.Flags().StringVarP(&o.Key, "key", "k", "",
"import key pair to use for signing")
_ = cmd.MarkFlagFilename("key", privateKeyExts...)
cmd.Flags().StringVarP(&o.OutputKeyPrefix, "output-key-prefix", "o", "import-cosign",
"name used for outputted key pairs")
// _ = cmd.MarkFlagFilename("output-key-prefix") // no typical extensions
cmd.Flags().BoolVarP(&o.SkipConfirmation, "yes", "y", false,
"skip confirmation prompts for overwriting existing key")
}
//
// Copyright 2021 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 options
import (
"github.com/sigstore/sigstore/pkg/tuf"
"github.com/spf13/cobra"
)
// InitializeOptions is the top level wrapper for the initialize command.
type InitializeOptions struct {
Mirror string
Root string
RootChecksum string
}
var _ Interface = (*InitializeOptions)(nil)
// AddFlags implements Interface
func (o *InitializeOptions) AddFlags(cmd *cobra.Command) {
cmd.Flags().StringVar(&o.Mirror, "mirror", tuf.DefaultRemoteRoot,
"GCS bucket to a SigStore TUF repository, or HTTP(S) base URL, or file:/// for local filestore remote (air-gap)")
cmd.Flags().StringVar(&o.Root, "root", "",
"path to trusted initial root. defaults to embedded root")
_ = cmd.MarkFlagDirname("root")
cmd.Flags().StringVar(&o.RootChecksum, "root-checksum", "",
"checksum of the initial root, required if root is downloaded via http(s). expects sha256 by default, can be changed to sha512 by providing sha512:<checksum>")
}
//
// Copyright 2021 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 options
import (
"github.com/spf13/cobra"
)
// LoadOptions is the top level wrapper for the load command.
type LoadOptions struct {
Directory string
Registry RegistryOptions
}
var _ Interface = (*LoadOptions)(nil)
// AddFlags implements Interface
func (o *LoadOptions) AddFlags(cmd *cobra.Command) {
o.Registry.AddFlags(cmd)
cmd.Flags().StringVar(&o.Directory, "dir", "",
"path to directory where the signed image is stored on disk")
_ = cmd.MarkFlagDirname("dir")
_ = cmd.MarkFlagRequired("dir")
}
//
// Copyright 2021 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 options
import (
"fmt"
"os"
"strings"
"unicode/utf8"
"github.com/spf13/cobra"
)
const DefaultOIDCIssuerURL = "https://oauth2.sigstore.dev/auth"
// OIDCOptions is the wrapper for OIDC related options.
type OIDCOptions struct {
Issuer string
ClientID string
clientSecretFile string
RedirectURL string
Provider string
DisableAmbientProviders bool
}
func (o *OIDCOptions) ClientSecret() (string, error) {
if o.clientSecretFile != "" {
clientSecretBytes, err := os.ReadFile(o.clientSecretFile)
if err != nil {
return "", fmt.Errorf("reading OIDC client secret: %w", err)
}
if !utf8.Valid(clientSecretBytes) {
return "", fmt.Errorf("OIDC client secret in file %s not valid utf8", o.clientSecretFile)
}
clientSecretString := string(clientSecretBytes)
clientSecretString = strings.TrimSpace(clientSecretString)
return clientSecretString, nil
}
return "", nil
}
var _ Interface = (*OIDCOptions)(nil)
// AddFlags implements Interface
func (o *OIDCOptions) AddFlags(cmd *cobra.Command) {
cmd.Flags().StringVar(&o.Issuer, "oidc-issuer", DefaultOIDCIssuerURL,
"OIDC provider to be used to issue ID token")
cmd.Flags().StringVar(&o.ClientID, "oidc-client-id", "sigstore",
"OIDC client ID for application")
cmd.Flags().StringVar(&o.clientSecretFile, "oidc-client-secret-file", "",
"Path to file containing OIDC client secret for application")
// _ = cmd.MarkFlagFilename("oidc-client-secret-file") // no typical extensions
cmd.Flags().StringVar(&o.RedirectURL, "oidc-redirect-url", "",
"OIDC redirect URL (Optional). The default oidc-redirect-url is 'http://localhost:0/auth/callback'.")
cmd.Flags().StringVar(&o.Provider, "oidc-provider", "",
"Specify the provider to get the OIDC token from (Optional). If unset, all options will be tried. Options include: [spiffe, google, github-actions, filesystem, buildkite-agent]")
cmd.Flags().BoolVar(&o.DisableAmbientProviders, "oidc-disable-ambient-providers", false,
"Disable ambient OIDC providers. When true, ambient credentials will not be read")
}
//
// Copyright 2021 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 options
import (
"github.com/spf13/cobra"
)
// PIVToolSetManagementKeyOptions is the wrapper for `piv-tool set-management-key` related options.
type PIVToolSetManagementKeyOptions struct {
OldKey string
NewKey string
RandomKey bool
}
var _ Interface = (*PIVToolSetManagementKeyOptions)(nil)
// AddFlags implements Interface
func (o *PIVToolSetManagementKeyOptions) AddFlags(cmd *cobra.Command) {
cmd.Flags().StringVar(&o.OldKey, "old-key", "",
"existing management key, uses default if empty")
cmd.Flags().StringVar(&o.NewKey, "new-key", "",
"new management key, uses default if empty")
cmd.Flags().BoolVar(&o.RandomKey, "random-management-key", false,
"if set to true, generates a new random management key and deletes it after")
}
// PIVToolSetPINOptions is the wrapper for `piv-tool set-pin` related options.
type PIVToolSetPINOptions struct {
OldPIN string
NewPIN string
}
var _ Interface = (*PIVToolSetPINOptions)(nil)
// AddFlags implements Interface
func (o *PIVToolSetPINOptions) AddFlags(cmd *cobra.Command) {
cmd.Flags().StringVar(&o.OldPIN, "old-pin", "",
"existing PIN, uses default if empty")
cmd.Flags().StringVar(&o.NewPIN, "new-pin", "",
"new PIN, uses default if empty")
}
// PIVToolSetPUKOptions is the wrapper for `piv-tool set-puk` related options.
type PIVToolSetPUKOptions struct {
OldPUK string
NewPUK string
}
var _ Interface = (*PIVToolSetPUKOptions)(nil)
// AddFlags implements Interface
func (o *PIVToolSetPUKOptions) AddFlags(cmd *cobra.Command) {
cmd.Flags().StringVar(&o.OldPUK, "old-puk", "",
"existing PUK, uses default if empty")
cmd.Flags().StringVar(&o.NewPUK, "new-puk", "",
"new PUK, uses default if empty")
}
// PIVToolUnblockOptions is the wrapper for `piv-tool unblock` related options.
type PIVToolUnblockOptions struct {
PUK string
NewPIN string
}
var _ Interface = (*PIVToolUnblockOptions)(nil)
// AddFlags implements Interface
func (o *PIVToolUnblockOptions) AddFlags(cmd *cobra.Command) {
cmd.Flags().StringVar(&o.PUK, "puk", "",
"existing PUK, uses default if empty")
cmd.Flags().StringVar(&o.NewPIN, "new-PIN", "",
"new PIN, uses default if empty")
}
// PIVToolAttestationOptions is the wrapper for `piv-tool attestation` related options.
type PIVToolAttestationOptions struct {
Output string
Slot string
}
var _ Interface = (*PIVToolAttestationOptions)(nil)
// AddFlags implements Interface
func (o *PIVToolAttestationOptions) AddFlags(cmd *cobra.Command) {
cmd.Flags().StringVarP(&o.Output, "output", "o", "text",
"format to output attestation information in. (text|json)")
cmd.Flags().StringVar(&o.Slot, "slot", "",
"Slot to use for generated key (authentication|signature|card-authentication|key-management)")
}
// PIVToolGenerateKeyOptions is the wrapper for `piv-tool generate-key` related options.
type PIVToolGenerateKeyOptions struct {
ManagementKey string
RandomKey bool
Slot string
PINPolicy string
TouchPolicy string
}
var _ Interface = (*PIVToolGenerateKeyOptions)(nil)
// AddFlags implements Interface
func (o *PIVToolGenerateKeyOptions) AddFlags(cmd *cobra.Command) {
cmd.Flags().StringVar(&o.ManagementKey, "management-key", "",
"management key, uses default if empty")
cmd.Flags().BoolVar(&o.RandomKey, "random-management-key", false,
"if set to true, generates a new random management key and deletes it after")
cmd.Flags().StringVar(&o.Slot, "slot", "",
"Slot to use for generated key (authentication|signature|card-authentication|key-management)")
cmd.Flags().StringVar(&o.PINPolicy, "pin-policy", "",
"PIN policy for slot (never|once|always)")
cmd.Flags().StringVar(&o.TouchPolicy, "touch-policy", "",
"Touch policy for slot (never|always|cached)")
}
//
// Copyright 2021 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 options
import (
"github.com/sigstore/cosign/v3/pkg/cosign/env"
"github.com/spf13/cobra"
)
// PKCS11ToolListTokens is the wrapper for `pkcs11-tool list-tokens` related options.
type PKCS11ToolListTokensOptions struct {
ModulePath string
}
var _ Interface = (*PKCS11ToolListTokensOptions)(nil)
// AddFlags implements Interface
func (o *PKCS11ToolListTokensOptions) AddFlags(cmd *cobra.Command) {
cmd.Flags().StringVar(&o.ModulePath, "module-path", env.Getenv(env.VariablePKCS11ModulePath),
"absolute path to the PKCS11 module")
_ = cmd.MarkFlagFilename("module-path", moduleExts...)
}
// PKCS11ToolListKeysUrisOptions is the wrapper for `pkcs11-tool list-keys-uris` related options.
type PKCS11ToolListKeysUrisOptions struct {
ModulePath string
SlotID uint
Pin string
}
var _ Interface = (*PKCS11ToolListKeysUrisOptions)(nil)
// AddFlags implements Interface
func (o *PKCS11ToolListKeysUrisOptions) AddFlags(cmd *cobra.Command) {
cmd.Flags().StringVar(&o.ModulePath, "module-path", env.Getenv(env.VariablePKCS11ModulePath),
"absolute path to the PKCS11 module")
_ = cmd.MarkFlagFilename("module-path", moduleExts...)
cmd.Flags().UintVar(&o.SlotID, "slot-id", 0,
"id of the PKCS11 slot, uses 0 if empty")
cmd.Flags().StringVar(&o.Pin, "pin", "",
"pin of the PKCS11 slot, uses environment variable COSIGN_PKCS11_PIN if empty")
}
//
// Copyright 2021 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 options
import (
"fmt"
"net/url"
"github.com/in-toto/in-toto-golang/in_toto"
slsa02 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.2"
slsa1 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v1"
"github.com/sigstore/cosign/v3/pkg/cosign/attestation"
"github.com/spf13/cobra"
)
const (
PredicateCustom = "custom"
PredicateSLSA = "slsaprovenance"
PredicateSLSA02 = "slsaprovenance02"
PredicateSLSA1 = "slsaprovenance1"
PredicateSPDX = "spdx"
PredicateSPDXJSON = "spdxjson"
PredicateCycloneDX = "cyclonedx"
PredicateLink = "link"
PredicateVuln = "vuln"
PredicateOpenVEX = "openvex"
)
// PredicateTypeMap is the mapping between the predicate `type` option to predicate URI.
var PredicateTypeMap = map[string]string{
PredicateCustom: attestation.CosignCustomProvenanceV01,
PredicateSLSA: slsa02.PredicateSLSAProvenance,
PredicateSLSA02: slsa02.PredicateSLSAProvenance,
PredicateSLSA1: slsa1.PredicateSLSAProvenance,
PredicateSPDX: in_toto.PredicateSPDX,
PredicateSPDXJSON: in_toto.PredicateSPDX,
PredicateCycloneDX: in_toto.PredicateCycloneDX,
PredicateLink: in_toto.PredicateLinkV1,
PredicateVuln: attestation.CosignVulnProvenanceV01,
PredicateOpenVEX: attestation.OpenVexNamespace,
}
// PredicateOptions is the wrapper for predicate related options.
type PredicateOptions struct {
Type string
}
var _ Interface = (*PredicateOptions)(nil)
// AddFlags implements Interface
func (o *PredicateOptions) AddFlags(cmd *cobra.Command) {
cmd.Flags().StringVar(&o.Type, "type", "custom",
"specify a predicate type (slsaprovenance|slsaprovenance02|slsaprovenance1|link|spdx|spdxjson|cyclonedx|vuln|openvex|custom) or an URI")
}
// ParsePredicateType parses the predicate `type` flag passed into a predicate URI, or validates `type` is a valid URI.
func ParsePredicateType(t string) (string, error) {
uri, ok := PredicateTypeMap[t]
if !ok {
if _, err := url.ParseRequestURI(t); err != nil {
return "", fmt.Errorf("invalid predicate type: %s", t)
}
uri = t
}
return uri, nil
}
// PredicateLocalOptions is the wrapper for predicate related options.
type PredicateLocalOptions struct {
PredicateOptions
Path string
Statement string
}
var _ Interface = (*PredicateLocalOptions)(nil)
// AddFlags implements Interface
func (o *PredicateLocalOptions) AddFlags(cmd *cobra.Command) {
o.PredicateOptions.AddFlags(cmd)
cmd.Flags().StringVar(&o.Path, "predicate", "",
"path to the predicate file.")
cmd.Flags().StringVar(&o.Statement, "statement", "",
"path to the statement file.")
cmd.MarkFlagsOneRequired("predicate", "statement")
}
// PredicateRemoteOptions is the wrapper for remote predicate related options.
type PredicateRemoteOptions struct {
PredicateOptions
}
var _ Interface = (*PredicateRemoteOptions)(nil)
// AddFlags implements Interface
func (o *PredicateRemoteOptions) AddFlags(cmd *cobra.Command) {
o.PredicateOptions.AddFlags(cmd)
}
//
// Copyright 2021 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 options
import (
"github.com/spf13/cobra"
)
// PublicKeyOptions is the top level wrapper for the public-key command.
type PublicKeyOptions struct {
Key string
SecurityKey SecurityKeyOptions
OutFile string
}
var _ Interface = (*PublicKeyOptions)(nil)
// AddFlags implements Interface
func (o *PublicKeyOptions) AddFlags(cmd *cobra.Command) {
o.SecurityKey.AddFlags(cmd)
cmd.Flags().StringVar(&o.Key, "key", "",
"path to the private key file, KMS URI or Kubernetes Secret")
_ = cmd.MarkFlagFilename("key", privateKeyExts...)
cmd.Flags().StringVar(&o.OutFile, "outfile", "",
"path to a payload file to use rather than generating one")
_ = cmd.MarkFlagFilename("outfile", publicKeyExts...)
}
//
// Copyright 2021 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 options
import (
"github.com/spf13/cobra"
)
// ReferenceOptions is a wrapper for image reference options.
type ReferenceOptions struct {
TagPrefix string
}
var _ Interface = (*ReferenceOptions)(nil)
// AddFlags implements Interface
func (o *ReferenceOptions) AddFlags(cmd *cobra.Command) {
cmd.Flags().StringVar(&o.TagPrefix, "attachment-tag-prefix", "", "optional custom prefix to use for attached image tags. Attachment images are tagged as: `[AttachmentTagPrefix]sha256-[TargetImageDigest].[AttachmentName]`")
}
// Copyright 2021 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 options
import (
"context"
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"io"
"net/http"
"os"
ecr "github.com/awslabs/amazon-ecr-credential-helper/ecr-login"
"github.com/chrismellard/docker-credential-acr-env/pkg/credhelper"
"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/authn/github"
"github.com/google/go-containerregistry/pkg/name"
"github.com/google/go-containerregistry/pkg/v1/google"
"github.com/google/go-containerregistry/pkg/v1/remote"
alibabaacr "github.com/mozillazg/docker-credential-acr-helper/pkg/credhelper"
ociremote "github.com/sigstore/cosign/v3/pkg/oci/remote"
"github.com/spf13/cobra"
)
// Keychain is an alias of authn.Keychain to expose this configuration option to consumers of this lib
type Keychain = authn.Keychain
// RegistryOptions is the wrapper for the registry options.
type RegistryOptions struct {
AllowInsecure bool
AllowHTTPRegistry bool
KubernetesKeychain bool
RefOpts ReferenceOptions
Keychain Keychain
AuthConfig authn.AuthConfig
RegistryCACert string
RegistryClientCert string
RegistryClientKey string
RegistryServerName string
// RegistryClientOpts allows overriding the result of GetRegistryClientOpts.
RegistryClientOpts []remote.Option
}
var _ Interface = (*RegistryOptions)(nil)
// AddFlags implements Interface
func (o *RegistryOptions) AddFlags(cmd *cobra.Command) {
cmd.Flags().BoolVar(&o.AllowInsecure, "allow-insecure-registry", false,
"whether to allow insecure connections to registries (e.g., with expired or self-signed TLS certificates). Don't use this for anything but testing")
cmd.Flags().BoolVar(&o.AllowHTTPRegistry, "allow-http-registry", false,
"whether to allow using HTTP protocol while connecting to registries. Don't use this for anything but testing")
cmd.Flags().BoolVar(&o.KubernetesKeychain, "k8s-keychain", false,
"whether to use the kubernetes keychain instead of the default keychain (supports workload identity).")
cmd.Flags().StringVar(&o.AuthConfig.Username, "registry-username", "",
"registry basic auth username")
cmd.Flags().StringVar(&o.AuthConfig.Password, "registry-password", "",
"registry basic auth password")
cmd.Flags().StringVar(&o.AuthConfig.RegistryToken, "registry-token", "",
"registry bearer auth token")
cmd.Flags().StringVar(&o.RegistryCACert, "registry-cacert", "",
"path to the X.509 CA certificate file in PEM format to be used for the connection to the registry")
_ = cmd.MarkFlagFilename("registry-cacert", certificateExts...)
cmd.Flags().StringVar(&o.RegistryClientCert, "registry-client-cert", "",
"path to the X.509 certificate file in PEM format to be used for the connection to the registry")
_ = cmd.MarkFlagFilename("registry-client-cert", certificateExts...)
cmd.Flags().StringVar(&o.RegistryClientKey, "registry-client-key", "",
"path to the X.509 private key file in PEM format to be used, together with the 'registry-client-cert' value, for the connection to the registry")
_ = cmd.MarkFlagFilename("registry-client-key", privateKeyExts...)
cmd.Flags().StringVar(&o.RegistryServerName, "registry-server-name", "",
"SAN name to use as the 'ServerName' tls.Config field to verify the mTLS connection to the registry")
o.RefOpts.AddFlags(cmd)
}
func (o *RegistryOptions) ClientOpts(ctx context.Context) ([]ociremote.Option, error) {
opts := []ociremote.Option{ociremote.WithRemoteOptions(o.GetRegistryClientOpts(ctx)...)}
if o.RefOpts.TagPrefix != "" {
opts = append(opts, ociremote.WithPrefix(o.RefOpts.TagPrefix))
}
targetRepoOverride, err := ociremote.GetEnvTargetRepository()
if err != nil {
return nil, err
}
if (targetRepoOverride != name.Repository{}) {
opts = append(opts, ociremote.WithTargetRepository(targetRepoOverride))
}
return opts, nil
}
func (o *RegistryOptions) NameOptions() []name.Option {
var nameOpts []name.Option
if o.AllowHTTPRegistry {
nameOpts = append(nameOpts, name.Insecure)
}
return nameOpts
}
func (o *RegistryOptions) GetRegistryClientOpts(ctx context.Context) []remote.Option {
if o.RegistryClientOpts != nil {
ropts := o.RegistryClientOpts
ropts = append(ropts, remote.WithContext(ctx))
return ropts
}
opts := []remote.Option{
remote.WithContext(ctx),
remote.WithUserAgent(UserAgent()),
}
switch {
case o.Keychain != nil:
opts = append(opts, remote.WithAuthFromKeychain(o.Keychain))
case o.KubernetesKeychain:
kc := authn.NewMultiKeychain(
authn.DefaultKeychain,
google.Keychain,
authn.NewKeychainFromHelper(ecr.NewECRHelper(ecr.WithLogger(io.Discard))),
authn.NewKeychainFromHelper(credhelper.NewACRCredentialsHelper()),
authn.NewKeychainFromHelper(alibabaacr.NewACRHelper().WithLoggerOut(io.Discard)),
github.Keychain,
)
opts = append(opts, remote.WithAuthFromKeychain(kc))
case o.AuthConfig.Username != "" && o.AuthConfig.Password != "":
opts = append(opts, remote.WithAuth(&authn.Basic{Username: o.AuthConfig.Username, Password: o.AuthConfig.Password}))
case o.AuthConfig.RegistryToken != "":
opts = append(opts, remote.WithAuth(&authn.Bearer{Token: o.AuthConfig.RegistryToken}))
default:
opts = append(opts, remote.WithAuthFromKeychain(authn.DefaultKeychain))
}
tlsConfig, err := o.getTLSConfig()
if err == nil {
tr := http.DefaultTransport.(*http.Transport).Clone()
tr.TLSClientConfig = tlsConfig
opts = append(opts, remote.WithTransport(tr))
}
// Reuse a remote.Pusher and a remote.Puller for all operations that use these opts.
// This allows us to avoid re-authenticating for everying remote.Function we call,
// which speeds things up a whole lot.
pusher, err := remote.NewPusher(opts...)
if err == nil {
opts = append(opts, remote.Reuse(pusher))
}
puller, err := remote.NewPuller(opts...)
if err == nil {
opts = append(opts, remote.Reuse(puller))
}
return opts
}
type RegistryReferrersMode string
const (
RegistryReferrersModeLegacy RegistryReferrersMode = "legacy"
RegistryReferrersModeOCI11 RegistryReferrersMode = "oci-1-1"
)
func (e *RegistryReferrersMode) String() string {
return string(*e)
}
func (e *RegistryReferrersMode) Set(v string) error {
switch v {
case "legacy":
*e = RegistryReferrersMode(v)
return nil
case "oci-1-1":
if !EnableExperimental() {
return fmt.Errorf(`in order to use mode "%s", you must set COSIGN_EXPERIMENTAL=1`, v)
}
*e = RegistryReferrersMode(v)
return nil
default:
return errors.New(`must be one of "legacy", "oci-1-1"`)
}
}
func (e *RegistryReferrersMode) Type() string {
return "registryReferrersMode"
}
// RegistryExperimentalOptions is the wrapper for the registry experimental options.
type RegistryExperimentalOptions struct {
RegistryReferrersMode RegistryReferrersMode
}
var _ Interface = (*RegistryExperimentalOptions)(nil)
// AddFlags implements Interface
func (o *RegistryExperimentalOptions) AddFlags(cmd *cobra.Command) {
cmd.Flags().Var(&o.RegistryReferrersMode, "registry-referrers-mode",
"mode for fetching references from the registry. allowed: legacy, oci-1-1")
}
func (o *RegistryOptions) getTLSConfig() (*tls.Config, error) {
var tlsConfig tls.Config
if o.RegistryCACert != "" {
f, err := os.Open(o.RegistryCACert)
if err != nil {
return nil, err
}
defer f.Close()
caCertBytes, err := io.ReadAll(f)
if err != nil {
return nil, fmt.Errorf("unable to read CA certs from %s: %w", o.RegistryCACert, err)
}
pool := x509.NewCertPool()
if !pool.AppendCertsFromPEM(caCertBytes) {
return nil, fmt.Errorf("no valid CA certs found in %s", o.RegistryCACert)
}
tlsConfig.RootCAs = pool
}
if o.RegistryClientCert != "" && o.RegistryClientKey != "" {
cert, err := tls.LoadX509KeyPair(o.RegistryClientCert, o.RegistryClientKey)
if err != nil {
return nil, fmt.Errorf("unable to read client certs from cert %s, key %s: %w",
o.RegistryClientCert, o.RegistryClientKey, err)
}
tlsConfig.Certificates = []tls.Certificate{cert}
}
if o.RegistryServerName != "" {
tlsConfig.ServerName = o.RegistryServerName
}
tlsConfig.InsecureSkipVerify = o.AllowInsecure // #nosec G402
return &tlsConfig, nil
}
//
// Copyright 2021 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 options
import (
"github.com/spf13/cobra"
)
const DefaultRekorURL = "https://rekor.sigstore.dev"
// RekorOptions is the wrapper for Rekor related options.
type RekorOptions struct {
URL string
}
var _ Interface = (*RekorOptions)(nil)
// AddFlags implements Interface
func (o *RekorOptions) AddFlags(cmd *cobra.Command) {
cmd.Flags().StringVar(&o.URL, "rekor-url", DefaultRekorURL,
"address of rekor STL server")
}
//
// Copyright 2021 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 options
import (
"fmt"
"os"
"strings"
"time"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/spf13/viper"
)
const EnvPrefix = "COSIGN"
// RootOptions define flags and options for the root cosign cli.
type RootOptions struct {
OutputFile string
Verbose bool
Timeout time.Duration
}
// DefaultTimeout specifies the default timeout for commands.
const DefaultTimeout = 3 * time.Minute
var _ Interface = (*RootOptions)(nil)
// AddFlags implements Interface
func (o *RootOptions) AddFlags(cmd *cobra.Command) {
cmd.PersistentFlags().StringVar(&o.OutputFile, "output-file", "",
"log output to a file")
_ = cmd.MarkFlagFilename("output-file", logExts...)
cmd.PersistentFlags().BoolVarP(&o.Verbose, "verbose", "d", false,
"log debug output")
cmd.PersistentFlags().DurationVarP(&o.Timeout, "timeout", "t", DefaultTimeout,
"timeout for commands")
}
func BindViper(cmd *cobra.Command, args []string) {
callPersistentPreRun(cmd, args)
v := viper.New()
v.SetEnvPrefix(EnvPrefix)
v.AutomaticEnv()
bindFlags(cmd, v)
}
// callPersistentPreRun calls parent commands. PersistentPreRun
// does not call parents PersistentPreRun functions
func callPersistentPreRun(cmd *cobra.Command, args []string) {
if parent := cmd.Parent(); parent != nil {
if parent.PersistentPreRun != nil {
parent.PersistentPreRun(parent, args)
}
if parent.PersistentPreRunE != nil {
err := parent.PersistentPreRunE(parent, args)
if err != nil {
cmd.PrintErrln("Error:", err.Error())
os.Exit(1)
}
}
callPersistentPreRun(parent, args)
}
}
func bindFlags(cmd *cobra.Command, v *viper.Viper) {
cmd.Flags().VisitAll(func(f *pflag.Flag) {
if strings.Contains(f.Name, "-") {
_ = v.BindEnv(f.Name, flagToEnvVar(f.Name))
}
if !f.Changed && v.IsSet((f.Name)) {
val := v.Get(f.Name)
_ = cmd.Flags().Set(f.Name, fmt.Sprintf("%v", val))
}
})
}
func flagToEnvVar(f string) string {
f = strings.ToUpper(f)
return fmt.Sprintf("%s_%s", EnvPrefix, strings.ReplaceAll(f, "-", "_"))
}
//
// Copyright 2021 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 options
import (
"github.com/spf13/cobra"
)
// SaveOptions is the top level wrapper for the load command.
type SaveOptions struct {
Directory string
Registry RegistryOptions
}
var _ Interface = (*SaveOptions)(nil)
// AddFlags implements Interface
func (o *SaveOptions) AddFlags(cmd *cobra.Command) {
o.Registry.AddFlags(cmd)
cmd.Flags().StringVar(&o.Directory, "dir", "",
"path to dir where the signed image should be stored on disk")
_ = cmd.MarkFlagDirname("dir")
_ = cmd.MarkFlagRequired("dir")
}
//
// Copyright 2021 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 options
import (
"github.com/spf13/cobra"
)
// SecurityKeyOptions is the wrapper for security key related options.
type SecurityKeyOptions struct {
Use bool
Slot string
}
var _ Interface = (*SecurityKeyOptions)(nil)
// AddFlags implements Interface
func (o *SecurityKeyOptions) AddFlags(cmd *cobra.Command) {
cmd.Flags().BoolVar(&o.Use, "sk", false,
"whether to use a hardware security key")
cmd.Flags().StringVar(&o.Slot, "slot", "",
"security key slot to use for generated key (default: signature) (authentication|signature|card-authentication|key-management)")
}
//
// Copyright 2021 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 options
import (
"github.com/spf13/cobra"
)
// SignOptions is the top level wrapper for the sign command.
type SignOptions struct {
Key string
Cert string
CertChain string
Upload bool
Output string // deprecated: TODO remove when the output flag is fully deprecated
OutputSignature string // TODO: this should be the root output file arg.
OutputPayload string
OutputCertificate string
BundlePath string
PayloadPath string
Recursive bool
Attachment string
SkipConfirmation bool
TlogUpload bool
TSAClientCACert string
TSAClientCert string
TSAClientKey string
TSAServerName string
TSAServerURL string
IssueCertificate bool
SignContainerIdentities []string
RecordCreationTimestamp bool
NewBundleFormat bool
UseSigningConfig bool
SigningConfigPath string
TrustedRootPath string
Rekor RekorOptions
Fulcio FulcioOptions
OIDC OIDCOptions
SecurityKey SecurityKeyOptions
AnnotationOptions
Registry RegistryOptions
RegistryExperimental RegistryExperimentalOptions
}
var _ Interface = (*SignOptions)(nil)
// AddFlags implements Interface
func (o *SignOptions) AddFlags(cmd *cobra.Command) {
o.Rekor.AddFlags(cmd)
o.Fulcio.AddFlags(cmd)
o.OIDC.AddFlags(cmd)
o.SecurityKey.AddFlags(cmd)
o.AnnotationOptions.AddFlags(cmd)
o.Registry.AddFlags(cmd)
o.RegistryExperimental.AddFlags(cmd)
cmd.Flags().StringVar(&o.Key, "key", "",
"path to the private key file, KMS URI or Kubernetes Secret")
_ = cmd.MarkFlagFilename("key", privateKeyExts...)
cmd.Flags().StringVar(&o.Cert, "certificate", "",
"path to the X.509 certificate in PEM format to include in the OCI Signature")
_ = cmd.MarkFlagFilename("certificate", certificateExts...)
cmd.Flags().StringVar(&o.CertChain, "certificate-chain", "",
"path to a list of CA X.509 certificates in PEM format which will be needed "+
"when building the certificate chain for the signing certificate. "+
"Must start with the parent intermediate CA certificate of the "+
"signing certificate and end with the root certificate. Included in the OCI Signature")
_ = cmd.MarkFlagFilename("certificate-chain", certificateExts...)
cmd.Flags().BoolVar(&o.Upload, "upload", true,
"whether to upload the signature")
cmd.Flags().StringVar(&o.OutputSignature, "output-signature", "",
"write the signature to FILE")
_ = cmd.MarkFlagFilename("output-signature", signatureExts...)
cmd.Flags().StringVar(&o.OutputPayload, "output-payload", "",
"write the signed payload to FILE")
// _ = cmd.MarkFlagFilename("output-payload") // no typical extensions
cmd.Flags().StringVar(&o.OutputCertificate, "output-certificate", "",
"write the certificate to FILE")
_ = cmd.MarkFlagFilename("output-certificate", certificateExts...)
cmd.Flags().StringVar(&o.BundlePath, "bundle", "",
"write everything required to verify the image to FILE")
_ = cmd.MarkFlagFilename("bundle", bundleExts...)
cmd.Flags().StringVar(&o.PayloadPath, "payload", "",
"path to a payload file to use rather than generating one")
// _ = cmd.MarkFlagFilename("payload") // no typical extensions
cmd.Flags().BoolVarP(&o.Recursive, "recursive", "r", false,
"if a multi-arch image is specified, additionally sign each discrete image")
cmd.Flags().StringVar(&o.Attachment, "attachment", "",
"DEPRECATED, related image attachment to sign (sbom), default none")
_ = cmd.MarkFlagFilename("attachment", sbomExts...)
cmd.Flags().BoolVarP(&o.SkipConfirmation, "yes", "y", false,
"skip confirmation prompts for non-destructive operations")
cmd.Flags().BoolVar(&o.TlogUpload, "tlog-upload", true,
"whether or not to upload to the tlog")
_ = cmd.Flags().MarkDeprecated("tlog-upload", "prefer using a --signing-config file with no transparency log services")
cmd.Flags().StringVar(&o.TSAClientCACert, "timestamp-client-cacert", "",
"path to the X.509 CA certificate file in PEM format to be used for the connection to the TSA Server")
_ = cmd.MarkFlagFilename("timestamp-client-cacert", certificateExts...)
cmd.Flags().StringVar(&o.TSAClientCert, "timestamp-client-cert", "",
"path to the X.509 certificate file in PEM format to be used for the connection to the TSA Server")
_ = cmd.MarkFlagFilename("timestamp-client-cert", certificateExts...)
cmd.Flags().StringVar(&o.TSAClientKey, "timestamp-client-key", "",
"path to the X.509 private key file in PEM format to be used, together with the 'timestamp-client-cert' value, for the connection to the TSA Server")
_ = cmd.MarkFlagFilename("timestamp-client-key", privateKeyExts...)
cmd.Flags().StringVar(&o.TSAServerName, "timestamp-server-name", "",
"SAN name to use as the 'ServerName' tls.Config field to verify the mTLS connection to the TSA Server")
cmd.Flags().StringVar(&o.TSAServerURL, "timestamp-server-url", "",
"url to the Timestamp RFC3161 server, default none. Must be the path to the API to request timestamp responses, e.g. https://freetsa.org/tsr")
_ = cmd.MarkFlagFilename("certificate", certificateExts...)
cmd.Flags().BoolVar(&o.IssueCertificate, "issue-certificate", false,
"issue a code signing certificate from Fulcio, even if a key is provided")
cmd.Flags().StringSliceVar(&o.SignContainerIdentities, "sign-container-identity", nil,
"manually set the .critical.docker-reference field for the signed identity, which is useful when image proxies are being used where the pull reference should match the signature, this flag is comma delimited. ex: --sign-container-identity=identity1,identity2")
cmd.Flags().BoolVar(&o.RecordCreationTimestamp, "record-creation-timestamp", false, "set the createdAt timestamp in the signature artifact to the time it was created; by default, cosign sets this to the zero value")
cmd.Flags().BoolVar(&o.NewBundleFormat, "new-bundle-format", true, "expect the signature/attestation to be packaged in a Sigstore bundle")
cmd.Flags().BoolVar(&o.UseSigningConfig, "use-signing-config", true,
"whether to use a TUF-provided signing config for the service URLs. Must set --new-bundle-format, which will store verification material in the new format")
cmd.Flags().StringVar(&o.SigningConfigPath, "signing-config", "",
"path to a signing config file. Must provide --new-bundle-format, which will store verification material in the new format")
cmd.MarkFlagsMutuallyExclusive("use-signing-config", "signing-config")
cmd.Flags().StringVar(&o.TrustedRootPath, "trusted-root", "",
"optional path to a TrustedRoot JSON file to verify a signature after signing")
}
//
// Copyright 2021 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 options
import (
"crypto"
_ "crypto/sha256" // for sha224 + sha256
_ "crypto/sha512" // for sha384 + sha512
"fmt"
"sort"
"strings"
"github.com/spf13/cobra"
)
var supportedSignatureAlgorithms = map[string]crypto.Hash{
"sha224": crypto.SHA224,
"sha256": crypto.SHA256,
"sha384": crypto.SHA384,
"sha512": crypto.SHA512,
}
func supportedSignatureAlgorithmNames() []string {
names := make([]string, 0, len(supportedSignatureAlgorithms))
for name := range supportedSignatureAlgorithms {
names = append(names, name)
}
sort.Strings(names)
return names
}
// SignatureDigestOptions holds options for specifying which digest algorithm should
// be used when processing a signature.
type SignatureDigestOptions struct {
AlgorithmName string
}
var _ Interface = (*SignatureDigestOptions)(nil)
// AddFlags implements Interface
func (o *SignatureDigestOptions) AddFlags(cmd *cobra.Command) {
validSignatureDigestAlgorithms := strings.Join(supportedSignatureAlgorithmNames(), "|")
cmd.Flags().StringVar(&o.AlgorithmName, "signature-digest-algorithm", "sha256",
fmt.Sprintf("digest algorithm to use when processing a signature (%s)", validSignatureDigestAlgorithms))
}
// HashAlgorithm converts the algorithm's name - provided as a string - into a crypto.Hash algorithm.
// Returns an error if the algorithm name doesn't match a supported algorithm, and defaults to SHA256
// in the event that the given algorithm is invalid.
func (o *SignatureDigestOptions) HashAlgorithm() (crypto.Hash, error) {
normalizedAlgo := strings.ToLower(strings.TrimSpace(o.AlgorithmName))
if normalizedAlgo == "" {
return crypto.SHA256, nil
}
algo, exists := supportedSignatureAlgorithms[normalizedAlgo]
if !exists {
return crypto.SHA256, fmt.Errorf("unknown digest algorithm: %s", o.AlgorithmName)
}
if !algo.Available() {
return crypto.SHA256, fmt.Errorf("hash %q is not available on this platform", o.AlgorithmName)
}
return algo, nil
}
//
// Copyright 2021 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 options
import (
"fmt"
"strings"
"github.com/sigstore/cosign/v3/pkg/cosign"
v1 "github.com/sigstore/protobuf-specs/gen/pb-go/common/v1"
"github.com/sigstore/sigstore/pkg/signature"
"github.com/spf13/cobra"
)
// SignBlobOptions is the top level wrapper for the sign-blob command.
// The new output-certificate flag is only in use when COSIGN_EXPERIMENTAL is enabled
type SignBlobOptions struct {
Key string
Base64Output bool
Output string // deprecated: TODO remove when the output flag is fully deprecated
OutputSignature string // TODO: this should be the root output file arg.
OutputCertificate string
SecurityKey SecurityKeyOptions
Fulcio FulcioOptions
Rekor RekorOptions
OIDC OIDCOptions
Registry RegistryOptions
BundlePath string
NewBundleFormat bool
SkipConfirmation bool
TlogUpload bool
TSAClientCACert string
TSAClientCert string
TSAClientKey string
TSAServerName string
TSAServerURL string
RFC3161TimestampPath string
IssueCertificate bool
SigningAlgorithm string
UseSigningConfig bool
SigningConfigPath string
TrustedRootPath string
}
var _ Interface = (*SignBlobOptions)(nil)
// AddFlags implements Interface
func (o *SignBlobOptions) AddFlags(cmd *cobra.Command) {
o.SecurityKey.AddFlags(cmd)
o.Fulcio.AddFlags(cmd)
o.Rekor.AddFlags(cmd)
o.OIDC.AddFlags(cmd)
cmd.Flags().StringVar(&o.Key, "key", "",
"path to the private key file, KMS URI or Kubernetes Secret")
_ = cmd.MarkFlagFilename("key", privateKeyExts...)
cmd.Flags().BoolVar(&o.Base64Output, "b64", true,
"whether to base64 encode the output")
cmd.Flags().StringVar(&o.OutputSignature, "output-signature", "",
"write the signature to FILE")
_ = cmd.MarkFlagFilename("output-signature", signatureExts...)
// TODO: remove when output flag is fully deprecated
cmd.Flags().StringVar(&o.Output, "output", "", "write the signature to FILE")
_ = cmd.MarkFlagFilename("output", signatureExts...)
cmd.Flags().StringVar(&o.OutputCertificate, "output-certificate", "",
"write the certificate to FILE")
_ = cmd.MarkFlagFilename("output-certificate", certificateExts...)
cmd.Flags().StringVar(&o.BundlePath, "bundle", "",
"write everything required to verify the blob to a FILE")
_ = cmd.MarkFlagFilename("bundle", bundleExts...)
cmd.Flags().BoolVar(&o.NewBundleFormat, "new-bundle-format", true,
"output bundle in new format that contains all verification material")
cmd.Flags().BoolVar(&o.UseSigningConfig, "use-signing-config", true,
"whether to use a TUF-provided signing config for the service URLs. Must provide --bundle, which will output verification material in the new format")
cmd.Flags().StringVar(&o.SigningConfigPath, "signing-config", "",
"path to a signing config file. Must provide --bundle, which will output verification material in the new format")
cmd.MarkFlagsMutuallyExclusive("use-signing-config", "signing-config")
cmd.Flags().StringVar(&o.TrustedRootPath, "trusted-root", "",
"optional path to a TrustedRoot JSON file to verify a signature after signing")
cmd.Flags().BoolVarP(&o.SkipConfirmation, "yes", "y", false,
"skip confirmation prompts for non-destructive operations")
cmd.Flags().BoolVar(&o.TlogUpload, "tlog-upload", true,
"whether or not to upload to the tlog")
_ = cmd.Flags().MarkDeprecated("tlog-upload", "prefer using a --signing-config file with no transparency log services")
cmd.Flags().StringVar(&o.TSAClientCACert, "timestamp-client-cacert", "",
"path to the X.509 CA certificate file in PEM format to be used for the connection to the TSA Server")
_ = cmd.MarkFlagFilename("timestamp-client-cacert", certificateExts...)
cmd.Flags().StringVar(&o.TSAClientCert, "timestamp-client-cert", "",
"path to the X.509 certificate file in PEM format to be used for the connection to the TSA Server")
_ = cmd.MarkFlagFilename("timestamp-client-cert", certificateExts...)
cmd.Flags().StringVar(&o.TSAClientKey, "timestamp-client-key", "",
"path to the X.509 private key file in PEM format to be used, together with the 'timestamp-client-cert' value, for the connection to the TSA Server")
_ = cmd.MarkFlagFilename("timestamp-client-key", privateKeyExts...)
cmd.Flags().StringVar(&o.TSAServerName, "timestamp-server-name", "",
"SAN name to use as the 'ServerName' tls.Config field to verify the mTLS connection to the TSA Server")
_ = cmd.RegisterFlagCompletionFunc("timestamp-server-name", cobra.NoFileCompletions)
cmd.Flags().StringVar(&o.TSAServerURL, "timestamp-server-url", "",
"url to the Timestamp RFC3161 server, default none. Must be the path to the API to request timestamp responses, e.g. https://freetsa.org/tsr")
_ = cmd.RegisterFlagCompletionFunc("timestamp-server-url", cobra.NoFileCompletions)
cmd.Flags().StringVar(&o.RFC3161TimestampPath, "rfc3161-timestamp", "",
"write the RFC3161 timestamp to a file")
// _ = cmd.MarkFlagFilename("rfc3161-timestamp") // no typical extensions
cmd.Flags().BoolVar(&o.IssueCertificate, "issue-certificate", false,
"issue a code signing certificate from Fulcio, even if a key is provided")
keyAlgorithmTypes := cosign.GetSupportedAlgorithms()
keyAlgorithmHelp := fmt.Sprintf("signing algorithm to use for signing/hashing (allowed %s)", strings.Join(keyAlgorithmTypes, ", "))
defaultKeyFlag, _ := signature.FormatSignatureAlgorithmFlag(v1.PublicKeyDetails_PKIX_ECDSA_P256_SHA_256)
cmd.Flags().StringVar(&o.SigningAlgorithm, "signing-algorithm", defaultKeyFlag, keyAlgorithmHelp)
}
// Copyright 2025 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 options
import (
"github.com/spf13/cobra"
)
type SigningConfigCreateOptions struct {
Fulcio []string
Rekor []string
OIDCProvider []string
TSA []string
TSAConfig string
RekorConfig string
Out string
}
var _ Interface = (*SigningConfigCreateOptions)(nil)
func (o *SigningConfigCreateOptions) AddFlags(cmd *cobra.Command) {
cmd.Flags().StringArrayVar(&o.Fulcio, "fulcio", nil,
"fulcio service specification, as a comma-separated key-value list.\nRequired keys: url, api-version (integer), start-time, operator. Optional keys: end-time.")
cmd.Flags().StringArrayVar(&o.Rekor, "rekor", nil,
"rekor service specification, as a comma-separated key-value list.\nRequired keys: url, api-version (integer), start-time, operator. Optional keys: end-time.")
cmd.Flags().StringArrayVar(&o.OIDCProvider, "oidc-provider", nil,
"oidc provider specification, as a comma-separated key-value list.\nRequired keys: url, api-version (integer), start-time, operator. Optional keys: end-time.")
cmd.Flags().StringArrayVar(&o.TSA, "tsa", nil,
"timestamping authority specification, as a comma-separated key-value list.\nRequired keys: url, api-version (integer), start-time, operator. Optional keys: end-time.")
cmd.Flags().StringVar(&o.TSAConfig, "tsa-config", "",
"timestamping authority configuration. Required if --tsa is provided. One of: ANY, ALL, EXACT:<count>")
cmd.Flags().StringVar(&o.RekorConfig, "rekor-config", "",
"rekor configuration. Required if --rekor is provided. One of: ANY, ALL, EXACT:<count>")
cmd.Flags().StringVar(&o.Out, "out", "", "path to output signing config")
}
// 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 options
import "github.com/spf13/cobra"
type TreeOptions struct {
Registry RegistryOptions
RegistryExperimental RegistryExperimentalOptions
CleanType string
ExperimentalOCI11 bool
}
var _ Interface = (*TreeOptions)(nil)
func (c *TreeOptions) AddFlags(cmd *cobra.Command) {
c.Registry.AddFlags(cmd)
c.RegistryExperimental.AddFlags(cmd)
cmd.Flags().BoolVar(&c.ExperimentalOCI11, "experimental-oci11", true,
"set to false to ignore OCI 1.1 behavior")
}
//
// Copyright 2021 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 options
import (
"github.com/spf13/cobra"
)
// TriangulateOptions is the top level wrapper for the triangulate command.
type TriangulateOptions struct {
Type string
Registry RegistryOptions
}
var _ Interface = (*TriangulateOptions)(nil)
// AddFlags implements Interface
func (o *TriangulateOptions) AddFlags(cmd *cobra.Command) {
o.Registry.AddFlags(cmd)
cmd.Flags().StringVar(&o.Type, "type", "signature",
"related attachment to triangulate (attestation|sbom|signature|digest), default signature (sbom is deprecated)")
}
//
// Copyright 2024 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 options
import (
"github.com/spf13/cobra"
)
type TrustedRootCreateOptions struct {
Fulcio []string
CTFE []string
TSA []string
Rekor []string
CertChain []string
FulcioURI []string
CtfeKeyPath []string
CtfeStartTime []string
CtfeEndTime []string
CtfeURL []string
Out string
RekorKeyPath []string
RekorStartTime []string
RekorEndTime []string
RekorURL []string
TSACertChainPath []string
TSAURI []string
}
var _ Interface = (*TrustedRootCreateOptions)(nil)
func (o *TrustedRootCreateOptions) AddFlags(cmd *cobra.Command) {
cmd.Flags().StringArrayVar(&o.Fulcio, "fulcio", nil,
"fulcio service specification, as a comma-separated key-value list.\nRequired keys: url, certificate-chain (path to PEM-encoded certificate chain). Optional keys: start-time, end-time.")
cmd.Flags().StringArrayVar(&o.CTFE, "ctfe", nil,
"ctfe service specification, as a comma-separated key-value list.\nRequired keys: url, public-key (path to PEM-encoded public key), start-time. Optional keys: end-time.")
cmd.Flags().StringArrayVar(&o.TSA, "tsa", nil,
"timestamping authority specification, as a comma-separated key-value list.\nRequired keys: url, certificate-chain (path to PEM-encoded certificate chain). Optional keys: start-time, end-time.")
cmd.Flags().StringArrayVar(&o.Rekor, "rekor", nil,
"rekor service specification, as a comma-separated key-value list.\nRequired keys: url, public-key (path to PEM-encoded public key), start-time. Optional keys: end-time, origin.")
cmd.Flags().StringArrayVar(&o.CertChain, "certificate-chain", nil,
"path to a list of CA certificates in PEM format which will be needed "+
"when building the certificate chain for the signing certificate. "+
"Must start with the parent intermediate CA certificate of the "+
"signing certificate and end with the root certificate.")
_ = cmd.MarkFlagFilename("certificate-chain", certificateExts...)
_ = cmd.Flags().MarkDeprecated("certificate-chain", "use --fulcio instead")
cmd.Flags().StringArrayVar(&o.FulcioURI, "fulcio-uri", nil,
"URI of the Fulcio server issuing certificates.")
_ = cmd.Flags().MarkDeprecated("fulcio-uri", "use --fulcio instead")
cmd.Flags().StringArrayVar(&o.CtfeKeyPath, "ctfe-key", nil,
"path to a PEM-encoded public key used by certificate authority for "+
"certificate transparency log.")
_ = cmd.MarkFlagFilename("ctfe-key", publicKeyExts...)
_ = cmd.Flags().MarkDeprecated("ctfe-key", "use --ctfe instead")
cmd.Flags().StringArrayVar(&o.CtfeStartTime, "ctfe-start-time", nil,
"RFC 3339 string describing validity start time for key use by "+
"certificate transparency log.")
_ = cmd.Flags().MarkDeprecated("ctfe-start-time", "use --ctfe instead")
cmd.Flags().StringArrayVar(&o.CtfeEndTime, "ctfe-end-time", nil,
"RFC 3339 string describing validity end time for key use by "+
"certificate transparency log.")
_ = cmd.Flags().MarkDeprecated("ctfe-end-time", "use --ctfe instead")
cmd.Flags().StringArrayVar(&o.CtfeURL, "ctfe-url", nil,
"URL of the certificate transparency log.")
_ = cmd.Flags().MarkDeprecated("ctfe-url", "use --ctfe instead")
cmd.Flags().StringVar(&o.Out, "out", "", "path to output trusted root")
// _ = cmd.MarkFlagFilename("output") // no typical extensions
cmd.Flags().StringArrayVar(&o.RekorKeyPath, "rekor-key", nil,
"path to a PEM-encoded public key used by transparency log like Rekor. "+
"For Rekor V2, append the Rekor server name with ',', e.g. "+
"'--rekor-key=/path/to/key.pub,rekor.example.test'.")
_ = cmd.MarkFlagFilename("rekor-key", publicKeyExts...)
_ = cmd.Flags().MarkDeprecated("rekor-key", "use --rekor instead")
cmd.Flags().StringArrayVar(&o.RekorStartTime, "rekor-start-time", nil,
"RFC 3339 string describing validity start time for key use by "+
"transparency log like Rekor.")
_ = cmd.Flags().MarkDeprecated("rekor-start-time", "use --rekor instead")
cmd.Flags().StringArrayVar(&o.RekorEndTime, "rekor-end-time", nil,
"RFC 3339 string describing validity end time for key use by "+
"transparency log like Rekor.")
_ = cmd.Flags().MarkDeprecated("rekor-end-time", "use --rekor instead")
cmd.Flags().StringArrayVar(&o.RekorURL, "rekor-url", nil,
"URL of the transparency log.")
_ = cmd.Flags().MarkDeprecated("rekor-url", "use --rekor instead")
cmd.Flags().StringArrayVar(&o.TSACertChainPath, "timestamp-certificate-chain", nil,
"path to PEM-encoded certificate chain file for the RFC3161 timestamp authority. Must contain the root CA certificate. "+
"Optionally may contain intermediate CA certificates")
_ = cmd.MarkFlagFilename("timestamp-certificate-chain", certificateExts...)
_ = cmd.Flags().MarkDeprecated("timestamp-certificate-chain", "use --tsa instead")
cmd.Flags().StringArrayVar(&o.TSAURI, "timestamp-uri", nil,
"URI of the timestamp authority server.")
_ = cmd.Flags().MarkDeprecated("timestamp-uri", "use --tsa instead")
}
//
// Copyright 2021 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 options
import (
"github.com/spf13/cobra"
)
// UploadBlobOptions is the top level wrapper for the `upload blob` command.
type UploadBlobOptions struct {
ContentType string
Files FilesOptions
Registry RegistryOptions
Annotations map[string]string
}
var _ Interface = (*UploadBlobOptions)(nil)
// AddFlags implements Interface
func (o *UploadBlobOptions) AddFlags(cmd *cobra.Command) {
o.Registry.AddFlags(cmd)
o.Files.AddFlags(cmd)
cmd.Flags().StringVar(&o.ContentType, "ct", "",
"content type to set")
cmd.Flags().StringToStringVarP(&o.Annotations, "annotation", "a", nil,
"annotations to set")
}
// UploadWASMOptions is the top level wrapper for the `upload wasm` command.
type UploadWASMOptions struct {
File string
Registry RegistryOptions
}
var _ Interface = (*UploadWASMOptions)(nil)
// AddFlags implements Interface
func (o *UploadWASMOptions) AddFlags(cmd *cobra.Command) {
o.Registry.AddFlags(cmd)
cmd.Flags().StringVarP(&o.File, "file", "f", "",
"path to the wasm file to upload")
_ = cmd.MarkFlagFilename("file", wasmExts...)
_ = cmd.MarkFlagRequired("file")
}
// Copyright 2021 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 options
import (
"fmt"
"runtime"
"sigs.k8s.io/release-utils/version"
)
var (
// uaString is meant to resemble the User-Agent sent by browsers with requests.
// See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent
uaString = fmt.Sprintf("cosign/%s (%s; %s)", version.GetVersionInfo().GitVersion, runtime.GOOS, runtime.GOARCH)
)
// UserAgent returns the User-Agent string which `cosign` should send with HTTP requests.ß
func UserAgent() string {
return uaString
}
//
// Copyright 2021 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 options
import (
"github.com/spf13/cobra"
"github.com/sigstore/cosign/v3/internal/pkg/cosign"
)
type CommonVerifyOptions struct {
Offline bool // Force offline verification
TSACertChainPath string
IgnoreTlog bool
MaxWorkers int
// This is added to CommonVerifyOptions to provide a path to support
// it for other verify options.
ExperimentalOCI11 bool
PrivateInfrastructure bool
UseSignedTimestamps bool
NewBundleFormat bool
TrustedRootPath string
}
func (o *CommonVerifyOptions) AddFlags(cmd *cobra.Command) {
cmd.Flags().BoolVar(&o.Offline, "offline", false,
"only verify an artifact's inclusion in a transparency log using a provided proof, rather than querying the log. May still include network requests to retrieve service keys from a TUF repository")
_ = cmd.Flags().MarkDeprecated("offline", "To verify in an airgapped environment, provide a --bundle with the signature and verification material, and a --trusted-root file with the service keys and certificates")
cmd.Flags().StringVar(&o.TSACertChainPath, "timestamp-certificate-chain", "",
"path to PEM-encoded certificate chain file for the RFC3161 timestamp authority. Must contain the root CA certificate. "+
"Optionally may contain intermediate CA certificates, and may contain the leaf TSA certificate if not present in the timestamp")
cmd.Flags().BoolVar(&o.UseSignedTimestamps, "use-signed-timestamps", false,
"verify rfc3161 timestamps")
cmd.Flags().BoolVar(&o.IgnoreTlog, "insecure-ignore-tlog", false,
"ignore transparency log verification, to be used when an artifact signature has not been uploaded to the transparency log. Artifacts "+
"cannot be publicly verified when not included in a log")
cmd.Flags().BoolVar(&o.PrivateInfrastructure, "private-infrastructure", false,
"skip transparency log verification when verifying artifacts in a privately deployed infrastructure")
cmd.Flags().BoolVar(&o.ExperimentalOCI11, "experimental-oci11", false,
"set to true to enable experimental OCI 1.1 behaviour (unrelated to bundle format)")
cmd.Flags().IntVar(&o.MaxWorkers, "max-workers", cosign.DefaultMaxWorkers,
"the amount of maximum workers for parallel executions")
cmd.Flags().StringVar(&o.TrustedRootPath, "trusted-root", "",
"Path to a Sigstore TrustedRoot JSON file. Requires --new-bundle-format to be set.")
cmd.Flags().BoolVar(&o.NewBundleFormat, "new-bundle-format", true,
"expect the signature/attestation to be packaged in a Sigstore bundle")
}
// VerifyOptions is the top level wrapper for the `verify` command.
type VerifyOptions struct {
Key string
CheckClaims bool
Attachment string
Output string
SignatureRef string
PayloadRef string
LocalImage bool
CommonVerifyOptions CommonVerifyOptions
SecurityKey SecurityKeyOptions
CertVerify CertVerifyOptions
Rekor RekorOptions
Registry RegistryOptions
SignatureDigest SignatureDigestOptions
AnnotationOptions
}
var _ Interface = (*VerifyOptions)(nil)
// AddFlags implements Interface
func (o *VerifyOptions) AddFlags(cmd *cobra.Command) {
o.SecurityKey.AddFlags(cmd)
o.Rekor.AddFlags(cmd)
o.CertVerify.AddFlags(cmd)
o.Registry.AddFlags(cmd)
o.SignatureDigest.AddFlags(cmd)
o.AnnotationOptions.AddFlags(cmd)
o.CommonVerifyOptions.AddFlags(cmd)
cmd.Flags().StringVar(&o.Key, "key", "",
"path to the public key file, KMS URI or Kubernetes Secret")
_ = cmd.MarkFlagFilename("key", publicKeyExts...)
cmd.Flags().BoolVar(&o.CheckClaims, "check-claims", true,
"whether to check the claims found")
cmd.Flags().StringVar(&o.Attachment, "attachment", "",
"DEPRECATED, related image attachment to verify (sbom), default none")
_ = cmd.MarkFlagFilename("attachment", sbomExts...)
cmd.Flags().StringVarP(&o.Output, "output", "o", "json",
"output format for the signing image information (json|text)")
cmd.Flags().StringVar(&o.SignatureRef, "signature", "",
"signature content or path or remote URL")
_ = cmd.MarkFlagFilename("signature", signatureExts...)
cmd.Flags().StringVar(&o.PayloadRef, "payload", "",
"payload path or remote URL")
// _ = cmd.MarkFlagFilename("payload") // no typical extensions
cmd.Flags().BoolVar(&o.LocalImage, "local-image", false,
"whether the specified image is a path to an image saved locally via 'cosign save'")
}
// VerifyAttestationOptions is the top level wrapper for the `verify attestation` command.
type VerifyAttestationOptions struct {
Key string
CheckClaims bool
Output string
CommonVerifyOptions CommonVerifyOptions
SecurityKey SecurityKeyOptions
Rekor RekorOptions
CertVerify CertVerifyOptions
Registry RegistryOptions
Predicate PredicateRemoteOptions
SignatureDigest SignatureDigestOptions
Policies []string
LocalImage bool
}
var _ Interface = (*VerifyAttestationOptions)(nil)
// AddFlags implements Interface
func (o *VerifyAttestationOptions) AddFlags(cmd *cobra.Command) {
o.SecurityKey.AddFlags(cmd)
o.Rekor.AddFlags(cmd)
o.CertVerify.AddFlags(cmd)
o.Registry.AddFlags(cmd)
o.Predicate.AddFlags(cmd)
o.CommonVerifyOptions.AddFlags(cmd)
o.SignatureDigest.AddFlags(cmd)
cmd.Flags().StringVar(&o.Key, "key", "",
"path to the public key file, KMS URI or Kubernetes Secret")
cmd.Flags().BoolVar(&o.CheckClaims, "check-claims", true,
"whether to check the claims found")
cmd.Flags().StringSliceVar(&o.Policies, "policy", nil,
"specify CUE or Rego files with policies to be used for validation")
cmd.Flags().StringVarP(&o.Output, "output", "o", "json",
"output format for the signing image information (json|text)")
cmd.Flags().BoolVar(&o.LocalImage, "local-image", false,
"whether the specified image is a path to an image saved locally via 'cosign save'")
}
// VerifyBlobOptions is the top level wrapper for the `verify blob` command.
type VerifyBlobOptions struct {
Key string
Signature string
BundlePath string
SecurityKey SecurityKeyOptions
CertVerify CertVerifyOptions
Rekor RekorOptions
CommonVerifyOptions CommonVerifyOptions
SignatureDigest SignatureDigestOptions
RFC3161TimestampPath string
}
var _ Interface = (*VerifyBlobOptions)(nil)
// AddFlags implements Interface
func (o *VerifyBlobOptions) AddFlags(cmd *cobra.Command) {
o.SecurityKey.AddFlags(cmd)
o.Rekor.AddFlags(cmd)
o.CertVerify.AddFlags(cmd)
o.CommonVerifyOptions.AddFlags(cmd)
o.SignatureDigest.AddFlags(cmd)
cmd.Flags().StringVar(&o.Key, "key", "",
"path to the public key file, KMS URI or Kubernetes Secret")
cmd.Flags().StringVar(&o.Signature, "signature", "",
"signature content or path or remote URL")
cmd.Flags().StringVar(&o.BundlePath, "bundle", "",
"path to bundle FILE")
cmd.Flags().StringVar(&o.RFC3161TimestampPath, "rfc3161-timestamp", "",
"path to RFC3161 timestamp FILE")
}
// VerifyDockerfileOptions is the top level wrapper for the `dockerfile verify` command.
type VerifyDockerfileOptions struct {
VerifyOptions
BaseImageOnly bool
}
var _ Interface = (*VerifyDockerfileOptions)(nil)
// AddFlags implements Interface
func (o *VerifyDockerfileOptions) AddFlags(cmd *cobra.Command) {
o.VerifyOptions.AddFlags(cmd)
cmd.Flags().BoolVar(&o.BaseImageOnly, "base-image-only", false,
"only verify the base image (the last FROM image in the Dockerfile)")
}
// VerifyBlobAttestationOptions is the top level wrapper for the `verify-blob-attestation` command.
type VerifyBlobAttestationOptions struct {
Key string
SignaturePath string
BundlePath string
PredicateOptions
CheckClaims bool
SecurityKey SecurityKeyOptions
CertVerify CertVerifyOptions
Rekor RekorOptions
CommonVerifyOptions CommonVerifyOptions
SignatureDigest SignatureDigestOptions
RFC3161TimestampPath string
Digest string
DigestAlg string
}
var _ Interface = (*VerifyBlobOptions)(nil)
// AddFlags implements Interface
func (o *VerifyBlobAttestationOptions) AddFlags(cmd *cobra.Command) {
o.PredicateOptions.AddFlags(cmd)
o.SecurityKey.AddFlags(cmd)
o.Rekor.AddFlags(cmd)
o.CertVerify.AddFlags(cmd)
o.CommonVerifyOptions.AddFlags(cmd)
o.SignatureDigest.AddFlags(cmd)
cmd.Flags().StringVar(&o.Key, "key", "",
"path to the public key file, KMS URI or Kubernetes Secret")
cmd.Flags().StringVar(&o.SignaturePath, "signature", "",
"path to base64-encoded signature over attestation in DSSE format")
cmd.Flags().StringVar(&o.BundlePath, "bundle", "",
"path to bundle FILE")
cmd.Flags().BoolVar(&o.CheckClaims, "check-claims", true,
"if true, verifies the digest exists in the in-toto subject (using either the provided digest and digest algorithm or the provided blob's sha256 digest). If false, only the DSSE envelope is verified.")
cmd.Flags().StringVar(&o.RFC3161TimestampPath, "rfc3161-timestamp", "",
"path to RFC3161 timestamp FILE")
cmd.Flags().StringVar(&o.Digest, "digest", "",
"Digest to use for verifying in-toto subject (instead of providing a blob)")
cmd.Flags().StringVar(&o.DigestAlg, "digestAlg", "",
"Digest algorithm to use for verifying in-toto subject (instead of providing a blob)")
}
// 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 cosign
import (
"crypto"
"errors"
"hash"
"io"
"io/fs"
"os"
)
const (
DefaultMaxWorkers int = 10
)
func FileExists(filename string) (bool, error) {
info, err := os.Stat(filename)
if errors.Is(err, fs.ErrNotExist) {
return false, nil
}
if err != nil {
return false, err
}
return !info.IsDir(), nil
}
// HashReader hashes while it reads.
type HashReader struct {
r io.Reader
h hash.Hash
ch crypto.Hash
}
func NewHashReader(r io.Reader, ch crypto.Hash) HashReader {
h := ch.New()
return HashReader{
r: io.TeeReader(r, h),
h: h,
ch: ch,
}
}
// Read implements io.Reader.
func (h *HashReader) Read(p []byte) (n int, err error) { return h.r.Read(p) }
// Sum implements hash.Hash.
func (h *HashReader) Sum(p []byte) []byte { return h.h.Sum(p) }
// Reset implements hash.Hash.
func (h *HashReader) Reset() { h.h.Reset() }
// Size implements hash.Hash.
func (h *HashReader) Size() int { return h.h.Size() }
// BlockSize implements hash.Hash.
func (h *HashReader) BlockSize() int { return h.h.BlockSize() }
// Write implements hash.Hash
func (h *HashReader) Write(p []byte) (int, error) { return 0, errors.New("not implemented") } //nolint: revive
// HashFunc implements cosign.NamedHash
func (h *HashReader) HashFunc() crypto.Hash { return h.ch }
// Copyright 2021 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 payload
import (
"context"
"crypto"
"encoding/base64"
"encoding/json"
"io"
"github.com/secure-systems-lab/go-securesystemslib/dsse"
"github.com/sigstore/cosign/v3/internal/pkg/cosign"
"github.com/sigstore/cosign/v3/pkg/oci"
"github.com/sigstore/cosign/v3/pkg/oci/static"
"github.com/sigstore/cosign/v3/pkg/types"
"github.com/sigstore/sigstore/pkg/signature"
)
type payloadAttestor struct {
signer payloadSigner
payloadType string
}
var _ cosign.DSSEAttestor = (*payloadAttestor)(nil)
// Attest implements `cosign.DSSEAttestor`
func (pa *payloadAttestor) DSSEAttest(ctx context.Context, payload io.Reader) (oci.Signature, crypto.PublicKey, error) {
p, err := io.ReadAll(payload)
if err != nil {
return nil, nil, err
}
pb := dsse.PAE(pa.payloadType, p)
sig, err := pa.signer.signPayload(ctx, pb)
if err != nil {
return nil, nil, err
}
pk, err := pa.signer.publicKey(ctx)
if err != nil {
return nil, nil, err
}
envelope := dsse.Envelope{
PayloadType: pa.payloadType,
Payload: base64.StdEncoding.EncodeToString(pb),
Signatures: []dsse.Signature{{
Sig: base64.StdEncoding.EncodeToString(sig),
}},
}
envelopeJSON, err := json.Marshal(envelope)
if err != nil {
return nil, nil, err
}
opts := []static.Option{static.WithLayerMediaType(types.DssePayloadType)}
att, err := static.NewAttestation(envelopeJSON, opts...)
if err != nil {
return nil, nil, err
}
return att, pk, nil
}
// NewDSSEAttestor returns a `cosign.DSSEAttestor` which uses the given `signature.Signer` to sign and create a DSSE attestation of given payloads.
// Option types other than `signature.SignOption` and `signature.PublicKeyOption` cause a runtime panic.
func NewDSSEAttestor(payloadType string,
s signature.Signer,
signAndPublicKeyOptions ...interface{}) cosign.DSSEAttestor {
return &payloadAttestor{
signer: newSigner(s, signAndPublicKeyOptions...),
payloadType: payloadType,
}
}
// Copyright 2021 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 payload
import (
"bytes"
"context"
"crypto"
"encoding/base64"
"fmt"
"io"
"github.com/sigstore/cosign/v3/internal/pkg/cosign"
"github.com/sigstore/cosign/v3/pkg/oci"
"github.com/sigstore/cosign/v3/pkg/oci/static"
"github.com/sigstore/sigstore/pkg/signature"
signatureoptions "github.com/sigstore/sigstore/pkg/signature/options"
)
type payloadSigner struct {
payloadSigner signature.Signer
payloadSignerOpts []signature.SignOption
publicKeyProviderOpts []signature.PublicKeyOption
}
var _ cosign.Signer = (*payloadSigner)(nil)
// Sign implements `Signer`
func (ps *payloadSigner) Sign(ctx context.Context, payload io.Reader) (oci.Signature, crypto.PublicKey, error) {
payloadBytes, err := io.ReadAll(payload)
if err != nil {
return nil, nil, err
}
sig, err := ps.signPayload(ctx, payloadBytes)
if err != nil {
return nil, nil, err
}
pk, err := ps.publicKey(ctx)
if err != nil {
return nil, nil, err
}
b64sig := base64.StdEncoding.EncodeToString(sig)
ociSig, err := static.NewSignature(payloadBytes, b64sig)
if err != nil {
return nil, nil, err
}
return ociSig, pk, nil
}
func (ps *payloadSigner) publicKey(ctx context.Context) (pk crypto.PublicKey, err error) {
pkOpts := []signature.PublicKeyOption{signatureoptions.WithContext(ctx)}
pkOpts = append(pkOpts, ps.publicKeyProviderOpts...)
pk, err = ps.payloadSigner.PublicKey(pkOpts...)
if err != nil {
return nil, err
}
return pk, nil
}
func (ps *payloadSigner) signPayload(ctx context.Context, payloadBytes []byte) (sig []byte, err error) {
sOpts := []signature.SignOption{signatureoptions.WithContext(ctx)}
sOpts = append(sOpts, ps.payloadSignerOpts...)
sig, err = ps.payloadSigner.SignMessage(bytes.NewReader(payloadBytes), sOpts...)
if err != nil {
return nil, err
}
return sig, nil
}
func newSigner(s signature.Signer,
signAndPublicKeyOptions ...interface{}) payloadSigner {
var sOpts []signature.SignOption
var pkOpts []signature.PublicKeyOption
for _, opt := range signAndPublicKeyOptions {
switch o := opt.(type) {
case signature.SignOption:
sOpts = append(sOpts, o)
case signature.PublicKeyOption:
pkOpts = append(pkOpts, o)
default:
panic(fmt.Sprintf("options must be of type `signature.SignOption` or `signature.PublicKeyOption`. Got a %T: %v", o, o))
}
}
return payloadSigner{
payloadSigner: s,
payloadSignerOpts: sOpts,
publicKeyProviderOpts: pkOpts,
}
}
// NewSigner returns a `cosign.Signer` which uses the given `signature.Signer` to sign requested payloads.
// Option types other than `signature.SignOption` and `signature.PublicKeyOption` cause a runtime panic.
func NewSigner(s signature.Signer,
signAndPublicKeyOptions ...interface{}) cosign.Signer {
signer := newSigner(s, signAndPublicKeyOptions...)
return &signer
}
// Copyright 2024 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 payload
import "fmt"
// MaxLayerSizeExceeded is an error indicating that the layer is too big to read into memory and cosign should abort processing it.
type MaxLayerSizeExceeded struct {
value uint64
maximum uint64
}
func NewMaxLayerSizeExceeded(value, maximum uint64) *MaxLayerSizeExceeded {
return &MaxLayerSizeExceeded{value, maximum}
}
func (e *MaxLayerSizeExceeded) Error() string {
return fmt.Sprintf("size of layer (%d) exceeded the limit (%d)", e.value, e.maximum)
}
// Copyright 2024 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 payload
import (
"github.com/dustin/go-humanize"
"github.com/sigstore/cosign/v3/pkg/cosign/env"
)
const defaultMaxSize = uint64(134217728) // 128MiB
func CheckSize(size uint64) error {
maxSize := defaultMaxSize
maxSizeOverride, exists := env.LookupEnv(env.VariableMaxAttachmentSize)
if exists {
var err error
maxSize, err = humanize.ParseBytes(maxSizeOverride)
if err != nil {
maxSize = defaultMaxSize
}
}
if size > maxSize {
return NewMaxLayerSizeExceeded(size, maxSize)
}
return nil
}
// 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 mock
import (
"errors"
"github.com/go-openapi/runtime"
"github.com/sigstore/rekor/pkg/generated/client/entries"
"github.com/sigstore/rekor/pkg/generated/models"
)
// EntriesClient is a client that implements entries.ClientService for Rekor
// To use:
// var mClient client.Rekor
// mClient.Entries = &logEntry
type EntriesClient struct {
Entries []*models.LogEntry
}
func (m *EntriesClient) CreateLogEntry(_ *entries.CreateLogEntryParams, _ ...entries.ClientOption) (*entries.CreateLogEntryCreated, error) {
if m.Entries != nil {
return &entries.CreateLogEntryCreated{
ETag: "",
Location: "",
Payload: *m.Entries[0],
}, nil
}
return nil, errors.New("entry not provided")
}
func (m *EntriesClient) GetLogEntryByIndex(_ *entries.GetLogEntryByIndexParams, _ ...entries.ClientOption) (*entries.GetLogEntryByIndexOK, error) {
if m.Entries != nil {
return &entries.GetLogEntryByIndexOK{
Payload: *m.Entries[0],
}, nil
}
return nil, errors.New("entry not provided")
}
func (m *EntriesClient) GetLogEntryByUUID(params *entries.GetLogEntryByUUIDParams, opts ...entries.ClientOption) (*entries.GetLogEntryByUUIDOK, error) { //nolint: revive
if m.Entries != nil {
return &entries.GetLogEntryByUUIDOK{
Payload: *m.Entries[0],
}, nil
}
return nil, errors.New("entry not provided")
}
func (m *EntriesClient) SearchLogQuery(params *entries.SearchLogQueryParams, opts ...entries.ClientOption) (*entries.SearchLogQueryOK, error) { //nolint: revive
resp := []models.LogEntry{}
if m.Entries != nil {
for _, entry := range m.Entries {
resp = append(resp, *entry)
}
}
return &entries.SearchLogQueryOK{
Payload: resp,
}, nil
}
// TODO: Implement mock
func (m *EntriesClient) SetTransport(transport runtime.ClientTransport) { //nolint: revive
// noop
}
// Copyright 2023 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 client
import (
"bytes"
"crypto/tls"
"crypto/x509"
"fmt"
"io"
"net"
"net/http"
"os"
"time"
"github.com/digitorus/timestamp"
)
// TimestampAuthorityClient should be implemented by clients that want to request timestamp responses
type TimestampAuthorityClient interface {
GetTimestampResponse(tsq []byte) ([]byte, error)
}
// TimestampAuthorityClient provides a client to query RFC 3161 timestamp authorities
type TimestampAuthorityClientImpl struct {
TimestampAuthorityClient
// URL is the path to the API to request timestamp responses
URL string
// CACert is the filepath to the PEM-encoded CA certificate for the connection to the TSA server
CACert string
// Cert is the filepath to the PEM-encoded certificate for the connection to the TSA server
Cert string
// Cert is the filepath to the PEM-encoded key corresponding to the certificate for the connection to the TSA server
Key string
// ServerName is the expected SAN value in the server's certificate - used for https://pkg.go.dev/crypto/tls#Config.ServerName
ServerName string
// Timeout is the request timeout
Timeout time.Duration
}
const defaultTimeout = 10 * time.Second
func getHTTPTransport(cacertFilename, certFilename, keyFilename, serverName string, timeout time.Duration) (http.RoundTripper, error) {
if timeout == 0 {
timeout = defaultTimeout
}
tr := &http.Transport{
TLSClientConfig: &tls.Config{
CipherSuites: []uint16{
// TLS 1.3 cipher suites.
tls.TLS_AES_128_GCM_SHA256,
tls.TLS_AES_256_GCM_SHA384,
tls.TLS_CHACHA20_POLY1305_SHA256,
},
MinVersion: tls.VersionTLS13,
SessionTicketsDisabled: true,
},
// the rest of default settings are copied verbatim from https://golang.org/pkg/net/http/#DefaultTransport
// to minimize surprises for the users.
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: timeout,
KeepAlive: 30 * time.Second,
DualStack: true,
}).DialContext,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
var pool *x509.CertPool
if cacertFilename != "" {
f, err := os.Open(cacertFilename)
if err != nil {
return nil, err
}
defer f.Close()
caCertBytes, err := io.ReadAll(f)
if err != nil {
return nil, fmt.Errorf("unable to read CA certs from %s: %w", cacertFilename, err)
}
pool = x509.NewCertPool()
if !pool.AppendCertsFromPEM(caCertBytes) {
return nil, fmt.Errorf("no valid CA certs found in %s", cacertFilename)
}
tr.TLSClientConfig.RootCAs = pool
}
if certFilename != "" && keyFilename != "" {
cert, err := tls.LoadX509KeyPair(certFilename, keyFilename)
if err != nil {
return nil, fmt.Errorf("unable to read CA certs from cert %s, key %s: %w",
certFilename, keyFilename, err)
}
tr.TLSClientConfig.Certificates = []tls.Certificate{cert}
}
if serverName != "" {
tr.TLSClientConfig.ServerName = serverName
}
return tr, nil
}
// GetTimestampResponse sends a timestamp query to a timestamp authority, returning a timestamp response.
// The query and response are defined by RFC 3161.
func (t *TimestampAuthorityClientImpl) GetTimestampResponse(tsq []byte) ([]byte, error) {
client := http.Client{
Timeout: t.Timeout,
}
// if mTLS-related fields are set, create a custom Transport for the Client
if t.CACert != "" || t.Cert != "" {
tr, err := getHTTPTransport(t.CACert, t.Cert, t.Key, t.ServerName, t.Timeout)
if err != nil {
return nil, err
}
client.Transport = tr
}
req, err := http.NewRequest(http.MethodPost, t.URL, bytes.NewReader(tsq))
if err != nil {
return nil, fmt.Errorf("error creating HTTP request: %w", err)
}
req.Header.Set("Content-Type", "application/timestamp-query")
tsr, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("error making request to timestamp authority: %w", err)
}
defer tsr.Body.Close()
if tsr.StatusCode != http.StatusOK && tsr.StatusCode != http.StatusCreated {
return nil, fmt.Errorf("request to timestamp authority failed with status code %d", tsr.StatusCode)
}
resp, err := io.ReadAll(tsr.Body)
if err != nil {
return nil, fmt.Errorf("error reading timestamp response: %w", err)
}
// validate that the timestamp response is parseable
ts, err := timestamp.ParseResponse(resp)
if err != nil {
return nil, err
}
fmt.Fprintln(os.Stderr, "Timestamp fetched with time: ", ts.Time)
return resp, nil
}
func NewTSAClient(url string) *TimestampAuthorityClientImpl {
return &TimestampAuthorityClientImpl{URL: url, Timeout: defaultTimeout}
}
func NewTSAClientMTLS(url, cacert, cert, key, serverName string) *TimestampAuthorityClientImpl {
return &TimestampAuthorityClientImpl{
URL: url,
CACert: cacert,
Cert: cert,
Key: key,
ServerName: serverName,
Timeout: defaultTimeout,
}
}
// 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 mock
import (
"crypto"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"encoding/asn1"
"fmt"
"time"
"github.com/digitorus/timestamp"
"github.com/sigstore/cosign/v3/internal/pkg/cosign/tsa/client"
"github.com/sigstore/sigstore/pkg/cryptoutils"
"github.com/sigstore/sigstore/pkg/signature"
"github.com/sigstore/timestamp-authority/pkg/signer"
)
// TSAClient creates RFC3161 timestamps and implements client.TimestampAuthority.
// Messages to sign can either be provided in the initializer or through the request.
// Time can be provided in the initializer, or defaults to time.Now().
// All other timestamp parameters are hardcoded.
type TSAClient struct {
client.TimestampAuthorityClient
Signer crypto.Signer
CertChain []*x509.Certificate
Time time.Time
Message []byte
}
// TSAClientOptions provide customization for the mock TSA client.
type TSAClientOptions struct {
// Time is an optional timestamp. Default is time.Now().
Time time.Time
// Message is the pre-hashed message to sign over, typically a raw signature.
Message []byte
// Signer is an optional signer created out of band. Client creates one if not set.
Signer crypto.Signer
}
func NewTSAClient(o TSAClientOptions) (*TSAClient, error) {
sv := o.Signer
if sv == nil {
var err error
sv, _, err = signature.NewECDSASignerVerifier(elliptic.P256(), rand.Reader, crypto.SHA256)
if err != nil {
return nil, err
}
}
certChain, err := signer.NewTimestampingCertWithChain(sv)
if err != nil {
return nil, fmt.Errorf("generating timestamping cert chain: %w", err)
}
return &TSAClient{
Signer: sv,
CertChain: certChain,
Time: o.Time,
Message: o.Message,
}, nil
}
func (c *TSAClient) GetTimestampResponse(tsq []byte) ([]byte, error) {
var hashAlg crypto.Hash
var hashedMessage []byte
if tsq != nil {
req, err := timestamp.ParseRequest(tsq)
if err != nil {
return nil, err
}
hashAlg = req.HashAlgorithm
hashedMessage = req.HashedMessage
} else {
hashAlg = crypto.SHA256
h := hashAlg.New()
h.Write(c.Message)
hashedMessage = h.Sum(nil)
}
nonce, err := cryptoutils.GenerateSerialNumber()
if err != nil {
return nil, err
}
duration, _ := time.ParseDuration("1s")
tsStruct := timestamp.Timestamp{
HashAlgorithm: hashAlg,
HashedMessage: hashedMessage,
Nonce: nonce,
Policy: asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 2},
Ordering: false,
Accuracy: duration,
Qualified: false,
AddTSACertificate: true,
}
if c.Time.IsZero() {
tsStruct.Time = time.Now()
} else {
tsStruct.Time = c.Time
}
return tsStruct.CreateResponseWithOpts(c.CertChain[0], c.Signer, crypto.SHA256)
}
// Copyright 2021 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 tsa
import (
"bytes"
"context"
"crypto"
"fmt"
"io"
"strconv"
"strings"
"github.com/digitorus/timestamp"
"github.com/sigstore/cosign/v3/internal/pkg/cosign"
"github.com/sigstore/cosign/v3/internal/pkg/cosign/tsa/client"
"github.com/sigstore/cosign/v3/pkg/cosign/bundle"
"github.com/sigstore/cosign/v3/pkg/oci"
"github.com/sigstore/cosign/v3/pkg/oci/mutate"
"github.com/sigstore/sigstore/pkg/cryptoutils"
)
// GetTimestampedSignature queries a timestamp authority to fetch an RFC3161 timestamp. sigBytes is an
// opaque blob, but is typically a signature over an artifact.
func GetTimestampedSignature(sigBytes []byte, tsaClient client.TimestampAuthorityClient) ([]byte, error) {
requestBytes, err := createTimestampAuthorityRequest(sigBytes, crypto.SHA256, "")
if err != nil {
return nil, fmt.Errorf("error creating timestamp request: %w", err)
}
return tsaClient.GetTimestampResponse(requestBytes)
}
// signerWrapper calls a wrapped, inner signer then uploads either the Cert or Pub(licKey) of the results to Rekor, then adds the resulting `Bundle`
type signerWrapper struct {
inner cosign.Signer
tsaClient client.TimestampAuthorityClient
}
var _ cosign.Signer = (*signerWrapper)(nil)
// Sign implements `cosign.Signer`
func (rs *signerWrapper) Sign(ctx context.Context, payload io.Reader) (oci.Signature, crypto.PublicKey, error) {
sig, pub, err := rs.inner.Sign(ctx, payload)
if err != nil {
return nil, nil, err
}
// create timestamp over raw bytes of signature
rawSig, err := sig.Signature()
if err != nil {
return nil, nil, err
}
// fetch rfc3161 timestamp from timestamp authority
responseBytes, err := GetTimestampedSignature(rawSig, rs.tsaClient)
if err != nil {
return nil, nil, err
}
bundle := bundle.TimestampToRFC3161Timestamp(responseBytes)
newSig, err := mutate.Signature(sig, mutate.WithRFC3161Timestamp(bundle))
if err != nil {
return nil, nil, err
}
return newSig, pub, nil
}
func createTimestampAuthorityRequest(artifactBytes []byte, hash crypto.Hash, policyStr string) ([]byte, error) {
reqOpts := ×tamp.RequestOptions{
Hash: hash,
Certificates: true, // if the timestamp response should contain the leaf certificate
}
// specify a pseudo-random nonce in the request
nonce, err := cryptoutils.GenerateSerialNumber()
if err != nil {
return nil, err
}
reqOpts.Nonce = nonce
if policyStr != "" {
var oidInts []int
for _, v := range strings.Split(policyStr, ".") {
i, _ := strconv.Atoi(v)
oidInts = append(oidInts, i)
}
reqOpts.TSAPolicyOID = oidInts
}
return timestamp.CreateRequest(bytes.NewReader(artifactBytes), reqOpts)
}
// NewSigner returns a `cosign.Signer` which uploads the signature to a TSA
func NewSigner(inner cosign.Signer, tsaClient client.TimestampAuthorityClient) cosign.Signer {
return &signerWrapper{
inner: inner,
tsaClient: tsaClient,
}
}
// 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 tsa
import (
"bytes"
"crypto/x509"
"github.com/sigstore/sigstore/pkg/cryptoutils"
)
// SplitPEMCertificateChain returns a list of leaf (non-CA) certificates, a certificate pool for
// intermediate CA certificates, and a certificate pool for root CA certificates
func SplitPEMCertificateChain(pem []byte) (leaves, intermediates, roots []*x509.Certificate, err error) {
certs, err := cryptoutils.UnmarshalCertificatesFromPEM(pem)
if err != nil {
return nil, nil, nil, err
}
for _, cert := range certs {
if !cert.IsCA {
leaves = append(leaves, cert)
} else {
// root certificates are self-signed
if bytes.Equal(cert.RawSubject, cert.RawIssuer) {
roots = append(roots, cert)
} else {
intermediates = append(intermediates, cert)
}
}
}
return leaves, intermediates, roots, nil
}
//
// Copyright 2023 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 now
import (
"fmt"
"os"
"strconv"
"time"
)
// Now returns SOURCE_DATE_EPOCH or time.Now().
func Now() (time.Time, error) {
// nolint
epoch := os.Getenv("SOURCE_DATE_EPOCH")
if epoch == "" {
return time.Now(), nil
}
seconds, err := strconv.ParseInt(epoch, 10, 64)
if err != nil {
return time.Now(), fmt.Errorf("SOURCE_DATE_EPOCH should be the number of seconds since January 1st 1970, 00:00 UTC, got: %w", err)
}
return time.Unix(seconds, 0), nil
}
//
// Copyright 2023 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 remote
import (
"fmt"
)
// ArtifactType converts a attachment name (sig/sbom/att/etc.) into a valid artifactType (OCI 1.1+).
func ArtifactType(attName string) string {
return fmt.Sprintf("application/vnd.dev.cosign.artifact.%s.v1+json", attName)
}
// 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"
"net"
"net/url"
"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.Hour),
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 GenerateLeafCertWithExpiration(subject string, oidcIssuer string, expiration time.Time,
priv *ecdsa.PrivateKey,
parentTemplate *x509.Certificate, parentPriv crypto.Signer) (*x509.Certificate, error) {
certTemplate := &x509.Certificate{
SerialNumber: big.NewInt(1),
EmailAddresses: []string{subject},
NotBefore: expiration,
NotAfter: expiration.Add(10 * time.Minute),
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),
},
},
}
cert, err := createCertificate(certTemplate, parentTemplate, &priv.PublicKey, parentPriv)
if err != nil {
return nil, err
}
return cert, nil
}
func GenerateLeafCert(subject string, oidcIssuer string, parentTemplate *x509.Certificate, parentPriv crypto.Signer, exts ...pkix.Extension) (*x509.Certificate, *ecdsa.PrivateKey, error) {
exts = append(exts, pkix.Extension{
// OID for OIDC Issuer extension
Id: asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 1},
Critical: false,
Value: []byte(oidcIssuer),
})
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: exts,
}
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
}
func GenerateLeafCertWithGitHubOIDs(subject string, oidcIssuer string, githubWorkflowTrigger, githubWorkflowSha, githubWorkflowName,
githubWorkflowRepository, githubWorkflowRef 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),
},
{Id: asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 2}, Value: []byte(githubWorkflowTrigger)},
{Id: asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 3}, Value: []byte(githubWorkflowSha)},
{Id: asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 4}, Value: []byte(githubWorkflowName)},
{Id: asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 5}, Value: []byte(githubWorkflowRepository)},
{Id: asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 6}, Value: []byte(githubWorkflowRef)}},
}
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
}
func GenerateLeafCertWithSubjectAlternateNames(dnsNames []string, emailAddresses []string, ipAddresses []net.IP, uris []*url.URL, oidcIssuer string, parentTemplate *x509.Certificate, parentPriv crypto.Signer) (*x509.Certificate, *ecdsa.PrivateKey, error) {
certTemplate := &x509.Certificate{
SerialNumber: big.NewInt(1),
EmailAddresses: emailAddresses,
DNSNames: dnsNames,
IPAddresses: ipAddresses,
URIs: uris,
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
}
// Copyright 2023 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 ui
import (
"bytes"
"context"
"io"
"os"
)
// An Env is the environment that the CLI exists in.
//
// It contains handles to STDERR and STDIN. Eventually, it will contain
// configuration pertaining to the current invocation (e.g., is this a terminal
// or not).
//
// UI methods should be defined on an Env. Then, the Env can be
// changed for easy testing. The Env will be retrieved from the current
// application context.
type Env struct {
Stderr io.Writer
Stdin io.Reader
}
// defaultEnv returns the default environment (writing to os.Stderr and
// reading from os.Stdin).
func defaultEnv() *Env {
return &Env{
Stderr: os.Stderr,
Stdin: os.Stdin,
}
}
type ctxKey struct{}
func (c ctxKey) String() string {
return "cosign/ui:env"
}
var ctxKeyEnv = ctxKey{}
// getEnv gets the environment from ctx.
//
// If ctx does not contain an environment, getEnv returns the default
// environment (see defaultEnvironment).
func getEnv(ctx context.Context) *Env {
e, ok := ctx.Value(ctxKeyEnv).(*Env)
if !ok {
return defaultEnv()
}
return e
}
// WithEnv adds the environment to the context.
func WithEnv(ctx context.Context, e *Env) context.Context {
return context.WithValue(ctx, ctxKeyEnv, e)
}
type WriteFunc func(string)
type callbackFunc func(context.Context, WriteFunc)
// RunWithTestCtx runs the provided callback in a context with the UI
// environment swapped out for one that allows for easy testing and captures
// STDOUT.
//
// The callback has access to a function that writes to the test STDIN.
func RunWithTestCtx(callback callbackFunc) string {
var stdin bytes.Buffer
var stderr bytes.Buffer
e := Env{&stderr, &stdin}
ctx := WithEnv(context.Background(), &e)
write := func(msg string) { stdin.WriteString(msg) }
callback(ctx, write)
return stderr.String()
}
// Copyright 2023 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 ui
import (
"context"
"fmt"
)
func (w *Env) infof(msg string, a ...any) {
msg = fmt.Sprintf(msg, a...)
fmt.Fprintln(w.Stderr, msg)
}
// Infof logs an informational message. It works like fmt.Printf, except that it
// always has a trailing newline.
func Infof(ctx context.Context, msg string, a ...any) {
getEnv(ctx).infof(msg, a...)
}
func (w *Env) warnf(msg string, a ...any) {
msg = fmt.Sprintf(msg, a...)
fmt.Fprintf(w.Stderr, "WARNING: %s\n", msg)
}
// Warnf logs a warning message (prefixed by "WARNING:"). It works like
// fmt.Printf, except that it always has a trailing newline.
func Warnf(ctx context.Context, msg string, a ...any) {
getEnv(ctx).warnf(msg, a...)
}
// Copyright 2023 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 ui
import (
"bufio"
"context"
"errors"
"fmt"
"io"
"strings"
)
type ErrPromptDeclined struct{}
func (e *ErrPromptDeclined) Error() string {
return "user declined the prompt"
}
type ErrInvalidInput struct {
Got string
Allowed string
}
func (e *ErrInvalidInput) Error() string {
return fmt.Sprintf("invalid input %#v (allowed values %v)", e.Got, e.Allowed)
}
func newInvalidYesOrNoInput(got string) error {
return &ErrInvalidInput{Got: got, Allowed: "y, n"}
}
func (w *Env) prompt() error {
fmt.Fprint(w.Stderr, "Are you sure you would like to continue? [y/N] ")
// TODO: what if it's not a terminal?
r, err := bufio.NewReader(w.Stdin).ReadString('\n')
if err != nil && !errors.Is(err, io.EOF) {
return err
}
value := strings.Trim(r, "\r\n")
switch strings.ToLower(value) {
case "y":
return nil
case "":
fallthrough // TODO: allow setting default=true?
case "n":
return &ErrPromptDeclined{}
default:
// TODO: allow retry on invalid input?
return newInvalidYesOrNoInput(value)
}
}
// ConfirmContinue prompts the user whether they would like to continue and
// returns the parsed answer.
//
// If the user enters anything other than "y" or "Y", ConfirmContinue returns an
// error.
func ConfirmContinue(ctx context.Context) error {
return getEnv(ctx).prompt()
}
// Copyright 2025 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 ui
import (
"context"
"fmt"
"os"
"strings"
"time"
"github.com/moby/term"
)
// Spinner shows progress for long-running operations in the terminal
type Spinner struct {
done chan struct{}
}
// NewSpinner starts a spinner in a goroutine and returns it.
func NewSpinner(ctx context.Context, message string) *Spinner {
s := &Spinner{
done: make(chan struct{}),
}
go func() {
// Don't show spinner if not in a terminal
fd := os.Stderr.Fd()
if !term.IsTerminal(fd) {
Infof(ctx, "%s", message)
return
}
ticker := time.NewTicker(100 * time.Millisecond)
defer ticker.Stop()
spinnerChars := []rune{'|', '/', '-', '\\'}
i := 0
for {
select {
case <-ticker.C:
i++
fmt.Fprintf(os.Stderr, "\r%s %c ", message, spinnerChars[i%len(spinnerChars)])
case <-s.done:
fmt.Fprintf(os.Stderr, "\r%s\r", strings.Repeat(" ", len(message)+3))
return
}
}
}()
return s
}
func (s *Spinner) Stop() {
close(s.done)
}
// Copyright 2021 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 blob
import (
"crypto/sha256"
"crypto/sha512"
"encoding/hex"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"strings"
)
type UnrecognizedSchemeError struct {
Scheme string
}
func (e *UnrecognizedSchemeError) Error() string {
return fmt.Sprintf("loading URL: unrecognized scheme: %s", e.Scheme)
}
func LoadFileOrURL(fileRef string) ([]byte, error) {
var raw []byte
var err error
parts := strings.SplitAfterN(fileRef, "://", 2)
if len(parts) == 2 {
scheme := parts[0]
switch scheme {
case "http://":
fallthrough
case "https://":
// #nosec G107
resp, err := http.Get(fileRef)
if err != nil {
return nil, err
}
defer resp.Body.Close()
raw, err = io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
case "env://":
envVar := parts[1]
// Most of Cosign should use `env.LookupEnv` (see #2236) to restrict us to known environment variables
// (usually `$COSIGN_*`). However, in this case, `envVar` is user-provided and not one of the allow-listed
// env vars.
value, found := os.LookupEnv(envVar) //nolint:forbidigo
if !found {
return nil, fmt.Errorf("loading URL: env var $%s not found", envVar)
}
raw = []byte(value)
default:
return nil, &UnrecognizedSchemeError{Scheme: scheme}
}
} else {
raw, err = os.ReadFile(filepath.Clean(fileRef))
if err != nil {
return nil, err
}
}
return raw, nil
}
func LoadFileOrURLWithChecksum(fileRef string, checksum string) ([]byte, error) {
checksumParts := strings.Split(checksum, ":")
if len(checksumParts) >= 3 {
return nil, fmt.Errorf("wrong checksum input format, must have at most 1 colon: %s", checksum)
}
checksumAlgo := sha256.New()
checksumValue := checksumParts[len(checksumParts)-1]
if len(checksumParts) == 2 {
switch checksumParts[0] {
case "sha256": // the default set above
case "sha512":
checksumAlgo = sha512.New()
default:
return nil, fmt.Errorf("unsupported checksum algorithm: %s", checksumParts[0])
}
}
fileContent, err := LoadFileOrURL(fileRef)
if err != nil {
return nil, err
}
checksumAlgo.Write(fileContent)
computedChecksum := hex.EncodeToString(checksumAlgo.Sum(nil))
if computedChecksum != checksumValue {
return nil, fmt.Errorf("incorrect checksum for file %s: expected %s but got %s", fileRef, checksumValue, computedChecksum)
}
return fileContent, nil
}
//
// Copyright 2021 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 attestation
import (
"encoding/json"
"fmt"
"io"
"reflect"
"strings"
"time"
"github.com/in-toto/in-toto-golang/in_toto"
slsa02 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.2"
slsa1 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v1"
)
const (
// CosignCustomProvenanceV01 specifies the type of the Predicate.
CosignCustomProvenanceV01 = "https://cosign.sigstore.dev/attestation/v1"
// CosignVulnProvenanceV01 specifies the type of VulnerabilityScan Predicate
CosignVulnProvenanceV01 = "https://cosign.sigstore.dev/attestation/vuln/v1"
// OpenVexNamespace holds the URI of the OpenVEX context to identify its
// predicate type. More info about the specification can be found at
// https://github.com/openvex/spec and the attestation spec is found here:
// https://github.com/openvex/spec/blob/main/ATTESTING.md
OpenVexNamespace = "https://openvex.dev/ns"
)
// CosignPredicate specifies the format of the Custom Predicate.
type CosignPredicate struct {
Data interface{}
Timestamp string
}
// VulnPredicate specifies the format of the Vulnerability Scan Predicate
type CosignVulnPredicate struct {
Invocation Invocation `json:"invocation"`
Scanner Scanner `json:"scanner"`
Metadata Metadata `json:"metadata"`
}
// I think this will be moving to upstream in-toto in the fullness of time
// but creating it here for now so that we have a way to deserialize it
// as a InToto Statement
// https://github.com/in-toto/attestation/issues/58
type CosignVulnStatement struct {
in_toto.StatementHeader
Predicate CosignVulnPredicate `json:"predicate"`
}
type Invocation struct {
Parameters interface{} `json:"parameters"`
URI string `json:"uri"`
EventID string `json:"event_id"`
BuilderID string `json:"builder.id"`
}
type DB struct {
URI string `json:"uri"`
Version string `json:"version"`
}
type Scanner struct {
URI string `json:"uri"`
Version string `json:"version"`
DB DB `json:"db"`
Result interface{} `json:"result"`
}
type Metadata struct {
ScanStartedOn time.Time `json:"scanStartedOn"`
ScanFinishedOn time.Time `json:"scanFinishedOn"`
}
// GenerateOpts specifies the options of the Statement generator.
type GenerateOpts struct {
// Predicate is the source of bytes (e.g. a file) to use as the statement's predicate.
Predicate io.Reader
// Type is the pre-defined enums (provenance|link|spdx).
// default: custom
Type string
// Digest of the Image reference.
Digest string
// Repo context of the reference.
Repo string
// Function to return the time to set
Time func() time.Time
}
// GenerateStatement returns an in-toto statement based on the provided
// predicate type (custom|slsaprovenance|slsaprovenance02|slsaprovenance1|spdx|spdxjson|cyclonedx|link).
func GenerateStatement(opts GenerateOpts) (interface{}, error) {
predicate, err := io.ReadAll(opts.Predicate)
if err != nil {
return nil, err
}
switch opts.Type {
case "slsaprovenance":
return generateSLSAProvenanceStatementSLSA02(predicate, opts.Digest, opts.Repo)
case "slsaprovenance02":
return generateSLSAProvenanceStatementSLSA02(predicate, opts.Digest, opts.Repo)
case "slsaprovenance1":
return generateSLSAProvenanceStatementSLSA1(predicate, opts.Digest, opts.Repo)
case "spdx":
return generateSPDXStatement(predicate, opts.Digest, opts.Repo, false)
case "spdxjson":
return generateSPDXStatement(predicate, opts.Digest, opts.Repo, true)
case "cyclonedx":
return generateCycloneDXStatement(predicate, opts.Digest, opts.Repo)
case "link":
return generateLinkStatement(predicate, opts.Digest, opts.Repo)
case "vuln":
return generateVulnStatement(predicate, opts.Digest, opts.Repo)
case "openvex":
return generateOpenVexStatement(predicate, opts.Digest, opts.Repo)
default:
stamp := timestamp(opts)
predicateType := customType(opts)
return generateCustomStatement(predicate, predicateType, opts.Digest, opts.Repo, stamp)
}
}
func generateVulnStatement(predicate []byte, digest string, repo string) (interface{}, error) {
var vuln CosignVulnPredicate
err := json.Unmarshal(predicate, &vuln)
if err != nil {
return nil, err
}
return in_toto.Statement{
StatementHeader: generateStatementHeader(digest, repo, CosignVulnProvenanceV01),
Predicate: vuln,
}, nil
}
func timestamp(opts GenerateOpts) string {
if opts.Time == nil {
opts.Time = time.Now
}
now := opts.Time()
return now.UTC().Format(time.RFC3339)
}
func customType(opts GenerateOpts) string {
if opts.Type != "custom" {
return opts.Type
}
return CosignCustomProvenanceV01
}
func generateStatementHeader(digest, repo, predicateType string) in_toto.StatementHeader {
return in_toto.StatementHeader{
Type: in_toto.StatementInTotoV01,
PredicateType: predicateType,
Subject: []in_toto.Subject{
{
Name: repo,
Digest: map[string]string{
"sha256": digest,
},
},
},
}
}
func generateCustomStatement(rawPayload []byte, customType, digest, repo, timestamp string) (interface{}, error) {
payload, err := generateCustomPredicate(rawPayload, customType, timestamp)
if err != nil {
return nil, err
}
return in_toto.Statement{
StatementHeader: generateStatementHeader(digest, repo, customType),
Predicate: payload,
}, nil
}
func generateCustomPredicate(rawPayload []byte, customType, timestamp string) (interface{}, error) {
if customType == CosignCustomProvenanceV01 {
return &CosignPredicate{
Data: string(rawPayload),
Timestamp: timestamp,
}, nil
}
var result map[string]interface{}
if err := json.Unmarshal(rawPayload, &result); err != nil {
return nil, fmt.Errorf("invalid JSON payload for predicate type %s: %w", customType, err)
}
return result, nil
}
func generateSLSAProvenanceStatementSLSA02(rawPayload []byte, digest string, repo string) (interface{}, error) {
var predicate slsa02.ProvenancePredicate
err := checkRequiredJSONFields(rawPayload, reflect.TypeOf(predicate))
if err != nil {
return nil, fmt.Errorf("provenance predicate: %w", err)
}
err = json.Unmarshal(rawPayload, &predicate)
if err != nil {
return "", fmt.Errorf("unmarshal Provenance predicate: %w", err)
}
return in_toto.ProvenanceStatementSLSA02{
StatementHeader: generateStatementHeader(digest, repo, slsa02.PredicateSLSAProvenance),
Predicate: predicate,
}, nil
}
func generateSLSAProvenanceStatementSLSA1(rawPayload []byte, digest string, repo string) (interface{}, error) {
var predicate slsa1.ProvenancePredicate
err := checkRequiredJSONFields(rawPayload, reflect.TypeOf(predicate))
if err != nil {
return nil, fmt.Errorf("provenance predicate: %w", err)
}
err = json.Unmarshal(rawPayload, &predicate)
if err != nil {
return "", fmt.Errorf("unmarshal Provenance predicate: %w", err)
}
return in_toto.ProvenanceStatementSLSA1{
StatementHeader: generateStatementHeader(digest, repo, slsa1.PredicateSLSAProvenance),
Predicate: predicate,
}, nil
}
func generateLinkStatement(rawPayload []byte, digest string, repo string) (interface{}, error) {
var link in_toto.Link
err := checkRequiredJSONFields(rawPayload, reflect.TypeOf(link))
if err != nil {
return nil, fmt.Errorf("link statement: %w", err)
}
err = json.Unmarshal(rawPayload, &link)
if err != nil {
return "", fmt.Errorf("unmarshal Link statement: %w", err)
}
return in_toto.LinkStatement{
StatementHeader: generateStatementHeader(digest, repo, in_toto.PredicateLinkV1),
Predicate: link,
}, nil
}
func generateOpenVexStatement(rawPayload []byte, digest string, repo string) (interface{}, error) {
var data interface{}
if err := json.Unmarshal(rawPayload, &data); err != nil {
return nil, err
}
return in_toto.Statement{
StatementHeader: generateStatementHeader(digest, repo, OpenVexNamespace),
Predicate: data,
}, nil
}
func generateSPDXStatement(rawPayload []byte, digest string, repo string, parseJSON bool) (interface{}, error) {
var data interface{}
if parseJSON {
if err := json.Unmarshal(rawPayload, &data); err != nil {
return nil, err
}
} else {
data = string(rawPayload)
}
return in_toto.SPDXStatement{
StatementHeader: generateStatementHeader(digest, repo, in_toto.PredicateSPDX),
Predicate: data,
}, nil
}
func generateCycloneDXStatement(rawPayload []byte, digest string, repo string) (interface{}, error) {
var data interface{}
if err := json.Unmarshal(rawPayload, &data); err != nil {
return nil, err
}
return in_toto.SPDXStatement{
StatementHeader: generateStatementHeader(digest, repo, in_toto.PredicateCycloneDX),
Predicate: data,
}, nil
}
func checkRequiredJSONFields(rawPayload []byte, typ reflect.Type) error {
var tmp map[string]interface{}
if err := json.Unmarshal(rawPayload, &tmp); err != nil {
return err
}
// Create list of json tags, e.g. `json:"_type"`
attributeCount := typ.NumField()
allFields := make([]string, 0)
for i := 0; i < attributeCount; i++ {
jsonTagFields := strings.SplitN(typ.Field(i).Tag.Get("json"), ",", 2)
if len(jsonTagFields) < 2 {
allFields = append(allFields, jsonTagFields[0])
}
}
// Assert that there's a key in the passed map for each tag
for _, field := range allFields {
if _, ok := tmp[field]; !ok {
return fmt.Errorf("required field %s missing", field)
}
}
return nil
}
// Copyright 2024 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 bundle
import (
"crypto"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/json"
"fmt"
"github.com/secure-systems-lab/go-securesystemslib/dsse"
protobundle "github.com/sigstore/protobuf-specs/gen/pb-go/bundle/v1"
protocommon "github.com/sigstore/protobuf-specs/gen/pb-go/common/v1"
protodsse "github.com/sigstore/protobuf-specs/gen/pb-go/dsse"
protorekor "github.com/sigstore/protobuf-specs/gen/pb-go/rekor/v1"
"github.com/sigstore/rekor/pkg/generated/models"
"github.com/sigstore/rekor/pkg/tle"
"github.com/sigstore/sigstore/pkg/cryptoutils"
"google.golang.org/protobuf/encoding/protojson"
)
const bundleV03MediaType = "application/vnd.dev.sigstore.bundle.v0.3+json"
func MakeProtobufBundle(hint string, rawCert []byte, rekorEntry *models.LogEntryAnon, timestampBytes []byte) (*protobundle.Bundle, error) {
bundle := &protobundle.Bundle{MediaType: bundleV03MediaType}
if hint != "" {
bundle.VerificationMaterial = &protobundle.VerificationMaterial{
Content: &protobundle.VerificationMaterial_PublicKey{
PublicKey: &protocommon.PublicKeyIdentifier{
Hint: hint,
},
},
}
} else if len(rawCert) > 0 {
bundle.VerificationMaterial = &protobundle.VerificationMaterial{
Content: &protobundle.VerificationMaterial_Certificate{
Certificate: &protocommon.X509Certificate{
RawBytes: rawCert,
},
},
}
}
if len(timestampBytes) > 0 {
bundle.VerificationMaterial.TimestampVerificationData = &protobundle.TimestampVerificationData{
Rfc3161Timestamps: []*protocommon.RFC3161SignedTimestamp{
{SignedTimestamp: timestampBytes},
},
}
}
if rekorEntry != nil {
tlogEntry, err := tle.GenerateTransparencyLogEntry(*rekorEntry)
if err != nil {
return nil, err
}
bundle.VerificationMaterial.TlogEntries = []*protorekor.TransparencyLogEntry{tlogEntry}
}
return bundle, nil
}
func MakeNewBundle(pubKey crypto.PublicKey, rekorEntry *models.LogEntryAnon, payload, sig, signer, timestampBytes []byte) ([]byte, error) {
// Determine if the signer is a certificate or not
var hint string
var rawCert []byte
cert, err := cryptoutils.UnmarshalCertificatesFromPEM(signer)
if err != nil || len(cert) == 0 {
pkixPubKey, err := x509.MarshalPKIXPublicKey(pubKey)
if err != nil {
return nil, err
}
hashedBytes := sha256.Sum256(pkixPubKey)
hint = base64.StdEncoding.EncodeToString(hashedBytes[:])
} else {
rawCert = cert[0].Raw
}
bundle, err := MakeProtobufBundle(hint, rawCert, rekorEntry, timestampBytes)
if err != nil {
return nil, err
}
var envelope dsse.Envelope
err = json.Unmarshal(sig, &envelope)
if err != nil {
return nil, err
}
if len(envelope.Signatures) == 0 {
return nil, fmt.Errorf("no signature in DSSE envelope")
}
sigBytes, err := base64.StdEncoding.DecodeString(envelope.Signatures[0].Sig)
if err != nil {
return nil, err
}
bundle.Content = &protobundle.Bundle_DsseEnvelope{
DsseEnvelope: &protodsse.Envelope{
Payload: payload,
PayloadType: envelope.PayloadType,
Signatures: []*protodsse.Signature{
{
Sig: sigBytes,
},
},
},
}
contents, err := protojson.Marshal(bundle)
if err != nil {
return nil, err
}
return contents, nil
}
// 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 bundle
import (
"github.com/sigstore/rekor/pkg/generated/models"
)
// RekorBundle holds metadata about recording a Signature's ephemeral key to
// a Rekor transparency log.
type RekorBundle struct {
SignedEntryTimestamp []byte
Payload RekorPayload
}
type RekorPayload struct {
Body interface{} `json:"body"`
IntegratedTime int64 `json:"integratedTime"`
LogIndex int64 `json:"logIndex"`
LogID string `json:"logID"`
}
func EntryToBundle(entry *models.LogEntryAnon) *RekorBundle {
if entry.Verification == nil {
return nil
}
return &RekorBundle{
SignedEntryTimestamp: entry.Verification.SignedEntryTimestamp,
Payload: RekorPayload{
Body: entry.Body,
IntegratedTime: *entry.IntegratedTime,
LogIndex: *entry.LogIndex,
LogID: *entry.LogID,
},
}
}
// Copyright 2025 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 bundle
import (
"context"
"crypto/x509"
"encoding/pem"
"fmt"
"log"
"time"
"github.com/sigstore/cosign/v3/internal/ui"
"github.com/sigstore/sigstore-go/pkg/root"
"github.com/sigstore/sigstore-go/pkg/sign"
"github.com/sigstore/sigstore/pkg/signature"
"google.golang.org/protobuf/encoding/protojson"
)
func SignData(ctx context.Context, content sign.Content, keypair sign.Keypair, idToken string, signingConfig *root.SigningConfig, trustedMaterial root.TrustedMaterial) ([]byte, error) {
var opts sign.BundleOptions
if trustedMaterial != nil {
opts.TrustedRoot = trustedMaterial
}
if idToken != "" {
if len(signingConfig.FulcioCertificateAuthorityURLs()) == 0 {
return nil, fmt.Errorf("no fulcio URLs provided in signing config")
}
fulcioSvc, err := root.SelectService(signingConfig.FulcioCertificateAuthorityURLs(), sign.FulcioAPIVersions, time.Now())
if err != nil {
return nil, err
}
fulcioOpts := &sign.FulcioOptions{
BaseURL: fulcioSvc.URL,
Timeout: 30 * time.Second,
Retries: 1,
}
opts.CertificateProvider = sign.NewFulcio(fulcioOpts)
opts.CertificateProviderOptions = &sign.CertificateProviderOptions{
IDToken: idToken,
}
} else {
publicKeyPem, err := keypair.GetPublicKeyPem()
if err != nil {
return nil, err
}
block, _ := pem.Decode([]byte(publicKeyPem))
pubKey, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
log.Fatal(err)
}
verifier, err := signature.LoadDefaultVerifier(pubKey)
if err != nil {
log.Fatal(err)
}
key := root.NewExpiringKey(verifier, time.Time{}, time.Time{})
keyTrustedMaterial := root.NewTrustedPublicKeyMaterial(func(_ string) (root.TimeConstrainedVerifier, error) {
return key, nil
})
trustedMaterial := &verifyTrustedMaterial{
TrustedMaterial: opts.TrustedRoot,
keyTrustedMaterial: keyTrustedMaterial,
}
opts.TrustedRoot = trustedMaterial
}
if len(signingConfig.TimestampAuthorityURLs()) != 0 {
tsaSvcs, err := root.SelectServices(signingConfig.TimestampAuthorityURLs(),
signingConfig.TimestampAuthorityURLsConfig(), sign.TimestampAuthorityAPIVersions, time.Now())
if err != nil {
log.Fatal(err)
}
for _, tsaSvc := range tsaSvcs {
tsaOpts := &sign.TimestampAuthorityOptions{
URL: tsaSvc.URL,
Timeout: 30 * time.Second,
Retries: 1,
}
opts.TimestampAuthorities = append(opts.TimestampAuthorities, sign.NewTimestampAuthority(tsaOpts))
}
}
if len(signingConfig.RekorLogURLs()) != 0 {
rekorSvcs, err := root.SelectServices(signingConfig.RekorLogURLs(),
signingConfig.RekorLogURLsConfig(), sign.RekorAPIVersions, time.Now())
if err != nil {
return nil, err
}
for _, rekorSvc := range rekorSvcs {
rekorOpts := &sign.RekorOptions{
BaseURL: rekorSvc.URL,
Timeout: 90 * time.Second,
Retries: 1,
Version: rekorSvc.MajorAPIVersion,
}
opts.TransparencyLogs = append(opts.TransparencyLogs, sign.NewRekor(rekorOpts))
}
}
spinner := ui.NewSpinner(ctx, "Signing artifact...")
defer spinner.Stop()
bundle, err := sign.Bundle(content, keypair, opts)
if err != nil {
return nil, fmt.Errorf("error signing bundle: %w", err)
}
return protojson.Marshal(bundle)
}
type verifyTrustedMaterial struct {
root.TrustedMaterial
keyTrustedMaterial root.TrustedMaterial
}
func (v *verifyTrustedMaterial) PublicKeyVerifier(hint string) (root.TimeConstrainedVerifier, error) {
return v.keyTrustedMaterial.PublicKeyVerifier(hint)
}
// 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 bundle
// RFC3161Timestamp holds metadata about timestamp RFC3161 verification data.
type RFC3161Timestamp struct {
// SignedRFC3161Timestamp contains a DER encoded TimeStampResponse.
// See https://www.rfc-editor.org/rfc/rfc3161.html#section-2.4.2
// Clients MUST verify the hashed message in the message imprint,
// typically using the artifact signature.
SignedRFC3161Timestamp []byte
}
// TimestampToRFC3161Timestamp receives a base64 encoded RFC3161 timestamp.
func TimestampToRFC3161Timestamp(timestampRFC3161 []byte) *RFC3161Timestamp {
if timestampRFC3161 != nil {
return &RFC3161Timestamp{
SignedRFC3161Timestamp: timestampRFC3161,
}
}
return nil
}
//
// 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 cosign
import (
"crypto/x509"
)
type CertExtensions struct {
Cert *x509.Certificate
}
var (
// Fulcio cert-extensions, documented here: https://github.com/sigstore/fulcio/blob/main/docs/oid-info.md
CertExtensionOIDCIssuer = "1.3.6.1.4.1.57264.1.1"
CertExtensionGithubWorkflowTrigger = "1.3.6.1.4.1.57264.1.2"
CertExtensionGithubWorkflowSha = "1.3.6.1.4.1.57264.1.3"
CertExtensionGithubWorkflowName = "1.3.6.1.4.1.57264.1.4"
CertExtensionGithubWorkflowRepository = "1.3.6.1.4.1.57264.1.5"
CertExtensionGithubWorkflowRef = "1.3.6.1.4.1.57264.1.6"
CertExtensionMap = map[string]string{
CertExtensionOIDCIssuer: "oidcIssuer",
CertExtensionGithubWorkflowTrigger: "githubWorkflowTrigger",
CertExtensionGithubWorkflowSha: "githubWorkflowSha",
CertExtensionGithubWorkflowName: "githubWorkflowName",
CertExtensionGithubWorkflowRepository: "githubWorkflowRepository",
CertExtensionGithubWorkflowRef: "githubWorkflowRef",
}
)
func (ce *CertExtensions) certExtensions() map[string]string {
extensions := map[string]string{}
for _, ext := range ce.Cert.Extensions {
readableName, ok := CertExtensionMap[ext.Id.String()]
if ok {
extensions[readableName] = string(ext.Value)
} else {
extensions[ext.Id.String()] = string(ext.Value)
}
}
return extensions
}
// GetIssuer returns the issuer for a Certificate
func (ce *CertExtensions) GetIssuer() string {
return ce.certExtensions()["oidcIssuer"]
}
// GetCertExtensionGithubWorkflowTrigger returns the GitHub Workflow Trigger for a Certificate
func (ce *CertExtensions) GetCertExtensionGithubWorkflowTrigger() string {
return ce.certExtensions()["githubWorkflowTrigger"]
}
// GetExtensionGithubWorkflowSha returns the GitHub Workflow SHA for a Certificate
func (ce *CertExtensions) GetExtensionGithubWorkflowSha() string {
return ce.certExtensions()["githubWorkflowSha"]
}
// GetCertExtensionGithubWorkflowName returns the GitHub Workflow Name for a Certificate
func (ce *CertExtensions) GetCertExtensionGithubWorkflowName() string {
return ce.certExtensions()["githubWorkflowName"]
}
// GetCertExtensionGithubWorkflowRepository returns the GitHub Workflow Repository for a Certificate
func (ce *CertExtensions) GetCertExtensionGithubWorkflowRepository() string {
return ce.certExtensions()["githubWorkflowRepository"]
}
// GetCertExtensionGithubWorkflowRef returns the GitHub Workflow Ref for a Certificate
func (ce *CertExtensions) GetCertExtensionGithubWorkflowRef() string {
return ce.certExtensions()["githubWorkflowRef"]
}
//
// Copyright 2021 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 cosign
import (
"errors"
"fmt"
"os"
"syscall"
"golang.org/x/term"
)
// TODO(jason): Move this to an internal package.
func GetPassFromTerm(confirm bool) ([]byte, error) {
fmt.Fprint(os.Stderr, "Enter password for private key: ")
// Unnecessary convert of syscall.Stdin on *nix, but Windows is a uintptr
// nolint:unconvert
pw1, err := term.ReadPassword(int(syscall.Stdin))
if err != nil {
return nil, err
}
fmt.Fprintln(os.Stderr)
if !confirm {
return pw1, nil
}
fmt.Fprint(os.Stderr, "Enter password for private key again: ")
// Unnecessary convert of syscall.Stdin on *nix, but Windows is a uintptr
// nolint:unconvert
confirmpw, err := term.ReadPassword(int(syscall.Stdin))
fmt.Fprintln(os.Stderr)
if err != nil {
return nil, err
}
if string(pw1) != string(confirmpw) {
return nil, errors.New("passwords do not match")
}
return pw1, nil
}
// TODO(jason): Move this to an internal package.
func IsTerminal() bool {
stat, _ := os.Stdin.Stat()
return (stat.Mode() & os.ModeCharDevice) != 0
}
// Copyright 2021 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 cosign
import (
"context"
"errors"
"fmt"
"os"
"github.com/sigstore/cosign/v3/pkg/cosign/env"
"github.com/sigstore/sigstore/pkg/tuf"
)
// This is the CT log public key target name
var ctPublicKeyStr = `ctfe.pub`
// GetCTLogPubs retrieves trusted CTLog public keys from the embedded or cached
// TUF root. If expired, makes a network call to retrieve the updated targets.
// By default the public keys comes from TUF, but you can override this for test
// purposes by using an env variable `SIGSTORE_CT_LOG_PUBLIC_KEY_FILE`. If using
// an alternate, the file can be PEM, or DER format.
func GetCTLogPubs(ctx context.Context) (*TrustedTransparencyLogPubKeys, error) {
publicKeys := NewTrustedTransparencyLogPubKeys()
altCTLogPub := env.Getenv(env.VariableSigstoreCTLogPublicKeyFile)
if altCTLogPub != "" {
raw, err := os.ReadFile(altCTLogPub)
if err != nil {
return nil, fmt.Errorf("error reading alternate CTLog public key file: %w", err)
}
if err := publicKeys.AddTransparencyLogPubKey(raw, tuf.Active); err != nil {
return nil, fmt.Errorf("AddCTLogPubKey: %w", err)
}
} else {
tufClient, err := tuf.NewFromEnv(ctx)
if err != nil {
return nil, err
}
targets, err := tufClient.GetTargetsByMeta(tuf.CTFE, []string{ctPublicKeyStr})
if err != nil {
return nil, err
}
for _, t := range targets {
if err := publicKeys.AddTransparencyLogPubKey(t.Target, t.Status); err != nil {
return nil, fmt.Errorf("AddCTLogPubKey: %w", err)
}
}
}
if len(publicKeys.Keys) == 0 {
return nil, errors.New("none of the CTLog public keys have been found")
}
return &publicKeys, nil
}
//
// Copyright 2021 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 cue
import (
"cuelang.org/go/cue/cuecontext"
"cuelang.org/go/cue/load"
cuejson "cuelang.org/go/encoding/json"
)
func ValidateJSON(jsonBody []byte, entrypoints []string) error {
ctx := cuecontext.New()
bis := load.Instances(entrypoints, nil)
for _, bi := range bis {
if bi.Err != nil {
return bi.Err
}
value := ctx.BuildInstance(bi)
if value.Err() != nil {
return value.Err()
}
err := cuejson.Validate(jsonBody, value)
if err != nil {
return err
}
}
return nil
}
//
// 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 env
import (
"fmt"
"os"
"strings"
)
// Variable is a type representing an environment variable
type Variable string
// VariableOpts closely describes a Variable
type VariableOpts struct {
// Description contains description for the environment variable
Description string
// Expects describes what value is expected by the environment variable
Expects string
// Sensitive is used for environment variables with sensitive values
// (e.g. passwords, credentials, etc.)
Sensitive bool
// External is used for environment variables coming from external projects
// and dependencies (e.g. GITHUB_TOKEN, SIGSTORE_, TUF_)
External bool
}
func (v Variable) String() string {
return string(v)
}
const (
// Cosign environment variables
VariableExperimental Variable = "COSIGN_EXPERIMENTAL"
VariableDockerMediaTypes Variable = "COSIGN_DOCKER_MEDIA_TYPES"
VariablePassword Variable = "COSIGN_PASSWORD"
VariablePKCS11Pin Variable = "COSIGN_PKCS11_PIN"
VariablePKCS11ModulePath Variable = "COSIGN_PKCS11_MODULE_PATH"
VariablePKCS11IgnoreCertificate Variable = "COSIGN_PKCS11_IGNORE_CERTIFICATE"
VariableRepository Variable = "COSIGN_REPOSITORY"
VariableMaxAttachmentSize Variable = "COSIGN_MAX_ATTACHMENT_SIZE"
// Sigstore environment variables
VariableSigstoreCTLogPublicKeyFile Variable = "SIGSTORE_CT_LOG_PUBLIC_KEY_FILE"
VariableSigstoreRootFile Variable = "SIGSTORE_ROOT_FILE"
VariableSigstoreRekorPublicKey Variable = "SIGSTORE_REKOR_PUBLIC_KEY"
VariableSigstoreIDToken Variable = "SIGSTORE_ID_TOKEN" //nolint:gosec
VariableSigstoreTSACertificateFile Variable = "SIGSTORE_TSA_CERTIFICATE_FILE"
// TUF environment variables
VariableTUFRootDir Variable = "TUF_ROOT"
VariableTUFMirror Variable = "TUF_MIRROR"
VariableTUFRootJSON Variable = "TUF_ROOT_JSON"
// Other external environment variables
VariableGitHubHost Variable = "GITHUB_HOST"
VariableGitHubToken Variable = "GITHUB_TOKEN" //nolint:gosec
VariableGitHubRequestToken Variable = "ACTIONS_ID_TOKEN_REQUEST_TOKEN"
VariableGitHubRequestURL Variable = "ACTIONS_ID_TOKEN_REQUEST_URL"
VariableSPIFFEEndpointSocket Variable = "SPIFFE_ENDPOINT_SOCKET"
VariableGoogleServiceAccountName Variable = "GOOGLE_SERVICE_ACCOUNT_NAME"
VariableGitLabHost Variable = "GITLAB_HOST"
VariableGitLabToken Variable = "GITLAB_TOKEN"
VariableBuildkiteAgentAccessToken Variable = "BUILDKITE_AGENT_ACCESS_TOKEN"
VariableBuildkiteAgentEndpoint Variable = "BUILDKITE_AGENT_ENDPOINT"
VariableBuildkiteJobID Variable = "BUILDKITE_JOB_ID"
VariableBuildkiteAgentLogLevel Variable = "BUILDKITE_AGENT_LOG_LEVEL"
VariableSourceDateEpoch Variable = "SOURCE_DATE_EPOCH"
)
var (
// NB: this is intentionally private to avoid anyone changing this from
// code. There's a getter function used to get this slice if needed.
environmentVariables = map[Variable]VariableOpts{
VariableExperimental: {
Description: "enables experimental cosign features",
Expects: "1 if experimental features should be enabled (0 by default)",
Sensitive: false,
},
VariableDockerMediaTypes: {
Description: "to be used with registries that do not support OCI media types",
Expects: "1 to fallback to legacy OCI media types equivalents (0 by default)",
Sensitive: false,
},
VariablePassword: {
Description: "overrides password inputs with this value",
Expects: "string with a password (asks on stdin by default)",
Sensitive: true,
},
VariablePKCS11Pin: {
Description: "to be used if PKCS11 PIN is not provided",
Expects: "string with a PIN",
Sensitive: true,
},
VariablePKCS11ModulePath: {
Description: "is PKCS11 module-path",
Expects: "string with a module-path",
Sensitive: false,
},
VariablePKCS11IgnoreCertificate: {
Description: "disables loading certificates with PKCS11",
Expects: "1 if loading certificates should be disabled (0 by default)",
Sensitive: false,
},
VariableRepository: {
Description: "can be used to store signatures in an alternate location",
Expects: "string with a repository",
Sensitive: false,
},
VariableMaxAttachmentSize: {
Description: "maximum attachment size to download (default 128MiB)",
Expects: "human-readable unit of memory, e.g. 5120, 20K, 3M, 45MiB, 1GB",
Sensitive: false,
},
VariableSigstoreCTLogPublicKeyFile: {
Description: "overrides what is used to validate the SCT coming back from Fulcio",
Expects: "path to the public key file",
Sensitive: false,
External: true,
},
VariableSigstoreRootFile: {
Description: "overrides the public good instance root CA",
Expects: "path to the root CA",
Sensitive: false,
External: true,
},
VariableSigstoreRekorPublicKey: {
Description: "if specified, you can specify an oob Public Key that Rekor uses",
Expects: "path to the public key",
Sensitive: false,
External: true,
},
VariableSigstoreTSACertificateFile: {
Description: "path to the concatenated PEM-encoded TSA certificate file (leaf, intermediate(s), root) used by Sigstore",
Expects: "path to the TSA certificate file",
Sensitive: false,
External: true,
},
VariableTUFMirror: {
Description: "URL of the TUF mirror. Use with TUF_ROOT_JSON to refresh TUF metadata during signing and verification commands. Setting this will cause cosign to attempt to use trusted_root.json if available and will ignore custom TUF metadata.",
Expects: "URL of the TUF mirror",
Sensitive: false,
External: true,
},
VariableTUFRootDir: {
Description: "path to the TUF cache directory",
Expects: "path to the TUF cache directory",
Sensitive: false,
External: true,
},
VariableTUFRootJSON: {
Description: "path to the TUF root.json file used to initialize and update a local TUF repository. Use with TUF_MIRROR to refresh TUF metadata during signing and verification commands. Setting this will cause cosign to attempt to use trusted_root.json if available and will ignore custom TUF metadata.",
Expects: "path to root.json",
Sensitive: false,
External: true,
},
VariableGitHubHost: {
Description: "is URL of the GitHub Enterprise instance",
Expects: "string with the URL of GitHub Enterprise instance",
Sensitive: false,
External: true,
},
VariableGitHubToken: {
Description: "is a token used to authenticate with GitHub",
Expects: "token generated on GitHub",
Sensitive: true,
External: true,
},
VariableGitHubRequestToken: {
Description: "is bearer token for the request to the OIDC provider",
Expects: "string with a bearer token",
Sensitive: true,
External: true,
},
VariableGitHubRequestURL: {
Description: "is the URL for GitHub's OIDC provider",
Expects: "string with the URL for the OIDC provider",
Sensitive: false,
External: true,
},
VariableSPIFFEEndpointSocket: {
Description: "allows you to specify non-default SPIFFE socket to use.",
Expects: "string with SPIFFE socket path",
Sensitive: false,
External: true,
},
VariableGoogleServiceAccountName: {
Description: "is a service account name to be used with the Google provider",
Expects: "string with the service account's name",
Sensitive: false,
External: true,
},
VariableGitLabHost: {
Description: "is URL of the GitLab instance",
Expects: "string with the URL of GitLab instance",
Sensitive: false,
External: true,
},
VariableGitLabToken: {
Description: "is a token used to authenticate with GitLab",
Expects: "string with a token",
Sensitive: true,
External: true,
},
VariableBuildkiteAgentAccessToken: {
Description: "is an access token used to identify the Buildkite agent",
Expects: "string with an access token",
Sensitive: true,
External: true,
},
VariableBuildkiteAgentEndpoint: {
Description: "the Buildkite agent endpoint",
Expects: "string with an endpoint",
Sensitive: false,
External: true,
},
VariableBuildkiteJobID: {
Description: "the Buildkite job ID to claim in the OIDC token",
Expects: "string with a job ID",
Sensitive: false,
External: true,
},
VariableBuildkiteAgentLogLevel: {
Description: "the log level for the Buildkite agent",
Expects: "string with log level, either debug, notice, info, error, warn, fatal (default: notice)",
Sensitive: false,
External: true,
},
VariableSigstoreIDToken: {
Description: "is a OIDC token used to authenticate to Fulcio",
Expects: "string with a OIDC token",
Sensitive: true,
External: true,
},
VariableSourceDateEpoch: {
Description: "overrides current time for reproducible builds, see https://reproducible-builds.org/docs/source-date-epoch/",
Expects: "number of seconds since unix epoch",
Sensitive: false,
External: true,
},
}
)
func EnvironmentVariables() map[Variable]VariableOpts {
return environmentVariables
}
func mustRegisterEnv(name Variable) {
opts, ok := environmentVariables[name]
if !ok {
panic(fmt.Sprintf("environment variable %q is not registered in pkg/cosign/env", name.String()))
}
if !opts.External && !strings.HasPrefix(name.String(), "COSIGN_") {
panic(fmt.Sprintf("cosign environment variable %q must start with COSIGN_ prefix", name.String()))
}
}
func Getenv(name Variable) string {
mustRegisterEnv(name)
return os.Getenv(name.String()) //nolint:forbidigo
}
func LookupEnv(name Variable) (string, bool) {
mustRegisterEnv(name)
return os.LookupEnv(name.String()) //nolint:forbidigo
}
// 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 cosign
import "fmt"
// VerificationFailure is the type of Go error that is used by cosign to surface
// errors actually related to verification (vs. transient, misconfiguration,
// transport, or authentication related issues).
type VerificationFailure struct {
err error
}
func (e *VerificationFailure) Error() string {
return e.err.Error()
}
func (e *VerificationFailure) Unwrap() error {
return e.err
}
type ErrNoMatchingSignatures struct {
err error
}
func (e *ErrNoMatchingSignatures) Error() string {
return e.err.Error()
}
func (e *ErrNoMatchingSignatures) Unwrap() error {
return e.err
}
type ErrImageTagNotFound struct {
err error
}
func (e *ErrImageTagNotFound) Error() string {
return e.err.Error()
}
func (e *ErrImageTagNotFound) Unwrap() error {
return e.err
}
type ErrNoSignaturesFound struct {
err error
}
func (e *ErrNoSignaturesFound) Error() string {
return e.err.Error()
}
func (e *ErrNoSignaturesFound) Unwrap() error {
return e.err
}
type ErrNoMatchingAttestations struct {
err error
}
func (e *ErrNoMatchingAttestations) Error() string {
return e.err.Error()
}
func (e *ErrNoMatchingAttestations) Unwrap() error {
return e.err
}
type ErrNoCertificateFoundOnSignature struct {
err error
}
func (e *ErrNoCertificateFoundOnSignature) Error() string {
return e.err.Error()
}
func (e *ErrNoCertificateFoundOnSignature) Unwrap() error {
return e.err
}
// NewVerificationError exists for backwards compatibility.
// Deprecated: see [VerificationFailure].
func NewVerificationError(msg string, args ...interface{}) error {
return &VerificationError{
message: fmt.Sprintf(msg, args...),
}
}
// VerificationError exists for backwards compatibility.
// Deprecated: see [VerificationFailure].
type VerificationError struct {
message string
}
func (e *VerificationError) Error() string {
return e.message
}
var (
// ErrNoMatchingAttestationsMessage exists for backwards compatibility.
// Deprecated: see [ErrNoMatchingAttestations].
ErrNoMatchingAttestationsMessage = "no matching attestations"
// ErrNoMatchingAttestationsType exists for backwards compatibility.
// Deprecated: see [ErrNoMatchingAttestations].
ErrNoMatchingAttestationsType = "NoMatchingAttestations"
)
//
// Copyright 2021 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 cosign
import (
"context"
"crypto/x509"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"os"
"runtime"
"sync"
"github.com/google/go-containerregistry/pkg/name"
"github.com/in-toto/in-toto-golang/in_toto"
"github.com/sigstore/cosign/v3/pkg/cosign/bundle"
"github.com/sigstore/cosign/v3/pkg/oci"
ociremote "github.com/sigstore/cosign/v3/pkg/oci/remote"
"golang.org/x/sync/errgroup"
)
const maxAllowedSigsOrAtts = 100
type SignedPayload struct {
Base64Signature string
Payload []byte
Cert *x509.Certificate
Chain []*x509.Certificate
Bundle *bundle.RekorBundle
RFC3161Timestamp *bundle.RFC3161Timestamp
}
type LocalSignedPayload struct {
Base64Signature string `json:"base64Signature"`
Cert string `json:"cert,omitempty"`
Bundle *bundle.RekorBundle `json:"rekorBundle,omitempty"`
}
type Signatures struct {
KeyID string `json:"keyid"`
Sig string `json:"sig"`
}
type AttestationPayload struct {
PayloadType string `json:"payloadType"`
PayLoad string `json:"payload"`
Signatures []Signatures `json:"signatures"`
}
const (
Signature = "signature"
SBOM = "sbom"
Attestation = "attestation"
Digest = "digest"
)
func FetchSignaturesForReference(_ context.Context, ref name.Reference, opts ...ociremote.Option) ([]SignedPayload, error) {
simg, err := ociremote.SignedEntity(ref, opts...)
if err != nil {
return nil, err
}
sigs, err := FetchSignatures(simg)
if err != nil {
return nil, fmt.Errorf("%s: %w", ref, err)
}
return sigs, nil
}
func FetchSignatures(se oci.SignedEntity) ([]SignedPayload, error) {
sigs, err := se.Signatures()
if err != nil {
return nil, fmt.Errorf("remote image: %w", err)
}
l, err := sigs.Get()
if err != nil {
return nil, fmt.Errorf("fetching signatures: %w", err)
}
if len(l) == 0 {
return nil, errors.New("no signatures associated")
}
if len(l) > maxAllowedSigsOrAtts {
return nil, fmt.Errorf("maximum number of signatures on an image is %d, found %d", maxAllowedSigsOrAtts, len(l))
}
signatures := make([]SignedPayload, len(l))
var g errgroup.Group
g.SetLimit(runtime.NumCPU())
for i, sig := range l {
i, sig := i, sig
g.Go(func() error {
var err error
signatures[i].Payload, err = sig.Payload()
if err != nil {
return err
}
signatures[i].Base64Signature, err = sig.Base64Signature()
if err != nil {
return err
}
signatures[i].Cert, err = sig.Cert()
if err != nil {
return err
}
signatures[i].Chain, err = sig.Chain()
if err != nil {
return err
}
signatures[i].RFC3161Timestamp, err = sig.RFC3161Timestamp()
if err != nil {
return err
}
signatures[i].Bundle, err = sig.Bundle()
return err
})
}
if err := g.Wait(); err != nil {
return nil, err
}
return signatures, nil
}
func FetchAttestationsForReference(_ context.Context, ref name.Reference, predicateType string, opts ...ociremote.Option) ([]AttestationPayload, error) {
se, err := ociremote.SignedEntity(ref, opts...)
if err != nil {
return nil, err
}
return FetchAttestations(se, predicateType)
}
func FetchAttestations(se oci.SignedEntity, predicateType string) ([]AttestationPayload, error) {
atts, err := se.Attestations()
if err != nil {
return nil, fmt.Errorf("remote image: %w", err)
}
l, err := atts.Get()
if err != nil {
return nil, fmt.Errorf("fetching attestations: %w", err)
}
if len(l) == 0 {
return nil, errors.New("found no attestations")
}
if len(l) > maxAllowedSigsOrAtts {
errMsg := fmt.Sprintf("maximum number of attestations on an image is %d, found %d", maxAllowedSigsOrAtts, len(l))
return nil, errors.New(errMsg)
}
attestations := make([]AttestationPayload, 0, len(l))
var attMu sync.Mutex
var g errgroup.Group
g.SetLimit(runtime.NumCPU())
for _, att := range l {
att := att
g.Go(func() error {
rawPayload, err := att.Payload()
if err != nil {
return fmt.Errorf("fetching payload: %w", err)
}
var payload AttestationPayload
if err := json.Unmarshal(rawPayload, &payload); err != nil {
return fmt.Errorf("unmarshaling payload: %w", err)
}
if predicateType != "" {
var decodedPayload []byte
decodedPayload, err = base64.StdEncoding.DecodeString(payload.PayLoad)
if err != nil {
return fmt.Errorf("decoding payload: %w", err)
}
var statement in_toto.Statement
if err := json.Unmarshal(decodedPayload, &statement); err != nil {
return fmt.Errorf("unmarshaling statement: %w", err)
}
if statement.PredicateType != predicateType {
return nil
}
}
attMu.Lock()
defer attMu.Unlock()
attestations = append(attestations, payload)
return nil
})
}
if err := g.Wait(); err != nil {
return nil, err
}
if len(attestations) == 0 && predicateType != "" {
return nil, fmt.Errorf("no attestations with predicate type '%s' found", predicateType)
}
return attestations, nil
}
// FetchLocalSignedPayloadFromPath fetches a local signed payload from a path to a file
func FetchLocalSignedPayloadFromPath(path string) (*LocalSignedPayload, error) {
contents, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("reading %s: %w", path, err)
}
var b *LocalSignedPayload
if err := json.Unmarshal(contents, &b); err != nil {
return nil, err
}
return b, nil
}
// Copyright 2018 Google LLC. All Rights Reserved.
//
// 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 copied from
// https://github.com/google/certificate-transparency-go/blob/master/ctutil/ctutil.go
// Package ctutil contains utilities for Certificate Transparency.
package ctutil
import (
"bytes"
"crypto"
"crypto/sha256"
"encoding/base64"
"errors"
"fmt"
ct "github.com/google/certificate-transparency-go"
"github.com/google/certificate-transparency-go/tls"
"github.com/google/certificate-transparency-go/x509"
)
var emptyHash = [sha256.Size]byte{}
// LeafHashB64 does as LeafHash does, but returns the leaf hash base64-encoded.
// The base64-encoded leaf hash returned by B64LeafHash can be used with the
// get-proof-by-hash API endpoint of Certificate Transparency Logs.
func LeafHashB64(chain []*x509.Certificate, sct *ct.SignedCertificateTimestamp, embedded bool) (string, error) {
hash, err := LeafHash(chain, sct, embedded)
if err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString(hash[:]), nil
}
// LeafHash calculates the leaf hash of the certificate or precertificate at
// chain[0] that sct was issued for.
//
// sct is required because the SCT timestamp is used to calculate the leaf hash.
// Leaf hashes are unique to (pre)certificate-SCT pairs.
//
// This function can be used with three different types of leaf certificate:
// - X.509 Certificate:
// If using this function to calculate the leaf hash for a normal X.509
// certificate then it is enough to just provide the end entity
// certificate in chain. This case assumes that the SCT being provided is
// not embedded within the leaf certificate provided, i.e. the certificate
// is what was submitted to the Certificate Transparency Log in order to
// obtain the SCT. For this case, set embedded to false.
// - Precertificate:
// If using this function to calculate the leaf hash for a precertificate
// then the issuing certificate must also be provided in chain. The
// precertificate should be at chain[0], and its issuer at chain[1]. For
// this case, set embedded to false.
// - X.509 Certificate containing the SCT embedded within it:
// If using this function to calculate the leaf hash for a certificate
// where the SCT provided is embedded within the certificate you
// are providing at chain[0], set embedded to true. LeafHash will
// calculate the leaf hash by building the corresponding precertificate.
// LeafHash will return an error if the provided SCT cannot be found
// embedded within chain[0]. As with the precertificate case, the issuing
// certificate must also be provided in chain. The certificate containing
// the embedded SCT should be at chain[0], and its issuer at chain[1].
//
// Note: LeafHash doesn't check that the provided SCT verifies for the given
// chain. It simply calculates what the leaf hash would be for the given
// (pre)certificate-SCT pair.
func LeafHash(chain []*x509.Certificate, sct *ct.SignedCertificateTimestamp, embedded bool) ([sha256.Size]byte, error) {
leaf, err := createLeaf(chain, sct, embedded)
if err != nil {
return emptyHash, err
}
return ct.LeafHashForLeaf(leaf)
}
// VerifySCT takes the public key of a Certificate Transparency Log, a
// certificate chain, and an SCT and verifies whether the SCT is a valid SCT for
// the certificate at chain[0], signed by the Log that the public key belongs
// to. If the SCT does not verify, an error will be returned.
//
// This function can be used with three different types of leaf certificate:
// - X.509 Certificate:
// If using this function to verify an SCT for a normal X.509 certificate
// then it is enough to just provide the end entity certificate in chain.
// This case assumes that the SCT being provided is not embedded within
// the leaf certificate provided, i.e. the certificate is what was
// submitted to the Certificate Transparency Log in order to obtain the
// SCT. For this case, set embedded to false.
// - Precertificate:
// If using this function to verify an SCT for a precertificate then the
// issuing certificate must also be provided in chain. The precertificate
// should be at chain[0], and its issuer at chain[1]. For this case, set
// embedded to false.
// - X.509 Certificate containing the SCT embedded within it:
// If the SCT you wish to verify is embedded within the certificate you
// are providing at chain[0], set embedded to true. VerifySCT will
// verify the provided SCT by building the corresponding precertificate.
// VerifySCT will return an error if the provided SCT cannot be found
// embedded within chain[0]. As with the precertificate case, the issuing
// certificate must also be provided in chain. The certificate containing
// the embedded SCT should be at chain[0], and its issuer at chain[1].
func VerifySCT(pubKey crypto.PublicKey, chain []*x509.Certificate, sct *ct.SignedCertificateTimestamp, embedded bool) error {
s, err := ct.NewSignatureVerifier(pubKey)
if err != nil {
return fmt.Errorf("error creating signature verifier: %w", err)
}
return VerifySCTWithVerifier(s, chain, sct, embedded)
}
// VerifySCTWithVerifier takes a ct.SignatureVerifier, a certificate chain, and
// an SCT and verifies whether the SCT is a valid SCT for the certificate at
// chain[0], signed by the Log whose public key was used to set up the
// ct.SignatureVerifier. If the SCT does not verify, an error will be returned.
//
// This function can be used with three different types of leaf certificate:
// - X.509 Certificate:
// If using this function to verify an SCT for a normal X.509 certificate
// then it is enough to just provide the end entity certificate in chain.
// This case assumes that the SCT being provided is not embedded within
// the leaf certificate provided, i.e. the certificate is what was
// submitted to the Certificate Transparency Log in order to obtain the
// SCT. For this case, set embedded to false.
// - Precertificate:
// If using this function to verify an SCT for a precertificate then the
// issuing certificate must also be provided in chain. The precertificate
// should be at chain[0], and its issuer at chain[1]. For this case, set
// embedded to false.
// - X.509 Certificate containing the SCT embedded within it:
// If the SCT you wish to verify is embedded within the certificate you
// are providing at chain[0], set embedded to true. VerifySCT will
// verify the provided SCT by building the corresponding precertificate.
// VerifySCT will return an error if the provided SCT cannot be found
// embedded within chain[0]. As with the precertificate case, the issuing
// certificate must also be provided in chain. The certificate containing
// the embedded SCT should be at chain[0], and its issuer at chain[1].
func VerifySCTWithVerifier(sv *ct.SignatureVerifier, chain []*x509.Certificate, sct *ct.SignedCertificateTimestamp, embedded bool) error {
if sv == nil {
return errors.New("ct.SignatureVerifier is nil")
}
leaf, err := createLeaf(chain, sct, embedded)
if err != nil {
return err
}
return sv.VerifySCTSignature(*sct, ct.LogEntry{Leaf: *leaf})
}
func createLeaf(chain []*x509.Certificate, sct *ct.SignedCertificateTimestamp, embedded bool) (*ct.MerkleTreeLeaf, error) {
if len(chain) == 0 {
return nil, errors.New("chain is empty")
}
if sct == nil {
return nil, errors.New("sct is nil")
}
if embedded {
sctPresent, err := ContainsSCT(chain[0], sct)
if err != nil {
return nil, fmt.Errorf("error checking for SCT in leaf certificate: %w", err)
}
if !sctPresent {
return nil, errors.New("SCT provided is not embedded within leaf certificate")
}
}
certType := ct.X509LogEntryType
if chain[0].IsPrecertificate() || embedded {
certType = ct.PrecertLogEntryType
}
var leaf *ct.MerkleTreeLeaf
var err error
if embedded {
leaf, err = ct.MerkleTreeLeafForEmbeddedSCT(chain, sct.Timestamp)
} else {
leaf, err = ct.MerkleTreeLeafFromChain(chain, certType, sct.Timestamp)
}
if err != nil {
return nil, fmt.Errorf("error creating MerkleTreeLeaf: %w", err)
}
return leaf, nil
}
// ContainsSCT checks to see whether the given SCT is embedded within the given
// certificate.
func ContainsSCT(cert *x509.Certificate, sct *ct.SignedCertificateTimestamp) (bool, error) {
if cert == nil || sct == nil {
return false, nil
}
sctBytes, err := tls.Marshal(*sct)
if err != nil {
return false, fmt.Errorf("error tls.Marshalling SCT: %w", err)
}
for _, s := range cert.SCTList.SCTList {
if bytes.Equal(sctBytes, s.Val) {
return true, nil
}
}
return false, nil
}
//
// Copyright 2021 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 git
import (
"context"
"github.com/sigstore/cosign/v3/pkg/cosign"
"github.com/sigstore/cosign/v3/pkg/cosign/git/github"
"github.com/sigstore/cosign/v3/pkg/cosign/git/gitlab"
)
var providerMap = map[string]Git{
github.ReferenceScheme: github.New(),
gitlab.ReferenceScheme: gitlab.New(),
}
type Git interface {
PutSecret(ctx context.Context, ref string, pf cosign.PassFunc) error
GetSecret(ctx context.Context, ref string, key string) (string, error)
}
func GetProvider(provider string) Git {
return providerMap[provider]
}
//
// Copyright 2021 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 github
import (
"context"
"encoding/base64"
"errors"
"fmt"
"io"
"net/http"
"os"
"strings"
"github.com/google/go-github/v73/github"
"golang.org/x/crypto/nacl/box"
"golang.org/x/oauth2"
"github.com/sigstore/cosign/v3/pkg/cosign"
"github.com/sigstore/cosign/v3/pkg/cosign/env"
)
const (
ReferenceScheme = "github"
)
type Gh struct{}
func New() *Gh {
return &Gh{}
}
func (g *Gh) PutSecret(ctx context.Context, ref string, pf cosign.PassFunc) error {
var httpClient *http.Client
if token, ok := env.LookupEnv(env.VariableGitHubToken); ok {
ts := oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: token},
)
httpClient = oauth2.NewClient(ctx, ts)
} else {
return fmt.Errorf("could not find %q environment variable", env.VariableGitHubToken.String())
}
var client *github.Client
if host, ok := env.LookupEnv(env.VariableGitHubHost); ok {
var err error
client, err = github.NewClient(httpClient).WithEnterpriseURLs(host, host)
if err != nil {
return fmt.Errorf("could not create github enterprise client: %w", err)
}
} else {
client = github.NewClient(httpClient)
}
keys, err := cosign.GenerateKeyPair(pf)
if err != nil {
return fmt.Errorf("generating key pair: %w", err)
}
var owner, repo string
split := strings.Split(ref, "/")
switch len(split) {
case 2:
owner, repo = split[0], split[1]
case 1:
owner = split[0]
default:
return errors.New("could not parse scheme, use github://<owner> or github://<owner>/<repo> format")
}
key, getPubKeyResp, err := getPublicKey(ctx, client, owner, repo)
if err != nil {
return fmt.Errorf("could not get repository public key: %w", err)
}
if getPubKeyResp.StatusCode < 200 && getPubKeyResp.StatusCode >= 300 {
bodyBytes, _ := io.ReadAll(getPubKeyResp.Body)
return fmt.Errorf("%s", bodyBytes)
}
encryptedCosignPasswd, err := encryptSecretWithPublicKey(key, "COSIGN_PASSWORD", keys.Password())
if err != nil {
return fmt.Errorf("could not encrypt the secret: %w", err)
}
passwordSecretEnvResp, err := createOrUpdateOrgSecret(ctx, client, owner, repo, encryptedCosignPasswd)
if err != nil {
return fmt.Errorf("could not create \"COSIGN_PASSWORD\" github actions secret: %w", err)
}
if passwordSecretEnvResp.StatusCode < 200 && passwordSecretEnvResp.StatusCode >= 300 {
bodyBytes, _ := io.ReadAll(passwordSecretEnvResp.Body)
return fmt.Errorf("%s", bodyBytes)
}
fmt.Fprintln(os.Stderr, "Password written to COSIGN_PASSWORD github actions secret")
encryptedCosignPrivKey, err := encryptSecretWithPublicKey(key, "COSIGN_PRIVATE_KEY", keys.PrivateBytes)
if err != nil {
return fmt.Errorf("could not encrypt the secret: %w", err)
}
privateKeySecretEnvResp, err := createOrUpdateOrgSecret(ctx, client, owner, repo, encryptedCosignPrivKey)
if err != nil {
return fmt.Errorf("could not create \"COSIGN_PRIVATE_KEY\" github actions secret: %w", err)
}
if privateKeySecretEnvResp.StatusCode < 200 && privateKeySecretEnvResp.StatusCode >= 300 {
bodyBytes, _ := io.ReadAll(privateKeySecretEnvResp.Body)
return fmt.Errorf("%s", bodyBytes)
}
fmt.Fprintln(os.Stderr, "Private key written to COSIGN_PRIVATE_KEY github actions secret")
encryptedCosignPubKey, err := encryptSecretWithPublicKey(key, "COSIGN_PUBLIC_KEY", keys.PublicBytes)
if err != nil {
return fmt.Errorf("could not encrypt the secret: %w", err)
}
publicKeySecretEnvResp, err := createOrUpdateOrgSecret(ctx, client, owner, repo, encryptedCosignPubKey)
if err != nil {
return fmt.Errorf("could not create \"COSIGN_PUBLIC_KEY\" github actions secret: %w", err)
}
if publicKeySecretEnvResp.StatusCode < 200 && publicKeySecretEnvResp.StatusCode >= 300 {
bodyBytes, _ := io.ReadAll(publicKeySecretEnvResp.Body)
return fmt.Errorf("%s", bodyBytes)
}
fmt.Fprintln(os.Stderr, "Public key written to COSIGN_PUBLIC_KEY github actions secret")
if err := os.WriteFile("cosign.pub", keys.PublicBytes, 0o600); err != nil {
return err
}
fmt.Fprintln(os.Stderr, "Public key also written to cosign.pub")
return nil
}
// NOTE: GetSecret is not implemented for GitHub
func (g *Gh) GetSecret(ctx context.Context, ref string, key string) (string, error) { //nolint: revive
return "", nil
}
func createOrUpdateOrgSecret(ctx context.Context, client *github.Client, owner string, repo string, encryptedCosignPasswd *github.EncryptedSecret) (*github.Response, error) {
if len(repo) > 0 {
return client.Actions.CreateOrUpdateRepoSecret(ctx, owner, repo, encryptedCosignPasswd)
}
return client.Actions.CreateOrUpdateOrgSecret(ctx, owner, encryptedCosignPasswd)
}
func getPublicKey(ctx context.Context, client *github.Client, owner string, repo string) (*github.PublicKey, *github.Response, error) {
if len(repo) > 0 {
return client.Actions.GetRepoPublicKey(ctx, owner, repo)
}
return client.Actions.GetOrgPublicKey(ctx, owner)
}
func encryptSecretWithPublicKey(publicKey *github.PublicKey, secretName string, secretValue []byte) (*github.EncryptedSecret, error) {
decodedPubKey, err := base64.StdEncoding.DecodeString(publicKey.GetKey())
if err != nil {
return nil, fmt.Errorf("failed to decode public key: %w", err)
}
var peersPubKey [32]byte
copy(peersPubKey[:], decodedPubKey[0:32])
var rand io.Reader
eBody, err := box.SealAnonymous(nil, secretValue, &peersPubKey, rand)
if err != nil {
return nil, fmt.Errorf("failed to encrypt body: %w", err)
}
encryptedString := base64.StdEncoding.EncodeToString(eBody)
keyID := publicKey.GetKeyID()
encryptedSecret := &github.EncryptedSecret{
Name: secretName,
KeyID: keyID,
EncryptedValue: encryptedString,
}
return encryptedSecret, nil
}
//
// Copyright 2021 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 gitlab
import (
"context"
"fmt"
"io"
"os"
"github.com/sigstore/cosign/v3/internal/ui"
"github.com/sigstore/cosign/v3/pkg/cosign"
"github.com/sigstore/cosign/v3/pkg/cosign/env"
gitlab "gitlab.com/gitlab-org/api/client-go"
)
const (
ReferenceScheme = "gitlab"
)
type Gl struct{}
func New() *Gl {
return &Gl{}
}
func (g *Gl) PutSecret(ctx context.Context, ref string, pf cosign.PassFunc) error {
keys, err := cosign.GenerateKeyPair(pf)
if err != nil {
return fmt.Errorf("generating key pair: %w", err)
}
token, tokenExists := env.LookupEnv(env.VariableGitLabToken)
if !tokenExists {
return fmt.Errorf("could not find %q", env.VariableGitLabToken.String())
}
var client *gitlab.Client
if url, baseURLExists := env.LookupEnv(env.VariableGitLabHost); baseURLExists {
client, err = gitlab.NewClient(token, gitlab.WithBaseURL(url))
if err != nil {
return fmt.Errorf("could not create GitLab client: %w", err)
}
} else {
client, err = gitlab.NewClient(token)
if err != nil {
return fmt.Errorf("could not create GitLab client: %w", err)
}
}
_, passwordResp, err := client.ProjectVariables.CreateVariable(ref, &gitlab.CreateProjectVariableOptions{
Key: gitlab.Ptr("COSIGN_PASSWORD"),
Value: gitlab.Ptr(string(keys.Password())),
VariableType: gitlab.Ptr(gitlab.EnvVariableType),
Protected: gitlab.Ptr(false),
Masked: gitlab.Ptr(false),
EnvironmentScope: gitlab.Ptr("*"),
})
if err != nil {
ui.Warnf(ctx, "If you are using a self-hosted gitlab please set the \"GITLAB_HOST\" your server name.")
return fmt.Errorf("could not create \"COSIGN_PASSWORD\" variable: %w", err)
}
if passwordResp.StatusCode < 200 && passwordResp.StatusCode >= 300 {
bodyBytes, _ := io.ReadAll(passwordResp.Body)
return fmt.Errorf("%s", bodyBytes)
}
ui.Infof(ctx, "Password written to \"COSIGN_PASSWORD\" variable")
_, privateKeyResp, err := client.ProjectVariables.CreateVariable(ref, &gitlab.CreateProjectVariableOptions{
Key: gitlab.Ptr("COSIGN_PRIVATE_KEY"),
Value: gitlab.Ptr(string(keys.PrivateBytes)),
VariableType: gitlab.Ptr(gitlab.EnvVariableType),
Protected: gitlab.Ptr(false),
Masked: gitlab.Ptr(false),
})
if err != nil {
return fmt.Errorf("could not create \"COSIGN_PRIVATE_KEY\" variable: %w", err)
}
if privateKeyResp.StatusCode < 200 && privateKeyResp.StatusCode >= 300 {
bodyBytes, _ := io.ReadAll(privateKeyResp.Body)
return fmt.Errorf("%s", bodyBytes)
}
ui.Infof(ctx, "Private key written to \"COSIGN_PRIVATE_KEY\" variable")
_, publicKeyResp, err := client.ProjectVariables.CreateVariable(ref, &gitlab.CreateProjectVariableOptions{
Key: gitlab.Ptr("COSIGN_PUBLIC_KEY"),
Value: gitlab.Ptr(string(keys.PublicBytes)),
VariableType: gitlab.Ptr(gitlab.EnvVariableType),
Protected: gitlab.Ptr(false),
Masked: gitlab.Ptr(false),
})
if err != nil {
return fmt.Errorf("could not create \"COSIGN_PUBLIC_KEY\" variable: %w", err)
}
if publicKeyResp.StatusCode < 200 && publicKeyResp.StatusCode >= 300 {
bodyBytes, _ := io.ReadAll(publicKeyResp.Body)
return fmt.Errorf("%s", bodyBytes)
}
ui.Infof(ctx, "Public key written to \"COSIGN_PUBLIC_KEY\" variable")
if err := os.WriteFile("cosign.pub", keys.PublicBytes, 0o600); err != nil {
return err
}
ui.Infof(ctx, "Public key also written to cosign.pub")
return nil
}
func (g *Gl) GetSecret(_ context.Context, ref string, key string) (string, error) {
token, tokenExists := env.LookupEnv(env.VariableGitLabToken)
var varPubKeyValue string
if !tokenExists {
return varPubKeyValue, fmt.Errorf("could not find %q", env.VariableGitLabToken.String())
}
var client *gitlab.Client
var err error
if url, baseURLExists := env.LookupEnv(env.VariableGitLabHost); baseURLExists {
client, err = gitlab.NewClient(token, gitlab.WithBaseURL(url))
if err != nil {
return varPubKeyValue, fmt.Errorf("could not create GitLab client): %w", err)
}
} else {
client, err = gitlab.NewClient(token)
if err != nil {
return varPubKeyValue, fmt.Errorf("could not create GitLab client: %w", err)
}
}
varPubKey, pubKeyResp, err := client.ProjectVariables.GetVariable(ref, key, nil)
if err != nil {
return varPubKeyValue, fmt.Errorf("could not retrieve \"COSIGN_PUBLIC_KEY\" variable: %w", err)
}
varPubKeyValue = varPubKey.Value
if pubKeyResp.StatusCode < 200 && pubKeyResp.StatusCode >= 300 {
bodyBytes, _ := io.ReadAll(pubKeyResp.Body)
return varPubKeyValue, fmt.Errorf("%s", bodyBytes)
}
return varPubKeyValue, nil
}
//
// Copyright 2021 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 cosign
import (
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/rand"
"crypto/rsa"
_ "crypto/sha256" // for `crypto.SHA256`
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"os"
"path/filepath"
"sort"
"github.com/secure-systems-lab/go-securesystemslib/encrypted"
"github.com/sigstore/cosign/v3/pkg/oci/static"
v1 "github.com/sigstore/protobuf-specs/gen/pb-go/common/v1"
"github.com/sigstore/sigstore/pkg/cryptoutils"
"github.com/sigstore/sigstore/pkg/signature"
"github.com/sigstore/sigstore/pkg/signature/options"
)
const (
CosignPrivateKeyPemType = "ENCRYPTED COSIGN PRIVATE KEY"
SigstorePrivateKeyPemType = "ENCRYPTED SIGSTORE PRIVATE KEY"
// PEM-encoded PKCS #1 RSA private key
RSAPrivateKeyPemType = "RSA PRIVATE KEY"
// PEM-encoded ECDSA private key
ECPrivateKeyPemType = "EC PRIVATE KEY"
// PEM-encoded PKCS #8 RSA, ECDSA or ED25519 private key
PrivateKeyPemType = "PRIVATE KEY"
BundleKey = static.BundleAnnotationKey
RFC3161TimestampKey = static.RFC3161TimestampAnnotationKey
)
var SupportedKeyDetails = []v1.PublicKeyDetails{
v1.PublicKeyDetails_PKIX_ECDSA_P256_SHA_256,
v1.PublicKeyDetails_PKIX_ECDSA_P384_SHA_384,
v1.PublicKeyDetails_PKIX_ECDSA_P521_SHA_512,
v1.PublicKeyDetails_PKIX_RSA_PKCS1V15_2048_SHA256,
v1.PublicKeyDetails_PKIX_RSA_PKCS1V15_3072_SHA256,
v1.PublicKeyDetails_PKIX_RSA_PKCS1V15_4096_SHA256,
// Ed25519ph is not supported by Fulcio, so we don't support it here for now.
// v1.PublicKeyDetails_PKIX_ED25519_PH,
}
// PassFunc is the function to be called to retrieve the signer password. If
// nil, then it assumes that no password is provided.
type PassFunc func(bool) ([]byte, error)
type Keys struct {
private crypto.PrivateKey
public crypto.PublicKey
}
type KeysBytes struct {
PrivateBytes []byte
PublicBytes []byte
password []byte
}
func (k *KeysBytes) Password() []byte {
return k.password
}
// GeneratePrivateKey generates an ECDSA private key with the P-256 curve.
func GeneratePrivateKey() (*ecdsa.PrivateKey, error) {
priv, err := GeneratePrivateKeyWithAlgorithm(nil)
if err != nil {
return nil, err
}
return priv.(*ecdsa.PrivateKey), nil
}
// GeneratePrivateKeyWithAlgorithm generates a private key for the given algorithm
func GeneratePrivateKeyWithAlgorithm(algo *signature.AlgorithmDetails) (crypto.PrivateKey, error) {
var currentAlgo signature.AlgorithmDetails
if algo == nil {
var err error
currentAlgo, err = signature.GetAlgorithmDetails(v1.PublicKeyDetails_PKIX_ECDSA_P256_SHA_256)
if err != nil {
return nil, fmt.Errorf("error getting algorithm details for default algorithm: %w", err)
}
} else {
currentAlgo = *algo
}
switch currentAlgo.GetKeyType() {
case signature.ECDSA:
curve, err := currentAlgo.GetECDSACurve()
if err != nil {
return nil, fmt.Errorf("error getting ECDSA curve: %w", err)
}
return ecdsa.GenerateKey(*curve, rand.Reader)
case signature.RSA:
rsaKeySize, err := currentAlgo.GetRSAKeySize()
if err != nil {
return nil, fmt.Errorf("error getting RSA key size: %w", err)
}
return rsa.GenerateKey(rand.Reader, int(rsaKeySize))
case signature.ED25519:
_, priv, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
return nil, fmt.Errorf("error generating ED25519 key: %w", err)
}
return priv, nil
default:
return nil, fmt.Errorf("unsupported key type: %v", currentAlgo.GetKeyType())
}
}
// ImportKeyPair imports a key pair from a file containing a PEM-encoded
// private key encoded with a password provided by the 'pf' function.
// The private key can be in one of the following formats:
// - RSA private key (PKCS #1)
// - ECDSA private key
// - PKCS #8 private key (RSA, ECDSA or ED25519).
func ImportKeyPair(keyPath string, pf PassFunc) (*KeysBytes, error) {
kb, err := os.ReadFile(filepath.Clean(keyPath))
if err != nil {
return nil, err
}
p, _ := pem.Decode(kb)
if p == nil {
return nil, fmt.Errorf("invalid pem block")
}
var pk crypto.Signer
switch p.Type {
case RSAPrivateKeyPemType:
rsaPk, err := x509.ParsePKCS1PrivateKey(p.Bytes)
if err != nil {
return nil, fmt.Errorf("error parsing rsa private key: %w", err)
}
if err = cryptoutils.ValidatePubKey(rsaPk.Public()); err != nil {
return nil, fmt.Errorf("error validating rsa key: %w", err)
}
pk = rsaPk
case ECPrivateKeyPemType:
ecdsaPk, err := x509.ParseECPrivateKey(p.Bytes)
if err != nil {
return nil, fmt.Errorf("error parsing ecdsa private key")
}
if err = cryptoutils.ValidatePubKey(ecdsaPk.Public()); err != nil {
return nil, fmt.Errorf("error validating ecdsa key: %w", err)
}
pk = ecdsaPk
case PrivateKeyPemType:
pkcs8Pk, err := x509.ParsePKCS8PrivateKey(p.Bytes)
if err != nil {
return nil, fmt.Errorf("error parsing pkcs #8 private key")
}
switch k := pkcs8Pk.(type) {
case *rsa.PrivateKey:
if err = cryptoutils.ValidatePubKey(k.Public()); err != nil {
return nil, fmt.Errorf("error validating rsa key: %w", err)
}
pk = k
case *ecdsa.PrivateKey:
if err = cryptoutils.ValidatePubKey(k.Public()); err != nil {
return nil, fmt.Errorf("error validating ecdsa key: %w", err)
}
pk = k
case ed25519.PrivateKey:
if err = cryptoutils.ValidatePubKey(k.Public()); err != nil {
return nil, fmt.Errorf("error validating ed25519 key: %w", err)
}
pk = k
default:
return nil, fmt.Errorf("unexpected private key")
}
default:
return nil, fmt.Errorf("unsupported private key")
}
return marshalKeyPair(p.Type, Keys{pk, pk.Public()}, pf)
}
func marshalKeyPair(ptype string, keypair Keys, pf PassFunc) (key *KeysBytes, err error) {
x509Encoded, err := x509.MarshalPKCS8PrivateKey(keypair.private)
if err != nil {
return nil, fmt.Errorf("x509 encoding private key: %w", err)
}
password := []byte{}
if pf != nil {
password, err = pf(true)
if err != nil {
return nil, err
}
}
encBytes, err := encrypted.Encrypt(x509Encoded, password)
if err != nil {
return nil, err
}
// default to SIGSTORE, but keep support of COSIGN
if ptype != CosignPrivateKeyPemType {
ptype = SigstorePrivateKeyPemType
}
// store in PEM format
privBytes := pem.EncodeToMemory(&pem.Block{
Bytes: encBytes,
Type: ptype,
})
// Now do the public key
pubBytes, err := cryptoutils.MarshalPublicKeyToPEM(keypair.public)
if err != nil {
return nil, err
}
return &KeysBytes{
PrivateBytes: privBytes,
PublicBytes: pubBytes,
password: password,
}, nil
}
func GenerateKeyPair(pf PassFunc) (*KeysBytes, error) {
priv, err := GeneratePrivateKey()
if err != nil {
return nil, err
}
// Emit SIGSTORE keys by default
return marshalKeyPair(SigstorePrivateKeyPemType, Keys{priv, priv.Public()}, pf)
}
func GenerateKeyPairWithAlgorithm(algo *signature.AlgorithmDetails, pf PassFunc) (*KeysBytes, error) {
priv, err := GeneratePrivateKeyWithAlgorithm(algo)
if err != nil {
return nil, err
}
signer, ok := priv.(crypto.Signer)
if !ok {
return nil, fmt.Errorf("private key is not a signer verifier")
}
// Emit SIGSTORE keys by default
return marshalKeyPair(SigstorePrivateKeyPemType, Keys{signer, signer.Public()}, pf)
}
// PemToECDSAKey marshals and returns the PEM-encoded ECDSA public key.
func PemToECDSAKey(pemBytes []byte) (*ecdsa.PublicKey, error) {
pub, err := cryptoutils.UnmarshalPEMToPublicKey(pemBytes)
if err != nil {
return nil, err
}
ecdsaPub, ok := pub.(*ecdsa.PublicKey)
if !ok {
return nil, fmt.Errorf("invalid public key: was %T, require *ecdsa.PublicKey", pub)
}
return ecdsaPub, nil
}
// LoadPrivateKey loads a cosign PEM private key encrypted with the given passphrase,
// and returns a SignerVerifier instance. The private key must be in the PKCS #8 format.
func LoadPrivateKey(key []byte, pass []byte, defaultLoadOptions *[]signature.LoadOption) (signature.SignerVerifier, error) {
// Decrypt first
p, _ := pem.Decode(key)
if p == nil {
return nil, errors.New("invalid pem block")
}
if p.Type != CosignPrivateKeyPemType && p.Type != SigstorePrivateKeyPemType {
return nil, fmt.Errorf("unsupported pem type: %s", p.Type)
}
x509Encoded, err := encrypted.Decrypt(p.Bytes, pass)
if err != nil {
return nil, fmt.Errorf("decrypt: %w", err)
}
pk, err := x509.ParsePKCS8PrivateKey(x509Encoded)
if err != nil {
return nil, fmt.Errorf("parsing private key: %w", err)
}
defaultLoadOptions = GetDefaultLoadOptions(defaultLoadOptions)
return signature.LoadDefaultSignerVerifier(pk, *defaultLoadOptions...)
}
func GetDefaultLoadOptions(defaultLoadOptions *[]signature.LoadOption) *[]signature.LoadOption {
if defaultLoadOptions == nil {
// Cosign uses ED25519ph by default for ED25519 keys, because that's the
// only available option for hashedrekord entries. This behaviour is
// configurable because we want to maintain compatibility with older
// cosign versions that used PureEd25519 for ED25519 keys (but which did
// not support TLog uploads).
return &[]signature.LoadOption{options.WithED25519ph()}
}
return defaultLoadOptions
}
// GetSupportedAlgorithms returns a list of supported algorithms sorted alphabetically.
func GetSupportedAlgorithms() []string {
algorithms := make([]string, 0, len(SupportedKeyDetails))
for _, algorithm := range SupportedKeyDetails {
signatureFlag, err := signature.FormatSignatureAlgorithmFlag(algorithm)
if err != nil {
continue
}
algorithms = append(algorithms, signatureFlag)
}
sort.Strings(algorithms)
return algorithms
}
//
// Copyright 2021 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 cosign
import (
"crypto/rand"
"errors"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/require"
)
const validrsa = `-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAx5piWVlE62NnZ0UzJ8Z6oKiKOC4dbOZ1HsNhIRtqkM+Oq4G+
25yq6P+0JU/Qvr9veOGEb3R/J9u8JBo+hv2i5X8OtgvP2V2pi6f1s6vK7L0+6uRb
4YTT/UdMshaVf97MgEqbq41Jf/cuvh+3AV0tZ1BpixZg4aXMKpY6HUP69lbsu27o
SUN1myMv7TSgZiV4CYs3l/gkEfpysBptWlcHRuw5RsB+C0RbjRtbJ/5VxmE/vd3M
lafd5t1WSpMb8yf0a84u5NFaXwZ7CweMfXeOddS0yb19ShSuW3PPRadruBM1mq15
js9GfagPxDS75Imcs+fA62lWvHxEujTGjYHxawIDAQABAoIBAH+sgLwmHa9zJfEo
klAe5NFe/QpydN/ziXbkAnzqzH9URC3wD+TpkWj4JoK3Sw635NWtasjf+3XDV9S/
9L7j/g5N91r6sziWcJykEsWaXXKQmm4lI6BdFjwsHyLKz1W7bZOiJXDWLu1rbrqu
DqEQuLoc9WXCKrYrFy0maoXNtfla/1p05kKN0bMigcnnyAQ+xBTwoyco4tkIz5se
IYxorz7qzXrkHQI+knz5BawmNe3ekoSaXUPoLoOR7TRTGsLteL5yukvWAi8S/0rE
gftC+PZCQpoQhSUYq7wXe7RowJ1f+kXb7HsSedOTfTSW1D/pUb/uW+CcRKig42ZI
I9H9TAECgYEA5XGBML6fJyWVqx64sHbUAjQsmQ0RwU6Zo7sqHIEPf6tYVYp7KtzK
KOfi8seOOL5FSy4pjCo11Dzyrh9bn45RNmtjSYTgOnVPSoCfuRNfOcpG+/wCHjYf
EjDvdrCpbg59kVUeaMeBDiyWAlM48HJAn8O7ez2U/iKQCyJmOIwFhSkCgYEA3rSz
Fi1NzqYWxWos4NBmg8iKcQ9SMkmPdgRLAs/WNnZJ8fdgJZwihevkXGytRGJEmav2
GMKRx1g6ey8fjXTQH9WM8X/kJC5fv8wLHnUCH/K3Mcp9CYwn7PFvSnBr4kQoc/el
bURhcF1+/opEC8vNX/Wk3zAG7Xs1PREXlH2SIHMCgYBV/3kgwBH/JkM25EjtO1yz
hsLAivmAruk/SUO7c1RP0fVF+qW3pxHOyztxLALOmeJ3D1JbSubqKf377Zz17O3b
q9yHDdrNjnKtxhAX2n7ytjJs+EQC9t4mf1kB761RpvTBqFnBhCWHHocLUA4jcW9v
cnmu86IIrwO2aKpPv4vCIQKBgHU9gY3qOazRSOmSlJ+hdmZn+2G7pBTvHsQNTIPl
cCrpqNHl3crO4GnKHkT9vVVjuiOAIKU2QNJFwzu4Og8Y8LvhizpTjoHxm9x3iV72
UDELcJ+YrqyJCTe2flUcy96o7Pbn50GXnwgtYD6WAW6IUszyn2ITgYIhu4wzZEt6
s6O7AoGAPTKbRA87L34LMlXyUBJma+etMARIP1zu8bXJ7hSJeMcog8zaLczN7ruT
pGAaLxggvtvuncMuTrG+cdmsR9SafSFKRS92NCxhOUonQ+NP6mLskIGzJZoQ5JvQ
qGzRVIDGbNkrVHM0IsAtHRpC0rYrtZY+9OwiraGcsqUMLwwQdCA=
-----END RSA PRIVATE KEY-----`
// RSA 2048 key encoded with PCKS#1
const validrsapkcs1 = `-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAqvVkqzBrMzl4TC5VsBgXnQiCo861QcB1TqdwgGzYkrNdF6fr
UisPWgjMJixolmpwHv+088rsbiSlD9hc9DmzgCJPto7wvGeTILa9cNHCGKm12q7K
TFnVUTH9z6D4E3F/IsI22Pg8+cSyeDn+LKxMadTohlcXJ8jqcH75KezoGngMp5OJ
vqs93lkLep+UJMspV029z7PzF9uoT8gI02Adfq5Zkfu8VmIy8gkpYgTBCpNnD01u
vo6HoAYG/mHqgPYivWBwi221GPjJWmCPB1rIJHlpAYUETA5jUY2UwP1oQx54ybcj
eNjMRP5J0cGyOI8MT0j9ul7/DJde3Ds5A7BA9QIDAQABAoIBAQCJ3Q5rhsZMLsI2
HP943FTei+heFOnStlNjNF/jEOOtmfsugnmgb50XrBSFjDZjZj44oVjZaQE06VQ6
7O44/PcmE4VY4Ph91sCtFvC6NE1j+ifuzBnTbHY73iah81tawqIV86yrV7REbzzE
+29fsyqEBe/ltgG0Ua/NPHfOOYALJwZVx8ozkz7xOyU23kNxSzp3T0FBnYYIuzrI
a4h7FVxGLbIJQ3xWBU5xkd4m7EqgFYkWCfSXAVoLT2z7eJSYAmITuiQXl8uDz1XY
lWKgOwkRJrMVVD8hDME7Hoc/RlKmYX64IZ3lv70NuyKDPTuhmoIQRJ49mVaqdPtH
v0Z9L0tBAoGBAMPFcJFaR+VdmsZ2DXQlsPQNAB064SYbIXx/pxNVoDYkHyMfZsf3
vjf4gMKNsHTM4u812UpsE5762OqdVKmWXQc60mkuEk7N55iXuBJiJxSpuj6IbiLw
ogV+B40UC9luOISQpDYdY1Km1ho4HRngNkXMlJ48tFuwIP3lwwz3FtFZAoGBAN+N
wVssBvNhHzGfcUMxxCwJKfHCx1ANWuTe+AsDtpZRTExMcX1PH1euxUV9aII9Klg7
A7FN1It78pDrQBNQJoeMON+5N53//geY6stDfhPkOoT8Zqg2VEz4WRihUgAUHESk
pUVYSvEXG7J7AG5iGgn0B3P9PMvvReIHnTeQ1rz9AoGAWAR31NHrSyMniBzhdZvQ
kBkcOQgU3AYMqyXVXyr7KfxZh3gBxNwMyKtQcKg1cn3/dZ8XP4+RzsNnLSxpOQni
b3Kx0RomnwmSG5fy6Uj52x9oHd9G7SyVG7UK/hHKNgqJHIjPW4kg87MQxZ7+7nhQ
zlbpZq9SQ3rPind3l2er+ZkCgYASFs9ZiEN7uBUlF8i7bjB4e7lYJbGpCZucP2qE
waUpnqR03A6m3BsmJi8yQ0aMm1Rs1UGkPC8BpmLnVRHXPjoP58nGWJ9meotcpAQD
tI9kHqiZkC7iV5sUq1fSRWN0PCxZZZU1+kH+JieIlqlfRTLkMUnVGd2shsz50DHp
iB/IJQKBgAO3kRRVszif2jdo7gzDsiSQ+fSyD6yEE9eP+uNwLZw9bQhyW5wlXF+t
dR5olNrc0bP542MHL5vigRnezq9hT1hbLkQg/MA2k5FrMHIZshfWITnI5B5I2sw6
wu/XEVtNr8RincoHXjov4DiqgbLPWubM7FHLN5CW6nRLXhGkb4+7
-----END RSA PRIVATE KEY-----`
// RSA 2048 key encoded with PKCS#8
const validrsapkcs8 = `-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCwDtRl4McMhk4Q
UD723TzrAlYt59w73Qy6SZJLdaRKLjl0T8NY4NqnscIl/ZFTPh7SwqIF7D33riYB
sp60j6cV7ADh3fiIFtl6hYMAEpgqboJgTSlyeVX2jeyZCg25stv9AtvcT1B5ZVqa
+CY4feeUzp4LbVb4vY0nk1NHP95AJkJdcQaod44NXkc7AVb8h/atWx53AujXQsJY
0t84Z1+WCUhA+2D1ECdGJlkinwy/cBjABKtMe+4jN4z9vRXnIqHn8gpTsoBqSWRP
Ua2aNQzho/JnbDSWDu8Jzs+75llwzGxKFEIsAVpB5v63ir5lVejgf2Bs2JSCfn0W
BSqdxaLXAgMBAAECggEAUwEK3mVVMvB3CXXr2ZOAzwOxAb+Ys5iKEaHyGSWDqX2V
lOKuJM8OB5XlBOhBhc951L/yh3xT0twGCzLdZB9+FPXJjLOMIw0yx3L+yh/6Ibcs
PJ7kdZYDE1TiQVzeD7jlwqmAYqP6OuGwD/QCgQvLDPtEw/pu0KL9U7U/xA22iOM4
MpbCZgQUBYvIikUgUBnhxtq9CXf9+NZOKGrLUV7zNn2Abqyw5047hDpBRejfiOIw
a6oO3UykJyEKv4P6DmCSIZyBUbgeo+jvJ/4FsGYvCSqIYCdtvOaHqHI5w5cwgUy5
hwLuqXNG4X/sqqmvqICVB2efK+vrGmGiUFizkWDjsQKBgQDi4mbKNsa1sDMS/F4W
fx0kNlriPGAlS5+I1RgGXOh3sQ3D2hi2hDKym3KlPVXMmDBH+FqHt83uPXXSEpTo
d6jCyprw0kBgmfZCnWrWEMY1KGHPsPDO+Vx7nsrpgPA9EpftXibgTu1wQqNR+NOE
RwweniZhqOjGkjMZagMvS701UwKBgQDGpq3c9SRPJT6TD77akfrWs2nKjXsD9xzP
1mjXVN+ft6Om5olSANrH8nRsbND4BfYMfDbm/LscjM/qZ8ueC/KRZZEBoB9qy7R9
76JbqvCctFLorTy6RNIYno0JS5tivU93SOKDv1V/eXuZb3BVyixyhIaCjjjjnE7u
AP195YIH7QKBgElHVGm1XWKrQSO9rOnZLmFWyO3PEEKbdTBtmu/bLB4Ualy6YUb5
1aIIQPQLpl2JPfbQyPSSsgljgl1SMRQQKcqYQ4jKb46Dy5ziWPJAwrPCkizRekVv
Fqa6t9DJG06uZbF9ulKyS0/5xeQg2LgddlWhQMZEFsKjz6tCqTqqXLcPAoGBAIBY
FCCb6XeREpqlI6PHiQ7KH+GUAxSOxXiqiFYHKevhE8SzUak/kBp61SlwLJryDwQG
BNq8Eo/hkjtaED3ubivuOP+Z2nJ/Zf+voXAkQwybnK1jr8aQzETHu0t0I9JpiTwC
RQblyXFwpaB+VU+4LXtXkCgthyfXR0+SKDT84UQJAoGAPxwl93a6voIoTmuT1WJn
RdInSOS8J/13xjhlTHwpWBbUVVZDZyM+vGit0e3cTBSoDnF4/axKVF9veN3GF0RY
doAvTv0rhEQ8VmRQY9cZsaPMVg0q9UcSLLel9lJRRcEXa7v2aUpTNXVtar9csphX
5ybCksQRTS2adHuIdOa111g=
-----END PRIVATE KEY-----`
const invalidrsawithpubkey = `-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx5piWVlE62NnZ0UzJ8Z6
oKiKOC4dbOZ1HsNhIRtqkM+Oq4G+25yq6P+0JU/Qvr9veOGEb3R/J9u8JBo+hv2i
5X8OtgvP2V2pi6f1s6vK7L0+6uRb4YTT/UdMshaVf97MgEqbq41Jf/cuvh+3AV0t
Z1BpixZg4aXMKpY6HUP69lbsu27oSUN1myMv7TSgZiV4CYs3l/gkEfpysBptWlcH
Ruw5RsB+C0RbjRtbJ/5VxmE/vd3Mlafd5t1WSpMb8yf0a84u5NFaXwZ7CweMfXeO
ddS0yb19ShSuW3PPRadruBM1mq15js9GfagPxDS75Imcs+fA62lWvHxEujTGjYHx
awIDAQAB
-----END PUBLIC KEY-----`
// RSA key with size 1024
const invalidrsasmallkey = `-----BEGIN RSA PRIVATE KEY-----
MIICWwIBAAKBgQCYm7UwrM1fEsB+pOagKMRy1G9sX3A9MbC+a/G/jI+AERiNhCxa
dQUnicqs1ct78mTSH15+MJa39tYhwwh2yePUMAzP4yeiFluCAjxl8MQOqg2Q0i2+
juF72HekB1RoXj5To7j9iYL71F3+A7C+ewvMRDQZ6wsJladiM3xbx3swhwIDAQAB
AoGAKstvUgkDRmfxxxHjAoKsJC9iV5ej1+U5VQzcLAT0sMsagYTRE0TBf0bqqPED
MOzWTP4y91wUx93WSn1wwC75TjaRQ8YS9u8lFN/7ANo7zEucCSMlomEJgdvURmpS
JwsEyx03W+VCPTIz1WNFG04ICnLQvimOihU6nanDE8t4UoECQQDKFpLsipARIgVs
Zr+V7CgN33O7sduaZvakq9ymgHqd5L1B+USglCVXBCYUPTombT9Li8ecDZk69e6I
aWm1Pb9hAkEAwVHyTzI3Lu4LCx/T6mTOaJR1ads6XlFqTKUNBLd+PeL89cJhnigO
Ad0faD4hW61IF0lHjPemLo3c6nGeNPOA5wJAIHLNdpOtHEMlMcmxu4XmzItzjtC5
HSqpMbmyvT1l8tJWnTBEF7CR6k3tO1S1cJQcFKpGC8WXNANnIJokcgiPIQJAJBNs
yoaucZ2OhgbsfwNM2YtK1fRJUiyTT7ZFVaoAbwAbAKnDmcYTxxlCsStXAkq191J/
fbkBVBK5NS76vRrr5QJAGdcfJrnB9UIJQrTrXZWYSGCqkKSHhSrI4bLxrG2I+KlR
yAA/xDHlrlrCK260XFCi47rpAowtLwB1JbUwGr6x0Q==
-----END RSA PRIVATE KEY-----`
// RSA key with size 5120
const invalidrsalargekey = `-----BEGIN RSA PRIVATE KEY-----
MIILaQIBAAKCAoEA1Lu6U/a2p7AmuXPxaAfkrLaRkiWfnfddkl0kWfbO8a+sAwpi
E4p6e33xcH65PLJh22a2KoZotobFq41uK0I0Jr0tCU/ZrtJIynLpq/jS/uLH2foY
fsymXzO6f45Sfyo0CRVGEs+TQbU/XM7wEJu9JkJsocBYnWqDmjQAMkPLGjK5xAqC
k3jEhwzlkgMSAAVU5aO3lol5N3j2A72JgX3uPkLXvkQoPdBSOeTPfpevjlFbnWEl
SSyIAlR17yrlQfi/MccK5/4DaMWrzWqJLtK9wtRl7sx1Vn20xNbgavmTbBxZzwwH
bMND6TGBeIbnZ+dN4u2sPcs36Y04qQ0yA6kitHvPEk/exS5NvY6I8KSTxbeECecM
1cXfVe/7FoyxE3G7+7VbdxWoy8yZav1If15ATictn2fp9u3DM3Z+Qjd9YhHyZoPt
dZQqlLcQTMtaBubMeKcbY8JMDNc29ljtO1ClMPNxd50MYO0sXl/QnXnIfPjecDV/
I0JHLtiwrXk1LxFrErZZGFskvGsS4APtI0Ds5r+j0d5QPuffqyKpadhf6+yashsI
HV123q7iMg1v6bBd/+tQobSBYRtTSGO37Ct428MKqG6kz5P/txmrBFr/JhjOGBBZ
WhXmTm965DLsfDejpEc+ocP8hjiKcn6qfKXjSfulMUGgEKue4iSvB62qMRNFkpH/
9Ftg9v+2bySURYiJWSu/A0RmXkRlKoTFGmmI0BOBGKpLDwnLhc2kZab6iqjDHBx1
qJAM58AAwWVQtzcY42RVMxzFVMWtdV2uVbPIOPX9ZXKOyex9b3+JD9XjCkBE1Wjv
a3PEZ0CTjSrAqtgQ3DN88P9izduuPlJSVb8+gwIDAQABAoICgEU+ZcP2xjWG7NPo
nWdTSme9dVywymfMoLSHhNGTuICKwd6rfokFxiB0OiZ32SuclKWppRnqbiMbczQH
8Rg7kGYbpZEmYKC66d6b0NudPnCguJSHB3oeevj6CXaDiO7DefSK7CgrUK9Oo7U9
1n5RcxwE+v8bcLyscvG6g2XZEz8Py8+37BC8epvK4t7ICQ/grGWjCJsDXGVmBg3p
n9x6dRXnA/p2jPKx4FHf3HpEPWyBpuRvPoe26v53J3wV5lG2+eTl+PLSh6GO1gEi
8ExBZGsKX7N+8aKZgEGh/6JSYl4KTGFMdQ498NjyuEXXA3Oaoot++VWT1Ds9MHg2
R1VRtG4y7o/zV3uvOra8sm5B46ezuFLQ1iivI6cBWiVY3jqKBrpDqeX1MuBDVJyy
nOp4b04BCqScWld5xNP3edlr3nAQ9xRod6BcMB9195uFnqJDcvbjKnJH6vDACOgS
loXApGW4zgmJ6hSk0Rwn1+3/JKpe149kkH0sRk2y2LE2C3hBOSbJ+z5z6wcyLEZi
QIRIV2IW+XFBLdJeUo+nx0AMV9wxUNlgs9q6ILFS78bxMWCi3Uy6ozTBaz7LPAGU
2zf//nbIXpOlJbMrK1xvGgMa5VUldCvC3oFsDkrLcNWb/cX5GDxPl6vNedPjiTw7
xcT7boZPNRpj1i3txR6eAoH5kU9NQOMtvU2zJG2UbADX6r/SHmfSH4ghYDuG5UKL
B1lY6H6GDUCFHWhhjY7e2yKfvMWGHfnvv8/cr3FwqXd0ksDELLzGEw08f329m9EV
ryA6rLGjPhyomqXtctD/zpmQSraBpE95e0bVfNZQ2PX2P/r+NzyShUhnQmFqnFkq
uoHDNoECggFBAPsyz6nQ8A6onswNzDFNEI26Bdf4ucQxqIO1fMuTfAv5+LGHQioO
tr3h++moFHt9xD56WfxTUzAYoVrubzkhg7SNFnanqgtMgeUBtItRXMU/ql2MPA08
D6kwdPy262kizgKfkxpdLlXacBo9g/B4WQgixEJqZ2rBg1NsP4y1sqKbV0iVEN61
yuMoJicgYpg44QxXAQqXTu+oPAPCAa5e9OgLiEL2uYlo0IT2axH6iMvYs6+uZSRB
t+J3eTpgR6wnNvlis3vzO5wszmRdNlh/i47tfIQGo8oNbzM0QMGOeZ0P5D75vc1h
cqctwbTC5y2LgQbGcAnmsJ5J8ZtzSF0gnKYn/8f9WfzzWbZgOTajxqym2hDnNuT6
DzQhgD+exmA6cdt+DDRB0vH1xHzQ8Kvm+AvjM15K1b8Uo8S7o2Q2hjkRAoIBQQDY
zLH1rSPKj7ZBcaxT+mA9LjYaykp43wjzMStB3Iw7b8CrRPwhdl9C1umW6l27e0ya
aciA0sHIGSFquQ3Exh3lqSg8laoGcEfMp9gn5dmlcHmzz+YmKq9ydY/gvHutkxoe
BDUb1ydcBNNimSz+ATQHq/+Mwlu6sfL6wwZ+S7LkF1goFPQmrdEAbvOhPerDkmki
2YE9VQ9CaWiPpobW4l/kYeFzDxC3706dRd56+jPmH13/3FPcOanSoy4CpZ8TlNix
8rrX9+b0o+BFxB5iqLIprimMwcPK3ioBzDYqkYzqp0nayZgUGf5fhsvsi7in08Ya
hBJbup5M5I+3iGmJrXcyKBX5CHORraMmYZfdPxTX1bK0SGOcMl2Km32WbAiYYSe2
Faww6QHKw+K6WAGO6vieQsKOv73jLOTMOzfKZt7eUwKCAUEA88/7k33CotezWadC
u89q88TMizVVSUJRp5TtzcIWsqEra1Q3Og8R+/dtxPpo9vu5EFM9KBXQNmyRoGqw
9ai75vDSDtTpzRGzOg2PqXGNM755o1bLqqTTJopr4iXBFEi93/n2k65BnP7ps+5l
M2/8KlNkXnpcalftGXmFrRNmkUFpVH+q4h9dD2IWtf9O8ySx+oIv9pGqAh8uMQ+L
Bi4QU3FuDmDe8KoVShjLD6Y2RHTO4wPIE4rd6ifAOJLevg9J4oCUaQhKoWkz4mI+
r2MMl+uV4ad4LlMfzXk4KSYakAGurhlEyiV9XRqiWsqaC7DNyT+t205XuytWIGWi
pRFUOkm0j+4t+8BPIR8AKTKJUWaZXbKtq02ymAy0KAv5y8iuXjZXrhj9n+/FiMhb
8N27f/5EC49jK5Xi5r6g9lGdsaECggFAOEotipRBzr4xnBxfmg5QHpJ5CcusOmXu
dPY3PQp+fpAtfkqTDD0nzrruO3jujVceNJlyrcALAGFGA+e4Y3btHEwnXlOdqb5N
Zh3OSc2sDQB/GOjJ4O8ETrund7p4gkDHbzO4dloOph26pMcQn4LAd5145JsyJe8+
H02zyebts7s78GxAWCqZMXudVig1ZEIHejzvCXWkWKH1vBaIvBJaw3mGh9FJjfhc
eQlDErsT7pQGXABg5bUzGrWzpIxMGVF0Uf+r85cyKCLEgFjDaupSF/BYaWuF4o58
aasUBUl1RRfaXSwqiE2XdkYRfIFqmGir7waLnbV+lIhjqEuK22xmnmc6DUbcet6S
lcyRGajfSIr7s0N4WX3aO7rTiNLUCHxxSx2lb62QAY2KuMdQ4EKx+qVqzpWKQAnP
/hcCDVNYWnECggFBALjWC1wKR8l2LusiaY+GPHM+8CIUb2++QBUYLDbnP4Tr97nu
s+H6/d6763J/hmoD52JrCiCgs2w7NfXh5rPb3sxWoDHb3VWgjbH2q3WMXr7VECcQ
vItnEiZSUbqns6buM+vSojhZOtP0mbC9sAJneF6bq93VnZNFDCiIjsHSgJAKYXLq
vxm201n4DKlyUJMd4MBV8jkw28bJq38SWpx0gx7QgRIngMKQBkOyVYyZxmtbkq4Q
KF5RR2WCFUZQ/4nfZRctlRkWTz2smRoO4aXTys5daAGx7EMVOuLvvdefmS+/P8VC
jBytpQdBlp8J7TaieCtFIf2fzziAVOWFtV1UpZ81j44Xs8Fo7SuzcCHZ1uenm+oc
mScruPO4CZ5cr0xP7Va3EDrZpoAtoPxmNVSztHGSK6z1E8j8xttn8mHZ+r5T
-----END RSA PRIVATE KEY-----`
const invalidkey = `-----BEGIN PGP PRIVATE KEY BLOCK-----
Version: BCPG C# v1.6.1.0
lQOsBGGTOVkBCACbhVqCN55SElw1rZxI9LQDf91sU5FmrSybGh5r1xGV8rOhpKe+
eGirYVY3KeI6XUdZoJEIRtXtd6IJWn3msFRgO/MwkUQ4CibORSXPjCwHnseJmh5D
axgZbXpzjP90fW03R+sBqm2AvrUANaWIKIXk8bWWdK5yUhB7TubIxpOZKg/nLlIE
1j6+XdCWIfo56z0mpJWRASzZRGuncfvkHRz73YfA00FpflQykiUDi6+vDV7KTh49
7nkivRwyx5JcsAT3W1MCXNjCEXsdmdtNah3mN7SMbzSh3RF+IMaonxT4KM5nmEj/
wGKJ4xUPtKy7kgIPYP+LMOj7j1qCsndYWILzABEBAAH/AwMC5uUvFLMg8b9gVFGU
B1Ak38tCEBPtON9gSIxg9HX80WyMI8/MdfaisEsnFvy4X3UolhTlFJ9v3aqK1Zc8
JSkEw7cgY0NmFWDr6k8y8LhLN1ATjnKr9J9jzr8G9XvQfgaFbtcuFOF35ylQdeoL
IKKa8GqrXL75rolg+p/OSw52n/7fb17fDXLNyeGQ0g8wjIVTv+7vuvr9Z0kxfIgG
Y9oGIV/SeJvXjoWZWG3GbpTXx+ktmtwCY+tAlxJUt23OwWRfsnC9rS2DAsnJLlG2
r3Exfl80MUza1sQ/7u1svcHbFuZZOrJ1S9OjRQAWMsfQHFcav34Yrbb3aFweXLjs
iT9BJOMR4W/nyXvKAnMt/6vHKfO6kbxCtDFstH5qZAKbSceWX1Y6UaGimHXCnTYi
tlUMRNWlf6fFLdYBrRCh+MpLs5tSLc6NAYaQXTe3dJrjTRyzkxzYxeE/Y6Mii8KR
gF3Fu5OwkJ39jKdWZf17i/LUofgQHzW4ymuDMWcrqX1kZXPjD6WN8c8NmNCGvlsT
n1V6jPGb8tORIn8+CX+mCyJcxLpbG3ke90DIPnMol7WJ+3xV7J9peJqp0fY4jkmF
I96EUhY1HTZcy4SnhiPwKb8NDpdqwFx1qwytf7eM+65Cf+rj9Nh6ShVOjIfOT9gh
zEp0W0SFTU7p5af9ULnONCJABvRB8Gneosc6iwVclgHhTJcUzILRqNjcrJQu1j1v
oK9Ls+VANww4zEOqx8g+T/P4pHmGTIYTDErzyDmBw8aFD7fDl+kPUtanqC1oTvnJ
qZvoJ3JJ9Z2edW7Ulc1+BhnB8Cfs/jEJQHCngciUjW8yLUcVKdmFKkd9cajhoeQz
bJp6/t9dRUVXo2ulZzvdN93TWV66rTxHQAI4OBZKqbQLYm9iQGJvYi5jb22JARwE
EAECAAYFAmGTOVkACgkQSL3lExF3kQq7swf+Ndhd9iogkofT9ihMuMksoSDituN+
7hZY5zCj0UzCS8o2vHU+XJCRCpRlOc7gxl+44S60YmwqKtTjT5eqXCXrqa1XTyZz
xYpRfRjwnS6coQbdREQUvIgKapHII+b5gmnNhVX8niO11KyUHc29RWRiLFvMMcYO
urG06WshDewpqrBdq0MYBSSWO7myQLR5xEW6ld6CKkU0153LHgVdlGVIzrLM7sRo
NoHsidPbBIYv+aQxSVHxdKpFEpCHi9vckLSew+8LG5sDA/X3G4l9P3c1KusXP248
hfOiWo/4tMCN8XJpe0L+99ubcnHjQR7C8htFB4DnIA8KhMBSDdF/Vgp97g==
=8+cN
-----END PGP PRIVATE KEY BLOCK-----`
// Valid NIST P-256 key
const validecp256 = `-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIIGhcmCI5F7BPMH4r3pWCpQdAsveErdU5DjvVQerErJuoAoGCCqGSM49
AwEHoUQDQgAE+9E3Qe+h25ofmz3Uo2T004Dfy49iX06MMbxf9rsGmLkOPrS0KYDl
1QMfFuSbrtf8wTWNT9HNxrW/Foz39mDhHw==
-----END EC PRIVATE KEY-----`
// Valid NIST P-384 key
const validecp384 = `-----BEGIN EC PRIVATE KEY-----
MIGkAgEBBDDWIebxSdyJVber6J/l5MKnD0+VU0b7fcjY/RbIxuIsHLVIvwJrSohY
r3gJ/iJmIKGgBwYFK4EEACKhZANiAAQeoKZkkc1GafmIgp1tbNbMBjr2EdvX2dtT
lGhzHJgE0YB6TavazcYH1iOBNmX7/pInCopiyWbjF/5olrRKJMG6DQz80td++fYf
O4tr+Z3nyUgRGmf/fqYA4PSN30CuOMU=
-----END EC PRIVATE KEY-----`
// Valid NIST P-521 key
const validecp521 = `-----BEGIN EC PRIVATE KEY-----
MIHcAgEBBEIA3JhHw1bhJyNnK80SPnvuIY9h++IaEhZcHR7SDHzMnFf72O5fF/RI
c+Dbd9lTfrP3Oy9BOC1opvXHvr5tqzV0MregBwYFK4EEACOhgYkDgYYABAALA20e
WqTf2chokKaCmxfnhs1lW70/J8slBzKBG6x3OjJX+yrpkDqe8zpkN08E+RZA8RSE
9TqbcXVHGtR6tj0IMgEFmybW+efJr4nz/iBvchGeIg1HtOX5V97z0bmwsCgQCD8L
4cNcYkeNJazrzzXte5Y5DK4Vm8QE4Jkux6FCw19QhQ==
-----END EC PRIVATE KEY-----`
// Valid NIST P-256 key encoded with PKCS#8
const validecpkcs8 = `-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgmKnLL81dCEYc6Spq
rFukFeTnU5JjtXfeNHAXJj3hYTahRANCAATjp4/Z4tUbZwUdId07GQeUdwLll4Br
YLP25Fk+mBY2G0lyKqjqLG5hxhimbEJH6j+lzg5tcYpQgzXtOJ66Zi0N
-----END PRIVATE KEY-----`
// We consider NIST P-224 below our standard.
const invalidecp224 = `-----BEGIN EC PRIVATE KEY-----
MGgCAQEEHNpr8qVw+OcDrk/2aSqCGkWFKIjJSH0smeuloomgBwYFK4EEACGhPAM6
AARL+L9osuD8BYNr/aCkJiDHfDhxosXNOkcml4XPsnN88EjUqI2J7lMqmea5guwr
eu5jUfVoZzti6A==
-----END EC PRIVATE KEY-----`
// Go crypto library does not support non-NIST curves.
const invalidecunsupported = `-----BEGIN EC PRIVATE KEY-----
MHQCAQEEIJ6iubjORxWrzL3Z0i30s80TuLD+N6YTYVk49nzl+O1eoAcGBSuBBAAK
oUQDQgAEBadoFlV8pUuQ+WvRapCRJRGYk34h2nYkXW0BPdaCiPiEHawiVq9XXwG9
BuLB48bb77i0pTkVEefOuiNrbdBibw==
-----END EC PRIVATE KEY-----`
// PKCS#8 ED25519 key
const ed25519key = `-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEIALEbo1EFnWFqBK/wC+hhypG/8hXEerwdNetAoFoFVdv
-----END PRIVATE KEY-----`
// COSIGN labeled RSA key
const pemcosignkey = `-----BEGIN ENCRYPTED COSIGN PRIVATE KEY-----
eyJrZGYiOnsibmFtZSI6InNjcnlwdCIsInBhcmFtcyI6eyJOIjozMjc2OCwiciI6
OCwicCI6MX0sInNhbHQiOiJ4WWdoc09JTUxUWGNOT0RsclNIOUNKc1FlOVFnZmN1
cmUrMXlLdHh1TlkwPSJ9LCJjaXBoZXIiOnsibmFtZSI6Im5hY2wvc2VjcmV0Ym94
Iiwibm9uY2UiOiI0cS9PSlVmaXJkSUkrUjZ0ajZBMmcyQ0JqL25xdFNicCJ9LCJj
aXBoZXJ0ZXh0IjoiKzB4Q3NzcFN0WStBczdKanJpOWtsbHBWd2JhcUI4ZWJNdWto
eS9aVE1MSXRsL3B1YS9jWVJvbytLRGxMWWdmOW1kSjk4K1FnQW9oTktoYnJPMTcw
MHdBY1JTMjFDOE4zQUNJRUVZaWpOMllBNnMraGJSbkhjUnd4eGhDMDFtb2FvL0dO
Y1pmbEJheXZMV3pXblo4d2NDZ2ZpT1o1VXlRTEFJMHh0dnR6dEh3cTdDV1Vhd3V4
RlhlNDZzck9TUE9SNHN6bytabWErUGovSFE9PSJ9
-----END ENCRYPTED COSIGN PRIVATE KEY-----`
// COSIGN labeled EC key
const pemcosigneckey = `-----BEGIN ENCRYPTED COSIGN PRIVATE KEY-----
eyJrZGYiOnsibmFtZSI6InNjcnlwdCIsInBhcmFtcyI6eyJOIjo2NTUzNiwiciI6
OCwicCI6MX0sInNhbHQiOiJHK3F5WTYrNzhNS0JzMXNGTGs1ajYwcS9kS3Z1czBW
VkhlSHZybC9POTF3PSJ9LCJjaXBoZXIiOnsibmFtZSI6Im5hY2wvc2VjcmV0Ym94
Iiwibm9uY2UiOiJRc2JGdG13WDRDK2ttV3ZCcVRaMEFGOUFYdk1jRmg1SCJ9LCJj
aXBoZXJ0ZXh0IjoiREM5T28zeldiYVQzSXYwdFVnWEdycjUxYW1samwwNlQ5MTNP
VkxPbWpuMWhnK2o2WXRUbWg3SGhZSlY1N2J5eGE0Q281bE9YYmRqbTJ3aklubEd1
Um5aZCt5OExnekpSNzFSeEhKVzgrWmRlcFJmYWJMTjdHbDgrSFZEcERVQ3NxQnRh
VngyblpGbFEwWUl1anZwbFphblNGaUVvdERLVGkxZ3VhUXIwUHNzYU01NXZxbTRY
WS9rPSJ9
-----END ENCRYPTED COSIGN PRIVATE KEY-----`
// SIGSTORE labeled key
const pemsigstorekey = `-----BEGIN ENCRYPTED SIGSTORE PRIVATE KEY-----
eyJrZGYiOnsibmFtZSI6InNjcnlwdCIsInBhcmFtcyI6eyJOIjozMjc2OCwiciI6
OCwicCI6MX0sInNhbHQiOiI3T3VGd2VsbWZZNXVId2NoaURSc210anNwZ2ZlZjFG
Mk5lOGFDTjVLYVpZPSJ9LCJjaXBoZXIiOnsibmFtZSI6Im5hY2wvc2VjcmV0Ym94
Iiwibm9uY2UiOiJQNHk4OGhCb3ZTa09MbXN0bFVBaGJwdDJ0K2xTNUxQSCJ9LCJj
aXBoZXJ0ZXh0IjoiMnB1QzdyZldJOWh3bnJlQ2s4aUZDRlVwQlRrSzRJNlIvbFBF
cnBDekpXUGpJWXl4eGVIL1A2VW52cFJHdVhla1NNb3JMdGhLamdoQ1JlNy82NDVH
QWtoVm1LRC92eEF0S2EvbE1abENSQ3FlekJGUFd1dzNpeFRtZ2xhb2J1ZFVSbUVs
bmNGOGlZbzBTMVl6Y1ZOMVFwY2J2c0dNcUlYRzVlbmdteGp5dCtBcXlyZTF0Q0Y0
V01tU1BlaEljNlBqd2h1Q2xHaVpJUWRvTGc9PSJ9
-----END ENCRYPTED SIGSTORE PRIVATE KEY-----`
func pass(s string) PassFunc {
return func(_ bool) ([]byte, error) {
return []byte(s), nil
}
}
func TestLoadECDSAPrivateKey(t *testing.T) {
// Generate a valid keypair
keys, err := GenerateKeyPair(pass("hello"))
if err != nil {
t.Fatal(err)
}
// Load the private key with the right password
if _, err := LoadPrivateKey(keys.PrivateBytes, []byte("hello"), nil); err != nil {
t.Errorf("unexpected error decrypting key: %s", err)
}
// Try it with the wrong one
if _, err := LoadPrivateKey(keys.PrivateBytes, []byte("wrong"), nil); err == nil {
t.Error("expected error decrypting key!")
}
// Try to decrypt garbage
buf := [100]byte{}
if _, err := rand.Read(buf[:]); err != nil {
t.Fatal(err)
}
if _, err := LoadPrivateKey(buf[:], []byte("wrong"), nil); err == nil {
t.Error("expected error decrypting key!")
}
}
func TestReadingPrivatePemTypes(t *testing.T) {
pemECErrMsg := "parsing private key: x509: failed to parse private key (use ParseECPrivateKey instead for this key format)"
testCases := []struct {
pemType string
pemData []byte
expected error
}{
{
pemType: "COSIGN PEM RSA Type",
pemData: []byte(pemcosignkey),
expected: nil,
},
{
pemType: "COSIGN PEM EC Type",
pemData: []byte(pemcosigneckey),
expected: errors.New(pemECErrMsg),
},
{
pemType: "SISTORE PEM Type",
pemData: []byte(pemsigstorekey),
expected: nil,
},
}
for _, tc := range testCases {
t.Run(tc.pemType, func(t *testing.T) {
_, err := LoadPrivateKey(tc.pemData, []byte("hello"), nil)
if tc.expected == nil {
require.NoError(t, err)
} else {
require.ErrorContains(t, err, tc.expected.Error())
}
})
}
}
func TestWritingPrivatePemTypes(t *testing.T) {
keys, err := GenerateKeyPair(pass("hello"))
if err != nil {
t.Fatal(err)
}
require.Contains(t, string(keys.PrivateBytes), SigstorePrivateKeyPemType)
}
func TestImportPrivateKey(t *testing.T) {
testCases := []struct {
fileName string
pemData string
expected error
}{
// RSA tests
{
fileName: "validrsa.key",
pemData: validrsa,
expected: nil,
},
{
fileName: "validrsapkcs1.key",
pemData: validrsapkcs1,
expected: nil,
},
{
fileName: "validrsapkcs8.key",
pemData: validrsapkcs8,
expected: nil,
},
{
fileName: "invalidrsawithpubkey.key",
pemData: invalidrsawithpubkey,
expected: errors.New("unsupported private key"),
},
{
fileName: "invalidrsasmallkey.key",
pemData: invalidrsasmallkey,
expected: errors.New("error validating rsa key: key size not supported: 1024"),
},
{
fileName: "invalidrsalargekey.key",
pemData: invalidrsalargekey,
expected: errors.New("error validating rsa key: key size not supported: 5120"),
},
// EC tests
{
fileName: "validecp256.key",
pemData: validecp256,
expected: nil,
},
{
fileName: "validecp384.key",
pemData: validecp384,
expected: nil,
},
{
fileName: "validecp521.key",
pemData: validecp521,
expected: nil,
},
{
fileName: "validecpkcs8.key",
pemData: validecpkcs8,
expected: nil,
},
{
fileName: "invalidecp224.key",
pemData: invalidecp224,
expected: errors.New("error validating ecdsa key: ECDSA curve P-224 not allowed"),
},
{
fileName: "invalidecunsupported.key",
pemData: invalidecunsupported,
expected: errors.New("error parsing ecdsa private key"),
},
// ED25519 tests
{
fileName: "ed25519.key",
pemData: ed25519key,
expected: nil,
},
// Additional tests
{
fileName: "invalidkey.key",
pemData: invalidkey,
expected: errors.New("invalid pem block"),
},
}
td := t.TempDir()
for _, tc := range testCases {
t.Run(tc.fileName, func(t *testing.T) {
f := filepath.Join(td, tc.fileName)
err := os.WriteFile(f, []byte(tc.pemData), 0600)
if err != nil {
t.Fatal(err)
}
keyBytes, err := ImportKeyPair(f, pass("hello"))
if err == nil || tc.expected == nil {
require.Equal(t, tc.expected, err)
// Loading the private key should also work.
_, err = LoadPrivateKey(keyBytes.PrivateBytes, []byte("hello"), nil)
require.Equal(t, tc.expected, err)
} else {
require.Equal(t, tc.expected.Error(), err.Error())
}
})
}
}
// Copyright 2021 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 kubernetes
import (
"fmt"
utilversion "k8s.io/apimachinery/pkg/util/version"
"k8s.io/client-go/kubernetes"
// Initialize all known client auth plugins
_ "k8s.io/client-go/plugin/pkg/client/auth"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
)
func client() (kubernetes.Interface, error) {
cfg, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
clientcmd.NewDefaultClientConfigLoadingRules(), nil).ClientConfig()
if clientcmd.IsEmptyConfig(err) {
cfg, err = rest.InClusterConfig()
if err != nil {
return nil, fmt.Errorf("error creating REST client config in-cluster: %w", err)
}
} else if err != nil {
return nil, fmt.Errorf("error creating REST client config: %w", err)
}
return kubernetes.NewForConfig(cfg)
}
func checkImmutableSecretSupported(client kubernetes.Interface) (bool, error) {
k8sVer, err := client.Discovery().ServerVersion()
if err != nil {
return false, err
}
semVer, err := utilversion.ParseSemantic(k8sVer.String())
if err != nil {
return false, err
}
// https://kubernetes.io/docs/concepts/configuration/secret/#secret-immutable
return semVer.Major() >= 1 && semVer.Minor() >= 21, nil
}
// Copyright 2021 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 kubernetes
import (
"context"
"errors"
"fmt"
"os"
"strings"
"github.com/sigstore/cosign/v3/pkg/cosign"
v1 "k8s.io/api/core/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/utils/ptr"
)
const (
KeyReference = "k8s://"
)
func GetKeyPairSecret(ctx context.Context, k8sRef string) (*v1.Secret, error) {
namespace, name, err := parseRef(k8sRef)
if err != nil {
return nil, err
}
client, err := client()
if err != nil {
return nil, fmt.Errorf("new for config: %w", err)
}
var s *v1.Secret
if s, err = client.CoreV1().Secrets(namespace).Get(ctx, name, metav1.GetOptions{}); err != nil {
return nil, fmt.Errorf("checking if secret exists: %w", err)
}
return s, nil
}
func KeyPairSecret(ctx context.Context, k8sRef string, pf cosign.PassFunc) error {
namespace, name, err := parseRef(k8sRef)
if err != nil {
return err
}
// now, generate the key in memory
keys, err := cosign.GenerateKeyPair(pf)
if err != nil {
return fmt.Errorf("generating key pair: %w", err)
}
// create the k8s client
client, err := client()
if err != nil {
return fmt.Errorf("new for config: %w", err)
}
immutable, err := checkImmutableSecretSupported(client)
if err != nil {
return fmt.Errorf("check immutable: %w", err)
}
var s *v1.Secret
if s, err = client.CoreV1().Secrets(namespace).Get(ctx, name, metav1.GetOptions{}); err != nil {
if k8serrors.IsNotFound(err) {
s, err = client.CoreV1().Secrets(namespace).Create(ctx, secret(keys, namespace, name, nil, immutable), metav1.CreateOptions{})
if err != nil {
return fmt.Errorf("creating secret %s in ns %s: %w", name, namespace, err)
}
} else {
return fmt.Errorf("checking if secret exists: %w", err)
}
} else { // Update the existing secret
s, err = client.CoreV1().Secrets(namespace).Update(ctx, secret(keys, namespace, name, s.Data, immutable), metav1.UpdateOptions{})
if err != nil {
return fmt.Errorf("updating secret %s in ns %s: %w", name, namespace, err)
}
}
fmt.Fprintf(os.Stderr, "Successfully created secret %s in namespace %s\n", s.Name, s.Namespace)
if err := os.WriteFile("cosign.pub", keys.PublicBytes, 0600); err != nil {
return err
}
fmt.Fprintln(os.Stderr, "Public key written to cosign.pub")
return nil
}
// creates a secret with the following data:
// * cosign.key
// * cosign.pub
// * cosign.password
func secret(keys *cosign.KeysBytes, namespace, name string, data map[string][]byte, immutable bool) *v1.Secret {
if data == nil {
data = map[string][]byte{}
}
data["cosign.key"] = keys.PrivateBytes
data["cosign.pub"] = keys.PublicBytes
data["cosign.password"] = keys.Password()
obj := metav1.ObjectMeta{
Name: name,
Namespace: namespace,
}
// For Kubernetes >= 1.21, set Immutable by default
if immutable {
return &v1.Secret{
ObjectMeta: obj,
Data: data,
Immutable: ptr.To[bool](true),
}
}
return &v1.Secret{
ObjectMeta: obj,
Data: data,
}
}
// the reference should be formatted as <namespace>/<secret name>
func parseRef(k8sRef string) (string, string, error) {
s := strings.Split(strings.TrimPrefix(k8sRef, KeyReference), "/")
if len(s) != 2 {
return "", "", errors.New("kubernetes specification should be in the format k8s://<namespace>/<secret>")
}
return s[0], s[1], nil
}
//
// Copyright 2021 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 cosign
import (
"context"
"github.com/google/go-containerregistry/pkg/name"
"github.com/sigstore/cosign/v3/internal/ui"
"github.com/sigstore/sigstore/pkg/signature/payload"
)
// ObsoletePayload returns the implied payload that some commands expect to match
// the signature if no payload is provided by the user.
// DO NOT ADD ANY NEW CALLERS OF THIS.
func ObsoletePayload(ctx context.Context, digestedImage name.Digest) ([]byte, error) {
blob, err := (&payload.Cosign{Image: digestedImage}).MarshalJSON()
if err != nil {
return nil, err
}
ui.Warnf(ctx, "using obsolete implied signature payload data (with digested reference %s); specify it explicitly with --payload instead",
digestedImage.Name())
return blob, nil
}
//go:build !pkcs11key
// +build !pkcs11key
// Copyright 2021 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 pkcs11key
import (
"context"
"crypto"
"crypto/x509"
"errors"
"io"
"github.com/sigstore/sigstore/pkg/signature"
)
// The empty struct is used so this file never imports piv-go which is
// dependent on cgo and will fail to build if imported.
type empty struct{} //nolint
type Key struct{}
func GetKeyWithURIConfig(config *Pkcs11UriConfig, askForPinIfNeeded bool) (*Key, error) { //nolint: revive
return nil, errors.New("unimplemented")
}
func (k *Key) Certificate() (*x509.Certificate, error) {
return nil, errors.New("unimplemented")
}
func (k *Key) PublicKey(opts ...signature.PublicKeyOption) (crypto.PublicKey, error) { //nolint: revive
return nil, errors.New("unimplemented")
}
func (k *Key) VerifySignature(signature, message io.Reader, opts ...signature.VerifyOption) error { //nolint: revive
return errors.New("unimplemented")
}
func (k *Key) Verifier() (signature.Verifier, error) {
return nil, errors.New("unimplemented")
}
func (k *Key) Sign(ctx context.Context, rawPayload []byte) ([]byte, []byte, error) { //nolint: revive
return nil, nil, errors.New("unimplemented")
}
func (k *Key) SignMessage(message io.Reader, opts ...signature.SignOption) ([]byte, error) { //nolint: revive
return nil, errors.New("unimplemented")
}
func (k *Key) SignerVerifier() (signature.SignerVerifier, error) { //nolint: revive
return nil, errors.New("unimplemented")
}
func (k *Key) Close() {
}
// Copyright 2021 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 pkcs11key
import (
"errors"
"fmt"
"net/url"
"strconv"
"strings"
"github.com/sigstore/cosign/v3/pkg/cosign/env"
)
const (
ReferenceScheme = "pkcs11:"
)
var pathAttrValueChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~:[]@!$'()*+,=&"
var queryAttrValueChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~:[]@!$'()*+,=/?|"
func percentEncode(input []byte) string {
if len(input) == 0 {
return ""
}
var stringBuilder strings.Builder
for i := 0; i < len(input); i++ {
stringBuilder.WriteByte('%')
stringBuilder.WriteString(fmt.Sprintf("%.2x", input[i]))
}
return stringBuilder.String()
}
func EncodeURIComponent(uriString string, isForPath bool, usePercentEncoding bool) (string, error) {
var stringBuilder strings.Builder
var allowedChars string
if isForPath {
allowedChars = pathAttrValueChars
} else {
allowedChars = queryAttrValueChars
}
for i := 0; i < len(uriString); i++ {
allowedChar := false
for j := 0; j < len(allowedChars); j++ {
if uriString[i] == allowedChars[j] {
allowedChar = true
break
}
}
if allowedChar {
stringBuilder.WriteByte(uriString[i])
} else {
if usePercentEncoding {
stringBuilder.WriteString(percentEncode([]byte{uriString[i]}))
} else {
return "", errors.New("string contains an invalid character")
}
}
}
return stringBuilder.String(), nil
}
type Pkcs11UriConfig struct {
uriPathAttributes url.Values
uriQueryAttributes url.Values
ModulePath string
SlotID *int
TokenLabel string
KeyLabel []byte
KeyID []byte
Pin string
}
func NewPkcs11UriConfig() *Pkcs11UriConfig {
return &Pkcs11UriConfig{
uriPathAttributes: make(url.Values),
uriQueryAttributes: make(url.Values),
}
}
func NewPkcs11UriConfigFromInput(modulePath string, slotID *int, tokenLabel string, keyLabel []byte, keyID []byte, pin string) *Pkcs11UriConfig {
return &Pkcs11UriConfig{
uriPathAttributes: make(url.Values),
uriQueryAttributes: make(url.Values),
ModulePath: modulePath,
SlotID: slotID,
TokenLabel: tokenLabel,
KeyLabel: keyLabel,
KeyID: keyID,
Pin: pin,
}
}
func (conf *Pkcs11UriConfig) Parse(uriString string) error {
var slotID *int
var pin string
uri, err := url.Parse(uriString)
if err != nil {
return fmt.Errorf("parse uri: %w", err)
}
if uri.Scheme != "pkcs11" {
return errors.New("invalid uri: not a PKCS11 uri")
}
// Semicolons are no longer valid separators, therefore,
// we need to replace all occurrences of ";" with "&"
// in uri.Opaque and uri.RawQuery before passing them to url.ParseQuery().
uri.Opaque = strings.ReplaceAll(uri.Opaque, ";", "&")
uriPathAttributes, err := url.ParseQuery(uri.Opaque)
if err != nil {
return fmt.Errorf("parse uri path: %w", err)
}
uri.RawQuery = strings.ReplaceAll(uri.RawQuery, ";", "&")
uriQueryAttributes, err := url.ParseQuery(uri.RawQuery)
if err != nil {
return fmt.Errorf("parse uri query: %w", err)
}
modulePath := uriQueryAttributes.Get("module-path")
pinValue := uriQueryAttributes.Get("pin-value")
tokenLabel := uriPathAttributes.Get("token")
slotIDStr := uriPathAttributes.Get("slot-id")
keyLabel := uriPathAttributes.Get("object")
keyID := uriPathAttributes.Get("id")
// At least one of token and slot-id must be specified.
if tokenLabel == "" && slotIDStr == "" {
return errors.New("invalid uri: one of token and slot-id must be set")
}
// slot-id, if specified, should be a number.
if slotIDStr != "" {
slot, err := strconv.Atoi(slotIDStr)
if err != nil {
return fmt.Errorf("invalid uri: slot-id '%s' is not a valid number", slotIDStr)
}
slotID = &slot
}
// If pin-value is specified, take it as it is.
if pinValue != "" {
pin = pinValue
}
// module-path should be specified and should point to the absolute path of the PKCS11 module.
// If it is not, COSIGN_PKCS11_MODULE_PATH environment variable must be set.
if modulePath == "" {
modulePath = env.Getenv(env.VariablePKCS11ModulePath)
if modulePath == "" {
return errors.New("invalid uri: module-path or COSIGN_PKCS11_MODULE_PATH must be set to the absolute path of the PKCS11 module")
}
}
// At least one of object and id must be specified.
if keyLabel == "" && keyID == "" {
return errors.New("invalid uri: one of object and id must be set")
}
conf.uriPathAttributes = uriPathAttributes
conf.uriQueryAttributes = uriQueryAttributes
conf.ModulePath = modulePath
conf.TokenLabel = tokenLabel
conf.SlotID = slotID
conf.KeyLabel = []byte(keyLabel)
conf.KeyID = []byte(keyID) // url.ParseQuery() already calls url.QueryUnescape() on the id, so we only need to cast the result into byte array
conf.Pin = pin
return nil
}
func (conf *Pkcs11UriConfig) Construct() (string, error) {
var modulePath, pinValue, tokenLabel, slotID, keyID, keyLabel string
var err error
uriString := "pkcs11:"
// module-path should be specified and should point to the absolute path of the PKCS11 module.
if conf.ModulePath == "" {
return "", errors.New("module path must be set to the absolute path of the PKCS11 module")
}
// At least one of keyLabel and keyID must be specified.
if len(conf.KeyLabel) == 0 && len(conf.KeyID) == 0 {
return "", errors.New("one of keyLabel and keyID must be set")
}
// At least one of tokenLabel and slotID must be specified.
if conf.TokenLabel == "" && conf.SlotID == nil {
return "", errors.New("one of tokenLabel and slotID must be set")
}
// Construct the URI.
if conf.TokenLabel != "" {
tokenLabel, err = EncodeURIComponent(conf.TokenLabel, true, true)
if err != nil {
return "", fmt.Errorf("encode token label: %w", err)
}
uriString += "token=" + tokenLabel
}
if conf.SlotID != nil {
slotID = fmt.Sprintf("%d", *conf.SlotID)
uriString += ";slot-id=" + slotID
}
if len(conf.KeyID) != 0 {
keyID = percentEncode(conf.KeyID)
uriString += ";id=" + keyID
}
if len(conf.KeyLabel) != 0 {
keyLabel, err = EncodeURIComponent(string(conf.KeyLabel), true, true)
if err != nil {
return "", fmt.Errorf("encode key label: %w", err)
}
uriString += ";object=" + keyLabel
}
modulePath, err = EncodeURIComponent(conf.ModulePath, false, true)
if err != nil {
return "", fmt.Errorf("encode module path: %w", err)
}
uriString += "?module-path=" + modulePath
if conf.Pin != "" {
pinValue, err = EncodeURIComponent(conf.Pin, false, true)
if err != nil {
return "", fmt.Errorf("encode pin: %w", err)
}
uriString += "&pin-value=" + pinValue
}
return uriString, nil
}
//
// Copyright 2021 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 rego
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"github.com/open-policy-agent/opa/v1/ast"
"github.com/open-policy-agent/opa/v1/rego"
)
// The query below should meet the following requirements:
// * Provides no Bindings. Do not use a query that sets a variable, e.g. x := data.signature.allow
// * Queries for a single value.
const QUERY = "data.signature.allow"
// CosignRegoPackageName defines the expected package name of a provided rego module
const CosignRegoPackageName = "sigstore"
// CosignEvaluationRule defines the expected evaluation role of a provided rego module
const CosignEvaluationRule = "isCompliant"
// CosignRuleResult defines a expected result object when wrapping the custom messages of the result of our cosign rego rule
type CosignRuleResult struct {
Warning string `json:"warning,omitempty"`
Error string `json:"error,omitempty"`
Result bool `json:"result,omitempty"`
}
func ValidateJSON(jsonBody []byte, entrypoints []string) []error {
ctx := context.Background()
r := rego.New(
rego.Query(QUERY),
rego.Load(entrypoints, nil),
rego.SetRegoVersion(ast.RegoV0),
)
query, err := r.PrepareForEval(ctx)
if err != nil {
return []error{err}
}
var input interface{}
dec := json.NewDecoder(bytes.NewBuffer(jsonBody))
dec.UseNumber()
if err := dec.Decode(&input); err != nil {
return []error{err}
}
rs, err := query.Eval(ctx, rego.EvalInput(input))
if err != nil {
return []error{err}
}
// Ensure the resultset contains a single result where the Expression contains a single value
// which is true and there are no Bindings.
if rs.Allowed() {
return nil
}
var errs []error
for _, result := range rs {
for _, expression := range result.Expressions {
errs = append(errs, fmt.Errorf("expression value, %v, is not true", expression))
}
}
// When rs.Allowed() is not true and len(rs) is 0, the result is undefined. This is a policy
// check failure.
if len(errs) == 0 {
errs = append(errs, fmt.Errorf("result is undefined for query '%s'", QUERY))
}
return errs
}
// ValidateJSONWithModuleInput takes the body of the results to evaluate and the defined module
// in a policy to validate against the input data
func ValidateJSONWithModuleInput(jsonBody []byte, moduleInput string) (warnings error, errors error) {
ctx := context.Background()
query := fmt.Sprintf("%s = data.%s.%s", CosignEvaluationRule, CosignRegoPackageName, CosignEvaluationRule)
module := fmt.Sprintf("%s.rego", CosignRegoPackageName)
r := rego.New(
rego.Query(query),
rego.Module(module, moduleInput),
rego.SetRegoVersion(ast.RegoV0),
)
evalQuery, err := r.PrepareForEval(ctx)
if err != nil {
return nil, err
}
var input interface{}
dec := json.NewDecoder(bytes.NewBuffer(jsonBody))
dec.UseNumber()
if err := dec.Decode(&input); err != nil {
return nil, err
}
rs, err := evalQuery.Eval(ctx, rego.EvalInput(input))
if err != nil {
return nil, err
}
for _, result := range rs {
switch response := result.Bindings[CosignEvaluationRule].(type) {
case []interface{}:
return evaluateRegoEvalMapResult(query, response)
case bool:
if response {
return nil, nil
}
}
}
return nil, fmt.Errorf("policy is not compliant for query '%s'", query)
}
func evaluateRegoEvalMapResult(query string, response []interface{}) (warning error, retErr error) {
retErr = fmt.Errorf("policy is not compliant for query %q", query) //nolint: revive
for _, r := range response {
rMap := r.(map[string]interface{})
mapBytes, err := json.Marshal(rMap)
if err != nil {
return nil, fmt.Errorf("policy is not compliant for query '%s' due to parsing errors: %w", query, err)
}
var resultObject CosignRuleResult
err = json.Unmarshal(mapBytes, &resultObject)
if err != nil {
return nil, fmt.Errorf("policy is not compliant for query '%s' due to parsing errors: %w", query, err)
}
// Check if it is complaint
if resultObject.Result {
if resultObject.Warning == "" {
return nil, nil
}
return fmt.Errorf("warning: %s", resultObject.Warning), nil
}
warning = errors.New(resultObject.Warning)
retErr = fmt.Errorf("policy is not compliant for query '%s' with errors: %s", query, resultObject.Error) //nolint: revive
}
return warning, retErr
}
//
// 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 cosign
import (
"context"
"github.com/sigstore/rekor/pkg/generated/client"
)
// key is used for associating the Rekor client client inside the
// context.Context.
type key struct{}
// TODO(jason): Rename this to something better than pkg/cosign.Set.
func Set(ctx context.Context, rekorClient *client.Rekor) context.Context {
return context.WithValue(ctx, key{}, rekorClient)
}
// Get extracts the Rekor client from the context.
// TODO(jason): Rename this to something better than pkg/cosign.Get.
func Get(ctx context.Context) *client.Rekor {
untyped := ctx.Value(key{})
if untyped == nil {
return nil
}
return untyped.(*client.Rekor)
}
//
// Copyright 2021 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 remote
import (
"fmt"
"net/http"
"os"
"strings"
"github.com/google/go-containerregistry/pkg/name"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/empty"
"github.com/google/go-containerregistry/pkg/v1/mutate"
"github.com/google/go-containerregistry/pkg/v1/remote"
"github.com/google/go-containerregistry/pkg/v1/types"
"github.com/sigstore/cosign/v3/pkg/oci/static"
)
type File interface {
Contents() ([]byte, error)
Platform() *v1.Platform
String() string
Path() string
}
func FilesFromFlagList(sl []string) []File {
files := make([]File, len(sl))
for i, s := range sl {
files[i] = FileFromFlag(s)
}
return files
}
func FileFromFlag(s string) File {
split := strings.Split(s, ":")
f := file{
path: split[0],
}
if len(split) > 1 {
split = strings.Split(split[1], "/")
f.platform = &v1.Platform{
OS: split[0],
}
if len(split) > 1 {
f.platform.Architecture = split[1]
}
}
return &f
}
type file struct {
path string
platform *v1.Platform
}
func (f *file) Path() string {
return f.path
}
func (f *file) Contents() ([]byte, error) {
return os.ReadFile(f.path)
}
func (f *file) Platform() *v1.Platform {
return f.platform
}
func (f *file) String() string {
r := f.path
if f.platform == nil {
return r
}
r += ":" + f.platform.OS
if f.platform.Architecture == "" {
return r
}
r += "/" + f.platform.Architecture
return r
}
type MediaTypeGetter func(b []byte) types.MediaType
func DefaultMediaTypeGetter(b []byte) types.MediaType {
return types.MediaType(strings.Split(http.DetectContentType(b), ";")[0])
}
func UploadFiles(ref name.Reference, files []File, annotations map[string]string, getMt MediaTypeGetter, remoteOpts ...remote.Option) (name.Digest, error) {
var lastHash v1.Hash
var idx v1.ImageIndex = empty.Index
for _, f := range files {
b, err := f.Contents()
if err != nil {
return name.Digest{}, err
}
mt := getMt(b)
fmt.Fprintf(os.Stderr, "Uploading file from [%s] to [%s] with media type [%s]\n", f.Path(), ref.Name(), mt)
img, err := static.NewFile(b, static.WithLayerMediaType(mt), static.WithAnnotations(annotations))
if err != nil {
return name.Digest{}, err
}
lastHash, err = img.Digest()
if err != nil {
return name.Digest{}, err
}
if err := remote.Write(ref, img, remoteOpts...); err != nil {
return name.Digest{}, err
}
l, err := img.Layers()
if err != nil {
return name.Digest{}, err
}
layerHash, err := l[0].Digest()
if err != nil {
return name.Digest{}, err
}
blobURL := ref.Context().RegistryStr() + "/v2/" + ref.Context().RepositoryStr() + "/blobs/" + layerHash.String()
fmt.Fprintf(os.Stderr, "File [%s] is available directly at [%s]\n", f.Path(), blobURL)
if f.Platform() != nil {
idx = mutate.AppendManifests(idx, mutate.IndexAddendum{
Add: img,
Descriptor: v1.Descriptor{
Platform: f.Platform(),
},
})
}
}
if len(files) > 1 {
if annotations != nil {
idx = mutate.Annotations(idx, annotations).(v1.ImageIndex)
}
err := remote.WriteIndex(ref, idx, remoteOpts...)
if err != nil {
return name.Digest{}, err
}
lastHash, err = idx.Digest()
if err != nil {
return name.Digest{}, err
}
}
return ref.Context().Digest(lastHash.String()), nil
}
//
// Copyright 2021 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 remote
import (
"bytes"
"encoding/base64"
"encoding/json"
"fmt"
"os"
"github.com/sigstore/cosign/v3/pkg/oci"
"github.com/sigstore/cosign/v3/pkg/oci/mutate"
"github.com/sigstore/cosign/v3/pkg/oci/static"
"github.com/sigstore/sigstore/pkg/signature"
)
// NewDupeDetector creates a new DupeDetector that looks for matching signatures that
// can verify the provided signature's payload.
func NewDupeDetector(v signature.Verifier) mutate.DupeDetector {
return &dd{verifier: v}
}
func NewReplaceOp(predicateURI string) mutate.ReplaceOp {
return &ro{predicateURI: predicateURI}
}
type dd struct {
verifier signature.Verifier
}
type ro struct {
predicateURI string
}
var _ mutate.DupeDetector = (*dd)(nil)
var _ mutate.ReplaceOp = (*ro)(nil)
func (dd *dd) Find(sigImage oci.Signatures, newSig oci.Signature) (oci.Signature, error) {
newDigest, err := newSig.Digest()
if err != nil {
return nil, err
}
newMediaType, err := newSig.MediaType()
if err != nil {
return nil, err
}
newAnnotations, err := newSig.Annotations()
if err != nil {
return nil, err
}
sigs, err := sigImage.Get()
if err != nil {
return nil, err
}
LayerLoop:
for _, sig := range sigs {
existingAnnotations, err := sig.Annotations()
if err != nil {
continue LayerLoop
}
// if there are any new annotations, then this isn't a duplicate
for a, value := range newAnnotations {
if a == static.SignatureAnnotationKey {
continue // Ignore the signature key, we check it with custom logic below.
}
if val, ok := existingAnnotations[a]; !ok || val != value {
continue LayerLoop
}
}
if existingDigest, err := sig.Digest(); err != nil || existingDigest != newDigest {
continue LayerLoop
}
if existingMediaType, err := sig.MediaType(); err != nil || existingMediaType != newMediaType {
continue LayerLoop
}
existingSignature, err := sig.Base64Signature()
if err != nil || existingSignature == "" {
continue LayerLoop
}
uploadedSig, err := base64.StdEncoding.DecodeString(existingSignature)
if err != nil {
continue LayerLoop
}
r, err := newSig.Uncompressed()
if err != nil {
return nil, err
}
if err := dd.verifier.VerifySignature(bytes.NewReader(uploadedSig), r); err == nil {
return sig, nil
}
}
return nil, nil
}
func (r *ro) Replace(signatures oci.Signatures, o oci.Signature) (oci.Signatures, error) {
sigs, err := signatures.Get()
if err != nil {
return nil, err
}
ros := &replaceOCISignatures{Signatures: signatures}
sigsCopy := make([]oci.Signature, 0, len(sigs))
sigsCopy = append(sigsCopy, o)
if len(sigs) == 0 {
ros.attestations = append(ros.attestations, sigsCopy...)
return ros, nil
}
for _, s := range sigs {
var signaturePayload map[string]interface{}
p, err := s.Payload()
if err != nil {
return nil, fmt.Errorf("could not get payload: %w", err)
}
err = json.Unmarshal(p, &signaturePayload)
if err != nil {
return nil, fmt.Errorf("unmarshal payload data: %w", err)
}
val, ok := signaturePayload["payload"]
if !ok {
return nil, fmt.Errorf("could not find 'payload' in payload data")
}
decodedPayload, err := base64.StdEncoding.DecodeString(val.(string))
if err != nil {
return nil, fmt.Errorf("could not decode 'payload': %w", err)
}
var payloadData map[string]interface{}
if err := json.Unmarshal(decodedPayload, &payloadData); err != nil {
return nil, fmt.Errorf("unmarshal payloadData: %w", err)
}
val, ok = payloadData["predicateType"]
if !ok {
return nil, fmt.Errorf("could not find 'predicateType' in payload data")
}
if r.predicateURI == val {
fmt.Fprintln(os.Stderr, "Replacing attestation predicate:", r.predicateURI)
continue
}
fmt.Fprintln(os.Stderr, "Not replacing attestation predicate:", val)
sigsCopy = append(sigsCopy, s)
}
ros.attestations = append(ros.attestations, sigsCopy...)
return ros, nil
}
type replaceOCISignatures struct {
oci.Signatures
attestations []oci.Signature
}
func (r *replaceOCISignatures) Get() ([]oci.Signature, error) {
return r.attestations, nil
}
// Copyright 2021 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 cosign
import (
"bytes"
"context"
"crypto"
"crypto/ecdsa"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/hex"
"errors"
"fmt"
"hash"
"os"
"strconv"
"strings"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/sigstore/cosign/v3/internal/ui"
"github.com/sigstore/cosign/v3/pkg/cosign/bundle"
"github.com/sigstore/cosign/v3/pkg/cosign/env"
"github.com/sigstore/rekor/pkg/generated/client"
"github.com/sigstore/rekor/pkg/generated/client/entries"
"github.com/sigstore/rekor/pkg/generated/models"
"github.com/sigstore/rekor/pkg/types"
"github.com/sigstore/rekor/pkg/types/dsse"
dsse_v001 "github.com/sigstore/rekor/pkg/types/dsse/v0.0.1"
hashedrekord_v001 "github.com/sigstore/rekor/pkg/types/hashedrekord/v0.0.1"
"github.com/sigstore/rekor/pkg/types/intoto"
intoto_v001 "github.com/sigstore/rekor/pkg/types/intoto/v0.0.1"
"github.com/sigstore/sigstore-go/pkg/root"
"github.com/sigstore/sigstore-go/pkg/tlog"
"github.com/sigstore/sigstore/pkg/cryptoutils"
"github.com/sigstore/sigstore/pkg/tuf"
"github.com/transparency-dev/merkle/proof"
"github.com/transparency-dev/merkle/rfc6962"
)
// This is the rekor transparency log public key target name
var rekorTargetStr = `rekor.pub`
type NamedHash interface {
hash.Hash
crypto.SignerOpts
}
type CryptoNamedHash struct {
hash.Hash
hashType crypto.Hash
}
func (h CryptoNamedHash) HashFunc() crypto.Hash {
return h.hashType
}
func NewCryptoNamedHash(hashType crypto.Hash) NamedHash {
return CryptoNamedHash{Hash: hashType.New(), hashType: hashType}
}
type SHA256NamedHash struct {
hash.Hash
}
func (h SHA256NamedHash) HashFunc() crypto.Hash {
return crypto.SHA256
}
func WrapSHA256Hash(hash hash.Hash) NamedHash {
return SHA256NamedHash{Hash: hash}
}
// TransparencyLogPubKey contains the ECDSA verification key and the current status
// of the key according to TUF metadata, whether it's active or expired.
type TransparencyLogPubKey struct {
PubKey crypto.PublicKey
Status tuf.StatusKind
}
// This is a map of TransparencyLog public keys indexed by log ID that's used
// in verification.
type TrustedTransparencyLogPubKeys struct {
// A map of keys indexed by log ID
Keys map[string]TransparencyLogPubKey
}
const treeIDHexStringLen = 16
const uuidHexStringLen = 64
const entryIDHexStringLen = treeIDHexStringLen + uuidHexStringLen
// GetTransparencyLogID generates a SHA256 hash of a DER-encoded public key.
// (see RFC 6962 S3.2)
// In CT V1 the log id is a hash of the public key.
func GetTransparencyLogID(pub crypto.PublicKey) (string, error) {
pubBytes, err := x509.MarshalPKIXPublicKey(pub)
if err != nil {
return "", err
}
digest := sha256.Sum256(pubBytes)
return hex.EncodeToString(digest[:]), nil
}
func dsseEntry(ctx context.Context, signature, pubKey []byte) (models.ProposedEntry, error) {
var pubKeyBytes [][]byte
if len(pubKey) == 0 {
return nil, errors.New("public key provided has 0 length")
}
pubKeyBytes = append(pubKeyBytes, pubKey)
return types.NewProposedEntry(ctx, dsse.KIND, dsse_v001.APIVERSION, types.ArtifactProperties{
ArtifactBytes: signature,
PublicKeyBytes: pubKeyBytes,
})
}
func intotoEntry(ctx context.Context, signature, pubKey []byte) (models.ProposedEntry, error) {
var pubKeyBytes [][]byte
if len(pubKey) == 0 {
return nil, errors.New("none of the Rekor public keys have been found")
}
pubKeyBytes = append(pubKeyBytes, pubKey)
return types.NewProposedEntry(ctx, intoto.KIND, intoto_v001.APIVERSION, types.ArtifactProperties{
ArtifactBytes: signature,
PublicKeyBytes: pubKeyBytes,
})
}
// GetRekorPubs retrieves trusted Rekor public keys from the embedded or cached
// TUF root. If expired, makes a network call to retrieve the updated targets.
// There are two Env variable that can be used to override this behaviour:
// SIGSTORE_REKOR_PUBLIC_KEY - If specified, location of the file that contains
// the Rekor Public Key on local filesystem
func GetRekorPubs(ctx context.Context) (*TrustedTransparencyLogPubKeys, error) {
publicKeys := NewTrustedTransparencyLogPubKeys()
altRekorPub := env.Getenv(env.VariableSigstoreRekorPublicKey)
if altRekorPub != "" {
raw, err := os.ReadFile(altRekorPub)
if err != nil {
return nil, fmt.Errorf("error reading alternate Rekor public key file: %w", err)
}
if err := publicKeys.AddTransparencyLogPubKey(raw, tuf.Active); err != nil {
return nil, fmt.Errorf("AddRekorPubKey: %w", err)
}
} else {
tufClient, err := tuf.NewFromEnv(ctx)
if err != nil {
return nil, err
}
targets, err := tufClient.GetTargetsByMeta(tuf.Rekor, []string{rekorTargetStr})
if err != nil {
return nil, err
}
for _, t := range targets {
if err := publicKeys.AddTransparencyLogPubKey(t.Target, t.Status); err != nil {
return nil, fmt.Errorf("AddRekorPubKey: %w", err)
}
}
}
if len(publicKeys.Keys) == 0 {
return nil, errors.New("none of the Rekor public keys have been found")
}
return &publicKeys, nil
}
// rekorPubsFromClient returns a RekorPubKey keyed by the log ID from the Rekor client.
// NOTE: This **must not** be used in the verification path, but may be used in the
// sign path to validate return responses are consistent from Rekor.
func rekorPubsFromClient(rekorClient *client.Rekor) (*TrustedTransparencyLogPubKeys, error) {
publicKeys := NewTrustedTransparencyLogPubKeys()
pubOK, err := rekorClient.Pubkey.GetPublicKey(nil)
if err != nil {
return nil, fmt.Errorf("unable to fetch rekor public key from rekor: %w", err)
}
if err := publicKeys.AddTransparencyLogPubKey([]byte(pubOK.Payload), tuf.Active); err != nil {
return nil, fmt.Errorf("constructRekorPubKey: %w", err)
}
return &publicKeys, nil
}
// TLogUpload will upload the signature, public key and payload to the transparency log.
func TLogUpload(ctx context.Context, rekorClient *client.Rekor, signature []byte, sha256CheckSum hash.Hash, pemBytes []byte) (*models.LogEntryAnon, error) {
cryptoChecksum := WrapSHA256Hash(sha256CheckSum)
return TLogUploadWithCustomHash(ctx, rekorClient, signature, cryptoChecksum, pemBytes)
}
// TLogUploadWithCustomHash will upload the signature, public key and payload to
// the transparency log. Clients can use this to specify a custom hash function.
func TLogUploadWithCustomHash(ctx context.Context, rekorClient *client.Rekor, signature []byte, checksum NamedHash, pemBytes []byte) (*models.LogEntryAnon, error) {
re := rekorEntry(checksum, signature, pemBytes)
returnVal := models.Hashedrekord{
APIVersion: swag.String(re.APIVersion()),
Spec: re.HashedRekordObj,
}
return doUpload(ctx, rekorClient, &returnVal)
}
// TLogUploadDSSEEnvelope will upload a DSSE entry for the signature and public key to the Rekor transparency log.
func TLogUploadDSSEEnvelope(ctx context.Context, rekorClient *client.Rekor, signature, pemBytes []byte) (*models.LogEntryAnon, error) {
e, err := dsseEntry(ctx, signature, pemBytes)
if err != nil {
return nil, err
}
return doUpload(ctx, rekorClient, e)
}
// TLogUploadInTotoAttestation will upload an in-toto entry for the signature and public key to the transparency log.
func TLogUploadInTotoAttestation(ctx context.Context, rekorClient *client.Rekor, signature, pemBytes []byte) (*models.LogEntryAnon, error) {
e, err := intotoEntry(ctx, signature, pemBytes)
if err != nil {
return nil, err
}
return doUpload(ctx, rekorClient, e)
}
func doUpload(ctx context.Context, rekorClient *client.Rekor, pe models.ProposedEntry) (*models.LogEntryAnon, error) {
params := entries.NewCreateLogEntryParamsWithContext(ctx)
params.SetProposedEntry(pe)
resp, err := rekorClient.Entries.CreateLogEntry(params)
if err != nil {
// If the entry already exists, we get a specific error.
// Here, we display the proof and succeed.
var existsErr *entries.CreateLogEntryConflict
if errors.As(err, &existsErr) {
ui.Infof(ctx, "Signature already exists. Fetching and verifying inclusion proof.")
uriSplit := strings.Split(existsErr.Location.String(), "/")
uuid := uriSplit[len(uriSplit)-1]
e, err := GetTlogEntry(ctx, rekorClient, uuid)
if err != nil {
return nil, err
}
rekorPubsFromAPI, err := rekorPubsFromClient(rekorClient)
if err != nil {
return nil, err
}
return e, VerifyTLogEntryOffline(ctx, e, rekorPubsFromAPI, nil)
}
return nil, err
}
// UUID is at the end of location
for _, p := range resp.Payload {
return &p, nil
}
return nil, errors.New("bad response from server")
}
func rekorEntryHashAlgorithm(checksum crypto.SignerOpts) string {
switch checksum.HashFunc() {
case crypto.SHA256:
return models.HashedrekordV001SchemaDataHashAlgorithmSha256
case crypto.SHA384:
return models.HashedrekordV001SchemaDataHashAlgorithmSha384
case crypto.SHA512:
return models.HashedrekordV001SchemaDataHashAlgorithmSha512
default:
return models.HashedrekordV001SchemaDataHashAlgorithmSha256
}
}
func rekorEntry(checksum NamedHash, signature, pubKey []byte) hashedrekord_v001.V001Entry {
return hashedrekord_v001.V001Entry{
HashedRekordObj: models.HashedrekordV001Schema{
Data: &models.HashedrekordV001SchemaData{
Hash: &models.HashedrekordV001SchemaDataHash{
Algorithm: swag.String(rekorEntryHashAlgorithm(checksum)),
Value: swag.String(hex.EncodeToString(checksum.Sum(nil))),
},
},
Signature: &models.HashedrekordV001SchemaSignature{
Content: strfmt.Base64(signature),
PublicKey: &models.HashedrekordV001SchemaSignaturePublicKey{
Content: strfmt.Base64(pubKey),
},
},
},
}
}
func ComputeLeafHash(e *models.LogEntryAnon) ([]byte, error) {
entryBytes, err := base64.StdEncoding.DecodeString(e.Body.(string))
if err != nil {
return nil, err
}
return rfc6962.DefaultHasher.HashLeaf(entryBytes), nil
}
func getUUID(entryUUID string) (string, error) {
switch len(entryUUID) {
case uuidHexStringLen:
if _, err := hex.DecodeString(entryUUID); err != nil {
return "", fmt.Errorf("uuid %v is not a valid hex string: %w", entryUUID, err)
}
return entryUUID, nil
case entryIDHexStringLen:
uid := entryUUID[len(entryUUID)-uuidHexStringLen:]
return getUUID(uid)
default:
return "", fmt.Errorf("invalid ID len %v for %v", len(entryUUID), entryUUID)
}
}
func getTreeUUID(entryUUID string) (string, error) {
switch len(entryUUID) {
case uuidHexStringLen:
// No Tree ID provided
return "", nil
case entryIDHexStringLen:
tid := entryUUID[:treeIDHexStringLen]
return getTreeUUID(tid)
case treeIDHexStringLen:
// Check that it's a valid int64 in hex (base 16)
i, err := strconv.ParseInt(entryUUID, 16, 64)
if err != nil {
return "", fmt.Errorf("could not convert treeID %v to int64: %w", entryUUID, err)
}
// Check for invalid TreeID values
if i == 0 {
return "", fmt.Errorf("0 is not a valid TreeID")
}
return entryUUID, nil
default:
return "", fmt.Errorf("invalid ID len %v for %v", len(entryUUID), entryUUID)
}
}
// Validates UUID and also shard if present.
func isExpectedResponseUUID(requestEntryUUID string, responseEntryUUID string) error {
// Comparare UUIDs
requestUUID, err := getUUID(requestEntryUUID)
if err != nil {
return err
}
responseUUID, err := getUUID(responseEntryUUID)
if err != nil {
return err
}
if requestUUID != responseUUID {
return fmt.Errorf("expected EntryUUID %s got UUID %s", requestEntryUUID, responseEntryUUID)
}
// Compare shards if it is in the request.
requestShardID, err := getTreeUUID(requestEntryUUID)
if err != nil {
return err
}
responseShardID, err := getTreeUUID(responseEntryUUID)
if err != nil {
return err
}
// no shard ID prepends the entry UUID
if requestShardID == "" || responseShardID == "" {
return nil
}
if requestShardID != responseShardID {
return fmt.Errorf("expected UUID %s from shard %s: got UUID %s from shard %s", requestEntryUUID, responseEntryUUID, requestShardID, responseShardID)
}
return nil
}
func verifyUUID(entryUUID string, e models.LogEntryAnon) error {
// Verify and get the UUID.
uid, err := getUUID(entryUUID)
if err != nil {
return err
}
uuid, _ := hex.DecodeString(uid)
// Verify leaf hash matches hash of the entry body.
computedLeafHash, err := ComputeLeafHash(&e)
if err != nil {
return err
}
if !bytes.Equal(computedLeafHash, uuid) {
return fmt.Errorf("computed leaf hash did not match UUID")
}
return nil
}
func GetTlogEntry(ctx context.Context, rekorClient *client.Rekor, entryUUID string) (*models.LogEntryAnon, error) {
params := entries.NewGetLogEntryByUUIDParamsWithContext(ctx)
params.SetEntryUUID(entryUUID)
resp, err := rekorClient.Entries.GetLogEntryByUUID(params)
if err != nil {
return nil, err
}
for k, e := range resp.Payload {
// Validate that request EntryUUID matches the response UUID and response shard ID
if err := isExpectedResponseUUID(entryUUID, k); err != nil {
return nil, fmt.Errorf("unexpected entry returned from rekor server: %w", err)
}
// Check that body hash matches UUID
if err := verifyUUID(k, e); err != nil {
return nil, err
}
return &e, nil
}
return nil, errors.New("empty response")
}
func proposedEntries(b64Sig string, payload, pubKey []byte) ([]models.ProposedEntry, error) {
var proposedEntry []models.ProposedEntry
signature, err := base64.StdEncoding.DecodeString(b64Sig)
if err != nil {
return nil, fmt.Errorf("decoding base64 signature: %w", err)
}
// The fact that there's no signature (or empty rather), implies
// that this is an Attestation that we're verifying.
if len(signature) == 0 {
intotoEntry, err := intotoEntry(context.Background(), payload, pubKey)
if err != nil {
return nil, err
}
dsseEntry, err := dsseEntry(context.Background(), payload, pubKey)
if err != nil {
return nil, err
}
proposedEntry = []models.ProposedEntry{dsseEntry, intotoEntry}
} else {
sha256CheckSum := NewCryptoNamedHash(crypto.SHA256)
if _, err := sha256CheckSum.Write(payload); err != nil {
return nil, err
}
re := rekorEntry(sha256CheckSum, signature, pubKey)
entry := &models.Hashedrekord{
APIVersion: swag.String(re.APIVersion()),
Spec: re.HashedRekordObj,
}
proposedEntry = []models.ProposedEntry{entry}
}
return proposedEntry, nil
}
func FindTlogEntry(ctx context.Context, rekorClient *client.Rekor,
b64Sig string, payload, pubKey []byte) ([]models.LogEntryAnon, error) {
searchParams := entries.NewSearchLogQueryParamsWithContext(ctx)
searchLogQuery := models.SearchLogQuery{}
proposedEntries, err := proposedEntries(b64Sig, payload, pubKey)
if err != nil {
return nil, err
}
searchLogQuery.SetEntries(proposedEntries)
searchParams.SetEntry(&searchLogQuery)
resp, err := rekorClient.Entries.SearchLogQuery(searchParams)
if err != nil {
return nil, fmt.Errorf("searching log query: %w", err)
}
if len(resp.Payload) == 0 {
return nil, errors.New("signature not found in transparency log")
}
// This may accumulate multiple entries on multiple tree IDs.
results := make([]models.LogEntryAnon, 0)
for _, logEntry := range resp.GetPayload() {
for k, e := range logEntry {
// Check body hash matches uuid
if err := verifyUUID(k, e); err != nil {
continue
}
results = append(results, e)
}
}
return results, nil
}
// VerifyTLogEntryOffline verifies a TLog entry against a map of trusted rekorPubKeys indexed
// by log id.
func VerifyTLogEntryOffline(ctx context.Context, e *models.LogEntryAnon, rekorPubKeys *TrustedTransparencyLogPubKeys, trustedMaterial root.TrustedMaterial) error {
if e.Verification == nil || e.Verification.InclusionProof == nil {
return errors.New("inclusion proof not provided")
}
if trustedMaterial == nil && (rekorPubKeys == nil || rekorPubKeys.Keys == nil) {
return errors.New("no trusted rekor public keys provided")
}
hashes := [][]byte{}
for _, h := range e.Verification.InclusionProof.Hashes {
hb, _ := hex.DecodeString(h)
hashes = append(hashes, hb)
}
rootHash, _ := hex.DecodeString(*e.Verification.InclusionProof.RootHash)
entryBytes, err := base64.StdEncoding.DecodeString(e.Body.(string))
if err != nil {
return err
}
leafHash := rfc6962.DefaultHasher.HashLeaf(entryBytes)
// Verify the inclusion proof.
if err := proof.VerifyInclusion(rfc6962.DefaultHasher, uint64(*e.Verification.InclusionProof.LogIndex), uint64(*e.Verification.InclusionProof.TreeSize),
leafHash, hashes, rootHash); err != nil {
return fmt.Errorf("verifying inclusion proof: %w", err)
}
// Verify rekor's signature over the SET.
if trustedMaterial != nil {
logID, err := hex.DecodeString(*e.LogID)
if err != nil {
return fmt.Errorf("decoding log ID: %w", err)
}
entry, err := tlog.NewEntry(entryBytes, *e.IntegratedTime, *e.LogIndex, logID, e.Verification.SignedEntryTimestamp, e.Verification.InclusionProof)
if err != nil {
return fmt.Errorf("converting tlog entry: %w", err)
}
if err := tlog.VerifySET(entry, trustedMaterial.RekorLogs()); err != nil {
return fmt.Errorf("verifying SET offline: %w", err)
}
return nil
}
// No trusted root available, so verify the SET with legacy TUF metadata:
payload := bundle.RekorPayload{
Body: e.Body,
IntegratedTime: *e.IntegratedTime,
LogIndex: *e.LogIndex,
LogID: *e.LogID,
}
// Make sure all the rekorPubKeys are ecsda.PublicKeys
for k, v := range rekorPubKeys.Keys {
if _, ok := v.PubKey.(*ecdsa.PublicKey); !ok {
return fmt.Errorf("rekor Public key for LogID %s is not type ecdsa.PublicKey", k)
}
}
pubKey, ok := rekorPubKeys.Keys[payload.LogID]
if !ok {
return errors.New("rekor log public key not found for payload. Check your TUF root (see cosign initialize) or set a custom key with env var SIGSTORE_REKOR_PUBLIC_KEY")
}
err = VerifySET(payload, []byte(e.Verification.SignedEntryTimestamp), pubKey.PubKey.(*ecdsa.PublicKey))
if err != nil {
return fmt.Errorf("verifying signedEntryTimestamp: %w", err)
}
if pubKey.Status != tuf.Active {
ui.Infof(ctx, "Successfully verified Rekor entry using an expired verification key")
}
return nil
}
func NewTrustedTransparencyLogPubKeys() TrustedTransparencyLogPubKeys {
return TrustedTransparencyLogPubKeys{Keys: make(map[string]TransparencyLogPubKey, 0)}
}
// constructRekorPubkey returns a log ID and RekorPubKey from a given
// byte-array representing the PEM-encoded Rekor key and a status.
func (t *TrustedTransparencyLogPubKeys) AddTransparencyLogPubKey(pemBytes []byte, status tuf.StatusKind) error {
pubKey, err := cryptoutils.UnmarshalPEMToPublicKey(pemBytes)
if err != nil {
return err
}
keyID, err := GetTransparencyLogID(pubKey)
if err != nil {
return err
}
t.Keys[keyID] = TransparencyLogPubKey{PubKey: pubKey, Status: status}
return nil
}
// Copyright 2024 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 cosign
import (
"bytes"
"context"
"crypto/x509"
"encoding/base64"
"encoding/json"
"fmt"
"os"
"github.com/secure-systems-lab/go-securesystemslib/dsse"
"github.com/sigstore/cosign/v3/pkg/cosign/env"
"github.com/sigstore/sigstore/pkg/cryptoutils"
"github.com/sigstore/sigstore/pkg/tuf"
)
const (
tsaLeafCertStr = `tsa_leaf.crt.pem`
tsaRootCertStr = `tsa_root.crt.pem`
tsaIntermediateCertStrPattern = `tsa_intermediate_%d.crt.pem`
)
type TSACertificates struct {
LeafCert *x509.Certificate
IntermediateCerts []*x509.Certificate
RootCert []*x509.Certificate
}
type GetTargetStub func(ctx context.Context, usage tuf.UsageKind, names []string) ([]byte, error)
func GetTufTargets(ctx context.Context, usage tuf.UsageKind, names []string) ([]byte, error) {
tufClient, err := tuf.NewFromEnv(ctx)
if err != nil {
return nil, fmt.Errorf("error creating TUF client: %w", err)
}
targets, err := tufClient.GetTargetsByMeta(usage, names)
if err != nil {
return nil, fmt.Errorf("error fetching targets by metadata with usage %v: %w", usage, err)
}
var buffer bytes.Buffer
for _, target := range targets {
buffer.Write(target.Target)
buffer.WriteByte('\n')
}
return buffer.Bytes(), nil
}
func isTufTargetExist(ctx context.Context, name string) (bool, error) {
tufClient, err := tuf.NewFromEnv(ctx)
if err != nil {
return false, fmt.Errorf("error creating TUF client: %w", err)
}
_, err = tufClient.GetTarget(name)
if err != nil {
return false, nil
}
return true, nil
}
// GetTSACerts retrieves trusted TSA certificates from the embedded or cached
// TUF root. If expired, makes a network call to retrieve the updated targets.
// By default, the certificates come from TUF, but you can override this for test
// purposes by using an env variable `SIGSTORE_TSA_CERTIFICATE_FILE` or a file path
// specified in `certChainPath`. If using an alternate, the file should be in PEM format.
func GetTSACerts(ctx context.Context, certChainPath string, fn GetTargetStub) (*TSACertificates, error) {
altTSACert := env.Getenv(env.VariableSigstoreTSACertificateFile)
var raw []byte
var err error
var exists bool
switch {
case altTSACert != "":
raw, err = os.ReadFile(altTSACert)
case certChainPath != "":
raw, err = os.ReadFile(certChainPath)
default:
certNames := []string{tsaLeafCertStr, tsaRootCertStr}
for i := 0; ; i++ {
intermediateCertStr := fmt.Sprintf(tsaIntermediateCertStrPattern, i)
exists, err = isTufTargetExist(ctx, intermediateCertStr)
if err != nil {
return nil, fmt.Errorf("error fetching TSA certificates: %w", err)
}
if !exists {
break
}
certNames = append(certNames, intermediateCertStr)
}
raw, err = fn(ctx, tuf.TSA, certNames)
if err != nil {
return nil, fmt.Errorf("error fetching TSA certificates: %w", err)
}
}
if err != nil {
return nil, fmt.Errorf("error reading TSA certificate file: %w", err)
}
leaves, intermediates, roots, err := splitPEMCertificateChain(raw)
if err != nil {
return nil, fmt.Errorf("error splitting TSA certificates: %w", err)
}
if len(leaves) != 1 {
return nil, fmt.Errorf("TSA certificate chain must contain exactly one leaf certificate")
}
if len(roots) == 0 {
return nil, fmt.Errorf("TSA certificate chain must contain at least one root certificate")
}
return &TSACertificates{
LeafCert: leaves[0],
IntermediateCerts: intermediates,
RootCert: roots,
}, nil
}
// splitPEMCertificateChain returns a list of leaf (non-CA) certificates, a certificate pool for
// intermediate CA certificates, and a certificate pool for root CA certificates
func splitPEMCertificateChain(pem []byte) (leaves, intermediates, roots []*x509.Certificate, err error) {
certs, err := cryptoutils.UnmarshalCertificatesFromPEM(pem)
if err != nil {
return nil, nil, nil, err
}
for _, cert := range certs {
if !cert.IsCA {
leaves = append(leaves, cert)
} else {
// root certificates are self-signed
if bytes.Equal(cert.RawSubject, cert.RawIssuer) {
roots = append(roots, cert)
} else {
intermediates = append(intermediates, cert)
}
}
}
return leaves, intermediates, roots, nil
}
func GetDSSESigBytes(envelopeBytes []byte) ([]byte, error) {
var envelope dsse.Envelope
err := json.Unmarshal(envelopeBytes, &envelope)
if err != nil {
return nil, err
}
if len(envelope.Signatures) == 0 {
return nil, fmt.Errorf("envelope has no signatures")
}
return base64.StdEncoding.DecodeString(envelope.Signatures[0].Sig)
}
// Copyright 2025 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 cosign
import (
"encoding/json"
"errors"
"fmt"
"os"
"path/filepath"
"github.com/sigstore/cosign/v3/pkg/cosign/env"
"github.com/sigstore/sigstore-go/pkg/root"
"github.com/sigstore/sigstore-go/pkg/tuf"
)
func TrustedRoot() (root.TrustedMaterial, error) {
opts, err := setTUFOpts()
if err != nil {
return nil, fmt.Errorf("error setting TUF options: %w", err)
}
tr, err := root.NewLiveTrustedRoot(opts)
if err != nil {
return nil, fmt.Errorf("error getting live trusted root: %w", err)
}
return tr, nil
}
func SigningConfig() (*root.SigningConfig, error) {
opts, err := setTUFOpts()
if err != nil {
return nil, fmt.Errorf("error setting TUF options: %w", err)
}
sc, err := root.FetchSigningConfigWithOptions(opts)
if err != nil {
return nil, fmt.Errorf("error getting signing config from TUF: %w", err)
}
return sc, nil
}
// setTUFOpts sets the TUF cache directory, the mirror URL, and the root.json in the TUF options.
// The cache directory is provided by the user as an environment variable TUF_ROOT, or the default $HOME/.sigstore/root is used.
// The mirror URL is provided by the user as an environment variable TUF_MIRROR. If not overridden by the user, the value set during `cosign initialize` in remote.json in the cache directory is used.
// If the mirror happens to be the sigstore.dev production TUF CDN, the options are returned since it is safe to use all the default settings.
// If the mirror is a custom mirror, we try to find a cached root.json. We must not use the default embedded root.json.
// If the TUF options cannot be found through these steps, the caller should not try to use this TUF client to fetch the trusted root and should instead fall back to the legacy TUF client to fetch individual trusted keys.
func setTUFOpts() (*tuf.Options, error) {
opts := tuf.DefaultOptions()
if tufCacheDir := env.Getenv(env.VariableTUFRootDir); tufCacheDir != "" { //nolint:forbidigo
opts.CachePath = tufCacheDir
}
err := setTUFMirror(opts)
if err != nil {
return nil, fmt.Errorf("error setting TUF mirror: %w", err)
}
if opts.RepositoryBaseURL == tuf.DefaultMirror {
// Using the default mirror, so just use the embedded root.json.
return opts, nil
}
err = setTUFRootJSON(opts)
if err != nil {
return nil, fmt.Errorf("error setting root: %w", err)
}
return opts, nil
}
func setTUFMirror(opts *tuf.Options) error {
if tufMirror := env.Getenv(env.VariableTUFMirror); tufMirror != "" { //nolint:forbidigo
opts.RepositoryBaseURL = tufMirror
return nil
}
// try using the mirror set by `cosign initialize`
cachedRemote := filepath.Join(opts.CachePath, "remote.json")
remoteBytes, err := os.ReadFile(cachedRemote)
if errors.Is(err, os.ErrNotExist) {
return nil // `cosign initialize` wasn't run, so use the default
}
if err != nil {
return fmt.Errorf("error reading remote.json: %w", err)
}
remote := make(map[string]string)
err = json.Unmarshal(remoteBytes, &remote)
if err != nil {
return fmt.Errorf("error unmarshalling remote.json: %w", err)
}
opts.RepositoryBaseURL = remote["mirror"]
return nil
}
func setTUFRootJSON(opts *tuf.Options) error {
// TUF root set by TUF_ROOT_JSON
if tufRootJSON := env.Getenv(env.VariableTUFRootJSON); tufRootJSON != "" { //nolint:forbidigo
rootJSONBytes, err := os.ReadFile(tufRootJSON)
if err != nil {
return fmt.Errorf("error reading root.json given by TUF_ROOT_JSON")
}
opts.Root = rootJSONBytes
return nil
}
// Look for cached root.json
cachedRootJSON := filepath.Join(opts.CachePath, tuf.URLToPath(opts.RepositoryBaseURL), "root.json")
if _, err := os.Stat(cachedRootJSON); !os.IsNotExist(err) {
rootJSONBytes, err := os.ReadFile(cachedRootJSON)
if err != nil {
return fmt.Errorf("error reading cached root.json")
}
opts.Root = rootJSONBytes
return nil
}
return fmt.Errorf("could not find cached root.json")
}
//
// Copyright 2021 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 cosign
import (
"encoding/base64"
"encoding/json"
"errors"
"fmt"
v1 "github.com/google/go-containerregistry/pkg/v1"
in_toto "github.com/in-toto/attestation/go/v1"
"github.com/secure-systems-lab/go-securesystemslib/dsse"
"github.com/sigstore/cosign/v3/pkg/oci"
"github.com/sigstore/sigstore/pkg/signature/payload"
)
// SimpleClaimVerifier verifies that sig.Payload() is a SimpleContainerImage payload which references the given image digest and contains the given annotations.
func SimpleClaimVerifier(sig oci.Signature, imageDigest v1.Hash, annotations map[string]interface{}) error {
p, err := sig.Payload()
if err != nil {
return err
}
ss := &payload.SimpleContainerImage{}
if err := json.Unmarshal(p, ss); err != nil {
return err
}
foundDgst := ss.Critical.Image.DockerManifestDigest
if foundDgst != imageDigest.String() {
return fmt.Errorf("invalid or missing digest in claim: %s", foundDgst)
}
if annotations != nil {
if !correctAnnotations(annotations, ss.Optional) {
return errors.New("missing or incorrect annotation")
}
}
return nil
}
// IntotoSubjectClaimVerifier verifies that sig.Payload() is an Intoto statement which references the given image digest.
func IntotoSubjectClaimVerifier(sig oci.Signature, imageDigest v1.Hash, annotations map[string]interface{}) error {
p, err := sig.Payload()
if err != nil {
return err
}
// The payload here is an envelope. We already verified the signature earlier.
e := dsse.Envelope{}
if err := json.Unmarshal(p, &e); err != nil {
return err
}
stBytes, err := base64.StdEncoding.DecodeString(e.Payload)
if err != nil {
return err
}
st := in_toto.Statement{}
if err := json.Unmarshal(stBytes, &st); err != nil {
return err
}
for _, subj := range st.Subject {
dgst, ok := subj.Digest["sha256"]
if !ok {
continue
}
subjDigest := "sha256:" + dgst
if subjDigest != imageDigest.String() {
continue
}
if !correctAnnotations(annotations, subj.Annotations.AsMap()) {
return errors.New("missing or incorrect annotation")
}
return nil
}
return errors.New("no matching subject digest found")
}
// Copyright 2021 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 cosign
import (
"bytes"
"context"
"crypto"
"crypto/ecdsa"
"crypto/sha256"
"crypto/x509"
"encoding/asn1"
"encoding/base64"
"encoding/hex"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"io/fs"
"log"
"net/http"
"os"
"regexp"
"strings"
"time"
"github.com/cyberphone/json-canonicalization/go/src/webpki.org/jsoncanonicalizer"
"github.com/digitorus/timestamp"
"github.com/go-openapi/runtime"
"github.com/google/go-containerregistry/pkg/name"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/remote/transport"
"github.com/nozzle/throttler"
ssldsse "github.com/secure-systems-lab/go-securesystemslib/dsse"
"github.com/sigstore/cosign/v3/internal/pkg/cosign"
ociexperimental "github.com/sigstore/cosign/v3/internal/pkg/oci/remote"
"github.com/sigstore/cosign/v3/internal/ui"
"github.com/sigstore/cosign/v3/pkg/blob"
cbundle "github.com/sigstore/cosign/v3/pkg/cosign/bundle"
"github.com/sigstore/cosign/v3/pkg/oci"
"github.com/sigstore/cosign/v3/pkg/oci/layout"
ociremote "github.com/sigstore/cosign/v3/pkg/oci/remote"
"github.com/sigstore/cosign/v3/pkg/oci/static"
"github.com/sigstore/cosign/v3/pkg/types"
protobundle "github.com/sigstore/protobuf-specs/gen/pb-go/bundle/v1"
"github.com/sigstore/rekor/pkg/generated/client"
"github.com/sigstore/rekor/pkg/generated/models"
rekor_types "github.com/sigstore/rekor/pkg/types"
dsse_v001 "github.com/sigstore/rekor/pkg/types/dsse/v0.0.1"
hashedrekord_v001 "github.com/sigstore/rekor/pkg/types/hashedrekord/v0.0.1"
intoto_v001 "github.com/sigstore/rekor/pkg/types/intoto/v0.0.1"
intoto_v002 "github.com/sigstore/rekor/pkg/types/intoto/v0.0.2"
rekord_v001 "github.com/sigstore/rekor/pkg/types/rekord/v0.0.1"
sgbundle "github.com/sigstore/sigstore-go/pkg/bundle"
"github.com/sigstore/sigstore-go/pkg/fulcio/certificate"
"github.com/sigstore/sigstore-go/pkg/root"
"github.com/sigstore/sigstore-go/pkg/tlog"
"github.com/sigstore/sigstore-go/pkg/verify"
"github.com/sigstore/sigstore/pkg/cryptoutils"
"github.com/sigstore/sigstore/pkg/signature"
"github.com/sigstore/sigstore/pkg/signature/dsse"
"github.com/sigstore/sigstore/pkg/signature/options"
"github.com/sigstore/sigstore/pkg/tuf"
tsaverification "github.com/sigstore/timestamp-authority/pkg/verification"
)
// Identity specifies an issuer/subject to verify a signature against.
// Both IssuerRegExp/SubjectRegExp support regexp while Issuer/Subject are for
// strict matching.
type Identity struct {
Issuer string
Subject string
IssuerRegExp string
SubjectRegExp string
}
// CheckOpts are the options for checking signatures.
type CheckOpts struct {
// RegistryClientOpts are the options for interacting with the container registry.
RegistryClientOpts []ociremote.Option
// Annotations optionally specifies image signature annotations to verify.
Annotations map[string]interface{}
// ClaimVerifier, if provided, verifies claims present in the oci.Signature.
ClaimVerifier func(sig oci.Signature, imageDigest v1.Hash, annotations map[string]interface{}) error
// TrustedMaterial contains trusted metadata for all Sigstore services. It is exclusive with RekorPubKeys, RootCerts, IntermediateCerts, CTLogPubKeys, and the TSA* cert fields.
TrustedMaterial root.TrustedMaterial
// RekorClient, if set, is used to make online tlog calls use to verify signatures and public keys.
RekorClient *client.Rekor
// RekorPubKeys, if set, is used to validate signatures on log entries from
// Rekor. It is a map from LogID to crypto.PublicKey. LogID is
// derived from the PublicKey (see RFC 6962 S3.2).
// Note that even though the type is of crypto.PublicKey, Rekor only allows
// for ecdsa.PublicKey: https://github.com/sigstore/cosign/issues/2540
RekorPubKeys *TrustedTransparencyLogPubKeys
// SigVerifier is used to verify signatures.
SigVerifier signature.Verifier
// PKOpts are the options provided to `SigVerifier.PublicKey()`.
PKOpts []signature.PublicKeyOption
// RootCerts are the root CA certs used to verify a signature's chained certificate.
RootCerts *x509.CertPool
// IntermediateCerts are the optional intermediate CA certs used to verify a certificate chain.
IntermediateCerts *x509.CertPool
// CertGithubWorkflowTrigger is the GitHub Workflow Trigger name expected for a certificate to be valid. The empty string means any certificate can be valid.
CertGithubWorkflowTrigger string
// CertGithubWorkflowSha is the GitHub Workflow SHA expected for a certificate to be valid. The empty string means any certificate can be valid.
CertGithubWorkflowSha string
// CertGithubWorkflowName is the GitHub Workflow Name expected for a certificate to be valid. The empty string means any certificate can be valid.
CertGithubWorkflowName string
// CertGithubWorkflowRepository is the GitHub Workflow Repository expected for a certificate to be valid. The empty string means any certificate can be valid.
CertGithubWorkflowRepository string
// CertGithubWorkflowRef is the GitHub Workflow Ref expected for a certificate to be valid. The empty string means any certificate can be valid.
CertGithubWorkflowRef string
// IgnoreSCT requires that a certificate contain an embedded SCT during verification. An SCT is proof of inclusion in a
// certificate transparency log.
IgnoreSCT bool
// Detached SCT. Optional, as the SCT is usually embedded in the certificate.
SCT []byte
// CTLogPubKeys, if set, is used to validate SCTs against those keys.
// It is a map from log id to LogIDMetadata. It is a map from LogID to crypto.PublicKey. LogID is derived from the PublicKey (see RFC 6962 S3.2).
CTLogPubKeys *TrustedTransparencyLogPubKeys
// SignatureRef is the reference to the signature file. PayloadRef should always be specified as well (though it’s possible for a _some_ signatures to be verified without it, with a warning).
SignatureRef string
// PayloadRef is a reference to the payload file. Applicable only if SignatureRef is set.
PayloadRef string
// Identities is an array of Identity (Subject, Issuer) matchers that have
// to be met for the signature to ve valid.
Identities []Identity
// Force offline verification of the signature
Offline bool
// Set of flags to verify an RFC3161 timestamp used for trusted timestamping
// TSACertificate is the certificate used to sign the timestamp. Optional, if provided in the timestamp
TSACertificate *x509.Certificate
// TSARootCertificates are the set of roots to verify the TSA certificate
TSARootCertificates []*x509.Certificate
// TSAIntermediateCertificates are the set of intermediates for chain building
TSAIntermediateCertificates []*x509.Certificate
// UseSignedTimestamps enables timestamp verification using a TSA
UseSignedTimestamps bool
// IgnoreTlog skip tlog verification
IgnoreTlog bool
// The amount of maximum workers for parallel executions.
// Defaults to 10.
MaxWorkers int
// Should the experimental OCI 1.1 behaviour be enabled or not.
// Defaults to false.
ExperimentalOCI11 bool
// NewBundleFormat enables the new bundle format (Cosign Bundle Spec) and the new verifier.
NewBundleFormat bool
}
type verifyTrustedMaterial struct {
root.TrustedMaterial
keyTrustedMaterial root.TrustedMaterial
}
func (v *verifyTrustedMaterial) PublicKeyVerifier(hint string) (root.TimeConstrainedVerifier, error) {
if v.keyTrustedMaterial == nil {
return nil, fmt.Errorf("no public key material available")
}
return v.keyTrustedMaterial.PublicKeyVerifier(hint)
}
// verificationOptions returns the verification options for verifying with sigstore-go.
func (co *CheckOpts) verificationOptions() (trustedMaterial root.TrustedMaterial, verifierOptions []verify.VerifierOption, policyOptions []verify.PolicyOption, err error) {
if co.TrustedMaterial == nil {
return nil, nil, nil, fmt.Errorf("TrustMaterial is required")
}
policyOptions = make([]verify.PolicyOption, 0)
if len(co.Identities) > 0 {
var sanMatcher verify.SubjectAlternativeNameMatcher
var issuerMatcher verify.IssuerMatcher
if len(co.Identities) > 1 {
return nil, nil, nil, fmt.Errorf("unsupported: multiple identities are not supported at this time")
}
sanMatcher, err = verify.NewSANMatcher(co.Identities[0].Subject, co.Identities[0].SubjectRegExp)
if err != nil {
return nil, nil, nil, err
}
issuerMatcher, err = verify.NewIssuerMatcher(co.Identities[0].Issuer, co.Identities[0].IssuerRegExp)
if err != nil {
return nil, nil, nil, err
}
extensions := certificate.Extensions{
GithubWorkflowTrigger: co.CertGithubWorkflowTrigger,
GithubWorkflowSHA: co.CertGithubWorkflowSha,
GithubWorkflowName: co.CertGithubWorkflowName,
GithubWorkflowRepository: co.CertGithubWorkflowRepository,
GithubWorkflowRef: co.CertGithubWorkflowRef,
}
certificateIdentities, err := verify.NewCertificateIdentity(sanMatcher, issuerMatcher, extensions)
if err != nil {
return nil, nil, nil, err
}
policyOptions = []verify.PolicyOption{verify.WithCertificateIdentity(certificateIdentities)}
}
// Wrap TrustedMaterial
vTrustedMaterial := &verifyTrustedMaterial{TrustedMaterial: co.TrustedMaterial}
verifierOptions = make([]verify.VerifierOption, 0)
if co.SigVerifier != nil {
// We are verifying with a public key
policyOptions = append(policyOptions, verify.WithKey())
newExpiringKey := root.NewExpiringKey(co.SigVerifier, time.Time{}, time.Time{})
vTrustedMaterial.keyTrustedMaterial = root.NewTrustedPublicKeyMaterial(func(_ string) (root.TimeConstrainedVerifier, error) {
return newExpiringKey, nil
})
} else { //nolint:gocritic
// We are verifying with a certificate
if !co.IgnoreSCT {
verifierOptions = append(verifierOptions, verify.WithSignedCertificateTimestamps(1))
}
}
if !co.IgnoreTlog {
verifierOptions = append(verifierOptions, verify.WithTransparencyLog(1))
// If you aren't using a signed timestamp, use the time from the transparency log
// to verify Fulcio certificates, or require no timestamp to verify a key.
// For Rekor v2, a signed timestamp must be provided.
if !co.UseSignedTimestamps {
if co.SigVerifier == nil {
verifierOptions = append(verifierOptions, verify.WithIntegratedTimestamps(1))
} else {
verifierOptions = append(verifierOptions, verify.WithNoObserverTimestamps())
}
}
}
if co.UseSignedTimestamps {
verifierOptions = append(verifierOptions, verify.WithSignedTimestamps(1))
}
// A time verification policy must be provided. Without a signed timestamp or integrated timestamp,
// verify a certificate with the current time, or require no timestamp to verify a key.
if co.IgnoreTlog && !co.UseSignedTimestamps {
if co.SigVerifier == nil {
verifierOptions = append(verifierOptions, verify.WithCurrentTime())
} else {
verifierOptions = append(verifierOptions, verify.WithNoObserverTimestamps())
}
}
return vTrustedMaterial, verifierOptions, policyOptions, nil
}
// This is a substitutable signature verification function that can be used for verifying
// attestations of blobs.
type signatureVerificationFn func(
ctx context.Context, verifier signature.Verifier, sig payloader) error
// For unit testing
type payloader interface {
// no-op for attestations
Base64Signature() (string, error)
Payload() ([]byte, error)
}
func verifyOCIAttestation(ctx context.Context, verifier signature.Verifier, att payloader) error {
payload, err := att.Payload()
if err != nil {
return err
}
env := ssldsse.Envelope{}
if err := json.Unmarshal(payload, &env); err != nil {
return err
}
if env.PayloadType != types.IntotoPayloadType {
return &VerificationFailure{
fmt.Errorf("invalid payloadType %s on envelope. Expected %s", env.PayloadType, types.IntotoPayloadType),
}
}
dssev, err := ssldsse.NewEnvelopeVerifier(&dsse.VerifierAdapter{SignatureVerifier: verifier})
if err != nil {
return err
}
_, err = dssev.Verify(ctx, &env)
return err
}
func verifyOCISignature(ctx context.Context, verifier signature.Verifier, sig payloader) error {
b64sig, err := sig.Base64Signature()
if err != nil {
return err
}
signature, err := base64.StdEncoding.DecodeString(b64sig)
if err != nil {
return err
}
payload, err := sig.Payload()
if err != nil {
return err
}
return verifier.VerifySignature(bytes.NewReader(signature), bytes.NewReader(payload), options.WithContext(ctx))
}
// ValidateAndUnpackCert creates a Verifier from a certificate. Verifies that the
// certificate chains up to a trusted root using intermediate certificate chain coming from CheckOpts.
// Optionally verifies the subject and issuer of the certificate.
func ValidateAndUnpackCert(cert *x509.Certificate, co *CheckOpts) (signature.Verifier, error) {
return ValidateAndUnpackCertWithIntermediates(cert, co, co.IntermediateCerts)
}
// ValidateAndUnpackCertWithIntermediates creates a Verifier from a certificate. Verifies that the
// certificate chains up to a trusted root using intermediate cert passed as separate argument.
// Optionally verifies the subject and issuer of the certificate.
func ValidateAndUnpackCertWithIntermediates(cert *x509.Certificate, co *CheckOpts, intermediateCerts *x509.CertPool) (signature.Verifier, error) {
verifier, err := signature.LoadVerifier(cert.PublicKey, crypto.SHA256)
if err != nil {
return nil, fmt.Errorf("invalid certificate found on signature: %w", err)
}
// Handle certificates where the Subject Alternative Name is not set to a supported
// GeneralName (RFC 5280 4.2.1.6). Go only supports DNS, IP addresses, email addresses,
// or URIs as SANs. Fulcio can issue a certificate with an OtherName GeneralName, so
// remove the unhandled critical SAN extension before verifying.
if len(cert.UnhandledCriticalExtensions) > 0 {
var unhandledExts []asn1.ObjectIdentifier
for _, oid := range cert.UnhandledCriticalExtensions {
if !oid.Equal(cryptoutils.SANOID) {
unhandledExts = append(unhandledExts, oid)
}
}
cert.UnhandledCriticalExtensions = unhandledExts
}
// Now verify the cert, then the signature.
// If trusted root is available, use the verifiers from sigstore-go (preferred).
var chains [][]*x509.Certificate
if co.TrustedMaterial != nil {
if chains, err = verify.VerifyLeafCertificate(cert.NotBefore, cert, co.TrustedMaterial); err != nil {
return nil, err
}
} else {
// If the trusted root is not available, use the verifiers from cosign (legacy).
chains, err = TrustedCert(cert, co.RootCerts, intermediateCerts)
if err != nil {
return nil, err
}
}
err = CheckCertificatePolicy(cert, co)
if err != nil {
return nil, err
}
// If IgnoreSCT is set, skip the SCT check
if co.IgnoreSCT {
return verifier, nil
}
contains, err := ContainsSCT(cert.Raw)
if err != nil {
return nil, err
}
if !contains && len(co.SCT) == 0 {
return nil, &VerificationFailure{
fmt.Errorf("certificate does not include required embedded SCT and no detached SCT was set"),
}
}
// If trusted root is available and the SCT is embedded, use the verifiers from sigstore-go (preferred).
if co.TrustedMaterial != nil && contains {
if err := verify.VerifySignedCertificateTimestamp(chains, 1, co.TrustedMaterial); err != nil {
return nil, err
}
return verifier, nil
}
// handle if chains has more than one chain - grab first and print message
if len(chains) > 1 {
fmt.Fprintf(os.Stderr, "**Info** Multiple valid certificate chains found. Selecting the first to verify the SCT.\n")
}
if contains {
if err := VerifyEmbeddedSCT(context.Background(), chains[0], co.CTLogPubKeys); err != nil {
return nil, err
}
return verifier, nil
}
chain := chains[0]
if len(chain) < 2 {
return nil, errors.New("certificate chain must contain at least a certificate and its issuer")
}
certPEM, err := cryptoutils.MarshalCertificateToPEM(chain[0])
if err != nil {
return nil, err
}
chainPEM, err := cryptoutils.MarshalCertificatesToPEM(chain[1:])
if err != nil {
return nil, err
}
if err := VerifySCT(context.Background(), certPEM, chainPEM, co.SCT, co.CTLogPubKeys); err != nil {
return nil, err
}
return verifier, nil
}
// CheckCertificatePolicy checks that the certificate subject and issuer match
// the expected values.
func CheckCertificatePolicy(cert *x509.Certificate, co *CheckOpts) error {
ce := CertExtensions{Cert: cert}
if err := validateCertExtensions(ce, co); err != nil {
return err
}
oidcIssuer := ce.GetIssuer()
sans := cryptoutils.GetSubjectAlternateNames(cert)
// If there are identities given, go through them and if one of them
// matches, call that good, otherwise, return an error.
if len(co.Identities) > 0 {
for _, identity := range co.Identities {
issuerMatches := false
switch {
// Check the issuer first
case identity.IssuerRegExp != "":
if regex, err := regexp.Compile(identity.IssuerRegExp); err != nil {
return fmt.Errorf("malformed issuer in identity: %s : %w", identity.IssuerRegExp, err)
} else if regex.MatchString(oidcIssuer) {
issuerMatches = true
}
case identity.Issuer != "":
if identity.Issuer == oidcIssuer {
issuerMatches = true
}
default:
// No issuer constraint on this identity, so checks out
issuerMatches = true
}
// Then the subject
subjectMatches := false
switch {
case identity.SubjectRegExp != "":
regex, err := regexp.Compile(identity.SubjectRegExp)
if err != nil {
return fmt.Errorf("malformed subject in identity: %s : %w", identity.SubjectRegExp, err)
}
for _, san := range sans {
if regex.MatchString(san) {
subjectMatches = true
break
}
}
case identity.Subject != "":
for _, san := range sans {
if san == identity.Subject {
subjectMatches = true
break
}
}
default:
// No subject constraint on this identity, so checks out
subjectMatches = true
}
if subjectMatches && issuerMatches {
// If both issuer / subject match, return verified
return nil
}
}
return &VerificationFailure{
fmt.Errorf("none of the expected identities matched what was in the certificate, got subjects [%s] with issuer %s", strings.Join(sans, ", "), oidcIssuer),
}
}
return nil
}
func validateCertExtensions(ce CertExtensions, co *CheckOpts) error {
if co.CertGithubWorkflowTrigger != "" {
if ce.GetCertExtensionGithubWorkflowTrigger() != co.CertGithubWorkflowTrigger {
return &VerificationFailure{
fmt.Errorf("expected GitHub Workflow Trigger not found in certificate"),
}
}
}
if co.CertGithubWorkflowSha != "" {
if ce.GetExtensionGithubWorkflowSha() != co.CertGithubWorkflowSha {
return &VerificationFailure{
fmt.Errorf("expected GitHub Workflow SHA not found in certificate"),
}
}
}
if co.CertGithubWorkflowName != "" {
if ce.GetCertExtensionGithubWorkflowName() != co.CertGithubWorkflowName {
return &VerificationFailure{
fmt.Errorf("expected GitHub Workflow Name not found in certificate"),
}
}
}
if co.CertGithubWorkflowRepository != "" {
if ce.GetCertExtensionGithubWorkflowRepository() != co.CertGithubWorkflowRepository {
return &VerificationFailure{
fmt.Errorf("expected GitHub Workflow Repository not found in certificate"),
}
}
}
if co.CertGithubWorkflowRef != "" {
if ce.GetCertExtensionGithubWorkflowRef() != co.CertGithubWorkflowRef {
return &VerificationFailure{
fmt.Errorf("expected GitHub Workflow Ref not found in certificate"),
}
}
}
return nil
}
// ValidateAndUnpackCertWithChain creates a Verifier from a certificate. Verifies that the certificate
// chains up to the provided root. Chain should start with the parent of the certificate and end with the root.
// Optionally verifies the subject and issuer of the certificate.
func ValidateAndUnpackCertWithChain(cert *x509.Certificate, chain []*x509.Certificate, co *CheckOpts) (signature.Verifier, error) {
if len(chain) == 0 {
return nil, errors.New("no chain provided to validate certificate")
}
rootPool := x509.NewCertPool()
rootPool.AddCert(chain[len(chain)-1])
co.RootCerts = rootPool
subPool := x509.NewCertPool()
for _, c := range chain[:len(chain)-1] {
subPool.AddCert(c)
}
co.IntermediateCerts = subPool
return ValidateAndUnpackCert(cert, co)
}
func tlogValidateEntry(ctx context.Context, client *client.Rekor, rekorPubKeys *TrustedTransparencyLogPubKeys, trustedMaterial root.TrustedMaterial,
sig oci.Signature, pem []byte) (*models.LogEntryAnon, error) {
b64sig, err := sig.Base64Signature()
if err != nil {
return nil, err
}
payload, err := sig.Payload()
if err != nil {
return nil, err
}
tlogEntries, err := FindTlogEntry(ctx, client, b64sig, payload, pem)
if err != nil {
return nil, err
}
if len(tlogEntries) == 0 {
return nil, fmt.Errorf("no valid tlog entries found with proposed entry")
}
// Always return the earliest integrated entry. That
// always suffices for verification of signature time.
var earliestLogEntry models.LogEntryAnon
var earliestLogEntryTime *time.Time
entryVerificationErrs := make([]string, 0)
for _, e := range tlogEntries {
entry := e
if err := VerifyTLogEntryOffline(ctx, &entry, rekorPubKeys, trustedMaterial); err != nil {
entryVerificationErrs = append(entryVerificationErrs, err.Error())
continue
}
entryTime := time.Unix(*entry.IntegratedTime, 0)
if earliestLogEntryTime == nil || entryTime.Before(*earliestLogEntryTime) {
earliestLogEntryTime = &entryTime
earliestLogEntry = entry
}
}
if earliestLogEntryTime == nil {
return nil, fmt.Errorf("no valid tlog entries found %s", strings.Join(entryVerificationErrs, ", "))
}
return &earliestLogEntry, nil
}
type fakeOCISignatures struct {
oci.Signatures
signatures []oci.Signature
}
func (fos *fakeOCISignatures) Get() ([]oci.Signature, error) {
return fos.signatures, nil
}
// VerifyImageSignatures does all the main cosign checks in a loop, returning the verified signatures.
// If there were no valid signatures, we return an error.
// Note that if co.ExperimentlOCI11 is set, we will attempt to verify
// signatures using the experimental OCI 1.1 behavior.
func VerifyImageSignatures(ctx context.Context, signedImgRef name.Reference, co *CheckOpts) (checkedSignatures []oci.Signature, bundleVerified bool, err error) {
// Try first using OCI 1.1 behavior if experimental flag is set.
if co.ExperimentalOCI11 {
verified, bundleVerified, err := verifyImageSignaturesExperimentalOCI(ctx, signedImgRef, co)
if err == nil {
return verified, bundleVerified, nil
}
}
if co.NewBundleFormat {
return nil, false, errors.New("bundle support for image signatures is not yet implemented")
}
// Enforce this up front.
if co.RootCerts == nil && co.SigVerifier == nil && co.TrustedMaterial == nil {
return nil, false, errors.New("one of verifier, root certs, or trusted root is required")
}
// This is a carefully optimized sequence for fetching the signatures of the
// entity that minimizes registry requests when supplied with a digest input
digest, err := ociremote.ResolveDigest(signedImgRef, co.RegistryClientOpts...)
if err != nil {
if terr := (&transport.Error{}); errors.As(err, &terr) && terr.StatusCode == http.StatusNotFound {
return nil, false, &ErrImageTagNotFound{
fmt.Errorf("image tag not found: %w", err),
}
}
return nil, false, err
}
h, err := v1.NewHash(digest.Identifier())
if err != nil {
return nil, false, err
}
var sigs oci.Signatures
sigRef := co.SignatureRef
if sigRef == "" {
st, err := ociremote.SignatureTag(digest, co.RegistryClientOpts...)
if err != nil {
return nil, false, err
}
sigs, err = ociremote.Signatures(st, co.RegistryClientOpts...)
if err != nil {
return nil, false, err
}
} else {
sigs, err = loadSignatureFromFile(ctx, sigRef, signedImgRef, co)
if err != nil {
return nil, false, err
}
}
return verifySignatures(ctx, sigs, h, co)
}
// VerifyLocalImageSignatures verifies signatures from a saved, local image, without any network calls, returning the verified signatures.
// If there were no valid signatures, we return an error.
func VerifyLocalImageSignatures(ctx context.Context, path string, co *CheckOpts) (checkedSignatures []oci.Signature, bundleVerified bool, err error) {
// Enforce this up front.
if co.RootCerts == nil && co.SigVerifier == nil && co.TrustedMaterial == nil {
return nil, false, errors.New("one of verifier, root certs, or trusted root is required")
}
se, err := layout.SignedImageIndex(path)
if err != nil {
return nil, false, err
}
var h v1.Hash
// Verify either an image index or image.
ii, err := se.SignedImageIndex(v1.Hash{})
if err != nil {
return nil, false, err
}
i, err := se.SignedImage(v1.Hash{})
if err != nil {
return nil, false, err
}
switch {
case ii != nil:
h, err = ii.Digest()
if err != nil {
return nil, false, err
}
case i != nil:
h, err = i.Digest()
if err != nil {
return nil, false, err
}
default:
return nil, false, errors.New("must verify either an image index or image")
}
sigs, err := se.Signatures()
if err != nil {
return nil, false, err
}
if sigs == nil {
return nil, false, fmt.Errorf("no signatures associated with the image saved in %s", path)
}
return verifySignatures(ctx, sigs, h, co)
}
func verifySignatures(ctx context.Context, sigs oci.Signatures, h v1.Hash, co *CheckOpts) (checkedSignatures []oci.Signature, bundleVerified bool, err error) {
sl, err := sigs.Get()
if err != nil {
return nil, false, err
}
if len(sl) == 0 {
return nil, false, &ErrNoSignaturesFound{
errors.New("no signatures found"),
}
}
signatures := make([]oci.Signature, len(sl))
bundlesVerified := make([]bool, len(sl))
workers := co.MaxWorkers
if co.MaxWorkers == 0 {
workers = cosign.DefaultMaxWorkers
}
t := throttler.New(workers, len(sl))
for i, sig := range sl {
go func(sig oci.Signature, index int) {
sig, err := static.Copy(sig)
if err != nil {
t.Done(err)
return
}
verified, err := VerifyImageSignature(ctx, sig, h, co)
bundlesVerified[index] = verified
if err != nil {
t.Done(err)
return
}
signatures[index] = sig
t.Done(nil)
}(sig, i)
// wait till workers are available
t.Throttle()
}
for _, s := range signatures {
if s != nil {
checkedSignatures = append(checkedSignatures, s)
}
}
for _, verified := range bundlesVerified {
bundleVerified = bundleVerified || verified
}
if len(checkedSignatures) == 0 {
var combinedErrors []string
for _, err := range t.Errs() {
combinedErrors = append(combinedErrors, err.Error())
}
// TODO: ErrNoMatchingSignatures.Unwrap should return []error,
// or we should replace "...%s" strings.Join with "...%w", errors.Join.
return nil, false, &ErrNoMatchingSignatures{
fmt.Errorf("no matching signatures: %s", strings.Join(combinedErrors, "\n ")),
}
}
return checkedSignatures, bundleVerified, nil
}
// verifyInternal holds the main verification flow for signatures and attestations.
// 1. Verifies the signature using the provided verifier.
// 2. Checks for transparency log entry presence:
// a. Verifies the Rekor entry in the bundle, if provided. This works offline OR
// b. If we don't have a Rekor entry retrieved via cert, do an online lookup (assuming
// we are in experimental mode).
// 3. If a certificate is provided, check its expiration using the transparency log timestamp.
func verifyInternal(ctx context.Context, sig oci.Signature, h v1.Hash,
verifyFn signatureVerificationFn, co *CheckOpts) (
bundleVerified bool, err error) {
var acceptableRFC3161Time, acceptableRekorBundleTime *time.Time // Timestamps for the signature we accept, or nil if not applicable.
var acceptableRFC3161Timestamp *timestamp.Timestamp
if co.UseSignedTimestamps {
acceptableRFC3161Timestamp, err = VerifyRFC3161Timestamp(sig, co)
if err != nil {
return false, fmt.Errorf("unable to verify RFC3161 timestamp bundle: %w", err)
}
if acceptableRFC3161Timestamp != nil {
acceptableRFC3161Time = &acceptableRFC3161Timestamp.Time
}
}
if !co.IgnoreTlog {
bundleVerified, err = VerifyBundle(sig, co)
if err != nil {
return false, fmt.Errorf("error verifying bundle: %w", err)
}
if bundleVerified {
// Update with the verified bundle's integrated time.
t, err := getBundleIntegratedTime(sig)
if err != nil {
return false, fmt.Errorf("error getting bundle integrated time: %w", err)
}
acceptableRekorBundleTime = &t
} else {
// If the --offline flag was specified, fail here. bundleVerified returns false with
// no error when there was no bundle provided.
if co.Offline {
return false, fmt.Errorf("offline verification failed")
}
// no Rekor client provided for an online lookup
if co.RekorClient == nil {
return false, fmt.Errorf("rekor client not provided for online verification")
}
pemBytes, err := keyBytes(sig, co)
if err != nil {
return false, err
}
e, err := tlogValidateEntry(ctx, co.RekorClient, co.RekorPubKeys, co.TrustedMaterial, sig, pemBytes)
if err != nil {
return false, err
}
t := time.Unix(*e.IntegratedTime, 0)
acceptableRekorBundleTime = &t
bundleVerified = true
}
}
verifier := co.SigVerifier
if verifier == nil {
// If we don't have a public key to check against, we can try a root cert.
cert, err := sig.Cert()
if err != nil {
return false, err
}
if cert == nil {
return false, &ErrNoCertificateFoundOnSignature{
fmt.Errorf("no certificate found on signature"),
}
}
// Create a certificate pool for intermediate CA certificates, excluding the root
chain, err := sig.Chain()
if err != nil {
return false, err
}
// If there is no chain annotation present, we preserve the pools set in the CheckOpts.
var pool *x509.CertPool
if len(chain) > 1 {
if co.IntermediateCerts == nil {
// If the intermediate certs have not been loaded in by TUF
pool = x509.NewCertPool()
for _, cert := range chain[:len(chain)-1] {
pool.AddCert(cert)
}
}
}
// In case pool is not set than set it from co.IntermediateCerts
if pool == nil {
pool = co.IntermediateCerts
}
verifier, err = ValidateAndUnpackCertWithIntermediates(cert, co, pool)
if err != nil {
return false, err
}
}
// 1. Perform cryptographic verification of the signature using the certificate's public key.
if err := verifyFn(ctx, verifier, sig); err != nil {
return false, err
}
// We can't check annotations without claims, both require unmarshalling the payload.
if co.ClaimVerifier != nil {
if err := co.ClaimVerifier(sig, h, co.Annotations); err != nil {
return false, err
}
}
// 2. if a certificate was used, verify the certificate expiration against a time
cert, err := sig.Cert()
if err != nil {
return false, err
}
if cert != nil {
// use the provided Rekor bundle or RFC3161 timestamp to check certificate expiration
expirationChecked := false
if acceptableRFC3161Time != nil {
// Verify the cert against the timestamp time.
if err := CheckExpiry(cert, *acceptableRFC3161Time); err != nil {
return false, fmt.Errorf("checking expiry on certificate with timestamp: %w", err)
}
expirationChecked = true
}
if acceptableRekorBundleTime != nil {
if err := CheckExpiry(cert, *acceptableRekorBundleTime); err != nil {
return false, fmt.Errorf("checking expiry on certificate with bundle: %w", err)
}
expirationChecked = true
}
// if no timestamp has been provided, use the current time
if !expirationChecked {
if err := CheckExpiry(cert, time.Now()); err != nil {
// If certificate is expired and not signed timestamp was provided then error the following message. Otherwise throw an expiration error.
if co.IgnoreTlog && acceptableRFC3161Time == nil {
return false, &VerificationFailure{
fmt.Errorf("expected a signed timestamp to verify an expired certificate"),
}
}
return false, fmt.Errorf("checking expiry on certificate with bundle: %w", err)
}
}
}
return bundleVerified, nil
}
func keyBytes(sig oci.Signature, co *CheckOpts) ([]byte, error) {
cert, err := sig.Cert()
if err != nil {
return nil, err
}
// We have a public key.
if co.SigVerifier != nil {
pub, err := co.SigVerifier.PublicKey(co.PKOpts...)
if err != nil {
return nil, err
}
return cryptoutils.MarshalPublicKeyToPEM(pub)
}
return cryptoutils.MarshalCertificateToPEM(cert)
}
// VerifyBlobSignature verifies a blob signature.
func VerifyBlobSignature(ctx context.Context, sig oci.Signature, co *CheckOpts) (bundleVerified bool, err error) {
// The hash of the artifact is unused.
return verifyInternal(ctx, sig, v1.Hash{}, verifyOCISignature, co)
}
// VerifyImageSignature verifies a signature
func VerifyImageSignature(ctx context.Context, sig oci.Signature, h v1.Hash, co *CheckOpts) (bundleVerified bool, err error) {
return verifyInternal(ctx, sig, h, verifyOCISignature, co)
}
func loadSignatureFromFile(ctx context.Context, sigRef string, signedImgRef name.Reference, co *CheckOpts) (oci.Signatures, error) {
var b64sig string
targetSig, err := blob.LoadFileOrURL(sigRef)
if err != nil {
if !errors.Is(err, fs.ErrNotExist) {
return nil, err
}
targetSig = []byte(sigRef)
}
_, err = base64.StdEncoding.DecodeString(string(targetSig))
if err == nil {
b64sig = string(targetSig)
} else {
b64sig = base64.StdEncoding.EncodeToString(targetSig)
}
var payload []byte
if co.PayloadRef != "" {
payload, err = blob.LoadFileOrURL(co.PayloadRef)
if err != nil {
return nil, err
}
} else {
digest, err := ociremote.ResolveDigest(signedImgRef, co.RegistryClientOpts...)
if err != nil {
return nil, err
}
payload, err = ObsoletePayload(ctx, digest)
if err != nil {
return nil, err
}
}
sig, err := static.NewSignature(payload, b64sig)
if err != nil {
return nil, err
}
return &fakeOCISignatures{
signatures: []oci.Signature{sig},
}, nil
}
// VerifyImageAttestations does all the main cosign checks in a loop, returning the verified attestations.
// If there were no valid attestations, we return an error.
func VerifyImageAttestations(ctx context.Context, signedImgRef name.Reference, co *CheckOpts, nameOpts ...name.Option) (checkedAttestations []oci.Signature, bundleVerified bool, err error) {
// Enforce this up front.
if co.RootCerts == nil && co.SigVerifier == nil && co.TrustedMaterial == nil {
return nil, false, errors.New("one of verifier, root certs, or TrustedMaterial is required")
}
if co.NewBundleFormat {
return verifyImageAttestationsSigstoreBundle(ctx, signedImgRef, co, nameOpts...)
}
// This is a carefully optimized sequence for fetching the attestations of
// the entity that minimizes registry requests when supplied with a digest
// input.
digest, err := ociremote.ResolveDigest(signedImgRef, co.RegistryClientOpts...)
if err != nil {
return nil, false, err
}
h, err := v1.NewHash(digest.Identifier())
if err != nil {
return nil, false, err
}
st, err := ociremote.AttestationTag(digest, co.RegistryClientOpts...)
if err != nil {
return nil, false, err
}
atts, err := ociremote.Signatures(st, co.RegistryClientOpts...)
if err != nil {
return nil, false, err
}
return VerifyImageAttestation(ctx, atts, h, co)
}
// VerifyLocalImageAttestations verifies attestations from a saved, local image, without any network calls,
// returning the verified attestations.
// If there were no valid signatures, we return an error.
func VerifyLocalImageAttestations(ctx context.Context, path string, co *CheckOpts) (checkedAttestations []oci.Signature, bundleVerified bool, err error) {
// Enforce this up front.
if co.RootCerts == nil && co.SigVerifier == nil && co.TrustedMaterial == nil {
return nil, false, errors.New("one of verifier, root certs, or trusted root is required")
}
se, err := layout.SignedImageIndex(path)
if err != nil {
return nil, false, err
}
var h v1.Hash
// Verify either an image index or image.
ii, err := se.SignedImageIndex(v1.Hash{})
if err != nil {
return nil, false, err
}
i, err := se.SignedImage(v1.Hash{})
if err != nil {
return nil, false, err
}
switch {
case ii != nil:
h, err = ii.Digest()
if err != nil {
return nil, false, err
}
case i != nil:
h, err = i.Digest()
if err != nil {
return nil, false, err
}
default:
return nil, false, errors.New("must verify either an image index or image")
}
atts, err := se.Attestations()
if err != nil {
return nil, false, err
}
return VerifyImageAttestation(ctx, atts, h, co)
}
func VerifyBlobAttestation(ctx context.Context, att oci.Signature, h v1.Hash, co *CheckOpts) (
bool, error) {
return verifyInternal(ctx, att, h, verifyOCIAttestation, co)
}
func VerifyImageAttestation(ctx context.Context, atts oci.Signatures, h v1.Hash, co *CheckOpts) (checkedAttestations []oci.Signature, bundleVerified bool, err error) {
if atts == nil {
return nil, false, errors.New("no attestations provided")
}
sl, err := atts.Get()
if err != nil {
return nil, false, err
}
attestations := make([]oci.Signature, len(sl))
bundlesVerified := make([]bool, len(sl))
workers := co.MaxWorkers
if co.MaxWorkers == 0 {
workers = cosign.DefaultMaxWorkers
}
t := throttler.New(workers, len(sl))
for i, att := range sl {
go func(att oci.Signature, index int) {
att, err := static.Copy(att)
if err != nil {
t.Done(err)
return
}
if err := func(att oci.Signature) error {
verified, err := verifyInternal(ctx, att, h, verifyOCIAttestation, co)
bundlesVerified[index] = verified
return err
}(att); err != nil {
t.Done(err)
return
}
attestations[index] = att
t.Done(nil)
}(att, i)
// wait till workers are available
t.Throttle()
}
for _, a := range attestations {
if a != nil {
checkedAttestations = append(checkedAttestations, a)
}
}
for _, verified := range bundlesVerified {
bundleVerified = bundleVerified || verified
}
if len(checkedAttestations) == 0 {
var combinedErrors []string
for _, err := range t.Errs() {
combinedErrors = append(combinedErrors, err.Error())
}
return nil, false, &ErrNoMatchingAttestations{
fmt.Errorf("no matching attestations: %s", strings.Join(combinedErrors, "\n ")),
}
}
return checkedAttestations, bundleVerified, nil
}
// CheckExpiry confirms the time provided is within the valid period of the cert
func CheckExpiry(cert *x509.Certificate, it time.Time) error {
ft := func(t time.Time) string {
return t.Format(time.RFC3339)
}
if cert.NotAfter.Before(it) {
return &VerificationFailure{
fmt.Errorf("certificate expired before signatures were entered in log: %s is before %s",
ft(cert.NotAfter), ft(it)),
}
}
if cert.NotBefore.After(it) {
return &VerificationFailure{
fmt.Errorf("certificate was issued after signatures were entered in log: %s is after %s",
ft(cert.NotAfter), ft(it)),
}
}
return nil
}
func getBundleIntegratedTime(sig oci.Signature) (time.Time, error) {
bundle, err := sig.Bundle()
if err != nil {
return time.Now(), err
} else if bundle == nil {
return time.Now(), nil
}
return time.Unix(bundle.Payload.IntegratedTime, 0), nil
}
// This verifies an offline bundle contained in the sig against the trusted
// Rekor publicKeys.
func VerifyBundle(sig oci.Signature, co *CheckOpts) (bool, error) {
bundle, err := sig.Bundle()
if err != nil {
return false, err
} else if bundle == nil {
return false, nil
}
if co.TrustedMaterial == nil && (co.RekorPubKeys == nil || co.RekorPubKeys.Keys == nil) {
return false, errors.New("no trusted rekor public keys provided")
}
if co.TrustedMaterial != nil {
payload := bundle.Payload
logID, err := hex.DecodeString(payload.LogID)
if err != nil {
return false, fmt.Errorf("decoding log ID: %w", err)
}
body, _ := base64.StdEncoding.DecodeString(payload.Body.(string))
entry, err := tlog.NewEntry(body, payload.IntegratedTime, payload.LogIndex, logID, bundle.SignedEntryTimestamp, nil)
if err != nil {
return false, fmt.Errorf("converting tlog entry: %w", err)
}
if err := tlog.VerifySET(entry, co.TrustedMaterial.RekorLogs()); err != nil {
return false, fmt.Errorf("verifying bundle with trusted root: %w", err)
}
return true, nil
}
// Make sure all the rekorPubKeys are ecsda.PublicKeys
for k, v := range co.RekorPubKeys.Keys {
if _, ok := v.PubKey.(*ecdsa.PublicKey); !ok {
return false, fmt.Errorf("rekor Public key for LogID %s is not type ecdsa.PublicKey", k)
}
}
if err := compareSigs(bundle.Payload.Body.(string), sig); err != nil {
return false, err
}
if err := comparePublicKey(bundle.Payload.Body.(string), sig, co); err != nil {
return false, err
}
pubKey, ok := co.RekorPubKeys.Keys[bundle.Payload.LogID]
if !ok {
return false, &VerificationFailure{
fmt.Errorf("verifying bundle: rekor log public key not found for payload"),
}
}
err = VerifySET(bundle.Payload, bundle.SignedEntryTimestamp, pubKey.PubKey.(*ecdsa.PublicKey))
if err != nil {
return false, err
}
if pubKey.Status != tuf.Active {
fmt.Fprintf(os.Stderr, "**Info** Successfully verified Rekor entry using an expired verification key\n")
}
payload, err := sig.Payload()
if err != nil {
return false, fmt.Errorf("reading payload: %w", err)
}
signature, err := sig.Base64Signature()
if err != nil {
return false, fmt.Errorf("reading base64signature: %w", err)
}
alg, bundlehash, err := bundleHash(bundle.Payload.Body.(string), signature)
if err != nil {
return false, fmt.Errorf("computing bundle hash: %w", err)
}
h := sha256.Sum256(payload)
payloadHash := hex.EncodeToString(h[:])
if alg != "sha256" {
return false, fmt.Errorf("unexpected algorithm: %q", alg)
} else if bundlehash != payloadHash {
return false, fmt.Errorf("matching bundle to payload: bundle=%q, payload=%q", bundlehash, payloadHash)
}
return true, nil
}
type signedEntityForTimestamp struct {
verify.BaseSignedEntity
timestamp *cbundle.RFC3161Timestamp
sigContent *sigContent
}
type sigContent struct {
rawSig []byte
}
func (e *signedEntityForTimestamp) Timestamps() ([][]byte, error) {
timestamps := make([][]byte, 1)
timestamps[0] = e.timestamp.SignedRFC3161Timestamp
return timestamps, nil
}
func (e *signedEntityForTimestamp) SignatureContent() (verify.SignatureContent, error) {
return e.sigContent, nil
}
func (s *sigContent) Signature() []byte {
return s.rawSig
}
func (s *sigContent) EnvelopeContent() verify.EnvelopeContent {
log.Fatal("programmer error: EnvelopeContent was called but not implemented")
return nil
}
func (s *sigContent) MessageSignatureContent() verify.MessageSignatureContent {
log.Fatal("programmer error: MessageSignatureContent was called but not implemented")
return nil
}
// VerifyRFC3161Timestamp verifies that the timestamp in sig is correctly signed, and if so,
// returns the timestamp value.
// It returns (nil, nil) if there is no timestamp, or (nil, err) if there is an invalid timestamp or if
// no root is provided with a timestamp.
func VerifyRFC3161Timestamp(sig oci.Signature, co *CheckOpts) (*timestamp.Timestamp, error) {
ts, err := sig.RFC3161Timestamp()
switch {
case err != nil:
return nil, err
case ts == nil:
return nil, nil
case co.TSARootCertificates == nil && co.TrustedMaterial == nil:
return nil, errors.New("no TSA root certificate(s) provided to verify timestamp")
}
b64Sig, err := sig.Base64Signature()
if err != nil {
return nil, fmt.Errorf("reading base64signature: %w", err)
}
var tsBytes []byte
if len(b64Sig) == 0 {
// For attestations, the Base64Signature is not set, therefore we rely on the signed payload
signedPayload, err := sig.Payload()
if err != nil {
return nil, fmt.Errorf("reading the payload: %w", err)
}
tsBytes = signedPayload
} else {
// create timestamp over raw bytes of signature
rawSig, err := base64.StdEncoding.DecodeString(b64Sig)
if err != nil {
return nil, err
}
tsBytes = rawSig
}
if co.TrustedMaterial != nil {
entity := &signedEntityForTimestamp{
timestamp: ts,
sigContent: &sigContent{rawSig: tsBytes},
}
verifiedTimestamps, verifyErrs, err := verify.VerifySignedTimestamp(entity, co.TrustedMaterial)
if err != nil {
return nil, fmt.Errorf("unable to verify signed timestamps with trusted root: %w", err)
}
if len(verifyErrs) > 0 {
log.Printf("Warning: subset of signed timestamps failed to verify: %v", verifyErrs)
}
return ×tamp.Timestamp{Time: verifiedTimestamps[0].Time}, nil
}
return tsaverification.VerifyTimestampResponse(ts.SignedRFC3161Timestamp, bytes.NewReader(tsBytes),
tsaverification.VerifyOpts{
TSACertificate: co.TSACertificate,
Intermediates: co.TSAIntermediateCertificates,
Roots: co.TSARootCertificates,
})
}
// compare bundle signature to the signature we are verifying
func compareSigs(bundleBody string, sig oci.Signature) error {
// TODO(nsmith5): modify function signature to make it more clear _why_
// we've returned nil (there are several reasons possible here).
actualSig, err := sig.Base64Signature()
if err != nil {
return fmt.Errorf("base64 signature: %w", err)
}
if actualSig == "" {
// NB: empty sig means this is an attestation
return nil
}
bundleSignature, err := bundleSig(bundleBody)
if err != nil {
return fmt.Errorf("failed to extract signature from bundle: %w", err)
}
if bundleSignature == "" {
return nil
}
if bundleSignature != actualSig {
return &VerificationFailure{
fmt.Errorf("signature in bundle does not match signature being verified"),
}
}
return nil
}
func comparePublicKey(bundleBody string, sig oci.Signature, co *CheckOpts) error {
pemBytes, err := keyBytes(sig, co)
if err != nil {
return err
}
bundleKey, err := bundleKey(bundleBody)
if err != nil {
return fmt.Errorf("failed to extract key from bundle: %w", err)
}
decodeSecond, err := base64.StdEncoding.DecodeString(bundleKey)
if err != nil {
return fmt.Errorf("decoding base64 string %s", bundleKey)
}
// Compare the PEM bytes, to ignore spurious newlines in the public key bytes.
pemFirst, rest := pem.Decode(pemBytes)
if len(rest) > 0 {
return fmt.Errorf("unexpected PEM block: %s", rest)
}
pemSecond, rest := pem.Decode(decodeSecond)
if len(rest) > 0 {
return fmt.Errorf("unexpected PEM block: %s", rest)
}
if !bytes.Equal(pemFirst.Bytes, pemSecond.Bytes) {
return fmt.Errorf("comparing public key PEMs, expected %s, got %s",
pemBytes, decodeSecond)
}
return nil
}
func extractEntryImpl(bundleBody string) (rekor_types.EntryImpl, error) {
pe, err := models.UnmarshalProposedEntry(base64.NewDecoder(base64.StdEncoding, strings.NewReader(bundleBody)), runtime.JSONConsumer())
if err != nil {
return nil, err
}
return rekor_types.UnmarshalEntry(pe)
}
func bundleHash(bundleBody, _ string) (string, string, error) {
ei, err := extractEntryImpl(bundleBody)
if err != nil {
return "", "", err
}
switch entry := ei.(type) {
case *dsse_v001.V001Entry:
return *entry.DSSEObj.EnvelopeHash.Algorithm, *entry.DSSEObj.EnvelopeHash.Value, nil
case *hashedrekord_v001.V001Entry:
return *entry.HashedRekordObj.Data.Hash.Algorithm, *entry.HashedRekordObj.Data.Hash.Value, nil
case *intoto_v001.V001Entry:
return *entry.IntotoObj.Content.Hash.Algorithm, *entry.IntotoObj.Content.Hash.Value, nil
case *intoto_v002.V002Entry:
return *entry.IntotoObj.Content.Hash.Algorithm, *entry.IntotoObj.Content.Hash.Value, nil
case *rekord_v001.V001Entry:
return *entry.RekordObj.Data.Hash.Algorithm, *entry.RekordObj.Data.Hash.Value, nil
default:
return "", "", errors.New("unsupported type")
}
}
// bundleSig extracts the signature from the rekor bundle body
func bundleSig(bundleBody string) (string, error) {
ei, err := extractEntryImpl(bundleBody)
if err != nil {
return "", err
}
switch entry := ei.(type) {
case *dsse_v001.V001Entry:
if len(entry.DSSEObj.Signatures) > 1 {
return "", errors.New("multiple signatures on DSSE envelopes are not currently supported")
}
return *entry.DSSEObj.Signatures[0].Signature, nil
case *hashedrekord_v001.V001Entry:
return entry.HashedRekordObj.Signature.Content.String(), nil
case *intoto_v002.V002Entry:
if len(entry.IntotoObj.Content.Envelope.Signatures) > 1 {
return "", errors.New("multiple signatures on DSSE envelopes are not currently supported")
}
return entry.IntotoObj.Content.Envelope.Signatures[0].Sig.String(), nil
case *rekord_v001.V001Entry:
return entry.RekordObj.Signature.Content.String(), nil
default:
return "", errors.New("unsupported type")
}
}
// bundleKey extracts the key from the rekor bundle body
func bundleKey(bundleBody string) (string, error) {
ei, err := extractEntryImpl(bundleBody)
if err != nil {
return "", err
}
switch entry := ei.(type) {
case *dsse_v001.V001Entry:
if len(entry.DSSEObj.Signatures) > 1 {
return "", errors.New("multiple signatures on DSSE envelopes are not currently supported")
}
return entry.DSSEObj.Signatures[0].Verifier.String(), nil
case *hashedrekord_v001.V001Entry:
return entry.HashedRekordObj.Signature.PublicKey.Content.String(), nil
case *intoto_v001.V001Entry:
return entry.IntotoObj.PublicKey.String(), nil
case *intoto_v002.V002Entry:
if len(entry.IntotoObj.Content.Envelope.Signatures) > 1 {
return "", errors.New("multiple signatures on DSSE envelopes are not currently supported")
}
return entry.IntotoObj.Content.Envelope.Signatures[0].PublicKey.String(), nil
case *rekord_v001.V001Entry:
return entry.RekordObj.Signature.PublicKey.Content.String(), nil
default:
return "", errors.New("unsupported type")
}
}
func VerifySET(bundlePayload cbundle.RekorPayload, signature []byte, pub *ecdsa.PublicKey) error {
contents, err := json.Marshal(bundlePayload)
if err != nil {
return fmt.Errorf("marshaling: %w", err)
}
canonicalized, err := jsoncanonicalizer.Transform(contents)
if err != nil {
return fmt.Errorf("canonicalizing: %w", err)
}
// verify the SET against the public key
hash := sha256.Sum256(canonicalized)
if !ecdsa.VerifyASN1(pub, hash[:], signature) {
return &VerificationFailure{
fmt.Errorf("unable to verify SET"),
}
}
return nil
}
func TrustedCert(cert *x509.Certificate, roots *x509.CertPool, intermediates *x509.CertPool) ([][]*x509.Certificate, error) {
chains, err := cert.Verify(x509.VerifyOptions{
// THIS IS IMPORTANT: WE DO NOT CHECK TIMES HERE
// THE CERTIFICATE IS TREATED AS TRUSTED FOREVER
// WE CHECK THAT THE SIGNATURES WERE CREATED DURING THIS WINDOW
CurrentTime: cert.NotBefore,
Roots: roots,
Intermediates: intermediates,
KeyUsages: []x509.ExtKeyUsage{
x509.ExtKeyUsageCodeSigning,
},
})
if err != nil {
return nil, fmt.Errorf("cert verification failed: %w. Check your TUF root (see cosign initialize) or set a custom root with env var SIGSTORE_ROOT_FILE", err)
}
return chains, nil
}
func correctAnnotations(wanted, have map[string]interface{}) bool {
for k, v := range wanted {
if have[k] != v {
return false
}
}
return true
}
// verifyImageSignaturesExperimentalOCI does all the main cosign checks in a loop, returning the verified signatures.
// If there were no valid signatures, we return an error, using OCI 1.1+ behavior.
func verifyImageSignaturesExperimentalOCI(ctx context.Context, signedImgRef name.Reference, co *CheckOpts) (checkedSignatures []oci.Signature, bundleVerified bool, err error) {
// Enforce this up front.
if co.RootCerts == nil && co.SigVerifier == nil && co.TrustedMaterial == nil {
return nil, false, errors.New("one of verifier, root certs, or trusted root is required")
}
// This is a carefully optimized sequence for fetching the signatures of the
// entity that minimizes registry requests when supplied with a digest input
digest, err := ociremote.ResolveDigest(signedImgRef, co.RegistryClientOpts...)
if err != nil {
return nil, false, err
}
h, err := v1.NewHash(digest.Identifier())
if err != nil {
return nil, false, err
}
var sigs oci.Signatures
sigRef := co.SignatureRef
if sigRef == "" {
artifactType := ociexperimental.ArtifactType("sig")
index, err := ociremote.Referrers(digest, artifactType, co.RegistryClientOpts...)
if err != nil {
return nil, false, err
}
results := index.Manifests
numResults := len(results)
if numResults == 0 {
return nil, false, fmt.Errorf("unable to locate reference with artifactType %s", artifactType)
} else if numResults > 1 {
// TODO: if there is more than 1 result.. what does that even mean?
ui.Warnf(ctx, "there were a total of %d references with artifactType %s\n", numResults, artifactType)
}
// TODO: do this smarter using "created" annotations
lastResult := results[numResults-1]
st, err := name.ParseReference(fmt.Sprintf("%s@%s", digest.Repository, lastResult.Digest.String()))
if err != nil {
return nil, false, err
}
sigs, err = ociremote.Signatures(st, co.RegistryClientOpts...)
if err != nil {
return nil, false, err
}
} else {
if co.PayloadRef == "" {
return nil, false, errors.New("payload is required with a manually-provided signature")
}
sigs, err = loadSignatureFromFile(ctx, sigRef, signedImgRef, co)
if err != nil {
return nil, false, err
}
}
return verifySignatures(ctx, sigs, h, co)
}
func GetBundles(_ context.Context, signedImgRef name.Reference, registryClientOpts []ociremote.Option, nameOpts ...name.Option) ([]*sgbundle.Bundle, *v1.Hash, error) {
// This is a carefully optimized sequence for fetching the signatures of the
// entity that minimizes registry requests when supplied with a digest input
digest, err := ociremote.ResolveDigest(signedImgRef, registryClientOpts...)
if err != nil {
if terr := (&transport.Error{}); errors.As(err, &terr) && terr.StatusCode == http.StatusNotFound {
return nil, nil, &ErrImageTagNotFound{
fmt.Errorf("image tag not found: %w", err),
}
}
return nil, nil, err
}
h, err := v1.NewHash(digest.Identifier())
if err != nil {
return nil, nil, err
}
index, err := ociremote.Referrers(digest, "", registryClientOpts...)
if err != nil {
return nil, nil, err
}
var bundles = make([]*sgbundle.Bundle, 0, len(index.Manifests))
for _, result := range index.Manifests {
st, err := name.ParseReference(fmt.Sprintf("%s@%s", digest.Repository, result.Digest.String()), nameOpts...)
if err != nil {
return nil, nil, err
}
bundle, err := ociremote.Bundle(st, registryClientOpts...)
if err != nil {
// There may be non-Sigstore referrers in the index, so we can ignore them.
// TODO: Should we surface any errors here (e.g. if the bundle is invalid)?
continue
}
bundles = append(bundles, bundle)
}
if len(bundles) == 0 {
return nil, nil, &ErrNoMatchingAttestations{
fmt.Errorf("no valid bundles exist in registry"),
}
}
return bundles, &h, nil
}
// verifyImageAttestationsSigstoreBundle verifies attestations from attached sigstore bundles
func verifyImageAttestationsSigstoreBundle(ctx context.Context, signedImgRef name.Reference, co *CheckOpts, nameOpts ...name.Option) (checkedAttestations []oci.Signature, atLeastOneBundleVerified bool, err error) {
bundles, hash, err := GetBundles(ctx, signedImgRef, co.RegistryClientOpts, nameOpts...)
if err != nil {
return nil, false, err
}
digestBytes, err := hex.DecodeString(hash.Hex)
if err != nil {
return nil, false, err
}
artifactPolicyOption := verify.WithArtifactDigest(hash.Algorithm, digestBytes)
attestations := make([]oci.Signature, len(bundles))
bundlesVerified := make([]bool, len(bundles))
workers := co.MaxWorkers
if co.MaxWorkers == 0 {
workers = cosign.DefaultMaxWorkers
}
t := throttler.New(workers, len(bundles))
for i, bundle := range bundles {
go func(bundle *sgbundle.Bundle, index int) {
var att oci.Signature
if err := func(bundle *sgbundle.Bundle) error {
_, err := VerifyNewBundle(ctx, co, artifactPolicyOption, bundle)
if err != nil {
return err
}
dsse, ok := bundle.Content.(*protobundle.Bundle_DsseEnvelope)
if !ok {
return fmt.Errorf("bundle does not contain a DSSE envelope")
}
payload, err := json.Marshal(dsse.DsseEnvelope)
if err != nil {
return fmt.Errorf("marshaling DSSE envelope: %w", err)
}
// We will return a slice of `[]oci.Signature` from this function for compatibility
// with the rest of the codebase. To do that, we wrap the verification output in a
// `oci.Signature` using static.NewAttestation(). This type may contain additional
// data such as the certificate chain, and rekor/tsa data, but for now we only use
// the payload (DSSE). TODO: Add additional data to returned `oci.Signature`. This
// can be done by passing a list of static.Option to NewAttestation (e.g. static.WithCertChain()).
// Depends on https://github.com/sigstore/sigstore-go/issues/328
att, err = static.NewAttestation(payload)
if err != nil {
return err
}
if co.ClaimVerifier != nil {
if err := co.ClaimVerifier(att, *hash, co.Annotations); err != nil {
return err
}
}
bundlesVerified[index] = true
return err
}(bundle); err != nil {
t.Done(err)
return
}
attestations[index] = att
t.Done(nil)
}(bundle, i)
// wait till workers are available
t.Throttle()
}
for _, a := range attestations {
if a != nil {
checkedAttestations = append(checkedAttestations, a)
}
}
for _, verified := range bundlesVerified {
atLeastOneBundleVerified = atLeastOneBundleVerified || verified
}
if len(checkedAttestations) == 0 {
return nil, false, &ErrNoMatchingAttestations{
fmt.Errorf("no matching attestations: %w", errors.Join(t.Errs()...)),
}
}
return checkedAttestations, atLeastOneBundleVerified, nil
}
//
// Copyright 2025 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 cosign
import (
"context"
"github.com/sigstore/sigstore-go/pkg/verify"
)
// VerifyNewBundle verifies a SigstoreBundle with the given parameters
func VerifyNewBundle(_ context.Context, co *CheckOpts, artifactPolicyOption verify.ArtifactPolicyOption, bundle verify.SignedEntity) (*verify.VerificationResult, error) {
trustedMaterial, verifierOptions, policyOptions, err := co.verificationOptions()
if err != nil {
return nil, err
}
verifier, err := verify.NewVerifier(trustedMaterial, verifierOptions...)
if err != nil {
return nil, err
}
return verifier.Verify(bundle, verify.NewPolicy(artifactPolicyOption, policyOptions...))
}
// 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 cosign
import (
"context"
"crypto/x509"
"encoding/hex"
"encoding/json"
"fmt"
"os"
ct "github.com/google/certificate-transparency-go"
ctx509 "github.com/google/certificate-transparency-go/x509"
"github.com/google/certificate-transparency-go/x509util"
"github.com/sigstore/cosign/v3/pkg/cosign/fulcioverifier/ctutil"
"github.com/sigstore/sigstore/pkg/cryptoutils"
"github.com/sigstore/sigstore/pkg/tuf"
)
// ContainsSCT checks if the certificate contains embedded SCTs. cert can either be
// DER or PEM encoded.
func ContainsSCT(cert []byte) (bool, error) {
embeddedSCTs, err := x509util.ParseSCTsFromCertificate(cert)
if err != nil {
return false, err
}
if len(embeddedSCTs) != 0 {
return true, nil
}
return false, nil
}
func getCTPublicKey(sct *ct.SignedCertificateTimestamp,
pubKeys *TrustedTransparencyLogPubKeys) (*TransparencyLogPubKey, error) {
keyID := hex.EncodeToString(sct.LogID.KeyID[:])
pubKeyMetadata, ok := pubKeys.Keys[keyID]
if !ok {
return nil, fmt.Errorf("ctfe public key not found for payload. Check your TUF root (see cosign initialize) or set a custom key with env var SIGSTORE_CT_LOG_PUBLIC_KEY_FILE")
}
return &pubKeyMetadata, nil
}
// VerifySCT verifies SCTs against the Fulcio CT log public key.
//
// The SCT is a `Signed Certificate Timestamp`, which promises that
// the certificate issued by Fulcio was also added to the public CT log within
// some defined time period.
//
// VerifySCT can verify an SCT list embedded in the certificate, or a detached
// SCT provided by Fulcio.
//
// Note that we can't pass in the CheckOpts here which has both RawSCT and
// CTLogPubKeys due to import cycle, so they are pulled out from the struct
// to arguments here.
//
// By default the public keys comes from TUF, but you can override this for test
// purposes by using an env variable `SIGSTORE_CT_LOG_PUBLIC_KEY_FILE`. If using
// an alternate, the file can be PEM, or DER format.
func VerifySCT(_ context.Context, certPEM, chainPEM, rawSCT []byte, pubKeys *TrustedTransparencyLogPubKeys) error {
if pubKeys == nil || len(pubKeys.Keys) == 0 {
return fmt.Errorf("none of the CTFE keys have been found")
}
// parse certificate and chain
cert, err := x509util.CertificateFromPEM(certPEM)
if err != nil {
return err
}
certChain, err := x509util.CertificatesFromPEM(chainPEM)
if err != nil {
return err
}
if len(certChain) == 0 {
return fmt.Errorf("no certificate chain found")
}
// fetch embedded SCT if present
embeddedSCTs, err := x509util.ParseSCTsFromCertificate(certPEM)
if err != nil {
return err
}
// SCT must be either embedded or in header
if len(embeddedSCTs) == 0 && len(rawSCT) == 0 {
return fmt.Errorf("no SCT found")
}
// check SCT embedded in certificate
if len(embeddedSCTs) != 0 {
for _, sct := range embeddedSCTs {
pubKeyMetadata, err := getCTPublicKey(sct, pubKeys)
if err != nil {
return err
}
err = ctutil.VerifySCT(pubKeyMetadata.PubKey, []*ctx509.Certificate{cert, certChain[0]}, sct, true)
if err != nil {
return fmt.Errorf("error verifying embedded SCT: %w", err)
}
if pubKeyMetadata.Status != tuf.Active {
fmt.Fprintf(os.Stderr, "**Info** Successfully verified embedded SCT using an expired verification key\n")
}
}
return nil
}
// check SCT in response header
var addChainResp ct.AddChainResponse
if err := json.Unmarshal(rawSCT, &addChainResp); err != nil {
return fmt.Errorf("unmarshal")
}
sct, err := addChainResp.ToSignedCertificateTimestamp()
if err != nil {
return err
}
pubKeyMetadata, err := getCTPublicKey(sct, pubKeys)
if err != nil {
return err
}
err = ctutil.VerifySCT(pubKeyMetadata.PubKey, []*ctx509.Certificate{cert}, sct, false)
if err != nil {
return fmt.Errorf("error verifying SCT")
}
if pubKeyMetadata.Status != tuf.Active {
fmt.Fprintf(os.Stderr, "**Info** Successfully verified SCT using an expired verification key\n")
}
return nil
}
// VerifyEmbeddedSCT verifies an embedded SCT in a certificate.
func VerifyEmbeddedSCT(ctx context.Context, chain []*x509.Certificate, pubKeys *TrustedTransparencyLogPubKeys) error {
if len(chain) < 2 {
return fmt.Errorf("certificate chain must contain at least a certificate and its issuer")
}
certPEM, err := cryptoutils.MarshalCertificateToPEM(chain[0])
if err != nil {
return err
}
chainPEM, err := cryptoutils.MarshalCertificatesToPEM(chain[1:])
if err != nil {
return err
}
return VerifySCT(ctx, certPEM, chainPEM, []byte{}, pubKeys)
}
//
// Copyright 2021 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 empty
import (
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/empty"
"github.com/google/go-containerregistry/pkg/v1/mutate"
"github.com/google/go-containerregistry/pkg/v1/types"
"github.com/sigstore/cosign/v3/pkg/oci"
)
// Signatures constructs an empty oci.Signatures.
func Signatures() oci.Signatures {
base := empty.Image
if !oci.DockerMediaTypes() {
base = mutate.MediaType(base, types.OCIManifestSchema1)
base = mutate.ConfigMediaType(base, types.OCIConfigJSON)
}
return &emptyImage{
Image: base,
}
}
type emptyImage struct {
v1.Image
}
var _ oci.Signatures = (*emptyImage)(nil)
// Get implements oci.Signatures
func (*emptyImage) Get() ([]oci.Signature, error) {
return nil, nil
}
//
// Copyright 2021 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 empty
import (
"errors"
"fmt"
"github.com/google/go-containerregistry/pkg/name"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/empty"
"github.com/sigstore/cosign/v3/pkg/oci"
)
type signedImage struct {
v1.Image
digest v1.Hash
signature oci.Signatures
attestations oci.Signatures
}
func (se *signedImage) Signatures() (oci.Signatures, error) {
return se.signature, nil
}
func (se *signedImage) Attestations() (oci.Signatures, error) {
return se.attestations, nil
}
func (se *signedImage) Attachment(name string) (oci.File, error) { //nolint: revive
return nil, errors.New("no attachments")
}
func (se *signedImage) Digest() (v1.Hash, error) {
if se.digest.Hex == "" {
return v1.Hash{}, fmt.Errorf("digest not available")
}
return se.digest, nil
}
func SignedImage(ref name.Reference) (oci.SignedImage, error) {
var err error
d := v1.Hash{}
base := empty.Image
if digest, ok := ref.(name.Digest); ok {
d, err = v1.NewHash(digest.DigestStr())
if err != nil {
return nil, err
}
}
return &signedImage{
Image: base,
digest: d,
signature: Signatures(),
attestations: Signatures(),
}, nil
}
// Copyright 2024 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 oci
import "fmt"
// MaxLayersExceeded is an error indicating that the artifact has too many layers and cosign should abort processing it.
type MaxLayersExceeded struct {
value int64
maximum int64
}
func NewMaxLayersExceeded(value, maximum int64) *MaxLayersExceeded {
return &MaxLayersExceeded{value, maximum}
}
func (e *MaxLayersExceeded) Error() string {
return fmt.Sprintf("number of layers (%d) exceeded the limit (%d)", e.value, e.maximum)
}
//
// Copyright 2021 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 signature
import (
"crypto/x509"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"strings"
v1 "github.com/google/go-containerregistry/pkg/v1"
payloadsize "github.com/sigstore/cosign/v3/internal/pkg/cosign/payload/size"
"github.com/sigstore/cosign/v3/pkg/cosign/bundle"
"github.com/sigstore/cosign/v3/pkg/oci"
"github.com/sigstore/sigstore/pkg/cryptoutils"
)
const (
sigkey = "dev.cosignproject.cosign/signature"
certkey = "dev.sigstore.cosign/certificate"
chainkey = "dev.sigstore.cosign/chain"
BundleKey = "dev.sigstore.cosign/bundle"
RFC3161TimestampKey = "dev.sigstore.cosign/rfc3161timestamp"
)
type sigLayer struct {
v1.Layer
desc v1.Descriptor
}
func New(l v1.Layer, desc v1.Descriptor) oci.Signature {
return &sigLayer{
Layer: l,
desc: desc,
}
}
var _ oci.Signature = (*sigLayer)(nil)
// Annotations implements oci.Signature
func (s *sigLayer) Annotations() (map[string]string, error) {
return s.desc.Annotations, nil
}
// Payload implements oci.Signature
func (s *sigLayer) Payload() ([]byte, error) {
size, err := s.Size()
if err != nil {
return nil, err
}
err = payloadsize.CheckSize(uint64(size))
if err != nil {
return nil, err
}
// Compressed is a misnomer here, we just want the raw bytes from the registry.
r, err := s.Compressed()
if err != nil {
return nil, err
}
defer r.Close()
payload, err := io.ReadAll(r)
if err != nil {
return nil, err
}
return payload, nil
}
// Signature implements oci.Signature
func (s *sigLayer) Signature() ([]byte, error) {
b64sig, err := s.Base64Signature()
if err != nil {
return nil, err
}
return base64.StdEncoding.DecodeString(b64sig)
}
// Base64Signature implements oci.Signature
func (s *sigLayer) Base64Signature() (string, error) {
b64sig, ok := s.desc.Annotations[sigkey]
if !ok {
return "", fmt.Errorf("signature layer %s is missing %q annotation", s.desc.Digest, sigkey)
}
return b64sig, nil
}
// Cert implements oci.Signature
func (s *sigLayer) Cert() (*x509.Certificate, error) {
certPEM := s.desc.Annotations[certkey]
if certPEM == "" {
return nil, nil
}
certs, err := cryptoutils.LoadCertificatesFromPEM(strings.NewReader(certPEM))
if err != nil {
return nil, err
}
return certs[0], nil
}
// Chain implements oci.Signature
func (s *sigLayer) Chain() ([]*x509.Certificate, error) {
chainPEM := s.desc.Annotations[chainkey]
if chainPEM == "" {
return nil, nil
}
certs, err := cryptoutils.LoadCertificatesFromPEM(strings.NewReader(chainPEM))
if err != nil {
return nil, err
}
return certs, nil
}
// Bundle implements oci.Signature
func (s *sigLayer) Bundle() (*bundle.RekorBundle, error) {
val := s.desc.Annotations[BundleKey]
if val == "" {
return nil, nil
}
var b bundle.RekorBundle
if err := json.Unmarshal([]byte(val), &b); err != nil {
return nil, fmt.Errorf("unmarshaling bundle: %w", err)
}
return &b, nil
}
// RFC3161Timestamp implements oci.Signature
func (s *sigLayer) RFC3161Timestamp() (*bundle.RFC3161Timestamp, error) {
val := s.desc.Annotations[RFC3161TimestampKey]
if val == "" {
return nil, nil
}
var b bundle.RFC3161Timestamp
if err := json.Unmarshal([]byte(val), &b); err != nil {
return nil, fmt.Errorf("unmarshaling RFC3161 timestamp bundle: %w", err)
}
return &b, nil
}
//
// Copyright 2021 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 layout
import (
"fmt"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/layout"
"github.com/sigstore/cosign/v3/pkg/oci"
"github.com/sigstore/cosign/v3/pkg/oci/signed"
)
const (
kindAnnotation = "kind"
imageAnnotation = "dev.cosignproject.cosign/image"
imageIndexAnnotation = "dev.cosignproject.cosign/imageIndex"
sigsAnnotation = "dev.cosignproject.cosign/sigs"
attsAnnotation = "dev.cosignproject.cosign/atts"
)
// SignedImageIndex provides access to a local index reference, and its signatures.
func SignedImageIndex(path string) (oci.SignedImageIndex, error) {
p, err := layout.FromPath(path)
if err != nil {
return nil, err
}
ii, err := p.ImageIndex()
if err != nil {
return nil, err
}
return &index{
v1Index: ii,
}, nil
}
// We alias ImageIndex so that we can inline it without the type
// name colliding with the name of a method it had to implement.
type v1Index v1.ImageIndex
type index struct {
v1Index
}
var _ oci.SignedImageIndex = (*index)(nil)
// Signatures implements oci.SignedImageIndex
func (i *index) Signatures() (oci.Signatures, error) {
img, err := i.imageByAnnotation(sigsAnnotation)
if err != nil {
return nil, err
}
if img == nil {
return nil, nil
}
return &sigs{img}, nil
}
// Attestations implements oci.SignedImageIndex
func (i *index) Attestations() (oci.Signatures, error) {
img, err := i.imageByAnnotation(attsAnnotation)
if err != nil {
return nil, err
}
if img == nil {
return nil, nil
}
return &sigs{img}, nil
}
// Attestations implements oci.SignedImage
func (i *index) Attachment(name string) (oci.File, error) { //nolint: revive
return nil, fmt.Errorf("not yet implemented")
}
// SignedImage implements oci.SignedImageIndex
// if an empty hash is passed in, return the original image that was signed
func (i *index) SignedImage(h v1.Hash) (oci.SignedImage, error) {
var img v1.Image
var err error
if h.String() == ":" {
img, err = i.imageByAnnotation(imageAnnotation)
} else {
img, err = i.Image(h)
}
if err != nil {
return nil, err
}
if img == nil {
return nil, nil
}
return signed.Image(img), nil
}
// imageByAnnotation searches through all manifests in the index.json
// and returns the image that has the matching annotation
func (i *index) imageByAnnotation(annotation string) (v1.Image, error) {
manifest, err := i.IndexManifest()
if err != nil {
return nil, err
}
for _, m := range manifest.Manifests {
if val, ok := m.Annotations[kindAnnotation]; ok && val == annotation {
return i.Image(m.Digest)
}
}
return nil, nil
}
func (i *index) imageIndexByAnnotation(annotation string) (v1.ImageIndex, error) {
manifest, err := i.IndexManifest()
if err != nil {
return nil, err
}
for _, m := range manifest.Manifests {
if val, ok := m.Annotations[kindAnnotation]; ok && val == annotation {
return i.ImageIndex(m.Digest)
}
}
return nil, nil
}
// SignedImageIndex implements oci.SignedImageIndex
func (i *index) SignedImageIndex(h v1.Hash) (oci.SignedImageIndex, error) {
var ii v1.ImageIndex
var err error
if h.String() == ":" {
ii, err = i.imageIndexByAnnotation(imageIndexAnnotation)
} else {
ii, err = i.ImageIndex(h)
}
if err != nil {
return nil, err
}
if ii == nil {
return nil, nil
}
return &index{
v1Index: ii,
}, nil
}
//
// Copyright 2021 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 layout
import (
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/sigstore/cosign/v3/pkg/oci"
"github.com/sigstore/cosign/v3/pkg/oci/internal/signature"
)
const maxLayers = 1000
type sigs struct {
v1.Image
}
var _ oci.Signatures = (*sigs)(nil)
// Get implements oci.Signatures
func (s *sigs) Get() ([]oci.Signature, error) {
manifest, err := s.Manifest()
if err != nil {
return nil, err
}
numLayers := int64(len(manifest.Layers))
if numLayers > maxLayers {
return nil, oci.NewMaxLayersExceeded(numLayers, maxLayers)
}
signatures := make([]oci.Signature, 0, numLayers)
for _, desc := range manifest.Layers {
l, err := s.LayerByDigest(desc.Digest)
if err != nil {
return nil, err
}
signatures = append(signatures, signature.New(l, desc))
}
return signatures, nil
}
//
// Copyright 2021 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 layout
import (
"fmt"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/empty"
"github.com/google/go-containerregistry/pkg/v1/layout"
"github.com/sigstore/cosign/v3/pkg/oci"
)
// WriteSignedImage writes the image and all related signatures, attestations and attachments
func WriteSignedImage(path string, si oci.SignedImage) error {
// First, write an empty index
layoutPath, err := layout.Write(path, empty.Index)
if err != nil {
return err
}
// write the image
if err := appendImage(layoutPath, si, imageAnnotation); err != nil {
return fmt.Errorf("appending signed image: %w", err)
}
return writeSignedEntity(layoutPath, si)
}
// WriteSignedImageIndex writes the image index and all related signatures, attestations and attachments
func WriteSignedImageIndex(path string, si oci.SignedImageIndex) error {
// First, write an empty index
layoutPath, err := layout.Write(path, empty.Index)
if err != nil {
return err
}
// write the image index
if err := layoutPath.AppendIndex(si, layout.WithAnnotations(
map[string]string{kindAnnotation: imageIndexAnnotation},
)); err != nil {
return fmt.Errorf("appending signed image index: %w", err)
}
return writeSignedEntity(layoutPath, si)
}
func writeSignedEntity(path layout.Path, se oci.SignedEntity) error {
// write the signatures
sigs, err := se.Signatures()
if err != nil {
return fmt.Errorf("getting signatures: %w", err)
}
if !isEmpty(sigs) {
if err := appendImage(path, sigs, sigsAnnotation); err != nil {
return fmt.Errorf("appending signatures: %w", err)
}
}
// write attestations
atts, err := se.Attestations()
if err != nil {
return fmt.Errorf("getting atts")
}
if !isEmpty(atts) {
if err := appendImage(path, atts, attsAnnotation); err != nil {
return fmt.Errorf("appending atts: %w", err)
}
}
// TODO (priyawadhwa@) and attachments
return nil
}
// isEmpty returns true if the signatures or attestations are empty
func isEmpty(s oci.Signatures) bool {
ss, _ := s.Get()
return ss == nil
}
func appendImage(path layout.Path, img v1.Image, annotation string) error {
return path.AppendImage(img, layout.WithAnnotations(
map[string]string{kindAnnotation: annotation},
))
}
//
// Copyright 2021 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 oci
import (
"strconv"
"github.com/sigstore/cosign/v3/pkg/cosign/env"
)
const (
// Deprecated: use `pkg/cosign/env/VariableDockerMediaTypes` instead.
DockerMediaTypesEnv = env.VariableDockerMediaTypes
)
func DockerMediaTypes() bool {
if b, err := strconv.ParseBool(env.Getenv(env.VariableDockerMediaTypes)); err == nil {
return b
}
return false
}
//
// Copyright 2021 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 mutate
import (
"context"
"errors"
"fmt"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/empty"
"github.com/google/go-containerregistry/pkg/v1/mutate"
"github.com/google/go-containerregistry/pkg/v1/types"
"github.com/sigstore/cosign/v3/pkg/oci"
)
// Fn is the signature of the callback supplied to Map.
// The oci.SignedEntity is either an oci.SignedImageIndex or an oci.SignedImage.
// This callback is called on oci.SignedImageIndex *before* its children are
// processed with a context that returns IsBeforeChildren(ctx) == true.
// If the images within the SignedImageIndex change after the Before pass, then
// the Fn will be invoked again on the new SignedImageIndex with a context
// that returns IsAfterChildren(ctx) == true.
// If the returned entity is nil, it is filtered from the result of Map.
type Fn func(context.Context, oci.SignedEntity) (oci.SignedEntity, error)
// ErrSkipChildren is a special error that may be returned from a Mutator
// to skip processing of an index's child entities.
var ErrSkipChildren = errors.New("skip child entities")
// Map calls `fn` on the signed entity and each of its constituent entities (`SignedImageIndex`
// or `SignedImage`) transitively.
// Any errors returned by an `fn` are returned by `Map`.
func Map(ctx context.Context, parent oci.SignedEntity, fn Fn) (oci.SignedEntity, error) {
parent, err := fn(before(ctx), parent)
switch {
case errors.Is(err, ErrSkipChildren):
return parent, nil
case err != nil:
return nil, err
case parent == nil:
// If the function returns nil, it filters it.
return nil, nil
}
sii, ok := parent.(oci.SignedImageIndex)
if !ok {
return parent, nil
}
im, err := sii.IndexManifest()
if err != nil {
return nil, err
}
// Track whether any of the child entities change.
changed := false
adds := []IndexAddendum{}
for _, desc := range im.Manifests {
switch desc.MediaType {
case types.OCIImageIndex, types.DockerManifestList:
x, err := sii.SignedImageIndex(desc.Digest)
if err != nil {
return nil, err
}
se, err := Map(ctx, x, fn)
if err != nil {
return nil, err
} else if se == nil {
// If the function returns nil, it filters it.
changed = true
continue
}
changed = changed || (x != se)
adds = append(adds, IndexAddendum{
Add: se.(oci.SignedImageIndex), // Must be an image index.
Descriptor: v1.Descriptor{
URLs: desc.URLs,
MediaType: desc.MediaType,
Annotations: desc.Annotations,
Platform: desc.Platform,
},
})
case types.OCIManifestSchema1, types.DockerManifestSchema2:
x, err := sii.SignedImage(desc.Digest)
if err != nil {
return nil, err
}
se, err := fn(ctx, x)
if err != nil {
return nil, err
} else if se == nil {
// If the function returns nil, it filters it.
changed = true
continue
}
changed = changed || (x != se)
adds = append(adds, IndexAddendum{
Add: se.(oci.SignedImage), // Must be an image
Descriptor: v1.Descriptor{
URLs: desc.URLs,
MediaType: desc.MediaType,
Annotations: desc.Annotations,
Platform: desc.Platform,
},
})
default:
return nil, fmt.Errorf("unknown mime type: %v", desc.MediaType)
}
}
if !changed {
return parent, nil
}
// Preserve the key attributes from the base IndexManifest.
e := mutate.IndexMediaType(empty.Index, im.MediaType)
e = mutate.Annotations(e, im.Annotations).(v1.ImageIndex)
// Construct a new ImageIndex from the new constituent signed images.
result := AppendManifests(e, adds...)
// Since the children changed, give the callback a crack at the new image index.
return fn(after(ctx), result)
}
// This is used to associate which pass of the Map a particular
// callback is being invoked for.
type mapPassKey struct{}
// before decorates the context such that IsBeforeChildren(ctx) is true.
func before(ctx context.Context) context.Context {
return context.WithValue(ctx, mapPassKey{}, "before")
}
// after decorates the context such that IsAfterChildren(ctx) is true.
func after(ctx context.Context) context.Context {
return context.WithValue(ctx, mapPassKey{}, "after")
}
// IsBeforeChildren is true within a Mutator when it is called before the children
// have been processed.
func IsBeforeChildren(ctx context.Context) bool {
return ctx.Value(mapPassKey{}) == "before"
}
// IsAfterChildren is true within a Mutator when it is called after the children
// have been processed; however, this call is only made if the set of children
// changes since the Before call.
func IsAfterChildren(ctx context.Context) bool {
return ctx.Value(mapPassKey{}) == "after"
}
//
// Copyright 2021 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 mutate
import (
"errors"
"fmt"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/mutate"
"github.com/sigstore/cosign/v3/pkg/oci"
"github.com/sigstore/cosign/v3/pkg/oci/empty"
"github.com/sigstore/cosign/v3/pkg/oci/signed"
)
// Appendable is our signed version of mutate.Appendable
type Appendable interface {
oci.SignedEntity
mutate.Appendable
}
// IndexAddendum is our signed version of mutate.IndexAddendum
type IndexAddendum struct {
Add Appendable
v1.Descriptor
}
// AppendManifests is a form of mutate.AppendManifests that produces an
// oci.SignedImageIndex. The index itself will contain no signatures,
// but allows access to the contained signed entities.
func AppendManifests(base v1.ImageIndex, adds ...IndexAddendum) oci.SignedImageIndex {
madds := make([]mutate.IndexAddendum, 0, len(adds))
for _, add := range adds {
madds = append(madds, mutate.IndexAddendum{
Add: add.Add,
Descriptor: add.Descriptor,
})
}
return &indexWrapper{
v1Index: mutate.AppendManifests(base, madds...),
ogbase: base,
addendum: adds,
}
}
// We alias ImageIndex so that we can inline it without the type
// name colliding with the name of a method it had to implement.
type v1Index v1.ImageIndex
type indexWrapper struct {
v1Index
ogbase v1Index
addendum []IndexAddendum
}
var _ oci.SignedImageIndex = (*indexWrapper)(nil)
// Signatures implements oci.SignedImageIndex
func (i *indexWrapper) Signatures() (oci.Signatures, error) {
return empty.Signatures(), nil
}
// Attestations implements oci.SignedImageIndex
func (i *indexWrapper) Attestations() (oci.Signatures, error) {
return empty.Signatures(), nil
}
// Attachment implements oci.SignedImage
func (*indexWrapper) Attachment(name string) (oci.File, error) { //nolint: revive
return nil, errors.New("unimplemented")
}
// SignedImage implements oci.SignedImageIndex
func (i *indexWrapper) SignedImage(h v1.Hash) (oci.SignedImage, error) {
for _, add := range i.addendum {
si, ok := add.Add.(oci.SignedImage)
if !ok {
continue
}
if d, err := si.Digest(); err != nil {
return nil, err
} else if d == h {
return si, nil
}
}
if sb, ok := i.ogbase.(oci.SignedImageIndex); ok {
return sb.SignedImage(h)
}
unsigned, err := i.Image(h)
if err != nil {
return nil, err
}
return signed.Image(unsigned), nil
}
// SignedImageIndex implements oci.SignedImageIndex
func (i *indexWrapper) SignedImageIndex(h v1.Hash) (oci.SignedImageIndex, error) {
for _, add := range i.addendum {
sii, ok := add.Add.(oci.SignedImageIndex)
if !ok {
continue
}
if d, err := sii.Digest(); err != nil {
return nil, err
} else if d == h {
return sii, nil
}
}
if sb, ok := i.ogbase.(oci.SignedImageIndex); ok {
return sb.SignedImageIndex(h)
}
unsigned, err := i.ImageIndex(h)
if err != nil {
return nil, err
}
return signed.ImageIndex(unsigned), nil
}
// AttachSignatureToEntity attaches the provided signature to the provided entity.
func AttachSignatureToEntity(se oci.SignedEntity, sig oci.Signature, opts ...SignOption) (oci.SignedEntity, error) {
switch obj := se.(type) {
case oci.SignedImage:
return AttachSignatureToImage(obj, sig, opts...)
case oci.SignedImageIndex:
return AttachSignatureToImageIndex(obj, sig, opts...)
default:
return AttachSignatureToUnknown(obj, sig, opts...)
}
}
// AttachAttestationToEntity attaches the provided attestation to the provided entity.
func AttachAttestationToEntity(se oci.SignedEntity, att oci.Signature, opts ...SignOption) (oci.SignedEntity, error) {
switch obj := se.(type) {
case oci.SignedImage:
return AttachAttestationToImage(obj, att, opts...)
case oci.SignedImageIndex:
return AttachAttestationToImageIndex(obj, att, opts...)
default:
return AttachAttestationToUnknown(obj, att, opts...)
}
}
// AttachFileToEntity attaches the provided file to the provided entity.
func AttachFileToEntity(se oci.SignedEntity, name string, f oci.File, opts ...SignOption) (oci.SignedEntity, error) {
switch obj := se.(type) {
case oci.SignedImage:
return AttachFileToImage(obj, name, f, opts...)
case oci.SignedImageIndex:
return AttachFileToImageIndex(obj, name, f, opts...)
default:
return AttachFileToUnknown(obj, name, f, opts...)
}
}
// AttachSignatureToImage attaches the provided signature to the provided image.
func AttachSignatureToImage(si oci.SignedImage, sig oci.Signature, opts ...SignOption) (oci.SignedImage, error) {
return &signedImage{
SignedImage: si,
sig: sig,
attachments: make(map[string]oci.File),
so: makeSignOpts(opts...),
}, nil
}
// AttachAttestationToImage attaches the provided attestation to the provided image.
func AttachAttestationToImage(si oci.SignedImage, att oci.Signature, opts ...SignOption) (oci.SignedImage, error) {
return &signedImage{
SignedImage: si,
att: att,
attachments: make(map[string]oci.File),
so: makeSignOpts(opts...),
}, nil
}
// AttachFileToImage attaches the provided file to the provided image.
func AttachFileToImage(si oci.SignedImage, name string, f oci.File, opts ...SignOption) (oci.SignedImage, error) {
return &signedImage{
SignedImage: si,
attachments: map[string]oci.File{
name: f,
},
so: makeSignOpts(opts...),
}, nil
}
type signedImage struct {
oci.SignedImage
sig oci.Signature
att oci.Signature
so *signOpts
attachments map[string]oci.File
}
// Signatures implements oci.SignedImage
func (si *signedImage) Signatures() (oci.Signatures, error) {
return si.so.dedupeAndReplace(si.sig, si.SignedImage.Signatures)
}
// Attestations implements oci.SignedImage
func (si *signedImage) Attestations() (oci.Signatures, error) {
return si.so.dedupeAndReplace(si.att, si.SignedImage.Attestations)
}
// Attachment implements oci.SignedImage
func (si *signedImage) Attachment(attName string) (oci.File, error) {
if f, ok := si.attachments[attName]; ok {
return f, nil
}
return nil, fmt.Errorf("attachment %q not found", attName)
}
// AttachSignatureToImageIndex attaches the provided signature to the provided image index.
func AttachSignatureToImageIndex(sii oci.SignedImageIndex, sig oci.Signature, opts ...SignOption) (oci.SignedImageIndex, error) {
return &signedImageIndex{
ociSignedImageIndex: sii,
sig: sig,
attachments: make(map[string]oci.File),
so: makeSignOpts(opts...),
}, nil
}
// AttachAttestationToImageIndex attaches the provided attestation to the provided image index.
func AttachAttestationToImageIndex(sii oci.SignedImageIndex, att oci.Signature, opts ...SignOption) (oci.SignedImageIndex, error) {
return &signedImageIndex{
ociSignedImageIndex: sii,
att: att,
attachments: make(map[string]oci.File),
so: makeSignOpts(opts...),
}, nil
}
// AttachFileToImageIndex attaches the provided file to the provided image index.
func AttachFileToImageIndex(sii oci.SignedImageIndex, name string, f oci.File, opts ...SignOption) (oci.SignedImageIndex, error) {
return &signedImageIndex{
ociSignedImageIndex: sii,
attachments: map[string]oci.File{
name: f,
},
so: makeSignOpts(opts...),
}, nil
}
type ociSignedImageIndex oci.SignedImageIndex
type signedImageIndex struct {
ociSignedImageIndex
sig oci.Signature
att oci.Signature
so *signOpts
attachments map[string]oci.File
}
// Signatures implements oci.SignedImageIndex
func (sii *signedImageIndex) Signatures() (oci.Signatures, error) {
return sii.so.dedupeAndReplace(sii.sig, sii.ociSignedImageIndex.Signatures)
}
// Attestations implements oci.SignedImageIndex
func (sii *signedImageIndex) Attestations() (oci.Signatures, error) {
return sii.so.dedupeAndReplace(sii.att, sii.ociSignedImageIndex.Attestations)
}
// Attachment implements oci.SignedImageIndex
func (sii *signedImageIndex) Attachment(attName string) (oci.File, error) {
if f, ok := sii.attachments[attName]; ok {
return f, nil
}
return nil, fmt.Errorf("attachment %q not found", attName)
}
// AttachSignatureToUnknown attaches the provided signature to the provided image.
func AttachSignatureToUnknown(se oci.SignedEntity, sig oci.Signature, opts ...SignOption) (oci.SignedEntity, error) {
return &signedUnknown{
SignedEntity: se,
sig: sig,
attachments: make(map[string]oci.File),
so: makeSignOpts(opts...),
}, nil
}
// AttachAttestationToUnknown attaches the provided attestation to the provided image.
func AttachAttestationToUnknown(se oci.SignedEntity, att oci.Signature, opts ...SignOption) (oci.SignedEntity, error) {
return &signedUnknown{
SignedEntity: se,
att: att,
attachments: make(map[string]oci.File),
so: makeSignOpts(opts...),
}, nil
}
// AttachFileToUnknown attaches the provided file to the provided image.
func AttachFileToUnknown(se oci.SignedEntity, name string, f oci.File, opts ...SignOption) (oci.SignedEntity, error) {
return &signedUnknown{
SignedEntity: se,
attachments: map[string]oci.File{
name: f,
},
so: makeSignOpts(opts...),
}, nil
}
type signedUnknown struct {
oci.SignedEntity
sig oci.Signature
att oci.Signature
so *signOpts
attachments map[string]oci.File
}
type digestable interface {
Digest() (v1.Hash, error)
}
// Digest is generally implemented by oci.SignedEntity implementations.
func (si *signedUnknown) Digest() (v1.Hash, error) {
d, ok := si.SignedEntity.(digestable)
if !ok {
return v1.Hash{}, fmt.Errorf("underlying signed entity not digestable: %T", si.SignedEntity)
}
return d.Digest()
}
// Signatures implements oci.SignedEntity
func (si *signedUnknown) Signatures() (oci.Signatures, error) {
return si.so.dedupeAndReplace(si.sig, si.SignedEntity.Signatures)
}
// Attestations implements oci.SignedEntity
func (si *signedUnknown) Attestations() (oci.Signatures, error) {
return si.so.dedupeAndReplace(si.att, si.SignedEntity.Attestations)
}
// Attachment implements oci.SignedEntity
func (si *signedUnknown) Attachment(attName string) (oci.File, error) {
if f, ok := si.attachments[attName]; ok {
return f, nil
}
return nil, fmt.Errorf("attachment %q not found", attName)
}
func (so *signOpts) dedupeAndReplace(sig oci.Signature, basefn func() (oci.Signatures, error)) (oci.Signatures, error) {
base, err := basefn()
if err != nil {
return nil, err
} else if sig == nil {
return base, nil
}
if so.dd != nil {
if existing, err := so.dd.Find(base, sig); err != nil {
return nil, err
} else if existing != nil {
// Just return base if the signature is redundant
return base, nil
}
}
if so.ro != nil {
replace, err := so.ro.Replace(base, sig)
if err != nil {
return nil, err
}
return ReplaceSignatures(replace)
}
return AppendSignatures(base, so.rct, sig)
}
//
// Copyright 2021 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 mutate
import (
"github.com/google/go-containerregistry/pkg/v1/types"
"github.com/sigstore/cosign/v3/pkg/cosign/bundle"
"github.com/sigstore/cosign/v3/pkg/oci"
)
// DupeDetector scans a list of signatures looking for a duplicate.
type DupeDetector interface {
Find(oci.Signatures, oci.Signature) (oci.Signature, error)
}
type ReplaceOp interface {
Replace(oci.Signatures, oci.Signature) (oci.Signatures, error)
}
type SignOption func(*signOpts)
type signOpts struct {
dd DupeDetector
ro ReplaceOp
rct bool
}
func makeSignOpts(opts ...SignOption) *signOpts {
so := &signOpts{}
for _, opt := range opts {
opt(so)
}
return so
}
// WithDupeDetector configures Sign* to use the following DupeDetector
// to avoid attaching duplicate signatures.
func WithDupeDetector(dd DupeDetector) SignOption {
return func(so *signOpts) {
so.dd = dd
}
}
func WithReplaceOp(ro ReplaceOp) SignOption {
return func(so *signOpts) {
so.ro = ro
}
}
func WithRecordCreationTimestamp(rct bool) SignOption {
return func(so *signOpts) {
so.rct = rct
}
}
type signatureOpts struct {
annotations map[string]string
bundle *bundle.RekorBundle
rfc3161Timestamp *bundle.RFC3161Timestamp
cert []byte
chain []byte
mediaType types.MediaType
}
type SignatureOption func(*signatureOpts)
// WithAnnotations specifies the annotations the Signature should have.
func WithAnnotations(annotations map[string]string) SignatureOption {
return func(so *signatureOpts) {
so.annotations = annotations
}
}
// WithBundle specifies the new Bundle the Signature should have.
func WithBundle(b *bundle.RekorBundle) SignatureOption {
return func(so *signatureOpts) {
so.bundle = b
}
}
// WithRFC3161Timestamp specifies the new RFC3161Timestamp the Signature should have.
func WithRFC3161Timestamp(b *bundle.RFC3161Timestamp) SignatureOption {
return func(so *signatureOpts) {
so.rfc3161Timestamp = b
}
}
// WithCertChain specifies the new cert and chain the Signature should have.
func WithCertChain(cert, chain []byte) SignatureOption {
return func(so *signatureOpts) {
so.cert = cert
so.chain = chain
}
}
// WithMediaType specifies the new MediaType the Signature should have.
func WithMediaType(mediaType types.MediaType) SignatureOption {
return func(so *signatureOpts) {
so.mediaType = mediaType
}
}
func makeSignatureOption(opts ...SignatureOption) *signatureOpts {
so := &signatureOpts{}
for _, opt := range opts {
opt(so)
}
return so
}
// Copyright 2021 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 mutate
import (
"bytes"
"crypto/x509"
"encoding/json"
"fmt"
"io"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/types"
"github.com/sigstore/cosign/v3/pkg/cosign/bundle"
"github.com/sigstore/cosign/v3/pkg/oci"
"github.com/sigstore/cosign/v3/pkg/oci/static"
"github.com/sigstore/sigstore/pkg/cryptoutils"
)
type sigWrapper struct {
wrapped oci.Signature
annotations map[string]string
bundle *bundle.RekorBundle
rfc3161Timestamp *bundle.RFC3161Timestamp
cert *x509.Certificate
chain []*x509.Certificate
mediaType types.MediaType
}
var _ v1.Layer = (*sigWrapper)(nil)
var _ oci.Signature = (*sigWrapper)(nil)
func copyAnnotations(ann map[string]string) map[string]string {
new := make(map[string]string, len(ann)) //nolint: revive
for k, v := range ann {
new[k] = v
}
return new
}
// Annotations implements oci.Signature.
func (sw *sigWrapper) Annotations() (map[string]string, error) {
if sw.annotations != nil {
return copyAnnotations(sw.annotations), nil
}
return sw.wrapped.Annotations()
}
// Payload implements oci.Signature.
func (sw *sigWrapper) Payload() ([]byte, error) {
return sw.wrapped.Payload()
}
// Signature implements oci.Signature
func (sw *sigWrapper) Signature() ([]byte, error) {
return sw.wrapped.Signature()
}
// Base64Signature implements oci.Signature.
func (sw *sigWrapper) Base64Signature() (string, error) {
return sw.wrapped.Base64Signature()
}
// Cert implements oci.Signature.
func (sw *sigWrapper) Cert() (*x509.Certificate, error) {
if sw.cert != nil {
return sw.cert, nil
}
return sw.wrapped.Cert()
}
// Chain implements oci.Signature.
func (sw *sigWrapper) Chain() ([]*x509.Certificate, error) {
if sw.chain != nil {
return sw.chain, nil
}
return sw.wrapped.Chain()
}
// Bundle implements oci.Signature.
func (sw *sigWrapper) Bundle() (*bundle.RekorBundle, error) {
if sw.bundle != nil {
return sw.bundle, nil
}
return sw.wrapped.Bundle()
}
// RFC3161Timestamp implements oci.Signature.
func (sw *sigWrapper) RFC3161Timestamp() (*bundle.RFC3161Timestamp, error) {
if sw.rfc3161Timestamp != nil {
return sw.rfc3161Timestamp, nil
}
return sw.wrapped.RFC3161Timestamp()
}
// MediaType implements v1.Layer
func (sw *sigWrapper) MediaType() (types.MediaType, error) {
if sw.mediaType != "" {
return sw.mediaType, nil
}
return sw.wrapped.MediaType()
}
// Digest implements v1.Layer
func (sw *sigWrapper) Digest() (v1.Hash, error) {
return sw.wrapped.Digest()
}
// DiffID implements v1.Layer
func (sw *sigWrapper) DiffID() (v1.Hash, error) {
return sw.wrapped.DiffID()
}
// Compressed implements v1.Layer
func (sw *sigWrapper) Compressed() (io.ReadCloser, error) {
return sw.wrapped.Compressed()
}
// Uncompressed implements v1.Layer
func (sw *sigWrapper) Uncompressed() (io.ReadCloser, error) {
return sw.wrapped.Uncompressed()
}
// Size implements v1.Layer
func (sw *sigWrapper) Size() (int64, error) {
return sw.wrapped.Size()
}
// Signature returns a new oci.Signature based on the provided original, plus the requested mutations.
func Signature(original oci.Signature, opts ...SignatureOption) (oci.Signature, error) {
newSig := sigWrapper{wrapped: original}
so := makeSignatureOption(opts...)
oldAnn, err := original.Annotations()
if err != nil {
return nil, fmt.Errorf("could not get annotations from signature to mutate: %w", err)
}
var newAnn map[string]string
if so.annotations != nil {
newAnn = copyAnnotations(so.annotations)
newAnn[static.SignatureAnnotationKey] = oldAnn[static.SignatureAnnotationKey]
for _, key := range []string{static.BundleAnnotationKey, static.CertificateAnnotationKey, static.ChainAnnotationKey, static.RFC3161TimestampAnnotationKey} {
if val, isSet := oldAnn[key]; isSet {
newAnn[key] = val
} else {
delete(newAnn, key)
}
}
} else {
newAnn = copyAnnotations(oldAnn)
}
if so.bundle != nil {
newSig.bundle = so.bundle
b, err := json.Marshal(so.bundle)
if err != nil {
return nil, err
}
newAnn[static.BundleAnnotationKey] = string(b)
}
if so.rfc3161Timestamp != nil {
newSig.rfc3161Timestamp = so.rfc3161Timestamp
b, err := json.Marshal(so.rfc3161Timestamp)
if err != nil {
return nil, err
}
newAnn[static.RFC3161TimestampAnnotationKey] = string(b)
}
if so.cert != nil {
var cert *x509.Certificate
var chain []*x509.Certificate
certs, err := cryptoutils.LoadCertificatesFromPEM(bytes.NewReader(so.cert))
if err != nil {
return nil, err
}
newAnn[static.CertificateAnnotationKey] = string(so.cert)
cert = certs[0]
delete(newAnn, static.ChainAnnotationKey)
if so.chain != nil {
chain, err = cryptoutils.LoadCertificatesFromPEM(bytes.NewReader(so.chain))
if err != nil {
return nil, err
}
newAnn[static.ChainAnnotationKey] = string(so.chain)
}
newSig.cert = cert
newSig.chain = chain
}
if so.mediaType != "" {
newSig.mediaType = so.mediaType
}
newSig.annotations = newAnn
return &newSig, nil
}
//
// Copyright 2021 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 mutate
import (
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/mutate"
"github.com/sigstore/cosign/v3/internal/pkg/now"
"github.com/sigstore/cosign/v3/pkg/oci"
"github.com/sigstore/cosign/v3/pkg/oci/empty"
)
const maxLayers = 1000
// AppendSignatures produces a new oci.Signatures with the provided signatures
// appended to the provided base signatures.
func AppendSignatures(base oci.Signatures, recordCreationTimestamp bool, sigs ...oci.Signature) (oci.Signatures, error) {
adds := make([]mutate.Addendum, 0, len(sigs))
for _, sig := range sigs {
ann, err := sig.Annotations()
if err != nil {
return nil, err
}
adds = append(adds, mutate.Addendum{
Layer: sig,
Annotations: ann,
})
}
img, err := mutate.Append(base, adds...)
if err != nil {
return nil, err
}
if recordCreationTimestamp {
t, err := now.Now()
if err != nil {
return nil, err
}
// Set the Created date to time of execution
img, err = mutate.CreatedAt(img, v1.Time{Time: t})
if err != nil {
return nil, err
}
}
return &sigAppender{
Image: img,
base: base,
sigs: sigs,
}, nil
}
// ReplaceSignatures produces a new oci.Signatures provided by the base signatures
// replaced with the new oci.Signatures.
func ReplaceSignatures(base oci.Signatures) (oci.Signatures, error) {
sigs, err := base.Get()
if err != nil {
return nil, err
}
adds := make([]mutate.Addendum, 0, len(sigs))
for _, sig := range sigs {
ann, err := sig.Annotations()
if err != nil {
return nil, err
}
adds = append(adds, mutate.Addendum{
Layer: sig,
Annotations: ann,
})
}
img, err := mutate.Append(empty.Signatures(), adds...)
if err != nil {
return nil, err
}
return &sigAppender{
Image: img,
base: base,
sigs: []oci.Signature{},
}, nil
}
type sigAppender struct {
v1.Image
base oci.Signatures
sigs []oci.Signature
}
var _ oci.Signatures = (*sigAppender)(nil)
// Get implements oci.Signatures
func (sa *sigAppender) Get() ([]oci.Signature, error) {
sl, err := sa.base.Get()
if err != nil {
return nil, err
}
sumLayers := int64(len(sl) + len(sa.sigs))
if sumLayers > maxLayers {
return nil, oci.NewMaxLayersExceeded(sumLayers, maxLayers)
}
return append(sl, sa.sigs...), nil
}
// Copyright 2021 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 remote
import (
"github.com/google/go-containerregistry/pkg/name"
)
// ResolveDigest returns the digest of the image at the reference.
//
// If the reference is by digest already, it simply extracts the digest.
// Otherwise, it looks up the digest from the registry.
func ResolveDigest(ref name.Reference, opts ...Option) (name.Digest, error) {
o := makeOptions(ref.Context(), opts...)
if d, ok := ref.(name.Digest); ok {
return d, nil
}
desc, err := remoteGet(ref, o.ROpt...)
if err != nil {
return name.Digest{}, err
}
return ref.Context().Digest(desc.Digest.String()), nil
}
//
// Copyright 2021 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 remote
import (
"errors"
"net/http"
"github.com/google/go-containerregistry/pkg/name"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/partial"
"github.com/google/go-containerregistry/pkg/v1/remote/transport"
"github.com/sigstore/cosign/v3/pkg/oci"
)
var ErrImageNotFound = errors.New("image not found in registry")
// SignedImage provides access to a remote image reference, and its signatures.
func SignedImage(ref name.Reference, options ...Option) (oci.SignedImage, error) {
o := makeOptions(ref.Context(), options...)
ri, err := remoteImage(ref, o.ROpt...)
var te *transport.Error
if errors.As(err, &te) && te.StatusCode == http.StatusNotFound {
return nil, ErrImageNotFound
} else if err != nil {
return nil, err
}
return &image{
Image: ri,
opt: o,
}, nil
}
type image struct {
v1.Image
opt *options
}
// The wrapped Image implements ConfigLayer, but the wrapping hides that from typechecks in pkg/v1/remote.
// Make image explicitly implement ConfigLayer so that this returns a mountable config layer for pkg/v1/remote.
func (i *image) ConfigLayer() (v1.Layer, error) {
return partial.ConfigLayer(i.Image)
}
var _ oci.SignedImage = (*image)(nil)
// Signatures implements oci.SignedImage
func (i *image) Signatures() (oci.Signatures, error) {
return signatures(i, i.opt)
}
// Attestations implements oci.SignedImage
func (i *image) Attestations() (oci.Signatures, error) {
return attestations(i, i.opt)
}
// Attestations implements oci.SignedImage
func (i *image) Attachment(name string) (oci.File, error) {
return attachment(i, name, i.opt)
}
//
// Copyright 2021 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 remote
import (
"errors"
"net/http"
"github.com/google/go-containerregistry/pkg/name"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/remote/transport"
"github.com/sigstore/cosign/v3/pkg/oci"
)
// SignedImageIndex provides access to a remote index reference, and its signatures.
func SignedImageIndex(ref name.Reference, options ...Option) (oci.SignedImageIndex, error) {
o := makeOptions(ref.Context(), options...)
ri, err := remoteIndex(ref, o.ROpt...)
var te *transport.Error
if errors.As(err, &te) && te.StatusCode == http.StatusNotFound {
return nil, errors.New("index not found in registry")
} else if err != nil {
return nil, err
}
return &index{
v1Index: ri,
ref: ref,
opt: o,
}, nil
}
// We alias ImageIndex so that we can inline it without the type
// name colliding with the name of a method it had to implement.
type v1Index v1.ImageIndex
type index struct {
v1Index
ref name.Reference
opt *options
}
var _ oci.SignedImageIndex = (*index)(nil)
// Signatures implements oci.SignedImageIndex
func (i *index) Signatures() (oci.Signatures, error) {
return signatures(i, i.opt)
}
// Attestations implements oci.SignedImageIndex
func (i *index) Attestations() (oci.Signatures, error) {
return attestations(i, i.opt)
}
// Attestations implements oci.SignedImage
func (i *index) Attachment(name string) (oci.File, error) {
return attachment(i, name, i.opt)
}
// SignedImage implements oci.SignedImageIndex
func (i *index) SignedImage(h v1.Hash) (oci.SignedImage, error) {
img, err := i.Image(h)
if err != nil {
return nil, err
}
return &image{
Image: img,
opt: i.opt,
}, nil
}
// SignedImageIndex implements oci.SignedImageIndex
func (i *index) SignedImageIndex(h v1.Hash) (oci.SignedImageIndex, error) {
ii, err := i.ImageIndex(h)
if err != nil {
return nil, err
}
return &index{
v1Index: ii,
opt: i.opt,
}, nil
}
//
// Copyright 2021 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 remote
import (
"fmt"
"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/name"
"github.com/google/go-containerregistry/pkg/v1/remote"
"github.com/sigstore/cosign/v3/pkg/cosign/env"
)
const (
SignatureTagSuffix = "sig"
SBOMTagSuffix = "sbom"
AttestationTagSuffix = "att"
CustomTagPrefix = ""
RepoOverrideEnvKey = "COSIGN_REPOSITORY"
)
// Option is a functional option for remote operations.
type Option func(*options)
type options struct {
SignatureSuffix string
AttestationSuffix string
SBOMSuffix string
TagPrefix string
TargetRepository name.Repository
ROpt []remote.Option
NameOpts []name.Option
OriginalOptions []Option
}
var defaultOptions = []remote.Option{
remote.WithAuthFromKeychain(authn.DefaultKeychain),
// TODO(mattmoor): Incorporate user agent.
}
func makeOptions(target name.Repository, opts ...Option) *options {
o := &options{
SignatureSuffix: SignatureTagSuffix,
AttestationSuffix: AttestationTagSuffix,
SBOMSuffix: SBOMTagSuffix,
TagPrefix: CustomTagPrefix,
TargetRepository: target,
ROpt: defaultOptions,
// Keep the original options around for things that want
// to call something that takes options!
OriginalOptions: opts,
}
for _, option := range opts {
option(o)
}
return o
}
// WithPrefix is a functional option for overriding the default
// tag prefix.
func WithPrefix(prefix string) Option {
return func(o *options) {
o.TagPrefix = prefix
}
}
// WithSignatureSuffix is a functional option for overriding the default
// signature tag suffix.
func WithSignatureSuffix(suffix string) Option {
return func(o *options) {
o.SignatureSuffix = suffix
}
}
// WithAttestationSuffix is a functional option for overriding the default
// attestation tag suffix.
func WithAttestationSuffix(suffix string) Option {
return func(o *options) {
o.AttestationSuffix = suffix
}
}
// WithSBOMSuffix is a functional option for overriding the default
// SBOM tag suffix.
func WithSBOMSuffix(suffix string) Option {
return func(o *options) {
o.SBOMSuffix = suffix
}
}
// WithRemoteOptions is a functional option for overriding the default
// remote options passed to GGCR.
func WithRemoteOptions(opts ...remote.Option) Option {
return func(o *options) {
o.ROpt = opts
}
}
// WithMoreRemoteOptions is a functional option for adding to the default
// remote options already specified
func WithMoreRemoteOptions(opts ...remote.Option) Option {
return func(o *options) {
o.ROpt = append(o.ROpt, opts...)
}
}
// WithTargetRepository is a functional option for overriding the default
// target repository hosting the signature and attestation tags.
func WithTargetRepository(repo name.Repository) Option {
return func(o *options) {
o.TargetRepository = repo
}
}
// GetEnvTargetRepository returns the Repository specified by
// `os.Getenv(RepoOverrideEnvKey)`, or the empty value if not set.
// Returns an error if the value is set but cannot be parsed.
func GetEnvTargetRepository() (name.Repository, error) {
if ro := env.Getenv(env.VariableRepository); ro != "" {
repo, err := name.NewRepository(ro)
if err != nil {
return name.Repository{}, fmt.Errorf("parsing $"+RepoOverrideEnvKey+": %w", err)
}
return repo, nil
}
return name.Repository{}, nil
}
// WithNameOptions is a functional option for overriding the default
// name options passed to GGCR.
func WithNameOptions(opts ...name.Option) Option {
return func(o *options) {
o.NameOpts = opts
}
}
//
// Copyright 2023 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 remote
import (
"github.com/google/go-containerregistry/pkg/name"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/remote"
)
// Referrers fetches references using registry options.
func Referrers(d name.Digest, artifactType string, opts ...Option) (*v1.IndexManifest, error) {
o := makeOptions(name.Repository{}, opts...)
rOpt := o.ROpt
if artifactType != "" {
rOpt = append(rOpt, remote.WithFilter("artifactType", artifactType))
}
idx, err := remote.Referrers(d, rOpt...)
if err != nil {
return nil, err
}
return idx.IndexManifest()
}
//
// Copyright 2021 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 remote
import (
"errors"
"fmt"
"io"
"net/http"
"github.com/google/go-containerregistry/pkg/name"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/remote"
"github.com/google/go-containerregistry/pkg/v1/remote/transport"
"github.com/google/go-containerregistry/pkg/v1/types"
payloadsize "github.com/sigstore/cosign/v3/internal/pkg/cosign/payload/size"
ociexperimental "github.com/sigstore/cosign/v3/internal/pkg/oci/remote"
"github.com/sigstore/cosign/v3/pkg/oci"
)
// These enable mocking for unit testing without faking an entire registry.
var (
remoteImage = remote.Image
remoteIndex = remote.Index
remoteGet = remote.Get
remoteWrite = remote.Write
remoteHead = remote.Head
remoteWriteLayer = remote.WriteLayer
remotePut = remote.Put
)
// EntityNotFoundError is the error that SignedEntity returns when the
// provided ref does not exist.
type EntityNotFoundError struct {
baseErr error
}
func (e *EntityNotFoundError) Error() string {
return fmt.Sprintf("entity not found in registry, error: %v", e.baseErr)
}
func NewEntityNotFoundError(err error) error {
return &EntityNotFoundError{
baseErr: err,
}
}
// SignedEntity provides access to a remote reference, and its signatures.
// The SignedEntity will be one of SignedImage or SignedImageIndex.
func SignedEntity(ref name.Reference, options ...Option) (oci.SignedEntity, error) {
o := makeOptions(ref.Context(), options...)
got, err := remoteGet(ref, o.ROpt...)
var te *transport.Error
if errors.As(err, &te) && te.StatusCode == http.StatusNotFound {
return nil, NewEntityNotFoundError(err)
} else if err != nil {
return nil, err
}
switch got.MediaType {
case types.OCIImageIndex, types.DockerManifestList:
ii, err := got.ImageIndex()
if err != nil {
return nil, err
}
return &index{
v1Index: ii,
ref: ref.Context().Digest(got.Digest.String()),
opt: o,
}, nil
case types.OCIManifestSchema1, types.DockerManifestSchema2:
i, err := got.Image()
if err != nil {
return nil, err
}
return &image{
Image: i,
opt: o,
}, nil
default:
return nil, fmt.Errorf("unknown mime type: %v", got.MediaType)
}
}
// normalize turns image digests into tags with optional prefix & suffix:
// sha256:d34db33f -> [prefix]sha256-d34db33f[.suffix]
func normalize(h v1.Hash, prefix string, suffix string) string {
return normalizeWithSeparator(h, prefix, suffix, "-")
}
// normalizeWithSeparator turns image digests into tags with optional prefix & suffix:
// sha256:d34db33f -> [prefix]sha256[algorithmSeparator]d34db33f[.suffix]
func normalizeWithSeparator(h v1.Hash, prefix string, suffix string, algorithmSeparator string) string {
if suffix == "" {
return fmt.Sprint(prefix, h.Algorithm, algorithmSeparator, h.Hex)
}
return fmt.Sprint(prefix, h.Algorithm, algorithmSeparator, h.Hex, ".", suffix)
}
// SignatureTag returns the name.Tag that associated signatures with a particular digest.
func SignatureTag(ref name.Reference, opts ...Option) (name.Tag, error) {
o := makeOptions(ref.Context(), opts...)
return suffixTag(ref, o.SignatureSuffix, "-", o)
}
// AttestationTag returns the name.Tag that associated attestations with a particular digest.
func AttestationTag(ref name.Reference, opts ...Option) (name.Tag, error) {
o := makeOptions(ref.Context(), opts...)
return suffixTag(ref, o.AttestationSuffix, "-", o)
}
// SBOMTag returns the name.Tag that associated SBOMs with a particular digest.
func SBOMTag(ref name.Reference, opts ...Option) (name.Tag, error) {
o := makeOptions(ref.Context(), opts...)
return suffixTag(ref, o.SBOMSuffix, "-", o)
}
// DigestTag returns the name.Tag that associated SBOMs with a particular digest.
func DigestTag(ref name.Reference, opts ...Option) (name.Tag, error) {
o := makeOptions(ref.Context(), opts...)
return suffixTag(ref, "", ":", o)
}
// DockerContentDigest fetches the Docker-Content-Digest header for the referenced tag,
// which is required to delete the object in registry API v2.3 and greater.
// See https://github.com/distribution/distribution/blob/main/docs/content/spec/api.md#deleting-an-image
// and https://github.com/distribution/distribution/issues/1579
func DockerContentDigest(ref name.Tag, opts ...Option) (name.Tag, error) {
o := makeOptions(ref.Context(), opts...)
desc, err := remoteGet(ref, o.ROpt...)
if err != nil {
return name.Tag{}, err
}
h := desc.Digest
return o.TargetRepository.Tag(normalizeWithSeparator(h, o.TagPrefix, "", ":")), nil
}
func suffixTag(ref name.Reference, suffix string, algorithmSeparator string, o *options) (name.Tag, error) {
var h v1.Hash
if digest, ok := ref.(name.Digest); ok {
var err error
h, err = v1.NewHash(digest.DigestStr())
if err != nil { // This is effectively impossible.
return name.Tag{}, err
}
} else {
desc, err := remoteGet(ref, o.ROpt...)
if err != nil {
return name.Tag{}, err
}
h = desc.Digest
}
return o.TargetRepository.Tag(normalizeWithSeparator(h, o.TagPrefix, suffix, algorithmSeparator)), nil
}
// signatures is a shared implementation of the oci.Signed* Signatures method.
func signatures(digestable oci.SignedEntity, o *options) (oci.Signatures, error) {
h, err := digestable.Digest()
if err != nil {
return nil, err
}
return Signatures(o.TargetRepository.Tag(normalize(h, o.TagPrefix, o.SignatureSuffix)), o.OriginalOptions...)
}
// attestations is a shared implementation of the oci.Signed* Attestations method.
func attestations(digestable oci.SignedEntity, o *options) (oci.Signatures, error) {
h, err := digestable.Digest()
if err != nil {
return nil, err
}
return Signatures(o.TargetRepository.Tag(normalize(h, o.TagPrefix, o.AttestationSuffix)), o.OriginalOptions...)
}
// attachment is a shared implementation of the oci.Signed* Attachment method.
func attachment(digestable oci.SignedEntity, attName string, o *options) (oci.File, error) {
// Try using OCI 1.1 behavior
if file, err := attachmentExperimentalOCI(digestable, attName, o); err == nil {
return file, nil
}
h, err := digestable.Digest()
if err != nil {
return nil, err
}
img, err := SignedImage(o.TargetRepository.Tag(normalize(h, o.TagPrefix, attName)), o.OriginalOptions...)
if err != nil {
return nil, err
}
ls, err := img.Layers()
if err != nil {
return nil, err
}
if len(ls) != 1 {
return nil, fmt.Errorf("expected exactly one layer in attachment, got %d", len(ls))
}
return &attached{
SignedImage: img,
layer: ls[0],
}, nil
}
type attached struct {
oci.SignedImage
layer v1.Layer
}
var _ oci.File = (*attached)(nil)
// FileMediaType implements oci.File
func (f *attached) FileMediaType() (types.MediaType, error) {
return f.layer.MediaType()
}
// Payload implements oci.File
func (f *attached) Payload() ([]byte, error) {
size, err := f.layer.Size()
if err != nil {
return nil, err
}
err = payloadsize.CheckSize(uint64(size))
if err != nil {
return nil, err
}
// remote layers are believed to be stored
// compressed, but we don't compress attachments
// so use "Compressed" to access the raw byte
// stream.
rc, err := f.layer.Compressed()
if err != nil {
return nil, err
}
defer rc.Close()
return io.ReadAll(rc)
}
// attachmentExperimentalOCI is a shared implementation of the oci.Signed* Attachment method (for OCI 1.1+ behavior).
func attachmentExperimentalOCI(digestable oci.SignedEntity, attName string, o *options) (oci.File, error) {
h, err := digestable.Digest()
if err != nil {
return nil, err
}
d := o.TargetRepository.Digest(h.String())
artifactType := ociexperimental.ArtifactType(attName)
index, err := Referrers(d, artifactType, o.OriginalOptions...)
if err != nil {
return nil, err
}
results := index.Manifests
numResults := len(results)
if numResults == 0 {
return nil, fmt.Errorf("unable to locate reference with artifactType %s", artifactType)
} else if numResults > 1 {
// TODO: if there is more than 1 result.. what does that even mean?
// TODO: use ui.Warn
fmt.Printf("WARNING: there were a total of %d references with artifactType %s\n", numResults, artifactType)
}
// TODO: do this smarter using "created" annotations
lastResult := results[numResults-1]
img, err := SignedImage(o.TargetRepository.Digest(lastResult.Digest.String()), o.OriginalOptions...)
if err != nil {
return nil, err
}
ls, err := img.Layers()
if err != nil {
return nil, err
}
if len(ls) != 1 {
return nil, fmt.Errorf("expected exactly one layer in attachment, got %d", len(ls))
}
return &attached{
SignedImage: img,
layer: ls[0],
}, nil
}
//
// Copyright 2021 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 remote
import (
"errors"
"io"
"net/http"
"strings"
"github.com/google/go-containerregistry/pkg/name"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/partial"
"github.com/google/go-containerregistry/pkg/v1/remote/transport"
"github.com/sigstore/cosign/v3/pkg/oci"
"github.com/sigstore/cosign/v3/pkg/oci/empty"
"github.com/sigstore/cosign/v3/pkg/oci/internal/signature"
sgbundle "github.com/sigstore/sigstore-go/pkg/bundle"
)
const maxLayers = 1000
// Signatures fetches the signatures image represented by the named reference.
// If the tag is not found, this returns an empty oci.Signatures.
func Signatures(ref name.Reference, opts ...Option) (oci.Signatures, error) {
o := makeOptions(ref.Context(), opts...)
img, err := remoteImage(ref, o.ROpt...)
var te *transport.Error
if errors.As(err, &te) {
if te.StatusCode != http.StatusNotFound {
return nil, te
}
return empty.Signatures(), nil
} else if err != nil {
return nil, err
}
return &sigs{
Image: img,
}, nil
}
func Bundle(ref name.Reference, opts ...Option) (*sgbundle.Bundle, error) {
o := makeOptions(ref.Context(), opts...)
img, err := remoteImage(ref, o.ROpt...)
if err != nil {
return nil, err
}
layers, err := img.Layers()
if err != nil {
return nil, err
}
if len(layers) != 1 {
return nil, errors.New("expected exactly one layer")
}
mediaType, err := layers[0].MediaType()
if err != nil {
return nil, err
}
if !strings.HasPrefix(string(mediaType), "application/vnd.dev.sigstore.bundle") {
return nil, errors.New("expected bundle layer")
}
layer0, err := layers[0].Uncompressed()
if err != nil {
return nil, err
}
bundleBytes, err := io.ReadAll(layer0)
if err != nil {
return nil, err
}
b := &sgbundle.Bundle{}
err = b.UnmarshalJSON(bundleBytes)
if err != nil {
return nil, err
}
if !b.MinVersion("v0.3") {
return nil, errors.New("bundle version too old")
}
return b, nil
}
type sigs struct {
v1.Image
}
// The wrapped Image implements ConfigLayer, but the wrapping hides that from typechecks in pkg/v1/remote.
// Make sigs explicitly implement ConfigLayer so that this returns a mountable config layer for pkg/v1/remote.
func (s *sigs) ConfigLayer() (v1.Layer, error) {
return partial.ConfigLayer(s.Image)
}
var _ oci.Signatures = (*sigs)(nil)
// Get implements oci.Signatures
func (s *sigs) Get() ([]oci.Signature, error) {
m, err := s.Manifest()
if err != nil {
return nil, err
}
numLayers := int64(len(m.Layers))
if numLayers > maxLayers {
return nil, oci.NewMaxLayersExceeded(numLayers, maxLayers)
}
signatures := make([]oci.Signature, 0, len(m.Layers))
for _, desc := range m.Layers {
layer, err := s.LayerByDigest(desc.Digest)
if err != nil {
return nil, err
}
signatures = append(signatures, signature.New(layer, desc))
}
return signatures, nil
}
//
// Copyright 2023 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 remote
import (
"github.com/google/go-containerregistry/pkg/name"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/sigstore/cosign/v3/pkg/oci"
)
// SignedUnknown provides access to signed metadata without directly accessing
// the underlying entity. This can be used to access signature metadata for
// digests that have not been published (yet).
func SignedUnknown(digest name.Digest, options ...Option) oci.SignedEntity {
o := makeOptions(digest.Context(), options...)
return &unknown{
digest: digest,
opt: o,
}
}
type unknown struct {
digest name.Digest
opt *options
}
var _ oci.SignedEntity = (*unknown)(nil)
// Digest implements digestable
func (i *unknown) Digest() (v1.Hash, error) {
return v1.NewHash(i.digest.DigestStr())
}
// Signatures implements oci.SignedEntity
func (i *unknown) Signatures() (oci.Signatures, error) {
return signatures(i, i.opt)
}
// Attestations implements oci.SignedEntity
func (i *unknown) Attestations() (oci.Signatures, error) {
return attestations(i, i.opt)
}
// Attachment implements oci.SignedEntity
func (i *unknown) Attachment(name string) (oci.File, error) {
return attachment(i, name, i.opt)
}
//
// Copyright 2021 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 remote
import (
"bytes"
"encoding/json"
"fmt"
"os"
"time"
"github.com/google/go-containerregistry/pkg/name"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/remote"
"github.com/google/go-containerregistry/pkg/v1/static"
"github.com/google/go-containerregistry/pkg/v1/types"
ociexperimental "github.com/sigstore/cosign/v3/internal/pkg/oci/remote"
"github.com/sigstore/cosign/v3/pkg/oci"
ctypes "github.com/sigstore/cosign/v3/pkg/types"
sgbundle "github.com/sigstore/sigstore-go/pkg/bundle"
)
const BundlePredicateType string = "dev.sigstore.bundle.predicateType"
// WriteSignedImageIndexImages writes the images within the image index
// This includes the signed image and associated signatures in the image index
// TODO (priyawadhwa@): write the `index.json` itself to the repo as well
// TODO (priyawadhwa@): write the attestations
func WriteSignedImageIndexImages(ref name.Reference, sii oci.SignedImageIndex, opts ...Option) error {
repo := ref.Context()
o := makeOptions(repo, opts...)
// write the image index if there is one
ii, err := sii.SignedImageIndex(v1.Hash{})
if err != nil {
return fmt.Errorf("signed image index: %w", err)
}
if ii != nil {
if err := remote.WriteIndex(ref, ii, o.ROpt...); err != nil {
return fmt.Errorf("writing index: %w", err)
}
}
// write the image if there is one
si, err := sii.SignedImage(v1.Hash{})
if err != nil {
return fmt.Errorf("signed image: %w", err)
}
if si != nil {
if err := remoteWrite(ref, si, o.ROpt...); err != nil {
return fmt.Errorf("remote write: %w", err)
}
}
// write the signatures
sigs, err := sii.Signatures()
if err != nil {
return err
}
if sigs != nil { // will be nil if there are no associated signatures
sigsTag, err := SignatureTag(ref, opts...)
if err != nil {
return fmt.Errorf("sigs tag: %w", err)
}
if err := remoteWrite(sigsTag, sigs, o.ROpt...); err != nil {
return err
}
}
// write the attestations
atts, err := sii.Attestations()
if err != nil {
return err
}
if atts != nil { // will be nil if there are no associated attestations
attsTag, err := AttestationTag(ref, opts...)
if err != nil {
return fmt.Errorf("sigs tag: %w", err)
}
return remoteWrite(attsTag, atts, o.ROpt...)
}
return nil
}
// WriteSignature publishes the signatures attached to the given entity
// into the provided repository.
func WriteSignatures(repo name.Repository, se oci.SignedEntity, opts ...Option) error {
o := makeOptions(repo, opts...)
// Access the signature list to publish
sigs, err := se.Signatures()
if err != nil {
return err
}
// Determine the tag to which these signatures should be published.
h, err := se.Digest()
if err != nil {
return err
}
tag := o.TargetRepository.Tag(normalize(h, o.TagPrefix, o.SignatureSuffix))
// Write the Signatures image to the tag, with the provided remote.Options
return remoteWrite(tag, sigs, o.ROpt...)
}
// WriteAttestations publishes the attestations attached to the given entity
// into the provided repository.
func WriteAttestations(repo name.Repository, se oci.SignedEntity, opts ...Option) error {
o := makeOptions(repo, opts...)
// Access the signature list to publish
atts, err := se.Attestations()
if err != nil {
return err
}
// Determine the tag to which these signatures should be published.
h, err := se.Digest()
if err != nil {
return err
}
tag := o.TargetRepository.Tag(normalize(h, o.TagPrefix, o.AttestationSuffix))
// Write the Signatures image to the tag, with the provided remote.Options
return remoteWrite(tag, atts, o.ROpt...)
}
// WriteSignaturesExperimentalOCI publishes the signatures attached to the given entity
// into the provided repository (using OCI 1.1 methods).
func WriteSignaturesExperimentalOCI(d name.Digest, se oci.SignedEntity, opts ...Option) error {
o := makeOptions(d.Repository, opts...)
signTarget := d.String()
ref, err := name.ParseReference(signTarget, o.NameOpts...)
if err != nil {
return err
}
desc, err := remoteHead(ref, o.ROpt...)
if err != nil {
return err
}
sigs, err := se.Signatures()
if err != nil {
return err
}
// Write the signature blobs
s, err := sigs.Get()
if err != nil {
return err
}
for _, v := range s {
if err := remoteWriteLayer(d.Repository, v, o.ROpt...); err != nil {
return err
}
}
// Write the config
configBytes, err := sigs.RawConfigFile()
if err != nil {
return err
}
var configDesc v1.Descriptor
if err := json.Unmarshal(configBytes, &configDesc); err != nil {
return err
}
configLayer := static.NewLayer(configBytes, configDesc.MediaType)
if err := remoteWriteLayer(d.Repository, configLayer, o.ROpt...); err != nil {
return err
}
// Write the manifest containing a subject
b, err := sigs.RawManifest()
if err != nil {
return err
}
var m v1.Manifest
if err := json.Unmarshal(b, &m); err != nil {
return err
}
artifactType := ociexperimental.ArtifactType("sig")
m.Config.MediaType = types.MediaType(artifactType)
m.Subject = desc
b, err = json.Marshal(&m)
if err != nil {
return err
}
digest, _, err := v1.SHA256(bytes.NewReader(b))
if err != nil {
return err
}
targetRef, err := name.ParseReference(fmt.Sprintf("%s/%s@%s", d.RegistryStr(), d.RepositoryStr(), digest.String()))
if err != nil {
return err
}
// TODO: use ui.Infof
fmt.Fprintf(os.Stderr, "Uploading signature for [%s] to [%s] with config.mediaType [%s] layers[0].mediaType [%s].\n",
d.String(), targetRef.String(), artifactType, ctypes.SimpleSigningMediaType)
return remotePut(targetRef, &taggableManifest{raw: b, mediaType: m.MediaType}, o.ROpt...)
}
type taggableManifest struct {
raw []byte
mediaType types.MediaType
}
func (taggable taggableManifest) RawManifest() ([]byte, error) {
return taggable.raw, nil
}
func (taggable taggableManifest) MediaType() (types.MediaType, error) {
return taggable.mediaType, nil
}
// WriteReferrer writes a referrer manifest for a given subject digest.
// It uploads the provided layers and creates a manifest that refers to the subject.
func WriteReferrer(d name.Digest, artifactType string, layers []v1.Layer, annotations map[string]string, opts ...Option) error {
o := makeOptions(d.Repository, opts...)
signTarget := d.String()
ref, err := name.ParseReference(signTarget, o.NameOpts...)
if err != nil {
return err
}
desc, err := remoteHead(ref, o.ROpt...)
if err != nil {
return err
}
// Write the empty config layer
configLayer := static.NewLayer([]byte("{}"), "application/vnd.oci.image.config.v1+json")
configDigest, err := configLayer.Digest()
if err != nil {
return fmt.Errorf("failed to calculate digest: %w", err)
}
configSize, err := configLayer.Size()
if err != nil {
return fmt.Errorf("failed to calculate size: %w", err)
}
err = remoteWriteLayer(o.TargetRepository, configLayer, o.ROpt...)
if err != nil {
return fmt.Errorf("failed to upload layer: %w", err)
}
layerDescriptors := make([]v1.Descriptor, len(layers))
for i, layer := range layers {
mediaType, err := layer.MediaType()
if err != nil {
return fmt.Errorf("failed to get media type: %w", err)
}
layerDigest, err := layer.Digest()
if err != nil {
return fmt.Errorf("failed to calculate digest: %w", err)
}
layerSize, err := layer.Size()
if err != nil {
return fmt.Errorf("failed to calculate size: %w", err)
}
err = remoteWriteLayer(o.TargetRepository, layer, o.ROpt...)
if err != nil {
return fmt.Errorf("failed to upload layer: %w", err)
}
layerDescriptors[i] = v1.Descriptor{
MediaType: mediaType,
Digest: layerDigest,
Size: layerSize,
}
}
// Create a manifest that includes the blob as a layer
manifest := referrerManifest{v1.Manifest{
SchemaVersion: 2,
MediaType: types.OCIManifestSchema1,
Config: v1.Descriptor{
MediaType: types.MediaType("application/vnd.oci.empty.v1+json"),
ArtifactType: artifactType,
Digest: configDigest,
Size: configSize,
},
Layers: layerDescriptors,
Subject: &v1.Descriptor{
MediaType: desc.MediaType,
Digest: desc.Digest,
Size: desc.Size,
},
Annotations: annotations,
}, artifactType}
targetRef, err := manifest.targetRef(o.TargetRepository, opts...)
if err != nil {
return fmt.Errorf("failed to create target reference: %w", err)
}
if err := remotePut(targetRef, manifest, o.ROpt...); err != nil {
return fmt.Errorf("failed to upload manifest: %w", err)
}
return nil
}
func WriteAttestationNewBundleFormat(d name.Digest, bundleBytes []byte, predicateType string, opts ...Option) error {
// generate bundle media type string
bundleMediaType, err := sgbundle.MediaTypeString("0.3")
if err != nil {
return fmt.Errorf("failed to generate bundle media type string: %w", err)
}
// Write the bundle layer
layer := static.NewLayer(bundleBytes, types.MediaType(bundleMediaType))
annotations := map[string]string{
"org.opencontainers.image.created": time.Now().UTC().Format(time.RFC3339),
"dev.sigstore.bundle.content": "dsse-envelope",
BundlePredicateType: predicateType,
}
return WriteReferrer(d, bundleMediaType, []v1.Layer{layer}, annotations, opts...)
}
// WriteAttestationsReferrer publishes the attestations attached to the given entity
// into the provided repository using the referrers API.
func WriteAttestationsReferrer(d name.Digest, se oci.SignedEntity, opts ...Option) error {
atts, err := se.Attestations()
if err != nil {
return err
}
layers, err := atts.Layers()
if err != nil {
return err
}
annotations := map[string]string{
"org.opencontainers.image.created": time.Now().UTC().Format(time.RFC3339),
}
// We have to pick an artifactType for the referrer manifest. The attestation
// layers themselves are DSSE envelopes, which wrap in-toto statements.
// For discovery, the artifactType should describe the semantic content (the
// in-toto statement) rather than the wrapper format (the DSSE envelope).
// Using the in-toto media type is the most appropriate and conventional choice,
// as policy engines and other tools will query for attestations using this type.
return WriteReferrer(d, ctypes.IntotoPayloadType, layers, annotations, opts...)
}
// referrerManifest implements Taggable for use in remotePut.
// This type also augments the built-in v1.Manifest with an ArtifactType field
// which is part of the OCI 1.1 Image Manifest spec but is unsupported by
// go-containerregistry at this time.
// See https://github.com/opencontainers/image-spec/blob/v1.1.0/manifest.md#image-manifest-property-descriptions
// and https://github.com/google/go-containerregistry/pull/1931
type referrerManifest struct {
v1.Manifest
ArtifactType string `json:"artifactType,omitempty"`
}
func (r referrerManifest) RawManifest() ([]byte, error) {
return json.Marshal(r)
}
func (r referrerManifest) targetRef(repo name.Repository, opts ...Option) (name.Reference, error) {
o := makeOptions(repo, opts...)
manifestBytes, err := r.RawManifest()
if err != nil {
return nil, err
}
digest, _, err := v1.SHA256(bytes.NewReader(manifestBytes))
if err != nil {
return nil, err
}
return name.ParseReference(fmt.Sprintf("%s/%s@%s", repo.RegistryStr(), repo.RepositoryStr(), digest.String()), o.NameOpts...)
}
func (r referrerManifest) MediaType() (types.MediaType, error) {
return types.OCIManifestSchema1, nil
}
//
// Copyright 2021 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 signed
import (
"errors"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/sigstore/cosign/v3/pkg/oci"
"github.com/sigstore/cosign/v3/pkg/oci/empty"
)
// Image returns an oci.SignedImage form of the v1.Image with no signatures.
func Image(i v1.Image) oci.SignedImage {
return &image{
Image: i,
}
}
type image struct {
v1.Image
}
var _ oci.SignedImage = (*image)(nil)
// Signatures implements oci.SignedImage
func (*image) Signatures() (oci.Signatures, error) {
return empty.Signatures(), nil
}
// Attestations implements oci.SignedImage
func (*image) Attestations() (oci.Signatures, error) {
return empty.Signatures(), nil
}
// Attestations implements oci.SignedImage
func (*image) Attachment(name string) (oci.File, error) { //nolint: revive
return nil, errors.New("unimplemented")
}
//
// Copyright 2021 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 signed
import (
"errors"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/sigstore/cosign/v3/pkg/oci"
"github.com/sigstore/cosign/v3/pkg/oci/empty"
)
// ImageIndex returns an oci.SignedImageIndex form of the v1.ImageIndex with
// no signatures.
func ImageIndex(i v1.ImageIndex) oci.SignedImageIndex {
return &index{
v1Index: i,
}
}
type v1Index v1.ImageIndex
type index struct {
v1Index
}
var _ oci.SignedImageIndex = (*index)(nil)
// SignedImage implements oci.SignedImageIndex
func (ii *index) SignedImage(h v1.Hash) (oci.SignedImage, error) {
i, err := ii.Image(h)
if err != nil {
return nil, err
}
return Image(i), nil
}
// SignedImageIndex implements oci.SignedImageIndex
func (ii *index) SignedImageIndex(h v1.Hash) (oci.SignedImageIndex, error) {
i, err := ii.ImageIndex(h)
if err != nil {
return nil, err
}
return ImageIndex(i), nil
}
// Signatures implements oci.SignedImageIndex
func (*index) Signatures() (oci.Signatures, error) {
return empty.Signatures(), nil
}
// Attestations implements oci.SignedImageIndex
func (*index) Attestations() (oci.Signatures, error) {
return empty.Signatures(), nil
}
// Attestations implements oci.SignedImage
func (*index) Attachment(name string) (oci.File, error) { //nolint: revive
return nil, errors.New("unimplemented")
}
//
// Copyright 2021 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 static
import (
"io"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/empty"
"github.com/google/go-containerregistry/pkg/v1/mutate"
"github.com/google/go-containerregistry/pkg/v1/types"
payloadsize "github.com/sigstore/cosign/v3/internal/pkg/cosign/payload/size"
"github.com/sigstore/cosign/v3/internal/pkg/now"
"github.com/sigstore/cosign/v3/pkg/oci"
"github.com/sigstore/cosign/v3/pkg/oci/signed"
)
// NewFile constructs a new v1.Image with the provided payload.
func NewFile(payload []byte, opts ...Option) (oci.File, error) {
o, err := makeOptions(opts...)
if err != nil {
return nil, err
}
base := mutate.MediaType(empty.Image, types.OCIManifestSchema1)
base = mutate.ConfigMediaType(base, o.ConfigMediaType)
layer := &staticLayer{
b: payload,
opts: o,
}
img, err := mutate.Append(base, mutate.Addendum{
Layer: layer,
})
if err != nil {
return nil, err
}
// Add annotations from options
img = mutate.Annotations(img, o.Annotations).(v1.Image)
if o.RecordCreationTimestamp {
t, err := now.Now()
if err != nil {
return nil, err
}
// Set the Created date to time of execution
img, err = mutate.CreatedAt(img, v1.Time{Time: t})
if err != nil {
return nil, err
}
}
return &file{
SignedImage: signed.Image(img),
layer: layer,
}, nil
}
type file struct {
oci.SignedImage
layer v1.Layer
}
var _ oci.File = (*file)(nil)
// FileMediaType implements oci.File
func (f *file) FileMediaType() (types.MediaType, error) {
return f.layer.MediaType()
}
// Payload implements oci.File
func (f *file) Payload() ([]byte, error) {
size, err := f.layer.Size()
if err != nil {
return nil, err
}
err = payloadsize.CheckSize(uint64(size))
if err != nil {
return nil, err
}
rc, err := f.layer.Uncompressed()
if err != nil {
return nil, err
}
defer rc.Close()
return io.ReadAll(rc)
}
//
// Copyright 2021 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 static
import (
"encoding/json"
"github.com/google/go-containerregistry/pkg/v1/types"
"github.com/sigstore/cosign/v3/pkg/cosign/bundle"
ctypes "github.com/sigstore/cosign/v3/pkg/types"
)
// Option is a functional option for customizing static signatures.
type Option func(*options)
type options struct {
LayerMediaType types.MediaType
ConfigMediaType types.MediaType
Bundle *bundle.RekorBundle
RFC3161Timestamp *bundle.RFC3161Timestamp
Cert []byte
Chain []byte
Annotations map[string]string
RecordCreationTimestamp bool
}
func makeOptions(opts ...Option) (*options, error) {
o := &options{
LayerMediaType: ctypes.SimpleSigningMediaType,
ConfigMediaType: types.OCIConfigJSON,
Annotations: make(map[string]string),
}
for _, opt := range opts {
opt(o)
}
if o.Cert != nil {
o.Annotations[CertificateAnnotationKey] = string(o.Cert)
o.Annotations[ChainAnnotationKey] = string(o.Chain)
}
if o.Bundle != nil {
b, err := json.Marshal(o.Bundle)
if err != nil {
return nil, err
}
o.Annotations[BundleAnnotationKey] = string(b)
}
if o.RFC3161Timestamp != nil {
b, err := json.Marshal(o.RFC3161Timestamp)
if err != nil {
return nil, err
}
o.Annotations[RFC3161TimestampAnnotationKey] = string(b)
}
return o, nil
}
// WithLayerMediaType sets the media type of the signature.
func WithLayerMediaType(mt types.MediaType) Option {
return func(o *options) {
o.LayerMediaType = mt
}
}
// WithConfigMediaType sets the media type of the signature.
func WithConfigMediaType(mt types.MediaType) Option {
return func(o *options) {
o.ConfigMediaType = mt
}
}
// WithAnnotations sets the annotations that will be associated.
func WithAnnotations(ann map[string]string) Option {
return func(o *options) {
o.Annotations = ann
}
}
// WithBundle sets the bundle to attach to the signature
func WithBundle(b *bundle.RekorBundle) Option {
return func(o *options) {
o.Bundle = b
}
}
// WithRFC3161Timestamp sets the time-stamping bundle to attach to the signature
func WithRFC3161Timestamp(b *bundle.RFC3161Timestamp) Option {
return func(o *options) {
o.RFC3161Timestamp = b
}
}
// WithCertChain sets the certificate chain for this signature.
func WithCertChain(cert, chain []byte) Option {
return func(o *options) {
o.Cert = cert
o.Chain = chain
}
}
// WithRecordCreationTimestamp sets the feature flag to honor the creation timestamp to time of running
func WithRecordCreationTimestamp(rct bool) Option {
return func(o *options) {
o.RecordCreationTimestamp = rct
}
}
//
// Copyright 2021 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 static
import (
"bytes"
"crypto/x509"
"encoding/base64"
"io"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/types"
"github.com/sigstore/cosign/v3/pkg/cosign/bundle"
"github.com/sigstore/cosign/v3/pkg/oci"
"github.com/sigstore/sigstore/pkg/cryptoutils"
)
const (
SignatureAnnotationKey = "dev.cosignproject.cosign/signature"
CertificateAnnotationKey = "dev.sigstore.cosign/certificate"
ChainAnnotationKey = "dev.sigstore.cosign/chain"
BundleAnnotationKey = "dev.sigstore.cosign/bundle"
RFC3161TimestampAnnotationKey = "dev.sigstore.cosign/rfc3161timestamp"
)
// NewSignature constructs a new oci.Signature from the provided options.
func NewSignature(payload []byte, b64sig string, opts ...Option) (oci.Signature, error) {
o, err := makeOptions(opts...)
if err != nil {
return nil, err
}
return &staticLayer{
b: payload,
b64sig: b64sig,
opts: o,
}, nil
}
// NewAttestation constructs a new oci.Signature from the provided options.
// Since Attestation is treated just like a Signature but the actual signature
// is baked into the payload, the Signature does not actually have
// the Base64Signature.
func NewAttestation(payload []byte, opts ...Option) (oci.Signature, error) {
return NewSignature(payload, "", opts...)
}
// Copy constructs a new oci.Signature from the provided one.
func Copy(sig oci.Signature) (oci.Signature, error) {
payload, err := sig.Payload()
if err != nil {
return nil, err
}
b64sig, err := sig.Base64Signature()
if err != nil {
return nil, err
}
var opts []Option
mt, err := sig.MediaType()
if err != nil {
return nil, err
}
opts = append(opts, WithLayerMediaType(mt))
ann, err := sig.Annotations()
if err != nil {
return nil, err
}
opts = append(opts, WithAnnotations(ann))
bundle, err := sig.Bundle()
if err != nil {
return nil, err
}
opts = append(opts, WithBundle(bundle))
rfc3161Timestamp, err := sig.RFC3161Timestamp()
if err != nil {
return nil, err
}
opts = append(opts, WithRFC3161Timestamp(rfc3161Timestamp))
cert, err := sig.Cert()
if err != nil {
return nil, err
}
if cert != nil {
rawCert, err := cryptoutils.MarshalCertificateToPEM(cert)
if err != nil {
return nil, err
}
chain, err := sig.Chain()
if err != nil {
return nil, err
}
rawChain, err := cryptoutils.MarshalCertificatesToPEM(chain)
if err != nil {
return nil, err
}
opts = append(opts, WithCertChain(rawCert, rawChain))
}
return NewSignature(payload, b64sig, opts...)
}
type staticLayer struct {
b []byte
b64sig string
opts *options
}
var _ v1.Layer = (*staticLayer)(nil)
var _ oci.Signature = (*staticLayer)(nil)
// Annotations implements oci.Signature
func (l *staticLayer) Annotations() (map[string]string, error) {
m := make(map[string]string, len(l.opts.Annotations)+1)
for k, v := range l.opts.Annotations {
m[k] = v
}
m[SignatureAnnotationKey] = l.b64sig
return m, nil
}
// Payload implements oci.Signature
func (l *staticLayer) Payload() ([]byte, error) {
return l.b, nil
}
// Signature implements oci.Signature
func (l *staticLayer) Signature() ([]byte, error) {
b64sig, err := l.Base64Signature()
if err != nil {
return nil, err
}
return base64.StdEncoding.DecodeString(b64sig)
}
// Base64Signature implements oci.Signature
func (l *staticLayer) Base64Signature() (string, error) {
return l.b64sig, nil
}
// Cert implements oci.Signature
func (l *staticLayer) Cert() (*x509.Certificate, error) {
certs, err := cryptoutils.LoadCertificatesFromPEM(bytes.NewReader(l.opts.Cert))
if err != nil {
return nil, err
}
if len(certs) == 0 {
return nil, nil
}
return certs[0], nil
}
// Chain implements oci.Signature
func (l *staticLayer) Chain() ([]*x509.Certificate, error) {
certs, err := cryptoutils.LoadCertificatesFromPEM(bytes.NewReader(l.opts.Chain))
if err != nil {
return nil, err
}
return certs, nil
}
// Bundle implements oci.Signature
func (l *staticLayer) Bundle() (*bundle.RekorBundle, error) {
return l.opts.Bundle, nil
}
// RFC3161Timestamp implements oci.Signature
func (l *staticLayer) RFC3161Timestamp() (*bundle.RFC3161Timestamp, error) {
return l.opts.RFC3161Timestamp, nil
}
// Digest implements v1.Layer
func (l *staticLayer) Digest() (v1.Hash, error) {
h, _, err := v1.SHA256(bytes.NewReader(l.b))
return h, err
}
// DiffID implements v1.Layer
func (l *staticLayer) DiffID() (v1.Hash, error) {
h, _, err := v1.SHA256(bytes.NewReader(l.b))
return h, err
}
// Compressed implements v1.Layer
func (l *staticLayer) Compressed() (io.ReadCloser, error) {
return io.NopCloser(bytes.NewReader(l.b)), nil
}
// Uncompressed implements v1.Layer
func (l *staticLayer) Uncompressed() (io.ReadCloser, error) {
return io.NopCloser(bytes.NewReader(l.b)), nil
}
// Size implements v1.Layer
func (l *staticLayer) Size() (int64, error) {
return int64(len(l.b)), nil
}
// MediaType implements v1.Layer
func (l *staticLayer) MediaType() (types.MediaType, error) {
return l.opts.LayerMediaType, nil
}
//
// 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 policy
import (
"context"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"github.com/in-toto/in-toto-golang/in_toto"
"github.com/sigstore/cosign/v3/cmd/cosign/cli/options"
"github.com/sigstore/cosign/v3/pkg/cosign/attestation"
"github.com/sigstore/cosign/v3/pkg/oci"
)
// PayloadProvider is a subset of oci.Signature that only provides the
// Payload() method.
type PayloadProvider interface {
// Payload fetches the opaque data that is being signed.
// This will always return data when there is no error.
Payload() ([]byte, error)
}
// Assert that oci.Signature implements PayloadProvider
var _ PayloadProvider = (oci.Signature)(nil)
// AttestationToPayloadJSON takes in a verified Attestation (oci.Signature) and
// marshals it into a JSON depending on the payload that's then consumable
// by policy engine like cue, rego, etc.
//
// Anything fed here must have been validated with either
// `VerifyLocalImageAttestations` or `VerifyImageAttestations`
//
// If there's no error, and payload is empty means the predicateType did not
// match the attestation.
// Returns the attestation type (PredicateType) if the payload was decoded
// before the error happened, or in the case the predicateType that was
// requested does not match. This is useful for callers to be able to provide
// better error messages. For example, if there's a typo in the predicateType,
// or the predicateType is not the one they are looking for. Without returning
// this, it's hard for users to know which attestations/predicateTypes were
// inspected.
func AttestationToPayloadJSON(_ context.Context, predicateType string, verifiedAttestation PayloadProvider) ([]byte, string, error) {
if predicateType == "" {
return nil, "", errors.New("missing predicate type")
}
predicateURI, ok := options.PredicateTypeMap[predicateType]
if !ok {
// Not a custom one, use it as is.
predicateURI = predicateType
}
var payloadData map[string]interface{}
p, err := verifiedAttestation.Payload()
if err != nil {
return nil, "", fmt.Errorf("getting payload: %w", err)
}
err = json.Unmarshal(p, &payloadData)
if err != nil {
return nil, "", fmt.Errorf("unmarshaling payload data")
}
var decodedPayload []byte
if val, ok := payloadData["payload"]; ok {
decodedPayload, err = base64.StdEncoding.DecodeString(val.(string))
if err != nil {
return nil, "", fmt.Errorf("decoding payload: %w", err)
}
} else {
return nil, "", fmt.Errorf("could not find payload in payload data")
}
// Only apply the policy against the requested predicate type
var statement in_toto.Statement
if err := json.Unmarshal(decodedPayload, &statement); err != nil {
return nil, "", fmt.Errorf("unmarshal in-toto statement: %w", err)
}
if statement.PredicateType != predicateURI {
// This is not the predicate we're looking for, so skip it.
return nil, statement.PredicateType, nil
}
// NB: In many (all?) of these cases, we could just return the
// 'json.Marshal', but we check for errors here to decorate them
// with more meaningful error message.
var payload []byte
switch predicateType {
case options.PredicateCustom:
payload, err = json.Marshal(statement)
if err != nil {
return nil, statement.PredicateType, fmt.Errorf("generating CosignStatement: %w", err)
}
case options.PredicateLink:
var linkStatement in_toto.LinkStatement
if err := json.Unmarshal(decodedPayload, &linkStatement); err != nil {
return nil, statement.PredicateType, fmt.Errorf("unmarshaling LinkStatement: %w", err)
}
payload, err = json.Marshal(linkStatement)
if err != nil {
return nil, statement.PredicateType, fmt.Errorf("marshaling LinkStatement: %w", err)
}
case options.PredicateSLSA:
var slsaProvenanceStatement in_toto.ProvenanceStatementSLSA02
if err := json.Unmarshal(decodedPayload, &slsaProvenanceStatement); err != nil {
return nil, statement.PredicateType, fmt.Errorf("unmarshaling ProvenanceStatementSLSA02): %w", err)
}
payload, err = json.Marshal(slsaProvenanceStatement)
if err != nil {
return nil, statement.PredicateType, fmt.Errorf("marshaling ProvenanceStatementSLSA02: %w", err)
}
case options.PredicateSPDX, options.PredicateSPDXJSON:
var spdxStatement in_toto.SPDXStatement
if err := json.Unmarshal(decodedPayload, &spdxStatement); err != nil {
return nil, statement.PredicateType, fmt.Errorf("unmarshaling SPDXStatement: %w", err)
}
payload, err = json.Marshal(spdxStatement)
if err != nil {
return nil, statement.PredicateType, fmt.Errorf("marshaling SPDXStatement: %w", err)
}
case options.PredicateCycloneDX:
var cyclonedxStatement in_toto.CycloneDXStatement
if err := json.Unmarshal(decodedPayload, &cyclonedxStatement); err != nil {
return nil, statement.PredicateType, fmt.Errorf("unmarshaling CycloneDXStatement: %w", err)
}
payload, err = json.Marshal(cyclonedxStatement)
if err != nil {
return nil, statement.PredicateType, fmt.Errorf("marshaling CycloneDXStatement: %w", err)
}
case options.PredicateVuln:
var vulnStatement attestation.CosignVulnStatement
if err := json.Unmarshal(decodedPayload, &vulnStatement); err != nil {
return nil, statement.PredicateType, fmt.Errorf("unmarshaling CosignVulnStatement: %w", err)
}
payload, err = json.Marshal(vulnStatement)
if err != nil {
return nil, statement.PredicateType, fmt.Errorf("marshaling CosignVulnStatement: %w", err)
}
default:
// Valid URI type reaches here.
payload, err = json.Marshal(statement)
if err != nil {
return nil, statement.PredicateType, fmt.Errorf("generating Statement: %w", err)
}
}
return payload, statement.PredicateType, nil
}
// 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 policy
type EvaluationFailure struct {
err error
}
func (e *EvaluationFailure) Error() string {
return e.err.Error()
}
func (e *EvaluationFailure) Unwrap() error {
return e.err
}
//
// 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 policy
import (
"context"
"fmt"
"cuelang.org/go/cue/cuecontext"
"github.com/sigstore/cosign/v3/pkg/cosign/rego"
)
// EvaluatePolicyAgainstJson is used to run a policy engine against JSON bytes.
// These bytes can be for example Attestations, or ClusterImagePolicy result
// types.
// name - which attestation are we evaluating
// policyType - cue|rego
// policyBody - String representing either cue or rego language
// jsonBytes - Bytes to evaluate against the policyBody in the given language
func EvaluatePolicyAgainstJSON(ctx context.Context, name, policyType string, policyBody string, jsonBytes []byte) (warnings error, errors error) {
switch policyType {
case "cue":
cueValidationErr := evaluateCue(ctx, jsonBytes, policyBody)
if cueValidationErr != nil {
return nil, &EvaluationFailure{
fmt.Errorf("failed evaluating cue policy for %s: %w", name, cueValidationErr),
}
}
case "rego":
regoValidationWarn, regoValidationErr := evaluateRego(ctx, jsonBytes, policyBody)
if regoValidationErr != nil {
return regoValidationWarn, &EvaluationFailure{
fmt.Errorf("failed evaluating rego policy for type %s: %w", name, regoValidationErr),
}
}
// It is possible to return warning messages when the policy is compliant
return regoValidationWarn, regoValidationErr
default:
return nil, fmt.Errorf("sorry Type %s is not supported yet", policyType)
}
return nil, nil
}
// evaluateCue evaluates a cue policy `evaluator` against `attestation`
func evaluateCue(_ context.Context, attestation []byte, evaluator string) error {
cueCtx := cuecontext.New()
cueEvaluator := cueCtx.CompileString(evaluator)
if cueEvaluator.Err() != nil {
return fmt.Errorf("failed to compile the cue policy with error: %w", cueEvaluator.Err())
}
cueAtt := cueCtx.CompileBytes(attestation)
if cueAtt.Err() != nil {
return fmt.Errorf("failed to compile the attestation data with error: %w", cueAtt.Err())
}
result := cueEvaluator.Unify(cueAtt)
if err := result.Validate(); err != nil {
return fmt.Errorf("failed to evaluate the policy with error: %w", err)
}
return nil
}
// evaluateRego evaluates a rego policy `evaluator` against `attestation`
func evaluateRego(_ context.Context, attestation []byte, evaluator string) (warnings error, errors error) {
return rego.ValidateJSONWithModuleInput(attestation, evaluator)
}
//
// Copyright 2021 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 signature
import (
_ "crypto/sha256" // for `crypto.SHA256`
"fmt"
"strings"
)
type AnnotationsMap struct {
Annotations map[string]interface{}
}
func (a *AnnotationsMap) Set(s string) error {
if a.Annotations == nil {
a.Annotations = map[string]interface{}{}
}
kvp := strings.SplitN(s, "=", 2)
if len(kvp) != 2 {
return fmt.Errorf("invalid flag: %s, expected key=value", s)
}
a.Annotations[kvp[0]] = kvp[1]
return nil
}
func (a *AnnotationsMap) String() string {
s := []string{}
for k, v := range a.Annotations {
s = append(s, fmt.Sprintf("%s=%s", k, v))
}
return strings.Join(s, ",")
}
// Copyright 2021 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 signature
import (
"context"
"crypto"
"errors"
"fmt"
"strings"
"github.com/sigstore/cosign/v3/pkg/blob"
"github.com/sigstore/cosign/v3/pkg/cosign"
"github.com/sigstore/cosign/v3/pkg/cosign/git"
"github.com/sigstore/cosign/v3/pkg/cosign/git/gitlab"
"github.com/sigstore/cosign/v3/pkg/cosign/kubernetes"
"github.com/sigstore/cosign/v3/pkg/cosign/pkcs11key"
"github.com/sigstore/sigstore/pkg/cryptoutils"
"github.com/sigstore/sigstore/pkg/signature"
"github.com/sigstore/sigstore/pkg/signature/kms"
)
// LoadPublicKey is a wrapper for VerifierForKeyRef, hardcoding SHA256 as the hash algorithm
func LoadPublicKey(ctx context.Context, keyRef string) (verifier signature.Verifier, err error) {
return VerifierForKeyRef(ctx, keyRef, crypto.SHA256)
}
// VerifierForKeyRef parses the given keyRef, loads the key and returns an appropriate
// verifier using the provided hash algorithm
func VerifierForKeyRef(ctx context.Context, keyRef string, hashAlgorithm crypto.Hash) (verifier signature.Verifier, err error) {
// The key could be plaintext, in a file, at a URL, or in KMS.
var perr *kms.ProviderNotFoundError
kmsKey, err := kms.Get(ctx, keyRef, hashAlgorithm)
switch {
case err == nil:
// KMS specified
return kmsKey, nil
case errors.As(err, &perr):
// We can ignore ProviderNotFoundError; that just means the keyRef
// didn't match any of the KMS schemes.
default:
// But other errors indicate something more insidious; pass those
// through.
return nil, err
}
raw, err := blob.LoadFileOrURL(keyRef)
if err != nil {
return nil, err
}
// PEM encoded file.
pubKey, err := cryptoutils.UnmarshalPEMToPublicKey(raw)
if err != nil {
return nil, fmt.Errorf("pem to public key: %w", err)
}
return signature.LoadVerifier(pubKey, hashAlgorithm)
}
func loadKey(keyPath string, pf cosign.PassFunc, defaultLoadOptions *[]signature.LoadOption) (signature.SignerVerifier, error) {
kb, err := blob.LoadFileOrURL(keyPath)
if err != nil {
return nil, err
}
pass := []byte{}
if pf != nil {
pass, err = pf(false)
if err != nil {
return nil, err
}
}
return cosign.LoadPrivateKey(kb, pass, defaultLoadOptions)
}
// LoadPublicKeyRaw loads a verifier from a PEM-encoded public key
func LoadPublicKeyRaw(raw []byte, hashAlgorithm crypto.Hash) (signature.Verifier, error) {
pub, err := cryptoutils.UnmarshalPEMToPublicKey(raw)
if err != nil {
return nil, err
}
return signature.LoadVerifier(pub, hashAlgorithm)
}
func SignerFromKeyRef(ctx context.Context, keyRef string, pf cosign.PassFunc) (signature.Signer, error) {
return SignerVerifierFromKeyRef(ctx, keyRef, pf, nil)
}
func SignerVerifierFromKeyRef(ctx context.Context, keyRef string, pf cosign.PassFunc, defaultLoadOptions *[]signature.LoadOption) (signature.SignerVerifier, error) {
switch {
case strings.HasPrefix(keyRef, pkcs11key.ReferenceScheme):
pkcs11UriConfig := pkcs11key.NewPkcs11UriConfig()
err := pkcs11UriConfig.Parse(keyRef)
if err != nil {
return nil, fmt.Errorf("parsing pkcs11 uri: %w", err)
}
// Since we'll be signing, we need to set askForPinIsNeeded to true
// because we need access to the private key.
sk, err := pkcs11key.GetKeyWithURIConfig(pkcs11UriConfig, true)
if err != nil {
return nil, fmt.Errorf("opening pkcs11 token key: %w", err)
}
sv, err := sk.SignerVerifier()
if err != nil {
return nil, fmt.Errorf("initializing pkcs11 token signer verifier: %w", err)
}
return sv, nil
case strings.HasPrefix(keyRef, kubernetes.KeyReference):
s, err := kubernetes.GetKeyPairSecret(ctx, keyRef)
if err != nil {
return nil, err
}
if len(s.Data) > 0 {
return cosign.LoadPrivateKey(s.Data["cosign.key"], s.Data["cosign.password"], defaultLoadOptions)
}
case strings.HasPrefix(keyRef, gitlab.ReferenceScheme):
split := strings.Split(keyRef, "://")
if len(split) < 2 {
return nil, errors.New("could not parse scheme, use <scheme>://<ref> format")
}
provider, targetRef := split[0], split[1]
pk, err := git.GetProvider(provider).GetSecret(ctx, targetRef, "COSIGN_PRIVATE_KEY")
if err != nil {
return nil, err
}
pass, err := git.GetProvider(provider).GetSecret(ctx, targetRef, "COSIGN_PASSWORD")
if err != nil {
return nil, err
}
return cosign.LoadPrivateKey([]byte(pk), []byte(pass), defaultLoadOptions)
}
if strings.Contains(keyRef, "://") {
sv, err := kms.Get(ctx, keyRef, crypto.SHA256)
if err == nil {
return sv, nil
}
var e *kms.ProviderNotFoundError
if !errors.As(err, &e) {
return nil, fmt.Errorf("kms get: %w", err)
}
// ProviderNotFoundError is okay; loadKey handles other URL schemes
}
return loadKey(keyRef, pf, defaultLoadOptions)
}
func PublicKeyFromKeyRef(ctx context.Context, keyRef string) (signature.Verifier, error) {
return PublicKeyFromKeyRefWithHashAlgo(ctx, keyRef, crypto.SHA256)
}
func PublicKeyFromKeyRefWithHashAlgo(ctx context.Context, keyRef string, hashAlgorithm crypto.Hash) (signature.Verifier, error) {
if strings.HasPrefix(keyRef, kubernetes.KeyReference) {
s, err := kubernetes.GetKeyPairSecret(ctx, keyRef)
if err != nil {
return nil, err
}
if len(s.Data) > 0 {
return LoadPublicKeyRaw(s.Data["cosign.pub"], hashAlgorithm)
}
}
if strings.HasPrefix(keyRef, pkcs11key.ReferenceScheme) {
pkcs11UriConfig := pkcs11key.NewPkcs11UriConfig()
err := pkcs11UriConfig.Parse(keyRef)
if err != nil {
return nil, fmt.Errorf("parsing pkcs11 uri): %w", err)
}
// Since we'll be verifying a signature, we do not need to set askForPinIsNeeded to true
// because we only need access to the public key.
sk, err := pkcs11key.GetKeyWithURIConfig(pkcs11UriConfig, false)
if err != nil {
return nil, fmt.Errorf("opening pkcs11 token key: %w", err)
}
v, err := sk.Verifier()
if err != nil {
return nil, fmt.Errorf("initializing pkcs11 token verifier: %w", err)
}
return v, nil
} else if strings.HasPrefix(keyRef, gitlab.ReferenceScheme) {
split := strings.Split(keyRef, "://")
if len(split) < 2 {
return nil, errors.New("could not parse scheme, use <scheme>://<ref> format")
}
provider, targetRef := split[0], split[1]
pubKey, err := git.GetProvider(provider).GetSecret(ctx, targetRef, "COSIGN_PUBLIC_KEY")
if err != nil {
return nil, err
}
if len(pubKey) > 0 {
return LoadPublicKeyRaw([]byte(pubKey), hashAlgorithm)
}
}
return VerifierForKeyRef(ctx, keyRef, hashAlgorithm)
}
func PublicKeyPem(key signature.PublicKeyProvider, pkOpts ...signature.PublicKeyOption) ([]byte, error) {
pub, err := key.PublicKey(pkOpts...)
if err != nil {
return nil, err
}
return cryptoutils.MarshalPublicKeyToPEM(pub)
}