package domain import ( "crypto/hmac" "crypto/sha256" "fmt" "time" ) // SignedJoinAuthorisation is the transport envelope. The signing primitive is // supplied by the backend signer so this policy stays independent of key // storage and cryptographic algorithm choice. type SignedJoinAuthorisation struct { Authorisation JoinAuthorisation Signature []byte } // JoinAuthorisationBytes is the canonical claim encoding. KeyID is appended // last and is covered by the signature, so an attacker cannot redirect an // authorisation at a different key than the one that signed it. Game/scripts/ // match_net.gd builds the identical byte sequence; the two must change // together. func JoinAuthorisationBytes(auth JoinAuthorisation) []byte { return []byte(fmt.Sprintf("%s\x00%s\x00%s\x00%s\x00%d\x00%d\x00%s\x00%d\x00%s\x00%s", auth.MatchID, auth.ServerID, auth.PlayerID, auth.SteamID, auth.Slot, auth.Team, auth.Protocol, auth.Generation, auth.ExpiresAt.UTC().Format(time.RFC3339Nano), auth.KeyID)) } func SignJoinAuthorisation(auth JoinAuthorisation, sign func([]byte) ([]byte, error)) (SignedJoinAuthorisation, error) { if sign == nil { return SignedJoinAuthorisation{}, ErrJoinAuthorisation } signature, err := sign(JoinAuthorisationBytes(auth)) if err != nil || len(signature) == 0 { return SignedJoinAuthorisation{}, ErrJoinAuthorisation } return SignedJoinAuthorisation{Authorisation: auth, Signature: append([]byte(nil), signature...)}, nil } // SignJoinAuthorisationHMAC is the interoperable production profile used by // the Godot allocated server. The key is mounted out-of-band; the signed // bytes remain the same canonical claim bytes used by the generic signer. // The caller must have set auth.KeyID to the ID of this key, so the verifier // can pick the right one out of its key set. func SignJoinAuthorisationHMAC(auth JoinAuthorisation, key []byte) (SignedJoinAuthorisation, error) { if len(key) == 0 { return SignedJoinAuthorisation{}, ErrJoinAuthorisation } mac := hmac.New(sha256.New, key) _, _ = mac.Write(JoinAuthorisationBytes(auth)) return SignedJoinAuthorisation{Authorisation: auth, Signature: mac.Sum(nil)}, nil } func (r *RankedConnections) AdmitSigned(signed SignedJoinAuthorisation, verify func([]byte, []byte) bool, now time.Time) (uint64, error) { if len(signed.Signature) == 0 || verify == nil || !verify(JoinAuthorisationBytes(signed.Authorisation), signed.Signature) { return 0, ErrJoinAuthorisation } return r.Admit(signed.Authorisation, now) }