#  -*- text -*-
#
#
#  $Id: b265627914ac6a214d4714bc6adc33635c772b9f $

#######################################################################
#
#  = DPSK authentication Module
#
#  Calculate dynamic PSKs
#
#  This module needs the following attributes as input:
#
#	* control.Pre-Shared-Key -  the PSK for the user
#	* User-Name - the supplicant MAC in hex format, e.g. "abcdef012345"
#	* Called-Station-MAC - the AP MAC in binary
#	  this attribute is set by the "rewrite_called_station_id" policy.
#	* FreeRADIUS-802.1X-Anonce - from the AP
#	* FreeRADIUS-802.1X-EAPoL-Key-Msg - from the AP
#
#  The attributes where data is found can be amened using
#  configuration options below, e.g. if you need to use different VSAs
#  for the anonce and EAPoL key msg.
#
#  Note that you MUST run the "rewrite_called_station_id" policy
#  before calling this module.
#
#  That policy MUST also create the Called-Station-MAC attribute.
#
#  Then place the following configuration into the "recv
#  Access-Request" section:
#
#	```
#	recv Access-Request {
#		...
#		rewrite_called_station_id
#
#		dpsk
#		if (updated) {
#			control.PSK-Identity := "bob"
#			control.Pre-Shared-Key := "this-is-super-secret"
#		}
#	}
#	```
#
#  Alternatively, you can read control.PSK-Identity and
#  control.Pre-Shared-Key from a database. Just do that before running
#  the "authenticate" section. You can do the database lookup after
#  running the "dpsk" module in the "recv Access-Request" section.
#
#  The database should look up the User-Name (i.e. MAC), and then set
#  control,PSK-Identity and control.Pre-Shared-Key. In general, it is
#  easiest to just set PSK-Identity to be same as the User-Name.
#
#  Then create an "authenticate dpsk" section which calls the "dpsk"
#  module:
#
#	```
#	authenticate dpsk {
#		dpsk
#		if (updated) {
#			... cache reply.Pre-Shared-Key
#			... cache reply.PSK-Identity
#		}
#	}
#	```
#
#  In the "authenticate" section, the module will return
#
#  * noop	if there are no DPSK attributes in the request
#
#  * fail	if there are DPSK attributes, but it is unable to get the
#		information necessary to check the DPSK data.
#
#  * reject 	if there are DPSK attributes, but they do not match any
#		given PSK.
#
#  * ok		if there are DPSK attributes, and they match a known PSK.
#
#  * updated	if there are DPSK attributes which match a PSK, and the
#		PSK was read from 'filename'.
#
#		It also updates the attributes reply.Pre-Shared-Key
#		with the found PSK, along with reply.PSK-Identity
#		with the found identity.  The attributes populated
#		can be configured below.
#
#		You can then check the return code for "updated", and
#		write those attributes into a database.  This step
#		ensures that 'filename' is read only as a last resort.
#		Since the file is read from top to bottom for every
#		packet, this process is much slower than using a
#		database which is keyed to the PSK Identity.
#
#  We STRONGLY RECOMMEND THAT NO ONE USE THIS MODULE.
#
#  While it works, it needs to use a brute-force method to match MAC
#  to PSK. That process is extremely slow, and scales very poorly.
#
#  i.e. if you have 10 PSKs, it's not too bad. If you have 10,000
#  PSKs, then the module can consume 100% of CPU trying to brute-force
#  every PSK.
#
#  This is a limitation of how DPSK works. There is no way to make it
#  better. The only thing we've done is to add a cache which can help
#  to minimise the amount of brute-force attempts.
#
#
#  == Configuration Settings
#
dpsk {
	#
	#  cache_size::
	#
	#  The maximum number of PSK entries to cache
	#
	#  The cache is keyed by (supplicant MAC + SSID)
	#
	#  The cache entry is the PSK-Identity and Pre-Shared-Key, and/or the
	#  PMK which are used to verify the information in the
	#  Access-Request.
	#
	#  Caching entries can help, even when using a database. It is very
	#  slow to calculate the PMK from the PSK.
	#
	cache_size = 1024

	#
	#  cache_lifetime::
	#
	#  How long cache entries are valid for
	#
	cache_lifetime = 24h

	#
	#  filename::
	#
	#
	#  PSKs can also be stored in a CSV file. The format of the file is:
	#
	#	```
	#	identity,psk,mac
	#	```
	#
	#  If there are commas in a field, then the field can be double
	#  quoted: "psk".
	#
	#  The mac field is optional. If it exists, then that PSK will be
	#  used. It is highly recommended that the MAC *not* be placed into
	#  the CSV file. Instead, the MAC and PSK should be placed into a
	#  database. The server can then be configured to look up the MAC in
	#  the database, which returns the PSK. That way this module will
	#  only ever check one PSK, which is fast.
	#
	#  i.e. the CSV file should only contain the small number of PSKs
	#  where you do not yet know the MAC. As soon as you know the MAC,
	#  you should put the MAC and PSK into a database, and then remove
	#  the MAC and PSK from the CSV file.
	#
	#  NOTE: the file is opened and read from top to bottom for every new
	#  request which comes in. This process can be very slow!
	#
	#  However, opening the file for every new request means that the
	#  server does not have to be reloaded when the file changes.
	#  Instead, the file can be generated, and then moved into place
	#  atomically:
	#
	#	```
	# 	create csv file > psk.csv.new
	#	mv psk.csv.new psk.csv
	#	```
	#
	#  Any process which writes a new "psk.csv" file MUST NOT write to
	#  the file directly, as that will cause the dpsk module to read
	#  partial entries and fail. Instead, use "mv" to atomically
	#  overwrite the old file with a new one.
	#
	#  Both "cache_size" and "filename" can be configured at the same
	#  time, which is recommended. When an entry in the file is found,
	#  the identity, PSK, and MAC are saved in the cache.
	#
	#  If a cache entry is found, then the filename is NOT read.
	#
	#  The resulting combination of features means that the module should
	#  be as fast as possible, given the limitations of DPSK.
	#
	#  NOTE: Tests show that the module can do ~100K PSK / DPSK checks
	#  per second. This means that if you have 10,000 users and 10
	#  packets a second, the system will be 100% busy checking PSKs.
	#  Similarly, of you have 100K DPSKs in the file, the one packet will
	#  take 1 second of CPU time to verify!
	#
	#  As a result, the DPSK functionality scales poorly. It should be
	#  used only with a small number of PSKs (100s perhaps), and only at
	#  low packet rates. If the server is getting 1000 packets per
	#  second, then it can only handle 100 PSKs before running out of
	#  CPU.
	#
	#  Using the cache will help substantially. But the cache is only in
	#  memory, which means that all cache entries are lost when the
	#  server restarts. As a result, the combination of number of PSKs
	#  and packet rates should be kept as low as possible.
	#
	#  The filename is dynamically expanded, so it can reference other
	#  attributes. This expansion lets you split up DPSK files by
	#  location, which can drastically reduce the overall search space,
	#  and thus the CPU requirements.
	#
#	filename = "${modconfdir}/${.:name}/psk.csv"

	#
	#  pre_shared_key::
	#
	#  Attribute containing the PSK for the user, as retrieved from a
	#  data store
	#
#	pre_shared_key = control.Pre-Shared-Key

	#
	#  psk_identity::
	#
	#  Attribute containing an identity name for the PSK, as retrieved
	#  from a data store.
	#
#	psk_identity = control.PSK-Identity

	#
	#  pairwise_master_key::
	#
	#  Attribute containing the pairwise master key for the user. This is
	#  used in preference to the PSK if set.
	#
#	pairwise_master_key = control.Pairwise-Master-Key

	#
	#  ssid::
	#
	#  Attribute containing the SSID of the network the client is
	#  connecting to.
	#
#	ssid = Called-Station-SSID

	#
	#  anonce::
	#
	#  Attribute containing the nonce from the AP.
	#
#	anonce = FreeRADIUS-EV5.802_1X-EAPoL-Anonce

	#
	#  key_msg:
	#
	#  Attribute containing the EAPoL key msg from the AP.
	#
#	key_msg = FreeRADIUS-EV5.802_1X-EAPoL-Key-Msg

	#
	#  username::
	#
	#  Attribute containing the supplicant MAC address in hex format,
	#  e.g. "abcdef012345".
	#
#	username = User-name

	#
	#  called_station::
	#
	#  Attribute containing the AP MAC in binary
	#
	#  Typically this is extracted from Called-Station-Id using a
	#  modified version of the "rewrite_called_station_id" policy.
	#
#	called_station = Called-Station-MAC

	#
	#  pre_shared_key_attr::
	#
	#  Attribute into which the matched pre shared key will be written.
	#
	#  When used with hostapd, this should be reply.Tunnel-Password
	#
#	pre_shared_key_attr = reply.Pre-Shared-Key

	#
	#  psk_identity_attr::
	#
	#  Attribute into which the matched PSK identity will be written.
	#
#	psk_identity_attr = reply.PSK-Identity
}
