Skip to content

Commit b918a3d

Browse files
author
Kugamoorthy Gajananan
authored
Fix/sign yaml (#332)
* Feature: Integrated keyless signing and verification mechnism that uses sigstore/cosgin with IShield * Feature: Integrated keyless signing and verification mechnism that uses sigstore/cosgin with IShield, added message matching, bundle verification steps, compress annotations, refactor
1 parent 319bf26 commit b918a3d

File tree

8 files changed

+2804
-0
lines changed

8 files changed

+2804
-0
lines changed

cmd/cli/key.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package cli
2+
3+
import (
4+
"context"
5+
"io/ioutil"
6+
"path/filepath"
7+
"strings"
8+
9+
"github.com/sigstore/cosign/pkg/cosign"
10+
"github.com/sigstore/sigstore/pkg/kms"
11+
"github.com/sigstore/sigstore/pkg/signature"
12+
)
13+
14+
func loadKey(keyPath string, pf cosign.PassFunc) (signature.ECDSASignerVerifier, error) {
15+
kb, err := ioutil.ReadFile(filepath.Clean(keyPath))
16+
if err != nil {
17+
return signature.ECDSASignerVerifier{}, err
18+
}
19+
pass, err := pf(false)
20+
if err != nil {
21+
return signature.ECDSASignerVerifier{}, err
22+
}
23+
return cosign.LoadECDSAPrivateKey(kb, pass)
24+
}
25+
26+
func signerFromKeyRef(ctx context.Context, keyRef string, pf cosign.PassFunc) (signature.Signer, error) {
27+
return signerVerifierFromKeyRef(ctx, keyRef, pf)
28+
}
29+
30+
func signerVerifierFromKeyRef(ctx context.Context, keyRef string, pf cosign.PassFunc) (signature.SignerVerifier, error) {
31+
for prefix := range kms.ProvidersMux().Providers() {
32+
if strings.HasPrefix(keyRef, prefix) {
33+
return kms.Get(ctx, keyRef)
34+
}
35+
}
36+
return loadKey(keyRef, pf)
37+
}
38+
39+
func publicKeyFromKeyRef(ctx context.Context, keyRef string) (cosign.PublicKey, error) {
40+
return cosign.LoadPublicKey(ctx, keyRef)
41+
}

cmd/cli/signyaml.go

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
package cli
2+
3+
import (
4+
"context"
5+
_ "crypto/sha256" // for `crypto.SHA256`
6+
"encoding/base64"
7+
"encoding/json"
8+
"flag"
9+
"fmt"
10+
"os"
11+
"strings"
12+
13+
gyaml "github.com/ghodss/yaml"
14+
"github.com/peterbourgon/ff/v3/ffcli"
15+
"github.com/pkg/errors"
16+
"gopkg.in/yaml.v2"
17+
18+
"github.com/IBM/integrity-enforcer/cmd/pkg/signyaml"
19+
"github.com/sigstore/cosign/pkg/cosign"
20+
"github.com/sigstore/cosign/pkg/cosign/fulcio"
21+
"github.com/sigstore/rekor/pkg/generated/models"
22+
23+
"github.com/sigstore/cosign/pkg/cosign/pivkey"
24+
"github.com/sigstore/sigstore/pkg/signature"
25+
)
26+
27+
// VerifyCommand verifies a signature on a supplied container image
28+
type SignYamlCommand struct {
29+
Upload bool
30+
KeyRef string
31+
Sk bool
32+
Annotations *map[string]interface{}
33+
PayloadPath string
34+
Pf cosign.PassFunc
35+
}
36+
37+
type annotationsMap struct {
38+
annotations map[string]interface{}
39+
}
40+
41+
func (a *annotationsMap) Set(s string) error {
42+
if a.annotations == nil {
43+
a.annotations = map[string]interface{}{}
44+
}
45+
kvp := strings.SplitN(s, "=", 2)
46+
if len(kvp) != 2 {
47+
return fmt.Errorf("invalid flag: %s, expected key=value", s)
48+
}
49+
50+
a.annotations[kvp[0]] = kvp[1]
51+
return nil
52+
}
53+
54+
func (a *annotationsMap) String() string {
55+
s := []string{}
56+
for k, v := range a.annotations {
57+
s = append(s, fmt.Sprintf("%s=%s", k, v))
58+
}
59+
return strings.Join(s, ",")
60+
}
61+
62+
func SignYaml() *ffcli.Command {
63+
64+
cmd := SignYamlCommand{}
65+
flagset := flag.NewFlagSet("yamlsign sign", flag.ExitOnError)
66+
annotations := annotationsMap{}
67+
68+
flagset.StringVar(&cmd.KeyRef, "key", "", "path to the public key file, URL, or KMS URI")
69+
flagset.BoolVar(&cmd.Sk, "sk", false, "whether to use a hardware security key")
70+
flagset.BoolVar(&cmd.Upload, "upload", true, "whether to upload the signature")
71+
flagset.StringVar(&cmd.PayloadPath, "payload", "", "path to the yaml file")
72+
73+
flagset.Var(&annotations, "a", "extra key=value pairs to sign")
74+
return &ffcli.Command{
75+
Name: "sign",
76+
ShortUsage: "yamlsign sign -key <key path>|<kms uri> [-payload <path>] [-a key=value] [-upload=true|false] [-f] <image uri>",
77+
ShortHelp: `Sign the supplied yaml file.`,
78+
LongHelp: `Sign the supplied yaml file.
79+
80+
EXAMPLES
81+
# sign a yaml file with Google sign-in
82+
yamlsign sign -payload <yaml file>
83+
84+
# sign a yaml file with a local key pair file
85+
yamlsign sign -key key.pub -payload <yaml file>
86+
87+
# sign a yaml file and add annotations
88+
yamlsign sign -key key.pub -a key1=value1 -a key2=value2 -payload <yaml file>
89+
90+
# sign a yaml file with a key pair stored in Google Cloud KMS
91+
yamlsign sign -key gcpkms://projects/<PROJECT>/locations/global/keyRings/<KEYRING>/cryptoKeys/<KEY> -payload <yaml file>`,
92+
FlagSet: flagset,
93+
Exec: cmd.Exec,
94+
}
95+
96+
}
97+
98+
func (c *SignYamlCommand) Exec(ctx context.Context, args []string) error {
99+
100+
payloadPath := c.PayloadPath
101+
102+
mPayload, _ := signyaml.FetchYamlContent(payloadPath)
103+
104+
cleanPayloadYaml, err := yaml.Marshal(mPayload)
105+
106+
// The payload can be specified via a flag to skip generation.
107+
var payloadJson []byte
108+
payloadJson, _ = gyaml.YAMLToJSON(cleanPayloadYaml)
109+
110+
fmt.Println("payloadJson")
111+
fmt.Println(string(payloadJson))
112+
113+
if err != nil {
114+
return errors.Wrap(err, "payloadJson")
115+
}
116+
117+
var signer signature.Signer
118+
119+
var cert string
120+
var pemBytes []byte
121+
switch {
122+
case c.Sk:
123+
sk, err := pivkey.NewSignerVerifier()
124+
if err != nil {
125+
return err
126+
}
127+
signer = sk
128+
pemBytes, err = cosign.PublicKeyPem(ctx, sk)
129+
if err != nil {
130+
return err
131+
}
132+
case c.KeyRef != "":
133+
k, err := signerVerifierFromKeyRef(ctx, c.KeyRef, c.Pf)
134+
if err != nil {
135+
return errors.Wrap(err, "reading key")
136+
}
137+
signer = k
138+
default: // Keyless!
139+
fmt.Fprintln(os.Stderr, "Generating ephemeral keys...")
140+
k, err := fulcio.NewSigner(ctx)
141+
if err != nil {
142+
return errors.Wrap(err, "getting key from Fulcio")
143+
}
144+
signer = k
145+
cert, _ = k.Cert, k.Chain
146+
pemBytes = []byte(cert)
147+
}
148+
149+
sig, _, err := signer.Sign(ctx, payloadJson)
150+
if err != nil {
151+
return errors.Wrap(err, "signing")
152+
}
153+
154+
if !c.Upload {
155+
fmt.Println(base64.StdEncoding.EncodeToString(sig))
156+
return nil
157+
}
158+
159+
fmt.Println("----------------------")
160+
fmt.Println("Yaml Signing Completed !!!")
161+
fmt.Println("----------------------")
162+
163+
// Upload the cert or the public key, depending on what we have
164+
var rekorBytes []byte
165+
if cert != "" {
166+
rekorBytes = []byte(cert)
167+
} else {
168+
pemBytes, err := cosign.PublicKeyPem(ctx, signer)
169+
if err != nil {
170+
return nil
171+
}
172+
rekorBytes = pemBytes
173+
}
174+
175+
entry, err := cosign.UploadTLog(sig, payloadJson, rekorBytes)
176+
if err != nil {
177+
return err
178+
}
179+
fmt.Println("tlog entry created with index: ", *entry.LogIndex)
180+
181+
bund, err := bundle(entry)
182+
if err != nil {
183+
return errors.Wrap(err, "bundle")
184+
}
185+
186+
bundleJson, err := json.Marshal(bund)
187+
188+
fmt.Println("bundleJson", string(bundleJson))
189+
190+
signyaml.WriteYamlContent(sig, pemBytes, bundleJson, mPayload, payloadPath)
191+
192+
return nil
193+
}
194+
195+
func bundle(entry *models.LogEntryAnon) (*cosign.Bundle, error) {
196+
if entry.Verification == nil {
197+
return nil, nil
198+
}
199+
return &cosign.Bundle{
200+
SignedEntryTimestamp: entry.Verification.SignedEntryTimestamp,
201+
Body: entry.Body,
202+
IntegratedTime: entry.IntegratedTime,
203+
LogIndex: entry.LogIndex,
204+
}, nil
205+
}

0 commit comments

Comments
 (0)