ykmangoath

package module
v0.4.0 Latest Latest
Warning

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

Go to latest
Published: Sep 7, 2022 License: Apache-2.0 Imports: 9 Imported by: 0

README



Got

ykmangoath
Ykman OATH TOTP with Go

Yet another ykman Go lib for requesting OATH TOTP Multi-Factor Authentication Codes from Yubikey Devices.



🚧 Work-in-Progress

There are already some packages out there which already wrap Yubikey CLI for Go to manage OATH TOTP, but they lack all or some of the following features:

  • Go Context support (handy for timeouts/cancellation etc)
  • Multiple Yubikeys (identified by Device Serial Number)
  • Password protected Yubikey OATH applications

Hence, this package, which covers those features! Big thanks to joshdk/ykmango and 99designs/aws-vault as they heavily influenced this library. Also this library is somewhat based on the previous implementation of Yubikey support in aripalo/vegas-credentials (which this partly replaces in near future).

This library supports only a small subset of features of Yubikeys & OATH account management, this is by design.


Installation

Requirements

  • Yubikey Series 5 device (or newer with a OATH TOTP support)
  • ykman Yubikey Manager CLI as a runtime dependency
  • Go 1.18 or newer (for development)

Get it

go get -u github.com/aripalo/ykmangoath

Getting Started

This ykmangoath library provides a struct OathAccounts which represents a the main functionality of Yubikey OATH accounts (via ykman CLI). You can “create an instance” of the struct with ykmangoath.New and provide the following:

  • Context (type of context.Context) which allows you to implement things like cancellations and timeouts
  • Device Serial Number which is the 8+ digit serial number of your Yubikey device which you can find:
    • printed in the back of your physical Yubikey device
    • by running command ykman info in your terminal
package main

import (
	"context"
	"fmt"
	"log"
	"time"

	"github.com/aripalo/ykmangoath"
)

func main() {
	myTimeout := 20*time.Second
	ctx, cancel := context.WithTimeout(context.Background(), myTimeout)
	defer cancel()

	deviceSerial := "12345678" // can be empty string if you only use one Yubikey device
	oathAccounts, err := ykmangoath.New(ctx, deviceSerial)
	// handler err

	// after this you can perform various operations on oathAccounts...
}

Once initialized, you may perform operations on oathAccounts such as List or Code methods. See Managing Password if you wish to support password protected Yubikey OATH applications (you really should).


Check if Device Available

To verify if the configured Yubikey device is connected & available:

IsAvailable := oathAccounts.IsAvailable()
fmt.Println(IsAvailable)

Example Go output:

true

List Accounts

List OATH accounts configured in the Yubikey device:

accounts, err := oathAccounts.List()
if err != nil {
  log.Fatal(err)
}

fmt.Println(accounts)

The above is the same as running the following in your terminal:

ykman --device 12345678 oath accounts list

Example Go output:

[
  "Amazon Web Services:john.doe@example",
]

Request Code

Requests a Time-based One-Time Password (TOTP) 6-digit code for given account (such as ":") from Yubikey OATH application.

account := "<issuer>:<name>"
code, err := oathAccounts.Code(account)
if err != nil {
  log.Fatal(err)
}

fmt.Println(code)

The above is the same as running the following in your terminal:

ykman --device 12345678 oath accounts code --single '<issuer>:<name>'

Example Go output:

"123456"

Managing Password

An end-user with Yubikey device may wish to password protect the Yubikey's OATH application. Generally speaking this is a good idea as it adds some protection from theft: A bad actor with someone else's Yubikey device can't actually use the device to generate TOTP MFA codes unless they somehow also know the device password.

The password protection for the Yubikey device's OATH application can be set either via the Yubico Authenticator GUI application or via command-line with ykman by running:

ykman oath access change

But, if the device is configured with a password protected OATH application, it means that there needs to be a way to provide the password for ykmangoath: Luckily this is one of the benefits of this specific library as it supports just that by either:

