I recently began integrating IMS Authentication functions into PyHSS, and thought I’d share my notes / research into the authentication used by IMS networks & served by a IMS capable HSS.
There’s very little useful info online on AKAv1-MD5 algorithm, but it’s actually fairly simple to understand.
Authentication and Key Agreement (AKA) is a method for authentication and key distribution in a EUTRAN network. AKA is challenge-response based using symmetric cryptography. AKA runs on the ISIM function of a USIM card.
The Nonce field is the Base64 encoded version of the RAND value and concatenated with the AUTN token from our AKA response. (Often called the Authentication Vectors).
That’s it!
It’s put in the SIP 401 response by the S-CSCF and sent to the UE. (Note, the Cyperhing Key & Integrity Keys are removed by the P-CSCF and used for IPsec SA establishment.
In our last post we went over all the theory, now let’s get started implementing these security features.
Kamailio’s core is a basis to start from, but many common needs are covered by special modules that we need to load to handle certain scenarios.
In order to authenticate traffic, we’ll need to have a source of authentication info (auth_db module) and authorization (permissions module). For this we’ll be using MySQL (although you could use text files, PostGres, etc) to store both sets of data, and using phpMyAdmin to make everything a bit more accessible.
We’ll build upon our last tutorial but we’ll install MySQL and phpMyAdmin:
apt-get install mysql-server phpmyadmin
After following along the install prompts we’ll setup our database connection.
Each of the two modules we’ll be using (auth_db and permissions) require a database source. In each we could specify our database info but instead we’ll create a new variable and fill it with our database info so we only need to update it in one place.
Let’s setup a new MySQL user for our Kamailio instance (in production you’d only grant privileges on the DB we’re going to use):
mysql> CREATE USER 'kamailio'@'localhost' IDENTIFIED BY 'my5yhtY7zPJzV8vu';
mysql> GRANT ALL PRIVILEGES ON * . * TO 'kamailio'@'localhost';
mysql> FLUSH PRIVILEGES;
We’ll now use the kamdbctl tool, bundled with Kamailio to create the database tables for us:
kamdbctl create
You’ll be asked for the root password for MySQL and if you want some optional tables (we don’t just yet) and presto, all the tables are now created!
We can now login with phpMyAdmin and see the tables we just added:
Adding Database Connectivity to Kamailio
The example config is designed to be nice and modular, so by simply un-commenting the WITH_MYSQL variable and setting the DBURL variable we’ll have set our MySQL database up for the modules we need.
We’ll change:
# *** To enable mysql:
# - define WITH_MYSQL
#
To:
# *** To enable mysql:
#!define WITH_MYSQL
#
So now we’ve defined an variabled named WITH_MYSQL
You’ll see later in the config there’s conditional (if statement) that looks at if WITH_MYSQL has been defined:
# *** Value defines - IDs used later in config
#!ifdef WITH_MYSQL
# - database URL - used to connect to database server by modules such
# as: auth_db, acc, usrloc, a.s.o.
#!ifndef DBURL
#!define DBURL "mysql://kamailio:my5yhtY7zPJzV8vu@localhost/kamailio"
#!endif
#!endif
We’ll change the !define DBURL to include the password in the Database connection string,
It breaks up like this:
mysql:// is the database type (you could use text:// for text based DB or pgsql:// for Postgres)
First part is the username:password@host/table
In this case, our username is kamailio, our password is the one we created (my5yhtY7zPJzV8vu), our host is localhost and our table is kamailio
Adding IP Authentication & Challenge / Response Auth
Like we defined the #!define WITH_MYSQL we’ll define two other blocks to add Authentication:
# *** To enable authentication execute:
# - enable mysql
# - define WITH_AUTH
# - add users using 'kamctl'
#
# *** To enable IP authentication execute:
# - enable mysql
# - enable authentication
# - define WITH_IPAUTH
We’ll change to:
# *** To enable authentication execute:
# - enable mysql
#!define WITH_AUTH
# - add users using 'kamctl'
#
# *** To enable IP authentication execute:
# - enable mysql
# - enable authentication
#!define WITH_IPAUTH
Adding Users & IP Addresses
Now we’ve gone and added these blocks to the code we’ll go about adding some users and IP addresses, to do this we’ll use the kamctl tool.
kamctl is another tool (like kamcmd) used to modify / change the Kamailio config, it’s a shell wrapper for managing Kamailio database among other things.
We do need to setup a few things to get the kamctl working, and to do that we’ve got to edit the kamctlrtc file in the /etc/kamailio directory, to include the details of the database we just setup.
Adding Users
Now we can get to work adding some users from the command line:
kamctl add 61312341234 supersecretpassword
Here we can see adding a user with the username 61312341234 and the password supersecretpassword. These will makeup the username and password we’ll have on our SIP endpoints. (We’ll make them match the phone numbers of our trunks to make the routing easier down the track)
We’ll add another user so we can make calls between users when we’re testing later too, we’ll add them using kamctl add USERNAME PASSWORD again.
Now if we have a look in the subscriber table in phpMyAdmin we can see the users we created:
Adding Carrier IPs
Next we’ll add the IP Address our carrier is going to send us calls from, so we can allow them call our users.
Kamailio’s permissions module relies on address groups – This means we could have address group (we’ll just use the number 200), which we decide is for our carriers, and add all our carrier’s IP addresses in here, without needing to put each of them into the config.
We could create another address group 300 and put the subnets of our offices in there and only allow REGISTER messages from IPs in those groups.
We’ll go through how to use the groups later on, but for now we’ll just add a new IP address to group 200, with a /32 subnet mask, on port 5060 with the name “Carrier IP address” so we know what it is.
kamctl address add 200 10.0.1.102 32 5060 "Carrier IP address"
Now if we have a look in the permissions table in Kamailio you’ll see the info we just added.
By now you’ve probably caught on to the fact kamctl is just a command line tool to add data to the database, but it’s useful none the less.
The final step is to reload the permissions address table – This is done when we restart but it’s good to know you can update it without a restart.
kamctl address reload
Adding authentication / authorization to REGISTER messages
Now let’s actually put this all into practice, the first thing we’ll do is call the REQINIT route to make sure our traffic if (reasonably) clean and take care of the basics.
request_route {
route(REQINIT); #Call REQINIT (Request Initial) route to filter out the worst of the bad traffic and take care of the basics.
Next we’ll setup how we handle REGISTER traffic, adding an auth challenge and only saving the location if the UA successfully responds to the challenge.
if(method=="REGISTER"){ # authenticate requests
if (!auth_check("$fd", "subscriber", "1")) { #If credentials don't match what we have in Subscriber table
auth_challenge("$fd", "0"); #Send an Auth Challenge
exit; #Stop processing
}
save("location"); #Save the location as an AoR
exit; #Stop processing
}
This may all seem a bit backward, but this is an example from the Kamailio devs we’re using, so it shows “the right way” of doing this, let’s break it down.
We know our if(method==”REGISTER”) means we’ll only do this check for REGISTER messages.
The auth_check checks to see if the presented credentials in our auth header are correct. For the first INVITE we don’t have an auth header so it’s not correct, and if we have an invalid password it’s also not correct. You’ll notice it’s prefixed with a if(!auth_check) meaning this if conditional block is only called if it fails the authentication check, and if we do fail the authentication check we issue an auth_challenge to generate a 401 response with an authentication header and send it back to the UA. Then we exit (stop processing).
As the above had an exit we know we’ll only hit blocks below if our credentials are correct, otherwise we’d just get the auth_challenge and exit from the auth_check block.
So as we know these credentials are correct we’ll save the location as an address on record using the save(“location”) function and exit.
So that’s our REGISTER block now handling & requiring authentication, now after restarting Kamailio we can register SIP devices with the username and password we setup, but if we get the username or password wrong, we’ll get rejected.
We can add extra users using the kamctl add command we touched on earlier.
Authorising / Authenticating INVITE messages
INVITE messages are used to setup sessions (calls), so it’s important we secure this too. At this point we’re authenticating to REGISTER, but not create a call (INVITE).
First let’s add a simple check to see if the INVITE has come from the IP of one of the carriers we defined earlier.
For this we’ll use the allow_source_address() command to see if the source address matches what we defined earlier using kamctl address add to address group 200 in the MySQL database.
if(method=="INVITE"){
if(allow_source_address("200")){ #If from a Carrier IP
if(!lookup("location")){ #Try looking up location
sl_reply("404", "User not Registered"); #If looking up location fails reply with 404
exit; #And exit
}
t_relay(); #Relay traffic to endpoint
exit(); #Exit
}else{
sl_reply("403", "Nope. Don't know who you are");
}
}
So we’ve got a simple if for if the source address is in group 200.
Presto, this works for calls from carriers coming in to registered endpoints! We can get inbound calls.
Small catch is our users can’t dial each other any more, as their IP isn’t in address group 200, they just get the 403 “Don’t know who you are” response.
Now we could go and add the subnet where our users are located, but then there’d be no point in using passwords at all. But before we do this let’s create a new routing module, called INVITE to keep everything pretty.
At the very bottom of our config we’ll add
route[ONNETINVITE]{
if(!lookup("location")){ #Try looking up location
sl_reply("404", "User not Registered"); #If looking up location fails reply with 404
exit; #And exit
}
t_relay(); #Relay traffic to endpoint
exit(); #Exit
}
And then we’ll remove most of the code in our if(method==”INVITE”){ block and replace it with this:
if(method=="INVITE"){
if(allow_source_address("200")){ #If from a Carrier IP
route(ONNETINVITE); #Call INVITE handling bloc
}else{
sl_reply("403", "Nope. Don't know who you are");
}
}
Now we’ve made it so we just call ROUTE(INVITE);when we have an INVITE we’ve authenticated. This will save us a lot of extra code when we add our checks to see if the call is from a user we recognize, instead of running through the lookup(“location”) code and relying, we’ll just call route(ONNETINVITE); when we’re happy we know who they are and off we go.
if(method=="INVITE"){
if(allow_source_address("200")){ #If from a Carrier IP
route(ONNETINVITE); #Call INVITE handling bloc
}else{
if (!auth_check("$fd", "subscriber", "1")) { #If credentials don't match what we have in Subscriber table
auth_challenge("$fd", "0"); #Send an Auth Challenge
exit; #Stop processing
}
route(ONNETINVITE); #Call invite handling block
}
}
You may recognize the !auth_check blocks as the same code we used for authenticating REGISTER messages, we’re using it again as it’s the same auth mechanism.
If we pass it we call the route(ONNETINVITE);
If we look at the packet captures we can see our INVITE gets a 407 “Proxy Authentication required” response back from Kamailio.
And the UA then resends the INVITE with an authentication header with correct username and password and we’re on our way!
And that’s it! Phew.
In production we’d want to handle other types of messages that we’d also want to authenticate, we’ll talk about that further down the line, but keep in mind every feature you add what the security ramifications might be.
Next up we’ll use our new found sense of security to add the ability to call numbers off-net (on the PSTN) via a SIP provider!