auth

package
v4.4.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 13, 2022 License: BSD-3-Clause Imports: 6 Imported by: 0

Documentation

Overview

Package auth calculates the challenge/response for a login attempt, and checks user input to determine if it was a valid response to the challenge.

BACKGROUND AND SECURITY NOTES

This authentication system is designed for validating connections between clients and servers in our game system and nothing else. This is an extremely casual, low-risk situation where the main intent is to deflect nuisance connections. It would not be suitable for use in most other situations where there is more sensitive information to be protected.

In this case, there are two passwords configured into the system. One is shared among all players, and is configured into their clients so they may automatically connect to the server when playing the game. The other is for the GM's use, and serves only to identify his or her clients to the system as being GM clients for the purposes of routing GM-only information to them.

If desired, individual passwords may be given to some users which they can use to authenticate in order to avoid confusion between players in the game.

While this scheme protects passwords from observation and replay in transit (but not man-in-the-middle or other more sophisticated attacks unless further protections are placed on the connection itself), the passwords themselves are handled on both client and server in plaintext form.

CLIENT-SIDE OPERATION

A client wishing to authenticate should create an Authenticator as

a := NewClientAuthenticator(myUserName, mySecretValue, myProgramName)

where mySecretValue is the password they are using to authenticate (which will be the shared secret (password) used by all clients to authenticate to the server, the GM's password, or the personal password assigned to the user with the name given by myUserName, if one exists); myProgramName is a string describing what sort of client this is.

The value in myUserName is arbitrary. If the password is the shared player password, then myUserName is taken as the name this player wants to be known by in the game system, and may be anything except "GM". If the password is the GM password, then "GM" will be used for the username regardless of the value passed as myUserName. If the user has a personal password defined for them on the server, then myUserName must match the name on file (since that's how the server will know which personal password to check).

If myUserName is empty, an attempt will be made to determine the local user name of the process, or will be "anonymous" if that cannot be accomplished. If the program name is empty, "unnamed" will be used.

The client then obtains a challenge from the server in the form of a base-64-encoded string, which is passed to the AcceptChallenge method:

response, err := a.AcceptChallenge(serverChallengeString)

This provides the response to send back to the server in order to log in.

SERVER-SIDE OPERATION

A server which accepts a client connection should create an Authenticator for each such client which will track that session's authentication state.

a := &Authenticator{Secret: commonPassword, GmSecret: gmPassword}

where commonPassword is the password used by all clients to authenticate, and gmPassword is the one used by privileged clients.

The server then generates a unique challenge for this client's session by calling

challenge, err := a.GenerateChallenge()

The generated challenge string is sent to the client, which will reply with a response string.

If the server knows it needs to validate against a specific non-GM user's personal password, the server needs to update the Authenticator with that password by calling

a.SetSecret(personalSecret)

before validating the client's response.

The server validates the authenticity of the response by calling

ok, err := a.ValidateResponse(clientResponse)

which will return true if the response is correct for that challenge.

AUTHENTICATION ALGORITHM

Given:

C:    the server's challenge as a byte slice
h(x): the binary SHA-256 hash digest of byte slice x
P:    the password needed to authenticate to the server
x‖y:  means to concatenate x and y

To calculate the response to the server's authentication challenge, the client must do the following:

(1) Obtain i from the first 2 bytes of C as a big-endian uint16 value.
(2) Calculate D=h(C‖P).
(3) Repeat i times: D'=h(P‖D); let D=D'

D is the response to send to the server for validation.

PROTOCOL

Although the auth package itself isn't involved in the client/server protocol directly, the way it is used by the map server and its clients uses the following protocol:

(server->client) "OK <v> <challenge>"

The server's greeting to the client includes this line which gives the server's protocol version (<v>) and a base-64 encoding of the binary challenge value (C in the algorithm described above)

(server<-client) "AUTH <response> [<user> <client>]"

The client's response is sent with this line, where <response> is the base-64 encoded representation of the response to the challenge (D above), and the optional <user> and <client> values are the desired user name and description of the client program.

(server->client) "DENIED [<message>]"

Server response indicating that the authentication was unsuccessful.

(server->client) "GRANTED <user>"

Server response indicating that the authentication was successful. The <user> value is "GM" if the GM password was used, or the user name supplied by the user, which will be the name they're known by inside the game map (usually a character name). If the user did not supply a name and did not use the GM password, "anonymous" is returned.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Authenticator

type Authenticator struct {
	// unprivileged (player) authentication password (client or server)
	Secret []byte

	// privileged (GM) authentication password (server only)
	GmSecret []byte

	// current generated challenge or nil
	Challenge []byte

	// text description of client program/version
	Client string

	// name of user authenticating
	Username string

	// true if GM authentication succeeded (server only)
	GmMode bool
}

An Authenticator object holds the authentication challenge/response context for a single client transaction.

func NewClientAuthenticator

func NewClientAuthenticator(username string, secret []byte, client string) *Authenticator

NewClientAuthenticator creates and returns a pointer to a new Authenticator instance, prepared for use by a typical client. (CLIENT)

If the username string is empty, an attempt will be made to determine the local username on the system, or the string "anonymous" will be used. If the client string is empty, "unnamed" will be used.

func (*Authenticator) AcceptChallenge

func (a *Authenticator) AcceptChallenge(challenge string) (string, error)

AcceptChallenge takes a server's challenge, stores it internally, and generates an appropriate response to it, which is returned as a base-64 encoded string. (CLIENT)

func (*Authenticator) CurrentChallenge

func (a *Authenticator) CurrentChallenge() string

CurrentChallenge returns the last-generated challenge created by GenerateChallenge. (SERVER)

This is returned as a base-64-encoded string suitable for transmitting directly to a client.

func (*Authenticator) GenerateChallenge

func (a *Authenticator) GenerateChallenge() (string, error)

GenerateChallenge creates an authentication challenge for this session. (SERVER)

This is a 256-bit random nonce which is remembered for subsequent operations on this Authenticator instance. It does not depend on the password(s).

Since the first two bytes of this challenge value specify the number of rounds to apply the hashing function on the client, the 16-bit number they represent is constrained to the range [64, 4095] to avoid too few rounds or so many rounds that a client may be unnecessarily burdened. (The latter is an actual runtime performance issue for one of our clients which is implemented as a Tcl script.)

The challenge is returned as a base-64-encoded string.

func (*Authenticator) Reset

func (a *Authenticator) Reset()

Reset returns an Authenticator instance back to its pre-challenge state so that it may be used again for another authentication attempt. (CLIENT/SERVER)

Existing secrets and user names are preserved.

func (*Authenticator) SetSecret

func (a *Authenticator) SetSecret(secret []byte)

SetSecret changes the non-GM secret and disables GM logins for this authenticator. (SERVER)

This is typically used on the server's side when it determines that the user trying to authenticate has their own password configured, so it calls SetSecret with that personal password. Subsequent calls to ValidateResponse will be based on this personal password.

GM logins are disabled since the GM already has their own password, so this feature would not apply to them.

func (*Authenticator) ValidateResponse

func (a *Authenticator) ValidateResponse(response string) (bool, error)

ValidateResponse takes a base-64-encoded response string and verifies that the value it encodes matches the expected response for the previously-generated challenge. (SERVER)

If the response is not correct for the expected user's secret, we try again against the GM's secret to see if the user is logging in as the GM role. If so, the GmMode member of the Authenticator is set to true.

Returns true if the authentication was successful.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL