diff --git a/security/oc_acl.c b/security/oc_acl.c index 07aa40bbe..3c416280c 100644 --- a/security/oc_acl.c +++ b/security/oc_acl.c @@ -23,19 +23,13 @@ #include "api/oc_helpers_internal.h" #include "api/oc_platform_internal.h" #include "api/oc_ri_internal.h" -#include "oc_acl_internal.h" #include "oc_api.h" -#include "oc_certs_validate_internal.h" -#include "oc_config.h" #include "oc_core_res.h" -#include "oc_cred_internal.h" -#include "oc_doxm_internal.h" -#include "oc_pstat_internal.h" -#include "oc_rep.h" -#include "oc_roles_internal.h" #include "oc_store.h" -#include "oc_tls_internal.h" #include "port/oc_assert.h" +#include "port/oc_random.h" +#include "security/oc_acl_internal.h" +#include "security/oc_pstat_internal.h" #include "util/oc_features.h" #include "util/oc_macros_internal.h" diff --git a/security/oc_acl_util.c b/security/oc_acl_util.c index 55797d198..2e71a8998 100644 --- a/security/oc_acl_util.c +++ b/security/oc_acl_util.c @@ -43,6 +43,7 @@ #include #if OC_DBG_IS_ENABLED + static void print_acls(size_t device) { @@ -101,6 +102,7 @@ print_acls(size_t device) ace = ace->next; } } + #endif /* OC_DBG_IS_ENABLED */ static uint16_t @@ -164,25 +166,17 @@ static bool eval_access(oc_method_t method, uint16_t permission) { if (permission != 0) { - switch (method) { - case OC_GET: - if ((permission & OC_PERM_RETRIEVE) || (permission & OC_PERM_NOTIFY)) { - return true; - } - break; - case OC_PUT: - case OC_POST: - if ((permission & OC_PERM_CREATE) || (permission & OC_PERM_UPDATE)) { - return true; - } - break; - case OC_DELETE: - if (permission & OC_PERM_DELETE) { - return true; - } - break; - default: - break; + if (method == OC_GET) { + return (permission & OC_PERM_RETRIEVE) != 0 || + (permission & OC_PERM_NOTIFY) != 0; + } + + if (method == OC_POST || method == OC_PUT) { + return (permission & OC_PERM_CREATE) != 0 || + (permission & OC_PERM_UPDATE) != 0; + } + if (method == OC_DELETE) { + return (permission & OC_PERM_DELETE) != 0; } } return false; @@ -287,39 +281,40 @@ oc_sec_check_acl(oc_method_t method, const oc_resource_t *resource, is_vertical = oc_core_is_vertical_resource(resource, resource->device); } - const oc_sec_pstat_t *pstat = oc_sec_get_pstat(endpoint->device); - /* All unicast requests which are not received over the open Device DOC - * shall be rejected with an appropriate error message (e.g. forbidden), - * regardless of the configuration of the ACEs in the "/oic/sec/acl2" - * Resource. - */ - if (pstat->s == OC_DOS_RFOTM && !(endpoint->flags & SECURED) && - oc_tls_num_peers(endpoint->device) == 1) { - OC_DBG("oc_sec_check_acl: unencrypted request received while DOC is open - " - "access forbidden"); - return false; - } + oc_dostype_t ps = oc_sec_get_pstat(endpoint->device)->s; + if (ps == OC_DOS_RFOTM && (endpoint->flags & SECURED) == 0) { + /* All unicast requests which are not received over the open Device DOC + * shall be rejected with an appropriate error message (e.g. forbidden), + * regardless of the configuration of the ACEs in the "/oic/sec/acl2" + * Resource. + */ + if (oc_tls_num_peers(endpoint->device) == 1) { + OC_DBG( + "oc_sec_check_acl: unencrypted request received while DOC is open - " + "access forbidden"); + return false; + } #ifdef OC_HAS_FEATURE_RESOURCE_ACCESS_IN_RFOTM - /* Allow access to resources in RFOTM mode if the feature is enabled and - * permission match the method. */ - if (pstat->s == OC_DOS_RFOTM && !(endpoint->flags & SECURED) && - (resource->properties & OC_ACCESS_IN_RFOTM) == OC_ACCESS_IN_RFOTM && - eval_access(method, resource->anon_permission_in_rfotm)) { - OC_DBG("oc_sec_check_acl: access granted to %s via anon permission in " - "RFOTM state", - oc_string(resource->uri)); - return true; - } + /* Allow access to resources in RFOTM mode if the feature is enabled and + * permission match the method. */ + if ((resource->properties & OC_ACCESS_IN_RFOTM) == OC_ACCESS_IN_RFOTM && + eval_access(method, (uint16_t)resource->anon_permission_in_rfotm)) { + OC_DBG("oc_sec_check_acl: access granted to %s via anon permission in " + "RFOTM state", + oc_string(resource->uri)); + return true; + } #endif /* OC_HAS_FEATURE_RESOURCE_ACCESS_IN_RFOTM */ + } /* NCRs are accessible only in RFNOP */ - if (!is_DCR && pstat->s != OC_DOS_RFNOP) { + if (!is_DCR && ps != OC_DOS_RFNOP) { OC_DBG("oc_sec_check_acl: resource is NCR and dos is not RFNOP"); return false; } /* anon-clear access to vertical resources is prohibited */ - if (is_vertical && !(endpoint->flags & SECURED)) { + if (is_vertical && (endpoint->flags & SECURED) == 0) { OC_DBG("oc_sec_check_acl: anon-clear access to vertical resources is " "prohibited"); return false; @@ -329,24 +324,26 @@ oc_sec_check_acl(oc_method_t method, const oc_resource_t *resource, * Resource. */ const oc_tls_peer_t *peer = oc_tls_get_peer(endpoint); - if (peer && peer->doc && is_DCR) { + if (is_DCR && peer && peer->doc) { OC_DBG("oc_sec_check_acl: connection is DOC and request directed to DCR - " "access granted"); return true; } if (method == OC_GET && - oc_sec_check_acl_on_get(resource, pstat->s == OC_DOS_RFOTM)) { + oc_sec_check_acl_on_get(resource, ps == OC_DOS_RFOTM)) { + OC_DBG("oc_sec_check_acl: access granted to %s via special GET rule", + oc_string(resource->uri)); return true; } /* Requests over unsecured channel prior to DOC */ - if (pstat->s == OC_DOS_RFOTM && oc_tls_num_peers(endpoint->device) == 0) { + if (ps == OC_DOS_RFOTM && oc_tls_num_peers(endpoint->device) == 0) { /* Anonymous Retrieve and Updates requests to “/oic/sec/doxm” shall be granted. */ if (oc_sec_is_doxm_resource_uri(oc_string_view2(&resource->uri))) { - OC_DBG("oc_sec_check_acl: RW access granted to /doxm prior to DOC"); + OC_DBG("oc_sec_check_acl: RW access granted to doxm prior to DOC"); return true; } /* All Retrieve requests to the “/oic/sec/pstat” Resource shall be @@ -359,12 +356,13 @@ oc_sec_check_acl(oc_method_t method, const oc_resource_t *resource, return true; } /* Reject all other requests */ + OC_DBG("oc_sec_check_acl: access denied to %s prior to DOC", + oc_string(resource->uri)); return false; } - if ((pstat->s == OC_DOS_RFPRO || pstat->s == OC_DOS_RFNOP || - pstat->s == OC_DOS_SRESET) && - !(endpoint->flags & SECURED)) { + if ((ps == OC_DOS_RFPRO || ps == OC_DOS_RFNOP || ps == OC_DOS_SRESET) && + (endpoint->flags & SECURED) == 0) { /* anon-clear requests to SVRs while the * dos is RFPRO, RFNOP or SRESET should not be authorized * regardless of the ACL configuration. @@ -381,8 +379,7 @@ oc_sec_check_acl(oc_method_t method, const oc_resource_t *resource, if (oc_sec_check_acl_by_uuid(uuid, endpoint->device, resource)) { return true; } - if ((pstat->s == OC_DOS_RFPRO || pstat->s == OC_DOS_RFNOP || - pstat->s == OC_DOS_SRESET) && + if ((ps == OC_DOS_RFPRO || ps == OC_DOS_RFNOP || ps == OC_DOS_SRESET) && oc_string_is_cstr_equal(&resource->uri, OCF_SEC_ROLES_URI, OC_CHAR_ARRAY_LEN(OCF_SEC_ROLES_URI))) { OC_DBG("oc_acl: peer has implicit access to /oic/sec/roles in RFPRO, " diff --git a/security/unittest/acltest.cpp b/security/unittest/acltest.cpp index 83a47ab81..e829ca72d 100644 --- a/security/unittest/acltest.cpp +++ b/security/unittest/acltest.cpp @@ -29,6 +29,11 @@ #include "security/oc_acl_internal.h" #include "security/oc_acl_util_internal.h" #include "security/oc_pstat_internal.h" +#include "security/oc_svr_internal.h" +#include "security/oc_tls_internal.h" +#include "tests/gtest/Endpoint.h" +#include "tests/gtest/Resource.h" +#include "tests/gtest/tls/Peer.h" #include "util/oc_list.h" #ifdef OC_HAS_FEATURE_PUSH @@ -38,6 +43,8 @@ #include "gtest/gtest.h" #include +static constexpr size_t kDeviceID = 0; + static const std::string kDeviceURI{ "/oic/d" }; static const std::string kDeviceType{ "oic.d.light" }; static const std::string kDeviceName{ "Table Lamp" }; @@ -53,28 +60,29 @@ class TestAcl : public testing::Test { oc_runtime_init(); oc_ri_init(); oc_core_init(); + ASSERT_EQ(0, oc_init_platform("plgd", nullptr, nullptr)); ASSERT_EQ(0, oc_add_device(kDeviceURI.c_str(), kDeviceType.c_str(), kDeviceName.c_str(), kOCFSpecVersion.c_str(), kOCFDataModelVersion.c_str(), nullptr, nullptr)); - device_id_ = 0; - oc_sec_acl_init(); + oc_sec_svr_create(); + + oc_log_set_level(OC_LOG_LEVEL_DEBUG); } void TearDown() override { + oc_log_set_level(OC_LOG_LEVEL_INFO); + #ifdef OC_HAS_FEATURE_PUSH oc_push_free(); #endif /* OC_HAS_FEATURE_PUSH */ - oc_sec_acl_free(); - oc_connectivity_shutdown(device_id_); + oc_sec_svr_free(); + oc_connectivity_shutdown(kDeviceID); oc_core_shutdown(); oc_ri_shutdown(); oc_runtime_shutdown(); oc_network_event_handler_mutex_destroy(); } - -public: - size_t device_id_; }; static size_t @@ -91,10 +99,10 @@ oc_sec_ace_count(size_t device) TEST_F(TestAcl, oc_sec_acl_add_bootstrap_acl) { - EXPECT_EQ(true, oc_sec_acl_add_bootstrap_acl(device_id_)); - const oc_sec_acl_t *acl = oc_sec_get_acl(device_id_); + EXPECT_EQ(true, oc_sec_acl_add_bootstrap_acl(kDeviceID)); + const oc_sec_acl_t *acl = oc_sec_get_acl(kDeviceID); EXPECT_NE(nullptr, acl); - EXPECT_EQ(1, oc_sec_ace_count(device_id_)); + EXPECT_EQ(1, oc_sec_ace_count(kDeviceID)); } TEST_F(TestAcl, oc_sec_acl_clear) @@ -104,7 +112,7 @@ TEST_F(TestAcl, oc_sec_acl_clear) anon_clear.conn = OC_CONN_ANON_CLEAR; EXPECT_EQ(true, oc_sec_ace_update_res(OC_SUBJECT_CONN, &anon_clear, -1, OC_PERM_RETRIEVE, nullptr, "/test/a", - OC_ACE_NO_WC, device_id_, nullptr)); + OC_ACE_NO_WC, kDeviceID, nullptr)); oc_uuid_t uuid = { { 0 } }; oc_gen_uuid(&uuid); @@ -113,7 +121,7 @@ TEST_F(TestAcl, oc_sec_acl_clear) memcpy(&subject.uuid, &uuid, sizeof(oc_uuid_t)); EXPECT_EQ(true, oc_sec_ace_update_res(OC_SUBJECT_UUID, &subject, -1, OC_PERM_UPDATE, nullptr, "/test/b", - OC_ACE_NO_WC, device_id_, nullptr)); + OC_ACE_NO_WC, kDeviceID, nullptr)); memset(&subject, 0, sizeof(oc_ace_subject_t)); std::string testRole{ "test.role" }; @@ -123,39 +131,40 @@ TEST_F(TestAcl, oc_sec_acl_clear) testAuthority.length()); EXPECT_EQ(true, oc_sec_ace_update_res(OC_SUBJECT_ROLE, &subject, -1, OC_PERM_NOTIFY, nullptr, "/test/c", - OC_ACE_NO_WC, device_id_, nullptr)); - EXPECT_EQ(3, oc_sec_ace_count(device_id_)); + OC_ACE_NO_WC, kDeviceID, nullptr)); + EXPECT_EQ(3, oc_sec_ace_count(kDeviceID)); oc_sec_acl_clear( - device_id_, [](const oc_sec_ace_t *, void *) { return false; }, nullptr); - EXPECT_EQ(3, oc_sec_ace_count(device_id_)); + kDeviceID, [](const oc_sec_ace_t *, void *) { return false; }, nullptr); + EXPECT_EQ(3, oc_sec_ace_count(kDeviceID)); oc_sec_ace_t *ace = - oc_sec_acl_find_subject(nullptr, OC_SUBJECT_CONN, &anon_clear, /*aceid*/ -1, + oc_sec_acl_find_subject(nullptr, OC_SUBJECT_CONN, &anon_clear, /*aceid*/ + -1, /*permission*/ 0, /*tag*/ nullptr, - /*match_tag*/ false, device_id_); + /*match_tag*/ false, kDeviceID); EXPECT_NE(nullptr, ace); oc_sec_acl_clear( - device_id_, + kDeviceID, [](const oc_sec_ace_t *entry, void *) { return entry->subject_type == OC_SUBJECT_CONN; }, nullptr); - EXPECT_EQ(2, oc_sec_ace_count(device_id_)); - ace = - oc_sec_acl_find_subject(nullptr, OC_SUBJECT_CONN, &anon_clear, /*aceid*/ -1, - /*permission*/ 0, /*tag*/ nullptr, - /*match_tag*/ false, device_id_); + EXPECT_EQ(2, oc_sec_ace_count(kDeviceID)); + ace = oc_sec_acl_find_subject(nullptr, OC_SUBJECT_CONN, &anon_clear, /*aceid*/ + -1, + /*permission*/ 0, /*tag*/ nullptr, + /*match_tag*/ false, kDeviceID); EXPECT_EQ(nullptr, ace); ace = oc_sec_acl_find_subject(nullptr, OC_SUBJECT_ROLE, &subject, /*aceid*/ -1, /*permission*/ 0, - /*tag*/ nullptr, /*match_tag*/ false, device_id_); + /*tag*/ nullptr, /*match_tag*/ false, kDeviceID); EXPECT_NE(nullptr, ace); int aceid{ ace->aceid }; oc_sec_acl_clear( - device_id_, + kDeviceID, [](const oc_sec_ace_t *entry, void *data) { const auto *id = static_cast(data); return entry->aceid == *id; @@ -164,11 +173,11 @@ TEST_F(TestAcl, oc_sec_acl_clear) ace = oc_sec_acl_find_subject(nullptr, OC_SUBJECT_ROLE, &subject, /*aceid*/ -1, /*permission*/ 0, - /*tag*/ nullptr, /*match_tag*/ false, device_id_); + /*tag*/ nullptr, /*match_tag*/ false, kDeviceID); EXPECT_EQ(nullptr, ace); - oc_sec_acl_clear(device_id_, nullptr, nullptr); - EXPECT_EQ(0, oc_sec_ace_count(device_id_)); + oc_sec_acl_clear(kDeviceID, nullptr, nullptr); + EXPECT_EQ(0, oc_sec_ace_count(kDeviceID)); oc_free_string(&subject.role.authority); oc_free_string(&subject.role.role); @@ -187,7 +196,7 @@ onGet(oc_request_t *, oc_interface_mask_t, void *) TEST_F(TestAcl, oc_sec_check_acl_in_RFOTM) { oc_sec_pstat_init(); - oc_sec_pstat_t *pstat = oc_sec_get_pstat(device_id_); + oc_sec_pstat_t *pstat = oc_sec_get_pstat(kDeviceID); EXPECT_NE(nullptr, pstat); pstat->s = OC_DOS_RFOTM; oc_resource_t *res = @@ -220,4 +229,152 @@ TEST_F(TestAcl, oc_sec_check_acl_in_RFOTM) } #endif /* OC_HAS_FEATURE_RESOURCE_ACCESS_IN_RFOTM */ +TEST_F(TestAcl, oc_sec_check_acl_FailInsecureDOC) +{ + oc_sec_pstat_t *pstat = oc_sec_get_pstat(kDeviceID); + ASSERT_NE(nullptr, pstat); + pstat->s = OC_DOS_RFOTM; + + auto tlsPeer = + oc::tls::MakePeer("coap://[ff02::41]:1336", MBEDTLS_SSL_IS_CLIENT); + oc_endpoint_t ep = oc::endpoint::FromString(tlsPeer.address); + ASSERT_EQ(0, ep.flags & SECURED); + ep.device = kDeviceID; + oc_tls_peer_t *peer = oc_tls_add_or_get_peer(&ep, tlsPeer.role, nullptr); + ASSERT_NE(nullptr, peer); + ASSERT_EQ(1, oc_tls_num_peers(kDeviceID)); + oc_resource_t resource{}; + resource.device = kDeviceID; + EXPECT_FALSE(oc_sec_check_acl(OC_GET, &resource, &ep)); + + oc_tls_remove_peer(&ep); +} + +#ifdef OC_HAS_FEATURE_RESOURCE_ACCESS_IN_RFOTM + +TEST_F(TestAcl, oc_sec_check_acl_AccessInRFOTM) +{ + oc_sec_pstat_t *pstat = oc_sec_get_pstat(kDeviceID); + ASSERT_NE(nullptr, pstat); + pstat->s = OC_DOS_RFOTM; + + oc_resource_t resource{}; + resource.device = kDeviceID; + ASSERT_TRUE( + oc::SetAccessInRFOTM(&resource, false, OC_PERM_RETRIEVE | OC_PERM_UPDATE)); + oc_endpoint_t ep{}; + ep.device = kDeviceID; + EXPECT_TRUE(oc_sec_check_acl(OC_GET, &resource, &ep)); +} + +#endif /* OC_HAS_FEATURE_RESOURCE_ACCESS_IN_RFOTM */ + +TEST_F(TestAcl, oc_sec_check_acl_FailNCRInNonRFNOP) +{ + oc_sec_pstat_t *pstat = oc_sec_get_pstat(kDeviceID); + ASSERT_NE(nullptr, pstat); + + oc_resource_t resource{}; + resource.device = kDeviceID; + ASSERT_FALSE(oc_core_is_DCR(&resource, kDeviceID)); + oc_endpoint_t ep{}; + ep.device = kDeviceID; + // device in non-RFNOP cannot access NCRs + std::vector states{ OC_DOS_RESET, OC_DOS_RFOTM, OC_DOS_RFPRO, + OC_DOS_SRESET }; + for (auto state : states) { + pstat->s = state; + EXPECT_FALSE(oc_sec_check_acl(OC_GET, &resource, &ep)); + } +} + +TEST_F(TestAcl, oc_sec_check_acl_FailInsecureAccessToVerticalResource) +{ + oc_sec_pstat_t *pstat = oc_sec_get_pstat(kDeviceID); + ASSERT_NE(nullptr, pstat); + pstat->s = OC_DOS_RFNOP; + oc_resource_t resource{}; + resource.device = kDeviceID; + ASSERT_FALSE(oc_core_is_DCR(&resource, kDeviceID)); + ASSERT_TRUE(oc_core_is_vertical_resource(&resource, kDeviceID)); + oc_endpoint_t ep{}; + ep.device = kDeviceID; + EXPECT_FALSE(oc_sec_check_acl(OC_GET, &resource, &ep)); +} + +TEST_F(TestAcl, oc_sec_check_acl_DOCAccessToDCR) +{ + // DCR + oc_resource_t *resource = oc_core_get_resource_by_index(OCF_P, kDeviceID); + ASSERT_NE(nullptr, resource); + ASSERT_TRUE(oc_core_is_DCR(resource, kDeviceID)); + // DOC peer + oc_sec_pstat_t *pstat = oc_sec_get_pstat(kDeviceID); + ASSERT_NE(nullptr, pstat); + pstat->s = OC_DOS_RFOTM; + auto tlsPeer = + oc::tls::MakePeer("coaps://[ff02::41]:1336", MBEDTLS_SSL_IS_CLIENT); + oc_endpoint_t ep = oc::endpoint::FromString(tlsPeer.address); + ASSERT_NE(0, ep.flags & SECURED); + ep.device = kDeviceID; + oc_tls_peer_t *peer = oc_tls_add_or_get_peer(&ep, tlsPeer.role, nullptr); + ASSERT_NE(nullptr, peer); + ASSERT_TRUE(peer->doc); + ASSERT_EQ(1, oc_tls_num_peers(kDeviceID)); + + EXPECT_TRUE(oc_sec_check_acl(OC_GET, resource, &ep)); + + oc_tls_remove_peer(&ep); +} + +TEST_F(TestAcl, oc_sec_check_acl_GETinRFOTM) +{ + // oic/d, oic/p and oic/res are accessible to GET requests in RFOTM + oc_sec_pstat_t *pstat = oc_sec_get_pstat(kDeviceID); + ASSERT_NE(nullptr, pstat); + pstat->s = OC_DOS_RFOTM; + + std::vector resources{ OCF_P, OCF_D, OCF_RES }; + for (auto type : resources) { + oc_resource_t *resource = oc_core_get_resource_by_index(type, kDeviceID); + ASSERT_NE(nullptr, resource); + oc_endpoint_t ep{}; + ep.device = kDeviceID; + EXPECT_TRUE(oc_sec_check_acl(OC_GET, resource, &ep)); + } +} + +TEST_F(TestAcl, oc_sec_check_acl_PriorToDOCAccessToDoxmInRFOTM) +{ + oc_sec_pstat_t *pstat = oc_sec_get_pstat(kDeviceID); + ASSERT_NE(nullptr, pstat); + pstat->s = OC_DOS_RFOTM; + oc_resource_t *resource = + oc_core_get_resource_by_index(OCF_SEC_DOXM, kDeviceID); + ASSERT_NE(nullptr, resource); + oc_endpoint_t ep{}; + ep.device = kDeviceID; + std::vector methods{ OC_GET, OC_POST, OC_PUT, OC_DELETE }; + for (auto method : methods) { + EXPECT_TRUE(oc_sec_check_acl(method, resource, &ep)); + } +} + +TEST_F(TestAcl, oc_sec_check_acl_PriorToDOCGetAccessToPstatInRFOTM) +{ + oc_sec_pstat_t *pstat = oc_sec_get_pstat(kDeviceID); + ASSERT_NE(nullptr, pstat); + pstat->s = OC_DOS_RFOTM; + oc_resource_t *resource = + oc_core_get_resource_by_index(OCF_SEC_PSTAT, kDeviceID); + ASSERT_NE(nullptr, resource); + oc_endpoint_t ep{}; + ep.device = kDeviceID; + EXPECT_TRUE(oc_sec_check_acl(OC_GET, resource, &ep)); + std::vector methods{ OC_POST, OC_PUT, OC_DELETE }; + for (auto method : methods) { + EXPECT_FALSE(oc_sec_check_acl(method, resource, &ep)); + } +} + #endif /* OC_SECURITY */