Implement Radsec Client for QA Simulation

In the earlier posts we have learnt about Radsec proxy and we have also learnt about Radius PAP authentication. We do have so many simulators to simulate the traffic for classic radius protocols. But we don’t have simulators to test the load or functional tests on radsec based radius servers yet.

The reason to send radius frames in a tls tunnel is to provide the security as the classic radius protocol uses weak md5 authenticators, and most of the radius attributes are plain text which is a privacy concern.

In this post we will learn about how to create a radsec client for TLS1.3 tunnels and test the simulated packets for QA testing.

Go through the below links to understand about Radsec proxy and Radius PAP implementation.

Radius PAP Authentication

Radsec Proxy

In this post we will learn about how to implement a radsec client to simulate radius access requests, we have to follow the same procedure for accounting packets as well.

Implementing a Radsec TLS1.3 tunnel

The below setup is used to simulate the traffic from radsec client. In this post we will create a radsec client for TLS1.3.

  1. We need to create client and server certificate and place them in both radsec client and radius server properly. (Use openssl to create client and server certificates)
  2. Create a free radius server on any linux machine and configure it to process radsec requests.
    1. Free radius will be waiting at 2083 port to process radsec client requests.
  3. Write a python code to simulate the radius access requests using sockets.
  4. Send the traffic to the Radius server waiting at 2083 port from radsec client.
Figure 1 : Setup Used

Step 1 and Step 2

Create client and server certificates using openssl and Start the radius server at 2083.

Once we start the radius server we see the below output on radius server, radius server is waiting to process the TLS requests.

Figure 2 : Radius Server waiting at port 2083

Step 3 (Radsec Implementation in Python)

Below is the python code that I have written to send the radius frames in TLS1.3 tunnel. Go through the code properly and create the radsec client.

Go through the below PAP authentication to create the RADIUS attributes properly.

Radius-PAP Link

Below is the code used to send the radius access requests from radsec client.

Figure 4 : Starting TLS 1.3 session from Client

The important parameters for the radsec client program are.

  1. Generating client and server certificates
  2. Username
  3. Generation of Password
    1. We have to generate the password based on the RFC-2865.
    2. I am using the password that I have used in my setup (Check the program)
  4. Authenticator (In the Program currently we are using the same authenticator for simulation, in real time scenario every time new authenticators will get generated, Understand that this authenticator not md5 authenticator it is a plain text that is randomly generated)
    1. If we are changing the authenticator for every request, we have to properly construct the User-Password as well because Generating the User-Password will take this authenticator as an input.
  5. Shared Secret is “radsec” as per RFC-6614
  6. Radius Attributes
  7. Generation of md5 for the data
  8. Send the Radius Frame in TLS1.3 tunnel
  9. Total Clients that user wants to simulate (Currently we have set 3 in the program)
  10. Send the Radius Data in the TLS1.3 tunnel using client socket
TOTAL_CLIENTS=3

CLI="0"
CLIENTS=list(range(TOTAL_CLIENTS))
for i in range(TOTAL_CLIENTS):
  CLIENTS[i]=CLI+CLI+str(CLIENTS[i]).zfill(10)

  print(CLIENTS[i])


