RADIUS and OTP Server mechanics


I am working on a authentication setup for a system which I am still conceptually trying to grasp. I’d like to get a confirmation of my current understanding, that way I can go ahead and proceed with the implementation if I got the concept right.

So conceptually, if I wanted to start implementing a RADIUS TOTP authentication solution:
1. Set up the OTP server which generates seeds for the TOTP tokens/generators
2. Bind a user in RADIUS with a token using a seed the OTP server generated
3. Import the seed into BitWarden which will create the token/TOTP generator there

After that I should be ready for authentication.
How I think it authentication works (correct me if I am wrong):
1. Use the TOTP code BitWarden generates when authenticating against RADIUS together with the defined username of the user
2. RADIUS takes the TOTP code I input and forwards it to the OTP server together with the user’s associated seed
3. The OTP server checks if the code that was input matches what it has got in its own token with the appropriate seed
4. Whether it’s correct or not, it returns that result to RADIUS which then either says ACCESS_ACCEPT or ACCESS_REJECT depending on what the OTP server said

To summarize: the OTP server has a token with an associated seed that is generating codes. Using that seed you can create a sort of duplicate of that token in any authenticator (BitWarden in case) which generates TOTP codes which match the ones on the OTP server.
Whenever you are authenticating, what your TOTP generator generates has to match what the OTP server has got, if you want auth to succeed.
Lastly, what the 3 components of the system (RADIUS, OTP server, TOTP generator(BitWarden)) have in common is the seed which binds all of components together.

Have I got it right? The last part about the seed binding everything together is what I am wondering the most about.
It’s crucial for me to understand what is going on exactly, before I can start the actual engineering.

Thank you in advance!

In your case:

Bitwarden is the TOTP token. It uses the seed to create the OTP value.

The RADIUS server is only forwarding the request. Not crypto logic involved.

The privacyIDEA server also knows the seed and uses it to verify, if the given and transferred OTP value is correct.

Alright, got it, thank you!

1 Like

Okay, I’ve got privacyIDEA running, can access the webUI.

  1. Configured freeRADIUS on the same VM so PI and freeRADIUS can talk over
  2. Created a passwdresolver which looks into the /etc/freeradius/3.0/users file
  3. Defined bob with pwd hello in the users file along with DEFAULT Auth-Type := Perl
  4. Added the passwdresolver to a realm.

Problem is, when I try sending a RADIUS test request from the PI webUI test RADIUS button, the RADIUS server receives it but returns a reply message 500 Internal Server Error.

Complete debug output from freeradius -X:

(0) Received Access-Request Id 130 from to length 56
(0)   User-Name = "bob"
(0)   NAS-Identifier = "privacyIDEA"
(0)   User-Password = "hello"
(0) # Executing section authorize from file /etc/freeradius/3.0/sites-enabled/privacyidea
(0)   authorize {
(0) perl-privacyidea:   $RAD_REQUEST{'User-Name'} = &request:User-Name -> 'bob'
(0) perl-privacyidea:   $RAD_REQUEST{'User-Password'} = &request:User-Password -> 'hello'
(0) perl-privacyidea:   $RAD_REQUEST{'NAS-Identifier'} = &request:NAS-Identifier -> 'privacyIDEA'
(0) perl-privacyidea: &request:NAS-Identifier = $RAD_REQUEST{'NAS-Identifier'} -> 'privacyIDEA'
(0) perl-privacyidea: &request:User-Password = $RAD_REQUEST{'User-Password'} -> 'hello'
(0) perl-privacyidea: &request:User-Name = $RAD_REQUEST{'User-Name'} -> 'bob'
(0)     [perl-privacyidea] = ok
(0)     if (ok || updated) {
(0)     if (ok || updated)  -> TRUE
(0)     if (ok || updated)  {
(0)       update control {
(0)         Auth-Type := Perl
(0)       } # update control = noop
(0)     } # if (ok || updated)  = noop
(0)   } # authorize = ok
(0) Found Auth-Type = Perl
(0) # Executing group from file /etc/freeradius/3.0/sites-enabled/privacyidea
(0)   Auth-Type Perl {
(0) perl-privacyidea:   $RAD_REQUEST{'User-Name'} = &request:User-Name -> 'bob'
(0) perl-privacyidea:   $RAD_REQUEST{'User-Password'} = &request:User-Password -> 'hello'
(0) perl-privacyidea:   $RAD_REQUEST{'NAS-Identifier'} = &request:NAS-Identifier -> 'privacyIDEA'
(0) perl-privacyidea:   $RAD_CHECK{'Auth-Type'} = &control:Auth-Type -> 'Perl'
(0) perl-privacyidea:   $RAD_CONFIG{'Auth-Type'} = &control:Auth-Type -> 'Perl'
rlm_perl: Config File /etc/privacyidea/rlm_perl.ini found!
rlm_perl: Debugging config:
rlm_perl: Default URL https://localhost/validate/check
rlm_perl: Looking for config for auth-type Perl
rlm_perl: Auth-Type: Perl
rlm_perl: url: https://localhost/validate/check
rlm_perl: user sent to privacyidea: bob
rlm_perl: realm sent to privacyidea:
rlm_perl: resolver sent to privacyidea: resolver1
rlm_perl: client sent to privacyidea:
rlm_perl: state sent to privacyidea:
rlm_perl: urlparam user
rlm_perl: urlparam resConf
rlm_perl: urlparam pass
rlm_perl: Request timeout: 10
rlm_perl: Not verifying SSL certificate!
rlm_perl: elapsed time for privacyidea call: 0.086769
rlm_perl: privacyIDEA request failed: 500 INTERNAL SERVER ERROR
rlm_perl: privacyIDEA Result status is false!
rlm_perl: 500 Internal Server Error: The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application.
rlm_perl: privacyIDEA failed to handle the request
rlm_perl: return RLM_MODULE_FAIL
(0) perl-privacyidea: &request:NAS-Identifier = $RAD_REQUEST{'NAS-Identifier'} -> 'privacyIDEA'
(0) perl-privacyidea: &request:User-Password = $RAD_REQUEST{'User-Password'} -> 'hello'
(0) perl-privacyidea: &request:User-Name = $RAD_REQUEST{'User-Name'} -> 'bob'
(0) perl-privacyidea: &reply:Reply-Message = $RAD_REPLY{'Reply-Message'} -> '500 Internal Server Error: The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application.'
(0) perl-privacyidea: &control:Auth-Type = $RAD_CHECK{'Auth-Type'} -> 'Perl'
(0)     [perl-privacyidea] = fail
(0)   } # Auth-Type Perl = fail
(0) Failed to authenticate the user
(0) Using Post-Auth-Type Reject
(0) Post-Auth-Type sub-section not found.  Ignoring.
(0) Delaying response for 1.000000 seconds

Radtest also doesn’t work with bob when I directly try doing it from the CLI. What am I missing here?

Ok, last post can be disregarded - I figured it out. Generated a flatfile with a user using the privacyidea-create-pwidresolver-user script and now it works!

One other question though - why do I need to prepend the OTP value with the user’s PIN when I send the authentication request? Wouldn’t just the OTP value be enough?

Also, what do I use the (during the privacyidea-create-pwidresolver-user flatfile creation) user’s password for actually? Authentication works now only with the username & PIN+OTPValue so I don’t need to enter the actual password anywhere.