package client import ( "crypto/rand" "crypto/rsa" "crypto/x509" "encoding/json" "errors" "fmt" "github.com/admin-else/strom/api" "github.com/admin-else/strom/crypto" "github.com/admin-else/strom/event" "github.com/admin-else/strom/proto" "github.com/admin-else/strom/proto_base" "github.com/admin-else/strom/text" "github.com/admin-else/strom/proto_generated/v1_21_8" ) type LoginClient struct { *proto.Conn Account *api.Account ServerHost string ServerPort uint16 GivenAccount *v1_21_8.LoginToClientPacketSuccess } var ( NoTokenErr = errors.New("the account has no token so we cant join online servers") BadPublicKeyTypeErr = errors.New("the public key is not rsa") FailedToLoginErr = errors.New("failed to login") ) type KickedDuringLoginErr struct { text.Component } func (k KickedDuringLoginErr) Error() string { return k.String() } func (s *LoginClient) OnDefault(event event.Unhandled) (err error) { err = fmt.Errorf("unexpected event: %#v", event) return } func (s *LoginClient) OnCycle(_ event.Tick) (err error) { return } func (s *LoginClient) OnClose(_ event.Close) (err error) { return } func (s *LoginClient) OnCompress(compress *v1_21_8.LoginToClientPacketCompress) (err error) { s.SetCompressionThreshold(compress.Threshold) return } func (s *LoginClient) OnEncrypt(packet *v1_21_8.LoginToClientPacketEncryptionBegin) (err error) { sharedSecret := make([]byte, 16) _, _ = rand.Read(sharedSecret) //never fails if packet.ShouldAuthenticate { if s.Account.Ygg == "" { err = NoTokenErr return } serverId := crypto.AuthDigest([]byte(packet.ServerId), sharedSecret, packet.PublicKey) err = s.Account.JoinServer(serverId) if err != nil { return } } pubAny, err := x509.ParsePKIXPublicKey(packet.PublicKey) if err != nil { return } pub, ok := pubAny.(*rsa.PublicKey) if !ok { err = BadPublicKeyTypeErr return } verifyTokenEnc, err := rsa.EncryptPKCS1v15(rand.Reader, pub, packet.VerifyToken) if err != nil { return } sharedSecretEnc, err := rsa.EncryptPKCS1v15(rand.Reader, pub, sharedSecret) if err != nil { return } err = s.Send(&v1_21_8.LoginToServerPacketEncryptionBegin{SharedSecret: sharedSecretEnc, VerifyToken: verifyTokenEnc}) if err != nil { return } err = s.SetSecret(sharedSecret) return } func (s *LoginClient) OnDisconnect(packet *v1_21_8.LoginToClientPacketDisconnect) (err error) { var reason text.Component err = json.Unmarshal([]byte(packet.Reason), &reason) if err != nil { return } err = KickedDuringLoginErr{reason} return } func (s *LoginClient) OnSuccess(success *v1_21_8.LoginToClientPacketSuccess) (err error) { s.GivenAccount = success err = s.Send(&v1_21_8.LoginToServerPacketLoginAcknowledged{}) if err != nil { return } s.SetState(proto_base.Configuration) err = event.HandlerDoneErr{} return } func (s *LoginClient) OnStart() (err error) { s.RegisterCritical(s.OnDefault) s.RegisterCritical(s.OnCycle) s.RegisterCritical(s.OnClose) s.RegisterUntilLatest(s.OnCompress) s.RegisterUntilLatest(s.OnEncrypt) s.RegisterUntilLatest(s.OnDisconnect) s.RegisterUntilLatest(s.OnSuccess) p, err := MakeHandshakePacket(s.Conn, proto_base.Login) if err != nil { return } if s.ServerHost == "" && s.ServerPort == 0 { p.ServerHost = s.ServerHost p.ServerPort = s.ServerPort } err = s.Send(p) if err != nil { return } s.SetState(proto_base.Login) err = s.Send(&v1_21_8.LoginToServerPacketLoginStart{Username: s.Account.Name, PlayerUUID: s.Account.Uuid}) return } func Login(c *proto.Conn, account *api.Account) (err error) { err = c.Start(&LoginClient{ Conn: c, Account: account, }) if err != nil { err = errors.Join(FailedToLoginErr, err) } return } func ConnectAndLogin(connectTo string, account *api.Account) (c *proto.Conn, err error) { c, err = Connect(connectTo) if err != nil { return } err = c.Start(&LoginClient{ Conn: c, Account: account, }) if err != nil { err = errors.Join(FailedToLoginErr, err) } return }