There's also functionality to retrieve the prompted password given by end-user, so you may implement caching mechanisms to provide a smoother user experience where the end-user doesn't have to type in the password for every Yubikey operation; Remember there are of course tradeoffs with security vs. user experience with caching the password!

Check if Password Protected

To check if the Yubikey OATH application is password protected you can use:

isProtected := oathAccounts.IsPasswordProtected()
fmt.Println(isProtected)

Example Go output:

true

Direct Assign

err := oathAccounts.SetPassword("p4ssword")
// handle err
account := "<issuer>:<name>"
code, err := oathAccounts.Code(account)
// handle err

The above is the same as running the following in your terminal:

ykman --device 12345678 oath accounts code --single '<issuer>:<name>' --password 'p4ssword'

Prompt Function

Instead of assigning the password directly ahead-of-time, you may provide a password prompt function that will be executed only if password is required. Often you'll use this to actually ask the password from end-user – either via terminal stdin or by showing a GUI dialog with tools such as ncruces/zenity.

It must return a password string (which can be empty) and an error (which of course could be nil on success). The password prompt function will also receive the context.Context given in ykmangoath.New initialization, therefore your password prompt function can be cancelled (for example due to timeout).

func myPasswordPrompt(ctx context.Context) (string, error) {
	password := "p4ssword" // in real life, you'd resolve this value by asking the end-user
	return password, nil
}

err := oathAccounts.SetPasswordPrompt(myPasswordPrompt)

Retrieve the prompted Password

func myPasswordPrompt(ctx context.Context) (string, error) {
	password := "p4ssword" // in real life, you'd resolve this value by asking the end-user
	return password, nil
}

err := oathAccounts.SetPasswordPrompt(myPasswordPrompt)
// handle err

code, err := oathAccounts.Code("<issuer>:<name>")
// handle err
// do something with code

password, err := oathAccounts.GetPassword()
// handle err
// do something with password (e.g. cache it somewhere):
myCacheSolution.Set(password) // ... just an example

This can be useful if you wish to cache the Yubikey OATH application password for short periods of time in your own application, so that the user doesn't have to type in the password everytime (remember: the physical touch of the Yubikey device should be the actual second factor). How you cache it (hopefully somewhat securely) is up to you.


Design

This tool is designed only for retrieval of specific information from a Yubikey device:

  • List of configured OATH accounts
  • Time-based One-Time Password (TOTP) code for given OATH account
  • ... with a support for password protected Yubikey OATH applications

By design, this tool does NOT support:

  • Setting or changing the Yubikey OATH application password: Configuring the password should be an explicit end-user operation (which they can do via Yubico Authenticator GUI or ykman CLI) – We do not want to enable situations where this library renders end-user's Yubikey OATH accounts useless by setting a password unknown to the end-user.
  • Removing or renaming OATH accounts from the Yubikey device: This is another area where – either by accident or on purpose – one could harm the end-user.

If you need some of the above-mentioned unsupported features, feel free to implement them yourself – for example by forking this library but we will never accept a pull request back into this project which implements those features.

This tool may implement following features in the future:

But this is not a promise to implement them. If you feel like it, you can create a Pull Request!

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ErrCommandInterrupted = errors.New("ykman command interrupted")

ErrCommandInterrupted indicates ykman process was killed by an external signal (such as SIGINT or SIGTERM).

View Source
var ErrCommandNotFound = errors.New("ykman command not found")

ErrCommandNotFound indicates ykman executable not found from $PATH. To resolve the error, user should install the ykman CLI tool and try again.

View Source
var ErrDeviceNotFound = errors.New("yubikey device not found")

ErrDeviceNotFound indicates Yubikey device is not connected to host machine.

View Source
var ErrDeviceRemoved = errors.New("yubikey device removed")

ErrDeviceRemoved means the Yubikey device was physically removed during the operation from the host machine.

View Source
var ErrDeviceSerial = errors.New("invalid device serial")

ErrDeviceSerial indicates incorrect Yubikey Device Serial Number. The serial number must be at least 8 digits long string.

View Source
var ErrDeviceTimeout = errors.New("yubikey device timeout")

ErrDeviceTimeout indicates Yubikey device timed out, usually because it was not touched during ykman's timeout period.

View Source
var ErrNoPassword = errors.New("password not set")

ErrNoPassword is returned when trying to retrieve a the password but it has not been resolved by password prompt (yet).

View Source
var ErrOathAccountCodeParseFailed = errors.New("oath account code could not be parsed")

ErrOathAccountCodeParseFailed indicates that valid OATH account code was not received.

View Source
var ErrOathAccountNotFound = errors.New("oath account not found")

ErrOathAccountNotFound indicates that the given OATH account name does not exists.

View Source
var ErrOathAccountPasswordIncorrect = errors.New("oath application password is incorrect")

ErrOathAccountPasswordIncorrect indicates a wrong password for OATH application.

View Source
var ErrOathAccountPasswordProtected = errors.New("oath application is password protected")

ErrOathAccountPasswordProtected indicates that the OATH application requires a password.

View Source
var ErrPasswordNotAllowedWithPrompt = errors.New("password string not allowed when using prompt")

ErrPasswordNotAllowedWithPrompt indicates that password should not be passed when using prompt.

View Source
var ErrPasswordSetup = errors.New("cannot set password")

ErrPasswordSetup means password string could not be configured and is usually returned when trying to set both password and password prompt which are mutually exclusive.

View Source
var ErrPromptSetup = errors.New("cannot set password prompt")

ErrPromptSetup means password prompt could not be configured and is usually returned when trying to set both password and password prompt which are mutually exclusive.

Functions

This section is empty.

Types

type OathAccounts

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

OathAccounts represents a the main functionality of Yubikey OATH accounts.

func New

func New(ctx context.Context, serial string) (OathAccounts, error)

New defines a new instance of OathAccounts.

func (*OathAccounts) Code

func (oa *OathAccounts) Code(account string) (string, error)

Code requests a Time-based one-time password (TOTP) 6-digit code for given account (such as "<issuer>:<name>") from Yubikey OATH application.

func (*OathAccounts) GetPassword

func (oa *OathAccounts) GetPassword() (string, error)

GetPassword returns the password that successfully unlocked the Yubikey OATH application.

func (*OathAccounts) GetSerial

func (oa *OathAccounts) GetSerial() string

GetSerial returns the currently configured Yubikey device serial.

func (*OathAccounts) HasAccount added in v0.3.3

func (oa *OathAccounts) HasAccount(account string) (bool, error)

HasAccount returns a boolean indicating if the device has the given account configured in its OATH application.

func (*OathAccounts) IsAvailable added in v0.3.0

func (oa *OathAccounts) IsAvailable() bool

IsAvailable checks whether the Yubikey device is connected & available

func (*OathAccounts) IsPasswordProtected added in v0.2.0

func (oa *OathAccounts) IsPasswordProtected() bool

IsPasswordProtected checks whether the OATH application is password protected

func (*OathAccounts) List

func (oa *OathAccounts) List() ([]string, error)

List returns a list of configured accounts in the Yubikey OATH application.

func (*OathAccounts) SetPassword

func (oa *OathAccounts) SetPassword(password string) error

SetPassword directly configures the Yubikey OATH application password. Mutually exclusive with SetPasswordPrompt.

func (*OathAccounts) SetPasswordPrompt

func (oa *OathAccounts) SetPasswordPrompt(prompt func(ctx context.Context) (string, error)) error

SetPasswordPrompt configures a function that will be called upon if/when the Yubikey OATH application password is required. Mutually exclusive with SetPassword.

type Ykman added in v0.3.3

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

func NewYkman added in v0.3.3

func NewYkman(ctx context.Context, serial string) *Ykman

func (*Ykman) Execute added in v0.3.3

func (y *Ykman) Execute(args []string) (string, error)

func (*Ykman) ExecuteWithPrompt added in v0.3.3

func (y *Ykman) ExecuteWithPrompt(args []string, prompt func(ctx context.Context) (string, error)) (string, error)

Jump to

Keyboard shortcuts

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