From 8634892aa6d5d59f73e50652dbe750df263853a3 Mon Sep 17 00:00:00 2001 From: Linus Nordberg Date: Fri, 25 Mar 2022 16:36:27 +0100 Subject: sign tree heads and leaves with SSHSIG --- pkg/requests/requests.go | 3 ++- pkg/requests/requests_test.go | 8 +++--- pkg/types/encoding.go | 31 +++++++++++++++++++++ pkg/types/encoding_test.go | 63 +++++++++++++++++++++++++++++++++++++++++++ pkg/types/leaf.go | 13 ++++++--- pkg/types/leaf_test.go | 15 ++++++----- pkg/types/tree_head.go | 28 ++++++++++++++----- pkg/types/tree_head_test.go | 13 +++++++-- 8 files changed, 150 insertions(+), 24 deletions(-) create mode 100644 pkg/types/encoding.go create mode 100644 pkg/types/encoding_test.go 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) } -- cgit v1.2.3