Book Image

BeagleBone Home Automation

By : Juha Lumme
Book Image

BeagleBone Home Automation

By: Juha Lumme

Overview of this book

<p>Home automation lets you control daily activities such as changing the temperature, opening the garage door, or dimming the lights of your house using microprocessors. BeagleBone is a low-cost, high-expansion, hardware-hacker-focused BeagleBoard. It is small and comes with the high-performance ARM capabilities you expect from a BeagleBoard. BeagleBone takes full-featured Linux to places it has never gone before.</p> <p>Starting with the absolute basics, BeagleBone Home Automation gives you the knowledge you will require to create an Internet-age home automation solution. This book will show you how to set up Linux on BeagleBone. You will learn how to use Python to control different electronic components and sensors to create a standalone embedded system that also accepts control remotely from a smartphone.</p> <p>This book starts with the very basics of Linux administration and application execution using terminal connections. You will learn the basics of the general purpose input/output pins and discover how various electronic sensors and electronic components work. The “hardware jargon” is explained, and example applications demonstrating their practical use are created so that you will feel in control of the capabilities provided.</p> <p>Network programming is also a big part of this book, as the created server will be made accessible from the Internet through a smartphone application. You will also learn how to create a fully working Android application that communicates with the home automation server over the Internet.</p>
Table of Contents (14 chapters)

Considering the security aspects


Our Android application from Chapter 6, Creating an Android Client, can operate over public networks (public from the point of view of your home network). This means that basically anyone can connect to your socket, if the connection is currently not busy and the person knows your IP address and the socket port you decide to use. We have currently not implemented any type of security enforcement; we only drop the connection from the server side if we receive an unclear response to our Hello Message. However, since we do send the initial data packet in a clearly readable form, the person receiving the data can deduce that our server operates with a clear text protocol. If this is a malicious attempt, reverse engineering the protocol from this point onwards is not too difficult, if time and energy is put to the task.

Of course, all socket communications work on streams, and there are several types of security measures that you can take depending on the level of security you feel your connection requires.

You are basically only limited by your imagination and the type of security you can implement. Let me give you some ideas.

Making the client identify his intentions first

One of the simplest things that you could do is to reverse the initial connection procedure. Instead of sending a welcome message, the server would never respond to a new socket, but immediately wait for a valid input. You could easily implement a protocol-version-checking sequence, so that if this functionality is required, a client has to initiate it himself.

In addition to this, a connection would be silently dropped, if input that is not valid is detected. Then a temporary counter would be incremented from this particular IP address, and if, for example, three consecutive incorrect sequence initiations follow, a ban would be set for this IP address for a specified length of time. Of course, all these failed connection attempts need to be logged and perhaps an e-mail should be sent to the administrator about it.

Implementing the encrypted password login

Much stronger security is provided by requiring all clients to authenticate themselves when they connect. But since sending a password without encryption over the network is not a healthy habit, we will also require encryption. Valid login and password combinations will be held on a file on the server, and this way only server administrator can manage logins. During the initial connection establishment, a valid encrypted login-password sequence must be sent to the server, else again the server initiates its cold shoulder disconnection sequence. There are several ways to encrypt and decrypt data in Python, and we will choose 128-bit AES encryption with preshared keys, as that encryption is secure, and available in Android by default as well.

To use encryption on our Beagle, we will need to download a library for this purpose from the Internet. So, make sure you are connected (and you have done the automatic date configuration part from Chapter 2, Input and Output) and type in the following code:

root@beaglebone:~# pip install pycrypto

This will install the necessary libraries to enable encryption on our Python code in Beagle. Next, we should create a file that will hold the password data. For now, we will not encrypt it, but after reading this chapter, we're sure you will have a good idea how to encrypt this data, if you wish to do so. Create a file called valid_passwords.txt, and in here, add each user to a separate line in the username:password format:

root@beaglebone:~/ch7# cat valid_passwords.txt
jlumme:mypassword
reader:anotherpassword

Now, we are ready to start modifying the actual code.

Version 2.1 modifications to the server code

Let's start implementing the first two security features to beagle_server.py we have talked about so far. First, will need to import an AES cipher from the Crypto library:

from Crypto.Cipher import AES

And then we will need some constants and a lambda definition:

password_filename = "valid_passwords.txt"

# Encryption related definitions
BS = 16 #Our key size (16 bytes -> 128bit encryption)
SECRET_KEY="BeagleHomeAutoma" # Keep it 16 characters if you change

