From 801afaa9147c4f70fc00fde1993f6ce0c91bd450 Mon Sep 17 00:00:00 2001
From: Rasmus Dahlberg <rasmus.dahlberg@kau.se>
Date: Mon, 2 Nov 2020 17:04:13 +0100
Subject: added stfe server descriptor

A collection of immutable log parameters, see documentation.
---
 server/descriptor/descriptor.go      | 40 +++++++++++++++
 server/descriptor/descriptor_test.go | 96 ++++++++++++++++++++++++++++++++++++
 server/descriptor/stfe.json          | 18 +++++++
 3 files changed, 154 insertions(+)
 create mode 100644 server/descriptor/descriptor.go
 create mode 100644 server/descriptor/descriptor_test.go
 create mode 100644 server/descriptor/stfe.json

diff --git a/server/descriptor/descriptor.go b/server/descriptor/descriptor.go
new file mode 100644
index 0000000..ba90289
--- /dev/null
+++ b/server/descriptor/descriptor.go
@@ -0,0 +1,40 @@
+package descriptor
+
+import (
+	"bytes"
+	"fmt"
+
+	"crypto/tls"
+	"encoding/base64"
+)
+
+const (
+	// Location is an url to a json-encoded list of stfe log operators
+	Location = "https://github.com/system-transparency/stfe/tree/main/server/descriptor/stfe.json"
+)
+
+// Operator is an stfe log operator that runs zero or more logs
+type Operator struct {
+	Name  string `json:"name"`
+	Email string `json:"email"`
+	Logs  []*Log `json:"logs"`
+}
+
+// Log is a collection of immutable stfe log parameters
+type Log struct {
+	Id        []byte                `json:"id"`                // H(PublicKey)
+	PublicKey []byte                `json:"public_key"`        // DER-encoded SubjectPublicKeyInfo
+	Scheme    tls.SignatureScheme   `json:"signature_scheme"`  // Signature schemes used by the log (RFC 8446, §4.2.3)
+	Schemes   []tls.SignatureScheme `json:"signature_schemes"` // Signature schemes that submitters can use (RFC 8446, §4.2.3)
+	MaxChain  uint8                 `json:"max_chain"`         // maximum certificate chain length
+	BaseUrl   string                `json:"base_url"`          // E.g., example.com/st/v1
+}
+
+func (op *Operator) FindLog(logId []byte) (*Log, error) {
+	for _, log := range op.Logs {
+		if bytes.Equal(logId, log.Id) {
+			return log, nil
+		}
+	}
+	return nil, fmt.Errorf("no such log: %s", base64.StdEncoding.EncodeToString(logId))
+}
diff --git a/server/descriptor/descriptor_test.go b/server/descriptor/descriptor_test.go
new file mode 100644
index 0000000..e461f88
--- /dev/null
+++ b/server/descriptor/descriptor_test.go
@@ -0,0 +1,96 @@
+package descriptor
+
+import (
+	"fmt"
+	"testing"
+
+	"crypto/sha256"
+	"crypto/tls"
+	"encoding/base64"
+	"encoding/json"
+)
+
+const (
+	operatorListJson = `[{"name":"Test operator","email":"test@example.com","logs":[{"id":"B9oCJk4XIOMXba8dBM5yUj+NLtqTE6xHwbvR9dYkHPM=","public_key":"MCowBQYDK2VwAyEAqM4b/SHOCRId9xgiCPn8D8r6+Nrk9JTZZqW6vj7TGa0=","signature_scheme":2055,"signature_schemes":[2055],"max_chain":3,"base_url":"example.com/st/v1"}]}]`
+)
+
+func TestMarshal(t *testing.T) {
+	for _, table := range []struct {
+		in   []Operator
+		want string
+	}{
+		{makeOperatorList(), operatorListJson},
+	} {
+		b, err := json.Marshal(table.in)
+		if err != nil {
+			t.Errorf("operator list marshaling failed: %v", err)
+		}
+		if string(b) != table.want {
+			t.Errorf("\nwant %s\n got %s", table.want, string(b))
+		}
+	}
+
+}
+
+func TestUnmarshal(t *testing.T) {
+	for _, table := range []struct {
+		in   []byte
+		want error
+	}{
+		{[]byte(operatorListJson), nil},
+	} {
+		var op []Operator
+		if err := json.Unmarshal(table.in, &op); err != table.want {
+			t.Errorf("wanted err=%v, got %v", table.want, err)
+		}
+	}
+}
+
+func TestFindLog(t *testing.T) {
+	for _, table := range []struct {
+		op        Operator
+		logId     []byte
+		wantError bool
+	}{
+		{makeOperatorList()[0], deb64("B9oCJk4XIOMXba8dBM5yUj+NLtqTE6xHwbvR9dYkHPM="), false},
+		{makeOperatorList()[0], []byte{0, 1, 2, 3}, true},
+	} {
+		_, err := table.op.FindLog(table.logId)
+		if (err != nil) != table.wantError {
+			t.Errorf("wanted log not found for id: %v", table.logId)
+		}
+	}
+}
+
+func makeOperatorList() []Operator {
+	pub := deb64("MCowBQYDK2VwAyEAqM4b/SHOCRId9xgiCPn8D8r6+Nrk9JTZZqW6vj7TGa0=")
+	h := sha256.New()
+	h.Write(pub)
+	id := h.Sum(nil)
+	return []Operator{
+		Operator{
+			Name:  "Test operator",
+			Email: "test@example.com",
+			Logs: []*Log{
+				&Log{
+					Id:        id,
+					PublicKey: pub,
+					Scheme:    tls.Ed25519,
+					Schemes: []tls.SignatureScheme{
+						tls.Ed25519,
+					},
+					MaxChain: 3,
+					BaseUrl:  "example.com/st/v1",
+				},
+			},
+		},
+	}
+}
+
+func deb64(s string) []byte {
+	b, err := base64.StdEncoding.DecodeString(s)
+	if err != nil {
+		panic(fmt.Sprintf("failed decoding base64: %v", err))
+	}
+	return b
+}
diff --git a/server/descriptor/stfe.json b/server/descriptor/stfe.json
new file mode 100644
index 0000000..69e84a0
--- /dev/null
+++ b/server/descriptor/stfe.json
@@ -0,0 +1,18 @@
+[
+    {
+        "name": "Test operator",
+        "email": "test@example.com",
+        "logs": [
+            {
+                "max_chain": 3,
+                "log_id": "B9oCJk4XIOMXba8dBM5yUj+NLtqTE6xHwbvR9dYkHPM=",
+                "signature_schemes": [
+                    2055
+                ],
+                "base_url": "example.com/st/v1",
+                "signature_scheme": 2055,
+                "public_key": "MCowBQYDK2VwAyEAqM4b/SHOCRId9xgiCPn8D8r6+Nrk9JTZZqW6vj7TGa0="
+            }
+        ]
+    }
+]
-- 
cgit v1.2.3