blob: 292ca969c0c1868ad1d4eb1e2efe3dc7f93e3226 [file] [log] [blame]
// Copyright 2018 The Fuchsia 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 "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/sm/types.h"
#include <string>
#include <gtest/gtest.h>
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/sm/smp.h"
#include "src/connectivity/bluetooth/core/bt-host/public/pw_bluetooth_sapphire/internal/host/testing/inspect_util.h"
#include "src/connectivity/bluetooth/lib/cpp-string/string_printf.h"
namespace bt::sm {
namespace {
using namespace inspect::testing;
TEST(TypesTest, LinkKeyTypeToSecurityProperties) {
SecurityProperties props(hci_spec::LinkKeyType::kCombination);
EXPECT_EQ(SecurityLevel::kNoSecurity, props.level());
EXPECT_EQ(16UL, props.enc_key_size());
EXPECT_EQ(false, props.authenticated());
EXPECT_EQ(false, props.secure_connections());
props = SecurityProperties(hci_spec::LinkKeyType::kLocalUnit);
EXPECT_EQ(SecurityLevel::kNoSecurity, props.level());
EXPECT_EQ(16UL, props.enc_key_size());
EXPECT_EQ(false, props.authenticated());
EXPECT_EQ(false, props.secure_connections());
props = SecurityProperties(hci_spec::LinkKeyType::kRemoteUnit);
EXPECT_EQ(SecurityLevel::kNoSecurity, props.level());
EXPECT_EQ(16UL, props.enc_key_size());
EXPECT_EQ(false, props.authenticated());
EXPECT_EQ(false, props.secure_connections());
props = SecurityProperties(hci_spec::LinkKeyType::kDebugCombination);
EXPECT_EQ(SecurityLevel::kEncrypted, props.level());
EXPECT_EQ(16UL, props.enc_key_size());
EXPECT_EQ(false, props.authenticated());
EXPECT_EQ(false, props.secure_connections());
props =
SecurityProperties(hci_spec::LinkKeyType::kUnauthenticatedCombination192);
EXPECT_EQ(SecurityLevel::kEncrypted, props.level());
EXPECT_EQ(16UL, props.enc_key_size());
EXPECT_EQ(false, props.authenticated());
EXPECT_EQ(false, props.secure_connections());
props =
SecurityProperties(hci_spec::LinkKeyType::kAuthenticatedCombination192);
EXPECT_EQ(SecurityLevel::kAuthenticated, props.level());
EXPECT_EQ(16UL, props.enc_key_size());
EXPECT_EQ(true, props.authenticated());
EXPECT_EQ(false, props.secure_connections());
props =
SecurityProperties(hci_spec::LinkKeyType::kUnauthenticatedCombination256);
EXPECT_EQ(SecurityLevel::kEncrypted, props.level());
EXPECT_EQ(16UL, props.enc_key_size());
EXPECT_EQ(false, props.authenticated());
EXPECT_EQ(true, props.secure_connections());
props =
SecurityProperties(hci_spec::LinkKeyType::kAuthenticatedCombination256);
EXPECT_EQ(SecurityLevel::kSecureAuthenticated, props.level());
EXPECT_EQ(16UL, props.enc_key_size());
EXPECT_EQ(true, props.authenticated());
EXPECT_EQ(true, props.secure_connections());
}
TEST(TypesTest, SecurityPropertiesToLinkKeyType) {
SecurityProperties props(hci_spec::LinkKeyType::kCombination);
EXPECT_EQ(std::nullopt, props.GetLinkKeyType());
props = SecurityProperties(hci_spec::LinkKeyType::kLocalUnit);
EXPECT_EQ(std::nullopt, props.GetLinkKeyType());
props = SecurityProperties(hci_spec::LinkKeyType::kRemoteUnit);
EXPECT_EQ(std::nullopt, props.GetLinkKeyType());
props = SecurityProperties(hci_spec::LinkKeyType::kDebugCombination);
ASSERT_TRUE(props.GetLinkKeyType().has_value());
EXPECT_EQ(hci_spec::LinkKeyType::kUnauthenticatedCombination192,
*props.GetLinkKeyType());
props =
SecurityProperties(hci_spec::LinkKeyType::kUnauthenticatedCombination192);
ASSERT_TRUE(props.GetLinkKeyType().has_value());
EXPECT_EQ(hci_spec::LinkKeyType::kUnauthenticatedCombination192,
*props.GetLinkKeyType());
props =
SecurityProperties(hci_spec::LinkKeyType::kAuthenticatedCombination192);
ASSERT_TRUE(props.GetLinkKeyType().has_value());
EXPECT_EQ(hci_spec::LinkKeyType::kAuthenticatedCombination192,
*props.GetLinkKeyType());
props =
SecurityProperties(hci_spec::LinkKeyType::kUnauthenticatedCombination256);
ASSERT_TRUE(props.GetLinkKeyType().has_value());
EXPECT_EQ(hci_spec::LinkKeyType::kUnauthenticatedCombination256,
*props.GetLinkKeyType());
props =
SecurityProperties(hci_spec::LinkKeyType::kAuthenticatedCombination256);
ASSERT_TRUE(props.GetLinkKeyType().has_value());
EXPECT_EQ(hci_spec::LinkKeyType::kAuthenticatedCombination256,
*props.GetLinkKeyType());
}
TEST(TypesTest, CorrectPropertiesToLevelMapping) {
for (auto sc : {true, false}) {
SCOPED_TRACE("secure connections: " + std::to_string(sc));
for (auto key_sz : {kMinEncryptionKeySize, kMaxEncryptionKeySize}) {
SCOPED_TRACE("encryption key size: " + std::to_string(key_sz));
ASSERT_EQ(SecurityLevel::kEncrypted,
SecurityProperties(true, false, sc, key_sz).level());
for (auto auth : {true, false}) {
SCOPED_TRACE("authenticated: " + std::to_string(auth));
ASSERT_EQ(SecurityLevel::kNoSecurity,
SecurityProperties(false, auth, sc, key_sz).level());
}
}
}
ASSERT_EQ(
SecurityLevel::kAuthenticated,
SecurityProperties(true, true, false, kMaxEncryptionKeySize).level());
ASSERT_EQ(
SecurityLevel::kAuthenticated,
SecurityProperties(true, true, true, kMinEncryptionKeySize).level());
ASSERT_EQ(
SecurityLevel::kSecureAuthenticated,
SecurityProperties(true, true, true, kMaxEncryptionKeySize).level());
}
TEST(TypesTest, PropertiesLevelConstructorWorks) {
for (auto enc_key_size : {kMinEncryptionKeySize, kMaxEncryptionKeySize}) {
SCOPED_TRACE("Enc key size: " + std::to_string(enc_key_size));
for (auto sc : {true, false}) {
SCOPED_TRACE("Secure Connections: " + std::to_string(sc));
ASSERT_EQ(SecurityLevel::kNoSecurity,
SecurityProperties(SecurityLevel::kNoSecurity, enc_key_size, sc)
.level());
ASSERT_EQ(SecurityLevel::kEncrypted,
SecurityProperties(SecurityLevel::kEncrypted, enc_key_size, sc)
.level());
if (sc && enc_key_size == kMaxEncryptionKeySize) {
ASSERT_EQ(
SecurityLevel::kSecureAuthenticated,
SecurityProperties(SecurityLevel::kAuthenticated, enc_key_size, sc)
.level());
ASSERT_EQ(SecurityLevel::kSecureAuthenticated,
SecurityProperties(
SecurityLevel::kSecureAuthenticated, enc_key_size, sc)
.level());
} else {
ASSERT_EQ(
SecurityLevel::kAuthenticated,
SecurityProperties(SecurityLevel::kAuthenticated, enc_key_size, sc)
.level());
}
}
}
}
TEST(TypesTest, HasKeysToDistribute) {
PairingFeatures local_link_key_and_others;
local_link_key_and_others.local_key_distribution =
KeyDistGen::kLinkKey | KeyDistGen::kEncKey;
EXPECT_TRUE(HasKeysToDistribute(local_link_key_and_others));
PairingFeatures remote_link_key_and_others;
remote_link_key_and_others.remote_key_distribution =
KeyDistGen::kLinkKey | KeyDistGen::kIdKey;
EXPECT_TRUE(HasKeysToDistribute(remote_link_key_and_others));
PairingFeatures remote_link_key_only;
remote_link_key_only.remote_key_distribution = KeyDistGen::kLinkKey;
EXPECT_FALSE(HasKeysToDistribute(remote_link_key_only));
// No keys set.
EXPECT_FALSE(HasKeysToDistribute(PairingFeatures{}));
}
TEST(TypesTest, SecurityPropertiesComparisonWorks) {
const SecurityProperties kInsecure(SecurityLevel::kNoSecurity,
kMinEncryptionKeySize,
/*secure_connections=*/false),
kEncryptedLegacy(SecurityLevel::kEncrypted,
kMaxEncryptionKeySize,
/*secure_connections=*/false),
kEncryptedSecure(SecurityLevel::kEncrypted,
kMaxEncryptionKeySize,
/*secure_connections=*/true),
kAuthenticatedLegacy(SecurityLevel::kAuthenticated,
kMaxEncryptionKeySize,
/*secure_connections=*/false),
kAuthenticatedSecure(SecurityLevel::kAuthenticated,
kMaxEncryptionKeySize,
/*secure_connections=*/true),
kAuthenticatedSecureShortKey(SecurityLevel::kAuthenticated,
kMinEncryptionKeySize,
/*secure_connections=*/true);
const std::array kTestProperties{kInsecure,
kEncryptedLegacy,
kEncryptedSecure,
kAuthenticatedLegacy,
kAuthenticatedSecure,
kAuthenticatedSecureShortKey};
// Tests that are true for all of kTestProperties
for (auto props : kTestProperties) {
SCOPED_TRACE(props.ToString());
ASSERT_TRUE(props.IsAsSecureAs(props));
// kAuthenticatedSecure is the "most secure" possible properties.
ASSERT_TRUE(kAuthenticatedSecure.IsAsSecureAs(props));
}
// Test based on the least secure SecurityProperties in kTestProperties
for (auto props : kTestProperties) {
SCOPED_TRACE(props.ToString());
if (props != kInsecure) {
ASSERT_FALSE(kInsecure.IsAsSecureAs(props));
ASSERT_TRUE(props.IsAsSecureAs(kInsecure));
if (props != kAuthenticatedSecureShortKey) {
ASSERT_FALSE(kAuthenticatedSecureShortKey.IsAsSecureAs(props));
}
}
}
// Test Encrypted Legacy properties
for (auto props : kTestProperties) {
SCOPED_TRACE(props.ToString());
if (props != kInsecure && props != kEncryptedLegacy) {
ASSERT_FALSE(kEncryptedLegacy.IsAsSecureAs(props));
}
}
// Test Encrypted Secure properties
ASSERT_TRUE(kEncryptedSecure.IsAsSecureAs(kEncryptedLegacy));
for (auto props : std::array{kAuthenticatedLegacy,
kAuthenticatedSecure,
kAuthenticatedSecureShortKey}) {
SCOPED_TRACE(props.ToString());
ASSERT_FALSE(kEncryptedSecure.IsAsSecureAs(props));
}
// Test Authenticated Legacy properties
ASSERT_TRUE(kAuthenticatedLegacy.IsAsSecureAs(kEncryptedLegacy));
for (auto props : std::array{kEncryptedSecure,
kAuthenticatedSecure,
kAuthenticatedSecureShortKey}) {
SCOPED_TRACE(props.ToString());
ASSERT_FALSE(kAuthenticatedLegacy.IsAsSecureAs(props));
}
}
#ifndef NINSPECT
TEST(TypesTest, InspectSecurityProperties) {
inspect::Inspector inspector;
SecurityProperties kInsecure(SecurityLevel::kNoSecurity,
kMinEncryptionKeySize,
/*secure_connections=*/false),
kEncryptedLegacy(SecurityLevel::kEncrypted,
kMaxEncryptionKeySize,
/*secure_connections=*/false),
kEncryptedSecure(SecurityLevel::kEncrypted,
kMaxEncryptionKeySize,
/*secure_connections=*/true),
kAuthenticatedLegacy(SecurityLevel::kAuthenticated,
kMaxEncryptionKeySize,
/*secure_connections=*/false),
kAuthenticatedSecure(SecurityLevel::kAuthenticated,
kMaxEncryptionKeySize,
/*secure_connections=*/true);
// kInsecure
kInsecure.AttachInspect(inspector.GetRoot(), "security_properties");
auto insecure_matcher = AllOf(NodeMatches(AllOf(
NameMatches("security_properties"),
PropertyList(UnorderedElementsAre(StringIs("level", "not secure"),
BoolIs("encrypted", false),
BoolIs("secure_connections", false),
BoolIs("authenticated", false))))));
// kEncryptedLegacy
kEncryptedLegacy.AttachInspect(inspector.GetRoot(), "security_properties");
auto encrypted_legacy_matcher = AllOf(NodeMatches(
AllOf(NameMatches("security_properties"),
PropertyList(UnorderedElementsAre(
StringIs("level", "encrypted"),
BoolIs("encrypted", true),
BoolIs("secure_connections", false),
BoolIs("authenticated", false),
StringIs("key_type", "kUnauthenticatedCombination192"))))));
// kEncryptedSecure
kEncryptedSecure.AttachInspect(inspector.GetRoot(), "security_properties");
auto encrypted_secure_matcher = AllOf(NodeMatches(
AllOf(NameMatches("security_properties"),
PropertyList(UnorderedElementsAre(
StringIs("level", "encrypted"),
BoolIs("encrypted", true),
BoolIs("secure_connections", true),
BoolIs("authenticated", false),
StringIs("key_type", "kUnauthenticatedCombination256"))))));
// kAuthenticatedLegacy
kAuthenticatedLegacy.AttachInspect(inspector.GetRoot(),
"security_properties");
auto authenticated_legacy_matcher = AllOf(NodeMatches(
AllOf(NameMatches("security_properties"),
PropertyList(UnorderedElementsAre(
StringIs("level", "Authenticated"),
BoolIs("encrypted", true),
BoolIs("secure_connections", false),
BoolIs("authenticated", true),
StringIs("key_type", "kAuthenticatedCombination192"))))));
// kAuthenticatedSecure
kAuthenticatedSecure.AttachInspect(inspector.GetRoot(),
"security_properties");
auto authenticated_secure_matcher = AllOf(NodeMatches(AllOf(
NameMatches("security_properties"),
PropertyList(UnorderedElementsAre(
StringIs("level",
"Authenticated with Secure Connections and 128-bit key"),
BoolIs("encrypted", true),
BoolIs("secure_connections", true),
BoolIs("authenticated", true),
StringIs("key_type", "kAuthenticatedCombination256"))))));
inspect::Hierarchy hierarchy = bt::testing::ReadInspect(inspector);
EXPECT_THAT(
hierarchy,
AllOf(ChildrenMatch(UnorderedElementsAre(insecure_matcher,
encrypted_legacy_matcher,
encrypted_secure_matcher,
authenticated_legacy_matcher,
authenticated_secure_matcher))));
}
#endif // NINSPECT
} // namespace
} // namespace bt::sm