I heard you need to write a tool that sets up a secure channel between two peers, but you cannot afford the luxury of using certificates for doing so. Maybe you want to develop a less braindead protocol than shadowsocks? All you have is a password? How about combining OPAQUE with a double-ratchet? You heard about OPAQUE, but are afraid to ask how to do this? Fear not, the following bits are here to enlighten you.
A warning
First a warning, since the security of the channel depends on the strength of your password it is essential that password is of high entropy and that your server employs some kind of rate-limiting, and possibly a whole range of other limitations and defense-in-depth mitigations. If possible try to use certificates, preferably in HW tokens though. Of course the best solution would be to use a strong password storage like SPHINX.
An example
The source code to this example can be found in the demos/chan-c-go directory of the libopaque sources. The example shows a simple client written in C connecting to a server written in Go. The client takes the password and the ip address of the server as command-line parameters. The server has one user record hard-coded that opens with the "super secure" password: "password". After completing an OPAQUE run, the client and server tries to exchange a message which is protected by the shared session key derived via OPAQUE and used with cryptosecretbox mechanism from libsodium. This will only succeed if the user provided the correct password at the command-line of the client.
Let's have a look at the relevant steps the client and server execute.
First step: client initiates
Initiating a OPAQUE session always starts with the client and the user providing the password to it. Well wrap the whole OPAQUE flow in a function that takes a password as input, and when everything is done returns the shared session secret:
#include <stdint.h> #include "opaque.h" int get_session_secret(const int sock, const uint8_t *pwdU, const size_t pwdU_len, uint8_t sk[OPAQUE_SHARED_SECRETBYTES]) { // let's prepare to make create a credential request, we need some // data to store it in: uint8_t request[OPAQUE_USER_SESSION_PUBLIC_LEN]; uint8_t ctx[OPAQUE_USER_SESSION_SECRET_LEN+pwdU_len]; // ctx is sensitive data we should protect it! with c you can // actually protect sensitive data much better than with other // languages, sodium wraps this up nicely and portably: if(-1==sodium_mlock(ctx,sizeof ctx)) { fprintf(stderr,"Failed to protect sensitive context\n"); return -1; } // let's create the credential request if(0!=opaque_CreateCredentialRequest(pwdU, pwdU_len, ctx, request)) { fprintf(stderr,"Failed to create credential request\n"); return -1; } // send off the request to the server if(sizeof request != write(sock, request, sizeof request)) { //fprintf(stderr,); perror("failed to send the request\n"); return -1; } ....
Second step: the server prepares a response
The server must read a user id and a request from the connection. Using the user id it can load the according user record, and with it can create a credential response.
import libopaque ... // we need to store the request request := make([]byte, libopaque.OPAQUE_USER_SESSION_PUBLIC_LEN) // read the request from the connection b, err := c.Read(request) if err != nil || b != libopaque.OPAQUE_USER_SESSION_PUBLIC_LEN { fmt.Println(b) panic(err) } // create a response based on the request, the hard-coded user // record, the ids and the context. resp, sk, _, err := libopaque.CreateCredResp(request, rec, ids, context) if err != nil { panic(err) } // send the response over to the client b, err = c.Write(resp) if err != nil || b != libopaque.OPAQUE_SERVER_SESSION_LEN { fmt.Println(b) panic(err) } ...
And this concludes the servers OPAQUE part, there is no further step necessary. The session secret should be used to setup an encrypted channel, like using the session secret as a key in a sodium/secretbox, or perhaps indeed feeding it to a double ratchet. Authentication will be implicit, if the authentication of any packages between the server and the client fails, then there is something afoul and you should abort your connection.
Last step: the client finishes the session setup
The last step is quite simple, the client reads the servers response, and recovers its credentials. Most notable are the two NULL parameters, one for the authentication token, which we don't need since we are doing implicit user authentication. And the other NULL is for the exportkey which we also do not need in our case.
... // we need to store the servers response uint8_t response[OPAQUE_SERVER_SESSION_LEN]; // receive a response from the server if(sizeof response != read(sock, response, sizeof response )) { perror("failed to read the response\n"); return -1; } // we need to supply the same context and user ids to the final step // as have been used by the server const uint8_t context[]="context"; const Opaque_Ids ids={4,(uint8_t*) "user",6,(uint8_t*)"server"}; // we recover the shared session key, and we set the authorization // token and the export_key parameters to NULL since we do not care // about them in this demo. if(0!=opaque_RecoverCredentials(response, ctx, context, strlen((char*)context), &ids, sk, NULL, NULL)) { fprintf(stderr,"Failed to recovercredential\n"); return 1; } // yay everything went fine. return 0; }
The result of this function is in the parameter sk, which should be fed into a counter-part of whatever the server is doing - be it a secretbox, or a double ratchet.
Summary
There is strong initiatives trying to get rid of passwords, and in some cases it makes sense. However there will be many use-cases where this cannot work. If the usage of certificates or hardware tokens is not possible, passwords if strong enough can still provide adequate protection even for protected communication channels. The nice thing about passwords is, that you do not need anything to use them, nothing to carry, nothing to sync, no state, no worries (ok, you still need a client though). And in this example and the accompanying demo we showed how simple it is to use OPAQUE with a password to create a forward secure communication channel between a client and a server.
Don't forget to come back for the next episode where we'll have a look how to store securely some sensitive data with OPAQUE.