Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add experimental TLOG model to cosign #44

Merged
merged 8 commits into from
Mar 5, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ jobs:
# Checks out a copy of your repository on the ubuntu-latest machine
- uses: actions/checkout@v2
- uses: actions/setup-go@v2
- run: go test ./...
- run: go build -o cosign ./cmd/
- run: ./test/e2e_test.sh
golangci:
name: lint
runs-on: ubuntu-latest
Expand Down
12 changes: 11 additions & 1 deletion cmd/cli/sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ import (
"github.com/google/go-containerregistry/pkg/name"
"github.com/google/go-containerregistry/pkg/v1/remote"
"github.com/peterbourgon/ff/v3/ffcli"
"github.com/pkg/errors"
"github.com/sigstore/cosign/pkg/cosign"
"github.com/sigstore/cosign/pkg/cosign/tlog"
)

type annotationsMap struct {
Expand Down Expand Up @@ -134,5 +136,13 @@ func SignCmd(ctx context.Context, keyPath string,
dstTag := ref.Context().Tag(cosign.Munge(get.Descriptor))

fmt.Fprintln(os.Stderr, "Pushing signature to:", dstTag.String())
return cosign.Upload(signature, payload, dstTag)
if err := cosign.Upload(signature, payload, dstTag); err != nil {
return err
}

pubKey, err := cosign.LoadPublicKeyFromPrivKey(pk)
if err != nil {
return errors.Wrap(err, "loading public key from priv key")
}
return tlog.Upload(signature, payload, pubKey)
}
3 changes: 2 additions & 1 deletion cmd/cli/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/google/go-containerregistry/pkg/name"
"github.com/peterbourgon/ff/v3/ffcli"
"github.com/sigstore/cosign/pkg/cosign"
"github.com/sigstore/cosign/pkg/cosign/tlog"
)

func Verify() *ffcli.Command {
Expand Down Expand Up @@ -58,7 +59,7 @@ func Verify() *ffcli.Command {
for _, vp := range verified {
fmt.Println(string(vp.Payload))
}
return nil
return tlog.Verify(verified, *key)
},
}
}
Expand Down
7 changes: 6 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,15 @@ module github.com/sigstore/cosign
go 1.15

require (
github.com/google/go-cmp v0.5.2
github.com/go-openapi/strfmt v0.20.0
github.com/go-openapi/swag v0.19.14
github.com/google/go-cmp v0.5.4
github.com/google/go-containerregistry v0.4.1-0.20210206001656-4d068fbcb51f
github.com/google/trillian v1.3.13
github.com/open-policy-agent/opa v0.26.0
github.com/peterbourgon/ff/v3 v3.0.0
github.com/pkg/errors v0.9.1
github.com/sigstore/rekor v0.1.1-0.20210228052401-f0b66bf3835c
github.com/theupdateframework/go-tuf v0.0.0-20201230183259-aee6270feb55
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf
)
632 changes: 632 additions & 0 deletions go.sum

Large diffs are not rendered by default.

85 changes: 85 additions & 0 deletions pkg/cosign/tlog/upload.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
Copyright The Rekor 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 tlog

import (
"fmt"
"os"

"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/pkg/errors"

"github.com/sigstore/rekor/cmd/cli/app"
"github.com/sigstore/rekor/pkg/generated/client/entries"
"github.com/sigstore/rekor/pkg/generated/models"
rekord_v001 "github.com/sigstore/rekor/pkg/types/rekord/v0.0.1"
)

const (
Env = "TLOG"
ServerEnv = "REKOR_SERVER"
rekorServer = "https://api.rekor.dev"
)

// Upload will upload the signature, public key and payload to the tlog
func Upload(signature, payload, publicKey []byte) error {
if os.Getenv(Env) != "1" {
return nil
}
rekorClient, err := app.GetRekorClient(tlogServer())
if err != nil {
return err
}
re := rekorEntry(payload, signature, publicKey)
returnVal := models.Rekord{
APIVersion: swag.String(re.APIVersion()),
Spec: re.RekordObj,
}
params := entries.NewCreateLogEntryParams()
params.SetProposedEntry(&returnVal)
if _, err := rekorClient.Entries.CreateLogEntry(params); err != nil {
return errors.Wrap(err, "creating log entry")
}
fmt.Println("Sucessfully appended to transparency log")
return nil
}

func rekorEntry(payload, signature, pubKey []byte) rekord_v001.V001Entry {
return rekord_v001.V001Entry{
RekordObj: models.RekordV001Schema{
Data: &models.RekordV001SchemaData{
Content: strfmt.Base64(payload),
},
Signature: &models.RekordV001SchemaSignature{
Content: strfmt.Base64(signature),
Format: models.RekordV001SchemaSignatureFormatX509,
PublicKey: &models.RekordV001SchemaSignaturePublicKey{
Content: strfmt.Base64(pubKey),
},
},
},
}
}

// tlogServer returns the name of the tlog server, can be overwritten via env var
func tlogServer() string {
if s := os.Getenv(ServerEnv); s != "" {
return s
}
return rekorServer
}
106 changes: 106 additions & 0 deletions pkg/cosign/tlog/verify.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
Copyright The Rekor 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 tlog

import (
"encoding/base64"
"encoding/hex"
"fmt"
"io/ioutil"
"os"

"github.com/go-openapi/swag"
"github.com/google/trillian/merkle/logverifier"
"github.com/google/trillian/merkle/rfc6962/hasher"
"github.com/pkg/errors"

"github.com/sigstore/cosign/pkg/cosign"
"github.com/sigstore/rekor/cmd/cli/app"
"github.com/sigstore/rekor/pkg/generated/client/entries"
"github.com/sigstore/rekor/pkg/generated/models"
)

