aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--go.mod10
-rw-r--r--go.sum18
-rw-r--r--main.go4
-rw-r--r--snapshot.go126
4 files changed, 155 insertions, 3 deletions
diff --git a/go.mod b/go.mod
index 400b49a..aa226a0 100644
--- a/go.mod
+++ b/go.mod
@@ -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
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..4dae2bd
--- /dev/null
+++ b/go.sum
@@ -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=
diff --git a/main.go b/main.go
index 3e71076..0511ab7 100644
--- a/main.go
+++ b/main.go
@@ -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
+}