SMQ Authentication and Authorization

Pub/Sub Security Considerations

Security is an important part of any design that utilizes a publish/subscribe protocol -- a one to many design pattern. A pub/sub protocol can potentially enable a penetrator to eavesdrop or publish messages to all connected clients. Having said that, we believe SMQ is one of the more secure pub/sub protocols due to its design. Let's go over some common security concerns with pub/sub protocols and their uses by various client types.

A good introduction to network and IoT security are the two following articles. The first article explains the benefit of designing a multilayered defense system. Multilayered defense systems are easy to design with SMQ. The second article explains the problem in giving too much trust in using authentication as the one and only line of defense.

Authentication

The following authentication mechanisms can be used with SMQ:

SMQ Protocol Authentication

The SMQ protocol includes clear-text authentication and salt based hash authentication.

The SMQ protocol authentication is typically used by devices, but not by SMQ web applications since the password would typically have to be stored as part of the JavaScript code in a web application. Web applications should use HTTP/Web Authentication.

A headless (non GUI) device would need to store the password embedded in the firmware.

Salt based hash authentication must be used if the communication link is not protected by TLS, for example, when using the non secure SMQ C client library.

The following example illustrates how salt based hash authentication is performed:

C Code (Client Code):

SHA256 sha; /* Assume we have an SHA256 library */
char* username="alice";
char* password="qwerty"; /* :-) */
uint32_t seed;
uint8_t digest[32];
int len;
uint8_t buf[50]; /* username and password must fit */
/* The seed (salt) value is a random number calculated by the broker */
SMQ_init(smq, "http://myserver.com/my-broker-path/", &seed);

/* Caluclate a digest based on password + seed */
char s[16];
SHA256_init(&sha);
SHA256_append(&sha, password, strlen(password));
sprintf(s, "%d", seed);
SHA256_append(&sha, s, strlen(s));
SHA256_finish(&sha, digest); /* Get SHA256 digest */

/* Copy username and digest to 'buf' */
len=strlen(username)+1; /* Including string terminator */
memcpy(buf,username,len);
memcpy(buf+len,digest,sizeof(digest));
len += sizeof(digest);

/* Complete SMQ handshake; buf now includes: username \0 digest */
SMQ_connect(smq, uid, uidLen, buf, len, info, infoLen);

Example 1: Calculating the salt based hash in C code.

Note that we are not handling any errors from SMQ_init and SMQ_connect in the above example. See the SMQ Client for more information on these two functions.

We are using both a username and password in the above example. You do not need to use a username with the SMQ authentication, but a username is typically required when having a unique password per device. As an option to using a username, the SMQ unique ID may be used instead. The server can store all unique ID's in a database, and the authentication callback function can lookup the password (credentials) based on using the unique ID as a key.

The corresponding server side code for handling salt based hash authentication is shown below. The fictitious method mydatabase:find() looks up the password by using the username as a key. As we mentioned above, the key could also be the SMQ unique ID.

Lua Code (Server Code):

-- SMQ broker callback function
local function authenticate(credentials, info)
   -- Credentials should have the form: username \0 hash
   -- Use Lua's string library (regex) to extract username and hash
   local uname,hash=credentials:match("^([^\0]-)\0(.+)$")
   if uname and hash then -- Variable 'credentials' has the correct format
      local password = mydatabase:find(uname) -- Find user in database
      if password then
         -- Calculate hash and check if hash is the same as received hash
         if ba.crypto.hash"sha256"(password)(info.seed)(true) == hash then
            return 0 -- Accepted
         end
      end
   end
   return 0x06 -- Connection Refused: Access Denied
end

local broker = require"smq.broker" -- Fetch "broker" module
local options={authenticate=authenticate}
local smq = broker.create(options)

Example 2: Calculating the salt based hash in Lua and verifying client's hash.

Function ba.crypto.hash() is part of the server API and enables us to calculate the salt based hash on password+seed. The seed value, which was sent to the SMQ client, is also provided in the info table passed into the "authenticate" callback. See the documentation for function smq.create() and the optional function argument "authenticate" for details on the info table.

The above example requires that the user database stores passwords in clear text. However, you can also use a database that stores hashed values.

User Database with HA1 Hashed Passwords

An option is to store the passwords in the user database as HA1 hash values. Storing the passwords as HA1 hash values makes the user database compatible with other HTTP authentication mechanisms supported by the Barracuda App Server. See encrypted passwords for detail.

