1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package app
17
18 import (
19 "context"
20 "crypto"
21 "crypto/x509"
22 "encoding/pem"
23 "errors"
24 "fmt"
25
26 "github.com/go-openapi/swag"
27 rclient "github.com/sigstore/rekor/pkg/generated/client"
28 "github.com/sigstore/rekor/pkg/generated/models"
29
30 "github.com/sigstore/rekor/pkg/verify"
31 "github.com/spf13/cobra"
32 "github.com/spf13/viper"
33
34 "github.com/sigstore/rekor/cmd/rekor-cli/app/format"
35 "github.com/sigstore/rekor/cmd/rekor-cli/app/state"
36 "github.com/sigstore/rekor/pkg/client"
37 "github.com/sigstore/rekor/pkg/generated/client/tlog"
38 "github.com/sigstore/rekor/pkg/log"
39 "github.com/sigstore/rekor/pkg/util"
40 "github.com/sigstore/sigstore/pkg/signature"
41 )
42
43 type logInfoCmdOutput struct {
44 ActiveTreeSize int64
45 TotalTreeSize int64
46 RootHash string
47 TreeID string
48 }
49
50 func (l *logInfoCmdOutput) String() string {
51
52 return fmt.Sprintf(`Verification Successful!
53 Active Tree Size: %v
54 Total Tree Size: %v
55 Root Hash: %s
56 TreeID: %s
57 `, l.ActiveTreeSize, l.TotalTreeSize, l.RootHash, l.TreeID)
58 }
59
60
61 var logInfoCmd = &cobra.Command{
62 Use: "loginfo",
63 Short: "Rekor loginfo command",
64 Long: `Prints info about the transparency log`,
65 Run: format.WrapCmd(func(_ []string) (interface{}, error) {
66 serverURL := viper.GetString("rekor_server")
67 ctx := context.Background()
68 rekorClient, err := client.GetRekorClient(serverURL, client.WithUserAgent(UserAgent()), client.WithRetryCount(viper.GetUint("retry")), client.WithLogger(log.CliLogger))
69 if err != nil {
70 return nil, err
71 }
72
73 params := tlog.GetLogInfoParams{}
74 params.SetTimeout(viper.GetDuration("timeout"))
75 result, err := rekorClient.Tlog.GetLogInfo(¶ms)
76 if err != nil {
77 return nil, err
78 }
79
80 logInfo := result.GetPayload()
81
82
83 if err := verifyInactiveTrees(ctx, rekorClient, serverURL, logInfo.InactiveShards); err != nil {
84 return nil, err
85 }
86
87
88 sth := util.SignedCheckpoint{}
89 signedTreeHead := swag.StringValue(logInfo.SignedTreeHead)
90 if err := sth.UnmarshalText([]byte(signedTreeHead)); err != nil {
91 return nil, err
92 }
93 treeID := swag.StringValue(logInfo.TreeID)
94
95 if err := verifyTree(ctx, rekorClient, signedTreeHead, serverURL, treeID); err != nil {
96 return nil, err
97 }
98
99 cmdOutput := &logInfoCmdOutput{
100 ActiveTreeSize: swag.Int64Value(logInfo.TreeSize),
101 TotalTreeSize: totalTreeSize(logInfo, logInfo.InactiveShards),
102 RootHash: swag.StringValue(logInfo.RootHash),
103 TreeID: swag.StringValue(logInfo.TreeID),
104 }
105 return cmdOutput, nil
106 }),
107 }
108
109 func verifyInactiveTrees(ctx context.Context, rekorClient *rclient.Rekor, serverURL string, inactiveShards []*models.InactiveShardLogInfo) error {
110 if inactiveShards == nil {
111 return nil
112 }
113 log.CliLogger.Infof("Validating inactive shards...")
114 for _, shard := range inactiveShards {
115 signedTreeHead := swag.StringValue(shard.SignedTreeHead)
116 treeID := swag.StringValue(shard.TreeID)
117 if err := verifyTree(ctx, rekorClient, signedTreeHead, serverURL, treeID); err != nil {
118 return fmt.Errorf("verifying inactive shard with ID %s: %w", treeID, err)
119 }
120 }
121 log.CliLogger.Infof("Successfully validated inactive shards")
122 return nil
123 }
124
125 func verifyTree(ctx context.Context, rekorClient *rclient.Rekor, signedTreeHead, serverURL, treeID string) error {
126 oldState := state.Load(serverURL)
127 if treeID != "" {
128 oldState = state.Load(treeID)
129 }
130 sth := util.SignedCheckpoint{}
131 if err := sth.UnmarshalText([]byte(signedTreeHead)); err != nil {
132 return err
133 }
134 verifier, err := loadVerifier(rekorClient)
135 if err != nil {
136 return err
137 }
138 if !sth.Verify(verifier) {
139 return errors.New("signature on tree head did not verify")
140 }
141
142 if oldState != nil {
143 if err := verify.ProveConsistency(ctx, rekorClient, oldState, &sth, treeID); err != nil {
144 return err
145 }
146 } else {
147 log.CliLogger.Infof("No previous log state stored, unable to prove consistency")
148 }
149
150 if viper.GetBool("store_tree_state") {
151 if treeID != "" {
152 if err := state.Dump(treeID, &sth); err != nil {
153 log.CliLogger.Infof("Unable to store previous state: %v", err)
154 }
155 }
156 if err := state.Dump(serverURL, &sth); err != nil {
157 log.CliLogger.Infof("Unable to store previous state: %v", err)
158 }
159 }
160 return nil
161 }
162
163 func loadVerifier(rekorClient *rclient.Rekor) (signature.Verifier, error) {
164 publicKey := viper.GetString("rekor_server_public_key")
165 if publicKey == "" {
166
167 keyResp, err := rekorClient.Pubkey.GetPublicKey(nil)
168 if err != nil {
169 return nil, err
170 }
171 publicKey = keyResp.Payload
172 }
173
174 block, _ := pem.Decode([]byte(publicKey))
175 if block == nil {
176 return nil, errors.New("failed to decode public key of server")
177 }
178
179 pub, err := x509.ParsePKIXPublicKey(block.Bytes)
180 if err != nil {
181 return nil, err
182 }
183
184 return signature.LoadVerifier(pub, crypto.SHA256)
185 }
186
187 func totalTreeSize(activeShard *models.LogInfo, inactiveShards []*models.InactiveShardLogInfo) int64 {
188 total := swag.Int64Value(activeShard.TreeSize)
189 for _, i := range inactiveShards {
190 total += swag.Int64Value(i.TreeSize)
191 }
192 return total
193 }
194
195 func init() {
196 initializePFlagMap()
197 rootCmd.AddCommand(logInfoCmd)
198 }
199
View as plain text