Various fixes: cert auth lookup by number, debug logging, mobile client

- handleAuthCert: find identity by cert CommonName instead of pubkey

- Add debug log to HandleDial

- mobile: AuthenticateB64, NewClientWithKey, recover guards

- go mod tidy
This commit is contained in:
Niko Marmeladkov 2026-07-01 17:15:05 +03:00
parent 50344a350a
commit 366386bade
5 changed files with 246 additions and 46 deletions

2
go.mod
View file

@ -1,6 +1,6 @@
module github.com/niko/qcc module github.com/niko/qcc
go 1.26 go 1.25.0
require ( require (
github.com/apernet/quic-go v0.59.1-0.20260425001925-6c6cc9bcb716 github.com/apernet/quic-go v0.59.1-0.20260425001925-6c6cc9bcb716

18
go.sum
View file

@ -82,16 +82,12 @@ go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto= golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto=
golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio= golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mobile v0.0.0-20260611195102-4dd8f1dbf5d2 h1:zoM1gIKhVkcQNm43kad8OHLgPNoJ12xIqmxHtKr8Mug= golang.org/x/mobile v0.0.0-20260611195102-4dd8f1dbf5d2 h1:zoM1gIKhVkcQNm43kad8OHLgPNoJ12xIqmxHtKr8Mug=
golang.org/x/mobile v0.0.0-20260611195102-4dd8f1dbf5d2/go.mod h1:QGMqsqLn6orFQ/ksqYMf+Fa33Soa1vPoHEd0Pj7N+lQ= golang.org/x/mobile v0.0.0-20260611195102-4dd8f1dbf5d2/go.mod h1:QGMqsqLn6orFQ/ksqYMf+Fa33Soa1vPoHEd0Pj7N+lQ=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ= golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ=
golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0= golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@ -100,14 +96,10 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220630215102-69896b714898/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220630215102-69896b714898/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM= golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -120,8 +112,6 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
@ -130,8 +120,6 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE= golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4= golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@ -139,10 +127,12 @@ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
golang.org/x/tools v0.46.0 h1:7jTurBkPZu4moS/Uy4OQT1M+QBlsj3wejyZwsT8Z7rk= golang.org/x/tools v0.46.0 h1:7jTurBkPZu4moS/Uy4OQT1M+QBlsj3wejyZwsT8Z7rk=
golang.org/x/tools v0.46.0/go.mod h1:FrD85F8l+NWL+9XWBSyVSHO6Ne4jutsfIFba7AWQ5Ys= golang.org/x/tools v0.46.0/go.mod h1:FrD85F8l+NWL+9XWBSyVSHO6Ne4jutsfIFba7AWQ5Ys=
golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM=
golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY=
golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM=
golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View file

@ -291,23 +291,19 @@ func (m *Manager) GetCall(callID uint64) *Call {
return m.calls[callID] return m.calls[callID]
} }
func (m *Manager) RelayMedia(sender *quic.Conn, data []byte) { func (m *Manager) RelayMedia(senderSess CallSession, data []byte) {
pkt, ok := protocol.UnmarshalMediaPacket(data) callID := senderSess.CallID()
if !ok {
return
}
m.mu.RLock() m.mu.RLock()
call, exists := m.calls[pkt.CallID] call, exists := m.calls[callID]
m.mu.RUnlock() m.mu.RUnlock()
if !exists || !call.Active { if !exists || !call.Active {
return return
} }
var target *quic.Conn var target *quic.Conn
if sender == call.FromSess.Conn() { if senderSess == call.FromSess {
target = call.ToSess.Conn() target = call.ToSess.Conn()
} else if sender == call.ToSess.Conn() { } else if senderSess == call.ToSess {
target = call.FromSess.Conn() target = call.FromSess.Conn()
} else { } else {
return return
@ -315,7 +311,7 @@ func (m *Manager) RelayMedia(sender *quic.Conn, data []byte) {
if err := target.SendDatagram(data); err != nil { if err := target.SendDatagram(data); err != nil {
m.logger.Warn("Failed to relay media datagram", m.logger.Warn("Failed to relay media datagram",
zap.Uint64("call_id", pkt.CallID), zap.Uint64("call_id", callID),
zap.Error(err)) zap.Error(err))
} }
} }

View file

@ -193,7 +193,7 @@ func (s *Server) handleDatagrams(ctx context.Context, conn *quic.Conn, sess *Ses
continue continue
} }
if s.callMgr != nil && sess.IsAuthenticated() && sess.CallID() != 0 { if s.callMgr != nil && sess.IsAuthenticated() && sess.CallID() != 0 {
s.callMgr.RelayMedia(conn, data) s.callMgr.RelayMedia(sess, data)
} }
} }
} }