The following pseudo code shows how the SMQ client would first calculate the HA1 and then calculate the salt based hash.

HA1 = MD5(username + ":" + realm + ":" + password)
hash = SHA256(HA1 + seed)

Example 3: Calculating salted HA1 -- i.e. SHA256(MD5(credentials) + seed)

On the server side, the HA1 values will be pre-computed and stored as hashed passwords in the user database. The following code snippet is from example 2, where the database has been changed to returning hash based passwords stored as HA1 values.

      -- Database returns pre-computed HA1 values
      local HA1 = mydatabase:find(uname) -- Find user in database
      if HA1 then
         -- Calculate salted HA1 hash and check if hash is the same as received hash
         if ba.crypto.hash"sha256"(HA1)(info.seed)(true) == hash then
            return 0 -- Accepted
         end
      end

Example 4: Verifying the salted HA1 value from example 3 at the server.

X.509 Certificate Authentication

Clients using SharkMQ or JavaMQ can use client X.509 Certificate Authentication. When using TLS, the server is always authenticated by the clients connecting to the server. The clients will not connect unless the server's X.509 certificate is trusted by the clients. Client X.509 Certificate Authentication means that we are also using certificates for authenticating the clients. In this case, the server also authenticates each client.

Creating a unique client side X.509 certificate and private key for each device is a complex task that greatly complicates the manufacturing process. Also, client side certificates have no real security advantage over password based client authentication, assuming that the server side authenticator (the SMQ broker's authenticate callback function) is designed intelligently. Intelligently means that the authenticator does not allow repeated failed logins (a machine will not provide incorrect credentials). An authenticator using standard SMQ authentication and that is designed to blacklist devices providing incorrect credentials is just as secure as a system using client side X.509 certificates.

Contact your Real Time Logic support line for more information on using client side X.509 certificates.

HTTP/Web Authentication

HTTP/Web Authentication is recommended for browser based solutions using SMQ.js. A browser that has been authenticated using any of the Barracuda App Server authenticators prior to initiating the SMQ connection will be authenticated when the SMQ connection is established.

The SMQ authenticator can easily detect if the client (the browser) is authenticated by checking that 'uname' is set on the info table passed into the authenticator callback function. The variable 'uname' is not set if not authenticated.

The following code snippet shows how to find out if the browser is authenticated.

-- SMQ broker authenticator callback function
local function authenticate(credentials, info)
   if info.uname then
      -- Browser has been authenticated by using HTTP/Web Authentication
   else
      -- Not a browser or browser is not authenticated
   end
end

Example 5: Verifying pre-authenticated HTTP/Web client.

Authorization

SMQ authorization has been designed such that authorization must be done programmatically by adding authorization callback functions to the SMQ broker.

By applying a set of callbacks and performing analysis on the data and topics being published, one can design an application that is secure even if authentication is not used.

Building Unique Client Information

Sometimes authorization is performed differently for each client connected. All authorization callbacks get the client's peer table as an argument. In addition to the values set by the broker, the peer table can be used for storing additional values that can be used to uniquely identify a particular client. This information can then be used for performing authorization.

Unique information for a particular client can be built as part of the authentication and connect sequence. The following example shows how we can build information when a client connects and authenticates.

Let's assume the SMQ client is using the URL: http://myserver.com/mybroker/?color=blue for connecting to the server.

function authenticate(credentials, info)
   -- fictitious method mydatabase:authenticate() returns true/false
   info.isAuthenticated = mydatabase:authenticate(credentials)
   return 0 -- Accept even if not authenticated
end

function onconnect(tid, info, peer)
   peer.isAuthenticated = info.isAuthenticated -- From authenticate callback
   if not peer.isAuthenticated and info.uname then
      peer.isAuthenticated = true
   end
   peer.color = info.data.color -- From URL
end

local smq = require("smq.broker").create{
   authenticate=authenticate,
   onconnect=onconnect
}

Example 6: Building unique SMQ client information and inserting into peer table.

In the above example, we carry forward the data provided by the client's URL (the color) and the authentication status. The "onconnect" callback further evaluates the authentication status by also checking if the client is authenticated by HTTP/Web authentication (line 9). We set 'isAuthenticated' and 'color' in the peer table. This information can later be used by other callbacks, such as the authorization callbacks. See the authenticate and onconnect callbacks for details on the arguments used in example 6.

Authorization Callbacks

Authorization can be performed by the 3 following callbacks:

The callback functions "permittop" and "permitsubtop" are called if a client wants to create a topic ID (tid) for a topic and sub-topic that is not registered in the broker. Function "permittop" is also called when a client subscribes to a topic.

You may create all topics and sub-topics used by your application in the server as part of the initialization sequence after creating a broker. You can then deny all attempts at creating new topics and/or sub-topics. Let's assume we have an application that only uses one topic, "/temperature". The following example shows how to pre-register the topic by the server code and then deny all other attempts at creating topics and sub-topics.

local function permitsubtop(subtopic, peer)
   myLogFunc("Warning: nasty client attempting to create sub-topic")
   return false -- Deny
end

function permittop(topic, issub, peer)
   if issub then -- A subscribe request. We must still allow this.
      if topic == "/temperature" then
         return true  -- Grant access.
      end
      myLogFunc("Warning: nasty client attempting to subscribe to topic")
   else
      myLogFunc("Warning: nasty client attempting to create topic")
   end
   return false -- Deny
end

local smq=require("smq.broker").create{
      permittop=permittop,
      permitsubtop=permittop
}

smq.create("/temperature") -- Pre-create topic

Example 7: Setting constraints on creating topics, sub-topics, and subscribing to topics.

On line 23 in the above example, we pre-create the topic used by the application. The broker will then create a unique topic ID (tid) for this topic name. The "permittop" callback (line 6) will not be called if a client attempts to create this topic since it's already been created by the server code (line 23). However, the "permittop" callback will be called each time a new client subscribes to this topic, thus we would normally allow this (line 9) unless you have a specific constraint for the client performing the request. Note that we do not allow any use of subtopics. Function "permitsubtop" simply returns false for any request.

The onpublish callback enables fine grained control of all messages published. The callback is called by the broker after receiving the published message from the sender and just before republishing the message to all subscribed clients. Note that the number of subscribed clients will be only one if the message is published to an ephemeral topic ID (one-to-one) communication.

The following example shows how to create a basic onpublish constraint function that allows messages less than 100 bytes in size; however, the SMQ server client is allowed to send messages of any size.

local smq -- An SMQ broker instance

function onpublish(data, ptid, tid, subtid, peer)
   if #data < 100 then
      return true -- Grant; Message size less than 100.
   end
   -- If publisher's topic ID is the ID of the server SMQ client.
   if ptid == smq.gettid() then
      return true -- Grant; Server can send any size.
   end
   return false -- Deny; Broker drops the message
end

Example 8: Setting constraints on message size.

Based on example 6 and 7, we can create an onpublish constraint function that prohibits publishing "/temperature" if the client is not authenticated. The beauty of this example is that it enables non authenticated clients to subscribe to "/temperature", but not to publish "/temperature".

local smq -- An SMQ broker instance initialized on line 13 below

function onpublish(data, ptid, tid, subtid, peer)
   -- Only allow publishing to "/temperature"
   if tid == smq.topic2tid"/temperature" then -- This can be optimized
      if peer.isAuthenticated then -- Set in example 6
         return true -- Grant
      end
   end
   return false -- Deny
end

smq = require("smq.broker").create{
   authenticate=authenticate,
   onconnect=onconnect,
   permittop=permittop,
   permitsubtop=permittop
   onpublish=onpublish
}

Example 9: Only authenticated clients can publish.

You should now have an understanding of how to create a system where the server code can grant or deny clients from performing certain actions. The authorization callbacks enable you to design secure applications that can be used without authentication or be used in a mixed environment where some clients are authenticated and some are not.

For learning more about creating an application that utilizes the SMQ protocol in a mixed authenticated/not-authenticated environment, download and study the Light Controller App. Open the server application's .preload script and look for the code section named "Security section".


Broker SSL Termination

SMQ Broker enables IoT SSL termination. To illustrate by example we have an external browser using secure WebSockets sending a message to non-secure devices utilizing the SimpleMQ Client. In this example the SMQ Broker decrypts the message received from the browser and then forwards the unencrypted result to the two non-secure devices. This communication also works in reciprocal pattern where a non secure client is able to send messages to a secure client.

SMQ SSL Termination