Skip to content

Commit 6fa7574

Browse files
committed
Support self-managed keys when signing with sigstore-go
This creates a wrapper around the Keypair interface when a SignerVerifier is provided for signing with KMS or any other provided keys. This also retains support for --issue-certificate to request a certificate for a managed key. Fixes #4327 Signed-off-by: Hayden <8418760+haydentherapper@users.noreply.github.com>
1 parent 5ad3dfe commit 6fa7574

5 files changed

Lines changed: 548 additions & 39 deletions

File tree

cmd/cosign/cli/attest/attest_blob.go

Lines changed: 37 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import (
3535
"github.com/sigstore/cosign/v2/cmd/cosign/cli/rekor"
3636
cosign_sign "github.com/sigstore/cosign/v2/cmd/cosign/cli/sign"
3737
"github.com/sigstore/cosign/v2/internal/auth"
38+
"github.com/sigstore/cosign/v2/internal/key"
3839
"github.com/sigstore/cosign/v2/internal/pkg/cosign/tsa"
3940
tsaclient "github.com/sigstore/cosign/v2/internal/pkg/cosign/tsa/client"
4041
"github.com/sigstore/cosign/v2/internal/ui"
@@ -157,30 +158,45 @@ func (c *AttestBlobCommand) Exec(ctx context.Context, artifactPath string) error
157158
}
158159

