139 lines
4.3 KiB
Go
139 lines
4.3 KiB
Go
package server
|
|
|
|
import (
|
|
"crypto/md5" //nolint:gosec,gci
|
|
"fmt"
|
|
"io"
|
|
"math/rand"
|
|
"net"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/pion/stun"
|
|
"github.com/pion/turn/v2/internal/proto"
|
|
)
|
|
|
|
const (
|
|
maximumAllocationLifetime = time.Hour // https://tools.ietf.org/html/rfc5766#section-6.2 defines 3600 seconds recommendation
|
|
nonceLifetime = time.Hour // https://tools.ietf.org/html/rfc5766#section-4
|
|
|
|
)
|
|
|
|
func randSeq(n int) string {
|
|
letters := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
|
b := make([]rune, n)
|
|
for i := range b {
|
|
b[i] = letters[rand.Intn(len(letters))] //nolint:gosec
|
|
}
|
|
return string(b)
|
|
}
|
|
|
|
func buildNonce() (string, error) {
|
|
/* #nosec */
|
|
h := md5.New()
|
|
if _, err := io.WriteString(h, strconv.FormatInt(time.Now().Unix(), 10)); err != nil {
|
|
return "", fmt.Errorf("%w: %v", errFailedToGenerateNonce, err)
|
|
}
|
|
if _, err := io.WriteString(h, strconv.FormatInt(rand.Int63(), 10)); err != nil { //nolint:gosec
|
|
return "", fmt.Errorf("%w: %v", errFailedToGenerateNonce, err)
|
|
}
|
|
return fmt.Sprintf("%x", h.Sum(nil)), nil
|
|
}
|
|
|
|
func buildAndSend(conn net.PacketConn, dst net.Addr, attrs ...stun.Setter) error {
|
|
msg, err := stun.Build(attrs...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = conn.WriteTo(msg.Raw, dst)
|
|
return err
|
|
}
|
|
|
|
// Send a STUN packet and return the original error to the caller
|
|
func buildAndSendErr(conn net.PacketConn, dst net.Addr, err error, attrs ...stun.Setter) error {
|
|
if sendErr := buildAndSend(conn, dst, attrs...); sendErr != nil {
|
|
err = fmt.Errorf("%w %v %v", errFailedToSendError, sendErr, err)
|
|
}
|
|
return err
|
|
}
|
|
|
|
func buildMsg(transactionID [stun.TransactionIDSize]byte, msgType stun.MessageType, additional ...stun.Setter) []stun.Setter {
|
|
return append([]stun.Setter{&stun.Message{TransactionID: transactionID}, msgType}, additional...)
|
|
}
|
|
|
|
func authenticateRequest(r Request, m *stun.Message, callingMethod stun.Method) (stun.MessageIntegrity, bool, error) {
|
|
respondWithNonce := func(responseCode stun.ErrorCode) (stun.MessageIntegrity, bool, error) {
|
|
nonce, err := buildNonce()
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
|
|
// Nonce has already been taken
|
|
if _, keyCollision := r.Nonces.LoadOrStore(nonce, time.Now()); keyCollision {
|
|
return nil, false, errDuplicatedNonce
|
|
}
|
|
|
|
return nil, false, buildAndSend(r.Conn, r.SrcAddr, buildMsg(m.TransactionID,
|
|
stun.NewType(callingMethod, stun.ClassErrorResponse),
|
|
&stun.ErrorCodeAttribute{Code: responseCode},
|
|
stun.NewNonce(nonce),
|
|
stun.NewRealm(r.Realm),
|
|
)...)
|
|
}
|
|
|
|
if !m.Contains(stun.AttrMessageIntegrity) {
|
|
return respondWithNonce(stun.CodeUnauthorized)
|
|
}
|
|
|
|
nonceAttr := &stun.Nonce{}
|
|
usernameAttr := &stun.Username{}
|
|
realmAttr := &stun.Realm{}
|
|
badRequestMsg := buildMsg(m.TransactionID, stun.NewType(callingMethod, stun.ClassErrorResponse), &stun.ErrorCodeAttribute{Code: stun.CodeBadRequest})
|
|
|
|
if err := nonceAttr.GetFrom(m); err != nil {
|
|
return nil, false, buildAndSendErr(r.Conn, r.SrcAddr, err, badRequestMsg...)
|
|
}
|
|
|
|
// Assert Nonce exists and is not expired
|
|
nonceCreationTime, nonceFound := r.Nonces.Load(string(*nonceAttr))
|
|
if !nonceFound {
|
|
r.Nonces.Delete(nonceAttr)
|
|
return respondWithNonce(stun.CodeStaleNonce)
|
|
}
|
|
|
|
if timeValue, ok := nonceCreationTime.(time.Time); !ok || time.Since(timeValue) >= nonceLifetime {
|
|
r.Nonces.Delete(nonceAttr)
|
|
return respondWithNonce(stun.CodeStaleNonce)
|
|
}
|
|
|
|
if err := realmAttr.GetFrom(m); err != nil {
|
|
return nil, false, buildAndSendErr(r.Conn, r.SrcAddr, err, badRequestMsg...)
|
|
} else if err := usernameAttr.GetFrom(m); err != nil {
|
|
return nil, false, buildAndSendErr(r.Conn, r.SrcAddr, err, badRequestMsg...)
|
|
}
|
|
|
|
ourKey, ok := r.AuthHandler(usernameAttr.String(), realmAttr.String(), r.SrcAddr)
|
|
if !ok {
|
|
return nil, false, buildAndSendErr(r.Conn, r.SrcAddr, fmt.Errorf("%w %s", errNoSuchUser, usernameAttr.String()), badRequestMsg...)
|
|
}
|
|
|
|
if err := stun.MessageIntegrity(ourKey).Check(m); err != nil {
|
|
return nil, false, buildAndSendErr(r.Conn, r.SrcAddr, err, badRequestMsg...)
|
|
}
|
|
|
|
return stun.MessageIntegrity(ourKey), true, nil
|
|
}
|
|
|
|
func allocationLifeTime(m *stun.Message) time.Duration {
|
|
lifetimeDuration := proto.DefaultLifetime
|
|
|
|
var lifetime proto.Lifetime
|
|
if err := lifetime.GetFrom(m); err == nil {
|
|
if lifetime.Duration < maximumAllocationLifetime {
|
|
lifetimeDuration = lifetime.Duration
|
|
}
|
|
}
|
|
|
|
return lifetimeDuration
|
|
}
|