Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Local HTTPS server on Android with client authentication

I have to create a key-distribution server, which is to be just a local server obtaining AES keys from DRM provider via their own API and returns them to the connected clients. The client is either my own media player based on FFmpeg library or the native Android media player.

I have experimented on Windows with Boost ASIO library and OpenSSL - there is an example of how to create a simple HTTPS server and client. In my case I have to enable access to the server only to dedicated applications/clients so I need a client authentication. There are a few things which are unclear to me as I have never been an expert in this kind of stuff.

I know that HTTPS server must ask client for authentication, the client should send its certificate, server then reeds and validates some data from the certificate. The questions are:

  • Which kind of certificate do I need for client, how can I create it?
  • Which kind of certificate do I need for server, how can I create it?
  • Where can I store the client certificate so that it is accessible for the clients (FFmpeg, Android MediaPlayer)? It must not be accessible for any other application.
  • Where can I store the server certificate?

Everything I am writing about performs at the native level i.e. it is implemented by Linux shared libraries. That’s why I think it is rather question for Linux gurus then for common Android developers.

Can somebody explain me – in a few bullets, how this could be done – if it is ever possible? Any hints are welcome too!

Many thanks!

like image 882
vitakot Avatar asked Nov 03 '22 03:11

vitakot


1 Answers

So, first off, the server does not ask the client for a certificate. It is the other way around. The client can, but not always, request the certificate from the server. From the wording of your question, it sounds like you might not need to use a certificate. See this link for more info otherwise. The server does typically need to authenticate the client, but this is usually done through a username / password message that is passed back to the server after a secure connection is in place. You are responsible for writing the code to handle that on both the server and client.

Here is some code that I use to connect to a server via an SSL connection:

