shill: Ethernet: Authenticate to 802.1x networks

Instantate a Network instance in wpa_supplicant to perform
802.1x authentication, given the service EAP parameters if
an EAP authenticator is detected on the link and credentials
are available.  Note that this is the only flow that is
supported with this change -- authenticating and
deauthenticating as credentials appear and disappear will
be supported in a follow-on CL.

CQ-DEPEND=CL:47403,CL:47428
BUG=chromium:224091
TEST=New autotest network_8021xWiredAuthentication
(https://gerrit.chromium.org/gerrit/47410)

Change-Id: I3feeceb02b15646130bf862f894a77c203fb9860
Reviewed-on: https://gerrit.chromium.org/gerrit/47411
Tested-by: Paul Stewart <pstew@chromium.org>
Commit-Queue: Paul Stewart <pstew@chromium.org>
Reviewed-by: Paul Stewart <pstew@chromium.org>
diff --git a/Makefile b/Makefile
index 1bc0e44..6a0d566 100644
--- a/Makefile
+++ b/Makefile
@@ -382,6 +382,7 @@
 	mock_dns_client.o \
 	mock_eap_listener.o \
 	mock_ethernet.o \
+	mock_ethernet_service.o \
 	mock_event_dispatcher.o \
 	mock_glib.o \
 	mock_http_request.o \
diff --git a/doc/device-api.txt b/doc/device-api.txt
index b0969a3..87a4794 100644
--- a/doc/device-api.txt
+++ b/doc/device-api.txt
@@ -430,6 +430,18 @@
 				     of the localized name. If "localized_name" exists,
 				     then this property will always exist as well.
 
+		bool EapAuthenticatorDetected [readonly]
+
+			(Ethernet only) Indicates whether an EAP (802.1X)
+			authenticator has been detected on this link.
+			This may mean that EAP credentials are necessary
+			to gain full access to this network.
+
+		bool EapAuthenticationCompleted [readonly]
+
+			(Ethernet only) Indicates whether an EAP (802.1X)
+			authentication is currently valid on this interface.
+
 		string Interface [readonly]
 
 			The device interface (for example "eth0" etc.)
diff --git a/ethernet.cc b/ethernet.cc
index d5a2326..589b127 100644
--- a/ethernet.cc
+++ b/ethernet.cc
@@ -4,12 +4,15 @@
 
 #include "shill/ethernet.h"
 
-#include <time.h>
-#include <stdio.h>
 #include <netinet/ether.h>
 #include <linux/if.h>  // Needs definitions from netinet/ether.h
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
 
+#include <map>
 #include <string>
+#include <vector>
 
 #include <base/bind.h>
 
@@ -22,10 +25,17 @@
 #include "shill/event_dispatcher.h"
 #include "shill/logging.h"
 #include "shill/manager.h"
+#include "shill/nss.h"
 #include "shill/profile.h"
+#include "shill/proxy_factory.h"
 #include "shill/rtnl_handler.h"
+#include "shill/supplicant_interface_proxy_interface.h"
+#include "shill/supplicant_process_proxy_interface.h"
+#include "shill/wpa_supplicant.h"
 
+using std::map;
 using std::string;
+using std::vector;
 
 namespace shill {
 
@@ -45,14 +55,20 @@
              interface_index,
              Technology::kEthernet),
       link_up_(false),
+      is_eap_authenticated_(false),
       is_eap_detected_(false),
-      eap_listener_(new EapListener(dispatcher, interface_index)) {
+      eap_listener_(new EapListener(dispatcher, interface_index)),
+      nss_(NSS::GetInstance()),
+      certificate_file_(manager->glib()),
+      proxy_factory_(ProxyFactory::GetInstance()),
+      weak_ptr_factory_(this) {
   PropertyStore *store = this->mutable_store();
+  store->RegisterConstBool(kEapAuthenticationCompletedProperty,
+                           &is_eap_authenticated_);
   store->RegisterConstBool(kEapAuthenticatorDetectedProperty,
                            &is_eap_detected_);
-  // Unretained() is okay here since eap_listener_ is owned by |this|.
   eap_listener_->set_request_received_callback(
-      base::Bind(&Ethernet::OnEapDetected, base::Unretained(this)));
+      base::Bind(&Ethernet::OnEapDetected, weak_ptr_factory_.GetWeakPtr()));
   SLOG(Ethernet, 2) << "Ethernet device " << link_name << " initialized.";
 }
 
@@ -78,6 +94,7 @@
     manager()->DeregisterService(service_);
     service_ = NULL;
   }
+  StopSupplicant();
   OnEnabledStateChanged(EnabledStateChangedCallback(), Error());
   if (error)
     error->Reset();       // indicate immediate completion
@@ -97,9 +114,11 @@
     link_up_ = false;
     is_eap_detected_ = false;
     DestroyIPConfig();
+    SetIsEapAuthenticated(false);
     if (service_)
       manager()->DeregisterService(service_);
     SelectService(NULL);
+    StopSupplicant();
     eap_listener_->Stop();
   }
 }
@@ -113,6 +132,7 @@
   } else {
     LOG(ERROR) << "Unable to acquire DHCP config.";
     SetServiceState(Service::kStateFailure);
+    DestroyIPConfig();
   }
 }
 
@@ -122,9 +142,207 @@
   DropConnection();
 }
 
+void Ethernet::TryEapAuthentication() {
+  if (!service_) {
+    LOG(INFO) << "Service is missing; not doing EAP authentication.";
+    return;
+  }
+
+  if (!service_->Is8021xConnectable()) {
+    if (is_eap_authenticated_) {
+      LOG(INFO) << "Service lost 802.1X credentials; "
+                << "terminating EAP authentication.";
+    } else {
+      LOG(INFO) << "Service lacks 802.1X credentials; "
+                << "not doing EAP authentication.";
+    }
+    StopSupplicant();
+    return;
+  }
+
+  if (!is_eap_detected_) {
+    LOG(INFO) << "EAP authenticator not detected; "
+              << "not doing EAP authentication.";
+    return;
+  }
+  if (!StartSupplicant()) {
+    LOG(ERROR) << "Failed to start supplicant.";
+    return;
+  }
+  StartEapAuthentication();
+}
+
+
+void Ethernet::BSSAdded(const ::DBus::Path &path,
+                        const map<string, ::DBus::Variant> &properties) {
+  NOTREACHED() << __func__ << " is not implemented for Ethernet";
+}
+
+void Ethernet::BSSRemoved(const ::DBus::Path &path) {
+  NOTREACHED() << __func__ << " is not implemented for Ethernet";
+}
+
+void Ethernet::Certification(const map<string, ::DBus::Variant> &properties) {
+  string subject;
+  uint32 depth;
+  if (WPASupplicant::ExtractRemoteCertification(properties, &subject, &depth)) {
+    dispatcher()->PostTask(Bind(&Ethernet::CertificationTask,
+                                weak_ptr_factory_.GetWeakPtr(),
+                                subject, depth));
+  }
+}
+
+void Ethernet::EAPEvent(const string &status, const string &parameter) {
+  dispatcher()->PostTask(Bind(&Ethernet::EAPEventTask,
+                              weak_ptr_factory_.GetWeakPtr(),
+                              status,
+                              parameter));
+}
+
+void Ethernet::PropertiesChanged(
+  const map<string, ::DBus::Variant> &properties) {
+  const map<string, ::DBus::Variant>::const_iterator properties_it =
+      properties.find(WPASupplicant::kInterfacePropertyState);
+  if (properties_it == properties.end()) {
+    return;
+  }
+  dispatcher()->PostTask(Bind(&Ethernet::SupplicantStateChangedTask,
+                              weak_ptr_factory_.GetWeakPtr(),
+                              properties_it->second.reader().get_string()));
+}
+
+void Ethernet::ScanDone() {
+  NOTREACHED() << __func__ << " is not implented for Ethernet";
+}
+
 void Ethernet::OnEapDetected() {
   is_eap_detected_ = true;
   eap_listener_->Stop();
+  TryEapAuthentication();
+}
+
+bool Ethernet::StartSupplicant() {
+  if (supplicant_process_proxy_.get()) {
+    return true;
+  }
+
+  supplicant_process_proxy_.reset(
+      proxy_factory_->CreateSupplicantProcessProxy(
+          WPASupplicant::kDBusPath, WPASupplicant::kDBusAddr));
+  ::DBus::Path interface_path;
+  try {
+    map<string, DBus::Variant> create_interface_args;
+    create_interface_args[WPASupplicant::kInterfacePropertyName].writer().
+        append_string(link_name().c_str());
+    create_interface_args[WPASupplicant::kInterfacePropertyDriver].writer().
+        append_string(WPASupplicant::kDriverWired);
+    create_interface_args[
+        WPASupplicant::kInterfacePropertyConfigFile].writer().
+        append_string(WPASupplicant::kSupplicantConfPath);
+    interface_path =
+        supplicant_process_proxy_->CreateInterface(create_interface_args);
+  } catch (const DBus::Error &e) {  // NOLINT
+    if (!strcmp(e.name(), WPASupplicant::kErrorInterfaceExists)) {
+      interface_path = supplicant_process_proxy_->GetInterface(link_name());
+    } else {
+      LOG(ERROR) << __func__ << ": Failed to create interface with supplicant.";
+      StopSupplicant();
+      return false;
+    }
+  }
+  supplicant_interface_proxy_.reset(
+      proxy_factory_->CreateSupplicantInterfaceProxy(
+          this, interface_path, WPASupplicant::kDBusAddr));
+  supplicant_interface_path_ = interface_path;
+  return true;
+}
+
+bool Ethernet::StartEapAuthentication() {
+  map<string, DBus::Variant> params;
+  vector<char> nss_identifier(link_name().begin(), link_name().end());
+  WPASupplicant::Populate8021xProperties(
+      service_->eap(), &certificate_file_, nss_, nss_identifier, &params);
+  params[WPASupplicant::kNetworkPropertyEapKeyManagement].writer().
+      append_string(WPASupplicant::kKeyManagementIeee8021X);
+  params[WPASupplicant::kNetworkPropertyEapolFlags].writer().
+      append_uint32(0);
+  params[WPASupplicant::kNetworkPropertyScanSSID].writer().
+      append_uint32(0);
+  service_->ClearEAPCertification();
+  eap_state_handler_.Reset();
+
+  try {
+    if (!supplicant_network_path_.empty()) {
+      supplicant_interface_proxy_->RemoveNetwork(supplicant_network_path_);
+    }
+    supplicant_network_path_ = supplicant_interface_proxy_->AddNetwork(params);
+    CHECK(!supplicant_network_path_.empty());
+  } catch (const DBus::Error &e) {  // NOLINT
+    LOG(ERROR) << "exception while adding network: " << e.what();
+    return false;
+  }
+
+  supplicant_interface_proxy_->SelectNetwork(supplicant_network_path_);
+  return true;
+}
+
+void Ethernet::StopSupplicant() {
+  supplicant_interface_proxy_.reset();
+  if (!supplicant_interface_path_.empty() && supplicant_process_proxy_.get()) {
+    try {
+      supplicant_process_proxy_->RemoveInterface(
+          ::DBus::Path(supplicant_interface_path_));
+    } catch (const DBus::Error &e) {  // NOLINT
+      LOG(ERROR) << __func__ << ": Failed to remove interface from supplicant.";
+    }
+  }
+  supplicant_network_path_ = "";
+  supplicant_interface_path_ = "";
+  supplicant_process_proxy_.reset();
+  SetIsEapAuthenticated(false);
+}
+
+void Ethernet::SetIsEapAuthenticated(bool is_eap_authenticated) {
+  if (is_eap_authenticated == is_eap_authenticated_) {
+    return;
+  }
+
+  // If our EAP authentication state changes, we have now joined a different
+  // network.  Restart the DHCP process and any other connection state.
+  DisconnectFrom(service_);
+  if (service_ && link_up_) {
+    ConnectTo(service_);
+  }
+  is_eap_authenticated_ = is_eap_authenticated;
+  adaptor()->EmitBoolChanged(kEapAuthenticationCompletedProperty,
+                             is_eap_authenticated_);
+}
+
+void Ethernet::CertificationTask(const string &subject, uint32 depth) {
+  if (!service_) {
+    LOG(ERROR) << "Ethernet " << link_name() << " " << __func__
+               << " with no service.";
+    return;
+  }
+
+  service_->AddEAPCertification(subject, depth);
+}
+
+void Ethernet::EAPEventTask(const string &status, const string &parameter) {
+  LOG(INFO) << "In " << __func__ << " with status " << status
+            << ", parameter " << parameter;
+  Service::ConnectFailure failure = Service::kFailureUnknown;
+  if (eap_state_handler_.ParseStatus(status, parameter, &failure)) {
+    LOG(INFO) << "EAP authentication succeeded!";
+    SetIsEapAuthenticated(true);
+  } else if (failure != Service::Service::kFailureUnknown) {
+    LOG(INFO) << "EAP authentication failed!";
+    SetIsEapAuthenticated(false);
+  }
+}
+
+void Ethernet::SupplicantStateChangedTask(const string &state) {
+  LOG(INFO) << "Supplicant state changed to " << state;
 }
 
 }  // namespace shill
