diff --git a/cmd/cosign/cli/options/key.go b/cmd/cosign/cli/options/key.go index b62dc65fefd..66fab5523d3 100644 --- a/cmd/cosign/cli/options/key.go +++ b/cmd/cosign/cli/options/key.go @@ -72,4 +72,8 @@ type KeyOpts struct { // By default, Ed25519ph is used for ed25519 keys and RSA-PKCS1v15 is used // for RSA keys. DefaultLoadOptions *[]signature.LoadOption + + // SigningAlgorithm is the AlgorithmDetails string representation used to + // sign/hash the payload. + SigningAlgorithm string } diff --git a/cmd/cosign/cli/options/signblob.go b/cmd/cosign/cli/options/signblob.go index 4d4b505cfb2..d40cfa783cf 100644 --- a/cmd/cosign/cli/options/signblob.go +++ b/cmd/cosign/cli/options/signblob.go @@ -16,6 +16,12 @@ 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" ) @@ -43,6 +49,7 @@ type SignBlobOptions struct { TSAServerURL string RFC3161TimestampPath string IssueCertificate bool + SigningAlgorithm string UseSigningConfig bool SigningConfigPath string @@ -127,4 +134,9 @@ func (o *SignBlobOptions) AddFlags(cmd *cobra.Command) { 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) } diff --git a/cmd/cosign/cli/sign/sign_blob.go b/cmd/cosign/cli/sign/sign_blob.go index a3e462a1bae..622fedf4f26 100644 --- a/cmd/cosign/cli/sign/sign_blob.go +++ b/cmd/cosign/cli/sign/sign_blob.go @@ -112,7 +112,7 @@ func SignBlobCmd(ro *options.RootOptions, ko options.KeyOpts, payloadPath string } defer closeSV() - hashFunction, err := getHashFunction(sv, ko.DefaultLoadOptions) + hashFunction, err := getHashFunction(sv, ko) if err != nil { return nil, err } @@ -277,18 +277,31 @@ func extractCertificate(ctx context.Context, sv *signcommon.SignerVerifier) ([]b return nil, nil } -func getHashFunction(sv *signcommon.SignerVerifier, defaultLoadOptions *[]signature.LoadOption) (crypto.Hash, error) { - pubKey, err := sv.PublicKey() - if err != nil { - return crypto.Hash(0), fmt.Errorf("error getting public key: %w", err) - } +func getHashFunction(sv *signcommon.SignerVerifier, ko options.KeyOpts) (crypto.Hash, error) { + if ko.Sk || ko.KeyRef != "" { + pubKey, err := sv.PublicKey() + if err != nil { + return crypto.Hash(0), fmt.Errorf("error getting public key: %w", err) + } + + defaultLoadOptions := cosign.GetDefaultLoadOptions(ko.DefaultLoadOptions) - defaultLoadOptions = cosign.GetDefaultLoadOptions(defaultLoadOptions) + // TODO: Ideally the SignerVerifier should have a method to get the hash function + algo, err := signature.GetDefaultAlgorithmDetails(pubKey, *defaultLoadOptions...) + if err != nil { + return crypto.Hash(0), fmt.Errorf("error getting default algorithm details: %w", err) + } + return algo.GetHashType(), nil + } - // TODO: Ideally the SignerVerifier should have a method to get the hash function - algo, err := signature.GetDefaultAlgorithmDetails(pubKey, *defaultLoadOptions...) + // New key was generated, using the signing algorithm specified by the user + keyDetails, err := signcommon.ParseSignatureAlgorithmFlag(ko.SigningAlgorithm) + if err != nil { + return crypto.Hash(0), fmt.Errorf("parsing signature algorithm: %w", err) + } + algo, err := signature.GetAlgorithmDetails(keyDetails) if err != nil { - return crypto.Hash(0), fmt.Errorf("error getting default algorithm details: %w", err) + return crypto.Hash(0), fmt.Errorf("getting algorithm details: %w", err) } return algo.GetHashType(), nil } diff --git a/cmd/cosign/cli/signblob.go b/cmd/cosign/cli/signblob.go index 5ad293030c1..30cb298280b 100644 --- a/cmd/cosign/cli/signblob.go +++ b/cmd/cosign/cli/signblob.go @@ -19,6 +19,7 @@ import ( "context" "fmt" "os" + "strings" "github.com/sigstore/cosign/v3/cmd/cosign/cli/generate" "github.com/sigstore/cosign/v3/cmd/cosign/cli/options" @@ -66,6 +67,21 @@ func SignBlob() *cobra.Command { if options.NOf(o.Key, o.SecurityKey.Use) > 1 { return &options.KeyParseError{} } + + // Check if the algorithm is in the list of supported algorithms + supportedAlgorithms := cosign.GetSupportedAlgorithms() + isValid := false + for _, algo := range supportedAlgorithms { + if algo == o.SigningAlgorithm { + isValid = true + break + } + } + if !isValid { + return fmt.Errorf("invalid signing algorithm: %s. Supported algorithms are: %s", + o.SigningAlgorithm, strings.Join(supportedAlgorithms, ", ")) + } + return nil }, RunE: func(_ *cobra.Command, args []string) error { @@ -99,6 +115,7 @@ func SignBlob() *cobra.Command { TSAServerURL: o.TSAServerURL, RFC3161TimestampPath: o.RFC3161TimestampPath, IssueCertificateForExistingKey: o.IssueCertificate, + SigningAlgorithm: o.SigningAlgorithm, } // If a signing config is used, then service URLs cannot be specified if (o.UseSigningConfig || o.SigningConfigPath != "") && diff --git a/cmd/cosign/cli/signcommon/common.go b/cmd/cosign/cli/signcommon/common.go index 6b1690693b2..b6b09267523 100644 --- a/cmd/cosign/cli/signcommon/common.go +++ b/cmd/cosign/cli/signcommon/common.go @@ -17,7 +17,6 @@ package signcommon import ( "bytes" "context" - "crypto" "crypto/x509" "encoding/json" "encoding/pem" @@ -44,6 +43,7 @@ import ( ociremote "github.com/sigstore/cosign/v3/pkg/oci/remote" sigs "github.com/sigstore/cosign/v3/pkg/signature" "github.com/sigstore/cosign/v3/pkg/types" + pb_go_v1 "github.com/sigstore/protobuf-specs/gen/pb-go/common/v1" rekorclient "github.com/sigstore/rekor/pkg/generated/client" "github.com/sigstore/rekor/pkg/generated/models" "github.com/sigstore/sigstore-go/pkg/root" @@ -82,6 +82,17 @@ func (c *SignerVerifier) Bytes(ctx context.Context) ([]byte, error) { return pemBytes, nil } +func getEphemeralKeypairOptions(signingAlgorithm string) (*sign.EphemeralKeypairOptions, error) { + keyDetails, err := ParseSignatureAlgorithmFlag(signingAlgorithm) + if err != nil { + return nil, fmt.Errorf("parsing signature algorithm: %w", err) + } + + return &sign.EphemeralKeypairOptions{ + Algorithm: keyDetails, + }, nil +} + // GetKeypairAndToken creates a keypair object from provided key or cert flags or generates an ephemeral key. // For an ephemeral key, it also uses the key to fetch an OIDC token, the pair of which are later used to get a Fulcio cert. func GetKeypairAndToken(ctx context.Context, ko options.KeyOpts, cert, certChain string) (sign.Keypair, string, error) { @@ -101,7 +112,11 @@ func GetKeypairAndToken(ctx context.Context, ko options.KeyOpts, cert, certChain return nil, "", fmt.Errorf("creating signerverifier keypair: %w", err) } } else { - keypair, err = sign.NewEphemeralKeypair(nil) + ephemeralKeypairOptions, err := getEphemeralKeypairOptions(ko.SigningAlgorithm) + if err != nil { + return nil, "", fmt.Errorf("getting ephemeral keypair options: %w", err) + } + keypair, err = sign.NewEphemeralKeypair(ephemeralKeypairOptions) if err != nil { return nil, "", fmt.Errorf("generating keypair: %w", err) } @@ -231,7 +246,7 @@ func signerFromKeyOpts(ctx context.Context, certPath string, certChainPath strin default: genKey = true ui.Infof(ctx, "Generating ephemeral keys...") - sv, err = signerFromNewKey() + sv, err = signerFromNewKey(ko.SigningAlgorithm, ko.DefaultLoadOptions) } if err != nil { return nil, false, err @@ -386,12 +401,23 @@ func signerFromKeyRef(ctx context.Context, certPath, certChainPath, keyRef strin return certSigner, nil } -func signerFromNewKey() (*SignerVerifier, error) { - privKey, err := cosign.GeneratePrivateKey() +func signerFromNewKey(signingAlgorithm string, defaultLoadOptions *[]signature.LoadOption) (*SignerVerifier, error) { + keyDetails, err := ParseSignatureAlgorithmFlag(signingAlgorithm) + if err != nil { + return nil, fmt.Errorf("parsing signature algorithm: %w", err) + } + algo, err := signature.GetAlgorithmDetails(keyDetails) + if err != nil { + return nil, fmt.Errorf("getting algorithm details: %w", err) + } + + privKey, err := cosign.GeneratePrivateKeyWithAlgorithm(&algo) if err != nil { return nil, fmt.Errorf("generating cert: %w", err) } - sv, err := signature.LoadECDSASignerVerifier(privKey, crypto.SHA256) + + defaultLoadOptions = cosign.GetDefaultLoadOptions(defaultLoadOptions) + sv, err := signature.LoadSignerVerifierFromAlgorithmDetails(privKey, algo, *defaultLoadOptions...) if err != nil { return nil, err } @@ -581,3 +607,14 @@ func ParseOCIReference(ctx context.Context, refStr string, opts ...name.Option) } return ref, nil } + +func ParseSignatureAlgorithmFlag(signingAlgorithm string) (pb_go_v1.PublicKeyDetails, error) { + if signingAlgorithm == "" { + var err error + signingAlgorithm, err = signature.FormatSignatureAlgorithmFlag(pb_go_v1.PublicKeyDetails_PKIX_ECDSA_P256_SHA_256) + if err != nil { + return pb_go_v1.PublicKeyDetails_PUBLIC_KEY_DETAILS_UNSPECIFIED, fmt.Errorf("formatting signature algorithm: %w", err) + } + } + return signature.ParseSignatureAlgorithmFlag(signingAlgorithm) +} diff --git a/doc/cosign_sign-blob.md b/doc/cosign_sign-blob.md index 4135f3aac22..e7585000c19 100644 --- a/doc/cosign_sign-blob.md +++ b/doc/cosign_sign-blob.md @@ -57,6 +57,7 @@ cosign sign-blob [flags] --output-signature string write the signature to FILE --rekor-url string address of rekor STL server (default "https://rekor.sigstore.dev") --rfc3161-timestamp string write the RFC3161 timestamp to a file + --signing-algorithm string signing algorithm to use for signing/hashing (allowed ecdsa-sha2-256-nistp256, ecdsa-sha2-384-nistp384, ecdsa-sha2-512-nistp521, rsa-sign-pkcs1-2048-sha256, rsa-sign-pkcs1-3072-sha256, rsa-sign-pkcs1-4096-sha256) (default "ecdsa-sha2-256-nistp256") --signing-config string path to a signing config file. Must provide --bundle, which will output verification material in the new format --sk whether to use a hardware security key --slot string security key slot to use for generated key (default: signature) (authentication|signature|card-authentication|key-management) diff --git a/pkg/cosign/keys.go b/pkg/cosign/keys.go index 26d7ed49059..8c299af1c01 100644 --- a/pkg/cosign/keys.go +++ b/pkg/cosign/keys.go @@ -28,6 +28,7 @@ import ( "fmt" "os" "path/filepath" + "sort" "github.com/secure-systems-lab/go-securesystemslib/encrypted" "github.com/sigstore/cosign/v3/pkg/oci/static" @@ -50,6 +51,17 @@ const ( 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) @@ -297,3 +309,17 @@ func GetDefaultLoadOptions(defaultLoadOptions *[]signature.LoadOption) *[]signat } 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 +}