OSDN Git Service

GD Security: Improve Enforce() workflow
authorHansong Zhang <hsz@google.com>
Wed, 19 Aug 2020 21:52:53 +0000 (14:52 -0700)
committerHansong Zhang <hsz@google.com>
Fri, 21 Aug 2020 03:54:08 +0000 (20:54 -0700)
InternalEnforceSecurityPolicy establishes the requirement
- ENCRYPTED_TRANSPORT: If paired but not encrypted, just wait for
  encryption change; if unpaired, pair with NO_BOND_NO_MITM
- AUTHENTICATED_ENCRYPTED_TRANSPORT: Similar as above, but we need to
  pair again if existing LK is not authenticated. Exception: If no MITM
  is needed during pairing, we assume authenticated LK is not possible,
  so we allow connection. In the future, use IO cap to check.

When link is encrypted, or new pairing is complete, we invoke
UpdateLinkSecurityCondition.

Test: cert/run --host
Test: CtsVerifier Insecure RFCOMM client
Tag: #gd-refactor
Bug: 141555841
Change-Id: Ic5792c8e967cd068e08df4702393ae3188c6d4e8

gd/security/cert/security_test.py
gd/security/internal/security_manager_impl.cc
gd/security/internal/security_manager_impl.h

index 17f3579..21a9b5c 100644 (file)
@@ -147,7 +147,7 @@ class SecurityTest(GdBaseTestClass):
         pass
 
     # no_input_no_output + no_input_no_output is JustWorks no confirmation
-    def test_dut_initiated_no_input_no_output_no_input_no_output_twice_same_acl(self):
+    def test_dut_initiated_no_input_no_output_no_input_no_output_twice_bond_and_enforce(self):
         # Arrange
         self.dut_security.set_io_capabilities(IoCapabilities.NO_INPUT_NO_OUTPUT)
         self.dut_security.set_authentication_requirements(AuthenticationRequirements.DEDICATED_BONDING)
@@ -171,17 +171,7 @@ class SecurityTest(GdBaseTestClass):
                                                   common.BluetoothAddressTypeEnum.PUBLIC_DEVICE_ADDRESS,
                                                   ClassicSecurityPolicy.ENCRYPTED_TRANSPORT)
 
-        self._verify_ssp_numeric_comparison(
-            initiator=self.dut_security,
-            responder=self.cert_security,
-            init_ui_response=True,
-            resp_ui_response=True,
-            expected_init_ui_event=None,
-            expected_resp_ui_event=None,
-            expected_init_bond_event=BondMsgType.DEVICE_BONDED,
-            expected_resp_bond_event=None)
-
-        self.dut_security.wait_for_enforce_security_event(expected_enforce_security_event=False)
+        # TODO: We verify enforcement when we make sure EncryptionChange is received on DUT
 
     # no_input_no_output + no_input_no_output is JustWorks no confirmation
     def test_dut_initiated_no_input_no_output_no_input_no_output_twice_with_remove_bond(self):
index a7719ab..751a69b 100644 (file)
@@ -334,11 +334,7 @@ void SecurityManagerImpl::OnEncryptionChange(hci::Address address, bool encrypte
   auto remote = hci::AddressWithType(address, hci::AddressType::PUBLIC_DEVICE_ADDRESS);
   auto record = security_database_.FindOrCreate(remote);
   record->SetIsEncrypted(encrypted);
-  auto cb_entry = enforce_security_policy_callback_map_.find(remote);
-  if (cb_entry != enforce_security_policy_callback_map_.end()) {
-    this->InternalEnforceSecurityPolicy(remote, cb_entry->second.first, std::move(cb_entry->second.second), false);
-    enforce_security_policy_callback_map_.erase(cb_entry);
-  }
+  UpdateLinkSecurityCondition(remote);
 }
 
 void SecurityManagerImpl::OnHciLeEvent(hci::LeMetaEventView event) {
@@ -388,11 +384,6 @@ void SecurityManagerImpl::OnPairingHandlerComplete(hci::Address address, Pairing
     security_manager_channel_->Release(address);
   }
   auto remote = hci::AddressWithType(address, hci::AddressType::PUBLIC_DEVICE_ADDRESS);
-  auto cb_entry = enforce_security_policy_callback_map_.find(remote);
-  if (cb_entry != enforce_security_policy_callback_map_.end()) {
-    this->InternalEnforceSecurityPolicy(remote, cb_entry->second.first, std::move(cb_entry->second.second), false);
-    enforce_security_policy_callback_map_.erase(cb_entry);
-  }
   if (!std::holds_alternative<PairingFailure>(status)) {
     NotifyDeviceBonded(remote);
   } else {
@@ -401,6 +392,7 @@ void SecurityManagerImpl::OnPairingHandlerComplete(hci::Address address, Pairing
   auto record = this->security_database_.FindOrCreate(remote);
   record->CancelPairing();
   security_database_.SaveRecordsToStorage();
+  UpdateLinkSecurityCondition(remote);
 }
 
 void SecurityManagerImpl::OnL2capRegistrationCompleteLe(
@@ -713,55 +705,106 @@ void SecurityManagerImpl::SetOobDataPresent(hci::OobDataPresent data_present) {
 void SecurityManagerImpl::InternalEnforceSecurityPolicy(
     hci::AddressWithType remote,
     l2cap::classic::SecurityPolicy policy,
-    l2cap::classic::SecurityEnforcementInterface::ResultCallback result_callback,
-    bool try_meet_requirements) {
+    l2cap::classic::SecurityEnforcementInterface::ResultCallback result_callback) {
+  if (IsSecurityRequirementSatisfied(remote, policy)) {
+    // Notify client immediately if already satisfied
+    result_callback.Invoke(true);
+    return;
+  }
+
   hci::AuthenticationRequirements authentication_requirements = kDefaultAuthenticationRequirements;
 
-  bool result = false;
   auto record = this->security_database_.FindOrCreate(remote);
+  bool need_to_pair = false;
+
   switch (policy) {
     case l2cap::classic::SecurityPolicy::BEST:
     case l2cap::classic::SecurityPolicy::AUTHENTICATED_ENCRYPTED_TRANSPORT:
-      result = record->IsAuthenticated() && record->RequiresMitmProtection() && record->IsEncrypted();
-      authentication_requirements = hci::AuthenticationRequirements::GENERAL_BONDING_MITM_PROTECTION;
+      if (!record->IsPaired()) {
+        need_to_pair = true;
+      } else if (record->IsAuthenticated()) {
+        // if paired with MITM, only encryption is missing, so we just need to wait for encryption change callback
+      } else {
+        // We have an unauthenticated link key, so we need to pair again with MITM
+        // Need to pair again with MITM
+        need_to_pair = true;
+        authentication_requirements = hci::AuthenticationRequirements::GENERAL_BONDING_MITM_PROTECTION;
+        if (record->RequiresMitmProtection()) {
+          // Workaround for headset: If no MITM is needed during pairing, we don't mandate authenticated LK
+          // TODO(b/165671060): Use IO cap to check whether we can waive authenticated LK requirement
+          need_to_pair = false;
+        }
+      }
       break;
     case l2cap::classic::SecurityPolicy::ENCRYPTED_TRANSPORT:
-      result = record->IsEncrypted();
-      authentication_requirements = hci::AuthenticationRequirements::NO_BONDING;
-      break;
-    case l2cap::classic::SecurityPolicy::_SDP_ONLY_NO_SECURITY_WHATSOEVER_PLAINTEXT_TRANSPORT_OK:
-      result = true;
+      if (!record->IsPaired()) {
+        need_to_pair = true;
+        authentication_requirements = hci::AuthenticationRequirements::NO_BONDING;
+      } else {
+        // just need to wait for encryption change callback
+      }
       break;
+    default:
+      return;
   }
-  if (!result && try_meet_requirements) {
-    auto entry = enforce_security_policy_callback_map_.find(remote);
-    if (entry != enforce_security_policy_callback_map_.end()) {
-      LOG_WARN("Callback already pending for remote: '%s' !", remote.ToString().c_str());
-    } else {
-      enforce_security_policy_callback_map_.emplace(
-          remote,
-          std::pair<l2cap::classic::SecurityPolicy, l2cap::classic::SecurityEnforcementInterface::ResultCallback>(
-              policy, std::move(result_callback)));
-      if (!record->IsPairing()) {
-        LOG_WARN("Dispatch #3");
-        DispatchPairingHandler(
-            record,
-            true,
-            this->local_io_capability_,
-            this->local_oob_data_present_,
-            std::as_const(authentication_requirements));
-      }
-    }
+
+  auto entry = enforce_security_policy_callback_map_.find(remote);
+  if (entry != enforce_security_policy_callback_map_.end()) {
+    LOG_WARN("Callback already pending for remote: '%s' !", remote.ToString().c_str());
+  } else {
+    enforce_security_policy_callback_map_[remote] = {policy, std::move(result_callback)};
+  }
+
+  if (need_to_pair && !record->IsPairing()) {
+    LOG_WARN("Dispatch #3");
+    DispatchPairingHandler(
+        record,
+        true,
+        this->local_io_capability_,
+        this->local_oob_data_present_,
+        std::as_const(authentication_requirements));
+  }
+}
+
+void SecurityManagerImpl::UpdateLinkSecurityCondition(hci::AddressWithType remote) {
+  auto record = this->security_database_.FindOrCreate(remote);
+  auto entry = enforce_security_policy_callback_map_.find(remote);
+  if (entry == enforce_security_policy_callback_map_.end()) {
+    LOG_ERROR("No security reuest pending for %s", remote.ToString().c_str());
     return;
   }
-  result_callback.Invoke(result);
+
+  auto policy = entry->second.policy_;
+
+  if (IsSecurityRequirementSatisfied(remote, policy)) {
+    entry->second.callback_.Invoke(true);
+    enforce_security_policy_callback_map_.erase(entry);
+  }
+}
+
+bool SecurityManagerImpl::IsSecurityRequirementSatisfied(
+    hci::AddressWithType remote, l2cap::classic::SecurityPolicy policy) {
+  auto record = security_database_.FindOrCreate(remote);
+
+  switch (policy) {
+    case l2cap::classic::SecurityPolicy::BEST:
+    case l2cap::classic::SecurityPolicy::AUTHENTICATED_ENCRYPTED_TRANSPORT:
+      // TODO(b/165671060): Use IO cap to check whether we can waive authenticated LK requirement
+      return (!record->RequiresMitmProtection() || record->IsAuthenticated()) && record->IsEncrypted();
+    case l2cap::classic::SecurityPolicy::ENCRYPTED_TRANSPORT:
+      return record->IsEncrypted();
+    case l2cap::classic::SecurityPolicy::_SDP_ONLY_NO_SECURITY_WHATSOEVER_PLAINTEXT_TRANSPORT_OK:
+      return true;
+    default:
+      return false;
+  }
 }
 
 void SecurityManagerImpl::EnforceSecurityPolicy(
     hci::AddressWithType remote,
     l2cap::classic::SecurityPolicy policy,
     l2cap::classic::SecurityEnforcementInterface::ResultCallback result_callback) {
-  this->InternalEnforceSecurityPolicy(remote, policy, std::move(result_callback), true);
+  this->InternalEnforceSecurityPolicy(remote, policy, std::move(result_callback));
 }
 
 void SecurityManagerImpl::EnforceLeSecurityPolicy(
index 79daa54..a38196d 100644 (file)
@@ -224,8 +224,9 @@ class SecurityManagerImpl : public channel::ISecurityManagerChannelListener, pub
   void InternalEnforceSecurityPolicy(
       hci::AddressWithType remote,
       l2cap::classic::SecurityPolicy policy,
-      l2cap::classic::SecurityEnforcementInterface::ResultCallback result_callback,
-      bool try_meet_requirements);
+      l2cap::classic::SecurityEnforcementInterface::ResultCallback result_callback);
+  void UpdateLinkSecurityCondition(hci::AddressWithType remote);
+  bool IsSecurityRequirementSatisfied(hci::AddressWithType remote, l2cap::classic::SecurityPolicy policy);
   void ConnectionIsReadyStartPairing(LeFixedChannelEntry* stored_channel);
   void WipeLePairingHandler();
 
@@ -251,10 +252,11 @@ class SecurityManagerImpl : public channel::ISecurityManagerChannelListener, pub
   std::optional<crypto_toolbox::Octet16> remote_oob_data_le_sc_r_;
   std::optional<FacadeDisconnectCallback> facade_disconnect_callback_;
 
-  std::unordered_map<
-      hci::AddressWithType,
-      std::pair<l2cap::classic::SecurityPolicy, l2cap::classic::SecurityEnforcementInterface::ResultCallback>>
-      enforce_security_policy_callback_map_;
+  struct PendingSecurityEnforcementEntry {
+    l2cap::classic::SecurityPolicy policy_;
+    l2cap::classic::SecurityEnforcementInterface::ResultCallback callback_;
+  };
+  std::unordered_map<hci::AddressWithType, PendingSecurityEnforcementEntry> enforce_security_policy_callback_map_;
 
   struct {
     hci::AddressWithType address_;