vpn_manager: IpsecManager: Add XAUTH authentication

Add a new  flag "--xauth_credentials_file" which IpsecManager
uses to extract an XAUTH username and password.  With this
file supplied, strongSwan will be used for "xauthpsk" authentication
instead of "psk" and an XAUTH credential will be added to the
secrets file.

BUG=chromium:267647
TEST=Unit test; upcoming VPNConnect/l2tpipsec_xauth

Change-Id: Ic1a0c62213f4cd6f6c336b1af27af2d3a59d3be8
Reviewed-on: https://chromium-review.googlesource.com/181130
Reviewed-by: Ben Chan <benchan@chromium.org>
Tested-by: Paul Stewart <pstew@chromium.org>
Commit-Queue: Paul Stewart <pstew@chromium.org>
diff --git a/ipsec_manager.cc b/ipsec_manager.cc
index 69addb7..bf36db6 100644
--- a/ipsec_manager.cc
+++ b/ipsec_manager.cc
@@ -17,6 +17,7 @@
 #include "base/logging.h"
 #include "base/posix/eintr_wrapper.h"
 #include "base/string_number_conversions.h"
+#include "base/string_split.h"
 #include "base/string_util.h"
 #include "chromeos/process.h"
 #include "gflags/gflags.h"
@@ -87,6 +88,7 @@
 bool IpsecManager::Initialize(int ike_version,
                               const struct sockaddr& remote_address,
                               const std::string& psk_file,
+                              const std::string& xauth_credentials_file,
                               const std::string& server_ca_file,
                               const std::string& server_id,
                               const std::string& client_cert_slot,
@@ -164,6 +166,16 @@
     psk_file_ = psk_file;
   }
 
+  if (!xauth_credentials_file.empty()) {
+    if (!file_util::PathExists(FilePath(xauth_credentials_file))) {
+      LOG(ERROR) << "Invalid xauth credentials file: "
+                 << xauth_credentials_file;
+      RegisterError(kServiceErrorInvalidArgument);
+      return false;
+    }
+    xauth_credentials_file_ = xauth_credentials_file;
+  }
+
   if (ike_version != 1 && ike_version != 2) {
     LOG(ERROR) << "Unsupported IKE version" << ike_version;
     RegisterError(kServiceErrorInvalidArgument);
@@ -209,7 +221,7 @@
   return true;
 }
 