for i in range(TOTAL_CLIENTS):

  AVP_HEADER="010200c2"
  AVP_AUTHENTICATOR="1abb68ddf899c17cb0f8fb107d7fe0fb"


  USERNAME="praneeth1234567891234567891234567891234"

  ############Here password is "praneethpassword", we have to generate this password  based on the RFC and send it as a hex#########
  UserPassword_Type_Length="0212"
  UserPassword="e1756a126ebe09d895b6431ba1014198"
  ##############################

  ##############radsec in hex (Shared Secret is "radsec" as per RFC)################

  SHARED_SECRET='\x72\x61\x64\x73\x65\x63'
  TYPE="01"
  LENGTH="29"
  USERNAME=USERNAME.encode()
  USERNAME=binascii.hexlify(USERNAME)
  USERNAME = USERNAME.decode("utf-8")

  print(USERNAME)
  AVP1=TYPE+LENGTH+USERNAME
  AVP2="04060a2399be0506000000000606000000010c06000004e2"
  print("AVP1+AVP2")
  print(AVP_HEADER+AVP_AUTHENTICATOR+AVP1+AVP2)

  AVP_CALLING_STATION_ID="1f13"
  print(CLIENTS[i])
  CALLING_STATION_ID='-'.join(CLIENTS[i][j:j+2] for j in range(0,12,2))
  print(CALLING_STATION_ID)
  CALLING_STATION_ID=CALLING_STATION_ID.encode('utf-8').hex()
  print("CALLING STATION ID")
  print(CALLING_STATION_ID)
  print("---------------------------->")


  AVP_CALLED_STATION_ID="1e13"
  #CALLED_STATION_ID='-'.join(CLIENTS[i][j:j+2] for j in range(0,12,2))
  CALLED_STATION_ID="30302d31302d46332d32362d43302d3043"
  print (CALLED_STATION_ID)
  print("================")
  CALLED_STATION_ID=AVP_CALLED_STATION_ID+CALLED_STATION_ID
  print(CALLED_STATION_ID)

  AVP_CONNECT_INFO="4d17434f4e4e45435420556e6b6f6e776e20526164696f"
  AVP_FRAMED_IP_ADDRESS="08060a219e1b"
  AVP_NAS_PORT_TYPE="3d0600000013"
  AuthType="5012"

  EMPTY_AUTH="00000000000000000000000000000000"

  RADIUS=AVP_HEADER+AVP_AUTHENTICATOR+AVP1+AVP2+AVP_CALLING_STATION_ID+CALLING_STATION_ID+CALLED_STATION_ID+AVP_CONNECT_INFO+A                                                               VP_FRAMED_IP_ADDRESS+AVP_NAS_PORT_TYPE+UserPassword_Type_Length+UserPassword+AuthType+EMPTY_AUTH

  RADIUS1=AVP_HEADER+AVP_AUTHENTICATOR+AVP1+AVP2+AVP_CALLING_STATION_ID+CALLING_STATION_ID+CALLED_STATION_ID+AVP_CONNECT_INFO+                                                               AVP_FRAMED_IP_ADDRESS+AVP_NAS_PORT_TYPE+UserPassword_Type_Length+UserPassword+AuthType

  RADIUS=bytes.fromhex(RADIUS)
  SHARED_SECRET=SHARED_SECRET.encode('utf-8')

  digest = hmac.new(SHARED_SECRET, RADIUS, hashlib.md5)
  MD5=digest.hexdigest().encode('utf-8')
  MD5=MD5.decode('utf-8')
  print(RADIUS1)
  print("============MD5")
  print(MD5)
  AVP_AUTHENTICATOR=MD5
  print (AVP_AUTHENTICATOR)
  FINAL_HEX=RADIUS1+MD5
  print(FINAL_HEX)



  client.sendall(bytes.fromhex(FINAL_HEX))
  time.sleep(0.1)
  AVP_HEADER=""
  AVP_AUTHENTICATOR=""
  USERNAME=""
  PASSWORD=""
  TYPE=""
  LENGTH=""
  USERNAME=""
  AVP1=""
  AVP2=""
  AVP_CALLING_STATION_ID=""
  CALLING_STATION_ID=""
  AVP_CALLED_STATION_ID=""
  CALLED_STATION_ID=""
  AVP_CONNECT_INFO=""
  AVP_FRAMED_IP_ADDRESS=""
  AVP_NAS_PORT_TYPE=""
  AuthType=""
  UserPassword=""
  EMPTY_AUTH=""
  RADIUS1=""
  RADIUS=""
  SHARED_SECRET=""
  digest = ""
  MD5=""
  AVP_AUTHENTICATOR=""
  FINAL_HEX=""
  time.sleep(1)


############


#############

client.close()
sock.close()

Now we will execute the python program and observe the sniffer data that is being sent from radsec client.

we have simulated the traffic for 3 clients and we will be able to see the below packets in the wireshark also observe that RADIUS packets are being sent in TLS1.3 tunnel.

Figure 4 : Wireshark Capture for RADIUS frame Observe that 3 Radius Access Requests are being sent, each request has Radius Access Accept Response

Observe that Radius frames are being sent in TLS tunnels. So all the attributes are encrypted.

Observe the below example log when radius server receive these TLS tunnels. We can see that RADIUS server has decrypted these tunnels and it is replying with Radius Access Accept as well.

