This plugin can be used as an additional factor for multi-factor authentication (MFA) by the Shibboleth Identity Provider (IdP). It supports token-based authentication and at its core follows the challenge-response pattern. The username has to be determined by one of the previous factors in the MFA chain. A common use-case is the preceding authentication with username/password against an LDAP using the authn/password flow.
The plugin itself does not contain functions for token administration nor special algorithms for validating tokens. An external token system can be connected via the generic ChallengeResponseClientInterface. An implementation of this interface for privacyIDEA is included as well as a mock-up implementation for testing purposes. Also an external implementation for Fortinet already exists.
So far the development has been done by Steffen Hofmann of Freien Universität Berlin. He is an employee at the university's IT department (ZEDAT – Zentraleinrichtung für Datenverarbeitung) and in charge of identity management. Because the identity management at FU is called FUDIS (FU Directory and Identity Service), for the challenge-response plugin the name 'fudiscr' is used for configuration parameters, etc. In the future the support for the plugin will be provided by Freie Universität Berlin. In case this can no longer be ensured by the university anymore, partner organizations will ensure it.
Installation of privacyIDEA: https://gitlab.daasi.de/training/privacyidea
Currently the following token methods from privacyIDEA (Token types in privacyIDEA) are supported:
Support of Push Token is currently in development.
The plugin can be used with Shibboleth IdP version >= 4.1.2.
Installation and updates are done via the Shibboleth plugin mechanism (see official docs PluginInstallation)
Warning: The Shibboleth Identity Provider will restart after plugin installation.
Plugin is installed using the following command:
%{idp.home}/bin/plugin.sh -i https://identity.fu-berlin.de/downloads/shibboleth/idp/plugins/authn/fudiscr/current/fudis-shibboleth-idp-plugin-authn-fudiscr-current.tar.gz
Future updates can be installed like this:
%{idp.home}/bin/plugin.sh -u de.zedat.fudis.shibboleth.idp.plugin.authn.fudiscr
For unattended installations the PGP keys can be downloaded upfront and supplied on installation like this:
wget -O %{idp.home}/PGP_KEYS_FUDIS https://identity.fu-berlin.de/downloads/shibboleth/idp/plugins/PGP_KEYS %{idp.home}/bin/plugin.sh -i https://identity.fu-berlin.de/downloads/shibboleth/idp/plugins/authn/fudiscr/current/fudis-shibboleth-idp-plugin-authn-fudiscr-current.tar.gz --truststore %{idp.home}/PGP_KEYS_FUDIS
The Shibboleth plugin communicates with privacyIDEA by its API. The following three end points are used:
Therefore the user has to have sufficient rights to carry out all the actions that are triggered by those request and ideally no additional rights.
First we need to set up an admin user. It is recommended to set up a separate (administrative) realm as well in order to be able to differentiate between different types of rights for administrative users later on. The user account can be supplied by any external source and be imported by a resolver defined in PrivacyIdea.
The setup of realms and resolvers os not part of this documentation but for the sake of simplicity and clarity we will set up the privacyIDEA internal user idp-admin.
pi-manage admin add idp-admin
Furthermore we assume that the initial admin super user admin exists.
The required permissions are granted in privacyIDEA via policy.
Warning: Admins in privacyIDEA get all permissions by default. This holds as long as no admin policy is defined. Once a policy is defined with scope 'admin', all admin users are restricted by the rights in this policy. In case there is no admin policy yet, make sure not to lock yourself out of the Web UI. That's why privacyIDEA informs you, if you define an admin policy, but the rights to edit policies are not granted in this (or any other) policy.
At first we create the functionally required policy for the super user and assign it to user admin.
Then we add a new policy for our idp-admin user.
Therefore we create a new file idp-admin-policy, which contains parameters for the policy as a python dictionary. The content should be like this:
{ 'action': {'tokenlist':True, 'triggerchallenge':True}, 'active': True, 'adminuser': ['idp-admin'], 'name': 'idp-admin-policy', 'scope': 'admin' }
The policy can then be created by using the file template like this:
pi-manage policy create -f idp-admin-policy name scope action
In order to successfully authenticate against the API an authorization token hast to be supplied. This token can be requested either by an API call to the auth endpoint or created by using pi-manage. By using the pi-manage-script you can create a token with an arbitrary expiry time in days (default 365). Tokens requested from the API are only valid for one hour. Therefore we use pi-manage:
pi-manage api createtoken -r admin -u idp-admin
The connection to privacyIDEA is set up by the configuration options in %{idp.home}/conf/authn/fudiscr.properties
fudiscr.privacyidea.base_uri=<e.g. https://localhost> fudiscr.privacyidea.authorization_token=<from previous step> #System user from previous step. Can be use to extend the authorization token: #fudiscr.privacyidea.service_username= #fudiscr.privacyidea.service_realm= #fudiscr.privacyidea.service_password= #Realm which is added to the user by default: #fudiscr.privacyidea.default_realm= #In case the server certificate should not be validated by the API, set this value to 'false" #fudiscr.privacyidea.check_connection_certificate=true
(For REFEDS Multi-Factor Authentication profile see REFEDS AuthN Profiles - Hinweise für Identity Provider (in German))
To use this plugin with MFA, the MFA module itself has to be enabled in the Identity Provider.
This is done by:
%{idp.home}/bin/module.sh -e idp.authn.MFA
In %{idp.home}/conf/authn/authn.properties
the following settings should be done:
idp.authn.flows = MFA idp.authn.fudiscr.supportedPrincipals= \ saml2/urn:de:zedat:fudis:SAML:2.0:ac:classes:CR idp.authn.MFA.supportedPrincipals = \ saml2/urn:oasis:names:tc:SAML:2.0:ac:classes:InternetProtocol, \ saml2/urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport, \ saml2/urn:oasis:names:tc:SAML:2.0:ac:classes:Password, \ saml1/urn:oasis:names:tc:SAML:1.0:am:password, \ saml2/urn:de:zedat:fudis:SAML:2.0:ac:classes:CR
idp.authn.MFA.supportedPrincipals
is already activated by default.
We only add the AuthenticationContextClass saml2/urn:de:zedat:fudis:SAML:2.0:ac:classes:CR
.
Instead of saml2/urn:de:zedat:fudis:SAML:2.0:ac:classes:CR
you can set a different or additional AuthenticationContextClass
using idp.authn.fudiscr.supportedPrincipals
AND idp.authn.MFA.supportedPrincipals
.
It is recommended to set at least one additional class by which the multi factor authentication can be triggered.
This is not mandatory because multi factor authentication might be triggered by other mechanisms as well.
By using the transitions in %{idp.home}/conf/authn/mfa-authn-config.xml
the order of the individual factors is specified.
You can use various mechanisms here to define conditions for transitions to the following factors.
The official documentation can be found here:
MultiFactorAuthnConfiguration
Now we will look at three examples for MFA configuration.
Example 1: After authentication with username and password, a token based authentication using fudiscr is done in any case.
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:util="http://www.springframework.org/schema/util" xmlns:p="http://www.springframework.org/schema/p" xmlns:c="http://www.springframework.org/schema/c" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd" default-init-method="initialize" default-destroy-method="destroy"> <!-- most parts copied from https://shibboleth.atlassian.net/wiki/spaces/IDP4/pages/1265631610/MultiFactorAuthnConfiguration --> <util:map id="shibboleth.authn.MFA.TransitionMap"> <entry key=""> <bean parent="shibboleth.authn.MFA.Transition" p:nextFlow="authn/Password"/> </entry> <entry key="authn/Password"> <bean parent="shibboleth.authn.MFA.Transition" p:nextFlow="authn/fudiscr"/> </entry> </util:map> </beans>
Example 2: After authentication with username and password we request a token based authentication using fudiscr if the AuthenticationContextClass required by the service provider is not sufficient.
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:util="http://www.springframework.org/schema/util" xmlns:p="http://www.springframework.org/schema/p" xmlns:c="http://www.springframework.org/schema/c" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd" default-init-method="initialize" default-destroy-method="destroy"> <!-- most parts copied from https://shibboleth.atlassian.net/wiki/spaces/IDP4/pages/1265631610/MultiFactorAuthnConfiguration --> <util:map id="shibboleth.authn.MFA.TransitionMap"> <entry key=""> <bean parent="shibboleth.authn.MFA.Transition" p:nextFlow="authn/Password"/> </entry> <entry key="authn/Password"> <bean parent="shibboleth.authn.MFA.Transition" p:nextFlowStrategy-ref="checkSecondFactor"/> </entry> </util:map> <bean id="checkSecondFactor" parent="shibboleth.ContextFunctions.Scripted" factory-method="inlineScript"> <constructor-arg> <value> <![CDATA[ nextFlow = "authn/fudiscr"; authCtx = input.getSubcontext("net.shibboleth.idp.authn.context.AuthenticationContext"); mfaCtx = authCtx.getSubcontext("net.shibboleth.idp.authn.context.MultiFactorAuthenticationContext"); if (mfaCtx.isAcceptable()) { nextFlow = null; } nextFlow; ]]> </value> </constructor-arg> </bean> </beans>
Example 3: After authentication with username and password we request a token based authentication using fudiscr if the AuthenticationContextClass required by the service provider is not sufficient or if the user belongs to the group 'employee' according to eduPersonAffiliation.
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:util="http://www.springframework.org/schema/util" xmlns:p="http://www.springframework.org/schema/p" xmlns:c="http://www.springframework.org/schema/c" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd" default-init-method="initialize" default-destroy-method="destroy"> <!-- most parts copied from https://shibboleth.atlassian.net/wiki/spaces/IDP4/pages/1265631610/MultiFactorAuthnConfiguration --> <util:map id="shibboleth.authn.MFA.TransitionMap"> <entry key=""> <bean parent="shibboleth.authn.MFA.Transition" p:nextFlow="authn/Password"/> </entry> <entry key="authn/Password"> <bean parent="shibboleth.authn.MFA.Transition" p:nextFlowStrategy-ref="checkSecondFactor"/> </entry> </util:map> <bean id="checkSecondFactor" parent="shibboleth.ContextFunctions.Scripted" factory-method="inlineScript" p:customObject-ref="shibboleth.AttributeResolverService"> <constructor-arg> <value> <![CDATA[ nextFlow = "authn/fudiscr"; authCtx = input.getSubcontext("net.shibboleth.idp.authn.context.AuthenticationContext"); mfaCtx = authCtx.getSubcontext("net.shibboleth.idp.authn.context.MultiFactorAuthenticationContext"); if (mfaCtx.isAcceptable()) { nextFlow = null; resCtx = input.getSubcontext( "net.shibboleth.idp.attribute.resolver.context.AttributeResolutionContext", true); usernameLookupStrategyClass = Java.type("net.shibboleth.idp.session.context.navigate.CanonicalUsernameLookupStrategy"); usernameLookupStrategy = new usernameLookupStrategyClass(); resCtx.setPrincipal(usernameLookupStrategy.apply(input)); resCtx.getRequestedIdPAttributeNames().add("eduPersonAffiliation"); resCtx.resolveAttributes(custom); attribute = resCtx.getResolvedIdPAttributes().get("eduPersonAffiliation"); valueType = Java.type("net.shibboleth.idp.attribute.StringAttributeValue"); if (attribute != null && attribute.getValues().contains(new valueType("employee"))) { nextFlow = "authn/fudiscr"; } input.removeSubcontext(resCtx); } nextFlow; ]]> </value> </constructor-arg> </bean> </beans>
Example 4:
After authentication with username and password we request a token based authentication using fudiscr if the AuthenticationContextClass required by the service provider is not sufficient
or if the user owns at least one token. The state of the tokens is not checked here, so a deactivated token would result in fudiscr.UserHasAnyTokenPredicate
returning true
. This predicate
was introduced primarily for rollout scenarios.
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:util="http://www.springframework.org/schema/util" xmlns:p="http://www.springframework.org/schema/p" xmlns:c="http://www.springframework.org/schema/c" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd" default-init-method="initialize" default-destroy-method="destroy"> <util:map id="shibboleth.authn.MFA.TransitionMap"> <entry key=""> <bean parent="shibboleth.authn.MFA.Transition" p:nextFlow="authn/Password"/> </entry> <entry key="authn/Password"> <bean parent="shibboleth.authn.MFA.Transition" p:nextFlowStrategy-ref="checkSecondFactor"/> </entry> </util:map> <bean id="checkSecondFactor" parent="shibboleth.ContextFunctions.Scripted" factory-method="inlineScript" p:customObject-ref="fudiscr.UserHasAnyTokenPredicate"> <constructor-arg> <value> <![CDATA[ nextFlow = "authn/fudiscr"; authCtx = input.getSubcontext("net.shibboleth.idp.authn.context.AuthenticationContext"); mfaCtx = authCtx.getSubcontext("net.shibboleth.idp.authn.context.MultiFactorAuthenticationContext"); if (mfaCtx.isAcceptable()) { nextFlow = null; if (custom.test(input)) { nextFlow = "authn/fudiscr"; } } nextFlow; ]]> </value> </constructor-arg> </bean> </beans>
For authentication methods on Shibboleth Identity Provider a reuse condition can be defined.
In general the default value is shibboleth.Conditions.TRUE
which makes this condition be fulfilled in any case.
You can define your own functions as reuse conditions.
For multi factor authentication special rules apply. You can find a description in the official documentation: MultiFactorAuthnConfiguration
Using the following configuration you can achieve that username and password is requested only once inside a SSO session, but token based authentication is requested once on every service provider authentication.
idp.authn.MFA.reuseCondition=shibboleth.Conditions.FALSE idp.authn.Password.reuseCondition=shibboleth.Conditions.TRUE idp.authn.fudiscr.reuseCondition=shibboleth.Conditions.FALSE
This means the result of the MFA authentication can not be reused. Inside of a multi factor authentication the existing successful result of the username/password authentication (password) can be reused, but the result of fudiscr can not.
idp.authn.MFA.reuseCondition=shibboleth.Conditions.FALSE
in case on every authentication request the logic in ./conf/authn/mfa-authn-config.xml
is gone through. In the reuse case for instance during a valid SSO session, it is not checked whether a user has a certain affiliation.
You can find further configuration options in %{idp.home}/conf/authn/fudiscr.properties
.
This document might be of help ChallengeResponseFlow.pdf.
Starting from version 1.2.0 WebAuthn token can be used.
If a user owns multiple active WebAuthn tokens and you set fudiscr.user_token_selection=none
, fudiscr.user_token_selection=multipleToken
or fudiscr.user_token_selection=multipleTokenTypeGroup
in %{idp.home}/conf/authn/fudiscr.properties
,
you have to set fudiscr.privacyidea.single_trigger_challenges=false
as well.
In general it applies that the domain of the Identity Provider has to either be identical to the rpId from WebAuthn or a subdomain of it. There is no preliminary filtering done in order to check if the domain of the Identity Provider is compatible to the rpId of the WebAuthn token.
Please email fudis@zedat.fu-berlin.de for corrections, questions and suggestions.