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
)
2022-04-08 04:13:47 +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
user * model . WebauthnUser
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
if w , err = newWebauthn ( ctx ) ; err != nil {
ctx . Logger . Errorf ( "Unable to configure %s during assertion challenge for user '%s': %+v" , regulation . AuthTypeWebauthn , userSession . Username , err )
respondUnauthorized ( ctx , messageMFAValidationFailed )
return
}
if user , err = getWebAuthnUser ( ctx , userSession ) ; err != nil {
ctx . Logger . Errorf ( "Unable to create %s assertion challenge for user '%s': %+v" , regulation . AuthTypeWebauthn , userSession . Username , err )
respondUnauthorized ( ctx , messageMFAValidationFailed )
return
}
var opts = [ ] webauthn . LoginOption {
webauthn . WithAllowedCredentials ( user . WebAuthnCredentialDescriptors ( ) ) ,
}
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
}
if len ( extensions ) != 0 {
opts = append ( opts , webauthn . WithAssertionExtensions ( extensions ) )
}
var assertion * protocol . CredentialAssertion
if assertion , userSession . Webauthn , err = w . BeginLogin ( user , opts ... ) ; err != nil {
ctx . Logger . Errorf ( "Unable to create %s assertion challenge for user '%s': %+v" , regulation . AuthTypeWebauthn , userSession . Username , err )
respondUnauthorized ( ctx , messageMFAValidationFailed )
return
}
if err = ctx . SaveSession ( userSession ) ; err != nil {
ctx . Logger . Errorf ( logFmtErrSessionSave , "assertion challenge" , regulation . AuthTypeWebauthn , userSession . Username , err )
respondUnauthorized ( ctx , messageMFAValidationFailed )
return
}
if err = ctx . SetJSONBody ( assertion ) ; err != nil {
ctx . Logger . Errorf ( logFmtErrWriteResponseBody , regulation . AuthTypeWebauthn , userSession . Username , err )
respondUnauthorized ( ctx , messageMFAValidationFailed )
return
}
}
2022-04-08 04:13:47 +00:00
// WebauthnAssertionPOST handler completes the assertion ceremony after verifying the challenge.
2023-01-25 09:36:40 +00:00
//
//nolint:gocyclo
2022-04-08 04:13:47 +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
2022-10-20 02:16:36 +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 {
2022-03-03 11:20:43 +00:00
ctx . Logger . Errorf ( logFmtErrParseRequestBody , regulation . AuthTypeWebauthn , err )
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
if userSession . Webauthn == nil {
ctx . Logger . Errorf ( "Webauthn session data is not present in order to handle assertion 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 )
respondUnauthorized ( ctx , messageMFAValidationFailed )
return
}
if w , err = newWebauthn ( ctx ) ; err != nil {
ctx . Logger . Errorf ( "Unable to configure %s during assertion challenge for user '%s': %+v" , regulation . AuthTypeWebauthn , userSession . Username , err )
respondUnauthorized ( ctx , messageMFAValidationFailed )
return
}
var (
assertionResponse * protocol . ParsedCredentialAssertionData
credential * webauthn . Credential
2022-03-06 05:47:40 +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 {
2022-03-03 11:20:43 +00:00
ctx . Logger . Errorf ( "Unable to parse %s assertionfor user '%s': %+v" , regulation . AuthTypeWebauthn , userSession . Username , err )
respondUnauthorized ( ctx , messageMFAValidationFailed )
return
}
if user , err = getWebAuthnUser ( ctx , userSession ) ; err != nil {
ctx . Logger . Errorf ( "Unable to load %s devices for assertion challenge for user '%s': %+v" , regulation . AuthTypeWebauthn , userSession . Username , err )
respondUnauthorized ( ctx , messageMFAValidationFailed )
return
}
if credential , err = w . ValidateLogin ( user , * userSession . Webauthn , assertionResponse ) ; err != nil {
_ = markAuthenticationAttempt ( ctx , false , nil , userSession . Username , regulation . AuthTypeWebauthn , err )
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
if err = ctx . Providers . StorageProvider . UpdateWebauthnDeviceSignIn ( ctx , device . ID , device . RPID , device . LastUsedAt , device . SignCount , device . CloneWarning ) ; err != nil {
ctx . Logger . Errorf ( "Unable to save %s device signin count for assertion challenge for user '%s': %+v" , regulation . AuthTypeWebauthn , userSession . Username , err )
respondUnauthorized ( ctx , messageMFAValidationFailed )
return
}
break
}
}
if ! found {
ctx . Logger . Errorf ( "Unable to save %s device signin count for assertion challenge for user '%s' device '%x' count '%d': unable to find device" , regulation . AuthTypeWebauthn , userSession . Username , credential . ID , credential . Authenticator . SignCount )
respondUnauthorized ( ctx , messageMFAValidationFailed )
return
}
2023-01-12 10:57:44 +00:00
if err = ctx . RegenerateSession ( ) ; err != nil {
2022-03-03 11:20:43 +00:00
ctx . Logger . Errorf ( logFmtErrSessionRegenerate , regulation . AuthTypeWebauthn , userSession . Username , err )
respondUnauthorized ( ctx , messageMFAValidationFailed )
return
}
if err = markAuthenticationAttempt ( ctx , true , nil , userSession . Username , regulation . AuthTypeWebauthn , nil ) ; err != nil {
respondUnauthorized ( ctx , messageMFAValidationFailed )
return
}
2022-04-01 11:18:58 +00:00
userSession . SetTwoFactorWebauthn ( ctx . Clock . Now ( ) ,
assertionResponse . Response . AuthenticatorData . Flags . UserPresent ( ) ,
assertionResponse . Response . AuthenticatorData . Flags . UserVerified ( ) )
2022-03-03 11:20:43 +00:00
if err = ctx . SaveSession ( userSession ) ; err != nil {
ctx . Logger . Errorf ( logFmtErrSessionSave , "removal of the assertion challenge and authentication time" , regulation . AuthTypeWebauthn , userSession . Username , err )
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
}
}