2022-03-03 18:20:43 +07:00
package handlers
import (
"bytes"
2022-03-04 06:46:38 +07:00
"github.com/go-webauthn/webauthn/protocol"
"github.com/go-webauthn/webauthn/webauthn"
2022-03-03 18:20:43 +07:00
"github.com/authelia/authelia/v4/internal/middlewares"
2022-03-06 12:47:40 +07:00
"github.com/authelia/authelia/v4/internal/model"
2022-03-03 18:20:43 +07:00
"github.com/authelia/authelia/v4/internal/regulation"
)
2022-04-08 11:13:47 +07:00
// WebauthnAssertionGET handler starts the assertion ceremony.
func WebauthnAssertionGET ( ctx * middlewares . AutheliaCtx ) {
2022-03-03 18:20:43 +07:00
var (
w * webauthn . WebAuthn
2022-03-06 12:47:40 +07:00
user * model . WebauthnUser
2022-03-03 18:20:43 +07:00
err error
)
userSession := ctx . GetSession ( )
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 ( ) ) ,
}
extensions := make ( map [ string ] interface { } )
if user . HasFIDOU2F ( ) {
extensions [ "appid" ] = w . Config . RPOrigin
}
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 11:13:47 +07:00
// WebauthnAssertionPOST handler completes the assertion ceremony after verifying the challenge.
func WebauthnAssertionPOST ( ctx * middlewares . AutheliaCtx ) {
2022-03-03 18:20:43 +07:00
var (
err error
w * webauthn . WebAuthn
2022-07-26 12:43:39 +07:00
bodyJSON signWebauthnRequestBody
2022-03-03 18:20:43 +07:00
)
2022-07-26 12:43:39 +07:00
if err = ctx . ParseBody ( & bodyJSON ) ; err != nil {
2022-03-03 18:20:43 +07:00
ctx . Logger . Errorf ( logFmtErrParseRequestBody , regulation . AuthTypeWebauthn , err )
respondUnauthorized ( ctx , messageMFAValidationFailed )
return
}
userSession := ctx . GetSession ( )
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 12:47:40 +07:00
user * model . WebauthnUser
2022-03-03 18:20:43 +07:00
)
if assertionResponse , err = protocol . ParseCredentialRequestResponseBody ( bytes . NewReader ( ctx . PostBody ( ) ) ) ; err != nil {
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
}
if err = ctx . Providers . SessionProvider . RegenerateSession ( ctx . RequestCtx ) ; err != nil {
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 18:18:58 +07:00
userSession . SetTwoFactorWebauthn ( ctx . Clock . Now ( ) ,
assertionResponse . Response . AuthenticatorData . Flags . UserPresent ( ) ,
assertionResponse . Response . AuthenticatorData . Flags . UserVerified ( ) )
2022-03-03 18:20:43 +07: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 12:43:39 +07:00
if bodyJSON . Workflow == workflowOpenIDConnect {
handleOIDCWorkflowResponse ( ctx , bodyJSON . TargetURL )
2022-03-03 18:20:43 +07:00
} else {
2022-07-26 12:43:39 +07:00
Handle2FAResponse ( ctx , bodyJSON . TargetURL )
2022-03-03 18:20:43 +07:00
}
}