Skip to content

Commit a8f9450

Browse files
authored
Convert STH to checkpoint format (sigstore#322)
* Convert STH to checkpoint format This switches away from sending the (now deprecated) Trillian LogRootV1 format over to the checkpoint format documented at https://github.com/google/trillian-examples/tree/master/formats/log Fixes: sigstore#313 Signed-off-by: Bob Callaway <bob.callaway@gmail.com>
1 parent 85787fd commit a8f9450

File tree

17 files changed

+851
-408
lines changed

17 files changed

+851
-408
lines changed

cmd/rekor-cli/app/log_info.go

Lines changed: 20 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import (
2222
"encoding/pem"
2323
"errors"
2424
"fmt"
25-
"strings"
2625
"time"
2726

2827
"github.com/google/trillian/merkle/logverifier"
@@ -34,7 +33,7 @@ import (
3433
"github.com/sigstore/rekor/cmd/rekor-cli/app/state"
3534
"github.com/sigstore/rekor/pkg/generated/client/tlog"
3635
"github.com/sigstore/rekor/pkg/log"
37-
"github.com/sigstore/rekor/pkg/verify"
36+
"github.com/sigstore/rekor/pkg/util"
3837
)
3938

4039
type logInfoCmdOutput struct {
@@ -72,14 +71,11 @@ var logInfoCmd = &cobra.Command{
7271

7372
logInfo := result.GetPayload()
7473

75-
logRoot := *logInfo.SignedTreeHead.LogRoot
76-
if logRoot == nil {
77-
return nil, errors.New("logroot should not be nil")
78-
}
79-
signature := *logInfo.SignedTreeHead.Signature
80-
if signature == nil {
81-
return nil, errors.New("signature should not be nil")
74+
sth := util.RekorSTH{}
75+
if err := sth.UnmarshalText([]byte(*logInfo.SignedTreeHead)); err != nil {
76+
return nil, err
8277
}
78+
8379
publicKey := viper.GetString("rekor_server_public_key")
8480
if publicKey == "" {
8581
// fetch key from server
@@ -100,33 +96,25 @@ var logInfoCmd = &cobra.Command{
10096
return nil, err
10197
}
10298

103-
lr, err := verify.SignedLogRoot(pub, logRoot, signature)
104-
if err != nil {
105-
return nil, err
99+
if !sth.Verify(pub) {
100+
return nil, errors.New("signature on tree head did not verify")
106101
}
102+
107103
cmdOutput := &logInfoCmdOutput{
108104
TreeSize: *logInfo.TreeSize,
109105
RootHash: *logInfo.RootHash,
110-
TimestampNanos: lr.TimestampNanos,
111-
}
112-
113-
if lr.TreeSize != uint64(*logInfo.TreeSize) {
114-
return nil, errors.New("tree size in signed tree head does not match value returned in API call")
115-
}
116-
117-
if !strings.EqualFold(hex.EncodeToString(lr.RootHash), *logInfo.RootHash) {
118-
return nil, errors.New("root hash in signed tree head does not match value returned in API call")
106+
TimestampNanos: sth.GetTimestamp(),
119107
}
120108

121109
oldState := state.Load(serverURL)
122110
if oldState != nil {
123-
persistedSize := oldState.TreeSize
124-
if persistedSize < lr.TreeSize {
125-
log.CliLogger.Infof("Found previous log state, proving consistency between %d and %d", oldState.TreeSize, lr.TreeSize)
111+
persistedSize := oldState.Size
112+
if persistedSize < sth.Size {
113+
log.CliLogger.Infof("Found previous log state, proving consistency between %d and %d", oldState.Size, sth.Size)
126114
params := tlog.NewGetLogProofParams()
127115
firstSize := int64(persistedSize)
128116
params.FirstSize = &firstSize
129-
params.LastSize = int64(lr.TreeSize)
117+
params.LastSize = int64(sth.Size)
130118
proof, err := rekorClient.Tlog.GetLogProof(params)
131119
if err != nil {
132120
return nil, err
@@ -137,25 +125,25 @@ var logInfoCmd = &cobra.Command{
137125
hashes = append(hashes, b)
138126
}
139127
v := logverifier.New(rfc6962.DefaultHasher)
140-
if err := v.VerifyConsistencyProof(firstSize, int64(lr.TreeSize), oldState.RootHash,
141-
lr.RootHash, hashes); err != nil {
128+
if err := v.VerifyConsistencyProof(firstSize, int64(sth.Size), oldState.Hash,
129+
sth.Hash, hashes); err != nil {
142130
return nil, err
143131
}
144132
log.CliLogger.Infof("Consistency proof valid!")
145-
} else if persistedSize == lr.TreeSize {
146-
if !bytes.Equal(oldState.RootHash, lr.RootHash) {
133+
} else if persistedSize == sth.Size {
134+
if !bytes.Equal(oldState.Hash, sth.Hash) {
147135
return nil, errors.New("root hash returned from server does not match previously persisted state")
148136
}
149137
log.CliLogger.Infof("Persisted log state matches the current state of the log")
150-
} else if persistedSize > lr.TreeSize {
151-
return nil, fmt.Errorf("current size of tree reported from server %d is less than previously persisted state %d", lr.TreeSize, persistedSize)
138+
} else if persistedSize > sth.Size {
139+
return nil, fmt.Errorf("current size of tree reported from server %d is less than previously persisted state %d", sth.Size, persistedSize)
152140
}
153141
} else {
154142
log.CliLogger.Infof("No previous log state stored, unable to prove consistency")
155143
}
156144

157145
if viper.GetBool("store_tree_state") {
158-
if err := state.Dump(serverURL, lr); err != nil {
146+
if err := state.Dump(serverURL, &sth); err != nil {
159147
log.CliLogger.Infof("Unable to store previous state: %v", err)
160148
}
161149
}

cmd/rekor-cli/app/root.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,10 @@ func GetRekorClient(rekorServerURL string) (*client.Rekor, error) {
131131
if viper.GetString("api-key") != "" {
132132
rt.DefaultAuthentication = httptransport.APIKeyAuth("apiKey", "query", viper.GetString("api-key"))
133133
}
134-
return client.New(rt, strfmt.Default), nil
134+
135+
registry := strfmt.Default
136+
registry.Add("signedCheckpoint", &util.SignedCheckpoint{}, util.SignedCheckpointValidator)
137+
return client.New(rt, registry), nil
135138
}
136139

137140
type urlFlag struct {

cmd/rekor-cli/app/state/state.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,13 @@ import (
2121
"os"
2222
"path/filepath"
2323

24-
"github.com/google/trillian/types"
2524
"github.com/mitchellh/go-homedir"
25+
"github.com/sigstore/rekor/pkg/util"
2626
)
2727

28-
type persistedState map[string]*types.LogRootV1
28+
type persistedState map[string]*util.RekorSTH
2929

30-
func Dump(url string, lr *types.LogRootV1) error {
30+
func Dump(url string, sth *util.RekorSTH) error {
3131
rekorDir, err := getRekorDir()
3232
if err != nil {
3333
return err
@@ -38,7 +38,7 @@ func Dump(url string, lr *types.LogRootV1) error {
3838
if state == nil {
3939
state = make(persistedState)
4040
}
41-
state[url] = lr
41+
state[url] = sth
4242

4343
b, err := json.Marshal(&state)
4444
if err != nil {
@@ -67,7 +67,7 @@ func loadStateFile() persistedState {
6767
return result
6868
}
6969

70-
func Load(url string) *types.LogRootV1 {
70+
func Load(url string) *util.RekorSTH {
7171
if state := loadStateFile(); state != nil {
7272
return state[url]
7373
}

cmd/rekor-server/app/root.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ func init() {
5959
rootCmd.PersistentFlags().String("trillian_log_server.address", "127.0.0.1", "Trillian log server address")
6060
rootCmd.PersistentFlags().Uint16("trillian_log_server.port", 8090, "Trillian log server port")
6161
rootCmd.PersistentFlags().Uint("trillian_log_server.tlog_id", 0, "Trillian tree id")
62+
rootCmd.PersistentFlags().String("rekor_server.hostname", "rekor.sigstore.dev", "public hostname of instance")
6263
rootCmd.PersistentFlags().String("rekor_server.address", "127.0.0.1", "Address to bind to")
6364
rootCmd.PersistentFlags().String("rekor_server.signer", "memory", "Rekor signer to use. Current valid options include: [gcpkms, memory]")
6465
rootCmd.PersistentFlags().String("rekor_server.timestamp_chain", "", "PEM encoded cert chain to use for timestamping")

cmd/rekor-server/app/watch.go

Lines changed: 12 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -29,17 +29,15 @@ import (
2929
_ "gocloud.dev/blob/fileblob" // fileblob
3030
_ "gocloud.dev/blob/gcsblob"
3131

32-
"github.com/google/trillian/types"
3332
"github.com/pkg/errors"
3433
"github.com/spf13/cobra"
3534
"github.com/spf13/viper"
3635
"gocloud.dev/blob"
3736

3837
"github.com/sigstore/rekor/cmd/rekor-cli/app"
3938
"github.com/sigstore/rekor/pkg/generated/client"
40-
"github.com/sigstore/rekor/pkg/generated/models"
4139
"github.com/sigstore/rekor/pkg/log"
42-
"github.com/sigstore/rekor/pkg/verify"
40+
"github.com/sigstore/rekor/pkg/util"
4341
)
4442

4543
const rekorSthBucketEnv = "REKOR_STH_BUCKET"
@@ -109,10 +107,10 @@ var watchCmd = &cobra.Command{
109107
log.Logger.Warnf("error verifiying tree: %s", err)
110108
continue
111109
}
112-
log.Logger.Infof("Found and verified state at %d %d", lr.VerifiedLogRoot.TreeSize, lr.VerifiedLogRoot.TimestampNanos)
113-
if last != nil && last.VerifiedLogRoot.TreeSize == lr.VerifiedLogRoot.TreeSize {
110+
log.Logger.Infof("Found and verified state at %d", lr.VerifiedLogRoot.Size)
111+
if last != nil && last.VerifiedLogRoot.Size == lr.VerifiedLogRoot.Size {
114112
log.Logger.Infof("Last tree size is the same as the current one: %d %d",
115-
last.VerifiedLogRoot.TreeSize, lr.VerifiedLogRoot.TreeSize)
113+
last.VerifiedLogRoot.Size, lr.VerifiedLogRoot.Size)
116114
// If it's the same, it shouldn't have changed but we'll still upload anyway
117115
// in case that failed.
118116
}
@@ -136,23 +134,17 @@ func doCheck(c *client.Rekor, pub crypto.PublicKey) (*SignedAndUnsignedLogRoot,
136134
if err != nil {
137135
return nil, errors.Wrap(err, "getting log info")
138136
}
139-
logRoot := *li.Payload.SignedTreeHead.LogRoot
140-
if logRoot == nil {
141-
return nil, errors.New("logroot should not be nil")
142-
}
143-
signature := *li.Payload.SignedTreeHead.Signature
144-
if signature == nil {
145-
return nil, errors.New("signature should not be nil")
137+
sth := util.RekorSTH{}
138+
if err := sth.UnmarshalText([]byte(*li.Payload.SignedTreeHead)); err != nil {
139+
return nil, errors.Wrap(err, "unmarshalling tree head")
146140
}
147141

148-
verifiedLogRoot, err := verify.SignedLogRoot(pub, logRoot, signature)
149-
if err != nil {
150-
return nil, errors.Wrap(err, "signing log root")
142+
if !sth.Verify(pub) {
143+
return nil, errors.Wrap(err, "signed tree head failed verification")
151144
}
152145

153146
return &SignedAndUnsignedLogRoot{
154-
SignedLogRoot: li.GetPayload().SignedTreeHead,
155-
VerifiedLogRoot: verifiedLogRoot,
147+
VerifiedLogRoot: &sth,
156148
}, nil
157149
}
158150

@@ -162,7 +154,7 @@ func uploadToBlobStorage(ctx context.Context, bucket *blob.Bucket, lr *SignedAnd
162154
return err
163155
}
164156

165-
objName := fmt.Sprintf("sth-%d.json", lr.VerifiedLogRoot.TreeSize)
157+
objName := fmt.Sprintf("sth-%d.json", lr.VerifiedLogRoot.Size)
166158
w, err := bucket.NewWriter(ctx, objName, nil)
167159
if err != nil {
168160
return err
@@ -176,6 +168,5 @@ func uploadToBlobStorage(ctx context.Context, bucket *blob.Bucket, lr *SignedAnd
176168

177169
// For JSON marshalling
178170
type SignedAndUnsignedLogRoot struct {
179-
SignedLogRoot *models.LogInfoSignedTreeHead
180-
VerifiedLogRoot *types.LogRootV1
171+
VerifiedLogRoot *util.RekorSTH
181172
}

go.mod

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ require (
1717
github.com/go-openapi/swag v0.19.15
1818
github.com/go-openapi/validate v0.20.2
1919
github.com/go-playground/validator v9.31.0+incompatible
20-
github.com/google/go-cmp v0.5.5
20+
github.com/google/go-cmp v0.5.6
2121
github.com/google/rpmpack v0.0.0-20210107155803-d6befbf05148
2222
github.com/google/trillian v1.3.14-0.20210413093047-5e12fb368c8f
2323
github.com/in-toto/in-toto-golang v0.1.1-0.20210528150343-f7dc21abaccf
@@ -40,6 +40,7 @@ require (
4040
go.uber.org/zap v1.17.0
4141
gocloud.dev v0.23.0
4242
golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf
43+
golang.org/x/mod v0.4.2
4344
golang.org/x/net v0.0.0-20210505214959-0714010a04ed
4445
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
4546
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf // indirect

go.sum

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -568,8 +568,9 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
568568
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
569569
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
570570
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
571-
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
572571
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
572+
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
573+
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
573574
github.com/google/go-containerregistry v0.4.1 h1:Lrcj2AOoZ7WKawsoKAh2O0dH0tBqMW2lTEmozmK4Z3k=
574575
github.com/google/go-containerregistry v0.4.1/go.mod h1:Ct15B4yir3PLOP5jsy0GNeYVaIZs/MK/Jz5any1wFW0=
575576
github.com/google/go-github/v28 v28.1.1/go.mod h1:bsqJWQX05omyWVmc00nEUql9mhQyv38lDZ8kPZcQVoM=

openapi.yaml

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -444,25 +444,9 @@ definitions:
444444
description: The current number of nodes in the merkle tree
445445
minimum: 1
446446
signedTreeHead:
447-
type: object
447+
type: string
448+
format: signedCheckpoint
448449
description: The current signed tree head
449-
properties:
450-
keyHint:
451-
type: string
452-
description: Key hint
453-
format: byte
454-
logRoot:
455-
type: string
456-
description: Log root
457-
format: byte
458-
signature:
459-
type: string
460-
description: Signature for log root
461-
format: byte
462-
required:
463-
- keyHint
464-
- logRoot
465-
- signature
466450
required:
467451
- rootHash
468452
- treeSize

pkg/api/error.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ const (
4545
lastSizeGreaterThanKnown = "The tree size requested(%d) was greater than what is currently observable(%d)"
4646
signingError = "Error signing"
4747
failedToGenerateTimestampResponse = "Error generating timestamp response"
48+
sthGenerateError = "Error generating signed tree head"
4849
)
4950

5051
func errorMsg(message string, code int) *models.Error {

pkg/api/tlog.go

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,22 @@
1616
package api
1717

1818
import (
19+
"encoding/base64"
20+
"encoding/binary"
1921
"encoding/hex"
2022
"fmt"
2123
"net/http"
24+
"time"
2225

2326
"github.com/go-openapi/runtime/middleware"
24-
"github.com/go-openapi/strfmt"
2527
"github.com/google/trillian/types"
28+
"github.com/spf13/viper"
29+
"golang.org/x/mod/sumdb/note"
2630
"google.golang.org/grpc/codes"
2731

2832
"github.com/sigstore/rekor/pkg/generated/models"
2933
"github.com/sigstore/rekor/pkg/generated/restapi/operations/tlog"
34+
"github.com/sigstore/rekor/pkg/util"
3035
)
3136

3237
// GetLogInfoHandler returns the current size of the tree and the STH
@@ -46,25 +51,42 @@ func GetLogInfoHandler(params tlog.GetLogInfoParams) middleware.Responder {
4651

4752
hashString := hex.EncodeToString(root.RootHash)
4853
treeSize := int64(root.TreeSize)
49-
logRoot := strfmt.Base64(result.SignedLogRoot.GetLogRoot())
54+
55+
sth := util.RekorSTH{
56+
SignedCheckpoint: util.SignedCheckpoint{
57+
Checkpoint: util.Checkpoint{
58+
Ecosystem: "Rekor",
59+
Size: root.TreeSize,
60+
Hash: root.RootHash,
61+
},
62+
},
63+
}
64+
sth.SetTimestamp(uint64(time.Now().UnixNano()))
65+
// TODO: once api.signer implements crypto.Signer, switch to using Sign() API on Checkpoint
5066

5167
// sign the log root ourselves to get the log root signature
52-
sig, _, err := api.signer.Sign(params.HTTPRequest.Context(), result.SignedLogRoot.GetLogRoot())
68+
cpString, _ := sth.Checkpoint.MarshalText()
69+
sig, _, err := api.signer.Sign(params.HTTPRequest.Context(), []byte(cpString))
5370
if err != nil {
54-
return handleRekorAPIError(params, http.StatusInternalServerError, fmt.Errorf("signing error: %w", err), trillianCommunicationError)
71+
return handleRekorAPIError(params, http.StatusInternalServerError, fmt.Errorf("signing error: %w", err), signingError)
5572
}
5673

57-
signature := strfmt.Base64(sig)
74+
sth.Signatures = append(sth.Signatures, note.Signature{
75+
Name: viper.GetString("rekor_server.hostname"),
76+
Hash: binary.BigEndian.Uint32([]byte(api.pubkeyHash)[0:4]),
77+
Base64: base64.StdEncoding.EncodeToString(sig),
78+
})
5879

59-
sth := models.LogInfoSignedTreeHead{
60-
LogRoot: &logRoot,
61-
Signature: &signature,
80+
scBytes, err := sth.MarshalText()
81+
if err != nil {
82+
return handleRekorAPIError(params, http.StatusInternalServerError, fmt.Errorf("marshalling error: %w", err), sthGenerateError)
6283
}
84+
scString := string(scBytes)
6385

6486
logInfo := models.LogInfo{
6587
RootHash: &hashString,
6688
TreeSize: &treeSize,
67-
SignedTreeHead: &sth,
89+
SignedTreeHead: &scString,
6890
}
6991
return tlog.NewGetLogInfoOK().WithPayload(&logInfo)
7092
}

0 commit comments

Comments
 (0)