# The data needs to (un-)padded to the BS borders
pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS) 
unpad = lambda s : s[0:-ord(s[-1])]

You can see here that we first defined a filename that will hold a login-password combination (we will leave it as a clear text file for now). Then we define the variable BS; this defines our block size for the encryption. You could use 16, 24, or 32 here for 128-, 192-, or 256-bit encryption respectively. We also defined our preshared secret encryption key SECRET_KEY that will be known to both the server and the client, and encryption and decryption will use this key. If you change it, keep in mind that you have to give it a proper block size, otherwise the crypto functions will fail to operate.

Then we defined two lambda functions, pad and unpad. Lambda functions are nameless functions that can be defined at any time and always return a value. Here we defined two functions that will automatically append or remove padding from an input if its size is not equal to the border defined by BS.

Now let's define a function that will read the password file and compare whether the supplied login is valid:

def compare_to_valid_logins(username, password):
  pw_list = open (password_filename, 'r').read().splitlines()

  for line in pw_list:
    uname = line[:line.index(":")]
    pword = line[line.index(":")+1:]

    # If username and password match, return 1
    if uname == username and pword == password:
      return 1

  # If no successful login combination found
  return 0

We read the password file line by line, and compared it with the function arguments to see if a matching combination is found. The login-password is supposed to be sent separated by the ":" character. Now we will need to implement the actual message decryption function. It is somewhat similar to a normal handle_client_request() function, but of course with some extras for handling the decryption:

def verify_password(cs):
  try:
    msg = cs.recv(4) # Retrieve the header, this is a blocking call
    header = unpack("!HH", msg) # Decode the mandatory headers

    # Verify that sender sent the PASSWORD header
    if header[0] == BP.MT_PASSWORD:

      # Check how much date is left in the stream
      remaining_size =  int(header[1])
      print "Encrypted package is %d bytes" % remaining_size

      # Read the encrypted package
      encrypted_data = cs.recv(remaining_size)

So far everything is the same as when reading a message from a client. But then we need to decrypt the secret data:

      iv = encrypted_data[:16]

      # Initialize our deciphering key
      key = AES.new(SECRET_KEY, AES.MODE_CBC,IV=iv)

      # Rest of the packet is supposed to be login data
      decrypted  = key.decrypt(encrypted_data[16:])
      login_details = unpad(decrypted) # Rmove possible padding

You can see that the client has appended the Initialization Vector (IV) to the first 16 bytes of the message. An IV is used to initialize the cipher, and it can be chosen randomly (again, as long as it is 16 bytes). After that we call the cipher to create us a key for decryption with the SECRET_KEY and IV information. Lastly we decrypt the data, and remove the possible padding using the unpad lambda function.

After this we hold the login details in the login_details variable. We will verify them with the function that we defined before, as follows:

      # Verify that the format is correct
      if ":" in received_login:
        username = received_login[:received_login.index(":")]
        password = received_login[received_login.index(":")+1:]
        return compare_to_valid_logins(username, password)
      else:
        return 0

  # Any error in the procedure, we just disconnect the client
  except Exception, e:
    print "Something went wrong:"
    print e
    return 0 # Drop the connection

Since it's mandatory for our client to authenticate itself, we also have to be sure that the authentication data is available. During startup, we have to check for the existence of the password file. In the main function, right in the very beginning, add the following code:

 #Check that password file has been defined:
  try:
    with open(password_filename):
      pass
  except IOError:
   print "Password file [%s] not found, exiting" % password_filename
   sys.exit(1)

Lastly, we should add a call to the verify_password() method in our main function, right after the client connects to the server:

    client = server_socket.wait_for_client(srv) # blocking!

    result = verify_password(client)

    if not result == 1:
      print "Something wrong, drop the socket"
      client.close()
      continue

Now the server will always expect an encrypted login from a newly connecting client. If something doesn't occur the way the server expects it to, it will just drop the connection. Next, let's add support for integrating this with our Android client.

Version 2.2 modifications to Android client code

We do not need too many modifications to MainActivity.java for now. All the encryption will be handled in the networking part of our code. On the UI side, we will only query the user for a username and password during the connection attempt and pass that information to our network thread. So, first we add a couple of new variables as follows:

private String username = "";
private String password = "";

