Skip to content

Commit 7b672ef

Browse files
authored
feat(cli): Verify attestations with local certificate and chain (#982)
Signed-off-by: Jose I. Paris <[email protected]>
1 parent 5def11f commit 7b672ef

File tree

8 files changed

+240
-19
lines changed

8 files changed

+240
-19
lines changed

app/cli/cmd/workflow_workflow_run_describe.go

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,17 +39,21 @@ const formatAttestation = "attestation"
3939
const formatPayloadPAE = "payload-pae"
4040

4141
func newWorkflowWorkflowRunDescribeCmd() *cobra.Command {
42-
var runID, attestationDigest, publicKey string
43-
var verifyAttestation bool
42+
var (
43+
runID, attestationDigest, publicKey string
44+
certPath, chainPath string
45+
verifyAttestation bool
46+
)
47+
4448
// TODO: Replace by retrieving key from rekor
4549
const signingKeyEnvVarName = "CHAINLOOP_SIGNING_PUBLIC_KEY"
4650

4751
cmd := &cobra.Command{
4852
Use: "describe",
4953
Short: "View a Workflow Run",
5054
PreRunE: func(cmd *cobra.Command, args []string) error {
51-
if verifyAttestation && publicKey == "" {
52-
return errors.New("a public key needs to be provided for verification")
55+
if verifyAttestation && publicKey == "" && certPath == "" {
56+
return errors.New("a public key or certificate needs to be provided for verification")
5357
}
5458

5559
if runID == "" && attestationDigest == "" {
@@ -59,7 +63,14 @@ func newWorkflowWorkflowRunDescribeCmd() *cobra.Command {
5963
return nil
6064
},
6165
RunE: func(cmd *cobra.Command, args []string) error {
62-
res, err := action.NewWorkflowRunDescribe(actionOpts).Run(context.Background(), runID, attestationDigest, verifyAttestation, publicKey)
66+
res, err := action.NewWorkflowRunDescribe(actionOpts).Run(context.Background(), &action.WorkflowRunDescribeOpts{
67+
RunID: runID,
68+
Digest: attestationDigest,
69+
PublicKeyRef: publicKey,
70+
CertPath: certPath,
71+
CertChainPath: chainPath,
72+
Verify: verifyAttestation,
73+
})
6374
if err != nil {
6475
return err
6576
}
@@ -78,6 +89,9 @@ func newWorkflowWorkflowRunDescribeCmd() *cobra.Command {
7889
publicKey = os.Getenv(signingKeyEnvVarName)
7990
}
8091

92+
cmd.Flags().StringVar(&certPath, "cert", "", "public certificate in PEM format to be used to verify the attestation")
93+
cmd.Flags().StringVar(&chainPath, "cert-chain", "", "certificate chain (intermediates, root) in PEM format to be used to verify the attestation")
94+
8195
// Override default output flag
8296
cmd.InheritedFlags().StringVarP(&flagOutputFormat, "output", "o", "table", "output format, valid options are table, json, attestation, statement or payload-pae")
8397

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
-----BEGIN CERTIFICATE-----
2+
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
32+
sQ==
33+
-----END CERTIFICATE-----
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"payloadType": "application/vnd.in-toto+json",
3+
"payload": "eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjEiLCAic3ViamVjdCI6W3sibmFtZSI6ImNoYWlubG9vcC53b3JrZmxvdy5teXdmIiwgImRpZ2VzdCI6eyJzaGEyNTYiOiI2YzhjNTFmOGM2YzEyMDM1YmEzYzg1OTdjOGE3ZjdjYTA1MDdlMDJiMjJlMjE5MjA3OTU1OTY2YmIwNGE3N2U1In19LCB7Im5hbWUiOiJnaXQuaGVhZCIsICJkaWdlc3QiOnsic2hhMSI6ImY5ZjI0OTNhNjQ3NDczYzBiZTBjODgxNGQzZDMyYjE3ZDYxNWZhMzgifSwgImFubm90YXRpb25zIjp7ImF1dGhvci5lbWFpbCI6ImppcGFyaXNAY2hhaW5sb29wLmRldiIsICJhdXRob3IubmFtZSI6Ikpvc2UgSS4gUGFyaXMiLCAiZGF0ZSI6IjIwMjQtMDYtMThUMTY6MTE6NThaIiwgIm1lc3NhZ2UiOiJkcmFmdCB2ZXJpZmllciB3aXRoIGNlcnRcblxuU2lnbmVkLW9mZi1ieTogSm9zZSBJLiBQYXJpcyA8amlwYXJpc0BjaGFpbmxvb3AuZGV2PlxuIiwgInJlbW90ZXMiOlt7Im5hbWUiOiJvcmlnaW4iLCAidXJsIjoiZ2l0QGdpdGh1Yi5jb206amlwYXJpcy9jaGFpbmxvb3AuZ2l0In0sIHsibmFtZSI6InVwc3RyZWFtIiwgInVybCI6ImdpdEBnaXRodWIuY29tOmNoYWlubG9vcC1kZXYvY2hhaW5sb29wLmdpdCJ9XX19XSwgInByZWRpY2F0ZVR5cGUiOiJjaGFpbmxvb3AuZGV2L2F0dGVzdGF0aW9uL3YwLjIiLCAicHJlZGljYXRlIjp7ImJ1aWxkVHlwZSI6ImNoYWlubG9vcC5kZXYvd29ya2Zsb3dydW4vdjAuMSIsICJidWlsZGVyIjp7ImlkIjoiY2hhaW5sb29wLmRldi9jbGkvZGV2QHNoYTI1Njo5NGMxOWJjZDQ4ZjE2MDMyZTU1OTI5ZDdmMTVmMTU2ZWNiZGQxZjk5YThhYmIzMjI2NzFjY2FkOGEzMmI1YzVkIn0sICJtYXRlcmlhbHMiOlt7ImFubm90YXRpb25zIjp7ImNoYWlubG9vcC5tYXRlcmlhbC5jYXMuaW5saW5lIjp0cnVlLCAiY2hhaW5sb29wLm1hdGVyaWFsLm5hbWUiOiJtYXRlcmlhbC0xNzE4NzI4Njg5ODQzNDEzMDAwIiwgImNoYWlubG9vcC5tYXRlcmlhbC50eXBlIjoiQVJUSUZBQ1QifSwgImRpZ2VzdCI6eyJzaGEyNTYiOiJlM2IwYzQ0Mjk4ZmMxYzE0OWFmYmY0Yzg5OTZmYjkyNDI3YWU0MWU0NjQ5YjkzNGNhNDk1OTkxYjc4NTJiODU1In0sICJuYW1lIjoiZXZpZGVuY2UudHh0In1dLCAibWV0YWRhdGEiOnsiZmluaXNoZWRBdCI6IjIwMjQtMDYtMThUMTY6Mzg6NDguMDQzMzU5WiIsICJpbml0aWFsaXplZEF0IjoiMjAyNC0wNi0xOFQxNjozNzo1OC4yMDQ4NjJaIiwgIm5hbWUiOiJteXdmIiwgIm9yZ2FuaXphdGlvbiI6ImZvY3VzZWQtd3UtNTUxMiIsICJwcm9qZWN0IjoibXlwcm9qZWN0IiwgInRlYW0iOiIiLCAid29ya2Zsb3dJRCI6ImViN2I0NjMzLTk2ZTItNGVmZS1iMjNmLWY2NjdmM2Y3YWNkYyIsICJ3b3JrZmxvd1J1bklEIjoiMDBiNjlhMzAtYjExNy00YjM3LWI3ZGMtYmUxOGI5NmRhY2MwIn0sICJydW5uZXJUeXBlIjoiUlVOTkVSX1RZUEVfVU5TUEVDSUZJRUQifX0=",
4+
"signatures": [
5+
{
6+
"keyid": "",
7+
"sig": "MEUCIQDCk5jvXtdY8K0vrkvipUmuR68y8T736pvuBnrpQAT/JwIgBY4jo7XAzVkvb4jcX9q22KApM1KU6CMx4XL+TciRJuI="
8+
}
9+
]
10+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIDtzCCAZ+gAwIBAgIUB/MxteHS8TrT8t3FMMXzr7a/1hMwDQYJKoZIhvcNAQEL
3+
BQAwXjELMAkGA1UEBhMCRVMxEzARBgNVBAgMClNvbWUtU3RhdGUxEjAQBgNVBAoM
4+
CUNoYWlubG9vcDESMBAGA1UECwwJY2hhaW5sb29wMRIwEAYDVQQDDAljaGFpbmxv
5+
b3AwHhcNMjQwNjE4MTYzODQ4WhcNMjQwNjE4MTY0ODQ4WjAvMS0wKwYDVQQKEyQ1
6+
ZTkwMTNjNy1hN2Y1LTRiNGQtOGM0Yi02NWZiMGE3OGRkMjUwWTATBgcqhkjOPQIB
7+
BggqhkjOPQMBBwNCAASEWfjdnjQEikYN3lw+Lk/XPby1eQLxUdMrCKdtne4qJhL7
8+
uOEvYUo94eTV+SHlStkDG5xDFsrdhW3ywRlUhN8mo2cwZTAOBgNVHQ8BAf8EBAMC
9+
B4AwEwYDVR0lBAwwCgYIKwYBBQUHAwMwHQYDVR0OBBYEFI2pKlr87YOANgRROdEj
10+
oKbI82t8MB8GA1UdIwQYMBaAFKDk5Tdk8eOOMf/+GDw+62HNH6XKMA0GCSqGSIb3
11+
DQEBCwUAA4ICAQBd9UnLEbmNSUMVSviiNgdgKl3vke6Ep3exRzhauhVrEy1ZTYqN
12+
pXDh2gKbPuOWtJqVbHqkYA9PN2M7N6o3+QDy7bDWPGs+HRMttB19hKOlyk2KJ5Yj
13+
VpNF7T+/muvlyeiCPv6fIsEMxSlf8QaH88bRcFReIMeRv585nMJFhsYv93yD7SnK
14+
wtddZOZ+9HdJJEQ+BzomkWUxSU9ITtUUZgeU2zLkSKkkgPFz05a2C7I4haCpURRn
15+
jVtv878XjsEfL5Qj1OrnBXoW6bKdEkqQnWEAFA5hIF0+kbgtM33U/2gpMRsBUyIc
16+
E/JAmTWtMEVnuhvxilh4d9L78+cMRNTH9RHV30CouY5SVSNtvzcmq8G2AIkJEM3N
17+
sTUPyL+Je29Eus9/1id9iLYgKLX1qOCFdDpzUKOdlBolRCh/Y75H+mw9PaCoR/aa
18+
pVtb9mMqg5SUc80XvmQAHIROxr4g1g3Pq7/ZyZLlFV/mAotGPEad2oAQ4po5luUy
19+
CZnBYquOGJMWMjJfhV6uZUNPiZCkxgJUdwssS3WZV6P+Dd89S3QcRG6ljTdnHJid
20+
4yVyt1YBowPjhktixHtz/1e/hAyWF79SPVO5I81CVamAJ0R/mpsf8IXjq4DsymQF
21+
JBdVbWQgu8Ru0eFXdoUrLp5CfQaVRA6qtxgb0vBmU2EGxWHiGLsNkHyxFg==
22+
-----END CERTIFICATE-----
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"payloadType": "application/vnd.in-toto+json",
3+
"payload": "eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjEiLCAic3ViamVjdCI6W3sibmFtZSI6ImNoYWlubG9vcC53b3JrZmxvdy5teXdmIiwgImRpZ2VzdCI6eyJzaGEyNTYiOiJiODE3MjZiMDJkNmVlM2IyZDZjZjRjYWFhNmQ5ODQ2NzQzZTg4MmE1NGY3NTk0ZjJlMWVjZmM3MTBjZmUzYTNjIn19LCB7Im5hbWUiOiJnaXQuaGVhZCIsICJkaWdlc3QiOnsic2hhMSI6ImY5ZjI0OTNhNjQ3NDczYzBiZTBjODgxNGQzZDMyYjE3ZDYxNWZhMzgifSwgImFubm90YXRpb25zIjp7ImF1dGhvci5lbWFpbCI6ImppcGFyaXNAY2hhaW5sb29wLmRldiIsICJhdXRob3IubmFtZSI6Ikpvc2UgSS4gUGFyaXMiLCAiZGF0ZSI6IjIwMjQtMDYtMThUMTY6MTE6NThaIiwgIm1lc3NhZ2UiOiJkcmFmdCB2ZXJpZmllciB3aXRoIGNlcnRcblxuU2lnbmVkLW9mZi1ieTogSm9zZSBJLiBQYXJpcyA8amlwYXJpc0BjaGFpbmxvb3AuZGV2PlxuIiwgInJlbW90ZXMiOlt7Im5hbWUiOiJvcmlnaW4iLCAidXJsIjoiZ2l0QGdpdGh1Yi5jb206amlwYXJpcy9jaGFpbmxvb3AuZ2l0In0sIHsibmFtZSI6InVwc3RyZWFtIiwgInVybCI6ImdpdEBnaXRodWIuY29tOmNoYWlubG9vcC1kZXYvY2hhaW5sb29wLmdpdCJ9XX19XSwgInByZWRpY2F0ZVR5cGUiOiJjaGFpbmxvb3AuZGV2L2F0dGVzdGF0aW9uL3YwLjIiLCAicHJlZGljYXRlIjp7ImJ1aWxkVHlwZSI6ImNoYWlubG9vcC5kZXYvd29ya2Zsb3dydW4vdjAuMSIsICJidWlsZGVyIjp7ImlkIjoiY2hhaW5sb29wLmRldi9jbGkvZGV2QHNoYTI1Njo5NGMxOWJjZDQ4ZjE2MDMyZTU1OTI5ZDdmMTVmMTU2ZWNiZGQxZjk5YThhYmIzMjI2NzFjY2FkOGEzMmI1YzVkIn0sICJtYXRlcmlhbHMiOlt7ImFubm90YXRpb25zIjp7ImNoYWlubG9vcC5tYXRlcmlhbC5jYXMuaW5saW5lIjp0cnVlLCAiY2hhaW5sb29wLm1hdGVyaWFsLm5hbWUiOiJtYXRlcmlhbC0xNzE4NzI5Njk0NjgwNTAxMDAwIiwgImNoYWlubG9vcC5tYXRlcmlhbC50eXBlIjoiQVJUSUZBQ1QifSwgImRpZ2VzdCI6eyJzaGEyNTYiOiJlM2IwYzQ0Mjk4ZmMxYzE0OWFmYmY0Yzg5OTZmYjkyNDI3YWU0MWU0NjQ5YjkzNGNhNDk1OTkxYjc4NTJiODU1In0sICJuYW1lIjoiZXZpZGVuY2UudHh0In1dLCAibWV0YWRhdGEiOnsiZmluaXNoZWRBdCI6IjIwMjQtMDYtMThUMTY6NTU6MDYuMzMzNzY3WiIsICJpbml0aWFsaXplZEF0IjoiMjAyNC0wNi0xOFQxNjo1NDo0Ny4yMjI5NjFaIiwgIm5hbWUiOiJteXdmIiwgIm9yZ2FuaXphdGlvbiI6ImZvY3VzZWQtd3UtNTUxMiIsICJwcm9qZWN0IjoibXlwcm9qZWN0IiwgInRlYW0iOiIiLCAid29ya2Zsb3dJRCI6ImViN2I0NjMzLTk2ZTItNGVmZS1iMjNmLWY2NjdmM2Y3YWNkYyIsICJ3b3JrZmxvd1J1bklEIjoiOWZlYTUxNGYtY2MzZS00MzI5LWJiYzUtY2QxYWVmYjAzNTVjIn0sICJydW5uZXJUeXBlIjoiUlVOTkVSX1RZUEVfVU5TUEVDSUZJRUQifX0=",
4+
"signatures": [
5+
{
6+
"keyid": "",
7+
"sig": "MEUCIQDzR5618pisxhyD9/Yvac337wXM9X1Kw3hT80/vQhfpJgIgBUEFO0KyvJ6fwwmxORr4EvtI1T2qFxLYBZXELbQbSMo="
8+
}
9+
]
10+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
-----BEGIN PUBLIC KEY-----
2+
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEALrHMpLAMILsB75Gmh5M9d8zt/j
3+
xv5PdUZ/LKXzgsGfjjGE8yC2lGV1xZhroXvietg3qKbjJHqFsWf0FVWBhQ==
4+
-----END PUBLIC KEY-----

app/cli/internal/action/workflow_run_describe.go

Lines changed: 67 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,20 @@
1616
package action
1717

1818
import (
19+
"bytes"
1920
"context"
21+
"crypto/x509"
2022
"errors"
2123
"fmt"
2224
"sort"
2325

2426
pb "github.com/chainloop-dev/chainloop/app/controlplane/api/controlplane/v1"
2527
"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"
2630
sigs "github.com/sigstore/cosign/v2/pkg/signature"
31+
"github.com/sigstore/sigstore/pkg/cryptoutils"
32+
"github.com/sigstore/sigstore/pkg/signature"
2733

2834
intoto "github.com/in-toto/attestation/go/v1"
2935
"github.com/secure-systems-lab/go-securesystemslib/dsse"
@@ -81,14 +87,21 @@ func NewWorkflowRunDescribe(cfg *ActionsOpts) *WorkflowRunDescribe {
8187
return &WorkflowRunDescribe{cfg}
8288
}
8389

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) {
8598
client := pb.NewWorkflowRunServiceClient(action.cfg.CPConnection)
8699

87100
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}
92105
}
93106

94107
resp, err := client.View(ctx, req)
@@ -119,8 +132,8 @@ func (action *WorkflowRunDescribe) Run(ctx context.Context, runID string, digest
119132
return nil, err
120133
}
121134

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 {
124137
action.cfg.Logger.Debug().Err(err).Msg("verifying the envelope")
125138
return nil, errors.New("invalid signature, did you provide the right key?")
126139
}
@@ -196,19 +209,59 @@ func materialPBToAction(in *pb.AttestationItem_Material) *Material {
196209
return m
197210
}
198211

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+
}
205245
}
206246

207247
dsseVerifier, err := dsse.NewEnvelopeVerifier(&sigdsee.VerifierAdapter{SignatureVerifier: verifier})
208248
if err != nil {
209-
return err
249+
return fmt.Errorf("creating DSSE verifier: %w", err)
210250
}
211251

212252
_, err = dsseVerifier.Verify(ctx, e)
213253
return err
214254
}
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+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
//
2+
// Copyright 2024 The Chainloop Authors.
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
16+
package action
17+
18+
import (
19+
"context"
20+
"encoding/json"
21+
"os"
22+
"testing"
23+
24+
"github.com/secure-systems-lab/go-securesystemslib/dsse"
25+
"github.com/stretchr/testify/suite"
26+
)
27+
28+
type WorkflowRunDescribeTestSuite struct {
29+
suite.Suite
30+
}
31+
32+
func TestWorkflowRunDescribe(t *testing.T) {
33+
suite.Run(t, new(WorkflowRunDescribeTestSuite))
34+
}
35+
36+
func (s *WorkflowRunDescribeTestSuite) SetupTest() {
37+
38+
}
39+
40+
func (s *WorkflowRunDescribeTestSuite) TestVerifyEnvelope() {
41+
s.Run("fails if no key or cert is provided", func() {
42+
err := verifyEnvelope(context.TODO(), nil, &WorkflowRunDescribeOpts{})
43+
s.Error(err, "no public key or cert path specified")
44+
})
45+
46+
s.Run("verifies when signed with cosign", func() {
47+
envelope, err := readEnvelope("testdata/cosign-attestation.json")
48+
s.Require().NoError(err)
49+
err = verifyEnvelope(context.TODO(), envelope, &WorkflowRunDescribeOpts{PublicKeyRef: "testdata/cosign.pub"})
50+
s.NoError(err)
51+
})
52+
53+
s.Run("verifies when signed with certificate", func() {
54+
envelope, err := readEnvelope("testdata/cert-attestation.json")
55+
s.Require().NoError(err)
56+
err = verifyEnvelope(context.TODO(), envelope, &WorkflowRunDescribeOpts{
57+
CertPath: "testdata/cert.pem",
58+
CertChainPath: "testdata/ca.pub",
59+
})
60+
s.NoError(err)
61+
})
62+
}
63+
64+
func readEnvelope(path string) (*dsse.Envelope, error) {
65+
f, err := os.ReadFile(path)
66+
if err != nil {
67+
return nil, err
68+
}
69+
var envelope dsse.Envelope
70+
err = json.Unmarshal(f, &envelope)
71+
if err != nil {
72+
return nil, err
73+
}
74+
return &envelope, nil
75+
}

0 commit comments

Comments
 (0)