Documentation ¶
Overview ¶
Package ssp implements the SQRL server-side protocol (SSP). The SqrlSspApi is a stateful server object that manages SQRL identities. The /cli.sqrl exposed at Cli is the only endpoint that is required to operate in conjunction with the SQRL client. This endpoint is required to be served over https.
While it's possible that this code can be run within a web server that terminates TLS itself, the expectation is that it is served from behind a load balancer or reverse proxy. While I attempt to reconstruct the host and paths from the request and standard forwarding headers, this can be unreliable and it's best to confgure the HostOverride and RootPath
Index ¶
- Constants
- Variables
- func ParseSqrlQuery(query string) (params map[string]string, err error)
- type Ask
- type AuthStore
- type Authenticator
- type CliRequest
- func (cr *CliRequest) Encode() string
- func (cr *CliRequest) Identity() *SqrlIdentity
- func (cr *CliRequest) IsAuthCommand() bool
- func (cr *CliRequest) SigningString() []byte
- func (cr *CliRequest) UpdateIdentity(identity *SqrlIdentity) bool
- func (cr *CliRequest) ValidateLastResponse(lastRepsonse []byte) bool
- func (cr *CliRequest) VerifyPidsSignature() error
- func (cr *CliRequest) VerifySignature() error
- func (cr *CliRequest) VerifyUrs(vuk string) error
- type CliResponse
- func (cr *CliResponse) ClearIDMatch() *CliResponse
- func (cr *CliResponse) ClearPreviousIDMatch() *CliResponse
- func (cr *CliResponse) Encode() []byte
- func (cr *CliResponse) WithBadIDAssociation() *CliResponse
- func (cr *CliResponse) WithClientFailure() *CliResponse
- func (cr *CliResponse) WithCommandFailed() *CliResponse
- func (cr *CliResponse) WithFunctionNotSupported() *CliResponse
- func (cr *CliResponse) WithIDMatch() *CliResponse
- func (cr *CliResponse) WithIPMatch() *CliResponse
- func (cr *CliResponse) WithIdentitySuperseded() *CliResponse
- func (cr *CliResponse) WithPreviousIDMatch() *CliResponse
- func (cr *CliResponse) WithSQRLDisabled() *CliResponse
- func (cr *CliResponse) WithTransientError() *CliResponse
- type ClientBody
- type GrcTree
- type Hoard
- type HoardCache
- type MapAuthStore
- type MapHoard
- type Nut
- type RandomTree
- type SqrlIdentity
- type SqrlSspAPI
- func (api *SqrlSspAPI) Cli(w http.ResponseWriter, r *http.Request)
- func (api *SqrlSspAPI) HTTPSRoot(r *http.Request) *url.URL
- func (api *SqrlSspAPI) Host(r *http.Request) string
- func (api *SqrlSspAPI) Nut(w http.ResponseWriter, r *http.Request)
- func (api *SqrlSspAPI) NutExpirationSeconds() int
- func (api *SqrlSspAPI) PNG(w http.ResponseWriter, r *http.Request)
- func (api *SqrlSspAPI) Pag(w http.ResponseWriter, r *http.Request)
- func (api *SqrlSspAPI) RemoteIP(r *http.Request) string
- type Tree
Constants ¶
const ( // the identity has been seen before TIFIDMatch = 0x1 // the previous identity is a known identity TIFPreviousIDMatch = 0x2 // the IP address of the current request and the original Nut request match TIFIPMatched = 0x4 // the SQRL account is disabled TIFSQRLDisabled = 0x8 // the ClientBody.Cmd is not recognized TIFFunctionNotSupported = 0x10 // used for all the random server errors like failures to connect to datastores TIFTransientError = 0x20 // the specific ClientBody.Cmd could not be completed for any reason TIFCommandFailed = 0x40 // the client sent bad or unrecognized data or signature validation failed TIFClientFailure = 0x80 // The owner of the Nut doesn't match this request TIFBadIDAssociation = 0x100 // The IDK has been rekeyed to a newer one TIFIdentitySuperseded = 0x200 )
TIF bitflags
const SqrlScheme = "sqrl"
SqrlScheme is sqrl
Variables ¶
var ErrNotFound = fmt.Errorf("Not Found")
ErrNotFound specific error returned if a Hoard or identity isn't found. This is to differentiate from more serious errors at the storage level
var Sqrl64 = base64.RawURLEncoding
Sqrl64 is a shortcut base64.RawURLEncoding encoding which is used pervasively throughout the SQRL protocol
var TIFDesc = map[uint32]string{ TIFIDMatch: "ID Matched", TIFPreviousIDMatch: "Previous ID Matched", TIFIPMatched: "IP Matched", TIFSQRLDisabled: "Identity disabled", TIFFunctionNotSupported: "Command not recognized", TIFTransientError: "Server Error", TIFCommandFailed: "Command failed", TIFClientFailure: "Bad client request", TIFBadIDAssociation: "Mismatch of nut to idk", TIFIdentitySuperseded: "Identity superseded by newer one", }
TIFDesc description of the TIF bits
Functions ¶
Types ¶
type Ask ¶
type Ask struct { Message string `json:"message"` Button1 string `json:"button1,omitempty"` URL1 string `json:"url1,omitempty"` Button2 string `json:"button2,omitempty"` URL2 string `json:"url2,omitempty"` }
Ask holds optional response to queries that will be shown to the user from the SQRL client
type AuthStore ¶
type AuthStore interface { FindIdentity(idk string) (*SqrlIdentity, error) SaveIdentity(identity *SqrlIdentity) error DeleteIdentity(idk string) error }
AuthStore stores SQRL identities
type Authenticator ¶
type Authenticator interface { // Called when a SQRL identity has been successfully authenticated. It // should return a URL that will finish authentication to create a // logged in session. This is also called for a new user. // If an error occurs this should return an error // page redirection AuthenticateIdentity(identity *SqrlIdentity) string // When an identity is rekeyed, it's necessary to swap the identity // associated with a given user. This callback happens when a user // wishes to swap their previous identity for a new one. SwapIdentities(previousIdentity, newIdentity *SqrlIdentity) error // This denotes an identity is now removed and this identity // should be disassociated with a user. This does not necessarily // mean the user should be deleted though. The SQRL spec mentions // being able to re-associate another identity at a later time (possibly // during the same login session) RemoveIdentity(identity *SqrlIdentity) error // Send an ask response back to the SQRL client. // Since this is triggered on query and not ident, // the identity may only contain Idk. Ask responses // will be included as part of the SqrlIdentity sent via // AuthenticateIdentity AskResponse(identity *SqrlIdentity) *Ask }
Authenticator interface to allow user management triggered by SQRL authentication events.
type CliRequest ¶
type CliRequest struct { Client *ClientBody `json:"client"` ClientEncoded string `json:"clientEncoded"` Server string `json:"server"` Ids string `json:"ids"` Pids string `json:"pids"` Urs string `json:"urs"` IPAddress string // saved here for reference }
CliRequest holds the data sent from the SQRL client to the /cli.sqrl endpoint
func ParseCliRequest ¶
func ParseCliRequest(r *http.Request) (*CliRequest, error)
ParseCliRequest parses and validates the request. The CliRequest can be trusted if no error is returned as the signatures have been checked.
func (*CliRequest) Encode ¶
func (cr *CliRequest) Encode() string
Encode creates the form encoded POST body from CliRequest
func (*CliRequest) Identity ¶
func (cr *CliRequest) Identity() *SqrlIdentity
Identity creates an identity from a request
func (*CliRequest) IsAuthCommand ¶
func (cr *CliRequest) IsAuthCommand() bool
IsAuthCommand is a command that authenticates (ident, enable)
func (*CliRequest) SigningString ¶
func (cr *CliRequest) SigningString() []byte
SigningString creates the string that is signed by ids, pids and urs
func (*CliRequest) UpdateIdentity ¶
func (cr *CliRequest) UpdateIdentity(identity *SqrlIdentity) bool
UpdateIdentity updates identity from request
func (*CliRequest) ValidateLastResponse ¶
func (cr *CliRequest) ValidateLastResponse(lastRepsonse []byte) bool
ValidateLastResponse checks to make sure the response on this request matches a stored on that's passed in.
func (*CliRequest) VerifyPidsSignature ¶
func (cr *CliRequest) VerifyPidsSignature() error
VerifyPidsSignature verifies the pids signature against the pidk in the ClientBody
func (*CliRequest) VerifySignature ¶
func (cr *CliRequest) VerifySignature() error
VerifySignature verifies the ids signature against the idk in the ClientBody. It also calls VerifyPidsSignature if necessary.
func (*CliRequest) VerifyUrs ¶
func (cr *CliRequest) VerifyUrs(vuk string) error
VerifyUrs validates a urs signature against a passed in vuk. This call will fail if the urs doesn't exist because it is required for several operations. Don't call this if you don't need it.
type CliResponse ¶
type CliResponse struct { Version []int Nut Nut TIF uint32 Qry string URL string Sin string Suk string Ask *Ask Can string // HoardCache is not serialized but the encoded response is saved here // so we can check it in the next request HoardCache *HoardCache }
CliResponse encodes a response to the SQRL client As specified https://www.grc.com/sqrl/semantics.htm
func NewCliResponse ¶
func NewCliResponse(nut Nut, qry string) *CliResponse
NewCliResponse creates a minimal valid CliResponse object
func ParseCliResponse ¶
func ParseCliResponse(body []byte) (*CliResponse, error)
ParseCliResponse parses a server response
func (*CliResponse) ClearIDMatch ¶
func (cr *CliResponse) ClearIDMatch() *CliResponse
ClearIDMatch set the appropriate TIF bits on this response. Returns the object for easier chaining (not immutability).
func (*CliResponse) ClearPreviousIDMatch ¶
func (cr *CliResponse) ClearPreviousIDMatch() *CliResponse
ClearPreviousIDMatch clears the appropriate TIF bits on this response. Returns the object for easier chaining (not immutability).
func (*CliResponse) Encode ¶
func (cr *CliResponse) Encode() []byte
Encode writes the response as the CRNL format and encodes it using Sqrl64 encoding.
func (*CliResponse) WithBadIDAssociation ¶
func (cr *CliResponse) WithBadIDAssociation() *CliResponse
WithBadIDAssociation set the appropriate TIF bits on this response. Returns the object for easier chaining (not immutability).
func (*CliResponse) WithClientFailure ¶
func (cr *CliResponse) WithClientFailure() *CliResponse
WithClientFailure set the appropriate TIF bits on this response. Returns the object for easier chaining (not immutability).
func (*CliResponse) WithCommandFailed ¶
func (cr *CliResponse) WithCommandFailed() *CliResponse
WithCommandFailed set the appropriate TIF bits on this response. Returns the object for easier chaining (not immutability).
func (*CliResponse) WithFunctionNotSupported ¶
func (cr *CliResponse) WithFunctionNotSupported() *CliResponse
WithFunctionNotSupported set the appropriate TIF bits on this response. Returns the object for easier chaining (not immutability).
func (*CliResponse) WithIDMatch ¶
func (cr *CliResponse) WithIDMatch() *CliResponse
WithIDMatch set the appropriate TIF bits on this response. Returns the object for easier chaining (not immutability).
func (*CliResponse) WithIPMatch ¶
func (cr *CliResponse) WithIPMatch() *CliResponse
WithIPMatch set the appropriate TIF bits on this response. Returns the object for easier chaining (not immutability).
func (*CliResponse) WithIdentitySuperseded ¶
func (cr *CliResponse) WithIdentitySuperseded() *CliResponse
func (*CliResponse) WithPreviousIDMatch ¶
func (cr *CliResponse) WithPreviousIDMatch() *CliResponse
WithPreviousIDMatch set the appropriate TIF bits on this response. Returns the object for easier chaining (not immutability).
func (*CliResponse) WithSQRLDisabled ¶
func (cr *CliResponse) WithSQRLDisabled() *CliResponse
WithSQRLDisabled set the appropriate TIF bits on this response. Returns the object for easier chaining (not immutability).
func (*CliResponse) WithTransientError ¶
func (cr *CliResponse) WithTransientError() *CliResponse
WithTransientError set the appropriate TIF bits on this response. Returns the object for easier chaining (not immutability).
type ClientBody ¶
type ClientBody struct { Version []int `json:"version"` Cmd string `json:"cmd"` Opt map[string]bool `json:"opt"` Suk string `json:"suk"` // Sqrl64.Encoded Vuk string `json:"vuk"` // Sqrl64.Encoded Pidk string `json:"pidk"` // Sqrl64.Encoded Idk string `json:"idk"` // Sqrl64.Encoded // valid values are 0,1,2; -1 means no value Btn int `json:"btn"` }
ClientBody holds the internal structure of the request "client" parameter; see https://www.grc.com/sqrl/protocol.htm in the section "The content of the “client” parameter." This is owned by a ClientRequest and probably shouldn't be used on it's own.
func ClientBodyFromParams ¶
func ClientBodyFromParams(params map[string]string) (*ClientBody, error)
ClientBodyFromParams creates ClientBody from the output of ParseSqrlQuery
func (*ClientBody) Encode ¶
func (cb *ClientBody) Encode() []byte
Encode returns the ClientBody encoded in Sqrl64
func (*ClientBody) PidkPublicKey ¶
func (cb *ClientBody) PidkPublicKey() (ed25519.PublicKey, error)
PidkPublicKey decodes and validates the Pidk as a ed25519.PublicKey
type GrcTree ¶
type GrcTree struct {
// contains filtered or unexported fields
}
GrcTree Creates a 64-bit nut based on the GRC spec using a monotonic counter and blowfish cipher
func NewGrcTree ¶
NewGrcTree takes an initial counter value (in the case of reboot) and a blowfish key (use a max key of random 56 bytes) https://godoc.org/golang.org/x/crypto/blowfish
type Hoard ¶
type Hoard interface { Get(nut Nut) (*HoardCache, error) GetAndDelete(nut Nut) (*HoardCache, error) Save(nut Nut, value *HoardCache, expiration time.Duration) error }
Hoard stores Nuts for later use
type HoardCache ¶
type HoardCache struct { State string `json:"state"` RemoteIP string `json:"remoteIP"` OriginalNut Nut `json:"originalNut"` PagNut Nut `json:"pagNut"` LastRequest *CliRequest `json:"lastRequest"` Identity *SqrlIdentity `json:"identity"` LastResponse []byte `json:"lastResponse"` }
HoardCache is the state associated with a Nut
type MapAuthStore ¶
type MapAuthStore struct {
// contains filtered or unexported fields
}
MapAuthStore stores identities in a map in the process. Great for testing but you should probably use some database to store these in a production environment.
func (*MapAuthStore) DeleteIdentity ¶
func (m *MapAuthStore) DeleteIdentity(idk string) error
DeleteIdentity implements AuthStore
func (*MapAuthStore) FindIdentity ¶
func (m *MapAuthStore) FindIdentity(idk string) (*SqrlIdentity, error)
FindIdentity implements AuthStore
func (*MapAuthStore) SaveIdentity ¶
func (m *MapAuthStore) SaveIdentity(identity *SqrlIdentity) error
SaveIdentity implements AuthStore
type MapHoard ¶
type MapHoard struct {
// contains filtered or unexported fields
}
MapHoard implements a Hoard that is backed by an in-memory map
func (*MapHoard) GetAndDelete ¶
func (mh *MapHoard) GetAndDelete(nut Nut) (*HoardCache, error)
GetAndDelete implements Hoard
type RandomTree ¶
type RandomTree struct {
// contains filtered or unexported fields
}
RandomTree produces random nuts
func NewRandomTree ¶
func NewRandomTree(byteSize int) (*RandomTree, error)
NewRandomTree takes a bytesize between 8 and 20 Shorter nuts are preferred; but if you think your deployment would require more bits to be unique you can create larger ones
type SqrlIdentity ¶
type SqrlIdentity struct { Idk string `json:"idk" sql:"primary_key"` Suk string `json:"suk"` Vuk string `json:"vuk"` Pidk string `json:"pidk"` // TODO do we need to keep track of Pidk? SQRLOnly bool `json:"sqrlOnly"` Hardlock bool `json:"hardlock"` Disabled bool `json:"disabled"` Rekeyed string `json:"rekeyed"` // If this Idk has been rekeyed, this links to the new ID // Btn is filled in if the request includes a button press response from an // ask. -1 if there's no value. Btn int `json:"-" sql:"-"` }
SqrlIdentity holds all the info about a valid SQRL identity
type SqrlSspAPI ¶
type SqrlSspAPI struct { NutExpiration time.Duration // set to the hostname for serving SQRL urls; this can include a port if necessary HostOverride string // if the SQRL endpoints are not at the root of the host, then this overrides the path where they are hosted RootPath string Authenticator Authenticator // contains filtered or unexported fields }
SqrlSspAPI implements the endpoitns outlined here https://www.grc.com/sqrl/sspapi.htm
func NewSqrlSspAPI ¶
func NewSqrlSspAPI(tree Tree, hoard Hoard, authenticator Authenticator, authStore AuthStore) *SqrlSspAPI
NewSqrlSspAPI takes a Tree implementation that produces Nuts. If set to nil, a the API defaults to NewRandomTree(8). Also needs a Hoard to store a retrieve Nuts
func (*SqrlSspAPI) Cli ¶
func (api *SqrlSspAPI) Cli(w http.ResponseWriter, r *http.Request)
Cli implements the /cli.sqrl endpoint
func (*SqrlSspAPI) HTTPSRoot ¶
func (api *SqrlSspAPI) HTTPSRoot(r *http.Request) *url.URL
HTTPSRoot returns the best guess at the https root URL for this server
func (*SqrlSspAPI) Host ¶
func (api *SqrlSspAPI) Host(r *http.Request) string
Host gets the host in order of preference: SqrlSspAPI.HostOverride, header X-Forwarded-Host, Request.Host
func (*SqrlSspAPI) Nut ¶
func (api *SqrlSspAPI) Nut(w http.ResponseWriter, r *http.Request)
Nut implements the /nut.sqrl endpoint TODO sin, ask and 1-9 params
func (*SqrlSspAPI) NutExpirationSeconds ¶
func (api *SqrlSspAPI) NutExpirationSeconds() int
NutExpirationSeconds has a self-explanatory name
func (*SqrlSspAPI) PNG ¶
func (api *SqrlSspAPI) PNG(w http.ResponseWriter, r *http.Request)
PNG implements the /png.sqrl endpoint
func (*SqrlSspAPI) Pag ¶
func (api *SqrlSspAPI) Pag(w http.ResponseWriter, r *http.Request)
Pag implements the /pag.sqrl endpoint