2012-12-18
Authentication and Encryption in Inferno OS
In my post about sharing resources in Inferno I used the -A
command line switch to commands to disable authentication. This allowed anyone to make connections and the data was passed across them unencrypted. This post explains how to set up an Inferno system to use authentication and to encrypt connections.
Signing Server
For authentication to work each machine that wishes to communicate securely must have a certificate signed by a common signing authority. That signing authority should be a separate machine with the sole task of running the signing processes.
The signing server needs to have a public and private key used for signing certificates. To generate these keys run the command auth/createsignerkey from within the Inferno shell. The command takes a name
argument which is the name that will appear as the signer name in the certificates generated by the server:
; auth/createsignerkey example.com
This command creates a /keydb/signerkey
file containing the keys. It's important to note that if this file is regenerated then any existing certificates issued by the server will be invalidated as the private key will have been lost. Keep this file safe!
The act of authentication involves checking that the signing server and the user know a shared secret. The secrets are kept in the /keydb/keys
file. The file, which starts off empty, is protected by a password. Run the svc/auth program to start the services which manage this file. You will need to provide the password, or create one if none exists:
; svc/auth
Key: ...enter password/passphrase...
Confirm key: ...confirm password/passphrase...
Each user to be authenticated must be set up on the signer by running the auth/changelogin command. This prompts for a password and an expiry date for the user. The expiry date is used as the maximum valid date for the certificates created for that user. At least one user must exist. Create a user on the signer for the current user on the signing machine (This might be inferno
or your host username, depending on your setup). You need to run auth/changelogin
for each remote or local user to be authenticated. That user can then run passwd command at a later time to change the password.
; auth/changelogin myuser
new account
secret: ...enter password/passphrase...
confirm: ...confirm password/passphrase...
expires [DDMMYYYY/permanent, return = 17122013]: ...enter expiry date or confirm default...
change written
Obtaining a client certificate
A client of authenticated Inferno services needs to have a certificate obtained from the signing server. The request for this certificate is done using getauthinfo. There is a also a wm/getauthinfo
variant that uses the Inferno GUI to prompt for required data. Other that that it is functionally the same as getauthinfo
.
The keyname
argument to getauthinfo
should either be default
or of the form net!example.com
where 'example.com' is the hostname of the server with the resources being accessed. This must exactly match the name given to bind
or mount
when accessing the service. This is how those commands know to find the certificate file. The default
keyname is used by file servers as the default certificate file for incoming connections.
When running getauthinfo
you are prompted for the signing server to be used, the username on the signing server that you wish to get the certificate for, a password for that username (which was created with auth/changelogin
previously, and whether you want to save the certificates in a file. If you answer yes
to 'save in file?' then an actual physical file is created in the users /usr/username/keyring
directory. If 'no' is answered then a fileserver is started on the client which serves a secure temporary file bound over the name in the given directory. This is removed when the file is unbound or Inferno is stopped.
For the following example I create a default
file on a new server so it can act as a file server to remote clients. I'm using file.example.com
as the hostname for this file server and example.com as the hostname for the signing server - replace this with the actual servers name. In these examples I assume that ndb/cs has been run to provide network services:
; getauthinfo default
use signer [$SIGNER]: example.com
remote user name [myuser]: ...enter username...
password: ...enter a password....
save in file [yes]: yes
On a client machine that wants to access resources on the file server run getauthinfo
, but using the net!hostname
form of the keyname, so bind
and mount
can find it.
; getauthinfo net!file.example.com
use signer [$SIGNER]: example.com
remote user name [myuser]: ...enter username...
password: ...enter password for user...
save in file [yes]: yes
The request for certificates only needs to be done once, assuming 'save in file?' was set to 'yes'. The certificate information is valid until the expiry date set for the user.
Initiating secure connections
Both server and client can now connect securely now that they have certificates provided by the common signing server. An example from my previous post on sharing resources was sharing a directory. On the file server:
; listen 'tcp!*!8000' { export '/usr/myuser' & }
This no longer has the -A
switch to disable authentication. To connect from the client:
; mount 'tcp!file.example.com!8000' /mnt/remote
This command will actually fail with a message saying it couldn't find 'keyring/default' if we haven't generated a default certificate. This is because our connection string includes a port and our getauthinfo
previously did not. Our getauthinfo
used 'net!file.example.com' but we're connecting to 'net!file.example.com!8000'. The default for mount
is to look for a file with the name exactly as specified with the host portion of the connection string and falling back to 'default'.
We can solve this by generating a default
file with getauthinfo
, or one for net!file.example.com!8000
which includes the port, or specifying the exact file on the mount
command line by using the -k
switch:
; mount -k 'net!file.example.com' 'tcp!file.example.com!8000' /mnt/remote
These mount examples
will authenticate using the certificates but will not encrypt the session. To encrypt as well as authenticate use the -C
switch to mount
to set the hashing and encryption algorithm to use. See the ssl documentation for the supported algorithms. For example:
; mount -C sha1/rc4_256 'tcp!file.example.com!8000' /mnt/remote
All the examples in my previous post can now be done with authenticated and encrypted sessions.
Conclusion and additional notes
The examples in this post show explicitly passing the hostname of the signing server in places. The file /lib/ndb/local
contains information for default servers. Changing the SIGNER
key to the signing servers hostname will avoid the need of always specifying this information.
The signing server should have the sole task of running svc/auth
. It shouldn't be used for other tasks. Doing so results in issues where parts of svc/auth
exit when a client fails to authenticate resulting in the signing server no longer handling authentication. This is briefly warned about in svc/keyfs:
Keyfs should be started only on the machine acting as authentication server (signer), before a listener is started for signer(8). Note that signer and keyfs must share the name space. Furthermore, no other application except the console should see that name space.
This mailing list post writes more about this. This is why I use a separate file server to export the path in the listen
statement in the examples here.
The following sources have additional information and examples on using authentication and encryption in Inferno. In particular the man
pages for the commands have a lot of detail: