whspbrd-final/relay/client.go
2026-05-02 22:09:19 +02:00

206 lines
5.5 KiB
Go

package relay
import (
"WhspBrd/menc"
"WhspBrd/owner"
"WhspBrd/thrembio"
"WhspBrd/typio/splco"
"crypto/mlkem"
"crypto/sha256"
"encoding/binary"
"errors"
"fmt"
"net"
"strconv"
"time"
)
type Client interface {
Register(tokenId uint32, token []byte) error
Login() error
PublishKem() error
GetKem(whos owner.Identity) (*mlkem.EncapsulationKey1024, error)
Send(to owner.Identity, encap *mlkem.EncapsulationKey1024, data []byte) error
MessageLen() uint32
MessageGet(id uint32) (sender owner.Identity, timestamp uint32, data []byte, err error)
}
type client struct {
th thrembio.Client
sc owner.Secret
spuha owner.Identity
}
func NewClient(addr string, secret owner.Secret, spuha owner.Identity) (Client, error) {
host, portStr, err := net.SplitHostPort(addr)
if err != nil {
return nil, fmt.Errorf("invalid address, expected ip:port: %w", err)
}
port, err := strconv.Atoi(portStr)
if err != nil {
return nil, fmt.Errorf("invalid port: %w", err)
}
ip := net.ParseIP(host)
if ip == nil {
return nil, fmt.Errorf("invalid ip: %s", host)
}
th, err := thrembio.NewClient(&net.UDPAddr{IP: ip, Port: port}, secret)
if err != nil {
return nil, err
}
return &client{
th: th,
sc: secret,
spuha: spuha,
}, nil
}
func (c *client) Register(tokenId uint32, token []byte) error {
return c.th.Register(tokenId, token)
}
func (c *client) Login() error {
return c.th.Login()
}
func (c *client) PublishKem() error {
// [serverPuha] [kek] [osign(serverPuha | kek)]
data := append(c.spuha[:], c.sc.EncapKey()...)
osign, err := c.sc.Sign(data)
if err != nil {
return err
}
data = append(data, osign...)
data = append([]byte("pubkem"), data...)
err = c.th.Write(data)
if err != nil {
return err
}
res, err := c.th.Read()
if err != nil {
return err
}
if string(res) != "done" {
return err
}
return nil
}
func (c *client) GetKem(whos owner.Identity) (*mlkem.EncapsulationKey1024, error) {
// [receiverPuha]
// <- ([serverPuha] [kek] [osign(serverPuha | kek)])(of that receiverPuha)
err := c.th.Write(append([]byte("getkem"), whos[:]...))
if err != nil {
return nil, err
}
res, err := c.th.Read()
if err != nil {
return nil, err
}
if len(res) < owner.IdentitySize+mlkem.EncapsulationKeySize1024 {
return nil, errors.New("invalid response length")
}
serverPuha := res[:owner.IdentitySize]
kek := res[owner.IdentitySize : owner.IdentitySize+mlkem.EncapsulationKeySize1024]
osign := res[owner.IdentitySize+mlkem.EncapsulationKeySize1024:]
whosver, err := owner.Verify(append(serverPuha, kek...), osign)
if err != nil {
return nil, err
}
if whosver != whos {
return nil, errors.New("not signed by the expected owner")
}
return mlkem.NewEncapsulationKey1024(kek)
}
func (c *client) Send(to owner.Identity, encap *mlkem.EncapsulationKey1024, data []byte) error {
// -> [serverPuha] [receiverPuha] [kct] [time] [encData] [osign(serverPuha | receiverPuha | kct | time | encData)]
// <- "send"
shared, kct := encap.Encapsulate()
key := sha256.Sum256(shared)
timee := make([]byte, 8)
binary.BigEndian.PutUint64(timee, uint64(time.Now().Unix()))
encData, err := menc.AESGCM_Quick_Encrypt(key[:], data, timee)
if err != nil {
return err
}
payload := splco.Append(c.spuha[:], to[:], kct, timee, encData)
osign, err := c.sc.Sign(payload)
if err != nil {
return err
}
payload = append(payload, osign...)
err = c.th.Write(append([]byte("send"), payload...))
if err != nil {
return err
}
res, err := c.th.Read()
if err != nil {
return err
}
if string(res) != "send" {
return errors.New("unexpected response")
}
return nil
}
func (c *client) MessageLen() uint32 {
// -> "msglen"
// <- [len]
err := c.th.Write([]byte("msglen"))
if err != nil {
return 0
}
res, err := c.th.Read()
if err != nil {
return 0
}
if len(res) != 4 {
return 0
}
return binary.BigEndian.Uint32(res)
}
func (c *client) MessageGet(id uint32) (sender owner.Identity, timestamp uint32, data []byte, err error) {
// -> [id]
// <- [serverPuha] [receiverPuha] [kct] [time] [encData] [osign(serverPuha | senderPuha | kct | time | encData)]
idB := make([]byte, 4)
binary.BigEndian.PutUint32(idB, id)
err = c.th.Write(append([]byte("msgget"), idB...))
if err != nil {
return owner.Identity{}, 0, nil, err
}
res, err := c.th.Read()
if err != nil {
return owner.Identity{}, 0, nil, err
}
if len(res) < owner.IdentitySize*2+mlkem.CiphertextSize1024+8 {
return owner.Identity{}, 0, nil, errors.New("invalid response length")
}
serverPuha := owner.Identity(res[:owner.IdentitySize])
//receiverPuha := owner.Identity(res[owner.IdentitySize : owner.IdentitySize*2])
kct := res[owner.IdentitySize*2 : owner.IdentitySize*2+mlkem.CiphertextSize1024]
timee := res[owner.IdentitySize*2+mlkem.CiphertextSize1024 : owner.IdentitySize*2+mlkem.CiphertextSize1024+8]
encData := res[owner.IdentitySize*2+mlkem.CiphertextSize1024+8 : owner.IdentitySize*2+mlkem.CiphertextSize1024+8+len(res)-owner.IdentitySize*2-mlkem.CiphertextSize1024-8]
osign := res[len(res)-owner.IdentitySize:]
verData := res[:len(res)-owner.IdentitySize]
whosver, err := owner.Verify(verData, osign)
if err != nil {
return owner.Identity{}, 0, nil, err
}
shared, err := c.sc.Decapsulate(kct)
if err != nil {
return owner.Identity{}, 0, nil, err
}
key := sha256.Sum256(shared)
data, err = menc.AESGCM_Quick_Decrypt(key[:], encData, timee)
if err != nil {
return owner.Identity{}, 0, nil, err
}
if owner.IdentityEq(c.spuha, serverPuha) {
return owner.Identity{}, 0, nil, errors.New("unexpected sender")
}
return whosver, binary.BigEndian.Uint32(timee), data, nil
}