// Copyright 2015 The Weave 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/macaroon_caveat.h" #include "src/macaroon_caveat_internal.h" #include #include "src/crypto_hmac.h" #include "src/macaroon.h" #include "src/macaroon_context.h" #include "src/macaroon_encoding.h" static bool is_valid_caveat_type_(UwMacaroonCaveatType type) { switch (type) { case kUwMacaroonCaveatTypeNonce: case kUwMacaroonCaveatTypeScope: case kUwMacaroonCaveatTypeExpirationAbsolute: case kUwMacaroonCaveatTypeTTL1Hour: case kUwMacaroonCaveatTypeTTL24Hour: case kUwMacaroonCaveatTypeDelegationTimestamp: case kUwMacaroonCaveatTypeDelegateeUser: case kUwMacaroonCaveatTypeDelegateeApp: case kUwMacaroonCaveatTypeAppCommandsOnly: case kUwMacaroonCaveatTypeDelegateeService: case kUwMacaroonCaveatTypeBleSessionID: case kUwMacaroonCaveatTypeLanSessionID: case kUwMacaroonCaveatTypeClientAuthorizationTokenV1: case kUwMacaroonCaveatTypeServerAuthenticationTokenV1: return true; } return false; } static bool is_valid_scope_type_(UwMacaroonCaveatScopeType type) { switch (type) { case kUwMacaroonCaveatScopeTypeOwner: case kUwMacaroonCaveatScopeTypeManager: case kUwMacaroonCaveatScopeTypeUser: case kUwMacaroonCaveatScopeTypeViewer: return true; } return false; } static bool create_caveat_no_value_(UwMacaroonCaveatType type, uint8_t* buffer, size_t buffer_size, UwMacaroonCaveat* new_caveat) { // (buffer_size == 0 || get_buffer_size_() > buffer_size) will conver the case // that get_buffer_size_() returns 0 (for errors), so there is no need to // check get_buffer_size_() == 0 again. if (buffer == NULL || buffer_size == 0 || new_caveat == NULL || uw_macaroon_caveat_creation_get_buffsize_(type, 0) > buffer_size) { return false; } size_t encoded_str_len = 0, total_str_len = 0; if (!uw_macaroon_encoding_encode_uint_((uint32_t)type, buffer, buffer_size, &encoded_str_len)) { return false; } total_str_len += encoded_str_len; new_caveat->bytes = buffer; new_caveat->num_bytes = total_str_len; return true; } static bool create_caveat_uint_value_(UwMacaroonCaveatType type, uint32_t unsigned_int, uint8_t* buffer, size_t buffer_size, UwMacaroonCaveat* new_caveat) { if (buffer == NULL || buffer_size == 0 || new_caveat == NULL || uw_macaroon_caveat_creation_get_buffsize_(type, 0) > buffer_size) { return false; } size_t encoded_str_len = 0, total_str_len = 0; if (!uw_macaroon_encoding_encode_uint_((uint32_t)type, buffer, buffer_size, &encoded_str_len)) { return false; } total_str_len += encoded_str_len; if (!uw_macaroon_encoding_encode_uint_(unsigned_int, buffer + total_str_len, buffer_size - total_str_len, &encoded_str_len)) { return false; } total_str_len += encoded_str_len; new_caveat->bytes = buffer; new_caveat->num_bytes = total_str_len; return true; } static bool create_caveat_bstr_value_(UwMacaroonCaveatType type, const uint8_t* str, size_t str_len, uint8_t* buffer, size_t buffer_size, UwMacaroonCaveat* new_caveat) { if ((str == NULL && str_len != 0) || buffer == NULL || buffer_size == 0 || new_caveat == NULL || uw_macaroon_caveat_creation_get_buffsize_(type, str_len) > buffer_size) { return false; } size_t encoded_str_len = 0, total_str_len = 0; if (!uw_macaroon_encoding_encode_uint_((uint32_t)type, buffer, buffer_size, &encoded_str_len)) { return false; } total_str_len += encoded_str_len; if (!uw_macaroon_encoding_encode_byte_str_( str, str_len, buffer + total_str_len, buffer_size - total_str_len, &encoded_str_len)) { return false; } total_str_len += encoded_str_len; new_caveat->bytes = buffer; new_caveat->num_bytes = total_str_len; return true; } size_t uw_macaroon_caveat_creation_get_buffsize_(UwMacaroonCaveatType type, size_t str_len) { switch (type) { // No values case kUwMacaroonCaveatTypeTTL1Hour: case kUwMacaroonCaveatTypeTTL24Hour: case kUwMacaroonCaveatTypeAppCommandsOnly: case kUwMacaroonCaveatTypeBleSessionID: return UW_MACAROON_ENCODING_MAX_UINT_CBOR_LEN; // Unsigned integers case kUwMacaroonCaveatTypeScope: case kUwMacaroonCaveatTypeExpirationAbsolute: case kUwMacaroonCaveatTypeDelegationTimestamp: return 2 * UW_MACAROON_ENCODING_MAX_UINT_CBOR_LEN; // Byte strings case kUwMacaroonCaveatTypeNonce: case kUwMacaroonCaveatTypeDelegateeUser: case kUwMacaroonCaveatTypeDelegateeApp: case kUwMacaroonCaveatTypeDelegateeService: case kUwMacaroonCaveatTypeLanSessionID: case kUwMacaroonCaveatTypeClientAuthorizationTokenV1: case kUwMacaroonCaveatTypeServerAuthenticationTokenV1: return str_len + UW_MACAROON_ENCODING_MAX_UINT_CBOR_LEN; default: return 0; // For errors } } bool uw_macaroon_caveat_create_nonce_(const uint8_t* nonce, size_t nonce_size, uint8_t* buffer, size_t buffer_size, UwMacaroonCaveat* new_caveat) { return create_caveat_bstr_value_(kUwMacaroonCaveatTypeNonce, nonce, nonce_size, buffer, buffer_size, new_caveat); } bool uw_macaroon_caveat_create_scope_(UwMacaroonCaveatScopeType scope, uint8_t* buffer, size_t buffer_size, UwMacaroonCaveat* new_caveat) { if (!is_valid_scope_type_(scope)) { return false; } return create_caveat_uint_value_(kUwMacaroonCaveatTypeScope, scope, buffer, buffer_size, new_caveat); } bool uw_macaroon_caveat_create_expiration_absolute_( uint32_t expiration_time, uint8_t* buffer, size_t buffer_size, UwMacaroonCaveat* new_caveat) { return create_caveat_uint_value_(kUwMacaroonCaveatTypeExpirationAbsolute, expiration_time, buffer, buffer_size, new_caveat); } bool uw_macaroon_caveat_create_ttl_1_hour_(uint8_t* buffer, size_t buffer_size, UwMacaroonCaveat* new_caveat) { return create_caveat_no_value_(kUwMacaroonCaveatTypeTTL1Hour, buffer, buffer_size, new_caveat); } bool uw_macaroon_caveat_create_ttl_24_hour_(uint8_t* buffer, size_t buffer_size, UwMacaroonCaveat* new_caveat) { return create_caveat_no_value_(kUwMacaroonCaveatTypeTTL24Hour, buffer, buffer_size, new_caveat); } bool uw_macaroon_caveat_create_delegation_timestamp_( uint32_t timestamp, uint8_t* buffer, size_t buffer_size, UwMacaroonCaveat* new_caveat) { return create_caveat_uint_value_(kUwMacaroonCaveatTypeDelegationTimestamp, timestamp, buffer, buffer_size, new_caveat); } bool uw_macaroon_caveat_create_delegatee_user_(const uint8_t* id_str, size_t id_str_len, uint8_t* buffer, size_t buffer_size, UwMacaroonCaveat* new_caveat) { return create_caveat_bstr_value_(kUwMacaroonCaveatTypeDelegateeUser, id_str, id_str_len, buffer, buffer_size, new_caveat); } bool uw_macaroon_caveat_create_delegatee_app_(const uint8_t* id_str, size_t id_str_len, uint8_t* buffer, size_t buffer_size, UwMacaroonCaveat* new_caveat) { return create_caveat_bstr_value_(kUwMacaroonCaveatTypeDelegateeApp, id_str, id_str_len, buffer, buffer_size, new_caveat); } bool uw_macaroon_caveat_create_app_commands_only_( uint8_t* buffer, size_t buffer_size, UwMacaroonCaveat* new_caveat) { return create_caveat_no_value_(kUwMacaroonCaveatTypeAppCommandsOnly, buffer, buffer_size, new_caveat); } bool uw_macaroon_caveat_create_delegatee_service_( const uint8_t* id_str, size_t id_str_len, uint8_t* buffer, size_t buffer_size, UwMacaroonCaveat* new_caveat) { return create_caveat_bstr_value_(kUwMacaroonCaveatTypeDelegateeService, id_str, id_str_len, buffer, buffer_size, new_caveat); } bool uw_macaroon_caveat_create_ble_session_id_(uint8_t* buffer, size_t buffer_size, UwMacaroonCaveat* new_caveat) { return create_caveat_no_value_(kUwMacaroonCaveatTypeBleSessionID, buffer, buffer_size, new_caveat); } bool uw_macaroon_caveat_create_lan_session_id_(const uint8_t* session_id, size_t session_id_len, uint8_t* buffer, size_t buffer_size, UwMacaroonCaveat* new_caveat) { return create_caveat_bstr_value_(kUwMacaroonCaveatTypeLanSessionID, session_id, session_id_len, buffer, buffer_size, new_caveat); } bool uw_macaroon_caveat_create_client_authorization_token_( const uint8_t* str, size_t str_len, uint8_t* buffer, size_t buffer_size, UwMacaroonCaveat* new_caveat) { if (str_len == 0) { return create_caveat_no_value_( kUwMacaroonCaveatTypeClientAuthorizationTokenV1, buffer, buffer_size, new_caveat); } return create_caveat_bstr_value_( kUwMacaroonCaveatTypeClientAuthorizationTokenV1, str, str_len, buffer, buffer_size, new_caveat); } bool uw_macaroon_caveat_create_server_authentication_token_( const uint8_t* str, size_t str_len, uint8_t* buffer, size_t buffer_size, UwMacaroonCaveat* new_caveat) { if (str_len == 0) { return create_caveat_no_value_( kUwMacaroonCaveatTypeServerAuthenticationTokenV1, buffer, buffer_size, new_caveat); } return create_caveat_bstr_value_( kUwMacaroonCaveatTypeServerAuthenticationTokenV1, str, str_len, buffer, buffer_size, new_caveat); } bool uw_macaroon_caveat_get_type_(const UwMacaroonCaveat* caveat, UwMacaroonCaveatType* type) { if (caveat == NULL || type == NULL) { return false; } uint32_t unsigned_int; if (!uw_macaroon_encoding_decode_uint_(caveat->bytes, caveat->num_bytes, &unsigned_int)) { return false; } *type = (UwMacaroonCaveatType)unsigned_int; return is_valid_caveat_type_(*type); } /* === Some internal functions defined in macaroon_caveat_internal.h === */ bool uw_macaroon_caveat_sign_(const uint8_t* key, size_t key_len, const UwMacaroonContext* context, const UwMacaroonCaveat* caveat, uint8_t* mac_tag, size_t mac_tag_size) { if (key == NULL || key_len == 0 || context == NULL || caveat == NULL || mac_tag == NULL || mac_tag_size == 0) { return false; } UwMacaroonCaveatType caveat_type; if (!uw_macaroon_caveat_get_type_(caveat, &caveat_type) || !is_valid_caveat_type_(caveat_type)) { return false; } // Need to encode the whole caveat as a byte string and then sign it // If there is no additional value from the context, just compute the HMAC on // the current byte string. uint8_t bstr_cbor_prefix[UW_MACAROON_ENCODING_MAX_UINT_CBOR_LEN] = {0}; size_t bstr_cbor_prefix_len = 0; if (caveat_type != kUwMacaroonCaveatTypeBleSessionID) { if (!uw_macaroon_encoding_encode_byte_str_len_( (uint32_t)(caveat->num_bytes), bstr_cbor_prefix, sizeof(bstr_cbor_prefix), &bstr_cbor_prefix_len)) { return false; } UwCryptoHmacMsg messages[] = { {bstr_cbor_prefix, bstr_cbor_prefix_len}, {caveat->bytes, caveat->num_bytes}, }; return uw_crypto_hmac_(key, key_len, messages, sizeof(messages) / sizeof(messages[0]), mac_tag, mac_tag_size); } // If there is additional value from the context. if (context->ble_session_id == NULL || context->ble_session_id_len == 0) { return false; } // The length here includes the length of the BLE session ID string. if (!uw_macaroon_encoding_encode_byte_str_len_( (uint32_t)(context->ble_session_id_len + caveat->num_bytes), bstr_cbor_prefix, sizeof(bstr_cbor_prefix), &bstr_cbor_prefix_len)) { return false; } uint8_t value_cbor_prefix[UW_MACAROON_ENCODING_MAX_UINT_CBOR_LEN] = {0}; size_t value_cbor_prefix_len = 0; if (!uw_macaroon_encoding_encode_byte_str_len_( (uint32_t)(context->ble_session_id_len), value_cbor_prefix, sizeof(value_cbor_prefix), &value_cbor_prefix_len)) { return false; } UwCryptoHmacMsg messages[] = { {bstr_cbor_prefix, bstr_cbor_prefix_len}, {caveat->bytes, caveat->num_bytes}, {value_cbor_prefix, value_cbor_prefix_len}, {context->ble_session_id, context->ble_session_id_len}, }; return uw_crypto_hmac_(key, key_len, messages, sizeof(messages) / sizeof(messages[0]), mac_tag, mac_tag_size); } static bool update_and_check_expiration_time( uint32_t current_time, uint32_t new_expiration_time, UwMacaroonValidationResult* result) { if (result->expiration_time > new_expiration_time) { result->expiration_time = new_expiration_time; } return current_time <= result->expiration_time; } static bool update_delegatee_list(UwMacaroonCaveatType caveat_type, const UwMacaroonCaveat* caveat, uint32_t issued_time, UwMacaroonValidationResult* result) { if (result->num_delegatees >= MAX_NUM_DELEGATEES || issued_time == 0) { return false; } UwMacaroonDelegateeType delegatee_type = kUwMacaroonDelegateeTypeNone; switch (caveat_type) { case kUwMacaroonCaveatTypeDelegateeUser: delegatee_type = kUwMacaroonDelegateeTypeUser; break; case kUwMacaroonCaveatTypeDelegateeApp: delegatee_type = kUwMacaroonDelegateeTypeApp; break; case kUwMacaroonCaveatTypeDelegateeService: delegatee_type = kUwMacaroonDelegateeTypeService; break; default: return false; } if (caveat_type != kUwMacaroonCaveatTypeDelegateeUser) { for (size_t i = 0; i < result->num_delegatees; i++) { // There must have at most one DelegateeApp or DelegateeService if (result->delegatees[i].type == delegatee_type) { return false; } } } if (!uw_macaroon_caveat_get_value_bstr_( caveat, &(result->delegatees[result->num_delegatees].id), &(result->delegatees[result->num_delegatees].id_len))) { return false; } result->delegatees[result->num_delegatees].type = delegatee_type; result->delegatees[result->num_delegatees].timestamp = issued_time; result->num_delegatees++; return true; } bool uw_macaroon_caveat_validate_(const UwMacaroonCaveat* caveat, const UwMacaroonContext* context, UwMacaroonValidationState* state, UwMacaroonValidationResult* result) { if (caveat == NULL || context == NULL || state == NULL || result == NULL) { return false; } uint32_t expiration_time = 0; uint32_t issued_time = 0; uint32_t scope = UW_MACAROON_CAVEAT_SCOPE_LOWEST_POSSIBLE; UwMacaroonCaveatType caveat_type; if (!uw_macaroon_caveat_get_type_(caveat, &caveat_type)) { return false; } switch (caveat_type) { // The types that always validate case kUwMacaroonCaveatTypeClientAuthorizationTokenV1: case kUwMacaroonCaveatTypeServerAuthenticationTokenV1: case kUwMacaroonCaveatTypeNonce: case kUwMacaroonCaveatTypeBleSessionID: return true; case kUwMacaroonCaveatTypeDelegationTimestamp: if (!uw_macaroon_caveat_get_value_uint_(caveat, &issued_time) || issued_time < state->issued_time) { return false; } state->issued_time = issued_time; return true; case kUwMacaroonCaveatTypeTTL1Hour: if (state->issued_time == 0) { return false; } return update_and_check_expiration_time( context->current_time, state->issued_time + 60 * 60, result); case kUwMacaroonCaveatTypeTTL24Hour: if (state->issued_time == 0) { return false; } return update_and_check_expiration_time( context->current_time, state->issued_time + 24 * 60 * 60, result); // Need to create a list of delegatees case kUwMacaroonCaveatTypeDelegateeUser: case kUwMacaroonCaveatTypeDelegateeApp: case kUwMacaroonCaveatTypeDelegateeService: return update_delegatee_list(caveat_type, caveat, state->issued_time, result); // Time related caveats case kUwMacaroonCaveatTypeExpirationAbsolute: if (!uw_macaroon_caveat_get_value_uint_(caveat, &expiration_time)) { return false; } return update_and_check_expiration_time(context->current_time, expiration_time, result); // The caveats that update the values of the result object case kUwMacaroonCaveatTypeScope: if (!uw_macaroon_caveat_get_value_uint_(caveat, &scope) || // Larger value means less priviledge scope > UW_MACAROON_CAVEAT_SCOPE_LOWEST_POSSIBLE) { return false; } if (scope > (uint32_t)(result->granted_scope)) { result->granted_scope = (UwMacaroonCaveatScopeType)scope; } return true; case kUwMacaroonCaveatTypeAppCommandsOnly: result->weave_app_restricted = true; return true; case kUwMacaroonCaveatTypeLanSessionID: return uw_macaroon_caveat_get_value_bstr_( caveat, &(result->lan_session_id), &(result->lan_session_id_len)); } return false; } bool uw_macaroon_caveat_get_value_uint_(const UwMacaroonCaveat* caveat, uint32_t* unsigned_int) { if (caveat == NULL || unsigned_int == NULL) { return false; } UwMacaroonCaveatType type; if (!uw_macaroon_caveat_get_type_(caveat, &type)) { return false; } if (type != kUwMacaroonCaveatTypeScope && type != kUwMacaroonCaveatTypeExpirationAbsolute && type != kUwMacaroonCaveatTypeDelegationTimestamp) { // Wrong type return false; } // Skip the portion for CBOR type size_t offset; if (!uw_macaroon_encoding_get_item_len_(caveat->bytes, caveat->num_bytes, &offset)) { return false; } return uw_macaroon_encoding_decode_uint_( caveat->bytes + offset, caveat->num_bytes - offset, unsigned_int); } bool uw_macaroon_caveat_get_value_bstr_(const UwMacaroonCaveat* caveat, const uint8_t** str, size_t* str_len) { if (caveat == NULL || str == NULL || str_len == NULL) { return false; } UwMacaroonCaveatType type; if (!uw_macaroon_caveat_get_type_(caveat, &type)) { return false; } if (type != kUwMacaroonCaveatTypeNonce && type != kUwMacaroonCaveatTypeDelegateeUser && type != kUwMacaroonCaveatTypeDelegateeApp && type != kUwMacaroonCaveatTypeDelegateeService && type != kUwMacaroonCaveatTypeLanSessionID && type != kUwMacaroonCaveatTypeClientAuthorizationTokenV1 && type != kUwMacaroonCaveatTypeServerAuthenticationTokenV1) { // Wrong type return false; } size_t offset; if (!uw_macaroon_encoding_get_item_len_(caveat->bytes, caveat->num_bytes, &offset)) { return false; } return uw_macaroon_encoding_decode_byte_str_( caveat->bytes + offset, caveat->num_bytes - offset, str, str_len); } bool uw_macaroon_caveat_init_validation_state_( UwMacaroonValidationState* state) { if (state == NULL) { return false; } state->issued_time = 0; return true; }