diff --git a/ethernet.h b/ethernet.h
index a690ec5..b50cf9d 100644
--- a/ethernet.h
+++ b/ethernet.h
@@ -8,16 +8,26 @@
 #include <string>
 
 #include <base/memory/scoped_ptr.h>
+#include <base/memory/weak_ptr.h>
 
+#include "shill/certificate_file.h"
 #include "shill/device.h"
+#include "shill/supplicant_eap_state_handler.h"
 #include "shill/event_dispatcher.h"
 #include "shill/refptr_types.h"
+#include "shill/supplicant_event_delegate_interface.h"
 
 namespace shill {
 
+class CertificateFile;
 class EapListener;
+class NSS;
+class ProxyFactory;
+class SupplicantEAPStateHandler;
+class SupplicantInterfaceProxyInterface;
+class SupplicantProcessProxyInterface;
 
-class Ethernet : public Device {
+class Ethernet : public Device, public SupplicantEventDelegateInterface {
  public:
   Ethernet(ControlInterface *control_interface,
            EventDispatcher *dispatcher,
@@ -34,18 +44,76 @@
   virtual void ConnectTo(EthernetService *service);
   virtual void DisconnectFrom(EthernetService *service);
 
+  // Test to see if conditions are correct for EAP authentication (both
+  // credentials and a remote EAP authenticator is present) and initiate
+  // an authentication if possible.
+  virtual void TryEapAuthentication();
+
+  // Implementation of SupplicantEventDelegateInterface.  These methods
+  // are called by SupplicantInterfaceProxy, in response to events from
+  // wpa_supplicant.
+  virtual void BSSAdded(
+      const ::DBus::Path &BSS,
+      const std::map<std::string, ::DBus::Variant> &properties);
+  virtual void BSSRemoved(const ::DBus::Path &BSS);
+  virtual void Certification(
+      const std::map<std::string, ::DBus::Variant> &properties);
+  virtual void EAPEvent(
+      const std::string &status, const std::string &parameter);
+  virtual void PropertiesChanged(
+      const std::map<std::string, ::DBus::Variant> &properties);
+  virtual void ScanDone();
+
  private:
   friend class EthernetTest;
 
+  // Invoked by |eap_listener_| when an EAP authenticator is detected.
   void OnEapDetected();
 
-  ServiceRefPtr service_;
+  // Start and stop a supplicant instance on this link.
+  bool StartSupplicant();
+  void StopSupplicant();
+
+  // Start the EAP authentication process.
+  bool StartEapAuthentication();
+
+  // Change our EAP authentication state.
+  void SetIsEapAuthenticated(bool is_eap_authenticated);
+
+  // Callback tasks run as a result of event delegate methods.
+  void CertificationTask(const std::string &subject, uint32 depth);
+  void EAPEventTask(const std::string &status, const std::string &parameter);
+  void SupplicantStateChangedTask(const std::string &state);
+
+  EthernetServiceRefPtr service_;
   bool link_up_;
 
+  // Track whether we have completed EAP authentication successfully.
+  bool is_eap_authenticated_;
+
   // Track whether an EAP authenticator has been detected on this link.
   bool is_eap_detected_;
   scoped_ptr<EapListener> eap_listener_;
 
+  // Track the progress of EAP authentication.
+  SupplicantEAPStateHandler eap_state_handler_;
+
+  // Proxy instances used to talk to wpa_supplicant.
+  scoped_ptr<SupplicantProcessProxyInterface> supplicant_process_proxy_;
+  scoped_ptr<SupplicantInterfaceProxyInterface> supplicant_interface_proxy_;
+  std::string supplicant_interface_path_;
+  std::string supplicant_network_path_;
+
+  // NSS and certificate file instances are needed to generate public key
+  // data for remote authentication.
+  NSS *nss_;
+  CertificateFile certificate_file_;
+
+  // Store cached copy of proxy factory singleton for speed/ease of testing.
+  ProxyFactory *proxy_factory_;
+
+  base::WeakPtrFactory<Ethernet> weak_ptr_factory_;
+
   DISALLOW_COPY_AND_ASSIGN(Ethernet);
 };
 
diff --git a/ethernet_unittest.cc b/ethernet_unittest.cc
index dad123a..0795bae 100644
--- a/ethernet_unittest.cc
+++ b/ethernet_unittest.cc
@@ -17,18 +17,28 @@
 #include "shill/mock_dhcp_provider.h"
 #include "shill/mock_eap_listener.h"
 #include "shill/mock_event_dispatcher.h"
+#include "shill/mock_ethernet_service.h"
 #include "shill/mock_glib.h"
+#include "shill/mock_log.h"
 #include "shill/mock_manager.h"
 #include "shill/mock_metrics.h"
 #include "shill/mock_rtnl_handler.h"
+#include "shill/mock_supplicant_interface_proxy.h"
+#include "shill/mock_supplicant_process_proxy.h"
 #include "shill/nice_mock_control.h"
+#include "shill/proxy_factory.h"
 
+using std::string;
 using testing::_;
 using testing::AnyNumber;
+using testing::EndsWith;
 using testing::Eq;
 using testing::Mock;
+using testing::NiceMock;
 using testing::Return;
+using testing::StrEq;
 using testing::StrictMock;
+using testing::Throw;
 
 namespace shill {
 
@@ -47,15 +57,16 @@
                                kInterfaceIndex)),
         dhcp_config_(new MockDHCPConfig(&control_interface_,
                                         kDeviceName)),
-        eap_listener_(new MockEapListener()) {
-    ON_CALL(dhcp_provider_, CreateConfig(_, _, _, _)).
-        WillByDefault(Return(dhcp_config_));
-    ON_CALL(*dhcp_config_.get(), RequestIP()).
-        WillByDefault(Return(true));
-  }
+        eap_listener_(new MockEapListener()),
+        mock_service_(new MockEthernetService(&control_interface_, &metrics_)),
+        proxy_factory_(this),
+        supplicant_interface_proxy_(
+            new NiceMock<MockSupplicantInterfaceProxy>()),
+        supplicant_process_proxy_(new NiceMock<MockSupplicantProcessProxy>()) {}
 
   virtual void SetUp() {
     ethernet_->rtnl_handler_ = &rtnl_handler_;
+    ethernet_->proxy_factory_ = &proxy_factory_;
     // Transfers ownership.
     ethernet_->eap_listener_.reset(eap_listener_);
 
@@ -70,23 +81,97 @@
   }
 
  protected:
+  class TestProxyFactory : public ProxyFactory {
+   public:
+    explicit TestProxyFactory(EthernetTest *test) : test_(test) {}
+
+    virtual SupplicantProcessProxyInterface *CreateSupplicantProcessProxy(
+        const char */*dbus_path*/, const char */*dbus_addr*/) {
+      return test_->supplicant_process_proxy_.release();
+    }
+
+    virtual SupplicantInterfaceProxyInterface *CreateSupplicantInterfaceProxy(
+        SupplicantEventDelegateInterface */*delegate*/,
+        const DBus::Path &/*object_path*/,
+        const char */*dbus_addr*/) {
+      return test_->supplicant_interface_proxy_.release();
+    }
+
+    MOCK_METHOD2(CreateSupplicantNetworkProxy,
+                 SupplicantNetworkProxyInterface *(
+                     const DBus::Path &object_path,
+                     const char *dbus_addr));
+
+   private:
+    EthernetTest *test_;
+  };
+
   static const char kDeviceName[];
   static const char kDeviceAddress[];
+  static const char kInterfacePath[];
   static const int kInterfaceIndex;
 
+  bool GetIsEapAuthenticated() { return ethernet_->is_eap_authenticated_; }
+  void SetIsEapAuthenticated(bool is_eap_authenticated) {
+    ethernet_->is_eap_authenticated_ = is_eap_authenticated;
+  }
+  bool GetIsEapDetected() { return ethernet_->is_eap_detected_; }
+  void SetIsEapDetected(bool is_eap_detected) {
+    ethernet_->is_eap_detected_ = is_eap_detected;
+  }
+  bool GetLinkUp() { return ethernet_->link_up_; }
+  const ServiceRefPtr &GetSelectedService() {
+    return ethernet_->selected_service();
+  }
+  ServiceRefPtr GetService() { return ethernet_->service_; }
+  void SetService(const EthernetServiceRefPtr &service) {
+    ethernet_->service_ = service;
+  }
+  const PropertyStore &GetStore() { return ethernet_->store(); }
   void StartEthernet() {
     EXPECT_CALL(rtnl_handler_,
                 SetInterfaceFlags(kInterfaceIndex, IFF_UP, IFF_UP));
     ethernet_->Start(NULL, EnabledStateChangedCallback());
   }
-  bool GetLinkUp() { return ethernet_->link_up_; }
-  bool GetIsEapDetected() { return ethernet_->is_eap_detected_; }
-  void SetIsEapDetected(bool is_eap_detected) {
-    ethernet_->is_eap_detected_ = is_eap_detected;
+  const SupplicantInterfaceProxyInterface *GetSupplicantInterfaceProxy() {
+    return ethernet_->supplicant_interface_proxy_.get();
   }
-  const PropertyStore &GetStore() { return ethernet_->store(); }
-  const ServiceRefPtr &GetService() { return ethernet_->service_; }
+  const SupplicantProcessProxyInterface *GetSupplicantProcessProxy() {
+    return ethernet_->supplicant_process_proxy_.get();
+  }
+  const string &GetSupplicantInterfacePath() {
+    return ethernet_->supplicant_interface_path_;
+  }
+  const string &GetSupplicantNetworkPath() {
+    return ethernet_->supplicant_network_path_;
+  }
+  void SetSupplicantNetworkPath(const string &network_path) {
+    ethernet_->supplicant_network_path_ = network_path;
+  }
+  bool InvokeStartSupplicant() {
+    return ethernet_->StartSupplicant();
+  }
+  void InvokeStopSupplicant() {
+    return ethernet_->StopSupplicant();
+  }
+  bool InvokeStartEapAuthentication() {
+    return ethernet_->StartEapAuthentication();
+  }
+  void StartSupplicant() {
+    SupplicantInterfaceProxyInterface *interface =
+        supplicant_interface_proxy_.get();
+    SupplicantProcessProxyInterface *process = supplicant_process_proxy_.get();
+    EXPECT_CALL(*supplicant_process_proxy_.get(), CreateInterface(_))
+        .WillOnce(Return(kInterfacePath));
+    EXPECT_TRUE(InvokeStartSupplicant());
+    EXPECT_EQ(interface, GetSupplicantInterfaceProxy());
+    EXPECT_EQ(process, GetSupplicantProcessProxy());
+    EXPECT_EQ(kInterfacePath, GetSupplicantInterfacePath());
+  }
   void TriggerOnEapDetected() { ethernet_->OnEapDetected(); }
+  void TriggerCertification(const string &subject, uint32 depth) {
+    ethernet_->CertificationTask(subject, depth);
+  }
 
   StrictMock<MockEventDispatcher> dispatcher_;
   MockGLib glib_;
@@ -97,19 +182,28 @@
   EthernetRefPtr ethernet_;
   MockDHCPProvider dhcp_provider_;
   scoped_refptr<MockDHCPConfig> dhcp_config_;
-  MockRTNLHandler rtnl_handler_;
+
   // Owned by Ethernet instance, but tracked here for expectations.
   MockEapListener *eap_listener_;
+
+  MockRTNLHandler rtnl_handler_;
+  scoped_refptr<MockEthernetService> mock_service_;
+  NiceMock<TestProxyFactory> proxy_factory_;
+  scoped_ptr<MockSupplicantInterfaceProxy> supplicant_interface_proxy_;
+  scoped_ptr<MockSupplicantProcessProxy> supplicant_process_proxy_;
 };
 
 // static
 const char EthernetTest::kDeviceName[] = "eth0";
 const char EthernetTest::kDeviceAddress[] = "000102030405";
+const char EthernetTest::kInterfacePath[] = "/interface/path";
 const int EthernetTest::kInterfaceIndex = 123;
 
 TEST_F(EthernetTest, Construct) {
   EXPECT_FALSE(GetLinkUp());
+  EXPECT_FALSE(GetIsEapAuthenticated());
   EXPECT_FALSE(GetIsEapDetected());
+  EXPECT_TRUE(GetStore().Contains(kEapAuthenticationCompletedProperty));
   EXPECT_TRUE(GetStore().Contains(kEapAuthenticatorDetectedProperty));
   EXPECT_EQ(NULL, GetService().get());
 }
@@ -164,6 +258,38 @@
   EXPECT_CALL(manager_, UpdateEnabledTechnologies()).Times(AnyNumber());
 }
 
+TEST_F(EthernetTest, ConnectToFailure) {
+  StartEthernet();
+  SetService(mock_service_);
+  EXPECT_EQ(NULL, GetSelectedService().get());
+  EXPECT_CALL(dhcp_provider_, CreateConfig(_, _, _, _)).
+      WillOnce(Return(dhcp_config_));
+  EXPECT_CALL(*dhcp_config_.get(), RequestIP()).WillOnce(Return(false));
+  EXPECT_CALL(dispatcher_, PostTask(_));  // Posts ConfigureStaticIPTask.
+  // Since we never called SelectService()...
+  EXPECT_CALL(*mock_service_, SetState(_)).Times(0);
+  ethernet_->ConnectTo(mock_service_);
+  EXPECT_EQ(NULL, GetSelectedService().get());
+}
+
+TEST_F(EthernetTest, ConnectToSuccess) {
+  StartEthernet();
+  SetService(mock_service_);
+  EXPECT_EQ(NULL, GetSelectedService().get());
+  EXPECT_CALL(dhcp_provider_, CreateConfig(_, _, _, _)).
+      WillOnce(Return(dhcp_config_));
+  EXPECT_CALL(*dhcp_config_.get(), RequestIP()).WillOnce(Return(true));
+  EXPECT_CALL(dispatcher_, PostTask(_));  // Posts ConfigureStaticIPTask.
+  EXPECT_CALL(*mock_service_, SetState(Service::kStateConfiguring));
+  ethernet_->ConnectTo(mock_service_.get());
+  EXPECT_EQ(GetService().get(), GetSelectedService().get());
+  Mock::VerifyAndClearExpectations(mock_service_);
+
+  EXPECT_CALL(*mock_service_, SetState(Service::kStateIdle));
+  ethernet_->DisconnectFrom(mock_service_);
+  EXPECT_EQ(NULL, GetSelectedService().get());
+}
+
 TEST_F(EthernetTest, OnEapDetected) {
   EXPECT_FALSE(GetIsEapDetected());
   EXPECT_CALL(*eap_listener_, Stop());
@@ -171,4 +297,158 @@
   EXPECT_TRUE(GetIsEapDetected());
 }
 
+TEST_F(EthernetTest, TryEapAuthenticationNoService) {
+  EXPECT_CALL(*mock_service_, Is8021xConnectable()).Times(0);
+  NiceScopedMockLog log;
+  EXPECT_CALL(log, Log(logging::LOG_INFO, _,
+                       EndsWith("Service is missing; "
+                                "not doing EAP authentication.")));
+  ethernet_->TryEapAuthentication();
+}
+
+TEST_F(EthernetTest, TryEapAuthenticationNotConnectableNotAuthenticated) {
+  SetService(mock_service_);
+  EXPECT_CALL(*mock_service_, Is8021xConnectable()).WillOnce(Return(false));
+  NiceScopedMockLog log;
+  EXPECT_CALL(log, Log(logging::LOG_INFO, _,
+                       EndsWith("Service lacks 802.1X credentials; "
+                                "not doing EAP authentication.")));
+  ethernet_->TryEapAuthentication();
+}
+
+TEST_F(EthernetTest, TryEapAuthenticationNotConnectableAuthenticated) {
+  SetService(mock_service_);
+  SetIsEapAuthenticated(true);
+  EXPECT_CALL(*mock_service_, Is8021xConnectable()).WillOnce(Return(false));
+  NiceScopedMockLog log;
+  EXPECT_CALL(log, Log(logging::LOG_INFO, _,
+                       EndsWith("Service lost 802.1X credentials; "
+                                "terminating EAP authentication.")));
+  ethernet_->TryEapAuthentication();
+  EXPECT_FALSE(GetIsEapAuthenticated());
+}
+
+TEST_F(EthernetTest, TryEapAuthenticationEapNotDetected) {
+  SetService(mock_service_);
+  EXPECT_CALL(*mock_service_, Is8021xConnectable()).WillOnce(Return(true));
+  NiceScopedMockLog log;
+  EXPECT_CALL(log, Log(logging::LOG_INFO, _,
+                       EndsWith("EAP authenticator not detected; "
+                                "not doing EAP authentication.")));
+  ethernet_->TryEapAuthentication();
+}
+
+TEST_F(EthernetTest, StartSupplicant) {
+  // Save the mock proxy pointers before the Ethernet instance accepts it.
+  MockSupplicantInterfaceProxy *interface_proxy =
+      supplicant_interface_proxy_.get();
+  MockSupplicantProcessProxy *process_proxy = supplicant_process_proxy_.get();
+
+  StartSupplicant();
+
+  // Starting it again should not invoke another call to create an interface.
+  Mock::VerifyAndClearExpectations(process_proxy);
+  EXPECT_CALL(*process_proxy, CreateInterface(_)).Times(0);
+  EXPECT_TRUE(InvokeStartSupplicant());
+
+  // Also, the mock pointers should remain; if the TestProxyFactory was
+  // invoked again, they would be NULL.
+  EXPECT_EQ(interface_proxy, GetSupplicantInterfaceProxy());
+  EXPECT_EQ(process_proxy, GetSupplicantProcessProxy());
+  EXPECT_EQ(kInterfacePath, GetSupplicantInterfacePath());
+}
+
+TEST_F(EthernetTest, StartSupplicantWithInterfaceExistsException) {
+  MockSupplicantInterfaceProxy *interface_proxy =
+      supplicant_interface_proxy_.get();
+  MockSupplicantProcessProxy *process_proxy = supplicant_process_proxy_.get();
+  EXPECT_CALL(*process_proxy, CreateInterface(_))
+      .WillOnce(Throw(DBus::Error(
+          "fi.w1.wpa_supplicant1.InterfaceExists",
+          "test threw fi.w1.wpa_supplicant1.InterfaceExists")));
+  EXPECT_CALL(*process_proxy, GetInterface(kDeviceName))
+      .WillOnce(Return(kInterfacePath));
+  EXPECT_TRUE(InvokeStartSupplicant());
+  EXPECT_EQ(interface_proxy, GetSupplicantInterfaceProxy());
+  EXPECT_EQ(process_proxy, GetSupplicantProcessProxy());
+  EXPECT_EQ(kInterfacePath, GetSupplicantInterfacePath());
+}
+
+TEST_F(EthernetTest, StartSupplicantWithUnknownException) {
+  MockSupplicantProcessProxy *process_proxy = supplicant_process_proxy_.get();
+  EXPECT_CALL(*process_proxy, CreateInterface(_))
+      .WillOnce(Throw(DBus::Error(
+          "fi.w1.wpa_supplicant1.UnknownError",
+          "test threw fi.w1.wpa_supplicant1.UnknownError")));
+  EXPECT_CALL(*process_proxy, GetInterface(kDeviceName)).Times(0);
+  EXPECT_FALSE(InvokeStartSupplicant());
+  EXPECT_EQ(NULL, GetSupplicantInterfaceProxy());
+  EXPECT_EQ(NULL, GetSupplicantProcessProxy());
+  EXPECT_EQ("", GetSupplicantInterfacePath());
+}
+
+TEST_F(EthernetTest, StartEapAuthentication) {
+  MockSupplicantInterfaceProxy *interface_proxy =
+      supplicant_interface_proxy_.get();
+
+  StartSupplicant();
+  SetService(mock_service_);
+
+  EXPECT_CALL(*mock_service_, ClearEAPCertification());
+  EXPECT_CALL(*interface_proxy, RemoveNetwork(_)).Times(0);
+  EXPECT_CALL(*interface_proxy, AddNetwork(_))
+      .WillOnce(Throw(DBus::Error(
+          "fi.w1.wpa_supplicant1.UnknownError",
+          "test threw fi.w1.wpa_supplicant1.UnknownError")));
+  EXPECT_CALL(*interface_proxy, SelectNetwork(_)).Times(0);
+  EXPECT_FALSE(InvokeStartEapAuthentication());
+  Mock::VerifyAndClearExpectations(mock_service_);
+  Mock::VerifyAndClearExpectations(interface_proxy);
+  EXPECT_EQ("", GetSupplicantNetworkPath());
+
+  EXPECT_CALL(*mock_service_, ClearEAPCertification());
+  EXPECT_CALL(*interface_proxy, RemoveNetwork(_)).Times(0);
+  const char kFirstNetworkPath[] = "/network/first-path";
+  EXPECT_CALL(*interface_proxy, AddNetwork(_))
+      .WillOnce(Return(kFirstNetworkPath));
+  EXPECT_CALL(*interface_proxy, SelectNetwork(StrEq(kFirstNetworkPath)));
+  EXPECT_TRUE(InvokeStartEapAuthentication());
+  Mock::VerifyAndClearExpectations(mock_service_);
+  Mock::VerifyAndClearExpectations(interface_proxy);
+  EXPECT_EQ(kFirstNetworkPath, GetSupplicantNetworkPath());
+
+  EXPECT_CALL(*mock_service_, ClearEAPCertification());
+  EXPECT_CALL(*interface_proxy, RemoveNetwork(StrEq(kFirstNetworkPath)));
+  const char kSecondNetworkPath[] = "/network/second-path";
+  EXPECT_CALL(*interface_proxy, AddNetwork(_))
+      .WillOnce(Return(kSecondNetworkPath));
+  EXPECT_CALL(*interface_proxy, SelectNetwork(StrEq(kSecondNetworkPath)));
+  EXPECT_TRUE(InvokeStartEapAuthentication());
+  EXPECT_EQ(kSecondNetworkPath, GetSupplicantNetworkPath());
+}
+
+TEST_F(EthernetTest, StopSupplicant) {
+  MockSupplicantProcessProxy *process_proxy = supplicant_process_proxy_.get();
+  StartSupplicant();
+  SetIsEapAuthenticated(true);
+  SetSupplicantNetworkPath("/network/1");
+  EXPECT_CALL(*process_proxy, RemoveInterface(StrEq(kInterfacePath)));
+  InvokeStopSupplicant();
+  EXPECT_EQ(NULL, GetSupplicantInterfaceProxy());
+  EXPECT_EQ(NULL, GetSupplicantProcessProxy());
+  EXPECT_EQ("", GetSupplicantInterfacePath());
+  EXPECT_EQ("", GetSupplicantNetworkPath());
+  EXPECT_FALSE(GetIsEapAuthenticated());
+}
+
+TEST_F(EthernetTest, Certification) {
+  const string kSubjectName("subject-name");
+  const uint32 kDepth = 123;
+  // Should not crash due to no service_.
+  TriggerCertification(kSubjectName, kDepth);
+  EXPECT_CALL(*mock_service_, AddEAPCertification(kSubjectName, kDepth));
+  SetService(mock_service_);
+  TriggerCertification(kSubjectName, kDepth);
+}
+
 }  // namespace shill
