TOTP

Overview

The TOTP plugin provides a login flow that implements a bare-bones form of support for TOTP OATH tokens. Any software or hardware token supporting the TOTP algorithm should work. It can be used to piggyback on the existing Password flow's login form view, with an additional field to collect the token code, or it can be run separately with a dedicated view, usually via the IdP's Multi-Factor Authentication feature. In either case, the identity of the subject used to resolve token seeds is generally based on an earlier authentication step.

There are some limitations to understand about this add-on:

  • There is no support for enrollment of users and tokens or for managing those enrollments.

  • The default implementation provides a couple of options for looking up token seeds for subjects, a static/dummy option for testing and making use of the IdP's Attribute Resolver service to look up (and optionally decrypt) token information at runtime. An extension point does exist for providing alternative token sources, but this interface is not at this time a public API so should be viewed as unstable.

Taken together, this plugin is probably best viewed as either a feature for local extension or as a tool for some limited kinds of deployments where tokens can be managed by hand in some way much as SSH keys often are by small teams.

Plugin Installation

Plugin ID

Module(s)

Authentication Flow ID

Latest Version

Plugin ID

Module(s)

Authentication Flow ID

Latest Version

net.shibboleth.idp.plugin.authn.totp

idp.authn.TOTP

authn/TOTP

1.0.0: download

Installing this plugin will automatically enable or update the "idp.authn.TOTP" module since that is the only functionality provided. You are of course free to subsequently re-enable or disable the module by hand.

Token Creation and Testing

While this plugin lacks real management functionality, there is a simple command-line tool installed into bin for generating new token seeds and performing test validations.

The output includes the unencrypted, Base32-encoded seed, a URL that can be used to manually install the token into software authenticators, and a URL to a QR code generator for the token.

Generating tokens
1 2 3 4 5 $ cd /opt/shibboleth-idp $ bin/totpauth.sh --home . --issuer Shibboleth --account jdoe Seed: 7ZODY4DQQ76DHYL3JK66DJIR3TE3QPW5 URL: otpauth://totp/Shibboleth:jdoe?secret=7ZODY4DQQ76DHYL3JK66DJIR3TE3QPW5&issuer=Shibboleth QR Code: https://api.qrserver.com/v1/create-qr-code/?data=otpauth%3A%2F%2Ftotp%2FShibboleth%3Ajdoe%3Fsecret%3D7ZODY4DQQ76DHYL3JK66DJIR3TE3QPW5%26issuer%3DShibboleth&size=200x200&ecc=M&margin=0

The same utility also includes a mode for validating codes by supplying both the seed and a code to check.

Configuration

Installing/enabling this module will only populate a few files:

File

Description

File

Description

views/totp.vm

View template for dedicated prompt for token code

views/totp-error.vm

View template for reporting errors with code validation

bin/totp.sh

Shell script for testing token code validation

bin/totp.bat

Batch script for testing token code validation

Other than the user interface, which is fairly self-explanatory, the only configuration required is to define how to resolve token seeds to use while validating a particular subject's token codes. This is assumed to be the Attribute Resolver in most "real" cases, but it's also possible to define some static tokens for testing in a Spring configuration file (e.g., conf/global.xml).

User Interface

After installation, a view (with some error handling examples) is provided to support a "two-stage" user interface where the token code is collected after an initial factor is completed that will supply the username to this flow. This is designed to work out of the box with a simple "Password flow, then TOTP flow" MFA configuration, much as the Duo flow works.

As an alternative, you may choose to "collapse" the collection process so that the Password login form includes the input field to collect the token code. This would perhaps be appropriate in cases when you impose TOTP as a second factor to all requests. The name of the input field is "tokencode" by default but is controllable with the idp.authn.TOTP.fieldName property. With this approach, the same underlying configuration is used but the flow will try and locate the form field before displaying the second view, and if for some reason the code fails, it will simply fall back to the second view to reprompt for the code.

One note about this: because of the way Spring messages work, unfortunately putting the input field on the Password collection view will not allow use of any Spring message identifiers that are pre-installed with this module, namely "idp.totp.field" for the field label. So you would need to add that to your own conf/messages.properties file or just inline the label.

Finally, for non-browser use, it's also possible to supply the code in a request header, "X-Shibboleth-TOTP" by default, also controllable with a property. Because of this, the flow is installed by default as supporting non-browser use, though if the header is missing there is no user interface to ask for one.

Source of Token Seeds

This plugin supports a public interface to obtain the token seeds for a specific user at runtime. There are two implementations provided, as described below.

The default behavior of the flow is to perform a special attribute resolution step to resolve an attribute named by the idp.authn.TOTP.tokenSeedAttribute property, defaulting to "tokenSeeds". Since the attribute name really shouldn't matter much, leaving it defaulted is usually the best choice but if you want to set the property you can put it in any of the existing property files, advisedly the conf/authn/authn.properties file added in V4.1.

