Skip to content

Commit

Permalink
continued sign refacc (#1098)
Browse files Browse the repository at this point in the history
* decompose `StaticSigner` into individual signing stages
* move rekor upload logic to `RekorSignerWrapper`
* select signing flows based on input variables
* add comments to the `Signer` implementations
* only perform signing actions in `cosign.Signer`
* flip comments on Signer implementations
* staticly assert that  impls are  impls
* split Rekor and Fulcio signers into their own packages

Signed-off-by: Jake Sanders <jsand@google.com>
  • Loading branch information
Jake Sanders committed Nov 30, 2021
1 parent a035b27 commit 304c2b2
Show file tree
Hide file tree
Showing 6 changed files with 421 additions and 78 deletions.
48 changes: 47 additions & 1 deletion cmd/cosign/cli/attest/attest.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,62 @@ import (
"github.com/sigstore/cosign/pkg/cosign"
"github.com/sigstore/cosign/pkg/cosign/attestation"
cremote "github.com/sigstore/cosign/pkg/cosign/remote"
"github.com/sigstore/cosign/pkg/oci"
"github.com/sigstore/cosign/pkg/oci/mutate"
ociremote "github.com/sigstore/cosign/pkg/oci/remote"
"github.com/sigstore/cosign/pkg/oci/static"
sigs "github.com/sigstore/cosign/pkg/signature"
"github.com/sigstore/cosign/pkg/types"
rekPkgClient "github.com/sigstore/rekor/pkg/client"
"github.com/sigstore/rekor/pkg/generated/client"
"github.com/sigstore/rekor/pkg/generated/models"
"github.com/sigstore/sigstore/pkg/signature/dsse"
signatureoptions "github.com/sigstore/sigstore/pkg/signature/options"
)

// TODO(dekkagaijin): remove this in favor of a function in pkg which handles both signatures and attestations
func bundle(entry *models.LogEntryAnon) *oci.Bundle {
if entry.Verification == nil {
return nil
}
return &oci.Bundle{
SignedEntryTimestamp: entry.Verification.SignedEntryTimestamp,
Payload: oci.BundlePayload{
Body: entry.Body,
IntegratedTime: *entry.IntegratedTime,
LogIndex: *entry.LogIndex,
LogID: *entry.LogID,
},
}
}

type tlogUploadFn func(*client.Rekor, []byte) (*models.LogEntryAnon, error)

func uploadToTlog(ctx context.Context, sv *sign.SignerVerifier, rekorURL string, upload tlogUploadFn) (*oci.Bundle, error) {
var rekorBytes []byte
// Upload the cert or the public key, depending on what we have
if sv.Cert != nil {
rekorBytes = sv.Cert
} else {
pemBytes, err := sigs.PublicKeyPem(sv, signatureoptions.WithContext(ctx))
if err != nil {
return nil, err
}
rekorBytes = pemBytes
}

rekorClient, err := rekPkgClient.GetRekorClient(rekorURL)
if err != nil {
return nil, err
}
entry, err := upload(rekorClient, rekorBytes)
if err != nil {
return nil, err
}
fmt.Fprintln(os.Stderr, "tlog entry created with index:", *entry.LogIndex)
return bundle(entry), nil
}

//nolint
func AttestCmd(ctx context.Context, ko sign.KeyOpts, regOpts options.RegistryOptions, imageRef string, certPath string,
noUpload bool, predicatePath string, force bool, predicateType string, replace bool, timeout time.Duration) error {
Expand Down Expand Up @@ -133,7 +179,7 @@ func AttestCmd(ctx context.Context, ko sign.KeyOpts, regOpts options.RegistryOpt

// Check whether we should be uploading to the transparency log
if sign.ShouldUploadToTlog(ctx, digest, force, ko.RekorURL) {
bundle, err := sign.UploadToTlog(ctx, sv, ko.RekorURL, func(r *client.Rekor, b []byte) (*models.LogEntryAnon, error) {
bundle, err := uploadToTlog(ctx, sv, ko.RekorURL, func(r *client.Rekor, b []byte) (*models.LogEntryAnon, error) {
return cosign.TLogUploadInTotoAttestation(ctx, r, signedPayload, b)
})
if err != nil {
Expand Down
101 changes: 24 additions & 77 deletions cmd/cosign/cli/sign/sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import (
"crypto/ecdsa"
"crypto/rsa"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"fmt"
"net/url"
Expand All @@ -37,6 +36,10 @@ import (
"github.com/sigstore/cosign/cmd/cosign/cli/fulcio"
"github.com/sigstore/cosign/cmd/cosign/cli/fulcio/fulcioverifier"
"github.com/sigstore/cosign/cmd/cosign/cli/options"
icos "github.com/sigstore/cosign/internal/pkg/cosign"
ifulcio "github.com/sigstore/cosign/internal/pkg/cosign/fulcio"
ipayload "github.com/sigstore/cosign/internal/pkg/cosign/payload"
irekor "github.com/sigstore/cosign/internal/pkg/cosign/rekor"
"github.com/sigstore/cosign/pkg/cosign"
"github.com/sigstore/cosign/pkg/cosign/pivkey"
"github.com/sigstore/cosign/pkg/cosign/pkcs11key"
Expand All @@ -45,17 +48,12 @@ import (
ociempty "github.com/sigstore/cosign/pkg/oci/empty"
"github.com/sigstore/cosign/pkg/oci/mutate"
ociremote "github.com/sigstore/cosign/pkg/oci/remote"
"github.com/sigstore/cosign/pkg/oci/static"
"github.com/sigstore/cosign/pkg/oci/walk"
providers "github.com/sigstore/cosign/pkg/providers/all"
sigs "github.com/sigstore/cosign/pkg/signature"
fulcPkgClient "github.com/sigstore/fulcio/pkg/client"
rekPkgClient "github.com/sigstore/rekor/pkg/client"
rekGenClient "github.com/sigstore/rekor/pkg/generated/client"
"github.com/sigstore/rekor/pkg/generated/models"
"github.com/sigstore/sigstore/pkg/cryptoutils"
"github.com/sigstore/sigstore/pkg/signature"
signatureoptions "github.com/sigstore/sigstore/pkg/signature/options"
sigPayload "github.com/sigstore/sigstore/pkg/signature/payload"
)

Expand Down Expand Up @@ -85,32 +83,6 @@ func ShouldUploadToTlog(ctx context.Context, ref name.Reference, force bool, url
return true
}

type Uploader func(*rekGenClient.Rekor, []byte) (*models.LogEntryAnon, error)

func UploadToTlog(ctx context.Context, sv *SignerVerifier, rekorURL string, upload Uploader) (*oci.Bundle, error) {
var rekorBytes []byte
// Upload the cert or the public key, depending on what we have
if sv.Cert != nil {
rekorBytes = sv.Cert
} else {
pemBytes, err := sigs.PublicKeyPem(sv, signatureoptions.WithContext(ctx))
if err != nil {
return nil, err
}
rekorBytes = pemBytes
}
rekorClient, err := rekPkgClient.GetRekorClient(rekorURL)
if err != nil {
return nil, err
}
entry, err := upload(rekorClient, rekorBytes)
if err != nil {
return nil, err
}
fmt.Fprintln(os.Stderr, "tlog entry created with index:", *entry.LogIndex)
return Bundle(entry), nil
}

func GetAttachedImageRef(ref name.Reference, attachment string, opts ...ociremote.Option) (name.Reference, error) {
if attachment == "" {
return ref, nil
Expand Down Expand Up @@ -230,12 +202,6 @@ func signDigest(ctx context.Context, digest name.Digest, payload []byte, ko KeyO
}
}

signature, err := sv.SignMessage(bytes.NewReader(payload), signatureoptions.WithContext(ctx))
if err != nil {
return errors.Wrap(err, "signing")
}
b64sig := base64.StdEncoding.EncodeToString(signature)

out := os.Stdout
if output != "" {
out, err = os.Create(output)
Expand All @@ -244,36 +210,33 @@ func signDigest(ctx context.Context, digest name.Digest, payload []byte, ko KeyO
}
defer out.Close()
}
if _, err := out.Write([]byte(b64sig)); err != nil {
return errors.Wrap(err, "write signature to file")
}

if !upload {
return nil
var s icos.Signer
s = ipayload.NewSigner(sv, nil, nil)
s = ifulcio.NewSigner(s, sv.Cert, sv.Chain)
if ShouldUploadToTlog(ctx, digest, force, ko.RekorURL) {
s = irekor.NewSigner(s, ko.RekorURL)
}

opts := []static.Option{}
if sv.Cert != nil {
opts = append(opts, static.WithCertChain(sv.Cert, sv.Chain))
}
if ShouldUploadToTlog(ctx, digest, force, ko.RekorURL) {
bundle, err := UploadToTlog(ctx, sv, ko.RekorURL, func(r *rekGenClient.Rekor, b []byte) (*models.LogEntryAnon, error) {
return cosign.TLogUpload(ctx, r, signature, payload, b)
})
if err != nil {
return err
}
opts = append(opts, static.WithBundle(bundle))
ociSig, _, err := s.Sign(ctx, bytes.NewReader(payload))
if err != nil {
return err
}

// Create the new signature for this entity.
sig, err := static.NewSignature(payload, b64sig, opts...)
b64sig, err := ociSig.Base64Signature()
if err != nil {
return err
}
if _, err := out.Write([]byte(b64sig)); err != nil {
return errors.Wrap(err, "write signature to file")
}

if !upload {
return nil
}

// Attach the signature to the entity.
newSE, err := mutate.AttachSignatureToEntity(se, sig, mutate.WithDupeDetector(dd))
newSE, err := mutate.AttachSignatureToEntity(se, ociSig, mutate.WithDupeDetector(dd))
if err != nil {
return err
}
Expand All @@ -288,22 +251,8 @@ func signDigest(ctx context.Context, digest name.Digest, payload []byte, ko KeyO
if err := ociremote.WriteSignatures(digest.Repository, newSE, walkOpts...); err != nil {
return err
}
return nil
}

func Bundle(entry *models.LogEntryAnon) *oci.Bundle {
if entry.Verification == nil {
return nil
}
return &oci.Bundle{
SignedEntryTimestamp: entry.Verification.SignedEntryTimestamp,
Payload: oci.BundlePayload{
Body: entry.Body,
IntegratedTime: *entry.IntegratedTime,
LogIndex: *entry.LogIndex,
LogID: *entry.LogID,
},
}
return nil
}

func signerFromSecurityKey(keySlot string) (*SignerVerifier, error) {
Expand Down Expand Up @@ -430,13 +379,11 @@ func keylessSigner(ctx context.Context, ko KeyOpts) (*SignerVerifier, error) {
var k *fulcio.Signer

if ko.InsecureSkipFulcioVerify {
k, err = fulcio.NewSigner(ctx, tok, ko.OIDCIssuer, ko.OIDCClientID, fClient)
if err != nil {
if k, err = fulcio.NewSigner(ctx, tok, ko.OIDCIssuer, ko.OIDCClientID, fClient); err != nil {
return nil, errors.Wrap(err, "getting key from Fulcio")
}
} else {
k, err = fulcioverifier.NewSigner(ctx, tok, ko.OIDCIssuer, ko.OIDCClientID, fClient)
if err != nil {
if k, err = fulcioverifier.NewSigner(ctx, tok, ko.OIDCIssuer, ko.OIDCClientID, fClient); err != nil {
return nil, errors.Wrap(err, "getting key from Fulcio")
}
}
Expand Down
89 changes: 89 additions & 0 deletions internal/pkg/cosign/fulcio/fulcio.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// 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 fulcio

import (
"context"
"crypto"
"io"

"github.com/sigstore/cosign/internal/pkg/cosign"
"github.com/sigstore/cosign/pkg/oci"
"github.com/sigstore/cosign/pkg/oci/static"
)

// signerWrapper still needs to actually upload keys to Fulcio and receive
// the resulting `Cert` and `Chain`, which are added to the returned `oci.Signature`
type signerWrapper struct {
inner cosign.Signer

cert, chain []byte
}

var _ cosign.Signer = (*signerWrapper)(nil)

// Sign implements `cosign.Signer`
func (fs *signerWrapper) Sign(ctx context.Context, payload io.Reader) (oci.Signature, crypto.PublicKey, error) {
sig, pub, err := fs.inner.Sign(ctx, payload)
if err != nil {
return nil, nil, err
}

payloadBytes, err := sig.Payload()
if err != nil {
return nil, nil, err
}
b64Sig, err := sig.Base64Signature()
if err != nil {
return nil, nil, err
}

// TODO(dekkagaijin): move the fulcio SignerVerififer logic here

opts := []static.Option{static.WithCertChain(fs.cert, fs.chain)}

// Copy over the other attributes:
if annotations, err := sig.Annotations(); err != nil {
return nil, nil, err
} else if len(annotations) > 0 {
opts = append(opts, static.WithAnnotations(annotations))
}
if bundle, err := sig.Bundle(); err != nil {
return nil, nil, err
} else if bundle != nil {
opts = append(opts, static.WithBundle(bundle))
}
if mt, err := sig.MediaType(); err != nil {
return nil, nil, err
} else if mt != "" {
opts = append(opts, static.WithLayerMediaType(mt))
}

newSig, err := static.NewSignature(payloadBytes, b64Sig, opts...)
if err != nil {
return nil, nil, err
}

return newSig, pub, nil
}

// NewSigner returns a `cosign.Signer` which leverages Fulcio to create a Cert and Chain for the signature
func NewSigner(inner cosign.Signer, cert, chain []byte) cosign.Signer {
return &signerWrapper{
inner: inner,
cert: cert,
chain: chain,
}
}

0 comments on commit 304c2b2

Please sign in to comment.