A Common Lisp Kerberos (version 5) implementation.
This is an implementation of the Kerberos v5 authentication protocol in Common Lisp. The intention is to provide a robust, reliable and portable (across both Lisp implementations and host OSs) Kerberos authentication system. It has been developed/tested against the Windows KDC (i.e. active directory) running on SBCL under both Windows and Linux.
Kerberos is the de facto standard method of authentication over a network, notably in Microsoft Windows environments.
The basic principal of Kerberos is there is a trusted central authority which stores credentials (password equivalents) for each principal (user account). This is known as the Key Distribution Centre (KDC). A client can prove its identity to an application server by requesting a message from the KDC which is encrypted with the server's private key. Only the server (and the KDC) have the knowledge to decrypt this message, the client itself does not. The client forwards this message to the server, who decrypts it and examines the contents of the message. Inside it will be some proof (e.g. a recent timestamp) that the client is who they say they are.
In its simplest form, the Kerberos protocol consists of the following sequence of exchanges:
The details get more complicated, but that is the general idea.
Users should first "logon" by providing credentials and IP address of the KDC (Domain controller):
(logon-user "username@realm" "password" :kdc-address "10.1.1.1")
This modifies the global *CURRENT-USER*
variable. Alternatively you may rebind this variable
if you require a local change of user.
(with-current-user ((logon-user "username@realm" "Pasword" :kdc-address "10.1.1.1"))
body)
Services, which do not require initial authentication with the KDC, should use
(logon-service "service/host.name.com@realm" keylist)
where KEYLIST
is a list of keys as returned from either GENERATE-KEYLIST
or LOAD-KEYTAB
.
Kerberos authentication is then performed using the GSSAPI as provided by the glass package.
;; ---------- client --------
CL-USER> (logon-user "username@realm" "password" :kdc-address "10.1.1.1")
;; acquire a client credential structure for the current user
CL-USER> (defparameter *client-creds* (gss:acquire-credentials :kerberos "service/host.name.com@realm"))
*CLIENT-CREDS*
;; initialize a context and generate a token buffer to send to the server
CL-USER> (multiple-value-bind (context buffer) (gss:initialize-security-context *client-creds* :mutual t)
(defvar *client-context* context)
(defvar *buffer* buffer))
*BUFFER*
;; -------- on the server -----
CL-USER> (logon-service "service/host.name.com@realm" *keylist*)
;; acquire a crednetial structure for the current user
CL-USER> (defparameter *server-creds* (gss:acquire-credentials :kerberos nil))
*SERVER-CREDS*
;; accept the context and generate a response token (if required)
CL-USER> (multiple-value-bind (context buffer) (gss:accept-security-context *server-creds* *buffer*)
(defvar *server-context* context)
(defvar *response-buffer* buffer))
*RESPONSE-BUFFER*
;; -------- client -----------
;; pass the token back to the client so it can validate the server
CL-USER> (gss:initialize-security-context *client-context* :buffer *response-buffer*)
To discover the location of your KDC on the network, you should issue a DNS SRV query, e.g. using dragons:
CL-USER> (dragons:query (dragons:question "_kerberos._tcp.my.domain.com" :srv))
(#S(DRAGONS::RR
:NAME "_kerberos._tcp.my.domain.com"
:TYPE :SRV
:CLASS :IN
:TTL 600
:RDATA (:PRIORITY 0 :WEIGHT 100 :PORT 88 :TARGET
"myDC.my.domain.com")))
NIL
(#S(DRAGONS::RR
:NAME "myDC.my.domain.com"
:TYPE :A
:CLASS :IN
:TTL 1200
:RDATA #(10 1 1 47)))
((:NAME "_kerberos._tcp.my.domain.com" :TYPE :SRV :CLASS :IN))
Cerberus supports a set of encryption "profiles", which are implemented by specializing a set of generic functions.
You can load keytab files (as output from other Kerberos implementations, such from ktpass utility) using
CL-USER> (cerberus:load-keytab "my.keytab")
This returns a list of KEYTAB-ENTRY structures, which include information about the principal as well as the encryption key.
Note: there currently is no way to use the contents of a keytab file.
Cerberus now supports a simple KDC server. Each SPN (service principal name) is stored as an entry in a pounds database. The krbtgt principal is mandatory because this is the principal under which the TGS runs. So you must first run
CL-USER> (cerberus-kdc:add-spn "krbtgt/MYREALM@MYREALM" "password")
before it can be used. Of course, you will also want to add other principals for each user and service.
Add some SPNs and start the server:
CL-USER> (cerberus-kdc:add-spn "krbtgt/FRANK@FRANK" "mykdcpassword")
CL-USER> (cerberus-kdc:add-spn "frank@FRANK" "1234")
CL-USER> (cerberus-kdc:add-spn "dave@FRANK" "4321")
CL-USER> (cerberus-kdc:start-kdc-server "FRANK")
On the client, logon and get a ticket:
CL-USER> (cerberus:logon-user "frank@FRANK" "1234" :kdc-address "10.1.1.1")
CL-USER> (gss:acquire-credentials :kerberos "dave@FRANK")
The Cerberus KDC supports an RPC interface for configuration over the network. It is defined in kdc.x and implemented in kdc.lisp. Clients MUST be authenticated using AUTH-GSS:
CL-USER> (defparameter *client* (make-instance 'frpc:gss-client :credentials (gss:acquire-credentials :kerberos "krbtgt/FRANK@FRANK") :program 901980025 :version 1 :host "10.1.1.1"))
CL-USER> (cerberus-kdc:call-find "frank@FRANK" :client *client*)
Licensed under the terms of the MIT license.
Frank James April 2015.