-bool IpsecManager::FormatSecrets(std::string* formatted) {
+bool IpsecManager::FormatIpsecSecret(std::string *formatted) {
   std::string secret_mode;
   std::string secret;
   if (psk_file_.empty()) {
@@ -246,6 +258,45 @@
   return true;
 }
 
+bool IpsecManager::FormatXauthSecret(std::string* formatted) {
+  if (xauth_credentials_file_.empty()) {
+    xauth_identity_ = "";
+    return true;
+  }
+
+  std::string xauth_contents;
+  if (!file_util::ReadFileToString(FilePath(xauth_credentials_file_),
+                                   &xauth_contents)) {
+    LOG(ERROR) << "Unable to read XAUTH credentials from "
+               << xauth_credentials_file_;
+    return false;
+  }
+  std::vector<std::string> xauth_parts;
+  base::SplitString(xauth_contents, '\n', &xauth_parts);
+  if (xauth_parts.size() < 2) {
+    LOG(ERROR) << "Unable to parse XAUTH credentials from "
+               << xauth_credentials_file_;
+    return false;
+  }
+
+  // Save this identity for use in the ipsec starter file.
+  xauth_identity_ = xauth_parts[0];
+  std::string xauth_password = xauth_parts[1];
+  *formatted = StringPrintf(
+      "%s : XAUTH \"%s\"\n", xauth_identity_.c_str(), xauth_password.c_str());
+  return true;
+}
+
+bool IpsecManager::FormatSecrets(std::string* formatted) {
+  std::string ipsec_secret, xauth_secret;
+  if (!FormatIpsecSecret(&ipsec_secret) || !FormatXauthSecret(&xauth_secret)) {
+    return false;
+  }
+
+  *formatted = ipsec_secret + xauth_secret;
+  return true;
+}
+
 void IpsecManager::KillCurrentlyRunning() {
   starter_daemon_->FindProcess();
   charon_daemon_->FindProcess();
@@ -319,7 +370,15 @@
   AppendStringSetting(&config, "esp", FLAGS_esp);
   AppendStringSetting(&config, "keyexchange",
                       ike_version_ == 1 ? "ikev1" : "ikev2");
-  if (!psk_file_.empty()) AppendStringSetting(&config, "authby", "psk");
+  if (!psk_file_.empty()) {
+    if (!xauth_identity_.empty()) {
+      AppendStringSetting(&config, "authby", "xauthpsk");
+      AppendStringSetting(&config, "xauth", "client");
+      AppendStringSetting(&config, "xauth_identity", xauth_identity_);
+    } else {
+      AppendStringSetting(&config, "authby", "psk");
+    }
+  }
   AppendBoolSetting(&config, "rekey", FLAGS_rekey);
   AppendStringSetting(&config, "left", "%defaultroute");
   if (!client_cert_slot_.empty()) {
diff --git a/ipsec_manager.h b/ipsec_manager.h
index bb1b3bc..daa3e1f 100644
--- a/ipsec_manager.h
+++ b/ipsec_manager.h
@@ -33,6 +33,7 @@
   bool Initialize(int ike_version,
                   const struct sockaddr& remote_address,
                   const std::string& psk_file,
+                  const std::string& xauth_credentials_file,
                   const std::string& server_ca_file,
                   const std::string& server_id,
                   const std::string& client_cert_tpm_slot,
@@ -59,6 +60,7 @@
   FRIEND_TEST(IpsecManagerTest, PollNothingIfRunning);
   FRIEND_TEST(IpsecManagerTest, FormatSecretsNoSlot);
   FRIEND_TEST(IpsecManagerTest, FormatSecretsNonZeroSlot);
+  FRIEND_TEST(IpsecManagerTest, FormatSecretsXauthCredentials);
   FRIEND_TEST(IpsecManagerTest, FormatStrongswanConfigFile);
   FRIEND_TEST(IpsecManagerTest, StartStarter);
   FRIEND_TEST(IpsecManagerTestIkeV1Psk, FormatSecrets);
@@ -72,6 +74,8 @@
 
   bool ReadCertificateSubject(const base::FilePath& filepath,
                               std::string* output);
+  bool FormatIpsecSecret(std::string* formatted);
+  bool FormatXauthSecret(std::string* formatted);
   bool FormatSecrets(std::string* formatted);
   void KillCurrentlyRunning();
   bool WriteConfigFile(const std::string &output_name,
@@ -130,6 +134,10 @@
   std::string user_pin_;
   // Last partial line read from output_fd_.
   std::string partial_output_line_;
+  // File containing the XAUTH username and password.
+  std::string xauth_credentials_file_;
+  // File containing the XAUTH username gained from FormatSecrets().
+  std::string xauth_identity_;
   // Time when ipsec was started.
   base::TimeTicks start_ticks_;
   // IPsec starter daemon.
diff --git a/ipsec_manager_test.cc b/ipsec_manager_test.cc
index 514c8de..556d825 100644
--- a/ipsec_manager_test.cc
+++ b/ipsec_manager_test.cc
@@ -5,6 +5,7 @@
 #include <base/command_line.h>
 #include <base/file_util.h>
 #include <base/files/scoped_temp_dir.h>
+#include <base/stringprintf.h>
 #include <chromeos/process_mock.h>
 #include <chromeos/syslog_logging.h>
 #include <chromeos/test_helpers.h>
@@ -52,6 +53,7 @@
     ServiceManager::temp_path_ = new FilePath(test_path_);
     ServiceManager::temp_base_path_ = test_path_.value().c_str();
     psk_file_ = test_path_.Append("psk").value();
+    xauth_credentials_file_ = test_path_.Append("xauth_credentials").value();
     server_ca_file_ = test_path_.Append("server.ca").value();
     WriteFile(server_ca_file_, "contents not used for testing");
     server_id_ = "CN=vpnserver";
@@ -61,6 +63,9 @@
     ipsec_run_path_ = test_path_.Append("run").value();
     ipsec_up_file_ = FilePath(ipsec_run_path_).Append("up").value();
     WriteFile(psk_file_, "secret");
+    WriteFile(xauth_credentials_file_,
+              base::StringPrintf("%s\n%s\n",
+                                 kXauthUser, kXauthPassword).c_str());
     const char *srcvar = getenv("SRC");
     FilePath srcdir = srcvar ? FilePath(srcvar) : cwd;
     file_util::CopyFile(srcdir.Append("testdata/cacert.der"),
@@ -83,6 +88,9 @@
   ProcessMock *SetStartStarterExpectations();
 
  protected:
+  static const char kXauthUser[];
+  static const char kXauthPassword[];
+
   void WriteFile(const std::string& file_path, const char* contents) {
     if (file_util::WriteFile(FilePath(file_path), contents,
                              strlen(contents)) < 0) {
@@ -90,7 +98,7 @@
     }
   }
 
-  void DoInitialize(int ike_version, bool use_psk);
+  void DoInitialize(int ike_version, bool use_psk, bool use_xauth);
 
   IpsecManager ipsec_;
   FilePath persistent_path_;
@@ -99,6 +107,7 @@
   struct sockaddr remote_address_;
   std::string psk_file_;
   std::string server_ca_file_;
+  std::string xauth_credentials_file_;
   std::string server_id_;
   std::string client_cert_tpm_slot_;
   std::string client_cert_tpm_id_;
@@ -112,12 +121,21 @@
   base::ScopedTempDir temp_dir_;
 };
 
-void IpsecManagerTest::DoInitialize(int ike_version, bool use_psk) {
+// static
+const char IpsecManagerTest::kXauthUser[] = "xauth_user";
+const char IpsecManagerTest::kXauthPassword[] = "xauth_password";
+
+void IpsecManagerTest::DoInitialize(
+    int ike_version, bool use_psk, bool use_xauth) {
+  std::string xauth_file;
+  if (use_xauth) {
+    xauth_file = xauth_credentials_file_;
+  }
   if (use_psk) {
     ASSERT_TRUE(ipsec_.Initialize(ike_version, remote_address_, psk_file_,
-                                  "", "", "", "", ""));
+                                  xauth_file, "", "", "", "", ""));
   } else {
-    ASSERT_TRUE(ipsec_.Initialize(ike_version, remote_address_, "",
+    ASSERT_TRUE(ipsec_.Initialize(ike_version, remote_address_, "", xauth_file,
                                   server_ca_file_, server_id_,
                                   client_cert_tpm_slot_, client_cert_tpm_id_,
                                   tpm_user_pin_));
@@ -144,13 +162,15 @@
 }
 
 TEST_F(IpsecManagerTest, InitializeNoAuth) {
-  EXPECT_FALSE(ipsec_.Initialize(1, remote_address_, "", "", "", "", "", ""));
+  EXPECT_FALSE(ipsec_.Initialize(
+                   1, remote_address_, "", "", "", "", "", "", ""));
   EXPECT_TRUE(FindLog("Must specify either PSK or certificates"));
 }
 
 TEST_F(IpsecManagerTest, InitializeNotBoth) {
   EXPECT_TRUE(ipsec_.Initialize(1, remote_address_,
                                 psk_file_,
+                                "",
                                 server_ca_file_,
                                 server_id_,
                                 client_cert_tpm_slot_,
@@ -160,13 +180,13 @@
 }
 
 TEST_F(IpsecManagerTest, InitializeUnsupportedVersion) {
-  EXPECT_FALSE(ipsec_.Initialize(3, remote_address_, psk_file_, "", "", "",
+  EXPECT_FALSE(ipsec_.Initialize(3, remote_address_, psk_file_, "", "", "", "",
                                  "", ""));
   EXPECT_TRUE(FindLog("Unsupported IKE version"));
 }
 
 TEST_F(IpsecManagerTest, InitializeIkev2WithCertificates) {
-  EXPECT_FALSE(ipsec_.Initialize(2, remote_address_, "",
+  EXPECT_FALSE(ipsec_.Initialize(2, remote_address_, "", "",
                                  server_ca_file_,
                                  server_id_,
                                  client_cert_tpm_slot_,
@@ -213,7 +233,7 @@
 
 TEST_F(IpsecManagerTest, FormatSecretsNoSlot) {
   client_cert_tpm_slot_ = "";
-  DoInitialize(1, false);
+  DoInitialize(1, false, false);
   std::string formatted;
   EXPECT_TRUE(ipsec_.FormatSecrets(&formatted));
   EXPECT_EQ("5.6.7.8 1.2.3.4 : PIN \%smartcard0@crypto_module:0a \"123456\"\n",
@@ -222,13 +242,25 @@
 
 TEST_F(IpsecManagerTest, FormatSecretsNonZeroSlot) {
   client_cert_tpm_slot_ = "1";
-  DoInitialize(1, false);
+  DoInitialize(1, false, false);
   std::string formatted;
   EXPECT_TRUE(ipsec_.FormatSecrets(&formatted));
   EXPECT_EQ("5.6.7.8 1.2.3.4 : PIN \%smartcard1@crypto_module:0a \"123456\"\n",
             formatted);
 }
 
+TEST_F(IpsecManagerTest, FormatSecretsXauthCredentials) {
+  client_cert_tpm_slot_ = "1";
+  DoInitialize(1, false, true);
+  std::string formatted;
+  EXPECT_TRUE(ipsec_.FormatSecrets(&formatted));
+  EXPECT_EQ(
+      base::StringPrintf(
+          "5.6.7.8 1.2.3.4 : PIN %%smartcard1@crypto_module:0a \"123456\"\n"
+          "%s : XAUTH \"%s\"\n", kXauthUser, kXauthPassword), formatted);
+  EXPECT_EQ(kXauthUser, ipsec_.xauth_identity_);
+}
+
 TEST_F(IpsecManagerTest, FormatStrongswanConfigFile) {
   std::string strongswan_config(
       "libstrongswan {\n"
@@ -282,11 +314,11 @@
  public:
   void SetUp() {
     IpsecManagerTest::SetUp();
-    DoInitialize(1, true);
+    DoInitialize(1, true, false);
   }
 
  protected:
-  std::string GetExpectedStarter(bool debug);
+  std::string GetExpectedStarter(bool debug, bool xauth);
 };
 
 TEST_F(IpsecManagerTestIkeV1Psk, Initialize) {
@@ -301,7 +333,8 @@
   EXPECT_EQ("5.6.7.8 1.2.3.4 : PSK \"pAssword\"\n", formatted);
 }
 
-std::string IpsecManagerTestIkeV1Psk::GetExpectedStarter(bool debug) {
+std::string IpsecManagerTestIkeV1Psk::GetExpectedStarter(
+    bool debug, bool xauth) {
   std::string expected(
       "config setup\n");
   if (debug) {
@@ -311,8 +344,18 @@
       "conn managed\n"
       "\tike=\"3des-sha1-modp1024\"\n"
       "\tesp=\"aes128-sha1,3des-sha1,aes128-md5,3des-md5\"\n"
-      "\tkeyexchange=\"ikev1\"\n"
-      "\tauthby=\"psk\"\n"
+      "\tkeyexchange=\"ikev1\"\n");
+
+  if (xauth) {
+    expected.append(base::StringPrintf(
+        "\tauthby=\"xauthpsk\"\n"
+        "\txauth=\"client\"\n"
+        "\txauth_identity=\"%s\"\n", kXauthUser));
+  } else {
+    expected.append("\tauthby=\"psk\"\n");
+  }
+
+  expected.append(
       "\trekey=yes\n"
       "\tleft=\"\%defaultroute\"\n"
       "\tleftprotoport=\"17/1701\"\n"
@@ -326,9 +369,13 @@
 }
 
 TEST_F(IpsecManagerTestIkeV1Psk, FormatStarterConfigFile) {
-  EXPECT_EQ(GetExpectedStarter(false), ipsec_.FormatStarterConfigFile());
+  EXPECT_EQ(GetExpectedStarter(false, false), ipsec_.FormatStarterConfigFile());
   ipsec_.set_debug(true);
-  EXPECT_EQ(GetExpectedStarter(true), ipsec_.FormatStarterConfigFile());
+  EXPECT_EQ(GetExpectedStarter(true, false), ipsec_.FormatStarterConfigFile());
+  ipsec_.xauth_identity_ = kXauthUser;
+  EXPECT_EQ(GetExpectedStarter(true, true), ipsec_.FormatStarterConfigFile());
+  ipsec_.set_debug(false);
+  EXPECT_EQ(GetExpectedStarter(false, true), ipsec_.FormatStarterConfigFile());
 }
 
 TEST_F(IpsecManagerTestIkeV1Psk, Start) {
@@ -343,7 +390,7 @@
   std::string conf_contents;
   ASSERT_TRUE(file_util::ReadFileToString(
       persistent_path_.Append("ipsec.conf"), &conf_contents));
-  EXPECT_EQ(GetExpectedStarter(false), conf_contents);
+  EXPECT_EQ(GetExpectedStarter(false, false), conf_contents);
   ASSERT_TRUE(file_util::PathExists(persistent_path_.Append(
       "ipsec.secrets")));
 }
@@ -352,7 +399,7 @@
  public:
   void SetUp() {
     IpsecManagerTest::SetUp();
-    DoInitialize(1, false);
+    DoInitialize(1, false, false);
   }
 
  protected:
@@ -360,7 +407,7 @@
 };
 
 TEST_F(IpsecManagerTestIkeV1Certs, Initialize) {
-  DoInitialize(1, false);
+  DoInitialize(1, false, false);
 }
 
 TEST_F(IpsecManagerTestIkeV1Certs, FormatSecrets) {
@@ -399,6 +446,11 @@
   EXPECT_EQ(GetExpectedStarter(false), ipsec_.FormatStarterConfigFile());
   ipsec_.set_debug(true);
   EXPECT_EQ(GetExpectedStarter(true), ipsec_.FormatStarterConfigFile());
+  ipsec_.set_debug(true);
+
+  // The xauth parameters aren't pertinent to certificate-based authentication.
+  ipsec_.xauth_identity_ = kXauthUser;
+  EXPECT_EQ(GetExpectedStarter(true), ipsec_.FormatStarterConfigFile());
 }
 
 TEST_F(IpsecManagerTestIkeV1Certs, WriteConfigFiles) {
diff --git a/l2tpipsec_vpn.cc b/l2tpipsec_vpn.cc
index 11f432f..e83900b 100644
--- a/l2tpipsec_vpn.cc
+++ b/l2tpipsec_vpn.cc
@@ -29,6 +29,7 @@
 DEFINE_string(server_ca_file, "", "File with IPsec server CA in DER format");
 DEFINE_string(server_id, "", "ID expected from server");
 DEFINE_string(user_pin, "", "PKCS#11 User PIN");
+DEFINE_string(xauth_credentials_file, "", "File with Xauth user credentials");
 #pragma GCC diagnostic error "-Wstrict-aliasing"
 
 using vpn_manager::IpsecManager;
@@ -132,9 +133,15 @@
     return vpn_manager::kServiceErrorResolveHostnameFailed;
   }
 
+  if (FLAGS_psk_file.empty() && !FLAGS_xauth_credentials_file.empty()) {
+    LOG(ERROR) << "Providing XAUTH credentials without a PSK is invalid";
+    return vpn_manager::kServiceErrorInvalidArgument;
+  }
+
   if (!ipsec.Initialize(1,
                         remote_address,
                         FLAGS_psk_file,
+                        FLAGS_xauth_credentials_file,
                         FLAGS_server_ca_file,
                         FLAGS_server_id,
                         FLAGS_client_cert_slot,