159160
if c.SigningConfig != nil {
160-
// TODO(#4327): Only ephemeral keys are currently supported
161-
// Need to add support for self-managed keys (e.g. PKCS11, KMS, on disk)
162-
// and determine if we want to store certificates for those as well.
161+
var keypair sign.Keypair
162+
var ephemeralKeypair bool
163+
var idToken string
164+
var err error
165+
163166
if c.Sk || c.Slot != "" || c.KeyRef != "" || c.CertPath != "" {
164-
return fmt.Errorf("using a signing config currently only supports signing with ephemeral keys and Fulcio")
165-
}
166-
keypair, err := sign.NewEphemeralKeypair(nil)
167-
if err != nil {
168-
return fmt.Errorf("generating keypair: %w", err)
167+
sv, err := cosign_sign.SignerFromKeyOpts(ctx, c.CertPath, c.CertChainPath, c.KeyOpts)
168+
if err != nil {
169+
return fmt.Errorf("getting signer: %w", err)
170+
}
171+
keypair, err = key.NewSignerVerifierKeypair(sv)
172+
if err != nil {
173+
return fmt.Errorf("creating signerverifier keypair: %w", err)
174+
}
175+
} else {
176+
keypair, err = sign.NewEphemeralKeypair(nil)
177+
if err != nil {
178+
return fmt.Errorf("generating keypair: %w", err)
179+
}
180+
ephemeralKeypair = true
169181
}
170-
idToken, err := auth.RetrieveIDToken(ctx, auth.IDTokenConfig{
171-
TokenOrPath: c.IDToken,
172-
DisableProviders: c.OIDCDisableProviders,
173-
Provider: c.OIDCProvider,
174-
AuthFlow: c.FulcioAuthFlow,
175-
SkipConfirm: c.SkipConfirmation,
176-
OIDCServices: c.SigningConfig.OIDCProviderURLs(),
177-
ClientID: c.OIDCClientID,
178-
ClientSecret: c.OIDCClientSecret,
179-
RedirectURL: c.OIDCRedirectURL,
180-
})
181-
if err != nil {
182-
return fmt.Errorf("retrieving ID token: %w", err)
182+
183+
if ephemeralKeypair || c.IssueCertificateForExistingKey {
184+
idToken, err = auth.RetrieveIDToken(ctx, auth.IDTokenConfig{
185+
TokenOrPath: c.IDToken,
186+
DisableProviders: c.OIDCDisableProviders,
187+
Provider: c.OIDCProvider,
188+
AuthFlow: c.FulcioAuthFlow,
189+
SkipConfirm: c.SkipConfirmation,
190+
OIDCServices: c.SigningConfig.OIDCProviderURLs(),
191+
ClientID: c.OIDCClientID,
192+
ClientSecret: c.OIDCClientSecret,
193+
RedirectURL: c.OIDCRedirectURL,
194+
})
195+
if err != nil {
196+
return fmt.Errorf("retrieving ID token: %w", err)
197+
}
183198
}
199+
184200
content := &sign.DSSEData{
185201
Data: payload,
186202
PayloadType: "application/vnd.in-toto+json",

cmd/cosign/cli/sign/sign_blob.go

Lines changed: 37 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import (
3131
"github.com/sigstore/cosign/v2/cmd/cosign/cli/options"
3232
"github.com/sigstore/cosign/v2/cmd/cosign/cli/rekor"
3333
"github.com/sigstore/cosign/v2/internal/auth"
34+
"github.com/sigstore/cosign/v2/internal/key"
3435
internal "github.com/sigstore/cosign/v2/internal/pkg/cosign"
3536
"github.com/sigstore/cosign/v2/internal/pkg/cosign/tsa"
3637
"github.com/sigstore/cosign/v2/internal/pkg/cosign/tsa/client"
@@ -65,27 +66,45 @@ func SignBlobCmd(ro *options.RootOptions, ko options.KeyOpts, payloadPath string
6566
}
6667

6768
if ko.SigningConfig != nil {
68-
// TODO(#4327): Only ephemeral keys are currently supported
69-
// Need to add support for self-managed keys (e.g. PKCS11, KMS, on disk)
70-
// and determine if we want to store certificates for those as well.
69+
var keypair sign.Keypair
70+
var ephemeralKeypair bool
71+
var idToken string
72+
var err error
73+
7174
if ko.Sk || ko.Slot != "" || ko.KeyRef != "" {
72-
return nil, fmt.Errorf("using a signing config currently only supports signing with ephemeral keys and Fulcio")
75+
sv, err := SignerFromKeyOpts(ctx, "", "", ko)
76+
if err != nil {
77+
return nil, fmt.Errorf("getting signer: %w", err)
78+
}
79+
keypair, err = key.NewSignerVerifierKeypair(sv)
80+
if err != nil {
81+
return nil, fmt.Errorf("creating signerverifier keypair: %w", err)
82+
}
83+
} else {
84+
keypair, err = sign.NewEphemeralKeypair(nil)
85+
if err != nil {
86+
return nil, fmt.Errorf("generating keypair: %w", err)
87+
}
88+
ephemeralKeypair = true
7389
}
74-
keypair, err := sign.NewEphemeralKeypair(nil)
75-
if err != nil {
76-
return nil, fmt.Errorf("generating keypair: %w", err)
90+
91+
if ephemeralKeypair || ko.IssueCertificateForExistingKey {
92+
idToken, err = auth.RetrieveIDToken(ctx, auth.IDTokenConfig{
93+
TokenOrPath: ko.IDToken,
94+
DisableProviders: ko.OIDCDisableProviders,
95+
Provider: ko.OIDCProvider,
96+
AuthFlow: ko.FulcioAuthFlow,
97+
SkipConfirm: ko.SkipConfirmation,
98+
OIDCServices: ko.SigningConfig.OIDCProviderURLs(),
99+
ClientID: ko.OIDCClientID,
100+
ClientSecret: ko.OIDCClientSecret,
101+
RedirectURL: ko.OIDCRedirectURL,
102+
})
103+
if err != nil {
104+
return nil, fmt.Errorf("retrieving ID token: %w", err)
105+
}
77106
}
78-
idToken, err := auth.RetrieveIDToken(ctx, auth.IDTokenConfig{
79-
TokenOrPath: ko.IDToken,
80-
DisableProviders: ko.OIDCDisableProviders,
81-
Provider: ko.OIDCProvider,
82-
AuthFlow: ko.FulcioAuthFlow,
83-
SkipConfirm: ko.SkipConfirmation,
84-
OIDCServices: ko.SigningConfig.OIDCProviderURLs(),
85-
ClientID: ko.OIDCClientID,
86-
ClientSecret: ko.OIDCClientSecret,
87-
RedirectURL: ko.OIDCRedirectURL,
88-
})
107+
89108
data, err := io.ReadAll(&payload)
90109
if err != nil {
91110
return nil, fmt.Errorf("reading payload: %w", err)

internal/key/svkeypair.go

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
// Copyright 2025 The Sigstore Authors.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package key
16+
17+
import (
18+
"bytes"
19+
"context"
20+
"crypto/ecdsa"
21+
"crypto/ed25519"
22+
"crypto/rsa"
23+
"crypto/sha256"
24+
"crypto/x509"
25+
"encoding/base64"
26+
"errors"
27+
"fmt"
28+
29+
protocommon "github.com/sigstore/protobuf-specs/gen/pb-go/common/v1"
30+
"github.com/sigstore/sigstore/pkg/cryptoutils"
31+
"github.com/sigstore/sigstore/pkg/signature"
32+
signatureoptions "github.com/sigstore/sigstore/pkg/signature/options"
33+
)
34+
35+
// SignerVerifierKeypair is a wrapper around a SignerVerifier that implements
36+
// sigstore-go's Keypair interface.
37+
type SignerVerifierKeypair struct {
38+
sv signature.SignerVerifier
39+
hint []byte
40+
keyAlg string
41+
sigAlg signature.AlgorithmDetails
42+
}
43+
44+
// NewSignerVerifierKeypair creates a new SignerVerifierKeypair from a SignerVerifier.
45+
func NewSignerVerifierKeypair(sv signature.SignerVerifier) (*SignerVerifierKeypair, error) {
46+
pubKey, err := sv.PublicKey()
47+
if err != nil {
48+
return nil, fmt.Errorf("getting public key: %w", err)
49+
}
50+
pubKeyBytes, err := x509.MarshalPKIXPublicKey(pubKey)
51+
if err != nil {
52+
return nil, fmt.Errorf("marshalling public key: %w", err)
53+
}
54+
hashedBytes := sha256.Sum256(pubKeyBytes)
55+
hint := []byte(base64.StdEncoding.EncodeToString(hashedBytes[:]))
56+
57+
var keyAlg string
58+
switch pubKey.(type) {
59+
case *ecdsa.PublicKey:
60+
keyAlg = "ECDSA"
61+
case *rsa.PublicKey:
62+
keyAlg = "RSA"
63+
case ed25519.PublicKey:
64+
keyAlg = "ED25519"
65+
default:
66+
return nil, errors.New("unsupported key type")
67+
}
68+
69+
// TODO: Ideally SignerVerifier would return its configured hash.
70+
algo, err := signature.GetDefaultAlgorithmDetails(pubKey)
71+
if err != nil {
72+
return nil, fmt.Errorf("getting default algorithm details: %w", err)
73+
}
74+
75+
return &SignerVerifierKeypair{
76+
sv: sv,
77+
hint: hint,
78+
keyAlg: keyAlg,
79+
sigAlg: algo,
80+
}, nil
81+
}
82+
83+
// GetHashAlgorithm returns the hash algorithm to generate the digest to be signed.
84+
func (k *SignerVerifierKeypair) GetHashAlgorithm() protocommon.HashAlgorithm {
85+
return k.sigAlg.GetProtoHashType()
86+
}
87+
88+
// GetHint returns a hint for the public key.
89+
func (k *SignerVerifierKeypair) GetHint() []byte {
90+
return k.hint
91+
}
92+
93+
// GetKeyAlgorithm returns the key algorithm, to be used in requests to Fulcio.
94+
func (k *SignerVerifierKeypair) GetKeyAlgorithm() string {
95+
return k.keyAlg
96+
}
97+
98+
// GetPublicKeyPem returns the public key in PEM format.
99+
func (k *SignerVerifierKeypair) GetPublicKeyPem() (string, error) {
100+
pubKey, err := k.sv.PublicKey()
101+
if err != nil {
102+
return "", err
103+
}
104+
pemBytes, err := cryptoutils.MarshalPublicKeyToPEM(pubKey)
105+
if err != nil {
106+
return "", err
107+
}
108+
return string(pemBytes), nil
109+
}
110+
111+
// SignData signs the given data with the SignerVerifier.
112+
func (k *SignerVerifierKeypair) SignData(ctx context.Context, data []byte) ([]byte, []byte, error) {
113+
h := k.sigAlg.GetHashType().New()
114+
h.Write(data)
115+
digest := h.Sum(nil)
116+
sOpts := []signature.SignOption{signatureoptions.WithContext(ctx), signatureoptions.WithDigest(digest)}
117+
sig, err := k.sv.SignMessage(bytes.NewReader(data), sOpts...)
118+
if err != nil {
119+
return nil, nil, err
120+
}
121+
return sig, digest, nil
122+
}

0 commit comments

Comments
 (0)