auth

package
v0.0.0-...-d325f09 Latest Latest
Warning

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

Go to latest
Published: Feb 28, 2022 License: BSD-3-Clause, ISC Imports: 19 Imported by: 0

README

common-auth

This package implements authentication using a variety of authentication mechanisms in a manner that allows multiple schemes to be supported.

The Authenticator

The Authenticator type carries the authentication information that can be used to validate a submitted password or authentication token. It contains four fields: a string identifying the type of the authenticator, a label (for example, to specify an account name), a byte slice containing the authentication secret, and a field for storing the last entered password (which is used by the one-time password authenticators to store the last OTP to prevent replay attacks).

An Authenticator is initialised using a constructor appropriate to the type of authenticator to be used. Once initialised, it may be stored as needed; for example, perhaps in a SQL table or as a map serialised to JSON.

A loaded Authenticator may be used to validate an authentication token (such as a password) using the Validate function:

shouldUpdate, err := auth.Validate(user.Auth, password)
if err != nil {
	log.Println("failed authentication from user", user.Name)
	// Handle failure in an app-appropriate way.
}

The Validate function uses the mapping from Validators to determine which function to call for a given authentication type. This may be used to add additional authentication types in a system that aren't present in this package.

Validate returns a bool and error pair; the error indicates whether the authentication is valid (nil), whether the authenticator is invalid in some way (ErrInvalidAuthenticator), or whether the authentication token is invalid (ErrValidationFail). The bool indicates whether the Authenticator has changed (for example, a counter was incremented or a new last OTP was stored). If it's true, the authenticator should be updated in any persistent storage; in the functions provided in this package, it will only ever be true if the validation was succesful.

An Authenticator also has the method Zero: this will zeroise its Secret field, and it's subject to the same caveats noted in the util.Zero function documentation.

Password authentication

Password authentication stores passwords as bcrypt hashes; a new Authenticator for a password is created using NewPasswordAuth:

user.Auth, err = auth.NewPasswordAuth(password, 0)
if err != nil {
	log.Printf("failed to create password auth for %s: %v", user.Name, err)
	// Application-specific error handling.
}

YubiKey authentication

Currently, this supports single-purpose YubiKey OTPs: where a YubiKey is used only for this application. Support for a validation server is planned, as is support for YubiHOTP.

A YubiKey Authenticator is initialised with NewYubiKey, which takes the key (which should be hex-decoded into a byte slice), and the initial OTP for syncing.

func readYubiAuth() (*auth.Authenticator, error) {
	k, err := util.ReadLine("Hex-encoded key: ")
	if err != nil {
		log.Printf("failed to create authenticator: %v", err)
		return nil, err
	}

	kb, err := hex.DecodeString(k)
	if err != nil {
		log.Printf("failed to create authenticator: %v", err)
		return nil, err
	}

	otp, err := util.ReadLine("OTP: ")
	if err != nil {
		log.Printf("failed to create authenticator: %v", err)
		return nil, err
	}

	a, err := auth.NewYubiKey(kb, otp)
	if err != nil {
		log.Printf("failed to create authenticator: %v", err)
		return nil, err
	}

	return a, nil
}

For the YubiKey in the tests, this would look like:

Hex-encoded key: 971ab1c6b0400448c685e650f895195a
OTP: brknecvrdjcrbvldbdffbvjuigjhjhugfcvudrndjufl

A YubiKey authenticator will always need to be updated, as it contains a counter and last value.

Google Authenticator authentication

The package also supports the TOTP tokens used by the Google Authenticator app. There are two options for creating a new TOTP token: generating a new token, or importing one.

Generating a random TOTP returns an Authenticator, the UserTOTP details that should be given to the user, and an error value. If the label passed to the NewGoogleTOTP function is not an empty string, a PNG-encoded QR code will be included in the user details.

func createNewTOTP(username string) {
	a, ud, err := NewGoogleTOTP("demo service")
	if err != nil {
		log.Printf("failed to create new Google TOTP: %v", err)
		// App-specific error handling.
	}

	// storeAuthenticator would be some function setting up a mapping
	// between username and the newly-created authenticator in
	// persisent storage.
	storeAuthenticator(username, a)

	// sendPNG might display the QR code for the user as part of
	// the registration process.
	sendPNG(username, ud.QR)
}

A TOTP authenticator will always need to be updated after a successful validation.

Session authentication

This authenticator is intended for several specific projects and will likely be of limited utility to other projects.

The session authenticator provides one mechanism for supporting shared sessions based on public keys and HMAC-SHA-256. The user should submit a public key generated using the common/public package of this project. The session initialisation function will generate an ephemeral key pair to derive a shared HMAC-SHA-256 key. The ephemeral public key will be returned and should be sent to the user, who sets up a key exchange. The server will send the user a "next" value. The user computes the HMAC of this next value and sends the (next, mac) pair up to the server as the next authentication.

The Last contains the last generated "next" value. This should be sent to the user after session setup and after each validation.

Setting up the session:

a, sessionPublic, err := auth.NewSession(userPublic)
if err != nil {
	log.Printf("failed to set up new session: %v", err)
	// App-specific error handling.
}

When the user receives the public key, they set up a session:

session, ok := auth.KeySession(privateKey, sessionPublic)
if !ok {
	log.Println("failed to key session")
	// App-specific error handling.
}

When the user wants to generate a new session token, they call OTP. The next value will be hex-encoded in the Authenticator; the user will need to decode it before passing it to this function.

otp := session.OTP(next)

When the session is done, it can be zeroised:

session.Zero()

Example: login

The examples/login package contains a demo program that simulates a login prompt and a registration system. It stores users and their authenticators in a JSON store file.

To register new users, use the "register" command:

$ go run login.go register
Login name: kyle
Supported authentication types:
        password
        yubikey
        TOTP
Authentication type: password
Password: 
2015/01/09 16:38:16 registered kyle

With the TOTP registration, a QR code will be stored in qr.png.

The authentication can be tested using the "run" command; enter an empty user name to exit.

$ go run login.go run
User name: kyle
Password: password
2015/01/09 16:41:23 authentication successful
User name: kyle
Password: passwort
2015/01/09 16:41:29 authentication failed (auth: authentication failed)
User name: nouser
Password: password
2015/01/09 16:41:40 authentication failed (no such user)
User name: 

Documentation

Overview

Package auth contains one-time password authentication functionality for implementing authentication in a system design. An Authenticator contains authentication information, presumably for a user; the authentication token or password submitted by the user can be validated using the Validate function.

This package currently supports password authentication (using bcrypt hashes), YubiKey OTPs, Google Authenticator standard TOTPs, and a basic session implementation.

Index

Constants

View Source
const TypePassword = "password"

TypePassword is a bcrypted password hash.

View Source
const TypeSession = "session"

TypeSession is a new session.

View Source
const TypeTOTP = "TOTP"

TypeTOTP is a TOTP token.

View Source
const TypeYubiKey = "yubikey"

TypeYubiKey is a YubiOTP token.

Variables

View Source
var (
	// ErrInvalidAuthenticator indicates that the authenticator
	// passed to Validate is not a valid authenticator. Ensure
	// that it is a type supported by the server, and that it is
	// an actual value.
	ErrInvalidAuthenticator = errors.New("auth: invalid authenticator")

	// ErrInvalidOTP indicates that an OTP is invalid for the
	// Authenticator.
	ErrValidationFail = errors.New("auth: authentication failed")
)
View Source
var (
	// ErrUnsupportedHash is returned when the user tries to use a
	// hash algorithm that isn't supported by the authentication scheme.
	ErrUnsupportedHash = errors.New("auth: unsupported hash algorithm")
)
View Source
var TOTPProvider string

TOTPProvider contains the value that should be used to fill in the Provider field of the TOTPConfig.

View Source
var Validators = map[string]func(*Authenticator, string) (bool, error){
	TypeYubiKey:  ValidateYubiKey,
	TypeTOTP:     ValidateTOTP,
	TypeSession:  ValidateSession,
	TypePassword: ValidatePassword,
}

Validators contains a mapping of authenticator types to validation functions.

Functions

func NewGoogleTOTP

func NewGoogleTOTP(label string) (*Authenticator, *UserTOTP, error)

NewGoogleTOTP generates a new Google-authenticator standard TOTP token.

func Validate

func Validate(auth *Authenticator, password string) (needsUpdate bool, err error)

Validate takes an Authenticator and an OTP, and checks whether the OTP is valid. It returns a boolean value indicating whether the Authenticator needs to be validated (i.e., if it contains a counter, and therefore the counter value needs to be stored).

func ValidatePassword

func ValidatePassword(auth *Authenticator, password string) (bool, error)

ValidatePassword takes an Authenticator that is presumed to be a bcrypted password hash and a password, and ensures that it matches.

func ValidateSession

func ValidateSession(auth *Authenticator, otp string) (bool, error)

ValidateSession ensures that the OTP provided contains the next value and the appropriate HMAC for the session.

func ValidateTOTP

func ValidateTOTP(auth *Authenticator, otp string) (bool, error)

ValidateTOTP takes an Authenticator that is presumed to be a TOTP authenticator and attempts to validate the given OTP using it. The TOTP authenticator will always need to be updated when successful to account for an updated last OTP.

func ValidateYubiKey

func ValidateYubiKey(auth *Authenticator, otp string) (bool, error)

