Skip to content

Commit

Permalink
Add support for Sigstore bundle in sign-blob
Browse files Browse the repository at this point in the history
  • Loading branch information
wata727 committed Feb 4, 2024
1 parent 493e6e2 commit d652a11
Show file tree
Hide file tree
Showing 13 changed files with 491 additions and 22 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ bin*
dist/
cosignImagerefs
bundle
!pkg/bundle
!pkg/cosign/bundle
signature
certificate

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 @@ -32,6 +32,7 @@ type KeyOpts struct {
OIDCDisableProviders bool // Disable OIDC credential providers in keyless signer
OIDCProvider string // Specify which OIDC credential provider to use for keyless signer
BundlePath string
SigstoreBundlePath string
SkipConfirmation bool
TSAClientCACert string
TSAClientCert string
Expand Down
5 changes: 5 additions & 0 deletions cmd/cosign/cli/options/signblob.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ type SignBlobOptions struct {
OIDC OIDCOptions
Registry RegistryOptions
BundlePath string
SigstoreBundlePath string
SkipConfirmation bool
TlogUpload bool
TSAClientCACert string
Expand Down Expand Up @@ -75,6 +76,10 @@ func (o *SignBlobOptions) AddFlags(cmd *cobra.Command) {
"write everything required to verify the blob to a FILE")
_ = cmd.Flags().SetAnnotation("bundle", cobra.BashCompFilenameExt, []string{})

cmd.Flags().StringVar(&o.SigstoreBundlePath, "sigstore-bundle", "",
"write the Sigstore bundle defined in sigstore/protobuf-specs to a FILE")
_ = cmd.Flags().SetAnnotation("sigstore-bundle", cobra.BashCompFilenameExt, []string{})

cmd.Flags().BoolVarP(&o.SkipConfirmation, "yes", "y", false,
"skip confirmation prompts for non-destructive operations")

Expand Down
83 changes: 61 additions & 22 deletions cmd/cosign/cli/sign/sign_blob.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,12 @@ import (
"github.com/sigstore/cosign/v2/internal/pkg/cosign/tsa"
"github.com/sigstore/cosign/v2/internal/pkg/cosign/tsa/client"
"github.com/sigstore/cosign/v2/internal/ui"
"github.com/sigstore/cosign/v2/pkg/bundle"
"github.com/sigstore/cosign/v2/pkg/cosign"
cbundle "github.com/sigstore/cosign/v2/pkg/cosign/bundle"
"github.com/sigstore/sigstore/pkg/cryptoutils"
signatureoptions "github.com/sigstore/sigstore/pkg/signature/options"
"google.golang.org/protobuf/encoding/protojson"
)

// nolint
Expand Down Expand Up @@ -69,13 +71,10 @@ func SignBlobCmd(ro *options.RootOptions, ko options.KeyOpts, payloadPath string
return nil, fmt.Errorf("signing blob: %w", err)
}

signedPayload := cosign.LocalSignedPayload{}
signedEntity := cbundle.SignedEntity{}

var rfc3161Timestamp *cbundle.RFC3161Timestamp
if ko.TSAServerURL != "" {
if ko.RFC3161TimestampPath == "" {
return nil, fmt.Errorf("timestamp output path must be set")
}
var respBytes []byte
var err error
if ko.TSAClientCACert == "" && ko.TSAClientCert == "" { // no mTLS params or custom CA
Expand All @@ -95,20 +94,24 @@ func SignBlobCmd(ro *options.RootOptions, ko options.KeyOpts, payloadPath string
}
}

rfc3161Timestamp = cbundle.TimestampToRFC3161Timestamp(respBytes)
// TODO: Consider uploading RFC3161 TS to Rekor
if ko.RFC3161TimestampPath != "" {
rfc3161Timestamp = cbundle.TimestampToRFC3161Timestamp(respBytes)
// TODO: Consider uploading RFC3161 TS to Rekor

if rfc3161Timestamp == nil {
return nil, fmt.Errorf("rfc3161 timestamp is nil")
}
ts, err := json.Marshal(rfc3161Timestamp)
if err != nil {
return nil, err
}
if err := os.WriteFile(ko.RFC3161TimestampPath, ts, 0600); err != nil {
return nil, fmt.Errorf("create RFC3161 timestamp file: %w", err)
if rfc3161Timestamp == nil {
return nil, fmt.Errorf("rfc3161 timestamp is nil")
}
ts, err := json.Marshal(rfc3161Timestamp)
if err != nil {
return nil, err
}
if err := os.WriteFile(ko.RFC3161TimestampPath, ts, 0600); err != nil {
return nil, fmt.Errorf("create RFC3161 timestamp file: %w", err)
}
ui.Infof(ctx, "RFC3161 timestamp written to file %s\n", ko.RFC3161TimestampPath)
}
ui.Infof(ctx, "RFC3161 timestamp written to file %s\n", ko.RFC3161TimestampPath)

signedEntity.RFC3161Timestamps = [][]byte{respBytes}
}
shouldUpload, err := ShouldUploadToTlog(ctx, ko, nil, tlogUpload)
if err != nil {
Expand All @@ -128,20 +131,28 @@ func SignBlobCmd(ro *options.RootOptions, ko options.KeyOpts, payloadPath string
return nil, err
}
ui.Infof(ctx, "tlog entry created with index: %d", *entry.LogIndex)
signedPayload.Bundle = cbundle.EntryToBundle(entry)
signedEntity.LogEntry = entry
}

// if bundle is specified, just do that and ignore the rest
if ko.BundlePath != "" {
signedPayload.Base64Signature = base64.StdEncoding.EncodeToString(sig)
signedEntity.Signature = bundle.NewMessageSignature(payload.Sum(nil), "SHA2_256", sig)

certBytes, err := extractCertificate(ctx, sv)
if sv.Cert != nil {
certs, err := cryptoutils.UnmarshalCertificatesFromPEM(sv.Cert)
if err != nil {
return nil, fmt.Errorf("unmarshal certificate from PEM: %w", err)
}
signedEntity.VerificationMaterial = &bundle.CertificateChain{Certificates: certs}
} else {
signedEntity.VerificationMaterial = &bundle.PublicKey{}
}

bundle, err := cbundle.Build(&signedEntity)
if err != nil {
return nil, err
}
signedPayload.Cert = base64.StdEncoding.EncodeToString(certBytes)

contents, err := json.Marshal(signedPayload)
contents, err := json.Marshal(bundle)
if err != nil {
return nil, err
}
Expand All @@ -150,6 +161,34 @@ func SignBlobCmd(ro *options.RootOptions, ko options.KeyOpts, payloadPath string
}
ui.Infof(ctx, "Wrote bundle to file %s", ko.BundlePath)
}
if ko.SigstoreBundlePath != "" {
signedEntity.Signature = bundle.NewMessageSignature(payload.Sum(nil), "SHA2_256", sig)

if sv.Cert != nil {
certs, err := cryptoutils.UnmarshalCertificatesFromPEM(sv.Cert)
if err != nil {
return nil, fmt.Errorf("unmarshal certificate from PEM: %w", err)
}
// TODO: how to cut out the root certificate?
signedEntity.VerificationMaterial = &bundle.CertificateChain{Certificates: certs}
} else {
signedEntity.VerificationMaterial = &bundle.PublicKey{}
}

bundle, err := bundle.Build(&signedEntity)
if err != nil {
return nil, err
}

contents, err := protojson.Marshal(bundle)
if err != nil {
return nil, err
}
if err := os.WriteFile(ko.SigstoreBundlePath, contents, 0600); err != nil {
return nil, fmt.Errorf("create bundle file: %w", err)
}
ui.Infof(ctx, "Wrote bundle to file %s", ko.SigstoreBundlePath)
}

if outputSignature != "" {
var bts = sig
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 {
OIDCRedirectURL: o.OIDC.RedirectURL,
OIDCDisableProviders: o.OIDC.DisableAmbientProviders,
BundlePath: o.BundlePath,
SigstoreBundlePath: o.SigstoreBundlePath,
SkipConfirmation: o.SkipConfirmation,
TSAClientCACert: o.TSAClientCACert,
TSAClientCert: o.TSAClientCert,
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ require (
github.com/pkg/errors v0.9.1
github.com/secure-systems-lab/go-securesystemslib v0.8.0
github.com/sigstore/fulcio v1.4.3
github.com/sigstore/protobuf-specs v0.2.1
github.com/sigstore/rekor v1.3.4
github.com/sigstore/sigstore v1.8.1
github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.1
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -609,6 +609,8 @@ github.com/shibumi/go-pathspec v1.3.0 h1:QUyMZhFo0Md5B8zV8x2tesohbb5kfbpTi9rBnKh
github.com/shibumi/go-pathspec v1.3.0/go.mod h1:Xutfslp817l2I1cZvgcfeMQJG5QnU2lh5tVaaMCl3jE=
github.com/sigstore/fulcio v1.4.3 h1:9JcUCZjjVhRF9fmhVuz6i1RyhCc/EGCD7MOl+iqCJLQ=
github.com/sigstore/fulcio v1.4.3/go.mod h1:BQPWo7cfxmJwgaHlphUHUpFkp5+YxeJes82oo39m5og=
github.com/sigstore/protobuf-specs v0.2.1 h1:KIoM7E3C4uaK092q8YoSj/XSf9720f8dlsbYwwOmgEA=
github.com/sigstore/protobuf-specs v0.2.1/go.mod h1:xPqQGnH/HllKuZ4VFPz/g+78epWM/NLRGl7Fuy45UdE=
github.com/sigstore/rekor v1.3.4 h1:RGIia1iOZU7fOiiP2UY/WFYhhp50S5aUm7YrM8aiA6E=
github.com/sigstore/rekor v1.3.4/go.mod h1:1GubPVO2yO+K0m0wt/3SHFqnilr/hWbsjSOe7Vzxrlg=
github.com/sigstore/sigstore v1.8.1 h1:mAVposMb14oplk2h/bayPmIVdzbq2IhCgy4g6R0ZSjo=
Expand Down
129 changes: 129 additions & 0 deletions pkg/bundle/bundle.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// Copyright 2024 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 bundle

import (
"fmt"

pbbundle "github.com/sigstore/protobuf-specs/gen/pb-go/bundle/v1"
pbcommon "github.com/sigstore/protobuf-specs/gen/pb-go/common/v1"
"github.com/sigstore/protobuf-specs/gen/pb-go/dsse"
pbrekor "github.com/sigstore/protobuf-specs/gen/pb-go/rekor/v1"
"github.com/sigstore/rekor/pkg/tle"
)

func Build(entity SignedEntity) (*pbbundle.Bundle, error) {
bundle := &pbbundle.Bundle{
MediaType: "application/vnd.dev.sigstore.bundle+json;version=0.2",
}
var err error

bundle.VerificationMaterial, err = buildVerificationMaterial(entity)
if err != nil {
return nil, err
}

sig, err := entity.SignatureContent()
if err != nil {
return nil, fmt.Errorf("failed to fetch signature content: %w", err)
}
if ms := sig.MessageSignatureContent(); ms != nil {
bundle.Content = &pbbundle.Bundle_MessageSignature{
MessageSignature: &pbcommon.MessageSignature{
MessageDigest: &pbcommon.HashOutput{
Algorithm: AsPbHashAlgorithm(ms.DigestAlgorithm()),
Digest: ms.Digest(),
},
Signature: ms.Signature(),
},
}
} else if envelop := sig.EnvelopeContent(); envelop != nil {
rawEnvelope := envelop.RawEnvelope()
dsseSignatures := make([]*dsse.Signature, len(rawEnvelope.Signatures))
for i, s := range rawEnvelope.Signatures {
dsseSignatures[i] = &dsse.Signature{Keyid: s.KeyID, Sig: []byte(s.Sig)}
}
bundle.Content = &pbbundle.Bundle_DsseEnvelope{
DsseEnvelope: &dsse.Envelope{
Payload: []byte(rawEnvelope.Payload),
PayloadType: rawEnvelope.PayloadType,
Signatures: dsseSignatures,
},
}
}

return bundle, nil
}

func buildVerificationMaterial(entity SignedEntity) (*pbbundle.VerificationMaterial, error) {
vm := pbbundle.VerificationMaterial{}

vc, err := entity.VerificationContent()
if err != nil {
return nil, fmt.Errorf("failed to fetch verification content: %w", err)
}
if cert, ok := vc.HasCertificate(); ok {
vm.Content = &pbbundle.VerificationMaterial_X509CertificateChain{
X509CertificateChain: &pbcommon.X509CertificateChain{
Certificates: []*pbcommon.X509Certificate{
{RawBytes: cert.Raw},
},
},
}
} else if pub, ok := vc.HasPublicKey(); ok {
vm.Content = &pbbundle.VerificationMaterial_PublicKey{
PublicKey: &pbcommon.PublicKeyIdentifier{
Hint: pub.Hint(),
},
}
}

logEntry, err := entity.TlogEntry()
if err != nil {
return nil, fmt.Errorf("failed to fetch transparency log entry: %w", err)
}
if logEntry != nil {
tlEntry, err := tle.GenerateTransparencyLogEntry(*logEntry)
if err != nil {
return nil, fmt.Errorf("failed to generate TransparencyLogEntry: %w", err)
}
vm.TlogEntries = []*pbrekor.TransparencyLogEntry{tlEntry}
}

timestamps, err := entity.Timestamps()
if err != nil {
return nil, fmt.Errorf("failed to fetch timestamps: %w", err)
}
if len(timestamps) > 0 {
rfc3161timestamps := make([]*pbcommon.RFC3161SignedTimestamp, len(timestamps))
for i, timestamp := range timestamps {
rfc3161timestamps[i] = &pbcommon.RFC3161SignedTimestamp{SignedTimestamp: timestamp}
}
vm.TimestampVerificationData = &pbbundle.TimestampVerificationData{
Rfc3161Timestamps: rfc3161timestamps,
}
}

return &vm, nil
}

func AsPbHashAlgorithm(in string) pbcommon.HashAlgorithm {
switch in {
case "SHA2_256":
return pbcommon.HashAlgorithm_SHA2_256
default:
panic(fmt.Sprintf("unknown hash algorithm: %s", in))
}
}
Loading

0 comments on commit d652a11

Please sign in to comment.