Support chat validation
This commit is contained in:
42
chat/sign/cache.go
Normal file
42
chat/sign/cache.go
Normal file
@ -0,0 +1,42 @@
|
||||
package sign
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
type SignatureCache struct {
|
||||
signatures [128]*Signature
|
||||
signIndexes map[Signature]int
|
||||
cachedBuffer []*Signature
|
||||
}
|
||||
|
||||
func NewSignatureCache() SignatureCache {
|
||||
return SignatureCache{
|
||||
signIndexes: make(map[Signature]int),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SignatureCache) PopOrInsert(self *Signature, lastSeen []*Signature) {
|
||||
var tmp *Signature
|
||||
s.cachedBuffer = s.cachedBuffer[:0] // clear buffer
|
||||
if self != nil {
|
||||
s.cachedBuffer = append(s.cachedBuffer, self)
|
||||
}
|
||||
for _, v := range lastSeen {
|
||||
s.cachedBuffer = append(s.cachedBuffer, v)
|
||||
}
|
||||
for i := 0; i < len(s.cachedBuffer) && i < len(s.signatures); i++ {
|
||||
v := s.cachedBuffer[i]
|
||||
if i, ok := s.signIndexes[*v]; ok {
|
||||
s.signatures[i] = nil
|
||||
}
|
||||
tmp, s.signatures[i] = s.signatures[i], v
|
||||
s.signIndexes[*v] = i
|
||||
if tmp != nil {
|
||||
s.cachedBuffer = append(s.cachedBuffer, tmp)
|
||||
delete(s.signIndexes, *tmp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var UncachedSignature = errors.New("uncached signature")
|
65
chat/sign/cache_test.go
Normal file
65
chat/sign/cache_test.go
Normal file
@ -0,0 +1,65 @@
|
||||
package sign
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSignatureCache(t *testing.T) {
|
||||
cache := NewSignatureCache()
|
||||
s1 := &Signature{1}
|
||||
s2 := &Signature{2}
|
||||
s3 := &Signature{3}
|
||||
s4 := &Signature{4}
|
||||
// t.Logf("%p, %p, %p, %p", s1, s2, s3, s4)
|
||||
cache.PopOrInsert(nil, []*Signature{s1, s2, s3})
|
||||
// cache: [s1, s2, s3, nil...]
|
||||
if cache.signatures[0] != s1 || cache.signatures[1] != s2 || cache.signatures[2] != s3 {
|
||||
t.Log(cache.signatures)
|
||||
t.Fatal("insert s1~3 error")
|
||||
}
|
||||
cache.PopOrInsert(s4, []*Signature{s3})
|
||||
// cache: [s4, s3, s1, s2, nil...]
|
||||
if cache.signatures[0] != s4 {
|
||||
t.Log(cache.signatures)
|
||||
t.Fatal("insert s4 error")
|
||||
}
|
||||
if cache.signatures[1] != s3 {
|
||||
t.Log(cache.signatures)
|
||||
t.Fatal("pop s3 error")
|
||||
}
|
||||
if cache.signatures[2] != s1 || cache.signatures[3] != s2 {
|
||||
t.Log(cache.signatures)
|
||||
t.Fatal("s1~2 position error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSignatureCache_2(t *testing.T) {
|
||||
cache := NewSignatureCache()
|
||||
signs := make([]*Signature, len(cache.signatures)+5)
|
||||
for i := range signs {
|
||||
signs[i] = new(Signature)
|
||||
_, _ = rand.Read(signs[i][:])
|
||||
}
|
||||
cache.PopOrInsert(nil, signs[:len(cache.signatures)])
|
||||
if !signatureEquals(cache.signatures[:], signs[:len(cache.signatures)]) {
|
||||
t.Fatal("insert error")
|
||||
}
|
||||
insert2 := signs[len(cache.signatures)-5:]
|
||||
cache.PopOrInsert(nil, insert2)
|
||||
if !signatureEquals(cache.signatures[:10], insert2) {
|
||||
t.Fatal("insert and pop error")
|
||||
}
|
||||
}
|
||||
|
||||
func signatureEquals(a, b []*Signature) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for i := range a {
|
||||
if a[i] != b[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
package sign
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
|
||||
"github.com/google/uuid"
|
||||
@ -11,40 +13,81 @@ import (
|
||||
)
|
||||
|
||||
type Message struct {
|
||||
Link struct {
|
||||
Index int
|
||||
Sender uuid.UUID
|
||||
Session uuid.UUID
|
||||
}
|
||||
Signature []byte
|
||||
PackedMessageBody
|
||||
Prev Prev
|
||||
Signature *Signature
|
||||
*MessageBody
|
||||
Unsigned *chat.Message
|
||||
FilterMask
|
||||
}
|
||||
|
||||
type Prev struct {
|
||||
Index int
|
||||
Sender uuid.UUID
|
||||
Session uuid.UUID
|
||||
}
|
||||
|
||||
type Session struct {
|
||||
sessionID uuid.UUID
|
||||
publicKey user.PublicKey
|
||||
SessionID uuid.UUID
|
||||
PublicKey user.PublicKey
|
||||
|
||||
valid bool
|
||||
lastMsg *Message
|
||||
}
|
||||
|
||||
func (s Session) WriteTo(w io.Writer) (n int64, err error) {
|
||||
n1, err := pk.UUID(s.sessionID).WriteTo(w)
|
||||
n1, err := pk.UUID(s.SessionID).WriteTo(w)
|
||||
if err != nil {
|
||||
return n1, err
|
||||
}
|
||||
n2, err := s.publicKey.WriteTo(w)
|
||||
n2, err := s.PublicKey.WriteTo(w)
|
||||
return n1 + n2, err
|
||||
}
|
||||
|
||||
func (s *Session) ReadFrom(r io.Reader) (n int64, err error) {
|
||||
n1, err := ((*pk.UUID)(&s.sessionID)).ReadFrom(r)
|
||||
n1, err := ((*pk.UUID)(&s.SessionID)).ReadFrom(r)
|
||||
if err != nil {
|
||||
return n1, err
|
||||
}
|
||||
n2, err := s.publicKey.ReadFrom(r)
|
||||
n2, err := s.PublicKey.ReadFrom(r)
|
||||
return n1 + n2, err
|
||||
}
|
||||
|
||||
func (s *Session) Update(msg Message) bool {
|
||||
panic("todo")
|
||||
func (s *Session) InitValidate() {
|
||||
s.valid = true
|
||||
s.lastMsg = nil
|
||||
}
|
||||
|
||||
func (s *Session) VerifyAndUpdate(msg *Message) bool {
|
||||
s.valid = s.valid && s.verifyHash(msg) && s.verifyChain(msg)
|
||||
if s.valid {
|
||||
s.lastMsg = msg
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *Session) verifyHash(msg *Message) bool {
|
||||
h := sha256.New()
|
||||
// 1
|
||||
_ = binary.Write(h, binary.BigEndian, int32(1))
|
||||
// Prev
|
||||
_, _ = h.Write(msg.Prev.Sender[:])
|
||||
_, _ = h.Write(msg.Prev.Session[:])
|
||||
_ = binary.Write(h, binary.BigEndian, msg.Prev.Index)
|
||||
// Body
|
||||
_ = binary.Write(h, binary.BigEndian, msg.Salt)
|
||||
_ = binary.Write(h, binary.BigEndian, msg.Timestamp.Unix())
|
||||
content := []byte(msg.PlainMsg)
|
||||
_ = binary.Write(h, binary.BigEndian, int32(len(content)))
|
||||
_, _ = h.Write(content)
|
||||
// Body.LastSeen
|
||||
_ = binary.Write(h, binary.BigEndian, int32(len(msg.LastSeen)))
|
||||
for _, v := range msg.LastSeen {
|
||||
_, _ = h.Write((*v)[:])
|
||||
}
|
||||
return s.PublicKey.VerifyMessage(h.Sum(nil), msg.Signature[:]) == nil
|
||||
}
|
||||
|
||||
func (s *Session) verifyChain(msg *Message) bool {
|
||||
return s.lastMsg != nil && (msg.Prev.Index < s.lastMsg.Prev.Index || msg.Prev.Sender != s.lastMsg.Prev.Sender || msg.Prev.Session != s.lastMsg.Prev.Session)
|
||||
}
|
||||
|
@ -9,6 +9,13 @@ import (
|
||||
pk "github.com/Tnze/go-mc/net/packet"
|
||||
)
|
||||
|
||||
type MessageBody struct {
|
||||
PlainMsg string
|
||||
Timestamp time.Time
|
||||
Salt int64
|
||||
LastSeen []*Signature
|
||||
}
|
||||
|
||||
type PackedMessageBody struct {
|
||||
PlainMsg string
|
||||
Timestamp time.Time
|
||||
@ -37,6 +44,25 @@ func (m *PackedMessageBody) ReadFrom(r io.Reader) (n int64, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (m *PackedMessageBody) Unpack(cache *SignatureCache) (*MessageBody, error) {
|
||||
LastSeen := make([]*Signature, len(m.LastSeen))
|
||||
for i, v := range m.LastSeen {
|
||||
if v.Signature != nil {
|
||||
LastSeen[i] = v.Signature
|
||||
} else if v.ID >= 0 && int(v.ID) < len(cache.signatures) {
|
||||
LastSeen[i] = cache.signatures[v.ID]
|
||||
} else {
|
||||
return nil, UncachedSignature
|
||||
}
|
||||
}
|
||||
return &MessageBody{
|
||||
PlainMsg: m.PlainMsg,
|
||||
Timestamp: m.Timestamp,
|
||||
Salt: m.Salt,
|
||||
LastSeen: LastSeen,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type HistoryMessage struct {
|
||||
Sender uuid.UUID
|
||||
Signature []byte
|
||||
|
Reference in New Issue
Block a user