u2fhost

package module
v0.0.0-...-3ccdec8 Latest Latest
Warning

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

Go to latest
Published: Jan 11, 2021 License: MIT Imports: 6 Imported by: 24

README

u2fhost

GoDoc CircleCI codecov

A library for using U2F USB devices from Go programs.

Who is this for

This library allows clients to interface with U2F USB devices to perform user authentication.

Because U2F is supported in most major browsers (either natively or by extensions), the only place I really foresee this being used (and why I wrote it in the first place) is to add U2F support to CLI apps.

Usage

Registration

To register with a new device, you will need to construct a RegistrationRequest.

request := &RegisterRequest{
	// The challenge is provided by the server
	Challenge: "randomstringprovidedbyserver",
	// "The facet should be provided by the client making the request
	Facet:	 "https://example.com",
	// "The AppId may be provided by the server or the client client making the request.
	AppId:	 "https://example.com",
}

Next, get a list of devices that you can attempt to register with.

allDevices := Devices()
// Filter only the devices that can be opened.
openDevices := []Device{}
for i, device := range devices {
	err := device.Open()
	if err == nil {
		openDevices = append(openDevices, devices[i])
		defer func(i int) {
			devices[i].Close()
		}(i)
	}
}

Once you have a slice of open devices, repeatedly call the Register function until the user activates a device, or you time out waiting for the user.

// Prompt the user to perform the registration request.
fmt.Println("\nTouch the U2F device you wish to register...")
var response RegisterResponse
var err error
timeout := time.After(time.Second * 25)
interval := time.NewTicker(time.Millisecond * 250)
defer interval.Stop()
for {
    select {
    case <-timeout:
		fmt.Println("Failed to get registration response after 25 seconds")
		break
    case <-interval:
		for _, device := range openDevices {
			response, err := device.Register(req)
			if err != nil {
				if _, ok := err.(TestOfUserPresenceRequiredError); ok {
					continue
				} else {
					// you should handle errors more gracefully than this
					panic(err)
				}
			} else {
				return response
			}
		}
    }
}

Once you have a registration response, send the results back to your server in the form it expects.

Authentication

To authenticate with a device, you will need to construct a AuthenticateRequest.

request := &AuthenticateRequest{
	// The challenge is provided by the server
	Challenge: "randomstringprovidedbytheserver",
	 // "The facet should be provided by the client making the request
	Facet:	 "https://example.com",
	// "The AppId may be provided by the server or the client client making the request.
	AppId:	 "https://example.com",
	// The KeyHandle is provided by the server
	KeyHandle: "keyhandleprovidedbytheserver",
}

Next, get a list of devices that you can attempt to authenticate with.

allDevices := Devices()
// Filter only the devices that can be opened.
openDevices := []Device{}
for i, device := range devices {
	err := device.Open()
	if err == nil {
		openDevices = append(openDevices, devices[i])
		defer func(i int) {
				devices[i].Close()
		}(i)
	}
}

Once you have a slice of open devices, repeatedly call the Authenticate function until the user activates a device, or you time out waiting for the user.

prompted := false
timeout := time.After(time.Second * 25)
interval := time.NewTicker(time.Millisecond * 250)
defer interval.Stop()
for {
    select {
	case <-timeout:
		fmt.Println("Failed to get authentication response after 25 seconds")
		break
	case <-interval.C:
		for _, device := range openDevices {
			response, err := device.Authenticate(req)
			if err == nil {
				return response
				log.Debugf("Got error from device, skipping: %s", err)
			} else if _, ok := err.(TestOfUserPresenceRequiredError); ok && !prompted {
				fmt.Println("\nTouch the flashing U2F device to authenticate...\n")
				prompted = true
			} else {
				fmt.Printf("Got status response %#x\n", err)
			}
		}
    }
}

Example

The cmd directory contains a sample CLI program that allows you to run the register and authenticate operations, providing all of the inputs that would normally be provided by the server via command line flags.

Known issues/FAQ

What platforms has this been tested on?

At the moment only Mac OS, however nothing in the go codebase is platform specific, and the hid library supports Mac, Windows, and Linux, so in theory it should work on all platforms.

Linux

If you are using a linux device you need to add these udev rules.

To enable support for discovering USB HID devices on Linux, go build needs to be passed -tags=hidraw. This will build the bearsh/hid package with the hidraw backend for hidapi, instead of the default libusb used in previous versions and used by default when no tags are passed. Changes in the upstream libusb HIDAPI library have made it possible to return Usage Page and Usage on Linux and so address issue #1, but this only works with hidraw. Linux packages, therefore, should be built with -tags=hidraw.

The interface seems too low level, why isn't it easier to use?

Mostly because I wasn't sure what a good high level API would look like, and opted to provide a more general purpose low level API.

That said, in the future I may add a high level API similar to the Javascript U2F API.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type AuthenticateRequest

type AuthenticateRequest struct {
	// A string to sign. If used for authentication it should be a random string,
	// but could also be used to sign other kinds of data (ex: commit sha).
	Challenge string

	// The AppId can be provided by the server, but if not it should
	// be provided by the client.
	// For more information on AppId and Facets see https://fidoalliance.org/specs/fido-u2f-v1.0-ps-20141009/fido-appid-and-facets-ps-20141009.html#the-appid-and-facetid-assertions
	AppId string

	// The Facet should be provided by the client.
	// For more information on AppId and Facets see https://fidoalliance.org/specs/fido-u2f-v1.0-ps-20141009/fido-appid-and-facets-ps-20141009.html#the-appid-and-facetid-assertions
	Facet string

	// The base64 encoded key handle that was returned in the RegistrationData field of the RegisterResponse.
	KeyHandle string

	// Optional channel id public key, mutually exclusive with setting ChannelIdUnused to true.
	ChannelIdPublicKey *JSONWebKey

	// Only set to true if the client supports channel id, but the server does not.
	// Setting to true is mutually exclusive with providing a ChannelIdPublicKey.
	ChannelIdUnused bool

	// Optional boolean (defaults to false) that when true, will not attempt to
	// sign the challenge, and will only return the status.
	// This can be used to determine if a U2F device matches any of the provided key handles
	// before attempting to prompt the user to activate their devices.
	CheckOnly bool

	// Optional boolean (defaults to false) to use WebAuthn authentication with U2f
	// devices
	WebAuthn bool
}

An AuthenticateRequest is used when attempting to sign the challenge with a previously registered U2F device.

type AuthenticateResponse

type AuthenticateResponse struct {
	KeyHandle         string `json:"keyHandle"`
	ClientData        string `json:"clientData"`
	SignatureData     string `json:"signatureData"`
	AuthenticatorData string `json:"authenticatorData,omitempty"`
}

A response from an Authenticate operation. The response fields are typically passed back to the server.

type BadKeyHandleError

type BadKeyHandleError struct{}

A BadKeyHandleError indicates the key handle was not created by the device, or was created with a different application parameter.

func (BadKeyHandleError) Error

func (e BadKeyHandleError) Error() string

type Device

type Device interface {
	Open() error
	Close()
	Version() (string, error)
	Register(*RegisterRequest) (*RegisterResponse, error)
	Authenticate(*AuthenticateRequest) (*AuthenticateResponse, error)
}

A Device is the interface for performing registration and authentication operations.

type HidDevice

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

func Devices

func Devices() []*HidDevice

Returns a list of supported U2F devices as HidDevice pointers. If no supported devices are found, the returned list is empty.

func (*HidDevice) Authenticate

func (dev *HidDevice) Authenticate(req *AuthenticateRequest) (*AuthenticateResponse, error)

Authenticates with the device using the AuthenticateRequest, returning an AuthenticateResponse.

func (*HidDevice) Close

func (dev *HidDevice) Close()

Closes the device.

func (*HidDevice) Open

func (dev *HidDevice) Open() error

Opens the device. Must be called before calling Register or Authenticate.

func (*HidDevice) Register

func (dev *HidDevice) Register(req *RegisterRequest) (*RegisterResponse, error)

Registers with the device using the RegisterRequest, returning a RegisterResponse.

func (*HidDevice) Version

func (dev *HidDevice) Version() (string, error)

Returns the U2F version the device supports.

type JSONWebKey

type JSONWebKey struct {
	Kty string `json:"kty"`
	Crv string `json:"crv"`
	X   string `json:"x"`
	Y   string `json:"y"`
}

type RegisterRequest

type RegisterRequest struct {
	// A random string which the new device will sign, this should be
	// provided by the server.
	Challenge string

	// The AppId can be provided by the server, but if not it should
	// be provided by the client.
	// For more information on AppId and Facets see https://fidoalliance.org/specs/fido-u2f-v1.0-ps-20141009/fido-appid-and-facets-ps-20141009.html#the-appid-and-facetid-assertions
	AppId string

	// The Facet should be provided by the client.
	// For more information on AppId and Facets see https://fidoalliance.org/specs/fido-u2f-v1.0-ps-20141009/fido-appid-and-facets-ps-20141009.html#the-appid-and-facetid-assertions
	Facet string

	// Optional channel id public key, mutually exclusive with setting ChannelIdUnused to true.
	ChannelIdPublicKey *JSONWebKey

	// Only set to true if the client supports channel id, but the server does not.
	// Setting to true is mutually exclusive with providing a ChannelIdPublicKey.
	ChannelIdUnused bool
}

A RegisterRequest struct is used when attempting to register a new U2F device.

type RegisterResponse

type RegisterResponse struct {
	// Base64 encoded registration data.
	RegistrationData string `json:"registrationData"`
	// Base64 encoded client data.
	ClientData string `json:"clientData"`
}

A response from a Register operation. The response fields are typically passed back to the server.

type TestOfUserPresenceRequiredError

type TestOfUserPresenceRequiredError struct{}

A TestOfUserPresenceRequiredError indicates that the device is requesting the user interact with it (such as pressing a button) to fulfill the given request.

func (TestOfUserPresenceRequiredError) Error

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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