...
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).
...
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.
...
Expand |
---|
title | Using the Attribute Resolver |
---|
|
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 UseWhile 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. The example below is specific to V5+. 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 Code Block |
---|
| $ cd /opt/shibboleth-idp
$ bin/seckeygen.sh --alias totp --count 1 \
--storefile credentials/totpsealer.jks \
--storepass password \
--versionfile credentials/totpsealer.kver |
Dedicated DataSealer configuration Code Block |
---|
| <?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 id="totp.DataSealerKeyStrategy" lazy-init="true"
class="net.shibboleth.shared.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.shared.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 Code Block |
---|
| $ cd /opt/shibboleth-idp
$ bin/sealer.sh --quiet conf/totpsealer.xml enc data |
Tip |
---|
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. |
|
...
Expand |
---|
|
The TOTP-specific properties defined are: 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 | idp.authn.TOTP.windowSize | 3 | Number of “windows” to allow in clock drift between the server’s time and the token’s time | idp.authn.TOTP.timeStepSize | 30000 | Time in milliseconds for each “window” of time (see previous setting) |
The general properties configuring this flow via authn/authn.properties are: 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: Code Block |
---|
| <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: Code Block |
---|
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 |
|
...