<strong>(0) tls_recv: Access-Request packet from host XX.XX.XX.XX port 35818, id=2, </strong>length=194
Waking up in 0.3 seconds.
Thread 2 got semaphore
Thread 2 handling request 10, (3 handled so far)
(10) Received Access-Request Id 2 from XX.XX.XX.XX:35818 to 0.0.0.0:2083 length 194
(10)   <strong>User-Name = "praneeth1234567891234567891234567891234"</strong>
(10)   NAS-IP-Address = xx.xx.xx.xx
(10)   NAS-Port = 0
(10)   Service-Type = Login-User
(10)   Framed-MTU = 1250
(10)   Calling-Station-Id = "00-00-00-00-00-00"
(10)   Called-Station-Id = "11-22-22-11-11-11"
(10)   Connect-Info = "CONNECT Unkonwn Radio"
(10)   Framed-IP-Address = xx.xx.xx.xx
(10)   NAS-Port-Type = Wireless-802.11
(10)   <strong>User-Password = "praneethpassword"</strong>
(10)   <strong>Message-Authenticator = 0xae7bdf1be6ffb15b05a0beb037fc2a26</strong>
(10) # Executing section authorize from file /usr/local/etc/raddb/sites-enabled/default
(10)   authorize {
(10)     policy filter_username {
(10)       if (&User-Name) {
(10)       if (&User-Name)  -> TRUE
(10)       if (&User-Name)  {
(10)         if (&User-Name =~ / /) {
(10)         if (&User-Name =~ / /)  -> FALSE
(10)         if (&User-Name =~ /@[^@]*@/ ) {
(10)         if (&User-Name =~ /@[^@]*@/ )  -> FALSE
(10)         if (&User-Name =~ /\.\./ ) {
(10)         if (&User-Name =~ /\.\./ )  -> FALSE
(10)         if ((&User-Name =~ /@/) && (&User-Name !~ /@(.+)\.(.+)$/))  {
(10)         if ((&User-Name =~ /@/) && (&User-Name !~ /@(.+)\.(.+)$/))   -> FALSE
(10)         if (&User-Name =~ /\.$/)  {
(10)         if (&User-Name =~ /\.$/)   -> FALSE
(10)         if (&User-Name =~ /@\./)  {
(10)         if (&User-Name =~ /@\./)   -> FALSE
(10)       } # if (&User-Name)  = notfound
(10)     } # policy filter_username = notfound
(10)     [preprocess] = ok
(10)     [chap] = noop
(10)     [mschap] = noop
(10)     [digest] = noop
(10) suffix: Checking for suffix after "@"
(10) suffix: No '@' in User-Name = "praneeth1234567891234567891234567891234", looking up realm NULL
(10) suffix: No such realm "NULL"
(10)     [suffix] = noop
(10) eap: No EAP-Message, not doing EAP
(10)     [eap] = noop
(10) files: users: Matched entry praneeth1234567891234567891234567891234 at line 88
(10)     [files] = ok
(10)     [expiration] = noop
(10)     [logintime] = noop
(10)     [pap] = updated
(10)   } # authorize = updated
(10) Found Auth-Type = PAP
(10) # Executing group from file /usr/local/etc/raddb/sites-enabled/default
(10)   Auth-Type PAP {
(10) pap: Login attempt with password
(10) pap: Comparing with "known good" Cleartext-Password
(10) pap: User authenticated successfully
(10)     [pap] = ok
(10)   } # Auth-Type PAP = ok
(10) # Executing section post-auth from file /usr/local/etc/raddb/sites-enabled/default
(10)   post-auth {
(10)     [exec] = noop
(10)     policy remove_reply_message_if_eap {
(10)       if (&reply:EAP-Message && &reply:Reply-Message) {
(10)       if (&reply:EAP-Message && &reply:Reply-Message)  -> FALSE
(10)       else {
(10)         [noop] = noop
(10)       } # else = noop
(10)     } # policy remove_reply_message_if_eap = noop
(10)     update reply {
(10)       Acct-Interim-Interval = 600
(10)     } # update reply = noop
(10)   } # post-auth = noop
<strong>(10) Sent Access-Accept Id 2 from 0.0.0.0:2083 to xx.xx.xx.xx:35818 length 0</strong>
(10)   Acct-Interim-Interval = 600
<strong>(0) (TLS) send TLS 1.3 Handshake, Finished</strong>
(10) Finished request
Thread 2 waiting to be assigned a request
Waking up in 4.6 seconds.
(0) (TLS) recv TLS 1.3 Handshake, Finished