ValidateYubiKey takes an Authenticator that is presumed to be a YubiKey authenticator and attempts to validate the given OTP using it. The YubiKey authenticator will always need to be updated when successful to account for changes in the counter, and to update the last OTP.

Types

type Authenticator

type Authenticator struct {
	Type   string `json:"type"`
	Label  string `json:"label"`
	Last   string `json:"last"`
	Secret []byte `json:"secret"`
}

An Authenticator stores a one-time password key for a user. The type is used to select the appropriate verification algorithm.

func ImportGoogleTOTP

func ImportGoogleTOTP(key []byte) (*Authenticator, error)

ImportGoogleTOTP creates a new Google-authenticator standard TOTP from an existing key.

func NewPasswordAuth

func NewPasswordAuth(password string, cost int) (*Authenticator, error)

NewPasswordAuth creates a bcrypt hash authenticator for the password using the given cost, which must be between 8 and 31. If the cost is an invalid value, a default cost will be used.

func NewSession

func NewSession(pub []byte) (*Authenticator, []byte, error)

NewSession sets up a new session. The Last field should be sent to the client. The returned public key should be sent to the user for generating a shared MAC key. The authenticator should ensure some mechanism for expiring sessions exists.

func NewYubiKey

func NewYubiKey(key []byte, initialOTP string) (*Authenticator, error)

NewYubiKey takes the key and initial OTP and returns an authenticator.

func (*Authenticator) Zero

func (a *Authenticator) Zero()

Zero wipes the Authenticator's secret.

type Session

type Session struct {
	// contains filtered or unexported fields
}

A Session is meant to be used by an authenticatee for computing the checksums.

func KeySession

func KeySession(priv, pub []byte) (*Session, bool)

KeySession sets up a new session from the user's private key and the server's ephemeral public key.

func (*Session) OTP

func (s *Session) OTP(next []byte) string

OTP computes the next OTP for the session.

func (*Session) Zero

func (s *Session) Zero()

Zero wipes the key. The session is no longer valid, and should be discarded.

type TOTPConfig

type TOTPConfig struct {
	// Key is used as the HMAC key for generating OTPs. This should
	// be the same length as the hash algorithm's output size, but
	// not everyone does that.
	Key []byte

	// Start is the start time for the TOTP. Google defaults this to
	// the start of the epoch.
	Start uint64

	// Step is the time step between OTPs. Users will not be able
	// to authenticate inside this time period after the first
	// successful authentication to prevent replay attacks. Google
	// defaults to 30 seconds for this.
	Step uint64

	// Size is the number of digits to generate. Google defaults
	// to six.
	Size int

	// Algo contains the hash algorithm used in the HMAC. Google
	// defaults to SHA1.
	Algo crypto.Hash

	// Provider is an optional string that identifies the
	// authentication provider. This is used in generating QR codes.
	Provider string
}

TOTPConfig contains the details required to use a TOTP token.

func ParseTOTPConfig

func ParseTOTPConfig(in []byte) (*TOTPConfig, error)

ParseTOTPConfig parses a serialised TOTP configuration.

func (*TOTPConfig) Bytes

func (config *TOTPConfig) Bytes() ([]byte, error)

Bytes exports the TOTP configuration as a byte slice.

func (*TOTPConfig) ExportKey

func (config *TOTPConfig) ExportKey() string

ExportKey returns the base32-encoded key for the TOTP to hand off to the user.

func (*TOTPConfig) ExportQR

func (config *TOTPConfig) ExportQR(label string) ([]byte, error)

ExportQR returns a QR code as a PNG for the TOTP token.

type UserTOTP

type UserTOTP struct {
	Secret string // The TOTP secret, base32-encoded.
	QR     []byte // A QR code that may be used to import the token.
}

UserTOTP contains the data a user needs to import the TOTP token in their TOTP app of choice.

func ExportUserTOTP

func ExportUserTOTP(auth *Authenticator, label string) (*UserTOTP, error)

ExportUserTOTP returns a UserTOTP value suitable for handing off to a user. If a label is provided, a QR code will be returned.

type YubiKeyConfig

type YubiKeyConfig struct {
	Key     []byte
	Counter uint32
	Public  []byte
}

YubiKeyConfig contains the token and key information for a YubiKey.

func ParseYubiKeyConfig

func ParseYubiKeyConfig(in []byte) (*YubiKeyConfig, error)

ParseYubiKeyConfig attempts to parse a YubiKeyConfig from a byte slice.

func (*YubiKeyConfig) Bytes

func (config *YubiKeyConfig) Bytes() []byte

Bytes returns a byte slice representation of the YubiKeyConfig.

Directories

Path Synopsis
examples
login
The login program accepts two commands: "register", to set up new users, and "run"
The login program accepts two commands: "register", to set up new users, and "run"

Jump to

Keyboard shortcuts

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