From e0266e8d6b873ab83cb9a0a42e009fa1a571f4b8 Mon Sep 17 00:00:00 2001 From: Muzzammil Shahid Date: Wed, 11 Dec 2024 19:06:20 +0500 Subject: [PATCH] Authenticate WebRTC using WAMP --- client.go | 26 +++++++++++++++--- cmd/client/main.go | 4 ++- cmd/provider/main.go | 63 ++++++++++++++++++++++++++++++++++++++++++++ provider.go | 41 +++++++++++++++------------- types.go | 3 +++ 5 files changed, 115 insertions(+), 22 deletions(-) diff --git a/client.go b/client.go index 8e89ff1..abb5dc3 100644 --- a/client.go +++ b/client.go @@ -7,6 +7,7 @@ import ( "github.com/google/uuid" "github.com/pion/webrtc/v4" + "github.com/xconnio/wampproto-go/auth" "github.com/xconnio/xconn-go" ) @@ -16,9 +17,10 @@ type ClientConfig struct { ProcedureWebRTCOffer string TopicAnswererOnCandidate string Serializer xconn.WSSerializerSpec + Authenticator auth.ClientAuthenticator } -func ConnectWebRTC(config *ClientConfig) (*WebRTCSession, error) { +func connectWebRTC(config *ClientConfig) (*WebRTCSession, error) { session, err := xconn.Connect(context.Background(), config.URL, config.Realm) if err != nil { return nil, err @@ -66,14 +68,32 @@ func ConnectWebRTC(config *ClientConfig) (*WebRTCSession, error) { }, nil } +func ConnectWebRTC(config *ClientConfig) (*WebRTCSession, error) { + webRTCSession, err := connectWebRTC(config) + if err != nil { + return nil, err + } + + peer := NewWebRTCPeer(webRTCSession.Channel) + _, err = xconn.Join(peer, config.Realm, config.Serializer.Serializer(), config.Authenticator) + if err != nil { + return nil, err + } + + return &WebRTCSession{ + Channel: webRTCSession.Channel, + Connection: webRTCSession.Connection, + }, nil +} + func ConnectWAMP(config *ClientConfig) (*xconn.Session, error) { - webRTCConnection, err := ConnectWebRTC(config) + webRTCConnection, err := connectWebRTC(config) if err != nil { return nil, err } peer := NewWebRTCPeer(webRTCConnection.Channel) - base, err := xconn.Join(peer, config.Realm, config.Serializer.Serializer(), nil) + base, err := xconn.Join(peer, config.Realm, config.Serializer.Serializer(), config.Authenticator) if err != nil { return nil, err } diff --git a/cmd/client/main.go b/cmd/client/main.go index d9ea073..f34f417 100644 --- a/cmd/client/main.go +++ b/cmd/client/main.go @@ -4,6 +4,7 @@ import ( log "github.com/sirupsen/logrus" "github.com/xconnio/wamp-webrtc-go" + "github.com/xconnio/wampproto-go/auth" "github.com/xconnio/xconn-go" ) @@ -19,8 +20,9 @@ func main() { ProcedureWebRTCOffer: procedureWebRTCOffer, TopicAnswererOnCandidate: topicAnswererOnCandidate, Serializer: xconn.CBORSerializerSpec, + Authenticator: auth.NewCRAAuthenticator("john", map[string]any{}, "hello"), } - session, err := wamp_webrtc_go.ConnectWAMP(config) + session, err := wamp_webrtc_go.ConnectWebRTC(config) if err != nil { log.Fatal(err) } diff --git a/cmd/provider/main.go b/cmd/provider/main.go index 801ce45..cfcf5a7 100644 --- a/cmd/provider/main.go +++ b/cmd/provider/main.go @@ -2,12 +2,14 @@ package main import ( "context" + "fmt" "os" "os/signal" log "github.com/sirupsen/logrus" "github.com/xconnio/wamp-webrtc-go" + "github.com/xconnio/wampproto-go/auth" "github.com/xconnio/wampproto-go/serializers" "github.com/xconnio/xconn-go" ) @@ -16,8 +18,68 @@ const ( procedureWebRTCOffer = "io.xconn.webrtc.offer" topicOffererOnCandidate = "io.xconn.webrtc.offerer.on_candidate" topicAnswererOnCandidate = "io.xconn.webrtc.answerer.on_candidate" + + testRealm = "realm1" + testSecret = "hello" + testTicket = "hello" + testPublicKey = "f0e3cff77bd851015a99d873e302803d83e693cde41ffe545b26124713bdb08b" ) +type Authenticator struct{} + +func NewAuthenticator() *Authenticator { + return &Authenticator{} +} + +func (a *Authenticator) Methods() []auth.Method { + return []auth.Method{auth.MethodAnonymous, auth.MethodTicket, auth.MethodCRA, auth.MethodCryptoSign} +} + +func (a *Authenticator) Authenticate(request auth.Request) (auth.Response, error) { + switch request.AuthMethod() { + case auth.MethodAnonymous: + if request.Realm() == testRealm { + return auth.NewResponse(request.AuthID(), request.AuthRole(), 0) + } + + return nil, fmt.Errorf("invalid realm") + + case auth.MethodTicket: + ticketRequest, ok := request.(*auth.TicketRequest) + if !ok { + return nil, fmt.Errorf("invalid request") + } + + if ticketRequest.Realm() == testRealm && ticketRequest.Ticket() == testTicket { + return auth.NewResponse(ticketRequest.AuthID(), ticketRequest.AuthRole(), 0) + } + + return nil, fmt.Errorf("invalid ticket") + + case auth.MethodCRA: + if request.Realm() == testRealm { + return auth.NewCRAResponse(request.AuthID(), request.AuthRole(), testSecret, 0), nil + } + + return nil, fmt.Errorf("invalid realm") + + case auth.MethodCryptoSign: + cryptosignRequest, ok := request.(*auth.RequestCryptoSign) + if !ok { + return nil, fmt.Errorf("invalid request") + } + + if cryptosignRequest.Realm() == testRealm && cryptosignRequest.PublicKey() == testPublicKey { + return auth.NewResponse(cryptosignRequest.AuthID(), cryptosignRequest.AuthRole(), 0) + } + + return nil, fmt.Errorf("unknown publickey") + + default: + return nil, fmt.Errorf("unknown authentication method: %v", request.AuthMethod()) + } +} + func main() { session, err := xconn.Connect(context.Background(), "ws://localhost:8080/ws", "realm1") if err != nil { @@ -31,6 +93,7 @@ func main() { TopicHandleRemoteCandidates: topicOffererOnCandidate, TopicPublishLocalCandidate: topicAnswererOnCandidate, Serializer: &serializers.CBORSerializer{}, + Authenticator: NewAuthenticator(), } webRtcManager.Setup(cfg) diff --git a/provider.go b/provider.go index 7e916d5..bee0813 100644 --- a/provider.go +++ b/provider.go @@ -3,6 +3,7 @@ package wamp_webrtc_go import ( "context" "encoding/json" + "fmt" "sync" "time" @@ -11,7 +12,6 @@ import ( log "github.com/sirupsen/logrus" "github.com/xconnio/wampproto-go" - "github.com/xconnio/wampproto-go/serializers" "github.com/xconnio/wampproto-go/util" "github.com/xconnio/xconn-go" ) @@ -63,8 +63,6 @@ func (r *WebRTCProvider) handleOffer(requestID string, offer Offer, answerConfig } func (r *WebRTCProvider) Setup(config *ProviderConfig) { - router := xconn.NewRouter() - router.AddRealm("realm1") _, err := config.Session.Register(config.ProcedureHandleOffer, r.offerFunc, nil) if err != nil { log.Errorf("failed to register webrtc offer: %v", err) @@ -94,7 +92,10 @@ func (r *WebRTCProvider) Setup(config *ProviderConfig) { go func() { select { case channel := <-answerer.WaitReady(): - r.handleWAMPClient(channel, router, config.Serializer) + if err = r.handleWAMPClient(channel, config); err != nil { + log.Errorf("failed to handle answer: %v", err) + _ = answerer.connection.Close() + } case <-time.After(20 * time.Second): log.Errorln("webrtc connection didn't establish after 20 seconds") } @@ -102,25 +103,27 @@ func (r *WebRTCProvider) Setup(config *ProviderConfig) { }) } -func (r *WebRTCProvider) handleWAMPClient(channel *webrtc.DataChannel, xconnRouter *xconn.Router, - serializer serializers.Serializer) { +func (r *WebRTCProvider) handleWAMPClient(channel *webrtc.DataChannel, config *ProviderConfig) error { rtcPeer := NewWebRTCPeer(channel) - hello, err := xconn.ReadHello(rtcPeer, serializer) + hello, err := xconn.ReadHello(rtcPeer, config.Serializer) if err != nil { - log.Errorf("failed to read hello: %v", err) - return + return err } - base, err := xconn.Accept(rtcPeer, hello, serializer, nil) + base, err := xconn.Accept(rtcPeer, hello, config.Serializer, config.Authenticator) if err != nil { - log.Errorln(err) - return + return err + } + + if !config.Routed { + return nil } + xconnRouter := xconn.NewRouter() + xconnRouter.AddRealm("realm1") if err = xconnRouter.AttachClient(base); err != nil { - log.Errorf("failed to attach client %v", err) - return + return fmt.Errorf("failed to attach client %w", err) } parser := NewWebRTCMessageAssembler() @@ -148,22 +151,24 @@ func (r *WebRTCProvider) handleWAMPClient(channel *webrtc.DataChannel, xconnRout if err = xconnRouter.ReceiveMessage(base, msg); err != nil { log.Println(err) - return + return nil } - data, err := serializer.Serialize(msg) + data, err := config.Serializer.Serialize(msg) if err != nil { log.Printf("failed to serialize message: %v", err) - return + return nil } for chunk := range parser.ChunkMessage(data) { if err = channel.Send(chunk); err != nil { log.Errorf("failed to write message: %v", err) - return + return nil } } } + + return err } func (r *WebRTCProvider) offerFunc(_ context.Context, invocation *xconn.Invocation) *xconn.Result { diff --git a/types.go b/types.go index 227d176..6865b54 100644 --- a/types.go +++ b/types.go @@ -3,6 +3,7 @@ package wamp_webrtc_go import ( "github.com/pion/webrtc/v4" + "github.com/xconnio/wampproto-go/auth" "github.com/xconnio/wampproto-go/serializers" "github.com/xconnio/xconn-go" ) @@ -32,6 +33,8 @@ type ProviderConfig struct { TopicHandleRemoteCandidates string TopicPublishLocalCandidate string Serializer serializers.Serializer + Routed bool + Authenticator auth.ServerAuthenticator } type WebRTCSession struct {