mfa

package module
v0.0.0-...-639c681 Latest Latest
Warning

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

Go to latest
Published: Feb 28, 2024 License: MIT Imports: 30 Imported by: 0

README

A Serverless MFA API with support for WebAuthn

This project provides a semi-generic backend API for supporting WebAuthn credential registration and authentication. It is intended to be run in a manner as to be shared between multiple consuming applications. It uses an API key and secret to authenticate requests, and further uses that secret as the encryption key. Loss of the API secret would mean loss of all WebAuthn credentials stored.

This application can be run in two ways:

  1. As a standalone server using the builtin webserver available in the server/ folder
  2. As a AWS Lambda function using the lambda/ implementation. This implementation can also use the Serverless Framework to help automate build/deployment. It should also be noted that the lambda format depends on some resources already existing in AWS. There is a lambda/terraform/ folder with the Terraform configurations needed to provision them.

The API

Yes, as you'll see below this API makes heavy use of custom headers for things that seem like they could go into the request body. We chose to use headers though so that what is sent in the body can be handed off directly to the WebAuthn library and fit the structures it was expecting without causing any conflicts, etc.

Required Headers
  1. x-mfa-apikey - The API Key
  2. x-mfa-apisecret - The API Key Secret
  3. x-mfa-RPDisplayName - The Relay Party Display Name, ex: ACME Inc.
  4. x-mfa-RPID - The Relay Party ID, ex: domain.com (should only be the top level domain, no subdomain, protocol, or path)
  5. x-mfa-RPOrigin - The browser Origin for the request, ex: https://sub.domain.com (include appropriate subdomain and protocol, no path or port)
  6. x-mfa-UserUUID - The UUID for the user attempting to register or authenticate with WebAuthn. This has nothing to do with WebAuthn, but is the primary key for finding the right records in DynamoDB
  7. x-mfa-Username - The user's username of your service
  8. x-mfa-UserDisplayName - The user's display name
Begin Registration

POST /webauthn/register

Finish Registration

PUT /webauthn/register

Begin Login

POST /webauthn/login

Finish Login

PUT /webauthn/login

Delete Webauthn "User"

DELETE /webauthn/user

Delete one of the user's Webauthn credentials

DELETE /webauthn/credential

Documentation

Index

Constants

View Source
const (
	UserContextKey  = "user"
	WebAuthnTablePK = "uuid"
	LegacyU2FCredID = "u2f"
)
View Source
const ApiKeyTablePK = "value"
View Source
const IDParam = "id"

Variables

This section is empty.

Functions

func BeginLogin

func BeginLogin(w http.ResponseWriter, r *http.Request)

func BeginRegistration

func BeginRegistration(w http.ResponseWriter, r *http.Request)

func DeleteCredential

func DeleteCredential(w http.ResponseWriter, r *http.Request)

func DeleteUser

func DeleteUser(w http.ResponseWriter, r *http.Request)

func FinishLogin

func FinishLogin(w http.ResponseWriter, r *http.Request)

func FinishRegistration

func FinishRegistration(w http.ResponseWriter, r *http.Request)

func GenerateAuthenticationSig

func GenerateAuthenticationSig(authData, clientData []byte, privateKey *ecdsa.PrivateKey) string

GenerateAuthenticationSig appends the clientData to the authData and uses the privateKey's public Key to sign it

via a sha256 hashing algorithm.

It returns the base64 encoded version of the marshaled version of the corresponding dsa signature {r:bigInt, s:bigInt} It does not use any kind of randomized data in this process

func GetPublicKeyAsBytes

func GetPublicKeyAsBytes(privateKey *ecdsa.PrivateKey) []byte

GetPublicKeyAsBytes starts with byte(4) and appends the private key's public key's X and then Y bytes

func SetConfig

func SetConfig(c EnvConfig)

Types

type ApiKey

type ApiKey struct {
	Key          string   `json:"value"`
	Secret       string   `json:"-"`
	HashedSecret string   `json:"hashedApiSecret"`
	Email        string   `json:"email"`
	CreatedAt    int      `json:"createdAt"`
	ActivatedAt  int      `json:"activatedAt"`
	Store        *Storage `json:"-"`
}

func (*ApiKey) Decrypt

func (k *ApiKey) Decrypt(ciphertext []byte) ([]byte, error)

func (*ApiKey) DecryptLegacy

func (k *ApiKey) DecryptLegacy(ciphertext []byte) ([]byte, error)

func (*ApiKey) Encrypt

func (k *ApiKey) Encrypt(plaintext []byte) ([]byte, error)

func (*ApiKey) Hash

func (k *ApiKey) Hash() error

Hash - Generate bcrypt hash from Secret and store in HashedSecret

func (*ApiKey) IsCorrect

func (k *ApiKey) IsCorrect(given string) (bool, error)

func (*ApiKey) Load

func (k *ApiKey) Load() error

type ApiMeta

type ApiMeta struct {
	RPDisplayName   string `json:"RPDisplayName"` // Display Name for your site
	RPID            string `json:"RPID"`          // Generally the FQDN for your site
	RPOrigin        string `json:"RPOrigin"`      // The origin URL for WebAuthn requests
	RPIcon          string `json:"RPIcon"`        // Optional icon URL for your site
	UserUUID        string `json:"UserUUID"`
	Username        string `json:"Username"`
	UserDisplayName string `json:"UserDisplayName"`
	UserIcon        string `json:"UserIcon"`
}

ApiMeta holds metadata about the calling service for use in WebAuthn responses. Since this service/api is consumed by multiple sources this information cannot be stored in the envConfig

type ClientData

type ClientData struct {
	Typ          string          `json:"type"`
	Challenge    string          `json:"challenge"`
	Origin       string          `json:"origin"`
	CIDPublicKey json.RawMessage `json:"cid_pubkey"`
}

ClientData as defined by the FIDO U2F Raw Message Formats specification.

type DynamoUser

type DynamoUser struct {
	// Shared fields between U2F and WebAuthn
	ID          string   `json:"uuid"`
	ApiKeyValue string   `json:"apiKey"`
	ApiKey      ApiKey   `json:"-"`
	Store       *Storage `json:"-"`

	// U2F fields
	AppId              string `json:"-"`
	EncryptedAppId     string `json:"encryptedAppId,omitempty"`
	KeyHandle          string `json:"-"`
	EncryptedKeyHandle string `json:"encryptedKeyHandle,omitempty"`
	PublicKey          string `json:"-"`
	EncryptedPublicKey string `json:"encryptedPublicKey,omitempty"`

	// WebAuthn fields
	SessionData          webauthn.SessionData `json:"-"`
	EncryptedSessionData []byte               `json:"EncryptedSessionData,omitempty"`

	// These can be multiple Yubikeys or other WebAuthn entries
	Credentials          []webauthn.Credential `json:"-"`
	EncryptedCredentials []byte                `json:"EncryptedCredentials,omitempty"`

	WebAuthnClient *webauthn.WebAuthn `json:"-"`
	Name           string             `json:"-"`
	DisplayName    string             `json:"-"`
	Icon           string             `json:"-"`
}

func AuthenticateRequest

func AuthenticateRequest(r *http.Request) (*DynamoUser, error)

func NewDynamoUser

func NewDynamoUser(apiConfig ApiMeta, storage *Storage, apiKey ApiKey, webAuthnClient *webauthn.WebAuthn) DynamoUser

func (*DynamoUser) BeginLogin

func (u *DynamoUser) BeginLogin() (*protocol.CredentialAssertion, error)

func (*DynamoUser) BeginRegistration

func (u *DynamoUser) BeginRegistration() (*protocol.CredentialCreation, error)

func (*DynamoUser) Delete

func (u *DynamoUser) Delete() error

func (*DynamoUser) DeleteCredential

func (u *DynamoUser) DeleteCredential(credIDHash string) (int, error)

DeleteCredential expects a hashed-encoded credential id. It finds a matching credential for that user and saves the user without that credential included. Alternatively, if the given credential id indicates that a legacy U2F key should be removed (e.g. by matching the string "u2f") then that user is saved with all of its legacy u2f fields blanked out.

func (*DynamoUser) FinishLogin

func (u *DynamoUser) FinishLogin(r *http.Request) (*webauthn.Credential, error)

func (*DynamoUser) FinishRegistration

func (u *DynamoUser) FinishRegistration(r *http.Request) (string, error)

func (*DynamoUser) Load

func (u *DynamoUser) Load() error

func (*DynamoUser) RemoveU2F

func (u *DynamoUser) RemoveU2F()

func (*DynamoUser) WebAuthnCredentials

func (u *DynamoUser) WebAuthnCredentials() []webauthn.Credential

WebAuthnCredentials returns an array of credentials plus a U2F cred if present

func (*DynamoUser) WebAuthnDisplayName

func (u *DynamoUser) WebAuthnDisplayName() string

Display Name of the user

func (*DynamoUser) WebAuthnID

func (u *DynamoUser) WebAuthnID() []byte

User ID according to the Relying Party

func (*DynamoUser) WebAuthnIcon

func (u *DynamoUser) WebAuthnIcon() string

User's icon url

func (*DynamoUser) WebAuthnName

func (u *DynamoUser) WebAuthnName() string

User Name according to the Relying Party

type EnvConfig

type EnvConfig struct {
	ApiKeyTable   string `required:"true" split_words:"true"`
	WebauthnTable string `required:"true" split_words:"true"`

	AwsEndpoint      string `default:"" split_words:"true"`
	AwsDefaultRegion string `default:"" split_words:"true"`
	AwsDisableSSL    bool   `default:"false" split_words:"true"`

	AWSConfig *aws.Config `json:"-"`
}

EnvConfig holds environment specific configurations and is populated on init

func (*EnvConfig) InitAWS

func (e *EnvConfig) InitAWS()

func (*EnvConfig) String

func (e *EnvConfig) String() string

type SignResponse

type SignResponse struct {
	KeyHandle     string `json:"keyHandle"`
	SignatureData string `json:"signatureData"`
	ClientData    string `json:"clientData"`
}

SignResponse as defined by the FIDO U2F Javascript API.

type Storage

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

Storage provides wrapper methods for interacting with DynamoDB

func NewStorage

func NewStorage(config *aws.Config) (*Storage, error)

func (*Storage) Delete

func (s *Storage) Delete(table, attrName, attrVal string) error

Delete deletes key.

func (*Storage) Load

func (s *Storage) Load(table, attrName, attrVal string, item interface{}) error

Load retrieves the value at key and unmarshals it into item.

func (*Storage) Store

func (s *Storage) Store(table string, item interface{}) error

Store puts item at key.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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