CRM-16173 - CIVICRM_CXN_VERIFY => CIVICRM_CXN_CA, CIVICRM_CXN_VERIFY_APPMETA
authorTim Otten <totten@civicrm.org>
Thu, 26 Mar 2015 23:50:41 +0000 (16:50 -0700)
committerTim Otten <totten@civicrm.org>
Tue, 14 Jul 2015 04:00:06 +0000 (21:00 -0700)
The CA may take one of three values - CiviRootCA, CiviTestRootCA, or none.

In the future, this may help with staging and private-beta apps.

CRM/Cxn/BAO/Cxn.php
Civi/Core/Container.php
api/v3/Cxn.php

index 4f6fa2f06e7fe6cb79e9777ace4f3b2542d13211..434ffa0561d928633e916d11e3aefd8eb82634db 100644 (file)
@@ -26,6 +26,8 @@
  +--------------------------------------------------------------------+
  */
 
+use Civi\Cxn\Rpc\Constants;
+
 /**
  *
  * @package CRM
@@ -64,4 +66,71 @@ class CRM_Cxn_BAO_Cxn extends CRM_Cxn_DAO_Cxn {
     return $appMeta;
   }
 
+  /**
+   * Parse the CIVICRM_CXN_CA constant. It may have the following
+   * values:
+   *   - 'CiviRootCA'|undefined -- Use the production civicrm.org root CA
+   *   - 'CiviTestRootCA' -- Use the test civicrm.org root CA
+   *   - 'none' -- Do not perform any certificate verification.
+   *
+   * This constant is emphatically *not* exposed through Civi's "Settings"
+   * system (or any other runtime-editable datastore). Manipulating
+   * this setting can expose the system to man-in-the-middle attacks,
+   * and allowing runtime manipulation would create a new vector
+   * for escalating privileges. This setting must only be manipulated
+   * by developers and sysadmins who already have full privileges
+   * to edit the source.
+   *
+   * @return string|NULL
+   *   The PEM-encoded root certificate. NULL if verification is disabled.
+   * @throws CRM_Core_Exception
+   */
+  public static function createCACert() {
+    if (!defined('CIVICRM_CXN_CA') || CIVICRM_CXN_CA === 'CiviRootCA') {
+      $file = Constants::getCert();
+    }
+    elseif (CIVICRM_CXN_CA === 'CiviTestRootCA') {
+      $file = Constants::getTestCert();
+    }
+    elseif (CIVICRM_CXN_CA === 'none') {
+      return NULL;
+    }
+    else {
+      throw new \CRM_Core_Exception("CIVICRM_CXN_CA is invalid.");
+    }
+
+    $content = file_get_contents($file);
+    if (empty($content)) {
+      // Fail hard. Returning an empty value is not acceptable.
+      throw new \CRM_Core_Exception("Error loading CA certificate: $file");
+    }
+    return $content;
+  }
+
+  /**
+   * Determine if this site's security policy allows connecting
+   * to apps based on untrusted metadata.
+   *
+   * @return bool
+   *   TRUE if application metadata must be verified.
+   */
+  public static function isAppMetaVerified() {
+    if (defined('CIVICRM_CXN_VERIFY_APPMETA')) {
+      return CIVICRM_CXN_VERIFY_APPMETA;
+    }
+    elseif (!defined('CIVICRM_CXN_CA')) {
+      return TRUE;
+    }
+    else {
+      return !in_array(CIVICRM_CXN_CA, array('CiviTestRootCA', 'none'));
+    }
+  }
+
+  public static function createRegistrationClient() {
+    $cxnStore = new \CRM_Cxn_CiviCxnStore();
+    $client = new \Civi\Cxn\Rpc\RegistrationClient(self::createCACert(), $cxnStore, \CRM_Cxn_BAO_Cxn::getSiteCallbackUrl());
+    $client->setLog(new \CRM_Utils_SystemLogger());
+    return $client;
+  }
+
 }
index 93d5bdce0ec5245e5b75179b83355e4ebb2c8da0..627f1a18c90d1c7c25e1c0e83cfeb8204f2c1a2d 100644 (file)
@@ -101,7 +101,7 @@ class Container {
       '\Civi\Cxn\Rpc\RegistrationClient',
       array()
     ))
-      ->setFactoryService(self::SELF)->setFactoryMethod('createRegistrationClient');
+      ->setFactoryClass('CRM_Cxn_BAO_Cxn')->setFactoryMethod('createRegistrationClient');
 
     // Expose legacy singletons as services in the container.
     $singletons = array(
@@ -140,9 +140,9 @@ class Container {
     $dispatcher->addListener('DAO::post-update', array('\CRM_Core_BAO_RecurringEntity', 'triggerUpdate'));
     $dispatcher->addListener('DAO::post-delete', array('\CRM_Core_BAO_RecurringEntity', 'triggerDelete'));
     $dispatcher->addListener('hook_civicrm_unhandled_exception', array(
-        'CRM_Core_LegacyErrorHandler',
-        'handleException',
-      ));
+      'CRM_Core_LegacyErrorHandler',
+      'handleException',
+    ));
     return $dispatcher;
   }
 
@@ -218,10 +218,4 @@ class Container {
     return $kernel;
   }
 
-  public function createRegistrationClient() {
-    $cxnStore = new \CRM_Cxn_CiviCxnStore();
-    $client = new \Civi\Cxn\Rpc\RegistrationClient(NULL, $cxnStore, \CRM_Cxn_BAO_Cxn::getSiteCallbackUrl());
-    $client->setLog(new \CRM_Utils_SystemLogger());
-    return $client;
-  }
 }
index f70f1f35c1b78c1949243787366c657016dc3009..29c53b3c9538a8ad2ae5d4f8d7fb3fd776d71504 100644 (file)
@@ -33,7 +33,7 @@
  */
 function civicrm_api3_cxn_register($params) {
   if (empty($params['appMeta']) && !empty($params['appMetaUrl'])) {
-    if (defined('CIVICRM_CXN_VERIFY') && !CIVICRM_CXN_VERIFY) {
+    if (!CRM_Cxn_BAO_Cxn::isAppMetaVerified()) {
       list ($status, $json) = CRM_Utils_HttpClient::singleton()->get($params['appMetaUrl']);
       if (CRM_Utils_HttpClient::STATUS_OK != $status) {
         throw new API_Exception("Failed to download appMeta.");
@@ -43,8 +43,8 @@ function civicrm_api3_cxn_register($params) {
     else {
       // Note: The metadata includes a cert, but the details aren't signed.
       // This is very useful in testing/development. In ordinary usage, we
-      // rely on a general signature for the full batch of all metadata.
-      throw new API_Exception('This site is configured to only use verified metadata.');
+      // rely on civicrm.org to sign the metadata for all apps en masse.
+      throw new API_Exception('This site is configured to only connect to applications with verified metadata.');
     }
   }