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:
- As a standalone server using the builtin webserver available in the
server/
folder - 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 thelambda
format depends on some resources already existing in AWS. There is alambda/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
x-mfa-apikey
- The API Keyx-mfa-apisecret
- The API Key Secretx-mfa-RPDisplayName
- The Relay Party Display Name, ex:ACME Inc.
x-mfa-RPID
- The Relay Party ID, ex:domain.com
(should only be the top level domain, no subdomain, protocol, or path)x-mfa-RPOrigin
- The browser Origin for the request, ex:https://sub.domain.com
(include appropriate subdomain and protocol, no path or port)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 DynamoDBx-mfa-Username
- The user's username of your servicex-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
- func BeginLogin(w http.ResponseWriter, r *http.Request)
- func BeginRegistration(w http.ResponseWriter, r *http.Request)
- func DeleteCredential(w http.ResponseWriter, r *http.Request)
- func DeleteUser(w http.ResponseWriter, r *http.Request)
- func FinishLogin(w http.ResponseWriter, r *http.Request)
- func FinishRegistration(w http.ResponseWriter, r *http.Request)
- func GenerateAuthenticationSig(authData, clientData []byte, privateKey *ecdsa.PrivateKey) string
- func GetPublicKeyAsBytes(privateKey *ecdsa.PrivateKey) []byte
- func SetConfig(c EnvConfig)
- type ApiKey
- type ApiMeta
- type ClientData
- type DynamoUser
- func (u *DynamoUser) BeginLogin() (*protocol.CredentialAssertion, error)
- func (u *DynamoUser) BeginRegistration() (*protocol.CredentialCreation, error)
- func (u *DynamoUser) Delete() error
- func (u *DynamoUser) DeleteCredential(credIDHash string) (int, error)
- func (u *DynamoUser) FinishLogin(r *http.Request) (*webauthn.Credential, error)
- func (u *DynamoUser) FinishRegistration(r *http.Request) (string, error)
- func (u *DynamoUser) Load() error
- func (u *DynamoUser) RemoveU2F()
- func (u *DynamoUser) WebAuthnCredentials() []webauthn.Credential
- func (u *DynamoUser) WebAuthnDisplayName() string
- func (u *DynamoUser) WebAuthnID() []byte
- func (u *DynamoUser) WebAuthnIcon() string
- func (u *DynamoUser) WebAuthnName() string
- type EnvConfig
- type SignResponse
- type Storage
Constants ¶
const ( UserContextKey = "user" WebAuthnTablePK = "uuid" LegacyU2FCredID = "u2f" )
const ApiKeyTablePK = "value"
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
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:"-"` }
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 (*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) 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
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.