void SSLSocket::Connect(SSLSocket* psSLS, const string& serverPath, string& port)
{
   // Connects to the server.
   // serverPath - specifies the path to the server.  Can be either an ip address or url.
   // port - port server is listening on.
   //
   try
   {
      boost::shared_ptr<boost::asio::io_service> IOServ(new boost::asio::io_service);
      IOService = IOServ; // Defined the same way in the class
      Locking CodeLock(SocketLock); // Single thread the code.
      // If the user has tried to connect before, then make sure everything is clean before trying to do so again.
      if (pSocket)
      {
         delete pSocket;
         pSocket = 0;
      }                                                                                                  
      // Create the resolver and query objects to resolve the host name in serverPath to an ip address.
      boost::asio::ip::tcp::resolver resolver(*IOService);
      boost::asio::ip::tcp::resolver::query query(serverPath, port);
      boost::asio::ip::tcp::resolver::iterator EndpointIterator = resolver.resolve(query);
      // Set up an SSL context.
      boost::asio::ssl::context ctx(*IOService, boost::asio::ssl::context::tlsv1_client);
      // Specify to not verify the server certificiate right now.
      ctx.set_verify_mode(boost::asio::ssl::context::verify_none);
      // Init the socket object used to initially communicate with the server.
      pSocket = new boost::asio::ssl::stream<boost::asio::ip::tcp::socket>(*IOService, ctx);
      //
      // The thread we are on now, is most likely the user interface thread.  Create a thread to handle all incoming and outgoing socket work messages.
      if (!RcvThreadCreated)
      {
         WorkerThreads.create_thread(boost::bind(&SSLSocket::RcvWorkerThread, this));
         RcvThreadCreated = true;
         WorkerThreads.create_thread(boost::bind(&SSLSocket::SendWorkerThread, this));
      }
      // Try to connect to the server.  Note - add timeout logic at some point.
      // This is an async method and will return right away.  When it completes, the
      // SSLSocket::HandleConnect method will be called and will contain error info to
      // identify if a successful connection was made or not.
      boost::asio::async_connect(pSocket->lowest_layer(), EndpointIterator,
         boost::bind(&SSLSocket::HandleConnect, this, boost::asio::placeholders::error));
}

void SSLSocket::HandleConnect(const boost::system::error_code& error)
{
   // This method is called asynchronously when the server has responded to the connect request.
   std::stringstream ss;
   try
   {
      if (!error)
      {
         pSocket->async_handshake(boost::asio::ssl::stream_base::client,
            boost::bind(&SSLSocket::HandleHandshake, this, boost::asio::placeholders::error));
         ss << "SSLSocket::HandleConnect: From worker thread " << Logger::NumberToString(boost::this_thread::get_id()) << ".\n";
         Log.LogString(ss.str(), LogInfo);
      }
      else
      {
         // Log an error.  This worker thread should exit gracefully after this.
         ss << "SSLSocket::HandleConnect: connect failed to " << sClientIp << " : " << uiClientPort << ".  Error: " << error.message() + ".\n";
         Log.LogString(ss.str(), LogError);
         Stop();
      }
   }
   catch (std::exception& e)
   {
      stringstream ss;
      ss << "SSLSocket::InitAsynchIO: threw an error - " << e.what() << ".\n";
      Log.LogString(ss.str(), LogError);
      Stop();
   }
}

void SSLSocket::HandleHandshake(const boost::system::error_code& error)
{
   // This method is called asynchronously when the server has responded to the handshake request.
   std::stringstream ss;
   try
   {
      if (!error)
      {
         // Try to send the first message that the server is expecting.  This msg tells the server we want to connect.
         // The first 4 bytes specifies the msg length after the first 4 bytes.  The next 2 bytes specifies the msg type.
         // The next 4 bytes specifies the source code.  The next 13 bytes specifies the msg "AttackPoker".
         // The next 2 bytes specifies the locale length.  The last 2 bytes specifies the locale - en for English.
         //
         unsigned char Msg[27] = {0x17, 0x00, 0x00, 0x00, 0x06, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x41,
            0x74, 0x74, 0x61, 0x63, 0x6b, 0x50, 0x6f, 0x6b, 0x65, 0x72, 0x02, 0x00, 0x65, 0x6e};
         boost::system::error_code Err;

         sClientIp = pSocket->lowest_layer().remote_endpoint().address().to_string();
         uiClientPort = pSocket->lowest_layer().remote_endpoint().port();
         ReqAlive = true;
         // boost::asio::async_write(*pSocket, boost::asio::buffer(Msg), boost::bind(&SSLSocket::HandleFirstWrite, this,
         //   boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
         int Count = boost::asio::write(*pSocket, boost::asio::buffer(Msg), boost::asio::transfer_exactly(27), Err);
         if (Err)
         {
            ss << "SSLSocket::HandleHandshake: write failed - " << error.message() << ".\n";
            Log.LogString(ss.str(), LogInfo);
         }
         HandleFirstWrite(Err, Count);
         // boost::asio::async_write(pSocket, boost::asio::buffer(Msg, 27), boost::bind(&SSLSocket::HandleWrite, this,
         // boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
         ss.str("");
         ss << "SSLSocket::HandleHandshake: From worker thread " << boost::this_thread::get_id() << ".\n";
      }
      else
      {
         ss << "SSLSocket::HandleHandshake: failed - " << error.message() << ".\n";
         IOService->stop();
      }
      Log.LogString(ss.str(), LogInfo);
   }
   catch (std::exception& e)
   {
      stringstream ss;
      ss << "SSLSocket::HandleHandshake: threw an error - " << e.what() << ".\n";
      Log.LogString(ss.str(), LogError);
      Stop();
   }
}

void SSLSocket::HandleFirstWrite(const boost::system::error_code& error, size_t bytesTransferred)
{
   // This method is called after a msg has been written out to the socket.
   std::stringstream ss;
   try
   {
      if (!error)
      {
         // boost::asio::async_read(pSocket, boost::asio::buffer(reply_, bytesTransferred), boost::bind(&SSLSocket::handle_read,
         //   this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
         // boost::asio::async_read(pSocket, boost::asio::buffer(reply_, 84), boost::bind(&SSLSocket::handle_read,
         //   this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
         // Locking CodeLock(ReadLock); // Single thread the code.
         // Signal the other threads that msgs are now ready to be sent and received.
         // boost::asio::async_read(pSocket, boost::asio::buffer(pRepBuf), boost::asio::transfer_exactly(4), boost::bind(&SSLSocket::HandleRead,
         //  this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
         //
         // Notify the UI that we are now connected. - TBD
         // Get the 1st 4 bytes of the next msg, which is always the length of the that msg.
         pDataBuf = BufMang.GetPtr(MsgLenBytes);

         // int i1=1,i2=2,i3=3,i4=4,i5=5,i6=6,i7=7,i8=8,i9=9;
         //   (boost::bind(&nine_arguments,_9,_2,_1,_6,_3,_8,_4,_5,_7))
         //     (i1,i2,i3,i4,i5,i6,i7,i8,i9);

         // boost::asio::read(*pSocket, boost::asio::buffer(pReqBuf, MsgLenBytes), boost::asio::transfer_exactly(MsgLenBytes), Err);
         // boost::asio::async_read(pSocket, boost::asio::buffer(pReqBuf, MsgLenBytes), boost::bind(&SSLSocket::HandleRead, _1,_2,_3))
         //   (this, pReqBuf, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred);
         //   boost::asio::async_read(*pSocket, boost::asio::buffer(reply_), boost::asio::transfer_exactly(ByteCount), boost::bind(&Client::handle_read,
         //      this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
         // boost::asio::async_write(*pSocket, boost::asio::buffer(pDataBuf, MsgLenBytes), boost::bind(&SSLSocket::HandleWrite, this,
         //    boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));

         HandShakeReady = true;
         Locking CodeLock(SocketLock); // Single thread the code.
         boost::asio::async_read(*pSocket, boost::asio::buffer(pDataBuf, MsgLenBytes), boost::bind(&SSLSocket::HandleRead, this,
            boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
      }
      else
      {
         ss << "SSLSocket::HandleFirstWrite: failed - " << error.message() << ".\n";
         Log.LogString(ss.str(), LogError);
         Stop();
      }
   }
   catch (std::exception& e)
   {
      stringstream ss;
      ss << "SSLSocket::HandleFirstWrite: threw an error - " << e.what() << ".\n";
      Log.LogString(ss.str(), LogError);
      Stop();
   }
}
like image 83
Bob Bryan Avatar answered Nov 08 '22 04:11

Bob Bryan