Skip to content

Commit

Permalink
Add experimental TLOG model to cosign (#44)
Browse files Browse the repository at this point in the history
* Start adding support for uploading to transparency log after sign

Signed-off-by: Priya Wadhwa <priyawadhwa@google.com>

* Verify log exists in transparency log when TLOG=1

Signed-off-by: Priya Wadhwa <priyawadhwa@google.com>

* Change func name to Upload to match rekor

Signed-off-by: Priya Wadhwa <priyawadhwa@google.com>

* fix int test

Signed-off-by: Priya Wadhwa <priyawadhwa@google.com>

* fix lint, use not deprecated hasher

Signed-off-by: Priya Wadhwa <priyawadhwa@google.com>

* use public key from private key for 'cosign sign', use REKOR_SERVER env var to match rekor project

Signed-off-by: Priya Wadhwa <priyawadhwa@google.com>

* revert test

Signed-off-by: Priya Wadhwa <priyawadhwa@google.com>

* Add integration test for tlog support

Sets up a local rekor server via the provided docker-compose.yaml file in the rekor repo.
I tested to make sure this test fails if the local server isn't running, which is the case.

Signed-off-by: Priya Wadhwa <priyawadhwa@google.com>
  • Loading branch information
priyawadhwa authored Mar 5, 2021
1 parent d4abc88 commit 3dbd913
Show file tree
Hide file tree
Showing 10 changed files with 925 additions and 5 deletions.
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 @@ -228,6 +229,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

0 comments on commit 3dbd913

Please sign in to comment.