From b4412196d9822dcb53b9e7d99e9d2116bdfed023 Mon Sep 17 00:00:00 2001 From: Jake Sanders Date: Thu, 2 Dec 2021 14:27:20 -0800 Subject: [PATCH] fix Signed-off-by: Jake Sanders --- cmd/cosign/cli/attest/attest.go | 255 +++++++------------------ cmd/cosign/cli/sign/sign.go | 4 +- internal/pkg/cosign/payload/payload.go | 116 ++--------- 3 files changed, 87 insertions(+), 288 deletions(-) diff --git a/cmd/cosign/cli/attest/attest.go b/cmd/cosign/cli/attest/attest.go index ebde751a55cf..a0daf3226314 100644 --- a/cmd/cosign/cli/attest/attest.go +++ b/cmd/cosign/cli/attest/attest.go @@ -18,14 +18,9 @@ package attest import ( "bytes" "context" - "crypto/ecdsa" - "crypto/rsa" _ "crypto/sha256" // for `crypto.SHA256` - "crypto/x509" "encoding/json" - "encoding/pem" "fmt" - "net/url" "os" "time" @@ -33,30 +28,67 @@ import ( v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/pkg/errors" - "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" "github.com/sigstore/cosign/cmd/cosign/cli/sign" - "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/attestation" - "github.com/sigstore/cosign/pkg/cosign/pivkey" - "github.com/sigstore/cosign/pkg/cosign/pkcs11key" 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/providers" - rekPkgClient "github.com/sigstore/rekor/pkg/client" - "github.com/sigstore/sigstore/pkg/cryptoutils" - "github.com/sigstore/sigstore/pkg/signature" - - icos "github.com/sigstore/cosign/internal/pkg/cosign" - ifulcio "github.com/sigstore/cosign/internal/pkg/cosign/fulcio" + "github.com/sigstore/cosign/pkg/oci/static" sigs "github.com/sigstore/cosign/pkg/signature" - fulcPkgClient "github.com/sigstore/fulcio/pkg/client" + "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 { @@ -101,13 +133,12 @@ func AttestCmd(ctx context.Context, ko sign.KeyOpts, regOpts options.RegistryOpt // each access. ref = digest // nolint - attestor, sv, closeFn, err := AttestorFromKeyOpts(ctx, certPath, predicateURI, ko) + sv, err := sign.SignerFromKeyOpts(ctx, certPath, ko) if err != nil { return errors.Wrap(err, "getting signer") } - if closeFn != nil { - defer closeFn() - } + defer sv.Close() + wrapped := dsse.WrapSigner(sv, predicateURI) dd := cremote.NewDupeDetector(sv) fmt.Fprintln(os.Stderr, "Using payload from:", predicatePath) @@ -131,30 +162,37 @@ func AttestCmd(ctx context.Context, ko sign.KeyOpts, regOpts options.RegistryOpt if err != nil { return err } + signedPayload, err := wrapped.SignMessage(bytes.NewReader(payload), signatureoptions.WithContext(ctx)) + if err != nil { + return errors.Wrap(err, "signing") + } + + if noUpload { + fmt.Println(string(signedPayload)) + return nil + } + + opts := []static.Option{static.WithLayerMediaType(types.DssePayloadType)} + if sv.Cert != nil { + opts = append(opts, static.WithCertChain(sv.Cert, sv.Chain)) + } // Check whether we should be uploading to the transparency log if sign.ShouldUploadToTlog(ctx, digest, force, ko.RekorURL) { - rClient, err := rekPkgClient.GetRekorClient(ko.RekorURL) + 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 { return err } - attestor = irekor.WrapDSSEAttestor(attestor, rClient) + opts = append(opts, static.WithBundle(bundle)) } - ociAtt, _, err := attestor.Attest(ctx, bytes.NewReader(payload)) + sig, err := static.NewAttestation(signedPayload, opts...) if err != nil { return err } - if noUpload { - signedPayload, err := ociAtt.Payload() - if err != nil { - return err - } - fmt.Println(string(signedPayload)) - return nil - } - se, err := ociremote.SignedEntity(digest, ociremoteOpts...) if err != nil { return err @@ -170,7 +208,7 @@ func AttestCmd(ctx context.Context, ko sign.KeyOpts, regOpts options.RegistryOpt } // Attach the attestation to the entity. - newSE, err := mutate.AttachAttestationToEntity(se, ociAtt, signOpts...) + newSE, err := mutate.AttachAttestationToEntity(se, sig, signOpts...) if err != nil { return err } @@ -178,148 +216,3 @@ func AttestCmd(ctx context.Context, ko sign.KeyOpts, regOpts options.RegistryOpt // Publish the attestations associated with this entity return ociremote.WriteAttestations(digest.Repository, newSE, ociremoteOpts...) } - -func attestorFromSecurityKey(keySlot, predicateURI string) (attestor icos.Attestor, sv signature.SignerVerifier, closeFn func(), err error) { - sk, err := pivkey.GetKeyWithSlot(keySlot) - if err != nil { - return nil, nil, nil, err - } - sv, err = sk.SignerVerifier() - if err != nil { - sk.Close() - return nil, nil, nil, err - } - - // Handle the -cert flag. - // With PIV, we assume the certificate is in the same slot on the PIV - // token as the private key. If it's not there, show a warning to the - // user. - certFromPIV, err := sk.Certificate() - var certPem []byte - if err != nil { - fmt.Fprintln(os.Stderr, "warning: no x509 certificate retrieved from the PIV token") - } else { - certPem, err = cryptoutils.MarshalCertificateToPEM(certFromPIV) - if err != nil { - sk.Close() - return nil, nil, nil, err - } - } - - return payload.NewDSSEAttestor(sv, nil, nil, certPem, nil, predicateURI), sv, sk.Close, nil -} - -func attestorFromKeyRef(ctx context.Context, certPath, keyRef string, passFunc cosign.PassFunc, predicateURI string) (attestor icos.Attestor, sv signature.SignerVerifier, closeFn func(), err error) { - k, err := sigs.SignerVerifierFromKeyRef(ctx, keyRef, passFunc) - if err != nil { - return nil, nil, nil, errors.Wrap(err, "reading key") - } - - var certBytes []byte - - // Handle the -cert flag - // With PKCS11, we assume the certificate is in the same slot on the PKCS11 - // token as the private key. If it's not there, show a warning to the - // user. - if pkcs11Key, ok := k.(*pkcs11key.Key); ok { - certFromPKCS11, _ := pkcs11Key.Certificate() - if certFromPKCS11 == nil { - fmt.Fprintln(os.Stderr, "warning: no x509 certificate retrieved from the PKCS11 token") - } else { - certBytes, err = cryptoutils.MarshalCertificateToPEM(certFromPKCS11) - if err != nil { - pkcs11Key.Close() - return nil, nil, nil, err - } - } - - return payload.NewDSSEAttestor(k, nil, nil, certBytes, nil, predicateURI), k, pkcs11Key.Close, nil - } - - if certPath == "" { - return payload.NewDSSEAttestor(k, nil, nil, nil, nil, predicateURI), k, nil, nil - } - - certBytes, err = os.ReadFile(certPath) - if err != nil { - return nil, nil, nil, errors.Wrap(err, "read certificate") - } - // Handle PEM. - if bytes.HasPrefix(certBytes, []byte("-----")) { - decoded, _ := pem.Decode(certBytes) - if decoded.Type != "CERTIFICATE" { - return nil, nil, nil, fmt.Errorf("supplied PEM file is not a certificate: %s", certPath) - } - certBytes = decoded.Bytes - } - parsedCert, err := x509.ParseCertificate(certBytes) - if err != nil { - return nil, nil, nil, errors.Wrap(err, "parse x509 certificate") - } - pk, err := k.PublicKey() - if err != nil { - return nil, nil, nil, errors.Wrap(err, "get public key") - } - switch kt := parsedCert.PublicKey.(type) { - case *ecdsa.PublicKey: - if !kt.Equal(pk) { - return nil, nil, nil, errors.New("public key in certificate does not match that in the signing key") - } - case *rsa.PublicKey: - if !kt.Equal(pk) { - return nil, nil, nil, errors.New("public key in certificate does not match that in the signing key") - } - default: - return nil, nil, nil, fmt.Errorf("unsupported key type: %T", parsedCert.PublicKey) - } - pemBytes, err := cryptoutils.MarshalCertificateToPEM(parsedCert) - if err != nil { - return nil, nil, nil, errors.Wrap(err, "marshaling certificate to PEM") - } - - return payload.NewDSSEAttestor(k, nil, nil, pemBytes, nil, predicateURI), k, nil, nil -} - -func keylessAttestor(ctx context.Context, predicateURI string, ko sign.KeyOpts) (attestor icos.Attestor, sv signature.SignerVerifier, err error) { - fulcioServer, err := url.Parse(ko.FulcioURL) - if err != nil { - return nil, nil, errors.Wrap(err, "parsing Fulcio URL") - } - fClient := fulcPkgClient.New(fulcioServer) - tok := ko.IDToken - if providers.Enabled(ctx) { - tok, err = providers.Provide(ctx, "sigstore") - if err != nil { - return nil, nil, errors.Wrap(err, "fetching ambient OIDC credentials") - } - } - - var k *fulcio.Signer - - if ko.InsecureSkipFulcioVerify { - if k, err = fulcio.NewSigner(ctx, tok, ko.OIDCIssuer, ko.OIDCClientID, fClient); err != nil { - return nil, nil, errors.Wrap(err, "getting key from Fulcio") - } - } else { - if k, err = fulcioverifier.NewSigner(ctx, tok, ko.OIDCIssuer, ko.OIDCClientID, fClient); err != nil { - return nil, nil, errors.Wrap(err, "getting key from Fulcio") - } - } - - return ifulcio.WrapAttestor(payload.NewDSSEAttestor(k, nil, nil, nil, nil, predicateURI), k.Cert, k.Chain), k, nil -} - -func AttestorFromKeyOpts(ctx context.Context, certPath, predicateURI string, ko sign.KeyOpts) (attestor icos.Attestor, sv signature.SignerVerifier, closeFn func(), err error) { - if ko.Sk { - return attestorFromSecurityKey(ko.Slot, predicateURI) - } - - if ko.KeyRef != "" { - return attestorFromKeyRef(ctx, certPath, ko.KeyRef, ko.PassFunc, predicateURI) - } - - // Default Keyless! - fmt.Fprintln(os.Stderr, "Generating ephemeral keys...") - attestor, sv, err = keylessAttestor(ctx, predicateURI, ko) - return attestor, sv, nil, err -} diff --git a/cmd/cosign/cli/sign/sign.go b/cmd/cosign/cli/sign/sign.go index 89ca5c98c3e6..20af92fe08ab 100644 --- a/cmd/cosign/cli/sign/sign.go +++ b/cmd/cosign/cli/sign/sign.go @@ -205,11 +205,9 @@ func signDigest(ctx context.Context, digest name.Digest, payload []byte, ko KeyO } } - // TODO(dekkagaijin): hoist the creation of these into SignerFromKeyOpts var s icos.Signer - s = ipayload.NewSigner(sv, nil, nil, nil, nil) + s = ipayload.NewSigner(sv, nil, nil) s = ifulcio.NewSigner(s, sv.Cert, sv.Chain) - if ShouldUploadToTlog(ctx, digest, force, ko.RekorURL) { rClient, err := rekorClient.GetRekorClient(ko.RekorURL) if err != nil { diff --git a/internal/pkg/cosign/payload/payload.go b/internal/pkg/cosign/payload/payload.go index 58c745aadbb2..3ca86fe7b905 100644 --- a/internal/pkg/cosign/payload/payload.go +++ b/internal/pkg/cosign/payload/payload.go @@ -19,14 +19,11 @@ import ( "context" "crypto" "encoding/base64" - "encoding/json" "io" - "github.com/secure-systems-lab/go-securesystemslib/dsse" "github.com/sigstore/cosign/internal/pkg/cosign" "github.com/sigstore/cosign/pkg/oci" "github.com/sigstore/cosign/pkg/oci/static" - "github.com/sigstore/cosign/pkg/types" "github.com/sigstore/sigstore/pkg/signature" signatureoptions "github.com/sigstore/sigstore/pkg/signature/options" ) @@ -35,57 +32,32 @@ type payloadSigner struct { payloadSigner signature.Signer payloadSignerOpts []signature.SignOption publicKeyProviderOpts []signature.PublicKeyOption - - certPEM, chainPEM []byte } var _ cosign.Signer = (*payloadSigner)(nil) -type payloadAttestor struct { - payloadSigner - - payloadType string -} - -var _ cosign.Attestor = (*payloadAttestor)(nil) - -func (ps *payloadSigner) signPayload(ctx context.Context, payload io.Reader) (payloadBytes, sig []byte, pk crypto.PublicKey, err error) { - payloadBytes, err = io.ReadAll(payload) +// 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, nil, err + return nil, nil, err } - sOpts := []signature.SignOption{signatureoptions.WithContext(ctx)} sOpts = append(sOpts, ps.payloadSignerOpts...) - sig, err = ps.payloadSigner.SignMessage(bytes.NewReader(payloadBytes), sOpts...) + sig, err := ps.payloadSigner.SignMessage(bytes.NewReader(payloadBytes), sOpts...) if err != nil { - return nil, nil, nil, err + return nil, nil, err } pkOpts := []signature.PublicKeyOption{signatureoptions.WithContext(ctx)} pkOpts = append(pkOpts, ps.publicKeyProviderOpts...) - pk, err = ps.payloadSigner.PublicKey(pkOpts...) - if err != nil { - return nil, nil, nil, err - } - - return payloadBytes, sig, pk, nil -} - -// Sign implements `Signer` -func (ps *payloadSigner) Sign(ctx context.Context, payload io.Reader) (oci.Signature, crypto.PublicKey, error) { - payloadBytes, sig, pk, err := ps.signPayload(ctx, payload) + pk, err := ps.payloadSigner.PublicKey(pkOpts...) if err != nil { return nil, nil, err } b64sig := base64.StdEncoding.EncodeToString(sig) - - var opts []static.Option - if len(ps.certPEM) > 0 { - opts = []static.Option{static.WithCertChain(ps.certPEM, ps.chainPEM)} - } - ociSig, err := static.NewSignature(payloadBytes, b64sig, opts...) + ociSig, err := static.NewSignature(payloadBytes, b64sig) if err != nil { return nil, nil, err } @@ -93,77 +65,13 @@ func (ps *payloadSigner) Sign(ctx context.Context, payload io.Reader) (oci.Signa return ociSig, pk, nil } -// Attest implements `cosign.Attestor` -func (pa *payloadAttestor) Attest(ctx context.Context, payload io.Reader) (oci.Signature, crypto.PublicKey, error) { - p, err := io.ReadAll(payload) - if err != nil { - return nil, nil, err - } - pae := dsse.PAE(pa.payloadType, string(p)) - - _, sig, pk, err := pa.signPayload(ctx, bytes.NewReader(pae)) - if err != nil { - return nil, nil, err - } - - envelope := dsse.Envelope{ - PayloadType: pa.payloadType, - Payload: base64.StdEncoding.EncodeToString(p), - 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 -} - -func newSigner(s signature.Signer, +// NewSigner returns a `cosign.Signer` uses the given `signature.Signer` to sign the requested payload, then returns the signature, the public key associated with it, the signed payload +func NewSigner(s signature.Signer, sOpts []signature.SignOption, - pkOpts []signature.PublicKeyOption, - certPEM, chainPEM []byte) payloadSigner { - return payloadSigner{ + pkOpts []signature.PublicKeyOption) cosign.Signer { + return &payloadSigner{ payloadSigner: s, payloadSignerOpts: sOpts, publicKeyProviderOpts: pkOpts, - - certPEM: certPEM, - chainPEM: chainPEM, - } -} - -// NewSigner returns a `cosign.Signer` which uses the given `signature.Signer` to sign requested payloads. -// The cert and chain, if provided, will be included in returned `oci.Signature`s. -func NewSigner(s signature.Signer, - sOpts []signature.SignOption, - pkOpts []signature.PublicKeyOption, - certPEM, chainPEM []byte) cosign.Signer { - ps := newSigner(s, sOpts, pkOpts, certPEM, chainPEM) - return &ps -} - -// NewDSSEAttestor returns a `cosign.Attestor` which uses the given `signature.Signer` to create an attestation out of given payloads. -// The cert and chain, if provided, will be included in returned `oci.Signature`s. -func NewDSSEAttestor(s signature.Signer, - sOpts []signature.SignOption, - pkOpts []signature.PublicKeyOption, - certPEM, chainPEM []byte, - payloadType string) cosign.Attestor { - return &payloadAttestor{ - payloadSigner: newSigner(s, sOpts, pkOpts, certPEM, chainPEM), - payloadType: payloadType, } }