Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
- Added `HashReaderOverlay`, a `tlog.HashReader` that extends a tree with the
stored hashes of appended records, held in memory.

- Added `NewLogVerifier` to parse a log vkey, accepting regular Ed25519 keys
and ML-DSA-44 cosignature keys.

### mpt

- New package with a Merkle Patricia Trie implementation.
Expand All @@ -30,12 +33,16 @@

- The bastion certificate is now generated once at startup.

- Logs can now sign checkpoints with ML-DSA-44 keys.

### witnessctl

- Added `set-bastions` command, which adds the given bastion(s) to every log
that has none configured (for example after `pull-logs`), or replaces the
bastions of every log with `-all`.

- `add-key` and `pull-logs` now accept logs that use ML-DSA-44 keys.

### litebastion

- Comments and empty lines are now allowed in the backends file.
Expand Down
59 changes: 59 additions & 0 deletions cmd/litewitness/testdata/gentest/mldsa.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
//go:build mldsa

// Run with "go run -tags mldsa -mod=mod ./cmd/litewitness/testdata/gentest"
// and re-run "go mod tidy" after use to clean up its dependencies.

package main

import (
"crypto/rand"
"encoding/base64"
"encoding/hex"
"flag"
"fmt"
"log"

"filippo.io/mldsa"
"filippo.io/torchwood"
"golang.org/x/mod/sumdb/tlog"
)

var seedFlag = flag.String("seed", "", "hex-encoded seed")

func main() {
origin := "example.com/mldsa-log"

flag.Parse()
var seed []byte
if *seedFlag == "" {
seed = make([]byte, 32)
if _, err := rand.Read(seed); err != nil {
log.Fatal(err)
}
} else {
seed = make([]byte, hex.DecodedLen(len(*seedFlag)))
if _, err := hex.Decode(seed, []byte(*seedFlag)); err != nil {
log.Fatal(err)
}
}
fmt.Printf("- seed: %x\n", seed)

logKey, err := mldsa.NewPrivateKey(mldsa.MLDSA44(), seed)
if err != nil {
log.Fatal(err)
}
signer, err := torchwood.NewCosignatureSigner(origin, logKey)
if err != nil {
log.Fatal(err)
}
fmt.Printf("- log vkey: %s\n", signer.Verifier().String())

// SignSubtree is used to get a deterministic timestamp (zero).
rootHash := tlog.RecordHash([]byte("testonly"))
checkpoint := fmt.Sprintf("%s\n%d\n%s\n", origin, 1, base64.StdEncoding.EncodeToString(rootHash[:]))
sigLine, err := signer.SignSubtree(origin, 0, 1, rootHash)
if err != nil {
log.Fatal(err)
}
fmt.Printf("- checkpoint (size 1):\n%s\n%s", checkpoint, string(sigLine))
}
4 changes: 3 additions & 1 deletion cmd/litewitness/testdata/gentest/sigsum.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
// Run with "go run -mod=mod ./cmd/litewitness/testdata/gentest"
//go:build sigsum

// Run with "go run -tags sigsum -mod=mod ./cmd/litewitness/testdata/gentest"
// and re-run "go mod tidy" after use to clean up its dependencies.

package main
Expand Down
49 changes: 49 additions & 0 deletions cmd/litewitness/testdata/mldsa.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# gentest seed: 2886adc2b0eabfce67c0472939127998bf886cf256bfee8ba76a253fce1146e0