The attribute itself must supply one or more string values containing the Base32-encoded token seed(s) to try and validate the supplied code against. Note these seeds cannot be encrypted at this stage. If you wish to store them in an encrypted form, it's possible to do so using the IdP using its "DataSealer" component that manages a list of keys shared amongst all the nodes of a cluster. Assuming this is done (details below), the DecryptedAttributeDefinition can be used to decrypt them on the fly to produce the resulting attribute.

DataSealer Definition and Use

While most IdPs include a DataSealer configured internally, this is typically not a good choice for this use case because the original use cases tend to assume the keys are being rotated frequently. To use a longer-term key, a dedicated component will typically be required. In order to facilitate using it to encrypt data using the command line, it's a good idea to define it in a separate Spring resource and load that into the Attribute Resolver's set of configuration resources in conf/services.xml.

Note that you will need to use the seckeygen utility yourself out of band to initialize the keystore and a key to use:

Initializing keystore
1 2 3 4 5 $ cd /opt/shibboleth-idp $ bin/seckeygen.sh --alias totp --count 1 \ --storefile credentials/totpsealer.jks \ --storepass password \ --versionfile credentials/totpsealer.kver
Dedicated DataSealer configuration
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 <?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"> <bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer" p:placeholderPrefix="%{" p:placeholderSuffix="}" /> <bean id="totp.DataSealerKeyStrategy" lazy-init="true" class="net.shibboleth.utilities.java.support.security.impl.BasicKeystoreKeyStrategy" p:keystoreType="JCEKS" p:keystoreResource="%{idp.home}/credentials/totpsealer.jks" p:keyVersionResource="%{idp.home}/credentials/totpsealer.kver" p:keyAlias="totp" p:keystorePassword="password" p:keyPassword="password" p:updateInterval="PT1H" /> <bean id="totp.DataSealer" lazy-init="true" class="net.shibboleth.utilities.java.support.security.DataSealer" p:keyStrategy-ref="totp.DataSealerKeyStrategy" /> </beans>

With this example, a DecryptedAttributeDefinition's dataSealerRef property would be set to "totp.DataSealer".

To use this from the command line to encrypt (and for example purposes, decrypt) data, assuming the file were named conf/totpsealer.xml:

DataSealer Command Example
1 2 3 $ cd /opt/shibboleth-idp $ bin/sealer.sh --quiet conf/totpsealer.xml dec `bin/sealer.sh --quiet conf/totpsealer.xml enc data` data

Note that the data produced by encryption is generally (though not absolutely required to be) Base64-encoded. This is not the same as how the token seed itself may be encoded, as the seed is in cleartext. The typical encoding for an encrypted seed would be a Base32-encoded seed, encrypted and encoded with Base64.

 

Primarily for testing, it's possible to supply a Spring bean containing a map of statically defined token seeds for users. The bean MUST be named shibboleth.authn.TOTP.SeedSource and will inherit from a parent bean named shibboleth.authn.TOTP.StaticSeedSource.

As an example, something like this in conf/global.xml will work:

Static token seeds
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <bean id="shibboleth.authn.TOTP.SeedSource" parent="shibboleth.authn.TOTP.StaticSeedSource"> <property name="seeds"> <map> <entry key="jdoe"> <list> <value>K44GQWBAI54IQELEGYPDZG5LAJ6EJX73</value> </list> </entry> <entry key="jsmith"> <list> <value>MLDE4YVTA7YKD5NZTDQX65ZPZ2A2OMW2</value> </list> </entry> </map> </property> </bean>

Reference

The TOTP-specific properties defined are:

Name

Default

Description

Name

Default

Description

idp.authn.TOTP.headerName

X-Shibboleth-TOTP

Name of request header to use for extracting non-browser submitted token codes

idp.authn.TOTP.fieldName

tokencode

Name of HTML form field to use for locating browser-submitted token codes

idp.authn.TOTP.tokenSeedAttribute

tokenSeeds

Name of IdPAttribute to resolve to obtain token seeds for users

The general properties configuring this flow via authn/authn.properties are:

Name

Default

Description

Name

Default

Description

idp.authn.TOTP.order

1000

Flow priority relative to other enabled login flows (lower is "higher" in priority)

idp.authn.TOTP.nonBrowserSupported

true

Whether the flow should handle non-browser request profiles (e.g., ECP)

idp.authn.TOTP.passiveAuthenticationSupported

true

Whether the flow allows for passive authentication

idp.authn.TOTP.forcedAuthenticationSupported

true

Whether the flow supports forced authentication

idp.authn.TOTP.proxyRestrictionsEnforced

%{idp.authn.enforceProxyRestrictions:true}

Whether the flow enforces upstream IdP-imposed restrictions on proxying

idp.authn.TOTP.proxyScopingEnforced

false

Whether the flow considers itself to be proxying, and therefore enforces SP-signaled restrictions on proxying

idp.authn.TOTP.discoveryRequired

false

Whether to invoke IdP-discovery prior to running flow

idp.authn.TOTP.lifetime

%{idp.authn.defaultLifetime:PT1H}

Lifetime of results produced by this flow

idp.authn.TOTP.inactivityTimeout

%{idp.authn.defaultTimeout:PT30M}

Inactivity timeout of results produced by this flow

idp.authn.TOTP.reuseCondition

shibboleth.Conditions.TRUE

Bean ID of Predicate<ProfileRequestContext> controlling result reuse for SSO

idp.authn.TOTP.activationCondition

shibboleth.Conditions.TRUE

Bean ID of Predicate<ProfileRequestContext> determining whether flow is usable for request

idp.authn.TOTP.subjectDecorator



Bean ID of BiConsumer<ProfileRequestContext,Subject> for subject customization

idp.authn.TOTP.supportedPrincipals

(see below)

Comma-delimited list of protocol-specific Principal strings associated with flow

idp.authn.TOTP.addDefaultPrincipals

true

Whether to auto-attach the preceding set of Principal objects to each Subject produced by this flow

As a non-password based flow, the supportedPrincipals property defaults to the following XML:

1 2 3 4 5 6 <list> <bean parent="shibboleth.SAML2AuthnContextClassRef" c:classRef="urn:oasis:names:tc:SAML:2.0:ac:classes:TimeSyncToken" /> <bean parent="shibboleth.SAML1AuthenticationMethod" c:method="urn:oasis:names:tc:SAML:1.0:am:HardwareToken" /> </list>

In property form, this is expressed as:

1 2 3 idp.authn.TOTP.supportedPrincipals = \ saml2/urn:oasis:names:tc:SAML:2.0:ac:classes:TimeSyncToken, \ saml1/urn:oasis:names:tc:SAML:1.0:am:HardwareToken

 

 

The following beans exist or may be defined to customize the flow. They may be placed in conf/global.xml or another imported location.

Name / Type

Default

Description

Name / Type

Default

Description

shibboleth.authn.TOTP.SeedSource

Consumer<ProfileRequestContext>

Use of Attribute Resolver

Overrides the component that populates seeds for a user into the context tree for validation of codes

shibboleth.authn.TOTP.Authenticator

net.shibboleth.idp.plugin.authn.totp.impl.TOTPAuthenticator

Google library-based implementation

Overrides the component that does validation of token codes

shibboleth.authn.TOTP.AccountLockoutManager

AccountLockoutManager



A lockout manager that, if defined, will enable account lockout feature

shibboleth.authn.TOTP.ClassifiedMessageMap

Map<String,List<String>>

Built-in component

A map between defined error/warning conditions and events and implementation-specific message fragments to map to them.

shibboleth.authn.TOTP.Validator

CredentialValidator

Built-in component

Override of the core component that validates token codes

shibboleth.authn.Duo.UsernameLookupStrategy

Function<ProfileRequestContext,String>

CanonicalUsernameLookupStrategy

Optional bean to supply username

shibboleth.authn.TOTP.resultCachingPredicate

Predicate<ProfileRequestContext>



An optional bean that can be defined to control whether to preserve the authentication result in an IdP session

 

To replace the internally defined flow descriptor bean, the following XML is required:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <util:list id="shibboleth.AvailableAuthenticationFlows"> <bean p:id="authn/TOTP" parent="shibboleth.AuthenticationFlow" p:order="%{idp.authn.TOTP.order:1000}" p:nonBrowserSupported="%{idp.authn.TOTP.nonBrowserSupported:true}" p:passiveAuthenticationSupported="%{idp.authn.TOTP.passiveAuthenticationSupported:true}" p:forcedAuthenticationSupported="%{idp.authn.TOTP.forcedAuthenticationSupported:true}" p:proxyRestrictionsEnforced="%{idp.authn.TOTP.proxyRestrictionsEnforced:%{idp.authn.enforceProxyRestrictions:true}}" p:proxyScopingEnforced="%{idp.authn.TOTP.proxyScopingEnforced:false}" p:discoveryRequired="%{idp.authn.TOTP.discoveryRequired:false}" p:lifetime="%{idp.authn.TOTP.lifetime:%{idp.authn.defaultLifetime:PT1H}}" p:inactivityTimeout="%{idp.authn.TOTP.inactivityTimeout:%{idp.authn.defaultTimeout:PT30M}}" p:reuseCondition-ref="#{'%{idp.authn.TOTP.reuseCondition:shibboleth.Conditions.TRUE}'.trim()}" p:activationCondition-ref="#{'%{idp.authn.TOTP.activationCondition:shibboleth.Conditions.TRUE}'.trim()}" p:subjectDecorator-ref="#{getObject('%{idp.authn.TOTP.subjectDecorator}'.trim())}"> <property name="supportedPrincipalsByString"> <bean parent="shibboleth.CommaDelimStringArray" c:_0="#{'%{idp.authn.TOTP.supportedPrincipals:}'.trim()}" /> </property> </bean> </util:list>

In older versions and upgraded systems, this list is defined in conf/authn/general-authn.xml. In V4.1+, no default version of the list is provided and it may simply be placed in conf/global.xml if needed.