diff options
-rw-r--r-- | go.mod | 10 | ||||
-rw-r--r-- | go.sum | 18 | ||||
-rw-r--r-- | main.go | 4 | ||||
-rw-r--r-- | snapshot.go | 126 |
4 files changed, 155 insertions, 3 deletions
@@ -2,4 +2,12 @@ module git.cs.kau.se/rasmoste/ct-sans go 1.19 -require gitlab.torproject.org/rgdd/ct v0.0.0-20230115071200-fa4d0bcd1cab // indirect +require ( + github.com/go-logr/logr v1.2.0 // indirect + github.com/google/certificate-transparency-go v1.1.4 // indirect + gitlab.torproject.org/rgdd/ct v0.0.0-20230115071200-fa4d0bcd1cab // indirect + golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect + golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect + google.golang.org/protobuf v1.28.1 // indirect + k8s.io/klog/v2 v2.80.1 // indirect +) @@ -0,0 +1,18 @@ +github.com/go-logr/logr v1.2.0 h1:QK40JKJyMdUDz+h+xvCsru/bJhvG0UxvePV0ufL/AcE= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/google/certificate-transparency-go v1.1.4 h1:hCyXHDbtqlr/lMXU0D4WgbalXL0Zk4dSWWMbPV8VrqY= +github.com/google/certificate-transparency-go v1.1.4/go.mod h1:D6lvbfwckhNrbM9WVl1EVeMOyzC19mpIjMOI4nxBHtQ= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +gitlab.torproject.org/rgdd/ct v0.0.0-20230115071200-fa4d0bcd1cab h1:Yj/plCmxPPidqP53forpttZhUeGgzYoIvq8C6GTMN8M= +gitlab.torproject.org/rgdd/ct v0.0.0-20230115071200-fa4d0bcd1cab/go.mod h1:dkEqBVulcsefxw3k5CX53bw88KUkTYcTRSJhMlj1Veg= +golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA= +golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4= +k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= @@ -85,8 +85,8 @@ func main() { // Hand-over to the respective subcommands var err error switch cmd := os.Args[1]; cmd { - //case "snapshot": - // err = snapshot(opts) + case "snapshot": + err = snapshot(opts) //case "collect": // err = collect(opts) case "assemble": diff --git a/snapshot.go b/snapshot.go new file mode 100644 index 0000000..fb17056 --- /dev/null +++ b/snapshot.go @@ -0,0 +1,126 @@ +package main + +import ( + "bytes" + "context" + "crypto/sha256" + "crypto/x509" + "encoding/json" + "errors" + "fmt" + "net/http" + "os" + "time" + + "git.cs.kau.se/rasmoste/ct-sans/internal/merkle" + "git.cs.kau.se/rasmoste/ct-sans/internal/utils" + ct "github.com/google/certificate-transparency-go" + "github.com/google/certificate-transparency-go/client" + "github.com/google/certificate-transparency-go/jsonclient" + "gitlab.torproject.org/rgdd/ct/pkg/metadata" +) + +func snapshot(opts options) error { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + if err := os.MkdirAll(opts.Directory, os.ModePerm); err != nil { + return err + } + + fmt.Fprintf(os.Stderr, "INFO: updating metadata file\n") + source := metadata.NewHTTPSource(metadata.HTTPSourceOptions{Name: "google"}) + msg, sig, md, err := source.Load(ctx) + if err != nil { + return err + } + if err := os.WriteFile(fmt.Sprintf("%s/%s", opts.Directory, opts.metadataFile), msg, 0644); err != nil { + return err + } + if err := os.WriteFile(fmt.Sprintf("%s/%s", opts.Directory, opts.metadataSignatureFile), sig, 0644); err != nil { + return err + } + timestamp := []byte(fmt.Sprintf("%d", time.Now().Unix())) + if err := os.WriteFile(fmt.Sprintf("%s/%s", opts.Directory, opts.metadataTimestampFile), timestamp, 0644); err != nil { + return err + } + + fmt.Fprintf(os.Stderr, "INFO: updating signed tree heads\n") + for _, log := range utils.Logs(md) { + id, _ := log.Key.ID() + der, _ := x509.MarshalPKIXPublicKey(log.Key) + dir := fmt.Sprintf("%s/%x", opts.logDirectory, id) + sthFile := fmt.Sprintf("%s/%s", dir, opts.sthFile) + if err := os.MkdirAll(dir, os.ModePerm); err != nil { + return fmt.Errorf("%s: %v", log.Description, err) + } + + // Fetch next STH + cli, err := client.New(string(log.URL), &http.Client{}, jsonclient.Options{PublicKeyDER: der, UserAgent: "wip"}) + if err != nil { + return fmt.Errorf("%s: %v", *log.Description, err) + } + nextSTH, err := cli.GetSTH(ctx) + if err != nil { + return fmt.Errorf("%s: %v", *log.Description, err) + } + nextSTHBytes, err := json.Marshal(nextSTH) + if err != nil { + return fmt.Errorf("%s: %v", *log.Description, err) + } + // + // It's a bit ugly that ct.SignedTreeHead contains fields that + // are not populated. Doesn't cause any prolems here, however. + // + + // Bootstrap log if we don't have any STH yet + if _, err := os.Stat(sthFile); err != nil { + if !errors.Is(err, os.ErrNotExist) { + return fmt.Errorf("%s: %v", *log.Description, err) + } + if err := os.WriteFile(sthFile, nextSTHBytes, 0644); err != nil { + return fmt.Errorf("%s: %v", *log.Description, err) + } + + fmt.Fprintf(os.Stderr, "INFO: bootstrapped %s at tree size %d\n", *log.Description, nextSTH.TreeSize) + continue + } + + // Otherwise: update an existing STH + currSTHBytes, err := os.ReadFile(sthFile) + if err != nil { + return fmt.Errorf("%s: %v", *log.Description, err) + } + var currSTH ct.SignedTreeHead + if err := json.Unmarshal(currSTHBytes, &currSTH); err != nil { + return fmt.Errorf("%s: %v", *log.Description, err) + } + if nextSTH.TreeSize < currSTH.TreeSize { + return fmt.Errorf("%s: next tree size is smaller: %s", *log.Description, nextSTHBytes) + } + if nextSTH.TreeSize == currSTH.TreeSize { + if !bytes.Equal(nextSTH.SHA256RootHash[:], currSTH.SHA256RootHash[:]) { + return fmt.Errorf("%s: split-view: %s", *log.Description, nextSTHBytes) + } + + fmt.Fprintf(os.Stderr, "INFO: %s is already up-to-date at size %d\n", *log.Description, nextSTH.TreeSize) + continue + } + hashes, err := cli.GetSTHConsistency(ctx, currSTH.TreeSize, nextSTH.TreeSize) + if err != nil { + return fmt.Errorf("%s: %v", *log.Description, err) + } + if err := merkle.VerifyConsistency(currSTH.TreeSize, + nextSTH.TreeSize, + [sha256.Size]byte(currSTH.SHA256RootHash), + [sha256.Size]byte(nextSTH.SHA256RootHash), + utils.Proof(hashes)); err != nil { + return fmt.Errorf("%s: inconsistent tree: %v", *log.Description, err) + } + if err := os.WriteFile(sthFile, nextSTHBytes, 0644); err != nil { + return fmt.Errorf("%s: %v", *log.Description, err) + } + fmt.Fprintf(os.Stderr, "INFO: updated %s to tree size %d\n", *log.Description, nextSTH.TreeSize) + } + + return nil +} |