diff --git a/mock_ethernet_service.cc b/mock_ethernet_service.cc
new file mode 100644
index 0000000..f39ef6d
--- /dev/null
+++ b/mock_ethernet_service.cc
@@ -0,0 +1,18 @@
+// Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "shill/mock_ethernet_service.h"
+
+#include "shill/ethernet.h"  // Needed to pass an EthernetRefPtr.
+
+namespace shill {
+
+MockEthernetService::MockEthernetService(ControlInterface *control_interface,
+                                         Metrics *metrics)
+    : EthernetService(control_interface, NULL, metrics, NULL,
+                      EthernetRefPtr()) {}
+
+MockEthernetService::~MockEthernetService() {}
+
+}  // namespace shill
diff --git a/mock_ethernet_service.h b/mock_ethernet_service.h
new file mode 100644
index 0000000..a5039bf
--- /dev/null
+++ b/mock_ethernet_service.h
@@ -0,0 +1,41 @@
+// Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SHILL_MOCK_ETHERNET_SERVICE_H_
+#define SHILL_MOCK_ETHERNET_SERVICE_H_
+
+#include <gmock/gmock.h>
+
+#include "shill/ethernet_service.h"
+
+namespace shill {
+
+class MockEthernetService : public EthernetService {
+ public:
+  MockEthernetService(ControlInterface *control_interface, Metrics *metrics);
+  virtual ~MockEthernetService();
+
+  MOCK_METHOD2(AddEAPCertification, bool(const std::string &name,
+                                         size_t depth));
+  MOCK_METHOD0(ClearEAPCertification, void ());
+  MOCK_METHOD2(Configure, void(const KeyValueStore &args, Error *error));
+  MOCK_METHOD2(DisconnectWithFailure,
+               void(ConnectFailure failure, Error *error));
+  MOCK_METHOD1(GetDeviceRpcId, std::string(Error *error));
+  MOCK_CONST_METHOD0(GetStorageIdentifier, std::string());
+  MOCK_CONST_METHOD0(Is8021xConnectable, bool());
+  MOCK_CONST_METHOD0(IsConnected, bool());
+  MOCK_CONST_METHOD0(IsConnecting, bool());
+  MOCK_CONST_METHOD0(IsRemembered, bool());
+  MOCK_METHOD1(SetFailure, void(ConnectFailure failure));
+  MOCK_METHOD1(SetFailureSilent, void(ConnectFailure failure));
+  MOCK_METHOD1(SetState, void(ConnectState state));
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MockEthernetService);
+};
+
+}  // namespace shill
+
+#endif  // SHILL_MOCK_ETHERNET_SERVICE_H_
diff --git a/mock_supplicant_interface_proxy.cc b/mock_supplicant_interface_proxy.cc
index 72a2fb7..9a67665 100644
--- a/mock_supplicant_interface_proxy.cc
+++ b/mock_supplicant_interface_proxy.cc
@@ -8,9 +8,7 @@
 
 namespace shill {
 
-MockSupplicantInterfaceProxy::MockSupplicantInterfaceProxy(
-    const WiFiRefPtr &wifi) : wifi_(wifi) {}
-
+MockSupplicantInterfaceProxy::MockSupplicantInterfaceProxy() {}
 MockSupplicantInterfaceProxy::~MockSupplicantInterfaceProxy() {}
 
 }  // namespace shill
