Skip to content

Commit

Permalink
Support for fetching Fulcio certs with self-managed key
Browse files Browse the repository at this point in the history
Added a new flag --issue-certificate to sign commands that allows users
to fetch Fulcio certificate with self-managed key

Signed-off-by: Anish Shah <anishshah@google.com>
  • Loading branch information
AnishShah committed Dec 11, 2022
1 parent 6cb723f commit 3251570
Show file tree
Hide file tree
Showing 10 changed files with 89 additions and 30 deletions.
94 changes: 65 additions & 29 deletions cmd/cosign/cli/fulcio/fulcio.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ import (
"context"
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"errors"
Expand All @@ -33,6 +35,7 @@ import (
"github.com/sigstore/cosign/internal/pkg/cosign/fulcio/fulcioroots"
"github.com/sigstore/cosign/pkg/cosign"
"github.com/sigstore/cosign/pkg/providers"
key "github.com/sigstore/cosign/pkg/signature"
"github.com/sigstore/fulcio/pkg/api"
"github.com/sigstore/sigstore/pkg/oauthflow"
"github.com/sigstore/sigstore/pkg/signature"
Expand Down Expand Up @@ -62,27 +65,54 @@ func (rf *realConnector) OIDConnect(url, clientID, secret, redirectURL string) (
return oauthflow.OIDConnect(url, clientID, secret, redirectURL, rf.flow)
}

func getCertForOauthID(priv *ecdsa.PrivateKey, fc api.LegacyClient, connector oidcConnector, oidcIssuer, oidcClientID, oidcClientSecret, oidcRedirectURL string) (*api.CertificateResponse, error) {
pubBytes, err := x509.MarshalPKIXPublicKey(&priv.PublicKey)
if err != nil {
return nil, err
}

func getCertForOauthID(sv signature.SignerVerifier, fc api.LegacyClient, connector oidcConnector, oidcIssuer, oidcClientID, oidcClientSecret, oidcRedirectURL string) (*api.CertificateResponse, error) {
tok, err := connector.OIDConnect(oidcIssuer, oidcClientID, oidcClientSecret, oidcRedirectURL)
if err != nil {
return nil, err
}

// Sign the email address as part of the request
h := sha256.Sum256([]byte(tok.Subject))
proof, err := ecdsa.SignASN1(rand.Reader, priv, h[:])
if err != nil {
return nil, err

var pubBytes, proof []byte
var algorithm string
switch sv := sv.(type) {
case *signature.RSAPSSSignerVerifier:
algorithm = "rsa"
pubBytes = x509.MarshalPKCS1PublicKey(sv.Public().(*rsa.PublicKey))
// Sign the email address as part of the request
proof, err = sv.Sign(rand.Reader, h[:], signature.SignerOpts{Hash: crypto.SHA256})
if err != nil {
return nil, err
}
case *signature.ECDSASignerVerifier:
algorithm = "ecdsa"
pubBytes, err = x509.MarshalPKIXPublicKey(sv.Public().(*ecdsa.PublicKey))
if err != nil {
return nil, err
}
// Sign the email address as part of the request
proof, err = sv.Sign(rand.Reader, h[:], nil)
if err != nil {
return nil, err
}
case *signature.ED25519SignerVerifier:
algorithm = "ed25519"
pubBytes, err = x509.MarshalPKIXPublicKey(sv.Public().(*ed25519.PublicKey))
if err != nil {
return nil, err
}
// Sign the email address as part of the request
proof, err = sv.Sign(nil, h[:], nil)
if err != nil {
return nil, err
}
default:
return nil, fmt.Errorf("unsupported key type: %T", sv)
}

cr := api.CertificateRequest{
PublicKey: api.Key{
Algorithm: "ecdsa",
Algorithm: algorithm,
Content: pubBytes,
},
SignedEmailAddress: proof,
Expand All @@ -92,7 +122,7 @@ func getCertForOauthID(priv *ecdsa.PrivateKey, fc api.LegacyClient, connector oi
}

// GetCert returns the PEM-encoded signature of the OIDC identity returned as part of an interactive oauth2 flow plus the PEM-encoded cert chain.
func GetCert(ctx context.Context, priv *ecdsa.PrivateKey, idToken, flow, oidcIssuer, oidcClientID, oidcClientSecret, oidcRedirectURL string, fClient api.LegacyClient) (*api.CertificateResponse, error) {
func GetCert(ctx context.Context, sv signature.SignerVerifier, idToken, flow, oidcIssuer, oidcClientID, oidcClientSecret, oidcRedirectURL string, fClient api.LegacyClient) (*api.CertificateResponse, error) {
c := &realConnector{}
switch flow {
case flowDevice:
Expand All @@ -105,15 +135,14 @@ func GetCert(ctx context.Context, priv *ecdsa.PrivateKey, idToken, flow, oidcIss
return nil, fmt.Errorf("unsupported oauth flow: %s", flow)
}

return getCertForOauthID(priv, fClient, c, oidcIssuer, oidcClientID, oidcClientSecret, oidcRedirectURL)
return getCertForOauthID(sv, fClient, c, oidcIssuer, oidcClientID, oidcClientSecret, oidcRedirectURL)
}

type Signer struct {
Cert []byte
Chain []byte
SCT []byte
pub *ecdsa.PublicKey
*signature.ECDSASignerVerifier
signature.SignerVerifier
}

func NewSigner(ctx context.Context, ko options.KeyOpts) (*Signer, error) {
Expand All @@ -140,13 +169,21 @@ func NewSigner(ctx context.Context, ko options.KeyOpts) (*Signer, error) {
}
}

priv, err := cosign.GeneratePrivateKey()
if err != nil {
return nil, fmt.Errorf("generating cert: %w", err)
}
signer, err := signature.LoadECDSASignerVerifier(priv, crypto.SHA256)
if err != nil {
return nil, err
var signer signature.SignerVerifier
if ko.IssueCertificate && ko.KeyRef != "" {
signer, err = key.SignerVerifierFromKeyRef(ctx, ko.KeyRef, ko.PassFunc)
if err != nil {
return nil, err
}
} else {
priv, err := cosign.GeneratePrivateKey()
if err != nil {
return nil, fmt.Errorf("generating cert: %w", err)
}
signer, err = signature.LoadECDSASignerVerifier(priv, crypto.SHA256)
if err != nil {
return nil, err
}
}
fmt.Fprintln(os.Stderr, "Retrieving signed certificate...")

Expand All @@ -172,24 +209,23 @@ func NewSigner(ctx context.Context, ko options.KeyOpts) (*Signer, error) {
}
flow = flowNormal
}
Resp, err := GetCert(ctx, priv, idToken, flow, ko.OIDCIssuer, ko.OIDCClientID, ko.OIDCClientSecret, ko.OIDCRedirectURL, fClient) // TODO, use the chain.
Resp, err := GetCert(ctx, signer, idToken, flow, ko.OIDCIssuer, ko.OIDCClientID, ko.OIDCClientSecret, ko.OIDCRedirectURL, fClient) // TODO, use the chain.
if err != nil {
return nil, fmt.Errorf("retrieving cert: %w", err)
}

f := &Signer{
pub: &priv.PublicKey,
ECDSASignerVerifier: signer,
Cert: Resp.CertPEM,
Chain: Resp.ChainPEM,
SCT: Resp.SCT,
SignerVerifier: signer,
Cert: Resp.CertPEM,
Chain: Resp.ChainPEM,
SCT: Resp.SCT,
}

return f, nil
}

func (f *Signer) PublicKey(opts ...signature.PublicKeyOption) (crypto.PublicKey, error) {
return &f.pub, nil
return f.SignerVerifier.PublicKey()
}

var _ signature.Signer = &Signer{}
Expand Down
8 changes: 7 additions & 1 deletion cmd/cosign/cli/fulcio/fulcio_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package fulcio

import (
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
Expand All @@ -28,6 +29,7 @@ import (
"github.com/sigstore/cosign/cmd/cosign/cli/options"
"github.com/sigstore/fulcio/pkg/api"
"github.com/sigstore/sigstore/pkg/oauthflow"
"github.com/sigstore/sigstore/pkg/signature"
)

type testFlow struct {
Expand Down Expand Up @@ -64,6 +66,10 @@ func TestGetCertForOauthID(t *testing.T) {
if err != nil {
t.Fatalf("Could not generate ecdsa keypair for test: %v", err)
}
sv, err := signature.LoadECDSASignerVerifier(testKey, crypto.SHA256)
if err != nil {
t.Fatalf("Could not create a signer: %v", err)
}

testCases := []struct {
desc string
Expand Down Expand Up @@ -118,7 +124,7 @@ func TestGetCertForOauthID(t *testing.T) {
err: tc.tokenGetterErr,
}

resp, err := getCertForOauthID(testKey, tscp, &tf, "", "", "", "")
resp, err := getCertForOauthID(sv, tscp, &tf, "", "", "", "")

if err != nil {
if !tc.expectErr {
Expand Down
1 change: 1 addition & 0 deletions cmd/cosign/cli/options/key.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ type KeyOpts struct {
TSAServerURL string
RFC3161TimestampPath string
TSACertChainPath string
IssueCertificate bool

// FulcioAuthFlow is the auth flow to use when authenticating against
// Fulcio. See https://pkg.go.dev/github.com/sigstore/cosign/cmd/cosign/cli/fulcio#pkg-constants
Expand Down
4 changes: 4 additions & 0 deletions cmd/cosign/cli/options/sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ type SignOptions struct {
SkipConfirmation bool
TlogUpload bool
TSAServerURL string
IssueCertificate bool

Rekor RekorOptions
Fulcio FulcioOptions
Expand Down Expand Up @@ -98,4 +99,7 @@ func (o *SignOptions) AddFlags(cmd *cobra.Command) {

cmd.Flags().StringVar(&o.TSAServerURL, "timestamp-server-url", "",
"url to the Timestamp RFC3161 server, default none")

cmd.Flags().BoolVar(&o.IssueCertificate, "issue-certificate", false,
"whether or not to issue code signing certificate from Fulcio")
}
4 changes: 4 additions & 0 deletions cmd/cosign/cli/options/signblob.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ type SignBlobOptions struct {
TlogUpload bool
TSAServerURL string
RFC3161TimestampPath string
IssueCertificate bool
}

var _ Interface = (*SignBlobOptions)(nil)
Expand Down Expand Up @@ -82,4 +83,7 @@ func (o *SignBlobOptions) AddFlags(cmd *cobra.Command) {
cmd.Flags().StringVar(&o.RFC3161TimestampPath, "rfc3161-timestamp", "",
"write the RFC3161 timestamp to a file")
_ = cmd.Flags().SetAnnotation("rfc3161-timestamp", cobra.BashCompFilenameExt, []string{})

cmd.Flags().BoolVar(&o.IssueCertificate, "issue-certificate", false,
"whether or not to issue code signing certificate from Fulcio")
}
1 change: 1 addition & 0 deletions cmd/cosign/cli/sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ race conditions or (worse) malicious tampering.
OIDCProvider: o.OIDC.Provider,
SkipConfirmation: o.SkipConfirmation,
TSAServerURL: o.TSAServerURL,
IssueCertificate: o.IssueCertificate,
}
if err := sign.SignCmd(ro, ko, *o, args); err != nil {
if o.Attachment == "" {
Expand Down
4 changes: 4 additions & 0 deletions cmd/cosign/cli/sign/sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,10 @@ func keylessSigner(ctx context.Context, ko options.KeyOpts) (*SignerVerifier, er
}

func SignerFromKeyOpts(ctx context.Context, certPath string, certChainPath string, ko options.KeyOpts) (*SignerVerifier, error) {
if ko.IssueCertificate {
return keylessSigner(ctx, ko)
}

if ko.Sk {
return signerFromSecurityKey(ko.Slot)
}
Expand Down
1 change: 1 addition & 0 deletions cmd/cosign/cli/signblob.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ func SignBlob() *cobra.Command {
SkipConfirmation: o.SkipConfirmation,
TSAServerURL: o.TSAServerURL,
RFC3161TimestampPath: o.RFC3161TimestampPath,
IssueCertificate: o.IssueCertificate,
}

for _, blob := range args {
Expand Down
1 change: 1 addition & 0 deletions doc/cosign_sign-blob.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions doc/cosign_sign.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 3251570

Please sign in to comment.