To get the login details from the user, we will not modify the existing UI, but instead add a new login Alert popup that is shown when the user clicks on the Connect button. For this purpose, we need to create a new UI layout definition file called password_query.xml. We will create it by navigating to File | New | New Android XML file and add it under resources. To this file, add the following definition:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
  <TextView
    android:layout_height="wrap_content"
    android:layout_width="wrap_content"
    android:id="@+id/login_username_textview"
    android:text="@string/login_username"
    android:textStyle="bold" />
  <EditText
    android:layout_height="wrap_content"
    android:layout_width="match_parent"
    android:id="@+id/login_username_edittext"
    android:inputType="text" />
  <TextView
    android:layout_height="wrap_content"
    android:layout_width="wrap_content"
    android:id="@+id/login_password_textview"
    android:text="@string/login_password"
    android:textStyle="bold" />
  <EditText
    android:layout_height="wrap_content"
    android:layout_width="match_parent"
    android:id="@+id/login_password_edittext"
    android:inputType="textPassword" />
</LinearLayout>

Now we can implement our loginDialog function as follows:

public AlertDialogloginDialog(Context c, String message) {
  //Inflate the login query window from resources
  LayoutInflater factory = LayoutInflater.from(c);            
  final View textEntryView = factory.inflate(R.layout.password_query, 
    null);
  //Set message and title, and button details
  AlertDialog.Builder alert = new AlertDialog.Builder(c); 
  alert.setTitle("Login"); 
  alert.setMessage(message); 
  alert.setView(textEntryView);

Here we retrieve the layout information from the resources, and start by creating a new alert dialog with the information supplied to the function. Next, we will add button handlers. First, we will add the "confirm" button as shown:

alert.setPositiveButton("Login", \
    new DialogInterface.OnClickListener() {
  public void onClick(DialogInterface dialog, int whichButton) {
    final EditText usernameInput = (EditText)
      textEntryView.findViewById(R.id.login_username_edittext);
    final EditText passwordInput = (EditText)
      textEntryView.findViewById(R.id.login_password_edittext);

    //Get the text user entered
    username = usernameInput.getText().toString();
    password = passwordInput.getText().toString();
        
    new Thread(new Runnable() {
      public void run() {
        nt = new NetworkTask(SERVER_ADDR, 
          SERVER_PORT, handler, username + ":" + password);
        nt.run();
      }
    }).start();  
  }
});

We add an onClickListener event for the login query window when the user clicks on the PositiveButton. It will fetch the data from the EditText fields and pass that information to NetworkTask (which we have moved here from the original uiEventHandler function). Next, we will add another onClickListener event for the NegativeButton as follows:

  //If user chooses to cancel

  alert.setNegativeButton("Cancel", new 
  DialogInterface.OnClickListener() { 
    public void onClick(DialogInterface dialog, intwhichButton) {
      // Do nothing
    }
  });

Now all that is left is to return the created dialog:

  return alert.create();

Now we will modify the uiEventHandler function. We will remove the NetworkTask starting code from here, and instead add the following code to show the alert dialog that we just created:

public void uiEventHandler(View v) {
  //Connect button pressed, and we are not connected -> connect
  if (v == connectButton&& !connectedToServer ) {
    AlertDialog a = loginDialog(this, "Login details:");
    a.show();
  }
  //Connect button pressed, and we're connected -> disconnect
...

Our UI can now handle user login prompts, and we can start thinking about the network thread implementation. For this purpose, we will need to define the following new constants in the BeagleProtocol.java file:

public final static short MT_PASSWORD       = 18;
public final static short LOGIN_FAILED      = 66;
public final static short ENCRYPTION_FAILED = 67;

Next, we need to add the piece of code that encrypts our login details and sends them to the server for validation in NetworkTask.java. First, we need to add the following new variables:

private String loginUsernamePassword = "";
//Encryption related
private IvParameterSpecivspec;
private SecretKeySpeckeyspec;
private Cipher cipher;
private String SecretKey = "BeagleHomeAutoma"; //16 bytes -> 128bit 
    encryption

We also need to modify our constructor as follows:

NetworkTask(String address, intport,Handler handler, String login) {
  serverAddress = address;
  serverPort = port;
  parent = handler;
  loginUsernamePassword = login;
  alive = true; //Set to false to end the thread during disconnection
}

Add a new function called sendEncryptedLoginDetails. This function will be responsible for encryption, and send the encrypted data to the server. Since our server does not reply to invalid logins, all we can do is proceed as follows, and evaluate the connection state later:

public intsendEncryptedLoginDetails() {
  //First initialize the random IV, and retrieve AES key spec
  SecureRandomrnd = new SecureRandom();
  byte [] ivbytes = rnd.generateSeed(16);
  ivspec = new IvParameterSpec(ivbytes);
  keyspec = new SecretKeySpec(SecretKey.getBytes(), "AES");

  byte[] encrypted = null;

Here we use the SecureRandom class to generate 16 random bytes for our IV. These bytes will be used as the "salt" for the encryption. Then we create the encryption key that is preshared between the client and server (String SecretKey). After this we initialize the cipher, and encrypt our message as follows:

//Define and initialize the cipher
  try {
    cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    cipher.init(Cipher.ENCRYPT_MODE, keyspec, ivspec);

    //Perform the encryption
    encrypted = cipher.doFinal(loginUsernamePassword.getBytes());
  } catch (Exception enc) {
    debug("Encryption failed");
    enc.printStackTrace();
    return 1;
  }

We first initialize the cipher by defining it with the desired specifics of the encryption, and then encrypt the loginUserNamePassword String. In the actual code, we separate the different try-catch blocks to identify which part of the code failed, so that the errors can be properly acted upon. But here, we have combined it with one catch-all call to save space.

Now that we have the encrypted bytes ready we can create our data package and send it, as shown in the following lines of code:

int enc_len = encrypted.length;
int iv_len = ivbytes.length;
  byte[] pw = new byte[4 + iv_len + enc_len];

  //Construct the message
  pw[0] = (byte)((BeagleProtocol.MT_PASSWORD>> 8) & 0xff);
  pw[1] = (byte)(BeagleProtocol.MT_PASSWORD&0xff);
  pw[2] = (byte)((enc_len + iv_len>> 8) & 0xff);
  pw[3] = (byte)(enc_len + iv_len& 0xff);

  //Copy the IV to the outgoing packet
  System.arraycopy(ivbytes,0,pw,4 ,iv_len);
  //Copy the encrypted data to outgoing packet
  System.arraycopy(encrypted, 0, pw, 4 + iv_len, enc_len);

  //Send the data
  try {
    os.write(pw);
  } catch (IOExceptionioe) {
    debug("Sending password has failed");
    ioe.printStackTrace();
  }

  //Cleanup
  rnd = null;
  ivspec = null;
  pw = null;

  return 0; //Success
}

Now that our encrypted login function is complete, all that is left is to add a proper place to call it in the run() method.

This call is placed after we have initialized our socket and data streams, as follows:

os = new DataOutputStream(socket.getOutputStream());

//Send the password information to the server:
int res = sendEncryptedLoginDetails();
if (res != 0) {
  debug("Encryption failed...");
  //In reality, we cannot really do much here.
  //Something wrong with selected encryption settings
}

//Read the initial welcome message
byte[] readArray = new byte[6];
int howmany = is.read(readArray, 0, 6);
//If our login fails, server will just drop the socket
if (howmany == -1) {
  debug("Stream has closed. Something wrong with login ?");

  //Inform the UI about login failure
  Message msg = new Message();
  msg.what = BeagleProtocol.LOGIN_FAILED;
  msg.obj = null;

  //send the message
  parent.handleMessage(msg);

  is.close();
  os.close();
  socket.close();
  return;
}

After we initialize our streams, we call the sendEncryptedLoginDetails function to identify us with the server along with the details we provided in the previous login query. After that, all we can do is check if the server is still sending us information (try to read the protocol version). If the socket has been dropped (the read stream returns -1), it means that the server has prevented our login, and we have to handle it. We inform the UI via our Handler class, close the streams, and return from this function, thus ending the life of this thread.

With the previous code, you are now securely transmitting your login details over the network.

Encrypting all of the communication

In the previous section, you saw how you can encrypt one message. The next step in beefing up the security would be to encrypt all of the communication. With the tips you saw earlier, this wouldn't be such a big hurdle; you will just have to modify the normal message sequence a bit. You could change the places of the "message request" and "message size" headers, so that only the incoming message size would be unencrypted, and everything else would always be encrypted.

The next step would be to also implement secure key exchange, so that the encryption would always be done with different keys. This will be getting into the advanced areas of cryptography and TCP/IP security, so we will leave it for a topic in another book.