# set up log with an ML-DSA-44 key
exec witnessctl add-log -origin example.com/mldsa-log
exec witnessctl add-key -origin example.com/mldsa-log -key example.com/mldsa-log+b5df45c5+BuhtcL97l3dcitjZ8Y9J54gmQ1TWn/BMF0yePKpTzhlFCkN6ouJDf+9kT9wve4/Y+JGXBUjlRvktAynZ6XOQSO+fOaxyPOQAwrtbZKYXnZWSQj9gHPz18q6brnckUv87Rsk/m5GIBvH8J3ymxZjUQS+DnAUIbctLsG2DD6I/ou0JtXpPLvfGhPDTkwc9QeFyBLBa02Pr9uuWCMohciLqRiFUo5+leUUDKba1g5TYjOjK/VKfntpdbEwoqhTJTG4armjUeXdrYDHeaaMaCAnW5F0lMWYsHjqzRpKjeTjgItseaWKSm+ga/mzTAgqsbo5Ob/AwaXf4Av7/mN+ZM6vHON/NE7Ip5vpXpCiNb9YbEQavn+fjNucFxffCacvzXghvaaQ+3C/5h5lpR8jeajBFsFIOvdkYKizt29lCjqZDkYQgVfB9nWeH6rbayMkBdDpmRH03cI+urD0Eb5zvRuGJn3PgfwdyQJ3WcDH5Gqn5dkKs5NIVMBC+FfKeD47ruAIruda4JiiYCAxZbWgwzTI80N5lZFHEndmxKWaw6z/QyphuYkNyvZVZ0R3kH3JN4jdy9KKpNtFoQqnoBTGeEE/fXEEs5ByPlKnrrefRgWTqxssE4nPzHHr5GGgv/NKNTXNKI4LvNPH5yO3Vv/gIiDZxPPc1Jen8uKkmc0s3kT7XtcuWTlb+zdQeSme9ODj0IoVu29Eav2OsGJpReX7vZx9i2fVfHEMHY5nFew8L5RxcWmZFFiYhMFZnLvk/SWmHcwr3DhzZchrODhabHQ+S6S49Ws+5aoBBPM3mOrSSKHgobdI7BJrdzdT07kjzyiyS9Gyv8iylUHHMuuh1O05RNRcX5SXmenwGDPcT9HuLS+hoesO0FQgEt+jAR7vVyUUU2C6g6FC6QNfHL/EUsulvYIAS5fOK7acOeVnoJlpI57Bq+uzLZm6g7IZD0xxQwZPYyT4ek99l9HxDSrVnU0rAr+Tq5vVh5YxvDkQVWroYIWek/AI5awjmhgpW4LPapBhLk2ipkF5CmU3T0cm3ET5f14gYQZfz23nCsmZ7qyqiQilgOxJzVYacVoGAStGzJNY1wEf26MADW5McyQzYI/3Fpaa4Ay6p2yFmcJMfpqZnu4flgWMqN1UpMbbJ6I7hOrCFWryExK/nvSbRrErHTkPz2wvVfI+enbFz86c0iNb7naQKv6LoIZYEHFOCEsWGL/sCChn6BR4nk1qAakyXEQwi96CkIa3AX562F8lhw6CU4x7T+NWqi2AWfEXImJenORhMP69hGIFsiLAVIjHtsWtJOi8cu+Brg1bFERkB4ryUTrLf/jFeb6KifZ+s8PZWclDaOLMINZ3E78a5u6AzgXpigJsAJ1A4BY89muedrW2O8kFyh3TTTKyY4kedCW6L+8bFHV2Gce25ZulNC1mZs41XAI9CteeYaWaiL7uGMXgyQ73wr4ftwAEMdsoQVQsQriutS1AhpBGvjzPCwD5LC5p1fa0WXBQ1puuaNqr3IJodmDLhEIAsZcv1gdXMg8EQg9QLqgLLrBijU3GxFUQllS8lpQ4TLwFYUJ41+qt28JEeky/VzSEeQd/qob8cbyNM44o7INB+lbhEXEGWvVO8Ak+uWMY+PRKeY9OQcE6E27Nf3eUoHQXkwmXkjGgj0NKeXBIJc1k/Ws6WDbsU4graVKk5FQjwULcRngOpyiJQ/n8sObkAPXr+1rImK3lQJnxif/B6p5/tsh4rrcUht4yVIvFAaqsgAAE=

# start ssh-agent
env SSH_AUTH_SOCK=$WORK/s # barely below the max path length
! exec ssh-agent -a $SSH_AUTH_SOCK -D & # ssh-agent always exits 2
waitfor $SSH_AUTH_SOCK
chmod 600 witness_key.pem
exec ssh-add witness_key.pem

# start litewitness
exec litewitness -listen=localhost:7382 -ssh-agent=$SSH_AUTH_SOCK -name=example.com/witness -key=65cabc55d6547110f83cdfe96ff79a28e3185f8de8febac7c18fa1b7532d5e70 &litewitness&
waitfor localhost:7382

# add-checkpoint
exec hurl --test --error-format long add-checkpoint.hurl

# check that litewitness shut down cleanly
killall
wait litewitness
stderr 'shutting down'


-- witness_key.pem --
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtz
c2gtZWQyNTUxOQAAACDHnZBBq5a4LUY4qcDYcNeJ1Y/N88jp8MSc0OvB1bdoNwAA
AIg1rkpuNa5KbgAAAAtzc2gtZWQyNTUxOQAAACDHnZBBq5a4LUY4qcDYcNeJ1Y/N
88jp8MSc0OvB1bdoNwAAAEBqlQdTPUkNspRSay6C9pt0CJBPfKZKFQmh70+lhEnq
O8edkEGrlrgtRjipwNhw14nVj83zyOnwxJzQ68HVt2g3AAAAAAECAwQF
-----END OPENSSH PRIVATE KEY-----

-- add-checkpoint.hurl --
POST http://localhost:7382/add-checkpoint
```
old 0

example.com/mldsa-log
1
+zoErtsZtGO6sfiSV/v4UJSIyowisE8hvjw0q5+bD38=

— example.com/mldsa-log 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
```
HTTP 200
[Asserts]
body contains "— example.com/witness"
4 changes: 2 additions & 2 deletions cmd/witnessctl/loglist.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"log"
"strings"

"golang.org/x/mod/sumdb/note"
"filippo.io/torchwood"
)