// Verify will verify the signature, public key and payload are in the tlog, as well as verifying the signature itself
// most of this code taken from github.com/sigstore/rekor/cmd/cli/app/verify.go
func Verify(signedPayload []cosign.SignedPayload, publicKey string) error {
if os.Getenv(Env) != "1" {
return nil
}
pubKey, err := ioutil.ReadFile(publicKey)
if err != nil {
return errors.Wrap(err, "reading public key")
}
rekorClient, err := app.GetRekorClient(tlogServer())
if err != nil {
return err
}

for _, sp := range signedPayload {
params := entries.NewGetLogEntryProofParams()
searchParams := entries.NewSearchLogQueryParams()
searchLogQuery := models.SearchLogQuery{}
signature, err := base64.StdEncoding.DecodeString(sp.Base64Signature)
if err != nil {
return errors.Wrap(err, "decoding base64 signature")
}
re := rekorEntry(sp.Payload, signature, pubKey)
entry := &models.Rekord{
APIVersion: swag.String(re.APIVersion()),
Spec: re.RekordObj,
}
entries := []models.ProposedEntry{entry}
searchLogQuery.SetEntries(entries)

searchParams.SetEntry(&searchLogQuery)
resp, err := rekorClient.Entries.SearchLogQuery(searchParams)
if err != nil {
return errors.Wrap(err, "searching log query")
}
if len(resp.Payload) == 0 {
return fmt.Errorf("entry in log cannot be located")
} else if len(resp.Payload) > 1 {
return fmt.Errorf("multiple entries returned; this should not happen")
}
logEntry := resp.Payload[0]
if len(logEntry) != 1 {
return errors.New("UUID value can not be extracted")
}
for k := range logEntry {
params.EntryUUID = k
}
lep, err := rekorClient.Entries.GetLogEntryProof(params)
if err != nil {
return err
}

hashes := [][]byte{}
for _, h := range lep.Payload.Hashes {
hb, _ := hex.DecodeString(h)
hashes = append(hashes, hb)
}

rootHash, _ := hex.DecodeString(*lep.Payload.RootHash)
leafHash, _ := hex.DecodeString(params.EntryUUID)

v := logverifier.New(hasher.DefaultHasher)
if err := v.VerifyInclusionProof(*lep.Payload.LogIndex, *lep.Payload.TreeSize, hashes, rootHash, leafHash); err != nil {
return errors.Wrap(err, "verifying inclusion proof")
}
}
fmt.Println("Verified signature, payload and public key exist in transparency log")
return nil
}
12 changes: 12 additions & 0 deletions pkg/cosign/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,18 @@ func LoadPublicKey(keyRef string) (ed25519.PublicKey, error) {
return ed, nil
}

func LoadPublicKeyFromPrivKey(pk ed25519.PrivateKey) ([]byte, error) {
pubKey, err := x509.MarshalPKIXPublicKey(pk.Public())
if err != nil {
return nil, err
}
pubBytes := pem.EncodeToMemory(&pem.Block{
Type: "PUBLIC KEY",
Bytes: pubKey,
})
return pubBytes, nil
}

func VerifySignature(pubkey ed25519.PublicKey, base64sig string, payload []byte) error {
signature, err := base64.StdEncoding.DecodeString(base64sig)
if err != nil {
Expand Down
33 changes: 33 additions & 0 deletions test/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"github.com/google/go-containerregistry/pkg/v1/remote"
"github.com/sigstore/cosign/cmd/cli"
"github.com/sigstore/cosign/pkg/cosign"
"github.com/sigstore/cosign/pkg/cosign/tlog"
)

var keyPass = []byte("hello")
Expand Down Expand Up @@ -186,6 +187,38 @@ func TestUploadDownload(t *testing.T) {
}
}

func TestTlog(t *testing.T) {
if err := os.Setenv(tlog.ServerEnv, "http://127.0.0.1:3000"); err != nil {
t.Fatalf("error setitng env: %v", err)
}
defer os.Unsetenv(tlog.ServerEnv)
if err := os.Setenv(tlog.Env, "1"); err != nil {
t.Fatalf("error setting env: %v", err)
}
defer os.Unsetenv(tlog.Env)

repo, stop := reg(t)
defer stop()
td := t.TempDir()

imgName := path.Join(repo, "cosign-e2e")

_, _, cleanup := mkimage(t, imgName)
defer cleanup()

_, privKeyPath, pubKeyPath := keypair(t, td)

ctx := context.Background()
// Verify should fail at first
mustErr(verify(pubKeyPath, imgName, true, nil), t)

// Now sign the image
must(cli.SignCmd(ctx, privKeyPath, imgName, true, "", nil, passFunc), t)

// Now verify should work!
must(verify(pubKeyPath, imgName, true, nil), t)
}

func mkfile(contents, td string, t *testing.T) string {
f, err := ioutil.TempFile(td, "")
if err != nil {
Expand Down
37 changes: 37 additions & 0 deletions test/e2e_test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#!/bin/bash
#set -ex

echo "copying rekor repo"
cd $HOME
git clone https://github.com/sigstore/rekor.git
cd rekor

echo "starting services"
docker-compose up -d

count=0

echo -n "waiting up to 60 sec for system to start"
until [ $(docker-compose ps | grep -c "(healthy)") == 3 ];
do
if [ $count -eq 6 ]; then
echo "! timeout reached"
exit 1
else
echo -n "."
sleep 10
let 'count+=1'
fi
done

echo
echo "running tests"

cd $GITHUB_WORKSPACE
go build -o cosign ./cmd/
go test ./...


echo "cleanup"
cd $HOME/rekor
docker-compose down