2022-03-03 11:20:43 +00:00
package handlers
import (
"bytes"
2022-03-03 23:46:38 +00:00
"github.com/go-webauthn/webauthn/protocol"
"github.com/go-webauthn/webauthn/webauthn"
2022-03-03 11:20:43 +00:00
"github.com/authelia/authelia/v4/internal/middlewares"
2022-03-06 05:47:40 +00:00
"github.com/authelia/authelia/v4/internal/model"
2022-03-03 11:20:43 +00:00
"github.com/authelia/authelia/v4/internal/regulation"
2023-01-25 09:36:40 +00:00
"github.com/authelia/authelia/v4/internal/session"
2022-03-03 11:20:43 +00:00
)
2023-04-11 04:40:09 +00:00
// WebAuthnAssertionGET handler starts the assertion ceremony.
func WebAuthnAssertionGET ( ctx * middlewares . AutheliaCtx ) {
2022-03-03 11:20:43 +00:00
var (
2023-01-25 09:36:40 +00:00
w * webauthn . WebAuthn
2023-04-10 07:01:23 +00:00
user * model . WebAuthnUser
2023-01-25 09:36:40 +00:00
userSession session . UserSession
err error
2022-03-03 11:20:43 +00:00
)
2023-01-25 09:36:40 +00:00
if userSession , err = ctx . GetSession ( ) ; err != nil {
ctx . Logger . WithError ( err ) . Error ( "Error occurred retrieving user session" )
respondUnauthorized ( ctx , messageMFAValidationFailed )
return
}
2022-03-03 11:20:43 +00:00
2023-04-11 04:40:09 +00:00
if w , err = newWebAuthn ( ctx ) ; err != nil {
ctx . Logger . Errorf ( "Unable to configure %s during authentication challenge for user '%s': %+v" , regulation . AuthTypeWebAuthn , userSession . Username , err )
2022-03-03 11:20:43 +00:00
respondUnauthorized ( ctx , messageMFAValidationFailed )
return
}
2023-04-11 04:40:09 +00:00
if user , err = getWebAuthnUserByRPID ( ctx , userSession . Username , userSession . DisplayName , w . Config . RPID ) ; err != nil {
ctx . Logger . Errorf ( "Unable to load %s user details during authentication challenge for user '%s': %+v" , regulation . AuthTypeWebAuthn , userSession . Username , err )
2022-03-03 11:20:43 +00:00
respondUnauthorized ( ctx , messageMFAValidationFailed )
return
}
2022-10-05 05:05:23 +00:00
extensions := map [ string ] any { }
2022-03-03 11:20:43 +00:00
if user . HasFIDOU2F ( ) {
2023-01-07 03:21:27 +00:00
extensions [ "appid" ] = w . Config . RPOrigins [ 0 ]
2022-03-03 11:20:43 +00:00
}
2023-02-18 04:36:58 +00:00
var opts = [ ] webauthn . LoginOption {
webauthn . WithAllowedCredentials ( user . WebAuthnCredentialDescriptors ( ) ) ,
}
2022-03-03 11:20:43 +00:00
if len ( extensions ) != 0 {
opts = append ( opts , webauthn . WithAssertionExtensions ( extensions ) )
}
2023-02-19 00:48:35 +00:00
var (
assertion * protocol . CredentialAssertion
2023-04-11 04:40:09 +00:00
data session . WebAuthn
2023-02-19 00:48:35 +00:00
)
2023-02-14 02:53:57 +00:00
if assertion , data . SessionData , err = w . BeginLogin ( user , opts ... ) ; err != nil {
2023-04-11 04:40:09 +00:00
ctx . Logger . Errorf ( "Unable to create %s authentication challenge for user '%s': %+v" , regulation . AuthTypeWebAuthn , userSession . Username , err )
2022-03-03 11:20:43 +00:00
respondUnauthorized ( ctx , messageMFAValidationFailed )
return
}
2023-04-11 04:40:09 +00:00
userSession . WebAuthn = & data
2023-02-14 02:53:57 +00:00
2022-03-03 11:20:43 +00:00
if err = ctx . SaveSession ( userSession ) ; err != nil {
2023-04-11 04:40:09 +00:00
ctx . Logger . Errorf ( logFmtErrSessionSave , "assertion challenge" , regulation . AuthTypeWebAuthn , userSession . Username , err )
2022-03-03 11:20:43 +00:00
respondUnauthorized ( ctx , messageMFAValidationFailed )
return
}
if err = ctx . SetJSONBody ( assertion ) ; err != nil {
2023-04-11 04:40:09 +00:00
ctx . Logger . Errorf ( logFmtErrWriteResponseBody , regulation . AuthTypeWebAuthn , userSession . Username , err )
2022-03-03 11:20:43 +00:00
respondUnauthorized ( ctx , messageMFAValidationFailed )
return
}
}
2023-04-11 04:40:09 +00:00
// WebAuthnAssertionPOST handler completes the assertion ceremony after verifying the challenge.
2023-01-25 09:36:40 +00:00
//
//nolint:gocyclo
2023-04-11 04:40:09 +00:00
func WebAuthnAssertionPOST ( ctx * middlewares . AutheliaCtx ) {
2022-03-03 11:20:43 +00:00
var (
2023-01-25 09:36:40 +00:00
userSession session . UserSession
2022-03-03 11:20:43 +00:00
err error
w * webauthn . WebAuthn
2023-04-11 04:40:09 +00:00
bodyJSON bodySignWebAuthnRequest
2022-03-03 11:20:43 +00:00
)
2022-07-26 05:43:39 +00:00
if err = ctx . ParseBody ( & bodyJSON ) ; err != nil {
2023-04-11 04:40:09 +00:00
ctx . Logger . Errorf ( logFmtErrParseRequestBody , regulation . AuthTypeWebAuthn , err )
2022-03-03 11:20:43 +00:00
respondUnauthorized ( ctx , messageMFAValidationFailed )
return
}
2023-01-25 09:36:40 +00:00
if userSession , err = ctx . GetSession ( ) ; err != nil {
ctx . Logger . WithError ( err ) . Error ( "Error occurred retrieving user session" )
respondUnauthorized ( ctx , messageMFAValidationFailed )
return
}
2022-03-03 11:20:43 +00:00
2023-04-11 04:40:09 +00:00
if userSession . WebAuthn == nil || userSession . WebAuthn . SessionData == nil {
ctx . Logger . Errorf ( "WebAuthn session data is not present in order to handle authentication challenge for user '%s'. This could indicate a user trying to POST to the wrong endpoint, or the session data is not present for the browser they used." , userSession . Username )
2022-03-03 11:20:43 +00:00
respondUnauthorized ( ctx , messageMFAValidationFailed )
return
}
2023-04-11 04:40:09 +00:00
if w , err = newWebAuthn ( ctx ) ; err != nil {
ctx . Logger . Errorf ( "Unable to configure %s during authentication challenge for user '%s': %+v" , regulation . AuthTypeWebAuthn , userSession . Username , err )
2022-03-03 11:20:43 +00:00
respondUnauthorized ( ctx , messageMFAValidationFailed )
return
}
var (
assertionResponse * protocol . ParsedCredentialAssertionData
credential * webauthn . Credential
2023-04-10 07:01:23 +00:00
user * model . WebAuthnUser
2022-03-03 11:20:43 +00:00
)
2023-01-30 02:47:54 +00:00
if assertionResponse , err = protocol . ParseCredentialRequestResponseBody ( bytes . NewReader ( bodyJSON . Response ) ) ; err != nil {
2023-04-11 04:40:09 +00:00
ctx . Logger . Errorf ( "Unable to parse %s authentication challenge for user '%s': %+v" , regulation . AuthTypeWebAuthn , userSession . Username , err )
2022-03-03 11:20:43 +00:00
respondUnauthorized ( ctx , messageMFAValidationFailed )
return
}
2023-04-11 04:40:09 +00:00
if user , err = getWebAuthnUserByRPID ( ctx , userSession . Username , userSession . DisplayName , w . Config . RPID ) ; err != nil {
ctx . Logger . Errorf ( "Unable to load %s credentials for authentication challenge for user '%s': %+v" , regulation . AuthTypeWebAuthn , userSession . Username , err )
2022-03-03 11:20:43 +00:00
respondUnauthorized ( ctx , messageMFAValidationFailed )
return
}
2023-04-11 04:40:09 +00:00
if credential , err = w . ValidateLogin ( user , * userSession . WebAuthn . SessionData , assertionResponse ) ; err != nil {
_ = markAuthenticationAttempt ( ctx , false , nil , userSession . Username , regulation . AuthTypeWebAuthn , err )
2022-03-03 11:20:43 +00:00
respondUnauthorized ( ctx , messageMFAValidationFailed )
return
}
var found bool
for _ , device := range user . Devices {
if bytes . Equal ( device . KID . Bytes ( ) , credential . ID ) {
device . UpdateSignInInfo ( w . Config , ctx . Clock . Now ( ) , credential . Authenticator . SignCount )
found = true
2023-04-11 04:40:09 +00:00
if err = ctx . Providers . StorageProvider . UpdateWebAuthnDeviceSignIn ( ctx , device ) ; err != nil {
ctx . Logger . Errorf ( "Unable to save %s device signin count for authentication challenge for user '%s': %+v" , regulation . AuthTypeWebAuthn , userSession . Username , err )
2022-03-03 11:20:43 +00:00
respondUnauthorized ( ctx , messageMFAValidationFailed )
return
}
break
}
}
if ! found {
2023-04-11 04:40:09 +00:00
ctx . Logger . Errorf ( "Unable to save %s device signin count for authentication challenge for user '%s' device '%x' count '%d': unable to find device" , regulation . AuthTypeWebAuthn , userSession . Username , credential . ID , credential . Authenticator . SignCount )
2022-03-03 11:20:43 +00:00
respondUnauthorized ( ctx , messageMFAValidationFailed )
return
}
2023-01-12 10:57:44 +00:00
if err = ctx . RegenerateSession ( ) ; err != nil {
2023-04-11 04:40:09 +00:00
ctx . Logger . Errorf ( logFmtErrSessionRegenerate , regulation . AuthTypeWebAuthn , userSession . Username , err )
2022-03-03 11:20:43 +00:00
respondUnauthorized ( ctx , messageMFAValidationFailed )
return
}
2023-04-11 04:40:09 +00:00
if err = markAuthenticationAttempt ( ctx , true , nil , userSession . Username , regulation . AuthTypeWebAuthn , nil ) ; err != nil {
2022-03-03 11:20:43 +00:00
respondUnauthorized ( ctx , messageMFAValidationFailed )
return
}
2023-04-11 04:40:09 +00:00
userSession . SetTwoFactorWebAuthn ( ctx . Clock . Now ( ) ,
2023-02-19 00:48:35 +00:00
assertionResponse . Response . AuthenticatorData . Flags . HasUserPresent ( ) ,
assertionResponse . Response . AuthenticatorData . Flags . HasUserVerified ( ) )
2022-03-03 11:20:43 +00:00
if err = ctx . SaveSession ( userSession ) ; err != nil {
2023-04-11 04:40:09 +00:00
ctx . Logger . Errorf ( logFmtErrSessionSave , "removal of the authentiation challenge and authentication time" , regulation . AuthTypeWebAuthn , userSession . Username , err )
2022-03-03 11:20:43 +00:00
respondUnauthorized ( ctx , messageMFAValidationFailed )
return
}
2022-07-26 05:43:39 +00:00
if bodyJSON . Workflow == workflowOpenIDConnect {
2022-10-20 02:16:36 +00:00
handleOIDCWorkflowResponse ( ctx , bodyJSON . TargetURL , bodyJSON . WorkflowID )
2022-03-03 11:20:43 +00:00
} else {
2022-07-26 05:43:39 +00:00
Handle2FAResponse ( ctx , bodyJSON . TargetURL )
2022-03-03 11:20:43 +00:00
}
}