IdP-Authn-Plugin fudiscr

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:

  • Email
  • HOTP Token
  • Indexed Secret Token
  • mOTP Token
  • Paper Token (PPR)
  • Questionnaire Token (Limitation: If only one answer is requested per directive.)
  • SMS Token
  • TAN Token
  • TOTP
  • WebAuthn (from version 1.2.0)

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

Adding a system user in privacyIDEA

The Shibboleth plugin communicates with privacyIDEA by its API. The following three end points are used:

  • /token/ (request a list of tokens)
  • /validate/triggerchallenge (initiate token challenge)
  • /validate/check (check an OTP, possibly in combination with a PIN)

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.

Setting up an admin user

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.

Granting permissions

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
Creating an API-Token

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

Configuration of privacyIDEA

The connection to privacyIDEA is set up by the configuration options in %{idp.home}/conf/authn/fudiscr.properties

./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:

./conf/authn/authn.properties
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

Examples

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.

./conf/authn/mfa-authn-config.xml
<?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.

./conf/authn/mfa-authn-config.xml
<?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.

./conf/authn/mfa-authn-config.xml
<?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.

./conf/authn/mfa-authn-config.xml
<?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>

Reuse Condition

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.

./conf/authn/authn.properties
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.

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.

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.

  • Last modified: 9 months ago