aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLinus Nordberg <linus@nordberg.se>2022-03-25 16:36:27 +0100
committerRasmus Dahlberg <rasmus@mullvad.net>2022-03-28 19:10:49 +0200
commit8634892aa6d5d59f73e50652dbe750df263853a3 (patch)
treeb979039d1c63107969f21ef5ce20dc9e827f2ab4
parentace5e7c406dee2ca533d41f5271de0be7403a139 (diff)
sign tree heads and leaves with SSHSIG
-rw-r--r--pkg/requests/requests.go3
-rw-r--r--pkg/requests/requests_test.go8
-rw-r--r--pkg/types/encoding.go31
-rw-r--r--pkg/types/encoding_test.go63
-rw-r--r--pkg/types/leaf.go13
-rw-r--r--pkg/types/leaf_test.go15
-rw-r--r--pkg/types/tree_head.go28
-rw-r--r--pkg/types/tree_head_test.go13
8 files changed, 150 insertions, 24 deletions
diff --git a/pkg/requests/requests.go b/pkg/requests/requests.go
index 26a5213..4d09101 100644
--- a/pkg/requests/requests.go
+++ b/pkg/requests/requests.go
@@ -8,7 +8,8 @@ import (
)
type Leaf struct {
- types.Statement
+ ShardHint uint64 `ascii:"shard_hint"`
+ Preimage types.Hash `ascii:"preimage"`
Signature types.Signature `ascii:"signature"`
VerificationKey types.PublicKey `ascii:"verification_key"`
DomainHint string `ascii:"domain_hint"`
diff --git a/pkg/requests/requests_test.go b/pkg/requests/requests_test.go
index 7102714..365f90f 100644
--- a/pkg/requests/requests_test.go
+++ b/pkg/requests/requests_test.go
@@ -243,10 +243,8 @@ func TestCosignatureFromASCII(t *testing.T) {
func validLeaf(t *testing.T) *Leaf {
t.Helper()
return &Leaf{
- Statement: types.Statement{
- ShardHint: 1,
- Checksum: *newHashBufferInc(t),
- },
+ ShardHint: 1,
+ Preimage: *types.HashFn(newHashBufferInc(t)[:]),
Signature: *newSigBufferInc(t),
VerificationKey: *newPubBufferInc(t),
DomainHint: "example.com",
@@ -257,7 +255,7 @@ func validLeafASCII(t *testing.T) string {
t.Helper()
return fmt.Sprintf("%s=%d\n%s=%x\n%s=%x\n%s=%x\n%s=%s\n",
"shard_hint", 1,
- "checksum", newHashBufferInc(t)[:],
+ "preimage", types.HashFn(newHashBufferInc(t)[:])[:],
"signature", newSigBufferInc(t)[:],
"verification_key", newPubBufferInc(t)[:],
"domain_hint", "example.com",
diff --git a/pkg/types/encoding.go b/pkg/types/encoding.go
new file mode 100644
index 0000000..54a1ac6
--- /dev/null
+++ b/pkg/types/encoding.go
@@ -0,0 +1,31 @@
+package types
+
+import (
+ "encoding/binary"
+ "fmt"
+)
+
+// RFC4251, section 5
+
+func putSSHString(b []byte, str string) int {
+ l := len(str)
+
+ i := 0
+ binary.BigEndian.PutUint32(b[i:i+4], uint32(l))
+ i += 4
+ copy(b[i:i+l], str)
+ i += l
+
+ return i
+}
+
+func getSSHString(b []byte) (*string, error) {
+ if len(b) < 4 {
+ return nil, fmt.Errorf("types: invalid SSH string")
+ }
+
+ l := binary.BigEndian.Uint32(b[:4])
+ str := string(b[4 : 4+l])
+ return &str, nil
+
+}
diff --git a/pkg/types/encoding_test.go b/pkg/types/encoding_test.go
new file mode 100644
index 0000000..e079a8c
--- /dev/null
+++ b/pkg/types/encoding_test.go
@@ -0,0 +1,63 @@
+package types
+
+import (
+ "bytes"
+ "testing"
+)
+
+func TestPutSSHString(t *testing.T) {
+ for _, tbl := range []struct {
+ desc string
+ in string
+ }{
+ {
+ desc: "valid",
+ in: "รถ foo is a bar",
+ },
+ } {
+ var b [128]byte
+ i := putSSHString(b[:], tbl.in)
+
+ if got, want := i, len(tbl.in)+4; got != want {
+ t.Errorf("%q: len: got %d but wanted %d in test", tbl.desc, got, want)
+ }
+
+ if got, want := b[4:4+len(tbl.in)], []byte(tbl.in); !bytes.Equal(got, want) {
+ t.Errorf("%q: got %x but wanted %x", tbl.desc, got, want)
+ }
+ }
+}
+
+func TestGetSSHString(t *testing.T) {
+ for _, tbl := range []struct {
+ desc string
+ in []byte
+ want string
+ wantErr bool
+ }{
+ {
+ desc: "valid",
+ in: []byte{0, 0, 0, 5, 65, 108, 108, 97, 110},
+ want: "Allan",
+ },
+ {
+ desc: "invalid: short",
+ in: []byte{0, 0, 0},
+ wantErr: true,
+ },
+ } {
+ str, err := getSSHString(tbl.in)
+
+ if got, want := err != nil, tbl.wantErr; got != want {
+ t.Errorf("%q: error: got %v but wanted %v: %v", tbl.desc, got, want, err)
+ }
+
+ if err != nil {
+ continue
+ }
+
+ if got, want := str, tbl.want; *got != want {
+ t.Errorf(`%q: got "%v" but wanted "%v"`, tbl.desc, *got, want)
+ }
+ }
+}
diff --git a/pkg/types/leaf.go b/pkg/types/leaf.go
index 1476ead..e7e81dd 100644
--- a/pkg/types/leaf.go
+++ b/pkg/types/leaf.go
@@ -24,9 +24,16 @@ type Leaf struct {
type Leaves []Leaf
func (s *Statement) ToBinary() []byte {
- b := make([]byte, 40)
- binary.BigEndian.PutUint64(b[0:8], s.ShardHint)
- copy(b[8:40], s.Checksum[:])
+ namespace := fmt.Sprintf("tree_leaf:v0:%d@sigsum.org", s.ShardHint)
+ b := make([]byte, 6+4+len(namespace)+4+0+4+6+4+HashSize)
+
+ copy(b[0:6], "SSHSIG")
+ i := 6
+ i += putSSHString(b[i:], namespace)
+ i += putSSHString(b[i:], "")
+ i += putSSHString(b[i:], "sha256")
+ i += putSSHString(b[i:], string(s.Checksum[:]))
+
return b
}
diff --git a/pkg/types/leaf_test.go b/pkg/types/leaf_test.go
index 00e8256..645f49e 100644
--- a/pkg/types/leaf_test.go
+++ b/pkg/types/leaf_test.go
@@ -223,14 +223,17 @@ func TestLeavesFromASCII(t *testing.T) {
func validStatement(t *testing.T) *Statement {
return &Statement{
ShardHint: 72623859790382856,
- Checksum: *newHashBufferInc(t),
+ Checksum: *HashFn(newHashBufferInc(t)[:]),
}
}
func validStatementBytes(t *testing.T) []byte {
return bytes.Join([][]byte{
- []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08},
- newHashBufferInc(t)[:],
+ []byte("SSHSIG"),
+ []byte{0, 0, 0, 41}, []byte("tree_leaf:v0:72623859790382856@sigsum.org"),
+ []byte{0, 0, 0, 0},
+ []byte{0, 0, 0, 6}, []byte("sha256"),
+ []byte{0, 0, 0, 32}, HashFn(newHashBufferInc(t)[:])[:],
}, nil)
}
@@ -238,7 +241,7 @@ func validLeaf(t *testing.T) *Leaf {
return &Leaf{
Statement: Statement{
ShardHint: 72623859790382856,
- Checksum: *newHashBufferInc(t),
+ Checksum: *HashFn(newHashBufferInc(t)[:]),
},
Signature: *newSigBufferInc(t),
KeyHash: *newHashBufferInc(t),
@@ -248,7 +251,7 @@ func validLeaf(t *testing.T) *Leaf {
func validLeafBytes(t *testing.T) []byte {
return bytes.Join([][]byte{
[]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08},
- newHashBufferInc(t)[:],
+ HashFn(newHashBufferInc(t)[:])[:],
newSigBufferInc(t)[:],
newHashBufferInc(t)[:],
}, nil)
@@ -257,7 +260,7 @@ func validLeafBytes(t *testing.T) []byte {
func validLeafASCII(t *testing.T) string {
return fmt.Sprintf("%s=%d\n%s=%x\n%s=%x\n%s=%x\n",
"shard_hint", 72623859790382856,
- "checksum", newHashBufferInc(t)[:],
+ "checksum", HashFn(newHashBufferInc(t)[:])[:],
"signature", newSigBufferInc(t)[:],
"key_hash", newHashBufferInc(t)[:],
)
diff --git a/pkg/types/tree_head.go b/pkg/types/tree_head.go
index 0f1efee..b0214ca 100644
--- a/pkg/types/tree_head.go
+++ b/pkg/types/tree_head.go
@@ -8,6 +8,7 @@ import (
"io"
"git.sigsum.org/sigsum-lib-go/pkg/ascii"
+ "git.sigsum.org/sigsum-lib-go/pkg/hex"
)
type TreeHead struct {
@@ -27,17 +28,30 @@ type CosignedTreeHead struct {
KeyHash []Hash `ascii:"key_hash"`
}
-func (th *TreeHead) ToBinary(keyHash *Hash) []byte {
- b := make([]byte, 80)
+func (th *TreeHead) toBinary() []byte {
+ b := make([]byte, 48)
binary.BigEndian.PutUint64(b[0:8], th.Timestamp)
binary.BigEndian.PutUint64(b[8:16], th.TreeSize)
copy(b[16:48], th.RootHash[:])
- copy(b[48:80], keyHash[:])
return b
}
-func (th *TreeHead) Sign(s crypto.Signer, ctx *Hash) (*SignedTreeHead, error) {
- sig, err := s.Sign(nil, th.ToBinary(ctx), crypto.Hash(0))
+func (th *TreeHead) ToBinary(keyHash *Hash) []byte {
+ namespace := fmt.Sprintf("tree_head:v0:%s@sigsum.org", hex.Serialize((*keyHash)[:])) // length 88
+ b := make([]byte, 6+4+88+4+0+4+6+4+HashSize)
+
+ copy(b[0:6], "SSHSIG")
+ i := 6
+ i += putSSHString(b[i:], namespace)
+ i += putSSHString(b[i:], "")
+ i += putSSHString(b[i:], "sha256")
+ i += putSSHString(b[i:], string((*HashFn(th.toBinary()))[:]))
+
+ return b
+}
+
+func (th *TreeHead) Sign(s crypto.Signer, kh *Hash) (*SignedTreeHead, error) {
+ sig, err := s.Sign(nil, th.ToBinary(kh), crypto.Hash(0))
if err != nil {
return nil, fmt.Errorf("types: failed signing tree head")
}
@@ -57,8 +71,8 @@ func (sth *SignedTreeHead) FromASCII(r io.Reader) error {
return ascii.StdEncoding.Deserialize(r, sth)
}
-func (sth *SignedTreeHead) Verify(key *PublicKey, ctx *Hash) bool {
- return ed25519.Verify(ed25519.PublicKey(key[:]), sth.TreeHead.ToBinary(ctx), sth.Signature[:])
+func (sth *SignedTreeHead) Verify(key *PublicKey, kh *Hash) bool {
+ return ed25519.Verify(ed25519.PublicKey(key[:]), sth.TreeHead.ToBinary(kh), sth.Signature[:])
}
func (cth *CosignedTreeHead) ToASCII(w io.Writer) error {
diff --git a/pkg/types/tree_head_test.go b/pkg/types/tree_head_test.go
index 03a52f5..f20afe0 100644
--- a/pkg/types/tree_head_test.go
+++ b/pkg/types/tree_head_test.go
@@ -7,6 +7,8 @@ import (
"io"
"reflect"
"testing"
+
+ "git.sigsum.org/sigsum-lib-go/pkg/hex"
)
func TestTreeHeadToBinary(t *testing.T) {
@@ -186,11 +188,18 @@ func validTreeHead(t *testing.T) *TreeHead {
}
func validTreeHeadBytes(t *testing.T, keyHash *Hash) []byte {
- return bytes.Join([][]byte{
+ ns := fmt.Sprintf("tree_head:v0:%s@sigsum.org", hex.Serialize((*keyHash)[:]))
+ th := bytes.Join([][]byte{
[]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08},
[]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01},
newHashBufferInc(t)[:],
- keyHash[:],
+ }, nil)
+ return bytes.Join([][]byte{
+ []byte("SSHSIG"),
+ []byte{0, 0, 0, 88}, []byte(ns),
+ []byte{0, 0, 0, 0},
+ []byte{0, 0, 0, 6}, []byte("sha256"),
+ []byte{0, 0, 0, 32}, (*HashFn(th))[:],
}, nil)
}