diff --git a/include/oc_acl.h b/include/oc_acl.h index f10435f00..7a2438a01 100644 --- a/include/oc_acl.h +++ b/include/oc_acl.h @@ -80,11 +80,13 @@ typedef enum { */ typedef struct oc_ace_res_t { - struct oc_ace_res_t *next; ///< pointer to next entry - oc_string_t href; ///< href + struct oc_ace_res_t *next; ///< pointer to next entry + oc_string_t href; ///< href +#if 0 oc_interface_mask_t interfaces; ///< applicable interfaces (as bit mask) oc_string_array_t types; ///< resource types (rt) - oc_ace_wildcard_t wildcard; ///< wildcard info +#endif + oc_ace_wildcard_t wildcard; ///< wildcard info } oc_ace_res_t; /** diff --git a/security/oc_ace.c b/security/oc_ace.c index 71d763edd..4aaab5004 100644 --- a/security/oc_ace.c +++ b/security/oc_ace.c @@ -67,6 +67,21 @@ oc_ace_wildcard_to_string(oc_ace_wildcard_t wc) return OC_STRING_VIEW_NULL; } +int +oc_ace_wildcard_from_string(oc_string_view_t str) +{ + if (oc_string_view_is_equal(str, OC_STRING_VIEW(OC_ACE_WC_ALL_STR))) { + return OC_ACE_WC_ALL; + } + if (oc_string_view_is_equal(str, OC_STRING_VIEW(OC_ACE_WC_ALL_SECURED_STR))) { + return OC_ACE_WC_ALL_SECURED; + } + if (oc_string_view_is_equal(str, OC_STRING_VIEW(OC_ACE_WC_ALL_PUBLIC_STR))) { + return OC_ACE_WC_ALL_PUBLIC; + } + return -1; +} + #if OC_DBG_IS_ENABLED static void log_new_ace(const oc_sec_ace_t *ace) @@ -148,9 +163,9 @@ log_new_ace_resource(const oc_ace_res_t *res, uint16_t permission) OC_DBG("Adding wildcard resource %s with permission %d", wcv.data, permission); } - if (oc_string(res->href) != NULL) { - OC_DBG("Adding resource %s with permission %d", oc_string(res->href), - permission); + const char *href = oc_string(res->href); + if (href != NULL) { + OC_DBG("Adding resource %s with permission %d", href, permission); } // GCOVR_EXCL_STOP } @@ -228,8 +243,8 @@ oc_sec_free_ace(oc_sec_ace_t *ace) oc_memb_free(&g_ace_l, ace); } -static bool -ace_has_matching_tag(const oc_sec_ace_t *ace, oc_string_view_t tag) +bool +oc_ace_has_matching_tag(const oc_sec_ace_t *ace, oc_string_view_t tag) { if (tag.data == NULL) { return oc_string(ace->tag) == NULL; @@ -237,9 +252,9 @@ ace_has_matching_tag(const oc_sec_ace_t *ace, oc_string_view_t tag) return oc_string_is_cstr_equal(&ace->tag, tag.data, tag.length); } -static bool -ace_has_matching_subject(const oc_sec_ace_t *ace, oc_ace_subject_type_t type, - oc_ace_subject_view_t subject) +bool +oc_ace_has_matching_subject(const oc_sec_ace_t *ace, oc_ace_subject_type_t type, + oc_ace_subject_view_t subject) { if (ace->subject_type != type) { return false; @@ -272,10 +287,10 @@ oc_sec_ace_find_subject(oc_sec_ace_t *ace, oc_ace_subject_type_t type, if (permission != 0 && ace->permission != permission) { continue; } - if (match_tag && !ace_has_matching_tag(ace, tag)) { + if (match_tag && !oc_ace_has_matching_tag(ace, tag)) { continue; } - if (ace_has_matching_subject(ace, type, subject)) { + if (oc_ace_has_matching_subject(ace, type, subject)) { return ace; } } @@ -468,59 +483,72 @@ oc_sec_encode_ace(CborEncoder *encoder, const oc_sec_ace_t *sub, } } -static int -ace_decode_subject(const oc_rep_t *rep, oc_ace_subject_view_t *subject) +typedef struct { - const oc_string_t *uuid = NULL; - const oc_string_t *role = NULL; - const oc_string_t *authority = NULL; - const oc_string_t *conntype = NULL; - for (; rep != NULL; rep = rep->next) { - if (rep->type == OC_REP_STRING) { - if (oc_rep_is_property(rep, OC_ACE_PROP_SUBJECT_UUID, - OC_CHAR_ARRAY_LEN(OC_ACE_PROP_SUBJECT_UUID))) { - uuid = &rep->value.string; - continue; - } + const oc_string_t *uuid; + const oc_string_t *role; + const oc_string_t *authority; + const oc_string_t *conntype; +} ace_subject_decode_t; - if (oc_rep_is_property(rep, OC_ACE_PROP_SUBJECT_ROLE, - OC_CHAR_ARRAY_LEN(OC_ACE_PROP_SUBJECT_ROLE))) { - role = &rep->value.string; - continue; - } +static bool +ace_decode_subject_string_property(const oc_rep_t *rep, + ace_subject_decode_t *decode) +{ + if (oc_rep_is_property(rep, OC_ACE_PROP_SUBJECT_UUID, + OC_CHAR_ARRAY_LEN(OC_ACE_PROP_SUBJECT_UUID))) { + decode->uuid = &rep->value.string; + return true; + } - if (oc_rep_is_property( - rep, OC_ACE_PROP_SUBJECT_AUTHORITY, - OC_CHAR_ARRAY_LEN(OC_ACE_PROP_SUBJECT_AUTHORITY))) { - authority = &rep->value.string; - continue; - } + if (oc_rep_is_property(rep, OC_ACE_PROP_SUBJECT_ROLE, + OC_CHAR_ARRAY_LEN(OC_ACE_PROP_SUBJECT_ROLE))) { + decode->role = &rep->value.string; + return true; + } - if (oc_rep_is_property(rep, OC_ACE_PROP_SUBJECT_CONNTYPE, - OC_CHAR_ARRAY_LEN(OC_ACE_PROP_SUBJECT_CONNTYPE))) { - conntype = &rep->value.string; - continue; - } - } + if (oc_rep_is_property(rep, OC_ACE_PROP_SUBJECT_AUTHORITY, + OC_CHAR_ARRAY_LEN(OC_ACE_PROP_SUBJECT_AUTHORITY))) { + decode->authority = &rep->value.string; + return true; + } + if (oc_rep_is_property(rep, OC_ACE_PROP_SUBJECT_CONNTYPE, + OC_CHAR_ARRAY_LEN(OC_ACE_PROP_SUBJECT_CONNTYPE))) { + decode->conntype = &rep->value.string; + return true; + } + return false; +} + +static int +ace_decode_subject(const oc_rep_t *rep, oc_ace_subject_view_t *subject) +{ + ace_subject_decode_t decode = { NULL, NULL, NULL, NULL }; + for (; rep != NULL; rep = rep->next) { + if (rep->type == OC_REP_STRING && + ace_decode_subject_string_property(rep, &decode)) { + continue; + } OC_ERR("ACE decode subject: unknown property (name=%s, type=%d)", oc_string(rep->name) != NULL ? oc_string(rep->name) : "(null)", (int)rep->type); return -1; } - bool has_uuid = uuid != NULL; - bool has_role = role != NULL || authority != NULL; - bool has_conntype = conntype != NULL; + bool has_uuid = decode.uuid != NULL; + bool has_role = decode.role != NULL || decode.authority != NULL; + bool has_conntype = decode.conntype != NULL; if (has_uuid) { if (has_role || has_conntype) { OC_ERR("ACE decode subject: uuid cannot be used with role or conntype"); return -1; } oc_uuid_t id; - if (oc_str_to_uuid_v1(oc_string(*uuid), oc_string_len_unsafe(*uuid), &id) < - 0) { - OC_ERR("ACE decode subject: uuid(%s) is invalid", oc_string(*uuid)); + if (oc_str_to_uuid_v1(oc_string(*decode.uuid), + oc_string_len_unsafe(*decode.uuid), &id) < 0) { + OC_ERR("ACE decode subject: uuid(%s) is invalid", + oc_string(*decode.uuid)); return -1; } subject->uuid = id; @@ -528,7 +556,7 @@ ace_decode_subject(const oc_rep_t *rep, oc_ace_subject_view_t *subject) } if (has_role) { - if (role == NULL) { + if (decode.role == NULL) { OC_ERR("ACE decode subject: role is missing"); return -1; } @@ -536,18 +564,19 @@ ace_decode_subject(const oc_rep_t *rep, oc_ace_subject_view_t *subject) OC_ERR("ACE decode subject: conntype cannot be used with role"); return -1; } - subject->role = (struct oc_ace_subject_role_view_t){ - .role = oc_string_view2(role), - .authority = oc_string_view2(authority), + subject->role = (oc_ace_subject_role_view_t){ + .role = oc_string_view2(decode.role), + .authority = oc_string_view2(decode.authority), }; return OC_SUBJECT_ROLE; } if (has_conntype) { - int conn = oc_ace_connection_type_from_string(oc_string_view2(conntype)); + int conn = + oc_ace_connection_type_from_string(oc_string_view2(decode.conntype)); if (conn < 0) { OC_ERR("ACE decode subject: conntype(%s) is invalid", - oc_string(*conntype)); + oc_string(*decode.conntype)); return -1; } subject->conn = (oc_ace_connection_type_t)conn; @@ -581,7 +610,7 @@ ace_decode_property(const oc_rep_t *rep, oc_sec_ace_decode_t *acedecode) acedecode->aceid = (int)rep->value.integer; return true; } - return false; + goto unknown_property; } if (rep->type == OC_REP_STRING) { @@ -590,7 +619,7 @@ ace_decode_property(const oc_rep_t *rep, oc_sec_ace_decode_t *acedecode) acedecode->tag = &rep->value.string; return true; } - return false; + goto unknown_property; } if (rep->type == OC_REP_OBJECT) { @@ -605,7 +634,7 @@ ace_decode_property(const oc_rep_t *rep, oc_sec_ace_decode_t *acedecode) acedecode->subject_type = (oc_ace_subject_type_t)subject_type; return true; } - return false; + goto unknown_property; } if (rep->type == OC_REP_OBJECT_ARRAY) { @@ -614,8 +643,13 @@ ace_decode_property(const oc_rep_t *rep, oc_sec_ace_decode_t *acedecode) acedecode->resources = rep->value.object_array; return true; } - return false; + goto unknown_property; } + +unknown_property: + OC_ERR("ACE decode: unknown property (name=%s, type=%d)", + oc_string(rep->name) != NULL ? oc_string(rep->name) : "(null)", + (int)rep->type); return false; } @@ -625,15 +659,85 @@ oc_sec_decode_ace(const oc_rep_t *rep, oc_sec_ace_decode_t *acedecode) for (; rep != NULL; rep = rep->next) { if (!ace_decode_property(rep, acedecode)) { - OC_ERR("ACE decode: unknown property (name=%s, type=%d)", - oc_string(rep->name) != NULL ? oc_string(rep->name) : "(null)", - (int)rep->type); return false; } - OC_DBG("aceid: %d, permission: %d, subject_type: %d", acedecode->aceid, - acedecode->permission, acedecode->subject_type); + OC_DBG("aceid: %d, permission: %" PRIu16 ", subject_type: %d", + acedecode->aceid, acedecode->permission, acedecode->subject_type); + } + return true; +} + +static bool +ace_decode_resource_string_property(const oc_rep_t *rep, + oc_sec_ace_res_decode_t *aceresdecode) +{ + if (oc_rep_is_property(rep, OC_ACE_PROP_RESOURCE_HREF, + OC_CHAR_ARRAY_LEN(OC_ACE_PROP_RESOURCE_HREF))) { + aceresdecode->href = &rep->value.string; + return true; + } + if (oc_rep_is_property(rep, OC_ACE_PROP_RESOURCE_WILDCARD, + OC_CHAR_ARRAY_LEN(OC_ACE_PROP_RESOURCE_WILDCARD))) { + int wc = oc_ace_wildcard_from_string(oc_string_view2(&rep->value.string)); + if (wc == -1) { + OC_ERR("ACE decode resource: wildcard(%s) is invalid", + oc_string(rep->value.string)); + return false; + } + aceresdecode->wildcard = (oc_ace_wildcard_t)wc; + return true; + } + return false; +} + +static bool +ace_decode_resource(const oc_rep_t *rep, oc_sec_ace_res_decode_t *aceresdecode) +{ + for (; rep != NULL; rep = rep->next) { + if (rep->type == OC_REP_STRING && + ace_decode_resource_string_property(rep, aceresdecode)) { + continue; + } + if (rep->type == OC_REP_STRING_ARRAY && + oc_rep_is_property(rep, "if", OC_CHAR_ARRAY_LEN("if"))) { + // TODO: remove from plgd tests + continue; + } + OC_ERR("ACE decode resource: unknown property (name=%s, type=%d)", + oc_string(rep->name) != NULL ? oc_string(rep->name) : "(null)", + (int)rep->type); + return false; + } + +#if 0 +#ifdef OC_SERVER + oc_resource_properties_t wc_r = 0; + if (wc == OC_ACE_WC_ALL || wc == OC_ACE_WC_ALL_SECURED) { + wc_r = ~0; + } + if (wc == OC_ACE_WC_ALL_PUBLIC) { + wc_r = ~OC_DISCOVERABLE; } + aceresdecode->wc_r = wc_r; +#endif /* OC_SERVER */ +#endif + return true; } +bool +oc_sec_decode_ace_resources(const oc_rep_t *rep, + oc_sec_on_decode_ace_resource_fn_t on_decode, + void *decode_fn_data) +{ + for (; rep != NULL; rep = rep->next) { + oc_sec_ace_res_decode_t aceres_decode; + memset(&aceres_decode, 0, sizeof(oc_sec_ace_res_decode_t)); + if (!ace_decode_resource(rep->value.object, &aceres_decode)) { + return false; + } + on_decode(&aceres_decode, decode_fn_data); + } + return true; +} #endif /* OC_SECURITY */ diff --git a/security/oc_ace_internal.h b/security/oc_ace_internal.h index 006dace63..abfafb750 100644 --- a/security/oc_ace_internal.h +++ b/security/oc_ace_internal.h @@ -41,6 +41,9 @@ extern "C" { /** Convert wildcard to string representation */ oc_string_view_t oc_ace_wildcard_to_string(oc_ace_wildcard_t wc); +/** Convert string to wildcard */ +int oc_ace_wildcard_from_string(oc_string_view_t str); + /** Convert connection type to string */ oc_string_view_t oc_ace_connection_type_to_string( oc_ace_connection_type_t conn); @@ -48,13 +51,15 @@ oc_string_view_t oc_ace_connection_type_to_string( /** Convert string to connection type */ int oc_ace_connection_type_from_string(oc_string_view_t str); +typedef struct +{ + oc_string_view_t role; + oc_string_view_t authority; +} oc_ace_subject_role_view_t; + typedef union { oc_uuid_t uuid; - struct oc_ace_subject_role_view_t - { - oc_string_view_t role; - oc_string_view_t authority; - } role; + oc_ace_subject_role_view_t role; oc_ace_connection_type_t conn; } oc_ace_subject_view_t; @@ -67,6 +72,15 @@ oc_sec_ace_t *oc_sec_new_ace(oc_ace_subject_type_t type, /** Free an ACE */ void oc_sec_free_ace(oc_sec_ace_t *ace) OC_NONNULL(); +/** Check if ACE has mathing tag */ +bool oc_ace_has_matching_tag(const oc_sec_ace_t *ace, oc_string_view_t tag) + OC_NONNULL(); + +/** Check if ACE has matching subject */ +bool oc_ace_has_matching_subject(const oc_sec_ace_t *ace, + oc_ace_subject_type_t type, + oc_ace_subject_view_t subject) OC_NONNULL(); + /** Find ACE in a list */ oc_sec_ace_t *oc_sec_ace_find_subject(oc_sec_ace_t *ace, oc_ace_subject_type_t type, @@ -94,19 +108,40 @@ oc_ace_res_t *oc_sec_ace_find_resource(oc_ace_res_t *start, /** Encode an ACE to encoder */ void oc_sec_encode_ace(CborEncoder *encoder, const oc_sec_ace_t *sub, - bool to_storage); + bool to_storage) OC_NONNULL(); typedef struct { int aceid; - int32_t permission; // uint16_t or -1 + uint16_t permission; oc_ace_subject_view_t subject; oc_ace_subject_type_t subject_type; const oc_rep_t *resources; const oc_string_t *tag; } oc_sec_ace_decode_t; -bool oc_sec_decode_ace(const oc_rep_t *rep, oc_sec_ace_decode_t *acedecode); +/** Decode representation to struct */ +bool oc_sec_decode_ace(const oc_rep_t *rep, oc_sec_ace_decode_t *acedecode) + OC_NONNULL(2); + +typedef struct +{ + oc_ace_wildcard_t wildcard; + const oc_string_t *href; +#if 0 +#ifdef OC_SERVER + oc_resource_properties_t wc_r; +#endif /* OC_SERVER */ +#endif +} oc_sec_ace_res_decode_t; + +typedef void (*oc_sec_on_decode_ace_resource_fn_t)( + const oc_sec_ace_res_decode_t *aceresdecode, void *user_data) OC_NONNULL(1); + +/** Decode resources object array and invoke decode callback on each resource */ +bool oc_sec_decode_ace_resources(const oc_rep_t *rep, + oc_sec_on_decode_ace_resource_fn_t on_decode, + void *decode_fn_data) OC_NONNULL(2); #ifdef __cplusplus } diff --git a/security/oc_acl.c b/security/oc_acl.c index 3bdd548fb..158f4272d 100644 --- a/security/oc_acl.c +++ b/security/oc_acl.c @@ -283,6 +283,65 @@ oc_sec_acl_default(size_t device) oc_sec_dump_acl(device); } +typedef struct +{ + oc_sec_ace_decode_t *ace_decode; + size_t device; + oc_sec_ace_t *ace; + bool created; + bool created_resource; +} acl_decode_ace_resources_data_t; + +static void +acl_decode_ace_resources(const oc_sec_ace_res_decode_t *aceres_decode, + void *user_data) +{ + acl_decode_ace_resources_data_t *dard = + (acl_decode_ace_resources_data_t *)user_data; + + oc_sec_ace_update_data_t ace_upd = { NULL, false, false }; + if (oc_sec_acl_update_res(dard->ace_decode->subject_type, + dard->ace_decode->subject, dard->ace_decode->aceid, + dard->ace_decode->permission, + oc_string_view2(dard->ace_decode->tag), + oc_string_view2(aceres_decode->href), + aceres_decode->wildcard, dard->device, &ace_upd)) { + dard->ace = ace_upd.ace; + dard->created |= ace_upd.created; + dard->created_resource |= ace_upd.created_resource; + } else { + OC_WRN("failed to create resource(href:%s wildcard:%d)", + aceres_decode->href != NULL ? oc_string(*aceres_decode->href) : "", + aceres_decode->wildcard); + } + +#if 0 + /* The following code block attaches "coap" endpoints to resources linked to + an anon-clear ACE. This logic is being currently disabled to comply with + the SH spec which requires that all vertical resources not expose a "coap" + endpoint. */ +#ifdef OC_SERVER + if (dard->ace_decode->subject_type == OC_SUBJECT_CONN && dard->ace_decode->subject.conn == OC_CONN_ANON_CLEAR) { + if (href) { + oc_resource_t *r = + oc_ri_get_app_resource_by_uri(href, strlen(href), device); + if (r) { + oc_resource_make_public(r); + } + } else { + oc_resource_t *r = oc_ri_get_app_resources(); + while (r != NULL) { + if ((r->properties & aceres_decode->wc_r) == r->properties) { + oc_resource_make_public(r); + } + r = r->next; + } + } + } +#endif /* OC_SERVER */ +#endif +} + bool oc_sec_decode_acl(const oc_rep_t *rep, bool from_storage, size_t device, oc_sec_on_apply_acl_cb_t on_apply_ace_cb, @@ -325,8 +384,8 @@ oc_sec_decode_acl(const oc_rep_t *rep, bool from_storage, size_t device, } break; case OC_REP_OBJECT_ARRAY: { - const oc_rep_t *aclist2 = rep->value.object_array; - while (aclist2 != NULL) { + for (const oc_rep_t *aclist2 = rep->value.object_array; aclist2 != NULL; + aclist2 = aclist2->next) { oc_sec_ace_decode_t ace_decode; memset(&ace_decode, 0, sizeof(oc_sec_ace_decode_t)); ace_decode.aceid = -1; @@ -335,123 +394,34 @@ oc_sec_decode_acl(const oc_rep_t *rep, bool from_storage, size_t device, return false; } - oc_sec_ace_t *upd_ace = NULL; oc_sec_ace_t *replaced_ace = NULL; - bool created = false; - bool created_resource = false; if (ace_decode.aceid != -1 && !acl_unique_aceid(ace_decode.aceid, device)) { replaced_ace = oc_acl_remove_ace_from_device_by_aceid(ace_decode.aceid, device); } - const oc_rep_t *resources = ace_decode.resources; - while (resources != NULL) { - oc_ace_wildcard_t wc = OC_ACE_NO_WC; - const oc_rep_t *resource = resources->value.object; - const oc_string_t *href = NULL; - /* - #ifdef OC_SERVER - oc_resource_properties_t wc_r = 0; - #endif - */ - - while (resource != NULL) { - switch (resource->type) { - case OC_REP_STRING: - if (oc_string_len(resource->name) == 4 && - memcmp(oc_string(resource->name), "href", 4) == 0) { - href = &resource->value.string; - } else if (oc_string_len(resource->name) == 2 && - memcmp(oc_string(resource->name), "wc", 2) == 0) { - if (oc_string(resource->value.string)[0] == '*') { - wc = OC_ACE_WC_ALL; - /* - #ifdef OC_SERVER - wc_r = ~0; - #endif - */ - } - if (oc_string(resource->value.string)[0] == '+') { - wc = OC_ACE_WC_ALL_SECURED; - /* - #ifdef OC_SERVER - wc_r = ~0; - #endif - */ - } - if (oc_string(resource->value.string)[0] == '-') { - wc = OC_ACE_WC_ALL_PUBLIC; - /* - #ifdef OC_SERVER - wc_r = ~OC_DISCOVERABLE; - #endif - */ - } - } - break; - default: - break; - } - - resource = resource->next; - } - - oc_sec_ace_update_data_t ace_upd = { NULL, false, false }; - if (oc_sec_acl_update_res( - ace_decode.subject_type, ace_decode.subject, ace_decode.aceid, - ace_decode.permission, oc_string_view2(ace_decode.tag), - oc_string_view2(href), wc, device, &ace_upd)) { - upd_ace = ace_upd.ace; - created |= ace_upd.created; - created_resource |= ace_upd.created_resource; - } else { - OC_WRN("failed to create resource(href:%s wildcard:%d)", - href != NULL ? oc_string(*href) : "", wc); - } - - /* The following code block attaches "coap" endpoints to - resources linked to an anon-clear ACE. This logic is - being currently disabled to comply with the SH spec - which - requires that all vertical resources not expose a "coap" endpoint. - #ifdef OC_SERVER - if (subject_type == OC_SUBJECT_CONN && - subject.conn == OC_CONN_ANON_CLEAR) { - if (href) { - oc_resource_t *r = - oc_ri_get_app_resource_by_uri(href, strlen(href), - device); if (r) { oc_resource_make_public(r); - } - } else { - oc_resource_t *r = oc_ri_get_app_resources(); - while (r != NULL) { - if ((r->properties & wc_r) == r->properties) { - oc_resource_make_public(r); - } - r = r->next; - } - } - } - #endif - */ - resources = resources->next; + acl_decode_ace_resources_data_t dard = { + .ace_decode = &ace_decode, + .device = device, + }; + if (!oc_sec_decode_ace_resources(ace_decode.resources, + acl_decode_ace_resources, &dard)) { + OC_ERR("oc_acl: error decoding ACE resources"); + return false; } - if (on_apply_ace_cb != NULL) { - if (upd_ace != NULL) { - oc_sec_on_apply_acl_data_t acl_data = { g_aclist[device].rowneruuid, - upd_ace, replaced_ace, - created, created_resource }; - on_apply_ace_cb(acl_data, on_apply_ace_data); - } + if (on_apply_ace_cb != NULL && dard.ace != NULL) { + oc_sec_on_apply_acl_data_t acl_data = { g_aclist[device].rowneruuid, + dard.ace, replaced_ace, + dard.created, + dard.created_resource }; + on_apply_ace_cb(acl_data, on_apply_ace_data); } if (replaced_ace) { oc_sec_free_ace(replaced_ace); } - - aclist2 = aclist2->next; } } break; default: diff --git a/security/oc_acl_util.c b/security/oc_acl_util.c index 6b38a9d8a..4ece19358 100644 --- a/security/oc_acl_util.c +++ b/security/oc_acl_util.c @@ -155,7 +155,7 @@ get_role_permissions(const oc_sec_cred_t *role_cred, do { oc_ace_subject_view_t role_subject = { .role = - (struct oc_ace_subject_role_view_t){ + (oc_ace_subject_role_view_t){ .role = oc_string_view2(&role_cred->role.role), .authority = oc_string_view2(&role_cred->role.authority), } diff --git a/security/oc_tls.c b/security/oc_tls.c index 2911ddea4..92949f3d8 100644 --- a/security/oc_tls.c +++ b/security/oc_tls.c @@ -319,7 +319,7 @@ oc_mbedtls_debug(void *ctx, int level, const char *file, int line, { (void)ctx; (void)level; - // OC_DBG("mbedtls_log: %s:%04d: %s", file, line, str); + OC_DBG("mbedtls_log: %s:%04d: %s", file, line, str); (void)file; (void)line; (void)str; diff --git a/security/unittest/acetest.cpp b/security/unittest/acetest.cpp index 8761378f7..5c892ea34 100644 --- a/security/unittest/acetest.cpp +++ b/security/unittest/acetest.cpp @@ -28,7 +28,9 @@ #include "tests/gtest/RepPool.h" #include "util/oc_list.h" +#include #include +#include class TestACE : public testing::Test { public: @@ -43,6 +45,55 @@ class TestACE : public testing::Test { } void TearDown() override { oc_log_set_level(OC_LOG_LEVEL_INFO); } + + static void checkEncodedACE( + const oc_sec_ace_t *ace, const oc_rep_t *rep, + std::vector expResources = {}) + { + ASSERT_NE(nullptr, rep); + OC_DBG("payload: %s", oc::RepPool::GetJson(rep, true).data()); + oc_sec_ace_decode_t decoded{}; + ASSERT_TRUE(oc_sec_decode_ace(rep, &decoded)); + EXPECT_EQ(ace->aceid, decoded.aceid); + EXPECT_TRUE( + oc_ace_has_matching_subject(ace, decoded.subject_type, decoded.subject)); + EXPECT_EQ(ace->permission, decoded.permission); + EXPECT_TRUE(oc_ace_has_matching_tag(ace, oc_string_view2(decoded.tag))); + if (expResources.empty()) { + EXPECT_EQ(nullptr, decoded.resources); + } else { + ASSERT_NE(nullptr, decoded.resources); + std::vector resources{}; + ASSERT_TRUE(oc_sec_decode_ace_resources( + decoded.resources, + [](const oc_sec_ace_res_decode_t *aceresdecode, void *user_data) { + auto *resources = + static_cast *>(user_data); + resources->push_back(*aceresdecode); + }, + &resources)); + ASSERT_EQ(expResources.size(), resources.size()); + for (auto resource : resources) { + auto it = std::find_if(expResources.begin(), expResources.end(), + [&resource](const oc_ace_res_t *aceres) { + return aceres->wildcard == resource.wildcard && + oc_string_is_equal(&aceres->href, + resource.href); + }); + ASSERT_NE(it, expResources.end()); + expResources.erase(it); + } + EXPECT_TRUE(expResources.empty()); + } + } + + static void checkInvalidPayload(const oc_rep_t *rep) + { + ASSERT_NE(nullptr, rep); + OC_DBG("payload: %s", oc::RepPool::GetJson(rep, true).data()); + oc_sec_ace_decode_t decoded{}; + EXPECT_FALSE(oc_sec_decode_ace(rep, &decoded)); + } }; TEST_F(TestACE, NewUUID) @@ -293,6 +344,18 @@ TEST_F(TestACE, WildcardToString) oc_ace_wildcard_to_string(OC_ACE_WC_ALL_SECURED).data); } +TEST_F(TestACE, WildcardFromString) +{ + EXPECT_EQ(-1, oc_ace_wildcard_from_string(OC_STRING_VIEW(""))); + + EXPECT_EQ(OC_ACE_WC_ALL, + oc_ace_wildcard_from_string(OC_STRING_VIEW(OC_ACE_WC_ALL_STR))); + EXPECT_EQ(OC_ACE_WC_ALL_PUBLIC, oc_ace_wildcard_from_string( + OC_STRING_VIEW(OC_ACE_WC_ALL_PUBLIC_STR))); + EXPECT_EQ(OC_ACE_WC_ALL_SECURED, oc_ace_wildcard_from_string(OC_STRING_VIEW( + OC_ACE_WC_ALL_SECURED_STR))); +} + TEST_F(TestACE, ConnectionTypeToString) { EXPECT_EQ(nullptr, oc_ace_connection_type_to_string( @@ -305,7 +368,7 @@ TEST_F(TestACE, ConnectionTypeToString) oc_ace_connection_type_to_string(OC_CONN_ANON_CLEAR).data); } -TEST_F(TestACE, FromStringToConnectionType) +TEST_F(TestACE, ConnectionTypeFromString) { EXPECT_EQ(-1, oc_ace_connection_type_from_string(OC_STRING_VIEW(""))); @@ -332,10 +395,7 @@ TEST_F(TestACE, EncodeUUID) oc_rep_end_root_object(); ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); - oc::oc_rep_unique_ptr rep = pool.ParsePayload(); - ASSERT_NE(nullptr, rep.get()); - OC_DBG("payload: %s", oc::RepPool::GetJson(rep.get(), true).data()); - // TODO: decode and check + checkEncodedACE(ace, pool.ParsePayload().get()); oc_sec_free_ace(ace); } @@ -349,6 +409,7 @@ TEST_F(TestACE, EncodeRole) oc_sec_ace_t *ace = oc_sec_new_ace(OC_SUBJECT_ROLE, subject_role, 13, OC_PERM_RETRIEVE | OC_PERM_UPDATE, OC_STRING_VIEW_NULL); + ASSERT_NE(ace, nullptr); oc::RepPool pool{}; oc_rep_begin_root_object(); @@ -356,10 +417,7 @@ TEST_F(TestACE, EncodeRole) oc_rep_end_root_object(); ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); - oc::oc_rep_unique_ptr rep = pool.ParsePayload(); - ASSERT_NE(nullptr, rep.get()); - OC_DBG("payload: %s", oc::RepPool::GetJson(rep.get(), true).data()); - // TODO: decode and check + checkEncodedACE(ace, pool.ParsePayload().get()); oc_sec_free_ace(ace); } @@ -370,6 +428,7 @@ TEST_F(TestACE, EncodeAnonConn) anon_conn.conn = OC_CONN_ANON_CLEAR; oc_sec_ace_t *ace = oc_sec_new_ace(OC_SUBJECT_CONN, anon_conn, 1, OC_PERM_NONE, OC_STRING_VIEW_NULL); + ASSERT_NE(ace, nullptr); oc::RepPool pool{}; oc_rep_begin_root_object(); @@ -377,10 +436,7 @@ TEST_F(TestACE, EncodeAnonConn) oc_rep_end_root_object(); ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); - oc::oc_rep_unique_ptr rep = pool.ParsePayload(); - ASSERT_NE(nullptr, rep.get()); - OC_DBG("payload: %s", oc::RepPool::GetJson(rep.get(), true).data()); - // TODO: decode and check + checkEncodedACE(ace, pool.ParsePayload().get()); oc_sec_free_ace(ace); } @@ -391,6 +447,7 @@ TEST_F(TestACE, EncodeCryptConn) crypt_conn.conn = OC_CONN_AUTH_CRYPT; oc_sec_ace_t *ace = oc_sec_new_ace(OC_SUBJECT_CONN, crypt_conn, 2, OC_PERM_CREATE, OC_STRING_VIEW_NULL); + ASSERT_NE(ace, nullptr); oc::RepPool pool{}; oc_rep_begin_root_object(); @@ -398,10 +455,7 @@ TEST_F(TestACE, EncodeCryptConn) oc_rep_end_root_object(); ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); - oc::oc_rep_unique_ptr rep = pool.ParsePayload(); - ASSERT_NE(nullptr, rep.get()); - OC_DBG("payload: %s", oc::RepPool::GetJson(rep.get(), true).data()); - // TODO: decode and check + checkEncodedACE(ace, pool.ParsePayload().get()); oc_sec_free_ace(ace); } @@ -413,6 +467,7 @@ TEST_F(TestACE, EncodeWithResources) subject_role.role = { testRole, {} }; oc_sec_ace_t *ace = oc_sec_new_ace(OC_SUBJECT_ROLE, subject_role, 17, OC_PERM_NOTIFY, OC_STRING_VIEW_NULL); + ASSERT_NE(ace, nullptr); // href resource auto href = OC_STRING_VIEW("/uri/1"); @@ -421,16 +476,16 @@ TEST_F(TestACE, EncodeWithResources) ASSERT_NE(nullptr, res_data.res); // wc-all secured resource - res_data = oc_sec_ace_get_or_add_res(ace, OC_STRING_VIEW_NULL, - OC_ACE_WC_ALL_SECURED, true); - ASSERT_TRUE(res_data.created); - ASSERT_NE(nullptr, res_data.res); + auto wc_all_res_data = oc_sec_ace_get_or_add_res(ace, OC_STRING_VIEW_NULL, + OC_ACE_WC_ALL_SECURED, true); + ASSERT_TRUE(wc_all_res_data.created); + ASSERT_NE(nullptr, wc_all_res_data.res); // wc-all public resource - res_data = oc_sec_ace_get_or_add_res(ace, OC_STRING_VIEW_NULL, - OC_ACE_WC_ALL_PUBLIC, true); - ASSERT_TRUE(res_data.created); - ASSERT_NE(nullptr, res_data.res); + auto wc_all_public_res_data = oc_sec_ace_get_or_add_res( + ace, OC_STRING_VIEW_NULL, OC_ACE_WC_ALL_PUBLIC, true); + ASSERT_TRUE(wc_all_public_res_data.created); + ASSERT_NE(nullptr, wc_all_public_res_data.res); oc::RepPool pool{}; oc_rep_begin_root_object(); @@ -438,10 +493,10 @@ TEST_F(TestACE, EncodeWithResources) oc_rep_end_root_object(); ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); - oc::oc_rep_unique_ptr rep = pool.ParsePayload(); - ASSERT_NE(nullptr, rep.get()); - OC_DBG("payload: %s", oc::RepPool::GetJson(rep.get(), true).data()); - // TODO: decode and check + std::vector expResources = { + res_data.res, wc_all_res_data.res, wc_all_public_res_data.res + }; + checkEncodedACE(ace, pool.ParsePayload().get(), expResources); oc_sec_free_ace(ace); } @@ -452,6 +507,7 @@ TEST_F(TestACE, EncodeWithWCAllResource) anon_conn.conn = OC_CONN_ANON_CLEAR; oc_sec_ace_t *ace = oc_sec_new_ace(OC_SUBJECT_CONN, anon_conn, 1, OC_PERM_CREATE, OC_STRING_VIEW_NULL); + ASSERT_NE(ace, nullptr); // wc-all resource auto res_data = @@ -465,12 +521,60 @@ TEST_F(TestACE, EncodeWithWCAllResource) oc_rep_end_root_object(); ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); - oc::oc_rep_unique_ptr rep = pool.ParsePayload(); - ASSERT_NE(nullptr, rep.get()); - OC_DBG("payload: %s", oc::RepPool::GetJson(rep.get(), true).data()); - // TODO: decode and check + std::vector expResources = { res_data.res }; + checkEncodedACE(ace, pool.ParsePayload().get(), expResources); oc_sec_free_ace(ace); } +TEST_F(TestACE, Decode_FailInvalidStringProperty) +{ + oc::RepPool pool{}; + oc_rep_begin_root_object(); + oc_rep_set_text_string(root, plgd, "dev"); + oc_rep_end_root_object(); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + + checkInvalidPayload(pool.ParsePayload().get()); +} + +TEST_F(TestACE, Decode_FailInvalidIntProperty) +{ + oc::RepPool pool{}; + oc_rep_begin_root_object(); + oc_rep_set_int(root, plgd.dev, 42); + oc_rep_end_root_object(); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + + checkInvalidPayload(pool.ParsePayload().get()); +} + +// permission is uint16_t type +TEST_F(TestACE, Decode_FailInvalidPermission) +{ + oc::RepPool pool{}; + oc_rep_begin_root_object(); + oc_rep_set_int(root, permission, std::numeric_limits::max()); + oc_rep_end_root_object(); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + + checkInvalidPayload(pool.ParsePayload().get()); +} + +#if INT_MAX < INT64_MAX + +// aceid is int type +TEST_F(TestACE, Decode_FailInvalidAceid) +{ + oc::RepPool pool{}; + oc_rep_begin_root_object(); + oc_rep_set_int(root, aceid, std::numeric_limits::max()); + oc_rep_end_root_object(); + ASSERT_EQ(CborNoError, oc_rep_get_cbor_errno()); + + checkInvalidPayload(pool.ParsePayload().get()); +} + +#endif /* INT_MAX < INT64_MAX */ + #endif /* OC_SECURITY */