Providing an additional layer of security

We are now theoretically ready to communicate with anybody who is our friend. But who can we be friends with? Who is allowed to befriend us? What are they allowed to read or control once they are befriended? Should we pre-program this? Should friendships be manually configured using another XMPP client, such as a chat client, or should we build in logic for this to be configured remotely? These questions are important to consider during the architectural design phase of a project. We don't just want anybody to be able to connect to the device and do anything with it.

Things connected to the Internet differ a lot in various ways from other machines that are connected to the Internet but operated by humans. Some of them are listed as follows:

  • Things need to make all the decisions without help from a human operator.
  • It might be difficult to update the firmware on the device, compared to updating software on a PC.
  • Multiple things probably collaborate together at the same time, forming part of a larger eco-system. It might be easy to control settings in one device, but how do you administer multiple devices across the Internet? Do you want to log in to each one and set them individually?

The basics of provisioning

Instead of making an implementation that will handle all the considerations we just discussed into each device we will create, we will take the opportunity to use another method in this chapter. In this method, we will delegate trust to a third party called provisioning server and let it tell each device what is permitted and to what extent. Instead of trying to implement individual privacy solutions in each device we will create, we will delegate this responsibility to a trusted third party where it is both easier and more practical to implement security decision logic than doing this in each device. It is also easier for the owner of the things to administrate since all the rules can be configured in one place. And it does not violate privacy or integrity of data since the provisioning server only stores simple rules concerning who can talk to whom and about what, not the actual data that belongs to the owner, which can be sensitive.

The principle is easy: if somebody wants to do something with the device, the device asks the provisioning server whether it is allowed and to what extent. When the response is returned from the provisioning server, the device responds to the original request accordingly. If the question is new, the provisioning server flags the owner that a new situation has arisen that it needs input on. The owner then responds, and the provisioning server learns and uses this knowledge to respond to future questions of the same sort.

The basics of provisioning

The basic principle behind delegated trust

Note

To avoid bottlenecks, the device actually only asks once for each new type of request it receives and then remembers the response. The provisioning server can then ask the device to forget the previous responses if the rules were to change. The details of how the provisioning protocol works is described in the XMPP extension called XEP-0324: IoT - Provisioning.

Before the provisioning server can give any meaningful responses to queries, the provisioning server needs to know who the device actually is and who its owner is. This connection between the thing's identity and owner is done by a Thing Registry. The thing first registers itself, possibly with its newly created identity together with some information or metadata about itself.

Then, it provides this metadata to its owner in some way. We will do this using a QR code that contains the metadata that is encoded. It is assumed that anybody who has access to this code (which might be on a sticker on the box) and presents it first to the registry is also the owner of the thing. So, the owner scans the QR code and sends an ownership claim to the registry, which then returns a receipt with the JID of the thing, if claimed. The registry also informs the thing that the thing has been claimed and who its owner is.

Note

QR code is discussed in more detail in Appendix Q, QR-Code. Thing registries and how things register themselves and are discovered is further described in the XMPP extension called XEP-0347: IoT - Discovery.

Once the thing has been claimed by an owner, the provisioning server knows who can decide what the thing is allowed to do.

Initializing the Thing Registry interface

At this point, we will assume we know the address of the Thing Registry interface we want to use. We keep the address in xmppSettings.ThingRegistry. So, before we open the XMPP connection, we check whether we have an address and call a special setup method to initialize the Thing Registry interface:

if (!string.IsNullOrEmpty (xmppSettings.ThingRegistry)) SetupThingRegistry ();

A class named ThingRegistry helps us handle communication with a Thing Registry and is defined in the Clayster.Library.IoT.Provisioning namespace. It has three events we need to provide handlers for. OnClaimed event is raised when the device has been claimed by its owner. OnRemoved event is raised when the owner removes the device from the registry database but maintains ownership of the device. Finally, the OnDisowned event is raised when the owner disowns the thing, making it possible for the thing to be claimed by a new owner. This can be done as follows:

private static void SetupThingRegistry ()
{
  xmppRegistry = new ThingRegistry (xmppClient,
    xmppSettings.ThingRegistry);
  xmppRegistry.OnClaimed += OnClaimed;
  xmppRegistry.OnRemoved += OnRemoved;
  xmppRegistry.OnDisowned += OnDisowned;
}

Registering a thing

To facilitate registration of the device from different parts of the code, we need to create a method for it. A Thing Registry has two purposes. The first is to match things with their owners. The second is to be a bulletin board of public things. A public thing is a thing that has been successfully claimed that the owner agrees to make public. Public things can be searched for using the tags provided by them, including numerical tags such as location. So we need to differ between registering a thing without an owner and registering an update for a public thing that has an owner. As shown in the following code, we begin with a case where the thing doesn't have an owner yet:

private static void RegisterDevice ()
{
  if (xmppRegistry != null)
  {
    if (string.IsNullOrEmpty (xmppSettings.Owner))
    {

We continue by performing a registration, which is simple. The device simply registers a set of tags, comprising metadata about the device. The tags will become associated with the JID of the sender at the registry. Each tag has a name and a value. The value can be either a string or a number. The first parameter to the Register method tells the registry whether the device is self-owned or not. This is shown in the following code:

xmppRegistry.Register (false, 
  new StringTag ("MAN", "clayster.com"), 
  new StringTag ("MODEL", "LearningIoT-Sensor"), 
  new StringTag ("KEY", xmppSettings.Key));

Note

A special tag named KEY is neither displayed to anybody, nor is it searchable. It is also removed from the registry once the thing has been claimed. The purpose is to provide a random string, such as a GUID, unique to the thing itself. The thing can be claimed only with access to the complete set of tags.

Any tag names can be used. But there exists a list of predefined tag names for interoperability between things. These are listed in XEP-0347; refer to http://xmpp.org/extensions/xep-0347.html#tags.

A registration will only be effective if the thing is not claimed before. If claimed, the request is ignored by the registry and the OnClaimed event will be raised with the JID of the current owner.

Also, note that a successful registration removes all the previous metadata in the registry corresponding to the JID of the sender.

Updating a public thing

If the thing has an owner and it is public, we make a similar call where we register updates to the metadata. In this case, previous metadata will be updated, and the tags that are not available in the request will be maintained as they are. We also avoid using the KEY tag as seen in the following code:

    }
    else if (xmppSettings.Public)
    {
      xmppRegistry.Update (
        new StringTag ("MAN", "clayster.com"), 
        new StringTag ("MODEL", "LearningIoT-Sensor"), 
        new NumericalTag ("LAT", -32.976425), 
        new NumericalTag ("LON", -71.531690));
  }
}

Claiming a thing

Once the thing is claimed, the OnClaimed event is raised. This event contains information about who the owner is and whether the owner has chosen to keep the thing private or publish it as a public thing in the registry. We update our internal settings with this information and call the RegisterDevice method to update the metadata in the registry accordingly. This is shown in the next code snippet:

private static void OnClaimed (object Sender, ClaimedEventArgs e)
{
  xmppSettings.Owner = e.Owner;
  xmppSettings.Public = e.Public;
  xmppSettings.UpdateIfModified ();
 
  RegisterDevice ();
}

Removing a thing from the registry

The owner (or the thing) can remove the thing from the Thing Registry and thus make it private. When this happens, the OnRemoved event is raised. Since the thing is now private, it doesn't need to update the registry with any metadata any longer. We update our information as follows:

private static void OnRemoved (object Sender,NodeReferenceEventArgs e)
{
  xmppSettings.Public = false;
  xmppSettings.UpdateIfModified ();
}

Disowning a thing

If an owner wants to pass on the ownership of the device to another or give it away, the owner starts by disowning the thing in the provisioning server. When this happens, the OnDisowned event is raised:

private static void OnDisowned (object Sender, NodeReferenceEventArgs e)
{
  xmppSettings.Owner = string.Empty;
  xmppSettings.Public = false;
  xmppSettings.UpdateIfModified ();

In our event, we also remove the owner from our roster. This makes sure that the previous owner will not be able access the device again without the permission of the new owner. This is done as follows:

string Jid = XMPPSettings.Owner;
if (!string.IsNullOrEmpty (Jid))
{
  XmppContact Contact = xmppClient.GetLocalContact (Jid);
  if (Contact != null)
    xmppClient.DeleteContact (Contact);
}

In this event, it is also important to re-register the thing so that it gets an opportunity to be reclaimed. We also make sure we display the QR code again since it gives the new owner a chance to see and use it to claim the device. The QR code is displayed again with the following code:

  RegisterDevice ();
  if (xmppSettings.QRCode != null)
    DisplayQRCode ();
}

Tip

QR code is discussed in more detail in Appendix Q, QR-Code.

Initializing the provisioning server interface

In the same way as for the Thing Registry interface, we set up the provisioning server interface if we have an address for it. This is done before we open the XMPP connection with the following code:

if (!string.IsNullOrEmpty (xmppSettings.ProvisioningServer))
  SetupProvisioningServer ();

The ProvisioningServer class in the Clayster.Library.IoT.Provisioning namespace handles communication with the provisioning server. Apart from a reference to our XMPP client and the address to the provisioning server, this class takes a third parameter, representing the number of unique questions to remember the answers for in the provisioning cache. Questions represent friendship requests, readout requests, and control requests, and the number should represent a number that can be stored and still encompass the estimated number of different queries expected in a normal operation to avoid spamming the provisioning server. Using a cache this way makes sure that each unique question is only forwarded to the provisioning server once, as long as rules do not change. This can be done with the following code snippet:

private static void SetupProvisioningServer ()
{
  xmppProvisioningServer = new ProvisioningServer (xmppClient, xmppSettings.ProvisioningServer, 1000);

The provisioning server interface also has two events we should provide event handlers for. The OnFriend event is raised when the provisioning server recommends a new friendship, and OnUnfriend is raised when an existing friendship needs to be removed. This is done as follows:

xmppProvisioningServer.OnFriend += OnFriend;
xmppProvisioningServer.OnUnfriend += OnUnfriend;

Handling friendship recommendations

In the OnFriend event, we receive a JID of a recommended friendship. To create a friendship, we start by subscribing to its presence. The contact will make a decision whether to accept or deny the presence subscription request. If it accepts the presence subscription request, it will probably send a presence subscription request back to the sender as well. When both have accepted each other's presence subscriptions, we will see them as friends:

private static void OnFriend (object Sender, JidEventArgs e)
{
  xmppClient.RequestPresenceSubscription (e.Jid);
}

Handling requests to unfriend somebody

The OnUnfriend event is raised when the provisioning server recommends that you remove an existing friendship. You can do this easily by simply removing the corresponding contact from your roster:

private static void OnUnfriend (object Sender, JidEventArgs e)
{
  XmppContact Contact = xmppClient.GetLocalContact (e.Jid);
  if (Contact != null)
    xmppClient.DeleteContact (Contact);
}

Searching for a provisioning server

Previously, we assumed that we know the address of the Thing Registry or the provisioning server. But what if we don't? We can have it pre-programmed or preconfigured or deduce it from the domain of the XMPP server. It can be a JID or a server component address. If a Thing Registry or provisioning server is hosted as components on the current XMPP server, we can also find it dynamically by going through all the published components and analyzing their capabilities. In our applications, we will use the latter because the XMPP server at thingk.me hosts both the Thing Registry and provisioning server as a subcomponent on the same server.

To start a search for the components on the server, we will issue a standard service discovery request to the server. We will do this in the OnConnected event handler, right after having our presence status set if a Thing Registry or provisioning server has not been initialized already:

if (xmppRegistry == null || xmppProvisioningServer == null)
  Client.RequestServiceDiscovery (string.Empty, 
    XmppServiceDiscoveryResponse, null);

The response to this query will contain a set of features. The available components are reported as items. So, we need to check whether such items are supported by the server, and if they are, perform a service items discovery request to the server, as follows.

private static void XmppServiceDiscoveryResponse (
  XmppClient Client, XmppServiceDiscoveryEventArgs e)
{
  if (Array.IndexOf<string> (e.Features, XmppClient.NamespaceDiscoveryItems) >= 0)
  Client.RequestServiceDiscoveryItems (Client.Domain, XmppServiceDiscoveryItemsResponse, null);
}

The response will contain a set of items. We loop through this and perform an individual service discovery request on each item, if it has a JID, to learn what features are supported by each one. This is done with the following code:

private static void XmppServiceDiscoveryItemsResponse (XmppClient Client, XmppServiceDiscoveryItemsEventArgs e)
{
  foreach (XmppServiceDiscoveryItem Item in e.Items)
  {
    if (!string.IsNullOrEmpty (Item.Jid))
      Client.RequestServiceDiscovery (Item.Jid, Item.Node, XmppServiceDiscoveryItemResponse, Item);
  }
}

In each response, we check the Features array to confirm whether the urn:Xmpp:iot:discovery namespace is present. If it is, it means the corresponding Jid is an address to a Thing Registry:

private static void XmppServiceDiscoveryItemResponse (XmppClient Client, XmppServiceDiscoveryEventArgs e)
{
  XmppServiceDiscoveryItem Item = (XMPPServiceDiscoveryItem)e.State;
  if (Array.IndexOf<string> (e.Features, "urn:xmpp:iot:discovery") >= 0)
  {
    XmppSettings.ThingRegistry = Item.Jid;
    SetupThingRegistry ();
  }

In the same way, we can check for the presence of the urn:xmpp:iot:provisioning namespace. If it is, it means the corresponding JID is an address to a provisioning server:

if (Array.IndexOf<string> (e.Features, 
  "urn:xmpp:iot:provisioning") >= 0)
{
  xmppSettings.ProvisioningServer = Item.Jid;
  SetupProvisioningServer ();
}

Providing registry information

We can now update our information accordingly. If we have found a Thing Registry, we make sure to display a QR code for the owner. If not available, we request for one. Finally, we register the device in the registry as follows:

  xmppSettings.UpdateIfModified ();
  if (!string.IsNullOrEmpty (xmppSettings.ThingRegistry))
  {
    if (xmppSettings.QRCode == null)
      RequestQRCode ();
    else if (string.IsNullOrEmpty (xmppSettings.Owner))
      DisplayQRCode ();

    RegisterDevice ();
  }
}

We need to provide similar logic at the end of our OnConnected event handler if a Thing Registry and provisioning server address are already configured and a service discovery request is not issued.

Now both the owner and the registry have sufficient information to claim the device.

Tip

QR code is discussed in more detail in Appendix Q, QR-Code.

Maintaining a connection

For a device to be able to receive requests, it needs to ensure its connection is open. In a network, a lot can happen. Servers and network could go down, services might need to be updated, and power could fail. All these things lead to the connection being dropped. For this reason, it is important to manage these events and try to reconnect. The first thing we can do is try to reconnect when a working connection is dropped. We can do this in a response to an OnClosed event if no permanent error is detected.

If this does not work and a connection is not possible for some time, we need to regularly check the state of the connection using some arbitrary interval. In our downloadable example code, every minute we check for either a missing connection or whether the client is in an Error or Offline state. If so, we recycle the connection by closing it and opening it again.

Negotiating friendships

A friendship between two peers in XMPP is where both peers subscribe to each other's presence. In order to negotiate such friendships, special presence stanzas are sent between each peer. The friendship negotiation involves the XMPP servers of each device, and negotiation is done using bare JIDs. The following illustration shows a simplified sequence of a friendship negotiation between two peers, where we only involve the two peers and hide the implicit server communication that also takes place.

The four specific presence types used are Subscribe, Subscribed, Unsubscribe, and Unsubscribed. To subscribe to the presence of somebody else, or "ask to be friends with them," you send a Subscribe presence stanza to its bare JID. If you want to accept such a request, you respond with a Subscribed presence stanza; if not, you respond with an Unsubscribed presence stanza. To unsubscribe your presence subscription, you send an Unsubscribe presence stanza. But this is not the same as removing the friendship since the other may continue to subscribe to your presence. If you want to remove a friendship, it's better to delete the contact from the roster directly. The following diagram shows the simplified friendship negotiations:

Negotiating friendships

Simplified friendship negotiation

Handling presence subscription requests

We implement the logic explained in the previous section in an event handler for the OnPresenceReceived event as follows:

xmppClient.OnPresenceReceived += (Client, Presence) =>
{
  switch (Presence.Type)
  {

We begin with presence subscription requests. If we are not connected with a provisioning server that helps us decide with whom we can connect, we will reject all the incoming requests with the following code:

case PresenceType.Subscribe:
  if (xmppProvisioningServer == null)
    Client.RefusePresenceSubscription (Presence.From);

If a provisioning server is detected, we ask it whether we can be friends with the peer requesting the subscription:

else
{
  xmppProvisioningServer.IsFriend (Presence.From, e =>
  {

If the provisioning server approves the friendship, we accept the presence subscription and return a request of a presence subscription of our new protofriend if it is a peer, that is, it has a JID and not only a domain or subdomain address. The request can be sent as follows:

if (e.Result)
{
  Client.AcceptPresenceSubscription (Presence.From);
  if (Presence.From.IndexOf ('@') > 0)
    Client.RequestPresenceSubscription (Presence.From);

If the provisioning server does not approve of the friendship, we simply refuse the subscription and delete the contact:

  }
  else
  {
    Client.RefusePresenceSubscription (Presence.From);
    XmppContact Contact = xmppClient.GetLocalContact (Presence.From);
    if (Contact != null)
      xmppClient.DeleteContact (Contact);
    }
  }, null);
}
break;

If a peer requests to unsubscribe from its presence subscription to our device, we simply acknowledge the request:

case PresenceType.Unsubscribe: Client.AcceptPresenceUnsubscription (Presence.From);
break;

The received presence stanzas of the type Subscribed and Unsubscribed are receipts we receive after a peer has processed our requests. In our application, we don't need to react to these.

Continuing interrupted negotiations

Since friendship relationships are negotiated using multiple asynchronous messages, things can go wrong if one or both of the peers are interrupted or their connections closed during the process. It is important to have this possibility in mind. Fortunately, it is easy to recover from such interruptions. When the client connects, it loads its roster from the server. The roster contains all the JIDs' contacts and their corresponding presence subscription status. The OnRosterReceived event is raised when the XMPP client has received the roster from the server after having connected successfully.

To continue with interrupted negotiations, we can add an event handler for this event and loop through all the contacts received to see whether any one of them has unfinished friendship negotiations. Each contact will have an Ask property, and if this is set to Subscribe, it would mean that the contact is asking to subscribe to our presence. We can handle it as a new incoming friendship request.

It can also be so that the contact is successfully subscribed to our presence but we are not subscribed to the presence of the contact. The Subscription property of each contact tells us who subscribes to whom. In a friendship relationship, the value is Both. But if the value is To or From, only one is subscribed to the other. From means a presence subscription from the contact of your presence exists. Is it because we should be friends or because we're unfriending the contact? We need to ask the provisioning server, and if allowed, we continue and request for the presence subscription from the contact. Otherwise, we properly delete the contact from the roster.

Adding XMPP support to the sensor

Now that we have the devices connected to the XMPP network, adding the appropriate sensor interfaces is easy. The Clayster.Library.IoT.XmppInterfaces namespace contains a series of classes that handle most of the interfaces we need.

Adding a sensor server interface

The XMPP extension XEP-0323: IoT – Sensor Data specifies how sensor data can be interchanged over the XMPP network. It defines a request/response model, similar to the one we have used already, where a client asks a server for sensor data. In our sensor, we therefore create an XmppSensorServer object as soon as we have both an XMPP client created and a provisioning server defined. Its OnReadout event is raised whenever data is to be sent somewhere. All of the negotiation with the provisioning server has already been taken care of, including the possible limitations of the original request. This is done with the help of the following code:

xmppSensorServer = new XmppSensorServer (xmppClient, 
  xmppProvisioningServer);
xmppSensorServer.OnReadout += OnReadout;

The actual readout of the sensor is simple. It fits into our already defined sensor data export we use for other protocols. We simply call the ExportSensorData method defined in previous chapters.

Tip

For an example on how you can access these sensor values through a chat session with the sensor, refer to Appendix P, Chat Interfaces.

Updating event subscriptions

The XmppSensorServer class also handles event subscriptions according to a ProtoXEP: IoT – Events. This makes it possible for clients to request for sensor data based on the change in conditions. To make sure all subscriptions are updated accordingly, we need to inform the sensor server interface when new momentary values are available. We do this as follows:

if (XMPPSensorServer != null)
{
xmppSensorServer.MomentaryValuesUpdated (
    new KeyValuePair<string, double> (
      "Temperature", temperatureC),
     new KeyValuePair<string, double> (
      "Light", lightPercent),
     new KeyValuePair<string, double> (
      "Motion", motionDetected ? 1 : 0));
}

Note

This extension has not been approved by the XSF at the time of writing this. Anyway, it can be used as a simple way to subscribe to sensor data events, but with conditions. You can view this work in progress at http://xmpp.org/extensions/inbox/iot-events.html.

Publishing contracts

When things interconnect, they need to analyze each other to see what capabilities they have. One way to do this is to use the XMPP service discovery query to figure out what it features. When the sensor is queried by a peer, it will learn that it is a sensor and that it can be read. But what values are supported? One way to find this out is to read the sensor and see what it supports. However, this requires some extensive analysis of incoming data, which may vary in content and order. Another method is to retrieve a list of interoperability interfaces or contracts, as defined by a ProtoXEP: IoT - Interoperability. Here, each reference corresponds to a contract, as shown in the next code, where the sender promises to work in accordance with the corresponding contract:

xmppInteroperabilityServer = new XmppInteroperabilityServer (
  xmppClient,
  "XMPP.IoT.Sensor.Temperature",
  "XMPP.IoT.Sensor.Temperature.History",
  "Clayster.LearningIoT.Sensor.Light",
  "Clayster.LearningIoT.Sensor.Light.History",
  "Clayster.LearningIoT.Sensor.Motion",
  "Clayster.LearningIoT.Sensor.Motion.History");

All contracts are ordered into the form of a tree structure. Contracts that begin with XMPP.IoT are defined in the proto-XEP. But you can define any contract you want. In our example, we inform the interested party that the sensor is a temperature sensor that supports historical values. We also define our own light sensor and motion sensor contracts with History. These will be used later by the controller to easily detect whether the connected peer is the sensor it is looking for.

Note

This extension has not been sent to the XSF and therefore neither approved nor published by the XSF. It can be used anyway as a simple way to interchange references with contracts between things. You can view this work in progress at http://htmlpreview.github.io/?https://github.com/joachimlindborg/XMPP-IoT/blob/master/xep-0000-IoT-Interoperability.html.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset