shill: openvpn: Point to default CAs when no CA is specified.

If no CaCert or CaCertNss property is specified, use the --capath
option to point openvpn to the default system CA certificates.

BUG=chrome-os-partner:13785
TEST=unit tests, tested on device that --capath is passed if CA
certificate is default

Change-Id: Ic61d6e69926d77c93804ffd1cd0975713a60db9a
Reviewed-on: https://gerrit.chromium.org/gerrit/34103
Tested-by: Darin Petkov <petkov@chromium.org>
Reviewed-by: Paul Stewart <pstew@chromium.org>
Commit-Ready: Darin Petkov <petkov@chromium.org>
diff --git a/openvpn_driver.cc b/openvpn_driver.cc
index 10a6ca7..59c0500 100644
--- a/openvpn_driver.cc
+++ b/openvpn_driver.cc
@@ -63,6 +63,8 @@
 }  // namespace
 
 // static
+const char OpenVPNDriver::kDefaultCACertificatesPath[] = "/etc/ssl/certs";
+// static
 const char OpenVPNDriver::kOpenVPNPath[] = "/usr/sbin/openvpn";
 // static
 const char OpenVPNDriver::kOpenVPNScript[] = SCRIPTDIR "/openvpn-script";
@@ -549,7 +551,7 @@
   AppendValueOption(flimflam::kOpenVPNServerPollTimeoutProperty,
                     "--server-poll-timeout", options);
 
-  if (!InitNSSOptions(options, error)) {
+  if (!InitCAOptions(options, error)) {
     return;
   }
 
@@ -558,7 +560,6 @@
   AppendValueOption(kOpenVPNPingExitProperty, "--ping-exit", options);
   AppendValueOption(kOpenVPNPingRestartProperty, "--ping-restart", options);
 
-  AppendValueOption(flimflam::kOpenVPNCaCertProperty, "--ca", options);
   AppendValueOption(kOpenVPNCertProperty, "--cert", options);
   AppendValueOption(
       flimflam::kOpenVPNNsCertTypeProperty, "--ns-cert-type", options);
@@ -620,26 +621,41 @@
   options->push_back("openvpn");
 }
 
-bool OpenVPNDriver::InitNSSOptions(vector<string> *options, Error *error) {
+bool OpenVPNDriver::InitCAOptions(vector<string> *options, Error *error) {
   string ca_cert =
+      args()->LookupString(flimflam::kOpenVPNCaCertProperty, "");
+  string ca_cert_nss =
       args()->LookupString(flimflam::kOpenVPNCaCertNSSProperty, "");
-  if (!ca_cert.empty()) {
-    if (!args()->LookupString(flimflam::kOpenVPNCaCertProperty, "").empty()) {
-      Error::PopulateAndLog(error,
-                            Error::kInvalidArguments,
-                            "Can't specify both CACert and CACertNSS.");
-      return false;
-    }
+  if (ca_cert.empty() && ca_cert_nss.empty()) {
+    // Use default CAs if no CA certificate is provided.
+    options->push_back("--capath");
+    options->push_back(kDefaultCACertificatesPath);
+    return true;
+  }
+  if (!ca_cert.empty() && !ca_cert_nss.empty()) {
+    Error::PopulateAndLog(error,
+                          Error::kInvalidArguments,
+                          "Can't specify both CACert and CACertNSS.");
+    return false;
+  }
+  options->push_back("--ca");
+  if (!ca_cert_nss.empty()) {
+    DCHECK(ca_cert.empty());
     const string &vpnhost = args()->GetString(flimflam::kProviderHostProperty);
     vector<char> id(vpnhost.begin(), vpnhost.end());
-    FilePath certfile = nss_->GetPEMCertfile(ca_cert, id);
+    FilePath certfile = nss_->GetPEMCertfile(ca_cert_nss, id);
     if (certfile.empty()) {
-      LOG(ERROR) << "Unable to extract certificate: " << ca_cert;
-    } else {
-      options->push_back("--ca");
-      options->push_back(certfile.value());
+      Error::PopulateAndLog(
+          error,
+          Error::kInvalidArguments,
+          "Unable to extract NSS CA certificate: " + ca_cert_nss);
+      return false;
     }
+    options->push_back(certfile.value());
+    return true;
   }
+  DCHECK(!ca_cert.empty() && ca_cert_nss.empty());
+  options->push_back(ca_cert);
   return true;
 }
 
diff --git a/openvpn_driver.h b/openvpn_driver.h
index 86f7e94..6f788d4 100644
--- a/openvpn_driver.h
+++ b/openvpn_driver.h
@@ -85,10 +85,10 @@
   FRIEND_TEST(OpenVPNDriverTest, DeleteInterface);
   FRIEND_TEST(OpenVPNDriverTest, Disconnect);
   FRIEND_TEST(OpenVPNDriverTest, GetRouteOptionEntry);
+  FRIEND_TEST(OpenVPNDriverTest, InitCAOptions);
   FRIEND_TEST(OpenVPNDriverTest, InitEnvironment);
   FRIEND_TEST(OpenVPNDriverTest, InitLoggingOptions);
   FRIEND_TEST(OpenVPNDriverTest, InitManagementChannelOptions);
-  FRIEND_TEST(OpenVPNDriverTest, InitNSSOptions);
   FRIEND_TEST(OpenVPNDriverTest, InitOptions);
   FRIEND_TEST(OpenVPNDriverTest, InitOptionsHostWithPort);
   FRIEND_TEST(OpenVPNDriverTest, InitOptionsNoHost);
@@ -109,6 +109,8 @@
   FRIEND_TEST(OpenVPNDriverTest, SplitPortFromHost);
   FRIEND_TEST(OpenVPNDriverTest, VerifyPaths);
 
+  static const char kDefaultCACertificatesPath[];
+
   static const char kOpenVPNPath[];
   static const char kOpenVPNScript[];
   static const Property kProperties[];
@@ -146,7 +148,7 @@
                                 std::string *port);
 
   void InitOptions(std::vector<std::string> *options, Error *error);
-  bool InitNSSOptions(std::vector<std::string> *options, Error *error);
+  bool InitCAOptions(std::vector<std::string> *options, Error *error);
   void InitPKCS11Options(std::vector<std::string> *options);
   bool InitManagementChannelOptions(
       std::vector<std::string> *options, Error *error);
diff --git a/openvpn_driver_unittest.cc b/openvpn_driver_unittest.cc
index e7f9345..b86faf4 100644
--- a/openvpn_driver_unittest.cc
+++ b/openvpn_driver_unittest.cc
@@ -448,17 +448,14 @@
 TEST_F(OpenVPNDriverTest, InitOptions) {
   static const char kHost[] = "192.168.2.254";
   static const char kTLSAuthContents[] = "SOME-RANDOM-CONTENTS\n";
-  static const char kCaCertNSS[] = "{1234}";
   static const char kID[] = "TestPKCS11ID";
   FilePath empty_cert;
   SetArg(flimflam::kProviderHostProperty, kHost);
   SetArg(flimflam::kOpenVPNTLSAuthContentsProperty, kTLSAuthContents);
-  SetArg(flimflam::kOpenVPNCaCertNSSProperty, kCaCertNSS);
   SetArg(flimflam::kOpenVPNClientCertIdProperty, kID);
   driver_->rpc_task_.reset(new RPCTask(&control_, this));
   driver_->tunnel_interface_ = kInterfaceName;
   EXPECT_CALL(*management_server_, Start(_, _, _)).WillOnce(Return(true));
-  EXPECT_CALL(nss_, GetPEMCertfile(kCaCertNSS, _)).WillOnce(Return(empty_cert));
   ServiceRefPtr null_service;
   EXPECT_CALL(manager_, GetDefaultService()).WillOnce(Return(null_service));
 
@@ -479,6 +476,7 @@
       file_util::ReadFileToString(driver_->tls_auth_file_, &contents));
   EXPECT_EQ(kTLSAuthContents, contents);
   ExpectInFlags(options, "--pkcs11-id", kID);
+  ExpectInFlags(options, "--capath", OpenVPNDriver::kDefaultCACertificatesPath);
   EXPECT_TRUE(std::find(options.begin(), options.end(), "--syslog") !=
               options.end());
 }
@@ -504,34 +502,49 @@
   EXPECT_EQ("1234", *it);
 }
 
-TEST_F(OpenVPNDriverTest, InitNSSOptions) {
+TEST_F(OpenVPNDriverTest, InitCAOptions) {
   static const char kHost[] = "192.168.2.254";
+  static const char kCaCert[] = "foo";
   static const char kCaCertNSS[] = "{1234}";
   static const char kNSSCertfile[] = "/tmp/nss-cert";
+
+  Error error;
+  vector<string> options;
+  EXPECT_TRUE(driver_->InitCAOptions(&options, &error));
+  EXPECT_TRUE(error.IsSuccess());
+  ExpectInFlags(options, "--capath", OpenVPNDriver::kDefaultCACertificatesPath);
+
+  options.clear();
+  SetArg(flimflam::kOpenVPNCaCertProperty, kCaCert);
+  EXPECT_TRUE(driver_->InitCAOptions(&options, &error));
+  ExpectInFlags(options, "--ca", kCaCert);
+  EXPECT_TRUE(error.IsSuccess());
+
+  SetArg(flimflam::kOpenVPNCaCertNSSProperty, kCaCertNSS);
+  EXPECT_FALSE(driver_->InitCAOptions(&options, &error));
+  EXPECT_EQ(Error::kInvalidArguments, error.type());
+  EXPECT_EQ("Can't specify both CACert and CACertNSS.", error.message());
+
+  SetArg(flimflam::kOpenVPNCaCertProperty, "");
+  SetArg(flimflam::kProviderHostProperty, kHost);
   FilePath empty_cert;
   FilePath nss_cert(kNSSCertfile);
-  SetArg(flimflam::kProviderHostProperty, kHost);
-  SetArg(flimflam::kOpenVPNCaCertNSSProperty, kCaCertNSS);
   EXPECT_CALL(nss_,
               GetPEMCertfile(kCaCertNSS,
                              ElementsAreArray(kHost, arraysize(kHost) - 1)))
       .WillOnce(Return(empty_cert))
       .WillOnce(Return(nss_cert));
 
-  Error error;
-  vector<string> options;
-  EXPECT_TRUE(driver_->InitNSSOptions(&options, &error));
-  EXPECT_TRUE(error.IsSuccess());
-  EXPECT_TRUE(options.empty());
-  EXPECT_TRUE(driver_->InitNSSOptions(&options, &error));
+  error.Reset();
+  EXPECT_FALSE(driver_->InitCAOptions(&options, &error));
+  EXPECT_EQ(Error::kInvalidArguments, error.type());
+  EXPECT_EQ("Unable to extract NSS CA certificate: {1234}", error.message());
+
+  error.Reset();
+  options.clear();
+  EXPECT_TRUE(driver_->InitCAOptions(&options, &error));
   ExpectInFlags(options, "--ca", kNSSCertfile);
   EXPECT_TRUE(error.IsSuccess());
-
-  SetArg(flimflam::kOpenVPNCaCertProperty, "foo");
-  options.clear();
-  EXPECT_FALSE(driver_->InitNSSOptions(&options, &error));
-  EXPECT_EQ(Error::kInvalidArguments, error.type());
-  EXPECT_EQ("Can't specify both CACert and CACertNSS.", error.message());
 }
 
 TEST_F(OpenVPNDriverTest, InitPKCS11Options) {