The Client
GSSAPI/Kerberos relies on a Service Principal Name(SPN) on the server. We specify it in the client code so that is why I mention it here. If you were creating an actual server for production you would want to create a separate SPN for it, but since this is a demo I will be using the 'host' SPN which should exist by the mere fact that the server is part of the Kerberos Realm. To run the client you will also need to have credentials of your own via 'kinit'.Require the appropriate gems.
require 'gssapi'
require 'base64'
require 'socket'
Specify the server and the service name. Together these become the SPN.
host = 'example.org'
service = 'host'
Open up the connection to the server
sock = TCPSocket.new(host, 8082)
Initialize the GSSAPI security context and Base64 encode it so it is ready for transport. The Base64 encoding is not really necessary in this case, but since GSSAPI is often used in HTTP communication and is Base64 encoded there I am using it in this example. The GSSAPI::Simple wrapper is doing a lot in the background for you. It's importing the SPN for use in GSSAPI string types, managing memory and all of that good stuff. If you need to do something a bit out of the ordinary with GSSAPI and need to call the gss_* methods directly, check out the Simple wrapper for examples.
cli = GSSAPI::Simple.new(host, service)
tok = cli.init_context
stok = Base64.strict_encode64(tok)
Send the initial token, get back a response token and complete the security context. Once this step is done the set-up is complete and we are ready to start passing messages.
sock.write("#{stok}\n") # send initial token
stok = sock.gets.chomp # get back continuation token
ctx = cli.init_context(Base64.strict_decode64(stok.chomp)) # complete security context
puts "Connection #{(ctx ? 'succeeded' : 'failed')}"
Now just prompt for text from STDIN, encrypt the message and send it to the server. Do this until "exit" is typed on the command line.
begin
print "> "
msg = STDIN.gets.chomp
emsg = cli.wrap_message(msg)
sock.write("#{Base64.strict_encode64(emsg)}\n")
end while msg != 'exit'
sock.close
The Server
Set up is the same.
require 'gssapi'
require 'base64'
require 'socket'
The host and service should be the same as the client as well.
host = 'example.org'
service = 'host'
You won't always want to run as root so the system keytab may not be available. You can specify an alternate keytab as long as it contains the correct SPN.
keytab = "#{ENV['HOME']}/.gssapi/krb5.keytab" # this is optional, but probably required if not running as root
Start listening....
tcpsrv = TCPServer.new(host, 8082)
Initialize the security context for the server and acquire the credentials from the keytab.
srv = GSSAPI::Simple.new(host, service, keytab)
srv.acquire_credentials
Start the main server loop. This will listen for incoming requests, accept the incoming token, and send back a token so the client can complete the security context. After that it just listens for messages, decrypts them and prints them out. It will exit when it receives the "exit" message.
loop do
Thread.start(tcpsrv.accept) do |s|
print(s, "Accepted Connection\n")
stok = s.gets.chomp
print(s, "Received string#{stok}\n")
otok = srv.accept_context(Base64.strict_decode64(stok.chomp))
s.write("#{Base64.strict_encode64(otok)}\n")
begin
emsg = s.gets.chomp
msg = srv.unwrap_message(Base64.strict_decode64(emsg.chomp))
puts "Received: #{msg}"
end while msg != 'exit'
print(s, "Closing Socket\n")
s.close
end
end
That's really it and it's much shorter and easier to understand than the C counter-part. If you have issues running it make sure you have your credentials on the client (kinit) and the server has access to the appropriate keytab. These are the two big follies that will mess things up. Other than that have fun with the gssapi gem. If you use it and want to share, add your app to the gssapi gem wiki:
https://github.com/zenchild/gssapi/wiki/Users-of-GSSAPI
Cheers and Happy Solstice!