func parseLogList(logList []byte, verbose bool) (map[string]string, error) {
Expand All @@ -21,7 +21,7 @@ func parseLogList(logList []byte, verbose bool) (map[string]string, error) {
vkey = ""
origin = ""
}()
v, err := note.NewVerifier(vkey)
v, err := torchwood.NewLogVerifier(vkey)
if err != nil {
if verbose {
log.Printf("Skipping invalid vkey %q: %v", vkey, err)
Expand Down
88 changes: 88 additions & 0 deletions cmd/witnessctl/loglist_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package main

import (
"crypto/ed25519"
"crypto/rand"
"fmt"
"testing"

"filippo.io/mldsa"
"filippo.io/torchwood"
"golang.org/x/mod/sumdb/note"
)

func TestParseLogListAccepts(t *testing.T) {
mldsaOrigin := "example.com/mldsa-log"
mldsaKey, err := mldsa.GenerateKey(mldsa.MLDSA44())
if err != nil {
t.Fatal(err)
}
mldsaSigner, err := torchwood.NewCosignatureSigner(mldsaOrigin, mldsaKey)
if err != nil {
t.Fatal(err)
}
mldsaVkey := mldsaSigner.Verifier().String()

_, edVkey, err := note.GenerateKey(rand.Reader, "example.com/ed25519-log")
if err != nil {
t.Fatal(err)
}

list := fmt.Sprintf(`
# Log list that should be accepted
logs/v0

# ML-DSA-44 log
vkey %s
qpd 24

# should be ignored
unknown-key some-value
contact test@mldsa-log

# Ed25519 log
vkey %s
qpd 3600
contact test@ed25519-log
`, mldsaVkey, edVkey)
logs, err := parseLogList([]byte(list), false)
if err != nil {
t.Fatal(err)
}
if len(logs) != 2 {
t.Errorf("expected two logs, got: %d", len(logs))
}
if logs[mldsaOrigin] != mldsaVkey {
t.Errorf("ML-DSA log not parsed: %v", logs)
}
if logs["example.com/ed25519-log"] != edVkey {
t.Errorf("Ed25519 log not parsed: %v", logs)
}
}

func TestParseLogListSkipsEd25519Cosignature(t *testing.T) {
origin := "example.com/ed-cosig-log"
_, k, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
t.Fatal(err)
}
signer, err := torchwood.NewCosignatureSigner(origin, k)
if err != nil {
t.Fatal(err)
}
vkey := signer.Verifier().String()

list := fmt.Sprintf(`
logs/v0
vkey %s
qpd 24
contact test@ed25519-cosig
`, vkey)
logs, err := parseLogList([]byte(list), true)
if err != nil {
t.Fatal(err)
}
if len(logs) != 0 {
t.Errorf("expected Ed25519 cosignature log to be skipped, got: %v", logs)
}
}
3 changes: 2 additions & 1 deletion cmd/witnessctl/witnessctl.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"strings"
"time"

"filippo.io/torchwood"
"filippo.io/torchwood/internal/witness"
"golang.org/x/mod/sumdb/note"
sigsum "sigsum.org/sigsum-go/pkg/crypto"
Expand Down Expand Up @@ -135,7 +136,7 @@ func addLog(db *sqlite.Conn, origin string) {
}

func checkKeyMatches(origin string, vk string) {
v, err := note.NewVerifier(vk)
v, err := torchwood.NewLogVerifier(vk)
if err != nil {
log.Fatalf("Error parsing verifier key: %v", err)
}
Expand Down
2 changes: 1 addition & 1 deletion internal/witness/witness.go
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,7 @@ func (w *Witness) getKeys(origin string) (note.Verifiers, error) {
}
var verifiers []note.Verifier
for _, k := range keys {
v, err := note.NewVerifier(k)
v, err := torchwood.NewLogVerifier(k)
if err != nil {
w.log.Warn("invalid key in database", "key", k, "error", err)
return nil, fmt.Errorf("invalid key %q: %v", k, err)
Expand Down
6 changes: 3 additions & 3 deletions policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ func ParsePolicy(p []byte) (Policy, error) {
if len(fields) < 2 {
return nil, fmt.Errorf("line %d: invalid log definition: %q", i+1, line)
}
v, err := newLogVerifier(fields[1])
v, err := NewLogVerifier(fields[1])
if err != nil {
return nil, fmt.Errorf("line %d: invalid log vkey %q: %w", i+1, fields[1], err)
}
Expand Down Expand Up @@ -228,9 +228,9 @@ func ParsePolicy(p []byte) (Policy, error) {
}
}

// newLogVerifier parses a log vkey, which may use the Ed25519 signature
// NewLogVerifier parses a log vkey, which may use the Ed25519 signature
// algorithm or the ML-DSA-44 (sub)tree cosignature algorithm.
func newLogVerifier(vkey string) (note.Verifier, error) {
func NewLogVerifier(vkey string) (note.Verifier, error) {
_, rest, _ := strings.Cut(vkey, "+")
_, key64, _ := strings.Cut(rest, "+")
key, err := base64.StdEncoding.DecodeString(key64)
Expand Down
Loading