diff --git a/mock_supplicant_interface_proxy.h b/mock_supplicant_interface_proxy.h
index 289231c..d2dc3b8 100644
--- a/mock_supplicant_interface_proxy.h
+++ b/mock_supplicant_interface_proxy.h
@@ -18,7 +18,7 @@
 
 class MockSupplicantInterfaceProxy : public SupplicantInterfaceProxyInterface {
  public:
-  explicit MockSupplicantInterfaceProxy(const WiFiRefPtr &wifi);
+  explicit MockSupplicantInterfaceProxy();
   virtual ~MockSupplicantInterfaceProxy();
 
   MOCK_METHOD1(AddNetwork, ::DBus::Path(
@@ -37,10 +37,6 @@
   MOCK_METHOD1(SetDisableHighBitrates, void(bool disable_high_bitrates));
 
  private:
-  // wifi_ is not used explicitly but its presence here tests that WiFi::Stop
-  // properly removes cyclic references.
-  WiFiRefPtr wifi_;
-
   DISALLOW_COPY_AND_ASSIGN(MockSupplicantInterfaceProxy);
 };
 
diff --git a/service.h b/service.h
index 98234f9..48b47ea 100644
--- a/service.h
+++ b/service.h
@@ -291,13 +291,13 @@
 
   // Examines the EAP credentials for the service and returns true if a
   // connection attempt can be made.
-  bool Is8021xConnectable() const;
+  virtual bool Is8021xConnectable() const;
 
   // Add an EAP certification id |name| at position |depth| in the stack.
   // Returns true if entry was added, false otherwise.
   virtual bool AddEAPCertification(const std::string &name, size_t depth);
   // Clear all EAP certification elements.
-  void ClearEAPCertification();
+  virtual void ClearEAPCertification();
 
   // The inherited class should register any custom metrics in this method.
   virtual void InitializeCustomMetrics() const {}
diff --git a/supplicant_event_delegate_interface.h b/supplicant_event_delegate_interface.h
index c4b3383..9347061 100644
--- a/supplicant_event_delegate_interface.h
+++ b/supplicant_event_delegate_interface.h
@@ -21,13 +21,26 @@
 
   virtual ~SupplicantEventDelegateInterface() {}
 
+  // Supplicant has added a BSS to its table of visible endpoints.
   virtual void BSSAdded(const ::DBus::Path &BSS,
                         const PropertyMap &properties) = 0;
+
+  // Supplicant has removed a BSS from its table of visible endpoints.
   virtual void BSSRemoved(const ::DBus::Path &BSS) = 0;
+
+  // Supplicant has received a certficate from the remote server during
+  // the process of authentication.
   virtual void Certification(const PropertyMap &properties) = 0;
+
+  // Supplicant state machine has output an EAP event notification.
   virtual void EAPEvent(const std::string &status,
                         const std::string &parameter) = 0;
+
+  // The interface element in the supplicant has changed one or more
+  // properties.
   virtual void PropertiesChanged(const PropertyMap &properties) = 0;
+
+  // A scan has completed on this interface.
   virtual void ScanDone() = 0;
 };
 
diff --git a/wifi_service.cc b/wifi_service.cc
index 3218b72..3e5ed3f 100644
--- a/wifi_service.cc
+++ b/wifi_service.cc
@@ -1017,7 +1017,7 @@
 
   // Dynamic WEP + 802.1x.
   if (security_ == flimflam::kSecurityWep &&
-      GetEAPKeyManagement() == "IEEE8021X")
+      GetEAPKeyManagement() == WPASupplicant::kKeyManagementIeee8021X)
     return true;
   return false;
 }
