top of page
  • Ondrej Kuznik

How-To Guide: Two-Factor Authentication

AVAILABLE WITH SYMAS OPENLDAP GOLD 2.4.46.1

Passwords, everyone loves to hate them and still, in the era of digital certificates, fingerprints, and voice recognition, we use them on a daily basis and want users to memorize tens of different complex passwords. So they cheat and passwords get reused, written down on a piece of paper, you name it. Not that service providers always get this right either.

And any time we use a password, we run the risk of it being intercepted, and at that point it's no longer secret and needs to be changed.

Still, passwords have one important advantage over most other forms of authentication: as a simple string they can be used anywhere. Everyone understands a login prompt and it tends to be rather hard to make anything else work for everyone.

One-time passwords represent a modest improvement being relatively unobtrusive for the user while anyone who sees the password should be unable to do anything with it. The user has little extra work to do, and chances are they already carry a smartphone with them. But how do we get existing applications to support them without extensive modification?

For applications that use OpenLDAP for authentication, the answer is easy: enable the otp_2fa overlay and share another secret with the user. It can be as simple as letting them scan a QR code.

GETTING STARTED

Symas OpenLDAP Gold now includes a module that lets any application that authenticates through LDAP to work with time-based and counter-based one-time passwords.

MODULE

The Two-factor Authentication module is standard in versions of Symas OpenLDAP Gold and Silver starting with release 2.4.46.1.

CONFIGURATION

  1. Load the otp_2fa module: $ ldapmodify -x -D cn=config -W -H ldap://localhost dn: cn=module{0},cn=config changetype: modify add: olcModuleLoad olcModuleLoad: path/to/otp_2fa.la

  2. Add the otp_2fa overlay to the definition of each database that will allow OTP authentication

For each database that you will be storing users with TOTP set up, enable the overlay:

$ ldapadd -x -D cn=config -W -H ldap://localhost
dn: olcOverlay=otp_2fa,olcDatabase={X}YYY,cn=config
objectClass: olcOverlayConfig

PROVISIONING THE COMMON OTP PARAMETERS

To let the overlay know how to generate the one-time passwords, we store the common HOTP and/or TOTP parameters. Usually, it is the organisation or group entry.

For TOTP we care about the password length, hash algorithm, time step size and a grace time window. Popular sites usually choose 6-character passwords with SHA1 hash updated every 30 seconds with a window of over a minute (three time steps).

$ ldapmodify -x -D o=example -W -H ldap://localhost
dn: ou=people,dc=example,dc=com
changetype: modify
add: objectClass
objectClass: oathTOTPParams
-
add: oathOTPLength
oathOTPLength: 6
-
add: oathHMACAlgorithm
# choose SHA1, algorithm OIDs are specified in RFC 8018
oathHMACAlgorithm: 1.2.840.113549.2.7
-
add: oathTOTPTimeStepPeriod
oathTOTPTimeStepPeriod: 30
-
add: oathTOTPTimeStepWindow
oathTOTPTimeStepWindow: 3

Alternatively, for HOTP we care about the password length, hash algorithm and look-ahead. Popular sites usually choose 6-character passwords with SHA1 hash and a look-ahead of 3 passwords.

$ ldapmodify -x -D cn=manager,cn=people,o=example -W -H ldap://localhost
dn: ou=people,dc=example,dc=com
changetype: modify
add: objectClass
objectClass: oathHOTPParams
-
add: oathOTPLength
oathOTPLength: 6
-
add: oathHMACAlgorithm
# choose SHA1, algorithm OIDs are specified in RFC 8018
oathHMACAlgorithm: 1.2.840.113549.2.7
-
add: oathHOTPLookAhead
oathHOTPLookAhead: 3

Now that the server knows how to use TOTP, we can let our user set things up.

They start with generating a random shared secret that's at least 20 bytes long:

