aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRasmus Dahlberg <rasmus@mullvad.net>2021-12-14 22:56:15 +0100
committerRasmus Dahlberg <rasmus@mullvad.net>2021-12-14 23:06:35 +0100
commit4a4a4e17d159070ceb7ab7a580c8be6ad2c57e06 (patch)
tree0a174fa7fddf9689eded85b0cc4e1567dc431980
parent30ac45b7d72b7c023c42d54e6d53b24810ed342b (diff)
instance: Add experimental checkpoint endpointv0.3.3
This commit adds an experimental endpoint that serves the log's to-sign tree head formatted as a signed checkpoint. See documentation at: https://github.com/transparency-dev/formats/tree/main/log#readme If we decide to adopt this endpoint we should consider in more detail what the origin string should be. Right now, it is assumed that the log's configured prefix is unique across the set of all sigsum logs. Example output: ``` $ echo "Public verification key: $vk" Public verification key: 8cf3ac85aadd42891c5ae9aef27244cb2a546a2312f80020aad3f2ae1af73314 $ $ echo "Formatted as a note key: $vkNote" Formatted as a note key: sigsum.org:testonly+8de2c54b+AYzzrIWq3UKJHFrprvJyRMsqVGojEvgAIKrT8q4a9zMU $ $ curl http://localhost:6965/testonly/sigsum/v0/get-checkpoint sigsum.org:testonly 23 HSt6W8ve4/36xAIf04qDOqaKLaKqSOUqKxLNrkK74+g= — sigsum.org:testonly jeLFS4WQDEeTavbiYArHzCQUPXbQ1Y/V8/dKJlBxqOAY4eLhSh3uWNJ8YXp1vs/zR4SDNHI+6UybLmHmFlg/VSf5OAs= ```
-rw-r--r--issues/add-checkpoint-support.md18
-rw-r--r--pkg/instance/experimental_endpoint.go85
-rw-r--r--pkg/instance/instance.go1
-rw-r--r--pkg/instance/instance_test.go1
4 files changed, 87 insertions, 18 deletions
diff --git a/issues/add-checkpoint-support.md b/issues/add-checkpoint-support.md
deleted file mode 100644
index f1d7951..0000000
--- a/issues/add-checkpoint-support.md
+++ /dev/null
@@ -1,18 +0,0 @@
-**Title:** Add checkpoint support </br>
-**Date:** 2021-12-09 </br>
-
-# Summary
-Add experimental checkpoint support.
-
-# Description
-Sigsum collaborated on a common
- [checkpoint format](https://github.com/google/trillian-examples/tree/master/formats/log)
-a while back. A checkpoint is basically a cosigned tree head.
-
-The current decision is to add experimental support for checkpoints. There is
-no formal decision to adopt the above checkpoint yet, hence _experimental_.
-
-To keep it simple:
-1. Don't add any timestamp extension.
-2. Only serve the most recent tree head as a checkpoint. This allows us to
-experiment with external feeders and distributors that are not part of the log.
diff --git a/pkg/instance/experimental_endpoint.go b/pkg/instance/experimental_endpoint.go
new file mode 100644
index 0000000..2986a27
--- /dev/null
+++ b/pkg/instance/experimental_endpoint.go
@@ -0,0 +1,85 @@
+package instance
+
+import (
+ "bytes"
+ "context"
+ "crypto"
+ "crypto/ed25519"
+ "crypto/sha256"
+ "encoding/base64"
+ "encoding/binary"
+ "fmt"
+ "net/http"
+
+ "git.sigsum.org/sigsum-log-go/pkg/types"
+ "github.com/golang/glog"
+)
+
+// algEd25519 identifies a checkpoint signature algorithm
+const algEd25519 byte = 1
+
+// getCheckpoint is an experimental endpoint that is not part of the official
+// Sigsum API. Documentation can be found in the transparency-dev repo.
+func getCheckpoint(ctx context.Context, i *Instance, w http.ResponseWriter, r *http.Request) (int, error) {
+ glog.V(3).Info("handling get-checkpoint request")
+ sth, err := i.Stateman.ToSign(ctx)
+ if err != nil {
+ return http.StatusInternalServerError, err
+ }
+ if err := i.signWriteNote(w, sth); err != nil {
+ return http.StatusInternalServerError, err
+ }
+ return http.StatusOK, nil
+}
+
+// signWriteNote signs and writes a checkpoint which uses "sigsum.org:<prefix>"
+// as origin string. Origin string is also used as ID in the note signature.
+// This means that a sigsum log's prefix (say, "glass-frog"), must be unique.
+func (i *Instance) signWriteNote(w http.ResponseWriter, sth *types.SignedTreeHead) error {
+ origin := fmt.Sprintf("sigsum.org:%s", i.Prefix)
+ msg := fmt.Sprintf("%s\n%d\n%s\n",
+ origin,
+ sth.TreeSize,
+ base64.StdEncoding.EncodeToString(sth.RootHash[:]),
+ )
+ sig, err := noteSign(i.Signer, origin, msg)
+ if err != nil {
+ return err
+ }
+
+ fmt.Fprintf(w, "%s\n\u2014 %s %s\n", msg, origin, sig)
+ return nil
+}
+
+// noteSign returns a note signature for the provided origin and message
+func noteSign(signer crypto.Signer, origin, msg string) (string, error) {
+ sig, err := signer.Sign(nil, []byte(msg), crypto.Hash(0))
+ if err != nil {
+ return "", err
+ }
+
+ var hbuf [4]byte
+ binary.BigEndian.PutUint32(hbuf[:], noteKeyHash(origin, notePubKeyEd25519(signer)))
+ sig = append(hbuf[:], sig...)
+ return base64.StdEncoding.EncodeToString(sig), nil
+}
+
+// See:
+// https://cs.opensource.google/go/x/mod/+/refs/tags/v0.5.1:sumdb/note/note.go;l=336
+func notePubKeyEd25519(signer crypto.Signer) []byte {
+ return bytes.Join([][]byte{
+ []byte{algEd25519},
+ signer.Public().(ed25519.PublicKey),
+ }, nil)
+}
+
+// Source:
+// https://cs.opensource.google/go/x/mod/+/refs/tags/v0.5.1:sumdb/note/note.go;l=222
+func noteKeyHash(name string, key []byte) uint32 {
+ h := sha256.New()
+ h.Write([]byte(name))
+ h.Write([]byte("\n"))
+ h.Write(key)
+ sum := h.Sum(nil)
+ return binary.BigEndian.Uint32(sum)
+}
diff --git a/pkg/instance/instance.go b/pkg/instance/instance.go
index bda553d..4dff31a 100644
--- a/pkg/instance/instance.go
+++ b/pkg/instance/instance.go
@@ -55,6 +55,7 @@ func (i *Instance) Handlers() []Handler {
Handler{Instance: i, Handler: getTreeHeadLatest, Endpoint: types.EndpointGetTreeHeadLatest, Method: http.MethodGet},
Handler{Instance: i, Handler: getTreeHeadToSign, Endpoint: types.EndpointGetTreeHeadToSign, Method: http.MethodGet},
Handler{Instance: i, Handler: getTreeHeadCosigned, Endpoint: types.EndpointGetTreeHeadCosigned, Method: http.MethodGet},
+ Handler{Instance: i, Handler: getCheckpoint, Endpoint: types.Endpoint("get-checkpoint"), Method: http.MethodGet},
Handler{Instance: i, Handler: getConsistencyProof, Endpoint: types.EndpointGetConsistencyProof, Method: http.MethodPost},
Handler{Instance: i, Handler: getInclusionProof, Endpoint: types.EndpointGetInclusionProof, Method: http.MethodPost},
Handler{Instance: i, Handler: getLeaves, Endpoint: types.EndpointGetLeaves, Method: http.MethodPost},
diff --git a/pkg/instance/instance_test.go b/pkg/instance/instance_test.go
index 9eeee5b..6ca7baf 100644
--- a/pkg/instance/instance_test.go
+++ b/pkg/instance/instance_test.go
@@ -19,6 +19,7 @@ func TestHandlers(t *testing.T) {
types.EndpointGetConsistencyProof: false,
types.EndpointGetInclusionProof: false,
types.EndpointGetLeaves: false,
+ types.Endpoint("get-checkpoint"): false,
}
i := &Instance{
Config: testConfig,