diff --git a/wifi_unittest.cc b/wifi_unittest.cc
index b718d01..5f57331 100644
--- a/wifi_unittest.cc
+++ b/wifi_unittest.cc
@@ -216,7 +216,7 @@
         dbus_manager_(new NiceMock<MockDBusManager>()),
         eap_state_handler_(new NiceMock<MockSupplicantEAPStateHandler>()),
         supplicant_interface_proxy_(
-            new NiceMock<MockSupplicantInterfaceProxy>(wifi_)),
+            new NiceMock<MockSupplicantInterfaceProxy>()),
         proxy_factory_(this) {
     ::testing::DefaultValue< ::DBus::Path>::Set("/default/path");
 
diff --git a/wpa_supplicant.cc b/wpa_supplicant.cc
index 7dc9179..4f0edf2 100644
--- a/wpa_supplicant.cc
+++ b/wpa_supplicant.cc
@@ -76,6 +76,7 @@
 const char WPASupplicant::kInterfaceStateGroupHandshake[] = "group_handshake";
 const char WPASupplicant::kInterfaceStateInactive[] = "inactive";
 const char WPASupplicant::kInterfaceStateScanning[] = "scanning";
+const char WPASupplicant::kKeyManagementIeee8021X[] = "IEEE8021X";
 const char WPASupplicant::kKeyManagementMethodSuffixEAP[] = "-eap";
 const char WPASupplicant::kKeyManagementMethodSuffixPSK[] = "-psk";
 const char WPASupplicant::kKeyModeNone[] = "NONE";
@@ -106,6 +107,7 @@
 const char WPASupplicant::kNetworkPropertyEapCaCertId[] = "ca_cert_id";
 const char WPASupplicant::kNetworkPropertyEapPin[] = "pin";
 const char WPASupplicant::kNetworkPropertyEapSubjectMatch[] = "subject_match";
+const char WPASupplicant::kNetworkPropertyEapolFlags[] = "eapol_flags";
 const char WPASupplicant::kNetworkPropertyEngine[] = "engine";
 const char WPASupplicant::kNetworkPropertyEngineId[] = "engine_id";
 const char WPASupplicant::kNetworkPropertyFrequency[] = "frequency";
diff --git a/wpa_supplicant.h b/wpa_supplicant.h
index a47c67d..78260e9 100644
--- a/wpa_supplicant.h
+++ b/wpa_supplicant.h
@@ -68,6 +68,7 @@
   static const char kInterfaceStateGroupHandshake[];
   static const char kInterfaceStateInactive[];
   static const char kInterfaceStateScanning[];
+  static const char kKeyManagementIeee8021X[];
   static const char kKeyManagementMethodSuffixEAP[];
   static const char kKeyManagementMethodSuffixPSK[];
   static const char kKeyModeNone[];
@@ -96,6 +97,7 @@
   static const char kNetworkPropertyEapCaCertId[];
   static const char kNetworkPropertyEapPin[];
   static const char kNetworkPropertyEapSubjectMatch[];
+  static const char kNetworkPropertyEapolFlags[];
   static const char kNetworkPropertyEngine[];
   static const char kNetworkPropertyEngineId[];
   static const char kNetworkPropertyFrequency[];