
LinOTP + RADIUS. One Time Password Authentication
1. Basic information
This manual describes the process of integrating LinOTP and FreeRadius on CentOS-based machines, as well as setting up SSH user authentication using OTP generated using Google Authenticator software (or anyone using a similar algorithm).
2. The source data
This manual implies that we have three machines running CentOS 6.7. They are on the same subnet and network connectivity is configured between them. The names and addresses of the machines are shown in the table below.
Hostname IP address Conditional name Operating system linotp 10.1.0.114 LinOTP CentOS 6.7 server radius-p 10.1.0.122 RADIUS CentOS 6.7 server client 10.1.0.113 Client CentOS 6.7 admin-PC 10.1.0.120 Workstation (AWP) Windows 7
Linotp software is deployed on the LinOTP Server according to the instructions from the official website ( “LinOTP Server Installation” document ). Linotp software version 2.7.2.
A freeradius server is deployed on the RADIUS Server. I won’t describe the installation of packages in detail, because it is performed trivially with the yum manager. A complete list of packages required for installation is given below:
- freeradius-2.2.6-6.el6_7.i686;
- freeradius-perl-2.2.6-6.el6_7.i686;
- perl-devel-5.10.1-141.el6.i686;
- perl-Pod-Escapes-1.04-141.el6.i686;
- perl-version-0.77-141.el6.i686;
- perl-Test-Harness-3.17-141.el6.i686;
- perl-ExtUtils-ParseXS-2.2003.0-141.el6.i686;
- perl-Digest-SHA-5.47-141.el6.i686;
- perl-libs-5.10.1-141.el6.i686;
- perl-Module-Pluggable-3.90-141.el6.i686;
- perl-5.10.1-141.el6.i686;
- perl-ExtUtils-MakeMaker-6.55-141.el6.i686;
- perl-DBI-1.609-4.el6.i686;
- perl-CPAN-1.9402-141.el6.i686;
- perl-Pod-Simple-3.13-141.el6.i686.
The Client machine is a RADIUS client on which OTP authentication will take place. To support RADIUS authentication, the pam_radius-1.3.17-2.el5 package is installed on the machine.
A Windows automated workstation machine imitates an administrator workstation that will access the Client machine via SSH using OTP. On this machine is Windows 7, SSH putty client and GAUTH software - OTP generator.
3. What will we get as a result
The aim of this work is to configure authentication on the Client through the RADIUS Server using OTP (generated on the LinOTP Server).
The RADIUS server in this configuration does not store user accounts in its database - this task is assigned to the LinOTP server. The RADIUS server stores the NAS base, i.e. a list of servers and network equipment whose users it authenticates. In this configuration, the configuration text file /etc/raddb/clients.conf is used for this.
The LinOTP server in this configuration serves to manage user tokens. Information about users and their tokens is stored in the MySQL database, which is also deployed on the LinOTP server. By itself, linotp software does not have the user management management function, so you need to add users and their data to the MySQL table manually.
4. Setup steps
4.1. RADIUS Setup Procedure
The settings described in this section are made on the RADIUS Server, i.e. by radius-p machine.
4.1.1. Installing additional Perl modules
# perl -MCPAN -e shell
cpan[1]> install LWP
cpan[1]> install LWP:UserAgent
cpan[1]> install LWP:UserAgent:Determined
cpan[1]> install LWP::Protocol::https
Some of the installed modules may produce the following error during installation:
Running make install make test had returned bad status, won't install without force Failed during this command: MSCHILLI / LWP-Protocol-https-6.06.tar.gz: make_test NO
In this case, use the force install command, for example:
cpan[1]> force install LWP::Protocol::https
A message like:
Failed during this command: MSCHILLI / LWP-Protocol-https-6.06.tar.gz: make_test FAILED but failure ignored because 'force' in effect
4.1.2. Radius Setup
Add radius client NAS in /etc/raddb/clients.conf file. In our case, it is:
client 10.1.0.113 { secret = testing123 shortname = client }
In the file / etc / raddb / users add the line
DEFAULT Auth-type: = perl
*** However, this is not the best option. In this case, you force the authentication type to be set, which makes it impossible for radius to authenticate users whose accounts, for example, are stored in mysql (and the corresponding settings are made in RADIUS) simultaneously with users authenticated via linOTP.
In practice, I just faced this situation. In this case, this directive (DEFAULT Auth-type: = perl) does not need to be written, but instead the condition was added to the / etc / raddb / sites-enabled / default file in the authorize section:
if (Sql-Group == sql) {pap} else {update control { Auth-Type: = perl } }
This condition (written in the unlang language built into radius) includes the authentication type “perl” only for those users who are not members of the group of users authenticated through sql.
In the file / etc / freeradius / modules / perl, modify the following directive:
perl { module = <path to linotp_radius.pm file>
In the file / etc / freeradius / sites-enabled / default, modify the “authenticate” section as follows:
authenticate { perl
4.1.3. Creating a linotp_radius.pm connector file
Create a linotp_radius.pm file with the following contents:
#________START______
use strict;
use LWP;
use LWP::UserAgent::Determined;
# use ...
# This is very important ! Without this script will not get the filled hashesh from main.
use vars qw(%RAD_REQUEST %RAD_REPLY %RAD_CHECK $URL);
use Data::Dumper;
$ENV{'PERL_LWP_SSL_VERIFY_HOSTNAME'} = 0;
$URL = "https:///validate/simplecheck";
# This is hash wich hold original request from radius
#my %RAD_REQUEST;
# In this hash you add values that will be returned to NAS.
#my %RAD_REPLY;
#This is for check items
#my %RAD_CHECK;
#
# This the remapping of return values
#
use constant RLM_MODULE_REJECT=> 0;# /* immediately reject the request */
use constant RLM_MODULE_FAIL=> 1;# /* module failed, don't reply */
use constant RLM_MODULE_OK=> 2;# /* the module is OK, continue */
use constant RLM_MODULE_HANDLED=> 3;# /* the module handled the request, so stop. */
use constant RLM_MODULE_INVALID=> 4;# /* the module considers the request invalid. */
use constant RLM_MODULE_USERLOCK=> 5;# /* reject the request (user is locked out) */
use constant RLM_MODULE_NOTFOUND=> 6;# /* user not found */
use constant RLM_MODULE_NOOP=> 7;# /* module succeeded without doing anything */
use constant RLM_MODULE_UPDATED=> 8;# /* OK (pairs modified) */
use constant RLM_MODULE_NUMCODES=> 9;# /* How many return codes there are */
# Function to handle authorize
sub authorize {
# For debugging purposes only
# &log_request_attributes;
# Here's where your authorization code comes
# You can call another function from here:
&test_call;
return RLM_MODULE_OK;
}
# Function to handle authenticate
sub authenticate {
# For debugging purposes only
# &log_request_attributes;
my $ua = LWP::UserAgent->new();
my $req = HTTP::Request->new(GET => $URL . "?user=" .
$RAD_REQUEST{'User-Name'} . "&pass=" .
$RAD_REQUEST{'User-Password'} );
my $response = $ua->request( $req );
die "Error at $URL\n ", $response->status_line, "\n Aborting"
unless $response->is_success;
if($response->content =~ m/:\-\)/i) {
return RLM_MODULE_OK;
} else {
$RAD_REPLY{'Reply-Message'} = "LinOTP server denied access!";
return RLM_MODULE_REJECT;
}
}
# Function to handle preacct
sub preacct {
# For debugging purposes only
# &log_request_attributes;
return RLM_MODULE_OK;
}
# Function to handle accounting
sub accounting {
# For debugging purposes only
# &log_request_attributes;
# You can call another subroutine from here
&test_call;
return RLM_MODULE_OK;
}
# Function to handle checksimul
sub checksimul {
# For debugging purposes only
# &log_request_attributes;
return RLM_MODULE_OK;
}
# Function to handle pre_proxy
sub pre_proxy {
# For debugging purposes only
# &log_request_attributes;
return RLM_MODULE_OK;
}
# Function to handle post_proxy
sub post_proxy {
# For debugging purposes only
# &log_request_attributes;
return RLM_MODULE_OK;
}
# Function to handle post_auth
sub post_auth {
# For debugging purposes only
# &log_request_attributes;
return RLM_MODULE_OK;
}
# Function to handle xlat
sub xlat {
# For debugging purposes only
# &log_request_attributes;
# Loads some external perl and evaluate it
my ($filename,$a,$b,$c,$d) = @_;
&radiusd::radlog(1, "From xlat $filename ");
&radiusd::radlog(1,"From xlat $a $b $c $d ");
local *FH;
open FH, $filename or die "open '$filename' $!";
local($/) = undef;
my $sub = ;
close FH;
my $eval = qq{ sub handler{ $sub;} };
eval $eval;
eval {main->handler;};
}
# Function to handle detach
sub detach {
# For debugging purposes only
# &log_request_attributes;
# Do some logging.
&radiusd::radlog(0,"rlm_perl::Detaching. Reloading. Done.");
}
#
# Some functions that can be called from other functions
#
sub test_call {
# Some code goes here
}
sub log_request_attributes {
# This shouldn't be done in production environments!
# This is only meant for debugging!
for (keys %RAD_REQUEST) {
&radiusd::radlog(1, "RAD_REQUEST: $_ = $RAD_REQUEST{$_}");
}
}
1;
#___________END
This file was placed in the / etc / raddb / folder, but you can place it in another place, the main thing to remember is that the path to this file must be written in / etc / freeradius / modules / perl (mentioned above).
At this stage, radius should start successfully with the command / usr / sbin / radiusd –X and, when trying to connect to the radius client, send a request to the LinOTP server. This can be verified by connecting to the Client via ssh and simultaneously listening to traffic on the LinOTP server using TCPDUMP. You should see packets on port 443 TCP.
4.2. LinOTP Setup Procedure
LinOTP was configured according to the official documentation of the developer ( see the LinOTP document - QuickStart Guide ).
LinOTP itself does not have the functionality to create users, it can only read information about the KM (user accounts) of users from LDAP, MySQL or text files. This instruction implies that we use MySQL deployed on the same server as LinOTP as the base of the US.
4.2.1. Formation of the base of user ultrasound
When creating a useridresolver (this is just an indication, a kind of “connector” to the KM database, from where LinOTP will take user data) during the initial initialization of the LinOTP server, you need to specify the password field in the mapping of the SQL table with user data “password”. This password is needed only for user access to the self service portal. If we do not plan to use this portal, then this can be omitted.
If this was not done at the initialization stage, then you can add the acc. column to the table. Example:
ALTER TABLE usertable ADD password VARCHAR (200);
Next, generate a password hash for the user with the command:
/opt/LINOTP/bin/linotp-create-sqlidresolver-user -u <имя пользователя> -i <его поле id в базе> -p <желаемый пароль>
At the output, we get a line of the form:
user7; 2; {SSHA512} 3QWyB5u2TkPLDc2mjIJxtcCLOVHSjLS / MyYjPGNIENchm5riFHF3M9CW7csaxADAFml8WkH / Whd1A047nUAaeWk2dFZod1ZhYzN3cTRNUkpRdlhrLmVsdFVSeUNIaUdWVEZVT1ZycW5BSlVXajVHbHVvckhHSmNYOWp5M3FQLi8 =;
All that is highlighted in yellow is the received password hash. Next, add it to the corresponding field of the table:
update usertable set password = <hash obtained with> where user = 'user7';
In my example, the useridresolver configuration looks like this:

Figure 1 - Useridresolver configuration example
Special attention should be paid to the "Attribute mapping" field, which is not fully visible in the figure. Here are its contents:
{"userid": "id", "username": "user", "email": "mail", "surname": "sn", "givenname": "givenName", "password": "password"}
And below is the MySQL table (located on the LinOTP server), in which UZ users are stored and with which mapping is configured:
mysql> select * from usertable; + ---- + ------- + ------------------------ + -------- + - --------- + ----------- + | id | user | mail | sn | givenName | password | + ---- + ------- + ------------------------ + -------- + - --------- + ----------- + | 1 | user6 | user6@localdomain.mail | ivan | petrov | <hash> | | 2 | user7 | user7@localdomain.mail | sergey | fedorov | <hash> | + ---- + ------- + ------------------------ + -------- + - --------- + ----------- +
I suppose additional comments are unnecessary. The "userid" field in useridresolver is mapped into the "id" field in the MySQL database. The username field in useridresolver is the user field in the MySQL database, etc.
And now, in the end, what we see in the LinOTP interface by going to the “User view” tab:

Figure 2 - Displaying users in the LinOTP interface
4.2.2. Configure LinOTP Policies
Policies are a key element of LinOTP and allow you to flexibly configure authentication options, types and restrictions on the number of tokens, etc.
The following set of policies will be sufficient to demonstrate the operation of LinOTP:

Figure 3 - Token Assignment Policies
This policy defines the maximum number of tokens that can be issued to the user - the “maxtoken” parameter.

Figure 4 - Self-service portal policies
This policy regulates the types of tokens and operations on them that users can independently perform through the self-service portal.

Figure 5 - System Policies
The necessary permission to read and modify system settings by the administrator (it makes sense to set additional settings in scope = system to limit administrators).

Figure 6 - Authentication policies
This policy requires the entry of OTP-PIN and OTP during authentication on the target system (Client). The otppin = 0 parameter is responsible for this. When set to “2”, authentication will only pass through OTP. When it is set to “1”, authentication will take place using the combination User Password + OTP (the password is the same as for access to the self-service portal).
Example: parameter otppin = 0; user user3 has a token with OTP-PIN = 123456 and generated OTP = 234567. In this case, the password of user3 when accessing via ssh to the Client will look like
4.2.3. Creating Tokens for Users
After LinOTP is connected to the user KD database (useridresolver is configured) and a minimum set of policies is created, you can proceed to create tokens for users.
Go to the “User view” tab, select the user for which you want to create a token. In the menu on the left, click the Enroll button. A window opens with the settings of the new token.

Figure 8 - Creating a token
Token type - token type. Many different tokens are supported. To support google authenticator (which we will use in this example) select “HMAC time based”. This type of token will generate one-time passwords (OTP) that are tied to the current time (sometimes called TOTR).From here follows the requirement of time synchronization on all our machines - RADIUS Server, LinOTP Server and Client.
Token Seed - you can ask to generate a random sequence (which was done in this example) - "generate random seed" or set your own "enter seed".
Token settings - to support Google authenticator, simply check the box “Google authenticator compliant”, otherwise you can set the length of the OTP token generated by the OTP token “OTP digits”, the algorithm used is the “Hash algorithm”, the time window in which this OTP will act "Time step".
An arbitrary description of the token - “Description” - can be left blank at all, it does not affect anything.
Token pin- pin code of this token. At least 6 digits for Google authenticator.
After entering all the necessary data, click the "Enroll" button. Token created.
You will see a window with information about the token: the QR code of the token to be read by the Google Authenticator application installed on your mobile device and a line containing the secret needed, for example, to import the token into the GAUTH software client running on Windows.
Copy the line of the form "otpauth: // totp / TOTP ...." at the bottom of the window and save. It will be needed at the stage of configuring the OTP client on Windows.

Figure 9 - Created token
After that, the window can be closed. This token will appear in the list of tokens on the “Token view” tab in the LinOTP interface.
4.3. Customer setup procedure
4.3.1. Installing the necessary software
To support Client authentication through RADIUS, you must install the libpam-radius-auth package on the Client (10.1.0.113 in the example). This is also done using the yum manager.
4.3.2 Configuring Client Authentication via RADIUS
You must configure authentication through the radius server. To do this, edit the line “other-server other-secret 3” in the file /etc/pam_radius_auth.conf. Replace “other server” with the IP address of the RADIUS Server, i.e. 10.1.0.122, and “other-secret” to the secret specified in section 4.1.2, i.e. "Testing123". The result is the following:
10.1.0.122 testing123 3
Edit the file /etc/pam.d/sshd. Add the line to it:
sufficient /lib/security/pam_radius_auth.so
above the lines
# Standard Un * x authentication. @include common-auth
It is also necessary to create a user account on the Client with the same name as the user on LinOTP for which the token was created. In this example, user7.
useradd –m –d / home / user7 user7
4.3.3. Workstation setup procedure
On the workstation you need to install an OTP generator, for example, GAUTH. You can get this software here. It is installed from the installer file contained in the downloaded archive. The installation process is trivial.
After installation, it is recommended to correct the key in the registry as described below. Without this edit, the software will work, but at startup it will give an error that does not please the eye. This is a GAUTH bug described in the FAQ for this software.
You must add a parameter of type String named key to the following branch:
HKEY_CURRENT_USER \ Software \ GoogleAuth \ GoogleAuth \ 1.0.0.
After that, we launch the GAUTH client through START - programs - Google Auth. The corresponding icon will appear in the tray. Click on it with the right mouse button and select “Modify key”. In the window that opens, insert the secret from the string stored during the creation of the token.
Example line: otpauth: // totp / TOTP00013B2A? Secret = APY ***** JH & counter = 0
In this case, the secret to be inserted will be:
APY ***** JH
Press “Save key”.

Figure 10 - Importing a secret into GAUTH
Now, when you hover the mouse over the tray icon, a six-digit OTP will appear and in parentheses next to it the time that this OTP will be valid.

Figure 11 - Generated OTP
5. Verification of authentication using OTP
We launch SSH “putty” on the client workstation. We are connected to the Client (in the example - 10.1.0.113). Specify the name of the user for whom the token was created (in the example, user7), enter the password
If everything is done correctly, the clocks on all servers and the workstation are synchronized, then user7 user access must be authorized.