buffet: Add TLS support to XMPP connection

Properly handle <starttls> XMPP request from the server by initiating
TLS handshake.

BUG=brillo:191
TEST=`FEATURES=test emerge-link buffet`
     Manually test on LINK DUT and inspecting logs to make sure TLS
     connection to XMPP server is established successfully.

Change-Id: I94d8b5eb9e29402fc3d662afcfdbf2e0c5ec1a02
Reviewed-on: https://chromium-review.googlesource.com/272263
Tested-by: Alex Vakulenko <avakulenko@chromium.org>
Reviewed-by: Vitaly Buka <vitalybuka@chromium.org>
Commit-Queue: Alex Vakulenko <avakulenko@chromium.org>
diff --git a/buffet/notification/xmpp_channel.cc b/buffet/notification/xmpp_channel.cc
index 4567408..3674184 100644
--- a/buffet/notification/xmpp_channel.cc
+++ b/buffet/notification/xmpp_channel.cc
@@ -10,6 +10,7 @@
 #include <chromeos/backoff_entry.h>
 #include <chromeos/data_encoding.h>
 #include <chromeos/streams/file_stream.h>
+#include <chromeos/streams/tls_stream.h>
 
 #include "buffet/notification/notification_delegate.h"
 #include "buffet/notification/xml_node.h"
@@ -137,9 +138,22 @@
 void XmppChannel::HandleStanza(std::unique_ptr<XmlNode> stanza) {
   VLOG(2) << "XMPP stanza received: " << stanza->ToString();
 
-  // TODO(nathanbullock): Need to add support for TLS (brillo:191).
   switch (state_) {
     case XmppState::kStarted:
+      if (stanza->name() == "stream:features" &&
+          stanza->FindFirstChild("starttls/required", false)) {
+        state_ = XmppState::kTlsStarted;
+        SendMessage("<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>");
+        return;
+      }
+      break;
+    case XmppState::kTlsStarted:
+      if (stanza->name() == "proceed") {
+        StartTlsHandshake();
+        return;
+      }
+      break;
+    case XmppState::kTlsCompleted:
       if (stanza->name() == "stream:features") {
         auto children = stanza->FindChildren("mechanisms/mechanism", false);
         for (const auto& child : children) {
@@ -208,6 +222,28 @@
   SendMessage("</stream:stream>");
 }
 
+void XmppChannel::StartTlsHandshake() {
+  stream_->CancelPendingAsyncOperations();
+  chromeos::TlsStream::Connect(
+      std::move(raw_socket_), host_,
+      base::Bind(&XmppChannel::OnTlsHandshakeComplete,
+                 weak_ptr_factory_.GetWeakPtr()),
+      base::Bind(&XmppChannel::OnTlsError,
+                 weak_ptr_factory_.GetWeakPtr()));
+}
+
+void XmppChannel::OnTlsHandshakeComplete(chromeos::StreamPtr tls_stream) {
+  tls_stream_ = std::move(tls_stream);
+  stream_ = tls_stream_.get();
+  state_ = XmppState::kTlsCompleted;
+  RestartXmppStream();
+}
+
+void XmppChannel::OnTlsError(const chromeos::Error* error) {
+  LOG(ERROR) << "TLS handshake failed. Restarting XMPP connection";
+  Restart();
+}
+
 void XmppChannel::SendMessage(const std::string& message) {
   if (write_pending_) {
     queued_write_data_ += message;
@@ -285,6 +321,8 @@
 
   backoff_entry_.InformOfRequest(raw_socket_ != nullptr);
   if (raw_socket_) {
+    host_ = host;
+    port_ = port;
     stream_ = raw_socket_.get();
     callback.Run();
   } else {
@@ -325,6 +363,10 @@
     delegate_->OnDisconnected();
 
   weak_ptr_factory_.InvalidateWeakPtrs();
+  if (tls_stream_) {
+    tls_stream_->CloseBlocking(nullptr);
+    tls_stream_.reset();
+  }
   if (raw_socket_) {
     raw_socket_->CloseBlocking(nullptr);
     raw_socket_.reset();