$ touch sharedkey
$ chmod 600 sharedkey
$ openssl rand 20 > sharedkey
$ base32 sharedkey
N6CIHPYDGHI6QC4ZV7Q3S3FXA22BZOCY

They then add the secret as a new token. A token can also be shared between accounts.

Example for TOTP:

$ ldapmodify -x -D cn=user,ou=people,dc=example,dc=com -W -H ldap://localhost
dn: cn=user,ou=people,dc=example,dc=com
changetype: modify
add: objectClass
objectClass: oathTOTPToken
-
add: oathTOTPParams
oathTOTPParams: ou=people,dc=example,dc=com
-
add: oathSecret
oathSecret:< file://sharedkey
-
add: objectClass
objectClass: oathTOTPUser
-
add: oathTOTPToken
oathTOTPToken: cn=user,ou=people,dc=example,dc=com

Example for HOTP:

$ ldapmodify -x -D cn=user,ou=people,dc=example,dc=com -W -H ldap://localhost
dn: cn=user,ou=people,dc=example,dc=com
changetype: modify
add: objectClass
objectClass: oathHOTPToken
-
add: oathHOTPParams
oathHOTPParams: ou=people,dc=example,dc=com
-
add: oathSecret
oathSecret:< file://sharedkey
-
add: objectClass
objectClass: oathHOTPUser
-
add: oathHOTPToken
oathHOTPToken: cn=user,ou=people,dc=example,dc=com

The user now adds this shared key to their authenticator. In the Google Authenticator app, they would select 'Enter a provided key' and then type the generated string.

SHOWTIME


Now it's time to test.

That's it! It's now trivial to log into the system: whenever you get asked to enter your password, check with your device and enter your password, immediately followed by the six-digit code that appears on the screen:


$ ldapwhoami -x -H ldap://localhost -D 'cn=user,dc=example,dc=com' -W
Enter LDAP Password:
dn:cn=user,ou=people,dc=example,dc=com

If your systems rely on your server for user authentication and use LDAP Binds, you are set. Once a user has a shared key set in the database, they can use the token to log in alongside their usual password. And just like they remember their password, they make sure they retain control over their device.

FINAL NOTES

