diff --git a/cmd/cli/app/log_info.go b/cmd/cli/app/log_info.go index 3c3a30de40fbc81f39d83b2969aa6f5f488dd2ba..5b9c2e49254f3b40a9a5df04686f50e1e7c4118d 100644 --- a/cmd/cli/app/log_info.go +++ b/cmd/cli/app/log_info.go @@ -19,16 +19,22 @@ import ( "crypto" "crypto/x509" "encoding/base64" + "encoding/hex" "encoding/pem" "errors" "fmt" + "github.com/projectrekor/rekor/cmd/cli/app/state" + "github.com/google/trillian" tclient "github.com/google/trillian/client" tcrypto "github.com/google/trillian/crypto" + "github.com/google/trillian/merkle" "github.com/google/trillian/merkle/rfc6962" "github.com/projectrekor/rekor/cmd/cli/app/format" + "github.com/projectrekor/rekor/pkg/generated/client/tlog" + "github.com/projectrekor/rekor/pkg/log" "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -104,9 +110,42 @@ var logInfoCmd = &cobra.Command{ } verifier := tclient.NewLogVerifier(rfc6962.DefaultHasher, pub, crypto.SHA256) - if _, err := tcrypto.VerifySignedLogRoot(verifier.PubKey, verifier.SigHash, &sth); err != nil { + lr, err := tcrypto.VerifySignedLogRoot(verifier.PubKey, verifier.SigHash, &sth) + if err != nil { return nil, err } + + oldState := state.Load() + if oldState != nil { + log.CliLogger.Infof("Found previous log state, proving consistency between %d and %d", oldState.TreeSize, lr.TreeSize) + params := tlog.NewGetLogProofParams() + firstSize := int64(oldState.TreeSize) + params.FirstSize = &firstSize + params.LastSize = int64(lr.TreeSize) + proof, err := rekorClient.Tlog.GetLogProof(params) + if err != nil { + return nil, err + } + hashes := [][]byte{} + for _, h := range proof.Payload.Hashes { + b, _ := hex.DecodeString(h) + hashes = append(hashes, b) + } + v := merkle.NewLogVerifier(rfc6962.DefaultHasher) + if err := v.VerifyConsistencyProof(firstSize, int64(lr.TreeSize), oldState.RootHash, + lr.RootHash, hashes); err != nil { + return nil, err + } + log.CliLogger.Infof("Consistency proof valid!") + } else { + log.CliLogger.Infof("No previous log state stored, unable to prove consistency") + } + + if viper.GetBool("store_tree_state") { + if err := state.Dump(lr); err != nil { + log.CliLogger.Infof("Unable to store previous state: %v", err) + } + } return cmdOutput, nil }), } diff --git a/cmd/cli/app/root.go b/cmd/cli/app/root.go index 086880b5e98e5b514fda9fe17e2a150e73c5ae53..68cb7a1a4f1a6927a7a6207ad8b7a1bb40adc89d 100644 --- a/cmd/cli/app/root.go +++ b/cmd/cli/app/root.go @@ -34,6 +34,7 @@ import ( ) var cfgFile string +var storeState bool var rootCmd = &cobra.Command{ Use: "rekor", @@ -53,6 +54,7 @@ func init() { cobra.OnInitialize(initConfig) rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.rekor.yaml)") + rootCmd.PersistentFlags().BoolVar(&storeState, "store_tree_state", true, "whether to store tree state in between invocations for additional verification") rootCmd.PersistentFlags().Var(&urlFlag{url: "https://api.rekor.dev"}, "rekor_server", "Server address:port") rootCmd.PersistentFlags().Var(&formatFlag{format: "default"}, "format", "Command output format") diff --git a/cmd/cli/app/state/state.go b/cmd/cli/app/state/state.go new file mode 100644 index 0000000000000000000000000000000000000000..82cf24ce394f830d706d0c005042314c097df1a3 --- /dev/null +++ b/cmd/cli/app/state/state.go @@ -0,0 +1,75 @@ +/* +Copyright © 2021 Dan Lorenc <lorenc.d@gmail.com> + +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 state + +import ( + "encoding/json" + "io/ioutil" + "os" + "path/filepath" + + "github.com/google/trillian/types" + "github.com/mitchellh/go-homedir" +) + +func Dump(lr *types.LogRootV1) error { + rekorDir, err := getRekorDir() + if err != nil { + return err + } + statePath := filepath.Join(rekorDir, "state.json") + + b, err := json.Marshal(lr) + if err != nil { + return err + } + if err := ioutil.WriteFile(statePath, b, 0600); err != nil { + return err + } + return nil +} + +func Load() *types.LogRootV1 { + rekorDir, err := getRekorDir() + if err != nil { + return nil + } + fp := filepath.Join(rekorDir, "state.json") + b, err := ioutil.ReadFile(filepath.Clean(fp)) + if err != nil { + return nil + } + result := &types.LogRootV1{} + if err := json.Unmarshal(b, result); err != nil { + return nil + } + return result +} + +func getRekorDir() (string, error) { + home, err := homedir.Dir() + if err != nil { + return "", err + } + rekorDir := filepath.Join(home, ".rekor") + if _, err := os.Stat(rekorDir); os.IsNotExist(err) { + if err := os.MkdirAll(rekorDir, 0644); err != nil { + return "", err + } + } + return rekorDir, nil +} diff --git a/pkg/log/log.go b/pkg/log/log.go index 839c53434f1d2b9cb12b55cceeaee5c7cd8004a6..7551ac4bbc822fc7168ad6a6fcc3ce826dc315cd 100644 --- a/pkg/log/log.go +++ b/pkg/log/log.go @@ -32,6 +32,21 @@ func ConfigureLogger(logType string) { Logger = logger.Sugar() } +var CliLogger = createCliLogger() + +func createCliLogger() *zap.SugaredLogger { + cfg := zap.NewDevelopmentConfig() + cfg.EncoderConfig.TimeKey = "" + cfg.EncoderConfig.LevelKey = "" + cfg.DisableCaller = true + logger, err := cfg.Build() + if err != nil { + log.Fatalln("createLogger", err) + } + + return logger.Sugar() +} + func WithRequestID(ctx context.Context, id string) context.Context { return context.WithValue(ctx, middleware.RequestIDKey, id) }