@@ -17,24 +17,34 @@ package renderer
1717
1818import (
1919 "bytes"
20+ "encoding/base64"
2021 "encoding/json"
22+ "encoding/pem"
2123 "errors"
2224 "fmt"
25+ "os"
2326
2427 v1 "github.com/chainloop-dev/chainloop/internal/attestation/crafter/api/attestation/v1"
2528 "github.com/chainloop-dev/chainloop/internal/attestation/renderer/chainloop"
29+ chainloopsigner "github.com/chainloop-dev/chainloop/internal/attestation/signer/chainloop"
2630 intoto "github.com/in-toto/attestation/go/v1"
2731 "github.com/rs/zerolog"
2832 "github.com/secure-systems-lab/go-securesystemslib/dsse"
33+ protobundle "github.com/sigstore/protobuf-specs/gen/pb-go/bundle/v1"
34+ v12 "github.com/sigstore/protobuf-specs/gen/pb-go/common/v1"
35+ sigstoredsse "github.com/sigstore/protobuf-specs/gen/pb-go/dsse"
2936 sigstoresigner "github.com/sigstore/sigstore/pkg/signature"
37+ sigdsee "github.com/sigstore/sigstore/pkg/signature/dsse"
3038 "google.golang.org/protobuf/encoding/protojson"
3139)
3240
3341type AttestationRenderer struct {
34- logger zerolog.Logger
35- att * v1.Attestation
36- renderer r
37- signer sigstoresigner.Signer
42+ logger zerolog.Logger
43+ att * v1.Attestation
44+ renderer r
45+ signer sigstoresigner.Signer
46+ dsseSigner sigstoresigner.Signer
47+ bundlePath string
3848}
3949
4050type r interface {
@@ -49,16 +59,23 @@ func WithLogger(logger zerolog.Logger) Opt {
4959 }
5060}
5161
62+ func WithBundleOutputPath (bundlePath string ) Opt {
63+ return func (ar * AttestationRenderer ) {
64+ ar .bundlePath = bundlePath
65+ }
66+ }
67+
5268func NewAttestationRenderer (state * v1.CraftingState , builderVersion , builderDigest string , signer sigstoresigner.Signer , opts ... Opt ) (* AttestationRenderer , error ) {
5369 if state .GetAttestation () == nil {
5470 return nil , errors .New ("attestation not initialized" )
5571 }
5672
5773 r := & AttestationRenderer {
58- logger : zerolog .Nop (),
59- att : state .GetAttestation (),
60- signer : signer ,
61- renderer : chainloop .NewChainloopRendererV02 (state .GetAttestation (), builderVersion , builderDigest ),
74+ logger : zerolog .Nop (),
75+ att : state .GetAttestation (),
76+ dsseSigner : sigdsee .WrapSigner (signer , "application/vnd.in-toto+json" ),
77+ signer : signer ,
78+ renderer : chainloop .NewChainloopRendererV02 (state .GetAttestation (), builderVersion , builderDigest ),
6279 }
6380
6481 for _ , opt := range opts {
@@ -87,15 +104,77 @@ func (ab *AttestationRenderer) Render() (*dsse.Envelope, error) {
87104 return nil , err
88105 }
89106
90- signedAtt , err := ab .signer .SignMessage (bytes .NewReader (rawStatement ))
107+ signedAtt , err := ab .dsseSigner .SignMessage (bytes .NewReader (rawStatement ))
91108 if err != nil {
92109 return nil , fmt .Errorf ("signing message: %w" , err )
93110 }
94111
95- var dseeEnvelope dsse.Envelope
96- if err := json .Unmarshal (signedAtt , & dseeEnvelope ); err != nil {
112+ var dsseEnvelope dsse.Envelope
113+ if err := json .Unmarshal (signedAtt , & dsseEnvelope ); err != nil {
97114 return nil , err
98115 }
99116
100- return & dseeEnvelope , nil
117+ if ab .bundlePath != "" {
118+ // Create sigstore bundle for the contents of this attestation
119+ bundle , err := ab .envelopeToBundle (dsseEnvelope )
120+ if err != nil {
121+ return nil , fmt .Errorf ("loading bundle: %w" , err )
122+ }
123+ json , err := protojson .Marshal (bundle )
124+ if err != nil {
125+ return nil , fmt .Errorf ("marshalling bundle: %w" , err )
126+ }
127+ ab .logger .Info ().Msg (fmt .Sprintf ("generating Sigstore bundle %s" , ab .bundlePath ))
128+ err = os .WriteFile (ab .bundlePath , json , 0600 )
129+ if err != nil {
130+ return nil , fmt .Errorf ("writing bundle: %w" , err )
131+ }
132+ }
133+
134+ return & dsseEnvelope , nil
135+ }
136+
137+ func (ab * AttestationRenderer ) envelopeToBundle (dsseEnvelope dsse.Envelope ) (* protobundle.Bundle , error ) {
138+ // DSSE Envelope is already base64 encoded, we need to decode to prevent it from being encoded twice
139+ payload , err := base64 .StdEncoding .DecodeString (dsseEnvelope .Payload )
140+ if err != nil {
141+ return nil , fmt .Errorf ("decoding: %w" , err )
142+ }
143+ bundle := & protobundle.Bundle {
144+ MediaType : "application/vnd.dev.sigstore.bundle+json;version=0.3" ,
145+ Content : & protobundle.Bundle_DsseEnvelope {DsseEnvelope : & sigstoredsse.Envelope {
146+ Payload : payload ,
147+ PayloadType : dsseEnvelope .PayloadType ,
148+ Signatures : []* sigstoredsse.Signature {
149+ {
150+ Sig : []byte (dsseEnvelope .Signatures [0 ].Sig ),
151+ Keyid : dsseEnvelope .Signatures [0 ].KeyID ,
152+ },
153+ },
154+ }},
155+ VerificationMaterial : & protobundle.VerificationMaterial {},
156+ }
157+
158+ // extract verification materials
159+ // Note: we don't support PublicKey materials (from cosign.key and KMS signers), since Chainloop doesn't (yet) store
160+ // public keys.
161+ if v , ok := ab .signer .(* chainloopsigner.Signer ); ok {
162+ chain := v .Chain
163+ certs := make ([]* v12.X509Certificate , 0 )
164+ // Store cert chain except root certificate, as it's required to be provided separately
165+ for _ , c := range chain [0 : len (chain )- 1 ] {
166+ block , _ := pem .Decode ([]byte (c ))
167+ if block == nil {
168+ return nil , fmt .Errorf ("failed to decode PEM block" )
169+ }
170+ certs = append (certs , & v12.X509Certificate {RawBytes : block .Bytes })
171+ }
172+ bundle .VerificationMaterial .Content = & protobundle.VerificationMaterial_X509CertificateChain {
173+ X509CertificateChain : & v12.X509CertificateChain {
174+ Certificates : certs ,
175+ },
176+ }
177+ }
178+
179+ return bundle , nil
101180}
0 commit comments