If you decide to use this in production, you might want to make it easier for users to manage their keys. Many authenticator apps support reading the key from a QR code, so rendering the key in a QR code on a web page becomes a handy way to deliver it. In modern web systems rendering QR codes is easy with generators written in node.js, such as [qr-image (https://github.com/alexeyten/qr-image).

This is a based one-time password scheme, and as such you will have some trouble using it as the only method of authentication when the credentials have to be reused or if there is a disconnect between the time the user enters the password and when authentication happens. In these circumstances, switching to proxy authorisation once you have authenticated your user might be an option worth investigating.


COMPLETE CN=CONFIG EXAMPLE

dn: cn=config
objectClass: olcGlobal

dn: cn=module{0},cn=config
objectClass: olcModuleList
olcModulePath: /opt/symas/lib64/openldap
olcModuleLoad: back_mdb.la
olcModuleLoad: otp_2fa.la

dn: cn=schema,cn=config
objectClass: olcSchemaConfig

dn: cn={0}core,cn=schema,cn=config
objectClass: olcSchemaConfig
olcAttributeTypes: ( 2.5.4.4 NAME ( 'sn' 'surname' )
 DESC 'RFC2256: last (family) name(s) for which the entity is known by'
 SUP name )
olcAttributeTypes: ( 2.5.4.20 NAME 'telephoneNumber'
 DESC 'RFC2256: Telephone Number'
 EQUALITY telephoneNumberMatch
 SUBSTR telephoneNumberSubstringsMatch
 SYNTAX 1.3.6.1.4.1.1466.115.121.1.50{32} )
olcObjectClasses: ( 2.5.6.6 NAME 'person'
 DESC 'RFC2256: a person'
 SUP top STRUCTURAL
 MUST ( sn $ cn )
 MAY ( userPassword $ telephoneNumber $ seeAlso $ description ) )
olcAttributeTypes: ( 0.9.2342.19200300.100.1.25
 NAME ( 'dc' 'domainComponent' )
 DESC 'RFC1274/2247: domain component'
 EQUALITY caseIgnoreIA5Match
 SUBSTR caseIgnoreIA5SubstringsMatch
 SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
olcObjectClasses: ( 1.3.6.1.4.1.1466.344 NAME 'dcObject'
 DESC 'RFC2247: domain component object'
 SUP top AUXILIARY MUST dc )

dn: olcDatabase={0}config,cn=config
objectClass: olcDatabaseConfig
olcRootDN: cn=config
olcRootPW: secret

dn: olcDatabase={1}mdb,cn=config
objectClass: olcMdbConfig
olcDbDirectory: /var/symas/openldap-data/example
olcSuffix: dc=example,dc=com
olcRootDN: dc=example,dc=com
olcRootPW: secret

dn: olcOverlay=otp_2fa,olcDatabase={1}mdb,cn=config
objectClass: olcOverlayConfig

COMPLETE SLAPD.CONF EXAMPLE

include /opt/symas/etc/openldap/schema/core.schema

modulepath /opt/symas/lib64/openldap
moduleload back_mdb.la
moduleload otp_2fa.la

database mdb
directory /var/symas/openldap-data/example
suffix "dc=example,dc=com"
rootdn "dc=example,dc=com"
rootpw secret

overlay otp_2fa

EXAMPLE DATABASE

dn: dc=example,dc=com
objectClass: organization
objectClass: dcObject
o: example.com

dn: ou=people,dc=example,dc=com
objectClass: organizationalUnit
objectClass: oathHOTPParams
oathOTPLength: 6
# choose SHA1, algorithm OIDs are specified in RFC 8018
oathHMACAlgorithm: 1.2.840.113549.2.7
oathHOTPLookAhead: 3
objectClass: oathTOTPParams
oathTOTPTimeStepPeriod: 30
oathTOTPTimeStepWindow: 3

# QR Code:
# otpauth://hotp/hotp@example.com?secret=N6CIHPYDGHI6QC4ZV7Q3S3FXA22BZOCY&issuer=example.com&digits=6&algorithm=SHA1
dn: cn=HOTP user,ou=people,dc=example,dc=com
objectClass: person
objectClass: oathHOTPToken
objectClass: oathHOTPUser
sn: HOTP
userPassword: secret
oathHOTPParams: ou=people,dc=example,dc=com
oathHOTPToken: cn=HOTP user,ou=people,dc=example,dc=com
oathSecret:: b4SDvwMx0egLma/huWy3BrQcuFg=

# QR Code:
# otpauth://totp/totp@example.com?secret=N6CIHPYDGHI6QC4ZV7Q3S3FXA22BZOCY&issuer=example.com&period=30&digits=6&algorithm=SHA1
dn: cn=TOTP user,ou=people,dc=example,dc=com
objectClass: person
objectClass: oathTOTPToken
objectClass: oathTOTPUser
sn: TOTP
userPassword: secret
oathTOTPParams: ou=people,dc=example,dc=com
oathTOTPToken: cn=TOTP user,ou=people,dc=example,dc=com
oathSecret:: b4SDvwMx0egLma/huWy3BrQcuFg=
693 views0 comments

Recent Posts

See All

OpenLDAP & LMDB Sizing Guide

Jan 17, 2022 Introduction Symas OpenLDAP configured with LMDB has been extensively tested and its performance characteristics are well understood. Both OpenLDAP and LMDB’s scaling characteristics are

Implementing LDAPS in Symas OpenLDAP 2.5+

Please note that the certificates must be in a pem format (.pem or .crt). You will need three certificates: Root CA certificate, server certificate (with the fqdn of server in subject line or in the s

bottom of page