|
16 | 16 | package action |
17 | 17 |
|
18 | 18 | import ( |
| 19 | + "bytes" |
19 | 20 | "context" |
| 21 | + "crypto/x509" |
20 | 22 | "errors" |
21 | 23 | "fmt" |
22 | 24 | "sort" |
23 | 25 |
|
24 | 26 | pb "github.com/chainloop-dev/chainloop/app/controlplane/api/controlplane/v1" |
25 | 27 | "github.com/chainloop-dev/chainloop/internal/attestation/renderer/chainloop" |
| 28 | + "github.com/sigstore/cosign/v2/pkg/blob" |
| 29 | + "github.com/sigstore/cosign/v2/pkg/cosign" |
26 | 30 | sigs "github.com/sigstore/cosign/v2/pkg/signature" |
| 31 | + "github.com/sigstore/sigstore/pkg/cryptoutils" |
| 32 | + "github.com/sigstore/sigstore/pkg/signature" |
27 | 33 |
|
28 | 34 | intoto "github.com/in-toto/attestation/go/v1" |
29 | 35 | "github.com/secure-systems-lab/go-securesystemslib/dsse" |
@@ -81,14 +87,21 @@ func NewWorkflowRunDescribe(cfg *ActionsOpts) *WorkflowRunDescribe { |
81 | 87 | return &WorkflowRunDescribe{cfg} |
82 | 88 | } |
83 | 89 |
|
84 | | -func (action *WorkflowRunDescribe) Run(ctx context.Context, runID string, digest string, verify bool, publicKey string) (*WorkflowRunItemFull, error) { |
| 90 | +type WorkflowRunDescribeOpts struct { |
| 91 | + RunID, Digest string |
| 92 | + Verify bool |
| 93 | + PublicKeyRef string |
| 94 | + CertPath, CertChainPath string |
| 95 | +} |
| 96 | + |
| 97 | +func (action *WorkflowRunDescribe) Run(ctx context.Context, opts *WorkflowRunDescribeOpts) (*WorkflowRunItemFull, error) { |
85 | 98 | client := pb.NewWorkflowRunServiceClient(action.cfg.CPConnection) |
86 | 99 |
|
87 | 100 | req := &pb.WorkflowRunServiceViewRequest{} |
88 | | - if digest != "" { |
89 | | - req.Ref = &pb.WorkflowRunServiceViewRequest_Digest{Digest: digest} |
90 | | - } else if runID != "" { |
91 | | - req.Ref = &pb.WorkflowRunServiceViewRequest_Id{Id: runID} |
| 101 | + if opts.Digest != "" { |
| 102 | + req.Ref = &pb.WorkflowRunServiceViewRequest_Digest{Digest: opts.Digest} |
| 103 | + } else if opts.RunID != "" { |
| 104 | + req.Ref = &pb.WorkflowRunServiceViewRequest_Id{Id: opts.RunID} |
92 | 105 | } |
93 | 106 |
|
94 | 107 | resp, err := client.View(ctx, req) |
@@ -119,8 +132,8 @@ func (action *WorkflowRunDescribe) Run(ctx context.Context, runID string, digest |
119 | 132 | return nil, err |
120 | 133 | } |
121 | 134 |
|
122 | | - if verify { |
123 | | - if err := verifyEnvelope(ctx, envelope, publicKey); err != nil { |
| 135 | + if opts.Verify { |
| 136 | + if err := verifyEnvelope(ctx, envelope, opts); err != nil { |
124 | 137 | action.cfg.Logger.Debug().Err(err).Msg("verifying the envelope") |
125 | 138 | return nil, errors.New("invalid signature, did you provide the right key?") |
126 | 139 | } |
@@ -196,19 +209,59 @@ func materialPBToAction(in *pb.AttestationItem_Material) *Material { |
196 | 209 | return m |
197 | 210 | } |
198 | 211 |
|
199 | | -func verifyEnvelope(ctx context.Context, e *dsse.Envelope, publicKey string) error { |
200 | | - // Currently we only support basic cosign public key check |
201 | | - // TODO: Add more verification methods |
202 | | - verifier, err := sigs.PublicKeyFromKeyRef(ctx, publicKey) |
203 | | - if err != nil { |
204 | | - return err |
| 212 | +func verifyEnvelope(ctx context.Context, e *dsse.Envelope, opts *WorkflowRunDescribeOpts) error { |
| 213 | + if opts.PublicKeyRef == "" && opts.CertPath == "" { |
| 214 | + return fmt.Errorf("no public key or cert path specified") |
| 215 | + } |
| 216 | + |
| 217 | + var verifier signature.Verifier |
| 218 | + var err error |
| 219 | + if opts.PublicKeyRef != "" { |
| 220 | + verifier, err = sigs.PublicKeyFromKeyRef(ctx, opts.PublicKeyRef) |
| 221 | + if err != nil { |
| 222 | + return fmt.Errorf("invalid public key: %w", err) |
| 223 | + } |
| 224 | + } |
| 225 | + |
| 226 | + if opts.CertPath != "" { |
| 227 | + // Load cert from PEM |
| 228 | + certs, err := loadCertificates(opts.CertPath) |
| 229 | + if err != nil { |
| 230 | + return fmt.Errorf("loading certificate: %w", err) |
| 231 | + } |
| 232 | + |
| 233 | + var chain []*x509.Certificate |
| 234 | + if opts.CertChainPath != "" { |
| 235 | + chain, err = loadCertificates(opts.CertChainPath) |
| 236 | + if err != nil { |
| 237 | + return fmt.Errorf("loading certificate chain: %w", err) |
| 238 | + } |
| 239 | + } |
| 240 | + |
| 241 | + verifier, err = cosign.ValidateAndUnpackCertWithChain(certs[0], chain, &cosign.CheckOpts{IgnoreSCT: true}) |
| 242 | + if err != nil { |
| 243 | + return fmt.Errorf("validating the certificate: %w", err) |
| 244 | + } |
205 | 245 | } |
206 | 246 |
|
207 | 247 | dsseVerifier, err := dsse.NewEnvelopeVerifier(&sigdsee.VerifierAdapter{SignatureVerifier: verifier}) |
208 | 248 | if err != nil { |
209 | | - return err |
| 249 | + return fmt.Errorf("creating DSSE verifier: %w", err) |
210 | 250 | } |
211 | 251 |
|
212 | 252 | _, err = dsseVerifier.Verify(ctx, e) |
213 | 253 | return err |
214 | 254 | } |
| 255 | + |
| 256 | +func loadCertificates(certPath string) ([]*x509.Certificate, error) { |
| 257 | + cert, err := blob.LoadFileOrURL(certPath) |
| 258 | + if err != nil { |
| 259 | + return nil, fmt.Errorf("loading certificate: %w", err) |
| 260 | + } |
| 261 | + certs, err := cryptoutils.LoadCertificatesFromPEM(bytes.NewReader(cert)) |
| 262 | + if err != nil { |
| 263 | + return nil, fmt.Errorf("loading certificates: %w", err) |
| 264 | + } |
| 265 | + |
| 266 | + return certs, nil |
| 267 | +} |
0 commit comments