View file

@ -5,6 +5,7 @@ import (
"crypto/ed25519" "crypto/ed25519"
"crypto/rand" "crypto/rand"
"crypto/sha256" "crypto/sha256"
"encoding/base64"
"crypto/tls" "crypto/tls"
"crypto/x509" "crypto/x509"
"encoding/binary" "encoding/binary"
@ -69,12 +70,212 @@ func NewClient() *Client {
} }
} }
func NewClientWithKey(privKey []byte) *Client {
priv := ed25519.PrivateKey(privKey)
pub := priv.Public().(ed25519.PublicKey)
return &Client{
privKey: priv,
pubKey: pub,
}
}
func Ping() string {
return "pong"
}
func (c *Client) PrivateKey() []byte {
c.mu.Lock()
defer c.mu.Unlock()
return []byte(c.privKey)
}
func (c *Client) PublicKey() []byte {
c.mu.Lock()
defer c.mu.Unlock()
return []byte(c.pubKey)
}
func (c *Client) SetCallbacks(cbs Callbacks) { func (c *Client) SetCallbacks(cbs Callbacks) {
c.mu.Lock() c.mu.Lock()
defer c.mu.Unlock() defer c.mu.Unlock()
c.callbacks = cbs c.callbacks = cbs
} }
func (c *Client) CertDER() []byte {
c.mu.Lock()
defer c.mu.Unlock()
return c.certDER
}
func (c *Client) CACertDER() []byte {
c.mu.Lock()
defer c.mu.Unlock()
return c.caCertDER
}
func (c *Client) SetCertDER(der []byte) {
c.mu.Lock()
defer c.mu.Unlock()
c.certDER = der
}
func (c *Client) SetCACertDER(der []byte) {
c.mu.Lock()
defer c.mu.Unlock()
c.caCertDER = der
}
func (c *Client) AuthenticateB64(certB64 string, caCertB64 string) (number string, err error) {
defer func() {
if r := recover(); r != nil {
number = ""
err = fmt.Errorf("panic in AuthenticateB64: %v", r)
}
}()
var certDER []byte
if certB64 != "" {
certDER, err = base64.StdEncoding.DecodeString(certB64)
if err != nil {
return "", fmt.Errorf("decode cert: %w", err)
}
}
c.mu.Lock()
conn := c.conn
c.mu.Unlock()
if conn == nil {
return "", fmt.Errorf("not connected")
}
authSt, streamErr := conn.OpenStream()
if streamErr != nil {
return "", streamErr
}
if streamErr = protocol.WriteFrame(authSt, byte(types.OpGetChallenge), nil); streamErr != nil {
authSt.Close()
return "", fmt.Errorf("send challenge: %w", streamErr)
}
frame, streamErr := protocol.ReadFrame(authSt)
if streamErr != nil {
authSt.Close()
return "", fmt.Errorf("read challenge: %w", streamErr)
}
if types.OpCode(frame.OpCode) != types.OpChallenge {
authSt.Close()
return "", fmt.Errorf("expected challenge, got op=%x", frame.OpCode)
}
challenge := auth.UnmarshalChallenge(frame.Payload)
if challenge == nil {
authSt.Close()
return "", fmt.Errorf("invalid challenge payload")
}
if len(certDER) > 0 {
c.mu.Lock()
privKey := c.privKey
c.mu.Unlock()
challengeData := challenge.Marshal()
signature := ed25519.Sign(privKey, challengeData)
payload := make([]byte, 1+64+2+len(certDER))
payload[0] = 64
copy(payload[1:], signature)
binary.BigEndian.PutUint16(payload[1+64:], uint16(len(certDER)))
copy(payload[1+64+2:], certDER)
if streamErr = protocol.WriteFrame(authSt, byte(types.OpAuthCert), payload); streamErr != nil {
authSt.Close()
return "", streamErr
}
frame, streamErr = protocol.ReadFrame(authSt)
if streamErr != nil {
authSt.Close()
return "", streamErr
}
if types.OpCode(frame.OpCode) == types.OpError {
authSt.Close()
errMsg := parseError(frame.Payload)
return "", fmt.Errorf("server error: %s", errMsg)
}
if types.OpCode(frame.OpCode) != types.OpIdentity {
authSt.Close()
return "", fmt.Errorf("expected identity, got op=%x", frame.OpCode)
}
number, _, _ = parseIdentityPayload(frame.Payload)
if number == "" {
authSt.Close()
return "", fmt.Errorf("invalid identity payload")
}
c.mu.Lock()
c.number = number
c.authOpened = true
c.mu.Unlock()
go c.handleIncomingStreams()
return number, nil
}
solution := c.solvePoW(challenge)
if solution == nil {
authSt.Close()
return "", fmt.Errorf("PoW solve failed")
}
solnPayload := make([]byte, 40)
copy(solnPayload[0:8], solution.ClientNonce[:])
copy(solnPayload[8:40], solution.ClientPubKey[:])
if streamErr = protocol.WriteFrame(authSt, byte(types.OpSolve), solnPayload); streamErr != nil {
authSt.Close()
return "", fmt.Errorf("send solution: %w", streamErr)
}
frame, streamErr = protocol.ReadFrame(authSt)
if streamErr != nil {
authSt.Close()
return "", fmt.Errorf("read response: %w", streamErr)
}
if types.OpCode(frame.OpCode) == types.OpError {
authSt.Close()
errMsg := parseError(frame.Payload)
return "", fmt.Errorf("server error: %s", errMsg)
}
if types.OpCode(frame.OpCode) != types.OpIdentity {
authSt.Close()
return "", fmt.Errorf("expected identity, got op=%x", frame.OpCode)
}
number, newCertDER, newCACertDER := parseIdentityPayload(frame.Payload)
if number == "" {
authSt.Close()
return "", fmt.Errorf("invalid identity payload")
}
c.mu.Lock()
c.number = number
c.certDER = newCertDER
c.caCertDER = newCACertDER
c.authOpened = true
c.mu.Unlock()
go c.handleIncomingStreams()
return number, nil
}
func (c *Client) Connect(addr string) error { func (c *Client) Connect(addr string) error {
c.mu.Lock() c.mu.Lock()
defer c.mu.Unlock() defer c.mu.Unlock()
@ -125,7 +326,14 @@ func (c *Client) Connect(addr string) error {
return nil return nil
} }
func (c *Client) Authenticate() (string, error) { func (c *Client) Authenticate() (number string, err error) {
defer func() {
if r := recover(); r != nil {
number = ""
err = fmt.Errorf("panic in Authenticate: %v", r)
}
}()
c.mu.Lock() c.mu.Lock()
conn := c.conn conn := c.conn
c.mu.Unlock() c.mu.Unlock()
@ -134,20 +342,20 @@ func (c *Client) Authenticate() (string, error) {
return "", fmt.Errorf("not connected") return "", fmt.Errorf("not connected")
} }
authSt, err := conn.OpenStream() authSt, streamErr := conn.OpenStream()
if err != nil { if streamErr != nil {
return "", fmt.Errorf("open auth stream: %w", err) return "", streamErr
} }
if err := protocol.WriteFrame(authSt, byte(types.OpGetChallenge), nil); err != nil { if streamErr = protocol.WriteFrame(authSt, byte(types.OpGetChallenge), nil); streamErr != nil {
authSt.Close() authSt.Close()
return "", fmt.Errorf("send challenge: %w", err) return "", fmt.Errorf("send challenge: %w", streamErr)
} }
frame, err := protocol.ReadFrame(authSt) frame, streamErr := protocol.ReadFrame(authSt)
if err != nil { if streamErr != nil {
authSt.Close() authSt.Close()
return "", fmt.Errorf("read challenge: %w", err) return "", fmt.Errorf("read challenge: %w", streamErr)
} }
if types.OpCode(frame.OpCode) != types.OpChallenge { if types.OpCode(frame.OpCode) != types.OpChallenge {
@ -171,15 +379,15 @@ func (c *Client) Authenticate() (string, error) {
copy(solnPayload[0:8], solution.ClientNonce[:]) copy(solnPayload[0:8], solution.ClientNonce[:])
copy(solnPayload[8:40], solution.ClientPubKey[:]) copy(solnPayload[8:40], solution.ClientPubKey[:])
if err := protocol.WriteFrame(authSt, byte(types.OpSolve), solnPayload); err != nil { if streamErr = protocol.WriteFrame(authSt, byte(types.OpSolve), solnPayload); streamErr != nil {
authSt.Close() authSt.Close()
return "", fmt.Errorf("send solution: %w", err) return "", fmt.Errorf("send solution: %w", streamErr)
} }
frame, err = protocol.ReadFrame(authSt) frame, streamErr = protocol.ReadFrame(authSt)
if err != nil { if streamErr != nil {
authSt.Close() authSt.Close()
return "", fmt.Errorf("read response: %w", err) return "", fmt.Errorf("read response: %w", streamErr)
} }
if types.OpCode(frame.OpCode) == types.OpError { if types.OpCode(frame.OpCode) == types.OpError {
@ -193,7 +401,7 @@ func (c *Client) Authenticate() (string, error) {
return "", fmt.Errorf("expected identity, got op=%x", frame.OpCode) return "", fmt.Errorf("expected identity, got op=%x", frame.OpCode)
} }
number, certDER, caCertDER := parseIdentityPayload(frame.Payload) number, newCertDER, newCACertDER := parseIdentityPayload(frame.Payload)
if number == "" { if number == "" {
authSt.Close() authSt.Close()
return "", fmt.Errorf("invalid identity payload") return "", fmt.Errorf("invalid identity payload")
@ -201,8 +409,8 @@ func (c *Client) Authenticate() (string, error) {
c.mu.Lock() c.mu.Lock()
c.number = number c.number = number
c.certDER = certDER c.certDER = newCertDER
c.caCertDER = caCertDER c.caCertDER = newCACertDER
c.authOpened = true c.authOpened = true
c.mu.Unlock() c.mu.Unlock()
@ -289,8 +497,6 @@ func (c *Client) Disconnect() {
c.transport = nil c.transport = nil
c.udpConn = nil c.udpConn = nil
c.number = "" c.number = ""
c.certDER = nil
c.caCertDER = nil
c.activeCall = false c.activeCall = false
c.callID = 0 c.callID = 0
c.mediaKey = nil c.mediaKey = nil
@ -298,6 +504,8 @@ func (c *Client) Disconnect() {
} }
func (c *Client) handleIncomingStreams() { func (c *Client) handleIncomingStreams() {
defer func() { recover() }()
c.mu.Lock() c.mu.Lock()
conn := c.conn conn := c.conn
ctx := c.ctx ctx := c.ctx
@ -312,12 +520,16 @@ func (c *Client) handleIncomingStreams() {
if err != nil { if err != nil {
return return
} }
go c.handleStream(st) go func() {
defer func() { recover() }()
c.handleStream(st)
}()
} }
} }
func (c *Client) handleStream(st *quic.Stream) { func (c *Client) handleStream(st *quic.Stream) {
defer st.Close() defer st.Close()
defer func() { recover() }()
for { for {
frame, err := protocol.ReadFrame(st) frame, err := protocol.ReadFrame(st)
@ -370,6 +582,8 @@ func (c *Client) handleStream(st *quic.Stream) {
} }
func (c *Client) handleDatagrams() { func (c *Client) handleDatagrams() {
defer func() { recover() }()
c.mu.Lock() c.mu.Lock()
conn := c.conn conn := c.conn
ctx := c.ctx ctx := c.ctx